Bug 578700 - BinaryData ArrayType method implementations. r=nmatsakis
authorNikhil Marathe <nsm.nikhil@gmail.com>
Thu, 25 Jul 2013 17:59:59 -0700
changeset 152368 13b28328f0106d87e8ed79858abaf9d779056a67
parent 152367 43d1eada77d636ac906a4d9e9c4788896252496e
child 152369 d5cab52418df1188955ff7a5dcb6961613545600
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnmatsakis
bugs578700
milestone25.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 578700 - BinaryData ArrayType method implementations. r=nmatsakis
js/src/builtin/BinaryData.cpp
js/src/builtin/BinaryData.h
js/src/js.msg
js/src/jsprototypes.h
js/src/tests/ecma_6/BinaryData/arraytype.js
js/src/tests/ecma_6/BinaryData/memory.js
js/src/vm/GlobalObject.h
--- a/js/src/builtin/BinaryData.cpp
+++ b/js/src/builtin/BinaryData.cpp
@@ -76,16 +76,27 @@ ReportTypeError(JSContext *cx, Value fro
 static bool
 ReportTypeError(JSContext *cx, Value fromValue, HandleObject exemplar)
 {
     RootedValue v(cx, ObjectValue(*exemplar));
     ReportTypeError(cx, fromValue, ToString<CanGC>(cx, v));
     return false;
 }
 
+static int32_t
+Clamp(int32_t value, int32_t min, int32_t max)
+{
+    JS_ASSERT(min < max);
+    if (value < min)
+        return min;
+    if (value > max)
+        return max;
+    return value;
+}
+
 static inline bool
 IsNumericType(HandleObject type)
 {
     return type && &NumericTypeClasses[NUMERICTYPE_UINT8] <= type->getClass() &&
                    type->getClass() <= &NumericTypeClasses[NUMERICTYPE_FLOAT64];
 }
 
 static inline bool
@@ -636,19 +647,25 @@ ArrayType::create(JSContext *cx, HandleO
         SetupAndGetPrototypeObjectForComplexTypeInstance(cx, arrayTypeGlobal));
 
     if (!prototypeObj)
         return NULL;
 
     if (!LinkConstructorAndPrototype(cx, obj, prototypeObj))
         return NULL;
 
-    if (!JS_DefineFunction(cx, prototypeObj, "fill", BinaryArray::fill, 1, 0))
+    JSFunction *fillFun = DefineFunctionWithReserved(cx, prototypeObj, "fill", BinaryArray::fill, 1, 0);
+    if (!fillFun)
         return NULL;
 
+    // This is important
+    // so that A.prototype.fill.call(b, val)
+    // where b.type != A raises an error
+    SetFunctionNativeReserved(fillFun, 0, ObjectValue(*obj));
+
     RootedId id(cx, NON_INTEGER_ATOM_TO_JSID(cx->names().length));
     unsigned flags = JSPROP_SHARED | JSPROP_GETTER | JSPROP_PERMANENT;
 
     RootedObject global(cx, cx->compartment()->maybeGlobal());
     JSObject *getter =
         NewFunction(cx, NullPtr(), BinaryArray::lengthGetter,
                     0, JSFunction::NATIVE_FUN, global, NullPtr());
     if (!getter)
@@ -697,23 +714,103 @@ ArrayType::construct(JSContext *cx, unsi
         return false;
     args.rval().setObject(*obj);
     return true;
 }
 
 JSBool
 DataInstanceUpdate(JSContext *cx, unsigned argc, Value *vp)
 {
-    return false;
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (args.length() < 1) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage,
+                             NULL, JSMSG_MORE_ARGS_NEEDED,
+                             "update()", "0", "s");
+        return false;
+    }
+
+    RootedObject thisObj(cx, args.thisv().toObjectOrNull());
+    if (!IsBlock(thisObj)) {
+        ReportTypeError(cx, ObjectValue(*thisObj), "BinaryData block");
+        return false;
+    }
+
+    RootedValue val(cx, args[0]);
+    uint8_t *memory = (uint8_t*) thisObj->getPrivate();
+    RootedObject type(cx, GetType(thisObj));
+    if (!ConvertAndCopyTo(cx, type, val, memory)) {
+        ReportTypeError(cx, val, type);
+        return false;
+    }
+
+    args.rval().setUndefined();
+    return true;
+}
+
+static bool
+FillBinaryArrayWithValue(JSContext *cx, HandleObject array, HandleValue val)
+{
+    JS_ASSERT(IsBinaryArray(array));
+
+    RootedObject type(cx, GetType(array));
+    RootedObject elementType(cx, ArrayType::elementType(cx, type));
+
+    uint8_t *base = (uint8_t *) array->getPrivate();
+
+    // set array[0] = [[Convert]](val)
+    if (!ConvertAndCopyTo(cx, elementType, val, base)) {
+        ReportTypeError(cx, val, elementType);
+        return false;
+    }
+
+    size_t elementSize = GetMemSize(cx, elementType);
+    // Copy a[0] into remaining indices.
+    for (uint32_t i = 1; i < ArrayType::length(cx, type); i++) {
+        uint8_t *dest = base + elementSize * i;
+        memcpy(dest, base, elementSize);
+    }
+
+    return true;
 }
 
 JSBool
 ArrayType::repeat(JSContext *cx, unsigned int argc, Value *vp)
 {
-    return false;
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (args.length() < 1) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage,
+                             NULL, JSMSG_MORE_ARGS_NEEDED,
+                             "repeat()", "0", "s");
+        return false;
+    }
+
+    RootedObject thisObj(cx, args.thisv().toObjectOrNull());
+    if (!IsArrayType(thisObj)) {
+        JSString *valueStr = JS_ValueToString(cx, args.thisv());
+        char *valueChars = "(unknown type)";
+        if (valueStr)
+            valueChars = JS_EncodeString(cx, valueStr);
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO, "ArrayType", "repeat", valueChars);
+        if (valueStr)
+            JS_free(cx, valueChars);
+        return false;
+    }
+
+    RootedObject binaryArray(cx, BinaryArray::create(cx, thisObj));
+    if (!binaryArray)
+        return false;
+
+    RootedValue val(cx, args[0]);
+    if (!FillBinaryArrayWithValue(cx, binaryArray, val))
+        return false;
+
+    args.rval().setObject(*binaryArray);
+    return true;
 }
 
 JSBool
 ArrayType::toString(JSContext *cx, unsigned int argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     RootedObject thisObj(cx, args.thisv().toObjectOrNull());
@@ -854,35 +951,142 @@ BinaryArray::lengthGetter(JSContext *cx,
     RootedObject thisObj(cx, args.thisv().toObjectOrNull());
     JS_ASSERT(IsBinaryArray(thisObj));
 
     RootedObject type(cx, GetType(thisObj));
     vp->setInt32(ArrayType::length(cx, type));
     return true;
 }
 
-JSBool
-BinaryArray::forEach(JSContext *cx, unsigned int argc, Value *vp)
+/**
+ * The subarray function first creates an ArrayType instance
+ * which will act as the elementType for the subarray.
+ *
+ * var MA = new ArrayType(elementType, 10);
+ * var mb = MA.repeat(val);
+ *
+ * mb.subarray(begin, end=mb.length) => (Only for +ve)
+ *     var internalSA = new ArrayType(elementType, end-begin);
+ *     var ret = new internalSA()
+ *     for (var i = begin; i < end; i++)
+ *         ret[i-begin] = ret[i]
+ *     return ret
+ *
+ * The range specified by the begin and end values is clamped to the valid
+ * index range for the current array. If the computed length of the new
+ * TypedArray would be negative, it is clamped to zero.
+ * see: http://www.khronos.org/registry/typedarray/specs/latest/#7
+ *
+ */
+JSBool BinaryArray::subarray(JSContext *cx, unsigned int argc, Value *vp)
 {
-    JS_ASSERT(0);
-    return false;
-}
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (args.length() < 1) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage,
+                             NULL, JSMSG_MORE_ARGS_NEEDED,
+                             "subarray()", "0", "s");
+        return false;
+    }
+
+    if (!args[0].isInt32()) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+                             JSMSG_BINARYDATA_SUBARRAY_INTEGER_ARG, "1");
+        return false;
+    }
+
+    RootedObject thisObj(cx, &args.thisv().toObject());
+    if (!IsBinaryArray(thisObj)) {
+        ReportTypeError(cx, ObjectValue(*thisObj), "binary array");
+        return false;
+    }
+
+    RootedObject type(cx, GetType(thisObj));
+    RootedObject elementType(cx, ArrayType::elementType(cx, type));
+    uint32_t length = ArrayType::length(cx, type);
+
+    int32_t begin = args[0].toInt32();
+    int32_t end = length;
 
-JSBool
-BinaryArray::subarray(JSContext *cx, unsigned int argc, Value *vp)
-{
-    JS_ASSERT(0);
-    return false;
+    if (args.length() >= 2) {
+        if (!args[1].isInt32()) {
+            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+                    JSMSG_BINARYDATA_SUBARRAY_INTEGER_ARG, "2");
+            return false;
+        }
+
+        end = args[1].toInt32();
+    }
+
+    if (begin < 0)
+        begin = length + begin;
+    if (end < 0)
+        end = length + end;
+
+    begin = Clamp(begin, 0, length);
+    end = Clamp(end, 0, length);
+
+    int32_t sublength = end - begin; // end exclusive
+    sublength = Clamp(sublength, 0, length);
+
+    RootedObject globalObj(cx, cx->compartment()->maybeGlobal());
+    JS_ASSERT(globalObj);
+    Rooted<GlobalObject*> global(cx, &globalObj->as<GlobalObject>());
+    RootedObject arrayTypeGlobal(cx, global->getOrCreateArrayTypeObject(cx));
+
+    RootedObject subArrayType(cx, ArrayType::create(cx, arrayTypeGlobal,
+                                                    elementType, sublength));
+    if (!subArrayType)
+        return false;
+
+    int32_t elementSize = GetMemSize(cx, elementType);
+    size_t offset = elementSize * begin;
+
+    RootedObject subarray(cx, BinaryArray::create(cx, subArrayType, thisObj, offset));
+    if (!subarray)
+        return false;
+
+    args.rval().setObject(*subarray);
+    return true;
 }
 
 JSBool
 BinaryArray::fill(JSContext *cx, unsigned int argc, Value *vp)
 {
-    JS_ASSERT(0);
-    return false;
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (args.length() < 1) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage,
+                             NULL, JSMSG_MORE_ARGS_NEEDED,
+                             "fill()", "0", "s");
+        return false;
+    }
+
+    if (!args.thisv().isObject())
+        return false;
+
+    RootedObject thisObj(cx, args.thisv().toObjectOrNull());
+    if (!IsBinaryArray(thisObj)) {
+        ReportTypeError(cx, ObjectValue(*thisObj), "binary array");
+        return false;
+    }
+
+    Value funArrayTypeVal = GetFunctionNativeReserved(&args.callee(), 0);
+    JS_ASSERT(funArrayTypeVal.isObject());
+
+    RootedObject type(cx, GetType(thisObj));
+    RootedObject funArrayType(cx, funArrayTypeVal.toObjectOrNull());
+    if (!IsSameBinaryDataType(cx, funArrayType, type)) {
+        ReportTypeError(cx, ObjectValue(*thisObj), funArrayType);
+        return false;
+    }
+
+    args.rval().setUndefined();
+    RootedValue val(cx, args[0]);
+    return FillBinaryArrayWithValue(cx, thisObj, val);
 }
 
 JSBool
 BinaryArray::obj_lookupGeneric(JSContext *cx, HandleObject obj, HandleId id,
                                 MutableHandleObject objp, MutableHandleShape propp)
 {
     JS_ASSERT(IsBinaryArray(obj));
     RootedObject type(cx, GetType(obj));
@@ -1655,17 +1859,17 @@ BinaryStruct::obj_setGeneric(JSContext *
     FieldInfo fieldInfo;
     if (!LookupFieldList(fieldList, id, &fieldInfo)) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                              JSMSG_UNDEFINED_PROP, IdToString(cx, id));
         return false;
     }
 
     uint8_t *loc = ((uint8_t *) obj->getPrivate()) + fieldInfo.offset;
-    
+
     RootedObject fieldType(cx, fieldInfo.type);
     if (!ConvertAndCopyTo(cx, fieldType, vp, loc))
         return false;
 
     return true;
 }
 
 JSBool
@@ -1795,16 +1999,27 @@ GlobalObject::initTypeObject(JSContext *
     if (!DefineConstructorAndPrototype(cx, global, JSProto_Type,
                                        TypeCtor, TypeProto))
         return false;
 
     global->setReservedSlot(JSProto_Type, ObjectValue(*TypeCtor));
     return true;
 }
 
+bool
+GlobalObject::initArrayTypeObject(JSContext *cx, Handle<GlobalObject *> global)
+{
+    RootedFunction ctor(cx,
+        global->createConstructor(cx, ArrayType::construct,
+                                  cx->names().ArrayType, 2));
+
+    global->setReservedSlot(JSProto_ArrayTypeObject, ObjectValue(*ctor));
+    return true;
+}
+
 static JSObject *
 SetupComplexHeirarchy(JSContext *cx, HandleObject obj, JSProtoKey protoKey,
                       HandleObject complexObject, MutableHandleObject proto,
                       MutableHandleObject protoProto)
 {
     Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
     // get the 'Type' constructor
     RootedObject TypeObject(cx, global->getOrCreateTypeObject(cx));
@@ -1860,36 +2075,43 @@ SetupComplexHeirarchy(JSContext *cx, Han
     return complexObject;
 }
 
 static JSObject *
 InitArrayType(JSContext *cx, HandleObject obj)
 {
     JS_ASSERT(obj->isNative());
     Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
-    RootedFunction ctor(cx,
-        global->createConstructor(cx, ArrayType::construct,
-                                  cx->names().ArrayType, 2));
-
+    RootedObject ctor(cx, global->getOrCreateArrayTypeObject(cx));
     if (!ctor)
         return NULL;
 
     RootedObject proto(cx);
     RootedObject protoProto(cx);
     if (!SetupComplexHeirarchy(cx, obj, JSProto_ArrayType,
                                ctor, &proto, &protoProto))
         return NULL;
 
     if (!JS_DefineFunction(cx, proto, "repeat", ArrayType::repeat, 1, 0))
         return NULL;
 
     if (!JS_DefineFunction(cx, proto, "toString", ArrayType::toString, 0, 0))
         return NULL;
 
-    if (!JS_DefineFunction(cx, protoProto, "forEach", BinaryArray::forEach, 1, 0))
+    RootedObject arrayProto(cx);
+    if (!FindProto(cx, &ArrayObject::class_, &arrayProto))
+        return NULL;
+
+    RootedValue forEachFunVal(cx);
+    RootedAtom forEachAtom(cx, Atomize(cx, "forEach", 7));
+    RootedId forEachId(cx, AtomToId(forEachAtom));
+    if (!JSObject::getProperty(cx, arrayProto, arrayProto, forEachAtom->asPropertyName(), &forEachFunVal))
+        return NULL;
+
+    if (!JSObject::defineGeneric(cx, protoProto, forEachId, forEachFunVal, NULL, NULL, 0))
         return NULL;
 
     if (!JS_DefineFunction(cx, protoProto, "subarray",
                            BinaryArray::subarray, 1, 0))
         return NULL;
 
     return proto;
 }
--- a/js/src/builtin/BinaryData.h
+++ b/js/src/builtin/BinaryData.h
@@ -140,21 +140,21 @@ static Class NumericTypeClasses[NUMERICT
 /* This represents the 'A' and it's [[Prototype]] chain
  * in:
  *   A = new ArrayType(Type, N);
  *   a = new A();
  */
 class ArrayType : public JSObject
 {
     private:
-        static JSObject *create(JSContext *cx, HandleObject arrayTypeGlobal,
-                                HandleObject elementType, uint32_t length);
     public:
         static Class class_;
 
+        static JSObject *create(JSContext *cx, HandleObject arrayTypeGlobal,
+                                HandleObject elementType, uint32_t length);
         static JSBool construct(JSContext *cx, unsigned int argc, jsval *vp);
         static JSBool repeat(JSContext *cx, unsigned int argc, jsval *vp);
 
         static JSBool toString(JSContext *cx, unsigned int argc, jsval *vp);
 
         static uint32_t length(JSContext *cx, HandleObject obj);
         static JSObject *elementType(JSContext *cx, HandleObject obj);
         static bool convertAndCopyTo(JSContext *cx, HandleObject exemplar,
@@ -164,34 +164,33 @@ class ArrayType : public JSObject
 };
 
 /* This represents the 'a' and it's [[Prototype]] chain */
 class BinaryArray
 {
     private:
         static JSObject *createEmpty(JSContext *cx, HandleObject type);
 
-        // creates initialized memory of size of type
-        static JSObject *create(JSContext *cx, HandleObject type);
         // attempts to [[Convert]]
         static JSObject *create(JSContext *cx, HandleObject type,
                                 HandleValue initial);
 
     public:
         static Class class_;
 
+        // creates initialized memory of size of type
+        static JSObject *create(JSContext *cx, HandleObject type);
         // uses passed block as memory
         static JSObject *create(JSContext *cx, HandleObject type,
                                 HandleObject owner, size_t offset);
         static JSBool construct(JSContext *cx, unsigned int argc, jsval *vp);
 
         static void finalize(FreeOp *op, JSObject *obj);
         static void obj_trace(JSTracer *tracer, JSObject *obj);
 
-        static JSBool forEach(JSContext *cx, unsigned int argc, jsval *vp);
         static JSBool subarray(JSContext *cx, unsigned int argc, jsval *vp);
         static JSBool fill(JSContext *cx, unsigned int argc, jsval *vp);
 
         static JSBool obj_lookupGeneric(JSContext *cx, HandleObject obj,
                                         HandleId id, MutableHandleObject objp,
                                         MutableHandleShape propp);
 
         static JSBool obj_lookupProperty(JSContext *cx, HandleObject obj,
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -401,8 +401,9 @@ MSG_DEF(JSMSG_WRONG_VALUE,            34
 MSG_DEF(JSMSG_PAR_ARRAY_SCATTER_BAD_TARGET, 348, 1, JSEXN_ERR, "target for index {0} is not an integer")
 MSG_DEF(JSMSG_SELFHOSTED_UNBOUND_NAME,349, 0, JSEXN_TYPEERR, "self-hosted code may not contain unbound name lookups")
 MSG_DEF(JSMSG_DEPRECATED_SOURCE_MAP,  350, 0, JSEXN_SYNTAXERR, "Using //@ to indicate source map URL pragmas is deprecated. Use //# instead")
 MSG_DEF(JSMSG_BAD_DESTRUCT_ASSIGN,    351, 1, JSEXN_SYNTAXERR, "can't assign to {0} using destructuring assignment")
 MSG_DEF(JSMSG_BINARYDATA_ARRAYTYPE_BAD_ARGS, 352, 0, JSEXN_ERR, "Invalid arguments")
 MSG_DEF(JSMSG_BINARYDATA_BINARYARRAY_BAD_INDEX, 353, 0, JSEXN_RANGEERR, "invalid or out-of-range index")
 MSG_DEF(JSMSG_BINARYDATA_STRUCTTYPE_BAD_ARGS, 354, 0, JSEXN_RANGEERR, "invalid field descriptor")
 MSG_DEF(JSMSG_BINARYDATA_NOT_BINARYSTRUCT,   355, 1, JSEXN_TYPEERR, "{0} is not a BinaryStruct")
+MSG_DEF(JSMSG_BINARYDATA_SUBARRAY_INTEGER_ARG, 356, 1, JSEXN_ERR, "argument {0} must be an integer")
--- a/js/src/jsprototypes.h
+++ b/js/src/jsprototypes.h
@@ -65,10 +65,11 @@
     macro(int8,                  44,     js_InitBinaryDataClasses) \
     macro(int16,                 45,     js_InitBinaryDataClasses) \
     macro(int32,                 46,     js_InitBinaryDataClasses) \
     macro(int64,                 47,     js_InitBinaryDataClasses) \
     macro(float32,               48,     js_InitBinaryDataClasses) \
     macro(float64,               49,     js_InitBinaryDataClasses) \
     macro(ArrayType,             50,     js_InitBinaryDataClasses) \
     macro(StructType,            51,     js_InitBinaryDataClasses) \
+    macro(ArrayTypeObject,       52,     js_InitBinaryDataClasses) \
 
 #endif /* jsprototypes_h */
--- a/js/src/tests/ecma_6/BinaryData/arraytype.js
+++ b/js/src/tests/ecma_6/BinaryData/arraytype.js
@@ -29,38 +29,45 @@ function runTests() {
     assertEq(A.length, 10);
     assertEq(A.elementType, uint8);
     assertEq(A.bytes, 10);
     assertEq(A.toString(), "ArrayType(uint8, 10)");
 
     assertEq(A.prototype.__proto__, ArrayType.prototype.prototype);
     assertEq(typeof A.prototype.fill, "function");
 
+    var X = { __proto__: A };
+    assertThrows(function() X.repeat(42));
+
     var a = new A();
     assertEq(a.__proto__, A.prototype);
     assertEq(a.length, 10);
 
     assertThrows(function() a.length = 2);
 
     for (var i = 0; i < a.length; i++)
         a[i] = i*2;
 
     for (var i = 0; i < a.length; i++)
         assertEq(a[i], i*2);
 
+    a.forEach(function(val, i) {
+        assertEq(val, i*2);
+        assertEq(arguments[2], a);
+    });
 
     // Range.
     assertThrows(function() a[i] = 5);
 
     assertEq(a[a.length], undefined);
 
     // constructor takes initial value
     var b = new A(a);
     for (var i = 0; i < a.length; i++)
-        assertEq(a[i], i*2);
+        assertEq(b[i], i*2);
 
     var b = new A([0, 1, 0, 1, 0, 1, 0, 1, 0, 1]);
     for (var i = 0; i < b.length; i++)
         assertEq(b[i], i%2);
 
 
     assertThrows(function() new A(5));
     assertThrows(function() new A(/fail/));
@@ -89,18 +96,90 @@ function runTests() {
     assertEq(Number.isNaN(mario[1][1]), true);
 
 
     // ok this is just for kicks
     var AllSprites = new ArrayType(Sprite, 65536);
     var as = new AllSprites();
     assertEq(as.length, 65536);
 
+    // test methods
+    var c = new A();
+    c.fill(3);
+    for (var i = 0; i < c.length; i++)
+        assertEq(c[i], 3);
 
-    as.foo = "bar";
+    assertThrows(function() c.update([3.14, 4.52, 5]));
+    //assertThrows(function() c.update([3000, 0, 1, 1, 1, 1, 1, 1, 1, 1]));
+
+    assertThrows(function() Vec3.prototype.fill.call(c, 2));
+
+    var updatingPos = new Vec3();
+    updatingPos.update([5, 3, 1]);
+    assertEq(updatingPos[0], 5);
+    assertEq(updatingPos[1], 3);
+    assertEq(updatingPos[2], 1);
+
+    var d = A.repeat(10);
+    for (var i = 0; i < d.length; i++)
+        assertEq(d[i], 10);
+
+    assertThrows(function() ArrayType.prototype.repeat.call(d, 2));
+
+    var MA = new ArrayType(uint32, 5);
+    var ma = new MA([1, 2, 3, 4, 5]);
+
+    var mb = ma.subarray(2);
+    assertEq(mb.length, 3);
+    assertEq(mb[0], 3);
+    assertEq(mb[1], 4);
+    assertEq(mb[2], 5);
+
+    assertThrows(function() ma.subarray());
+    assertThrows(function() ma.subarray(2.14));
+    assertThrows(function() ma.subarray({}));
+    assertThrows(function() ma.subarray(2, []));
+
+    // check similarity even though mb's ArrayType
+    // is not script accessible
+    var Similar = new ArrayType(uint32, 3);
+    var sim = new Similar();
+    sim.update(mb);
+    assertEq(sim[0], 3);
+    assertEq(sim[1], 4);
+    assertEq(sim[2], 5);
+
+    var range = ma.subarray(0, 3);
+    assertEq(range.length, 3);
+    assertEq(range[0], 1);
+    assertEq(range[1], 2);
+    assertEq(range[2], 3);
+
+    assertEq(ma.subarray(ma.length).length, 0);
+    assertEq(ma.subarray(ma.length, ma.length-1).length, 0);
+
+    var rangeNeg = ma.subarray(-2);
+    assertEq(rangeNeg.length, 2);
+    assertEq(rangeNeg[0], 4);
+    assertEq(rangeNeg[1], 5);
+
+    var rangeNeg = ma.subarray(-5, -3);
+    assertEq(rangeNeg.length, 2);
+    assertEq(rangeNeg[0], 1);
+    assertEq(rangeNeg[1], 2);
+
+    assertEq(ma.subarray(-2, -3).length, 0);
+    assertEq(ma.subarray(-6).length, ma.length);
+
+    var modifyOriginal = ma.subarray(2);
+    modifyOriginal[0] = 42;
+    assertEq(ma[2], 42);
+
+    ma[4] = 97;
+    assertEq(modifyOriginal[2], 97);
 
     var indexPropDesc = Object.getOwnPropertyDescriptor(as, '0');
     assertEq(typeof indexPropDesc == "undefined", false);
     assertEq(indexPropDesc.configurable, false);
     assertEq(indexPropDesc.enumerable, true);
     assertEq(indexPropDesc.writable, true);
 
 
--- a/js/src/tests/ecma_6/BinaryData/memory.js
+++ b/js/src/tests/ecma_6/BinaryData/memory.js
@@ -36,16 +36,28 @@ function runTests() {
     gc();
     spin();
     for (var i = 0; i < a0.length; i++)
         assertEq(a0[i], i);
 
     var Color = new StructType({'r': uint8, 'g': uint8, 'b': uint8});
     var Rainbow = new ArrayType(Color, 7);
 
+    var theOneISawWasJustBlack = Rainbow.repeat({'r': 0, 'g': 0, 'b': 0});
+
+    var middleBand = theOneISawWasJustBlack[3];
+
+    theOneISawWasJustBlack = null;
+    gc();
+    spin();
+    assertEq(middleBand['r'] == 0 && middleBand['g'] == 0 && middleBand['b'] == 0, true);
+    middleBand.update({'r': 255, 'g': 207, 'b': 142});
+    assertEq(middleBand['r'] == 255 && middleBand['g'] == 207 && middleBand['b'] == 142, true);
+
+
     var scopedType = function() {
         var Point = new StructType({'x': int32, 'y': int32});
         var aPoint = new Point();
         aPoint.x = 4;
         aPoint.y = 5;
         return aPoint;
     }
 
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -333,16 +333,20 @@ class GlobalObject : public JSObject
     JSObject *getOrCreateDataObject(JSContext *cx) {
         return getOrCreateObject(cx, JSProto_Data, initDataObject);
     }
 
     JSObject *getOrCreateTypeObject(JSContext *cx) {
         return getOrCreateObject(cx, JSProto_Type, initTypeObject);
     }
 
+    JSObject *getOrCreateArrayTypeObject(JSContext *cx) {
+        return getOrCreateObject(cx, JSProto_ArrayTypeObject, initArrayTypeObject);
+    }
+
   private:
     typedef bool (*ObjectInitOp)(JSContext *cx, Handle<GlobalObject*> global);
 
     JSObject *getOrCreateObject(JSContext *cx, unsigned slot, ObjectInitOp init) {
         Value v = getSlotRef(slot);
         if (v.isObject())
             return &v.toObject();
         Rooted<GlobalObject*> self(cx, this);
@@ -438,16 +442,17 @@ class GlobalObject : public JSObject
     static bool initIntlObject(JSContext *cx, Handle<GlobalObject*> global);
     static bool initCollatorProto(JSContext *cx, Handle<GlobalObject*> global);
     static bool initNumberFormatProto(JSContext *cx, Handle<GlobalObject*> global);
     static bool initDateTimeFormatProto(JSContext *cx, Handle<GlobalObject*> global);
 
     // Implemented in builtin/BinaryData.cpp
     static bool initTypeObject(JSContext *cx, Handle<GlobalObject*> global);
     static bool initDataObject(JSContext *cx, Handle<GlobalObject*> global);
+    static bool initArrayTypeObject(JSContext *cx, Handle<GlobalObject*> global);
 
     static bool initStandardClasses(JSContext *cx, Handle<GlobalObject*> global);
 
     typedef js::Vector<js::Debugger *, 0, js::SystemAllocPolicy> DebuggerVector;
 
     /*
      * The collection of Debugger objects debugging this global. If this global
      * is not a debuggee, this returns either NULL or an empty vector.