Bug 578700 - StructType and BinaryStruct implementation. r=nmatsakis
authorNikhil Marathe <nsm.nikhil@gmail.com>
Thu, 25 Jul 2013 17:59:47 -0700
changeset 140082 43d1eada77d636ac906a4d9e9c4788896252496e
parent 140081 8253f5b39cbd23c09ee7485489d5daee520555a9
child 140083 13b28328f0106d87e8ed79858abaf9d779056a67
push id25013
push userryanvm@gmail.com
push dateFri, 26 Jul 2013 14:47:14 +0000
treeherdermozilla-central@52f9e8ffe111 [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 - StructType and BinaryStruct implementation. r=nmatsakis
js/src/builtin/BinaryData.cpp
js/src/builtin/BinaryData.h
js/src/js.msg
js/src/jsapi.cpp
js/src/tests/ecma_6/BinaryData/memory.js
js/src/tests/ecma_6/BinaryData/structtype.js
--- a/js/src/builtin/BinaryData.cpp
+++ b/js/src/builtin/BinaryData.cpp
@@ -1,21 +1,24 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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/. */
 
 #include "builtin/BinaryData.h"
 
+#include <vector>
+
 #include "mozilla/FloatingPoint.h"
 
 #include "jscompartment.h"
 #include "jsfun.h"
 #include "jsobj.h"
+#include "jsutil.h"
 
 #include "vm/TypedArrayObject.h"
 #include "vm/String.h"
 #include "vm/StringBuffer.h"
 #include "vm/GlobalObject.h"
 
 #include "jsatominlines.h"
 #include "jsobjinlines.h"
@@ -89,17 +92,17 @@ static inline bool
 IsArrayType(HandleObject type)
 {
     return type && type->hasClass(&ArrayType::class_);
 }
 
 static inline bool
 IsStructType(HandleObject type)
 {
-    return type && type->hasClass(&StructTypeClass);
+    return type && type->hasClass(&StructType::class_);
 }
 
 static inline bool
 IsComplexType(HandleObject type)
 {
     return IsArrayType(type) || IsStructType(type);
 }
 
@@ -111,20 +114,26 @@ IsBinaryType(HandleObject type)
 
 static inline bool
 IsBinaryArray(HandleObject type)
 {
     return type && type->hasClass(&BinaryArray::class_);
 }
 
 static inline bool
+IsBinaryStruct(HandleObject type)
+{
+    return type && type->hasClass(&BinaryStruct::class_);
+}
+
+static inline bool
 IsBlock(HandleObject type)
 {
-    return type->hasClass(&BinaryArray::class_);
-    /* TODO || type->hasClass(&BinaryStruct::class_); */
+    return type->hasClass(&BinaryArray::class_) ||
+           type->hasClass(&BinaryStruct::class_);
 }
 
 static inline JSObject *
 GetType(HandleObject block)
 {
     JS_ASSERT(IsBlock(block));
     return block->getFixedSlot(SLOT_DATATYPE).toObjectOrNull();
 }
@@ -151,36 +160,98 @@ GetAlign(JSContext *cx, HandleObject typ
     RootedObject typeObj(cx, type);
     RootedValue val(cx);
     JS_ASSERT(&NumericTypeClasses[NUMERICTYPE_UINT8] <= type->getClass() &&
               type->getClass() <= &NumericTypeClasses[NUMERICTYPE_FLOAT64]);
     JSObject::getProperty(cx, typeObj, typeObj, cx->names().bytes, &val);
     return val.toInt32();
 }
 
+struct FieldInfo
+{
+    jsid name;
+    JSObject *type;
+    size_t offset;
+};
+
+typedef std::vector<FieldInfo> FieldList;
+
+static
+bool
+LookupFieldList(FieldList *list, jsid fieldName, FieldInfo *out)
+{
+    for (FieldList::const_iterator it = list->begin(); it != list->end(); ++it) {
+        if ((*it).name == fieldName) {
+            out->name = it->name;
+            out->type = it->type;
+            out->offset = it->offset;
+            return true;
+        }
+    }
+    return false;
+}
+
+static bool
+IsSameBinaryDataType(JSContext *cx, HandleObject type1, HandleObject type2);
+
+static bool
+IsSameArrayType(JSContext *cx, HandleObject type1, HandleObject type2)
+{
+    JS_ASSERT(IsArrayType(type1) && IsArrayType(type2));
+    if (ArrayType::length(cx, type1) != ArrayType::length(cx, type2))
+        return false;
+
+    RootedObject elementType1(cx, ArrayType::elementType(cx, type1));
+    RootedObject elementType2(cx, ArrayType::elementType(cx, type2));
+    return IsSameBinaryDataType(cx, elementType1, elementType2);
+}
+
+static bool
+IsSameStructType(JSContext *cx, HandleObject type1, HandleObject type2)
+{
+    JS_ASSERT(IsStructType(type1) && IsStructType(type2));
+
+    FieldList *fieldList1 = static_cast<FieldList *>(type1->getPrivate());
+    FieldList *fieldList2 = static_cast<FieldList *>(type2->getPrivate());
+
+    if (fieldList1->size() != fieldList2->size())
+        return false;
+
+    // Names and layout should be the same.
+    for (uint32_t i = 0; i < fieldList1->size(); ++i) {
+        FieldInfo fieldInfo1 = fieldList1->at(i);
+        FieldInfo fieldInfo2 = fieldList2->at(i);
+
+        if (fieldInfo1.name != fieldInfo2.name)
+            return false;
+
+        if (fieldInfo1.offset != fieldInfo2.offset)
+            return false;
+
+        RootedObject fieldType1(cx, fieldInfo1.type);
+        RootedObject fieldType2(cx, fieldInfo2.type);
+        if (!IsSameBinaryDataType(cx, fieldType1, fieldType2))
+            return false;
+    }
+
+    return true;
+}
+
 static bool
 IsSameBinaryDataType(JSContext *cx, HandleObject type1, HandleObject type2)
 {
     JS_ASSERT(IsBinaryType(type1));
     JS_ASSERT(IsBinaryType(type2));
 
     if (IsNumericType(type1)) {
         return type1->hasClass(type2->getClass());
     } else if (IsArrayType(type1) && IsArrayType(type2)) {
-        if (ArrayType::length(cx, type1) != ArrayType::length(cx, type2))
-            return false;
-
-        RootedObject elementType1(cx, ArrayType::elementType(cx, type1));
-        RootedObject elementType2(cx, ArrayType::elementType(cx, type2));
-        return IsSameBinaryDataType(cx, elementType1, elementType2);
+        return IsSameArrayType(cx, type1, type2);
     } else if (IsStructType(type1) && IsStructType(type2)) {
-        // Struct types compare on structural equality.
-        // FIXME(nsm): Implement.
-        JS_ASSERT(0);
-        return false;
+        return IsSameStructType(cx, type1, type2);
     }
 
     return false;
 }
 
 template <typename Domain, typename Input>
 bool
 InRange(Input x)
@@ -220,17 +291,17 @@ InRange<double, double>(double x)
     return -std::numeric_limits<double>::max() <= x &&
            x <= std::numeric_limits<double>::max();
 }
 
 template <typename T>
 Class *
 js::NumericType<T>::typeToClass()
 {
-    JS_ASSERT(0);
+    abort();
     return NULL;
 }
 
 #define BINARYDATA_TYPE_TO_CLASS(constant_, type_)\
     template <>\
     Class *\
     NumericType<type_##_t>::typeToClass()\
     {\
@@ -624,22 +695,16 @@ ArrayType::construct(JSContext *cx, unsi
     JSObject *obj = create(cx, arrayTypeGlobal, elementType, args[1].toInt32());
     if (!obj)
         return false;
     args.rval().setObject(*obj);
     return true;
 }
 
 JSBool
-createStructType(JSContext *cx, unsigned argc, Value *vp)
-{
-    return false;
-}
-
-JSBool
 DataInstanceUpdate(JSContext *cx, unsigned argc, Value *vp)
 {
     return false;
 }
 
 JSBool
 ArrayType::repeat(JSContext *cx, unsigned int argc, Value *vp)
 {
@@ -875,18 +940,19 @@ BinaryArray::obj_lookupElement(JSContext
         return JSObject::lookupElement(cx, proto, index, objp, propp);
 
     objp.set(NULL);
     propp.set(NULL);
     return true;
 }
 
 JSBool
-BinaryArray::obj_lookupSpecial(JSContext *cx, HandleObject obj, HandleSpecialId sid,
-                                MutableHandleObject objp, MutableHandleShape propp)
+BinaryArray::obj_lookupSpecial(JSContext *cx, HandleObject obj,
+                               HandleSpecialId sid, MutableHandleObject objp,
+                               MutableHandleShape propp)
 {
     RootedId id(cx, SPECIALID_TO_JSID(sid));
     return obj_lookupGeneric(cx, obj, id, objp, propp);
 }
 
 JSBool
 BinaryArray::obj_getGeneric(JSContext *cx, HandleObject obj, HandleObject receiver,
                              HandleId id, MutableHandleValue vp)
@@ -948,18 +1014,19 @@ BinaryArray::obj_getElementIfPresent(JSC
     }
 
     *present = false;
     vp.setUndefined();
     return true;
 }
 
 JSBool
-BinaryArray::obj_getSpecial(JSContext *cx, HandleObject obj, HandleObject receiver,
-                             HandleSpecialId sid, MutableHandleValue vp)
+BinaryArray::obj_getSpecial(JSContext *cx, HandleObject obj,
+                            HandleObject receiver, HandleSpecialId sid,
+                            MutableHandleValue vp)
 {
     RootedId id(cx, SPECIALID_TO_JSID(sid));
     return obj_getGeneric(cx, obj, receiver, id, vp);
 }
 
 JSBool
 BinaryArray::obj_setGeneric(JSContext *cx, HandleObject obj, HandleId id,
                              MutableHandleValue vp, JSBool strict)
@@ -1044,17 +1111,18 @@ BinaryArray::obj_getGenericAttributes(JS
         return true;
     }
 
 	return false;
 }
 
 JSBool
 BinaryArray::obj_getPropertyAttributes(JSContext *cx, HandleObject obj,
-                                        HandlePropertyName name, unsigned *attrsp)
+                                        HandlePropertyName name,
+                                        unsigned *attrsp)
 {
     RootedId id(cx, NameToId(name));
     return obj_getGenericAttributes(cx, obj, id, attrsp);
 }
 
 JSBool
 BinaryArray::obj_getElementAttributes(JSContext *cx, HandleObject obj,
                                        uint32_t index, unsigned *attrsp)
@@ -1103,25 +1171,527 @@ BinaryArray::obj_enumerate(JSContext *cx
         case JSENUMERATE_DESTROY:
             statep.setNull();
             break;
     }
 
 	return true;
 }
 
+/*********************************
+ * Structs
+ *********************************/
+Class StructType::class_ = {
+    "StructType",
+    JSCLASS_HAS_RESERVED_SLOTS(TYPE_RESERVED_SLOTS) |
+    JSCLASS_HAS_PRIVATE | // used to store FieldList
+    JSCLASS_HAS_CACHED_PROTO(JSProto_StructType),
+    JS_PropertyStub,
+    JS_DeletePropertyStub,
+    JS_PropertyStub,
+    JS_StrictPropertyStub,
+    JS_EnumerateStub,
+    JS_ResolveStub,
+    JS_ConvertStub,
+    StructType::finalize,
+    NULL,
+    NULL,
+    NULL,
+    BinaryStruct::construct,
+    NULL
+};
+
+Class BinaryStruct::class_ = {
+    "BinaryStruct",
+    Class::NON_NATIVE |
+    JSCLASS_HAS_RESERVED_SLOTS(BLOCK_RESERVED_SLOTS) |
+    JSCLASS_HAS_PRIVATE |
+    JSCLASS_HAS_CACHED_PROTO(JSProto_StructType),
+    JS_PropertyStub,
+    JS_DeletePropertyStub,
+    JS_PropertyStub,
+    JS_StrictPropertyStub,
+    JS_EnumerateStub,
+    JS_ResolveStub,
+    JS_ConvertStub,
+    BinaryStruct::finalize,
+    NULL,           /* checkAccess */
+    NULL,           /* call        */
+    NULL,           /* construct   */
+    NULL,           /* hasInstance */
+    BinaryStruct::obj_trace,
+    JS_NULL_CLASS_EXT,
+    {
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        BinaryStruct::obj_getGeneric,
+        BinaryStruct::obj_getProperty,
+        NULL,
+        NULL,
+        BinaryStruct::obj_getSpecial,
+        BinaryStruct::obj_setGeneric,
+        BinaryStruct::obj_setProperty,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+    }
+};
+
+/*
+ * NOTE: layout() does not check for duplicates in fields since the arguments
+ * to StructType are currently passed as an object literal. Fix this if it
+ * changes to taking an array of arrays.
+ */
+bool
+StructType::layout(JSContext *cx, HandleObject structType, HandleObject fields)
+{
+    AutoIdVector fieldProps(cx);
+    if (!GetPropertyNames(cx, fields, JSITER_OWNONLY, &fieldProps))
+        return false;
+
+    FieldList *fieldList = new FieldList(fieldProps.length());
+
+    uint32_t structAlign = 0;
+    uint32_t structMemSize = 0;
+    uint32_t structByteSize = 0;
+
+    for (unsigned int i = 0; i < fieldProps.length(); i++) {
+        RootedValue fieldTypeVal(cx);
+        RootedId id(cx, fieldProps[i]);
+        if (!JSObject::getGeneric(cx, fields, fields, id, &fieldTypeVal))
+            return false;
+
+        RootedObject fieldType(cx, fieldTypeVal.toObjectOrNull());
+        if (!IsBinaryType(fieldType))
+            return false;
+
+        size_t fieldMemSize = GetMemSize(cx, fieldType);
+        size_t fieldAlign = GetAlign(cx, fieldType);
+        size_t fieldOffset = AlignBytes(structMemSize, fieldAlign);
+
+        structMemSize = fieldOffset + fieldMemSize;
+
+        if (fieldAlign > structAlign)
+            structAlign = fieldAlign;
+
+        RootedValue fieldTypeBytes(cx);
+        if (!JSObject::getProperty(cx, fieldType, fieldType, cx->names().bytes, &fieldTypeBytes))
+            return false;
+
+        JS_ASSERT(fieldTypeBytes.isInt32());
+        structByteSize += fieldTypeBytes.toInt32();
+
+        (*fieldList)[i].name = fieldProps[i];
+        (*fieldList)[i].type = fieldType.get();
+        (*fieldList)[i].offset = fieldOffset;
+    }
+
+    size_t structTail = AlignBytes(structMemSize, structAlign);
+    JS_ASSERT(structTail >= structMemSize);
+    structMemSize = structTail;
+
+    structType->setFixedSlot(SLOT_MEMSIZE, Int32Value(structMemSize));
+    structType->setFixedSlot(SLOT_ALIGN, Int32Value(structAlign));
+    structType->setPrivate(fieldList);
+
+    if (!JS_DefineProperty(cx, structType, "bytes",
+                           Int32Value(structByteSize), NULL, NULL,
+                           JSPROP_READONLY | JSPROP_PERMANENT))
+        return false;
+
+    return true;
+}
+
+bool
+StructType::convertAndCopyTo(JSContext *cx, HandleObject exemplar,
+                             HandleValue from, uint8_t *mem)
+{
+
+    if (!from.isObject()) {
+        return ReportTypeError(cx, from, exemplar);
+    }
+
+    RootedObject val(cx, from.toObjectOrNull());
+    if (IsBlock(val)) {
+        RootedObject type(cx, GetType(val));
+        if (IsSameBinaryDataType(cx, exemplar, type)) {
+            uint8_t *priv = (uint8_t*) val->getPrivate();
+            memcpy(mem, priv, GetMemSize(cx, exemplar));
+            return true;
+        }
+        return ReportTypeError(cx, from, exemplar);
+    }
+
+    RootedObject valRooted(cx, val);
+    AutoIdVector ownProps(cx);
+    if (!GetPropertyNames(cx, valRooted, JSITER_OWNONLY, &ownProps))
+        return ReportTypeError(cx, from, exemplar);
+
+    FieldList *fieldList = static_cast<FieldList *>(exemplar->getPrivate());
+
+    if (ownProps.length() != fieldList->size()) {
+        return ReportTypeError(cx, from, exemplar);
+    }
+
+    FieldInfo info;
+    for (unsigned int i = 0; i < ownProps.length(); i++) {
+        if (!LookupFieldList(fieldList, ownProps[i], &info)) {
+            return ReportTypeError(cx, from, exemplar);
+        }
+    }
+
+    for (FieldList::const_iterator it = fieldList->begin(); it != fieldList->end(); ++it) {
+        RootedPropertyName fieldName(cx, JSID_TO_ATOM(it->name)->asPropertyName());
+
+        RootedValue fromProp(cx);
+        if (!JSObject::getProperty(cx, valRooted, valRooted,
+                                   fieldName, &fromProp)) {
+            return ReportTypeError(cx, from, exemplar);
+        }
+
+        RootedObject fieldType(cx, it->type);
+        if (!ConvertAndCopyTo(cx, fieldType, fromProp,
+                              (uint8_t *) mem + it->offset)) {
+            return false; // TypeError raised by ConvertAndCopyTo.
+        }
+    }
+    return true;
+}
+
+bool
+StructType::reify(JSContext *cx, HandleObject type, HandleObject owner,
+                  size_t offset, MutableHandleValue to) {
+    JSObject *obj = BinaryStruct::create(cx, type, owner, offset);
+    if (!obj)
+        return false;
+    to.setObject(*obj);
+    return true;
+}
+
+JSObject *
+StructType::create(JSContext *cx, HandleObject structTypeGlobal,
+                   HandleObject fields)
+{
+    RootedObject obj(cx, NewBuiltinClassInstance(cx, &StructType::class_));
+    if (!obj)
+        return NULL;
+
+    if (!StructType::layout(cx, obj, fields)) {
+        ReportTypeError(cx, ObjectValue(*fields), "StructType field specifier");
+        return NULL;
+    }
+
+    RootedObject fieldsProto(cx);
+    if (!JSObject::getProto(cx, fields, &fieldsProto))
+        return NULL;
+
+    RootedObject clone(cx, CloneObject(cx, fields, fieldsProto, NullPtr()));
+    if (!clone)
+        return NULL;
+
+    if (!JS_DefineProperty(cx, obj, "fields",
+                           ObjectValue(*fields), NULL, NULL,
+                           JSPROP_READONLY | JSPROP_PERMANENT))
+        return NULL;
+
+    JSObject *prototypeObj =
+        SetupAndGetPrototypeObjectForComplexTypeInstance(cx, structTypeGlobal);
+
+    if (!prototypeObj)
+        return NULL;
+
+    if (!LinkConstructorAndPrototype(cx, obj, prototypeObj))
+        return NULL;
+
+    return obj;
+}
+
+JSBool
+StructType::construct(JSContext *cx, unsigned int argc, Value *vp)
+{
+    if (!JS_IsConstructing(cx, vp)) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+                             JSMSG_NOT_FUNCTION, "StructType");
+        return false;
+    }
+
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (argc >= 1 && args[0].isObject()) {
+        RootedObject structTypeGlobal(cx, &args.callee());
+        RootedObject fields(cx, args[0].toObjectOrNull());
+        JSObject *obj = create(cx, structTypeGlobal, fields);
+        if (!obj)
+            return false;
+        args.rval().setObject(*obj);
+        return true;
+    }
+
+    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+                         JSMSG_BINARYDATA_STRUCTTYPE_BAD_ARGS);
+    return false;
+}
+
+void
+StructType::finalize(FreeOp *op, JSObject *obj)
+{
+    FieldList *list = static_cast<FieldList *>(obj->getPrivate());
+    delete list;
+}
+
+JSBool
+StructType::toString(JSContext *cx, unsigned int argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    RootedObject thisObj(cx, args.thisv().toObjectOrNull());
+
+    if (!IsStructType(thisObj))
+        return false;
+
+    StringBuffer contents(cx);
+    contents.append("StructType({");
+
+    FieldList *fieldList = static_cast<FieldList *>(thisObj->getPrivate());
+    JS_ASSERT(fieldList);
+
+    for (FieldList::const_iterator it = fieldList->begin(); it != fieldList->end(); ++it) {
+        if (it != fieldList->begin())
+            contents.append(", ");
+
+        contents.append(IdToString(cx, it->name));
+        contents.append(": ");
+
+        Value fieldStringVal;
+        if (!JS_CallFunctionName(cx, it->type,
+                                 "toString", 0, NULL, &fieldStringVal))
+            return false;
+
+        contents.append(fieldStringVal.toString());
+    }
+
+    contents.append("})");
+
+    args.rval().setString(contents.finishString());
+    return true;
+}
+
+JSObject *
+BinaryStruct::createEmpty(JSContext *cx, HandleObject type)
+{
+    JS_ASSERT(IsStructType(type));
+    RootedObject typeRooted(cx, type);
+
+    RootedValue protoVal(cx);
+    if (!JSObject::getProperty(cx, typeRooted, typeRooted,
+                               cx->names().classPrototype, &protoVal))
+        return NULL;
+
+    RootedObject obj(cx,
+        NewObjectWithClassProto(cx, &BinaryStruct::class_,
+                                protoVal.toObjectOrNull(), NULL));
+
+    obj->setFixedSlot(SLOT_DATATYPE, ObjectValue(*type));
+    obj->setFixedSlot(SLOT_BLOCKREFOWNER, NullValue());
+    return obj;
+}
+
+JSObject *
+BinaryStruct::create(JSContext *cx, HandleObject type)
+{
+    JSObject *obj = createEmpty(cx, type);
+    if (!obj)
+        return NULL;
+
+    int32_t memsize = GetMemSize(cx, type);
+    void *memory = JS_malloc(cx, memsize);
+    if (!memory)
+        return NULL;
+    memset(memory, 0, memsize);
+    obj->setPrivate(memory);
+    return obj;
+}
+
+JSObject *
+BinaryStruct::create(JSContext *cx, HandleObject type,
+                     HandleObject owner, size_t offset)
+{
+    JS_ASSERT(IsBlock(owner));
+    JSObject *obj = createEmpty(cx, type);
+    if (!obj)
+        return NULL;
+
+    obj->setPrivate(((uint8_t*) owner->getPrivate()) + offset);
+    obj->setFixedSlot(SLOT_BLOCKREFOWNER, ObjectValue(*owner));
+    return obj;
+}
+
+JSBool
+BinaryStruct::construct(JSContext *cx, unsigned int argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    RootedObject callee(cx, &args.callee());
+
+    if (!IsStructType(callee)) {
+        ReportTypeError(cx, args.calleev(), "is not an StructType");
+        return false;
+    }
+
+    JSObject *obj = create(cx, callee);
+
+    if (obj)
+        args.rval().setObject(*obj);
+
+    return obj != NULL;
+}
+
+void
+BinaryStruct::finalize(js::FreeOp *op, JSObject *obj)
+{
+    if (obj->getFixedSlot(SLOT_BLOCKREFOWNER).isNull())
+        op->free_(obj->getPrivate());
+}
+
+void
+BinaryStruct::obj_trace(JSTracer *tracer, JSObject *obj)
+{
+    Value val = obj->getFixedSlot(SLOT_BLOCKREFOWNER);
+    if (val.isObject()) {
+        HeapPtrObject owner(val.toObjectOrNull());
+        MarkObject(tracer, &owner, "binarystruct.blockRefOwner");
+    }
+
+    HeapPtrObject type(obj->getFixedSlot(SLOT_DATATYPE).toObjectOrNull());
+    MarkObject(tracer, &type, "binarystruct.type");
+}
+
+JSBool
+BinaryStruct::obj_getGeneric(JSContext *cx, HandleObject obj,
+                             HandleObject receiver, HandleId id,
+                             MutableHandleValue vp)
+{
+    if (!IsBinaryStruct(obj)) {
+        char *valueStr = JS_EncodeString(cx, JS_ValueToString(cx, ObjectValue(*obj)));
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+                JSMSG_BINARYDATA_NOT_BINARYSTRUCT, valueStr);
+        JS_free(cx, (void *) valueStr);
+        return false;
+    }
+
+    RootedObject type(cx, GetType(obj));
+    JS_ASSERT(IsStructType(type));
+
+    FieldList *fieldList = static_cast<FieldList *>(type->getPrivate());
+    JS_ASSERT(fieldList);
+
+    FieldInfo fieldInfo;
+    if (!LookupFieldList(fieldList, id, &fieldInfo)) {
+        RootedObject proto(cx, obj->getProto());
+        if (!proto) {
+            vp.setUndefined();
+            return true;
+        }
+
+        return JSObject::getGeneric(cx, proto, receiver, id, vp);
+    }
+
+    RootedObject fieldType(cx, fieldInfo.type);
+    return Reify(cx, fieldType, obj, fieldInfo.offset, vp);
+}
+
+JSBool
+BinaryStruct::obj_getProperty(JSContext *cx, HandleObject obj,
+                              HandleObject receiver, HandlePropertyName name,
+                              MutableHandleValue vp)
+{
+    RootedId id(cx, NON_INTEGER_ATOM_TO_JSID(&(*name)));
+    return obj_getGeneric(cx, obj, receiver, id, vp);
+}
+
+JSBool
+BinaryStruct::obj_getSpecial(JSContext *cx, HandleObject obj,
+                             HandleObject receiver, HandleSpecialId sid,
+                             MutableHandleValue vp)
+{
+    RootedId id(cx, SPECIALID_TO_JSID(sid));
+    return obj_getGeneric(cx, obj, receiver, id, vp);
+}
+
+JSBool
+BinaryStruct::obj_setGeneric(JSContext *cx, HandleObject obj, HandleId id,
+                             MutableHandleValue vp, JSBool strict)
+{
+    if (!IsBinaryStruct(obj)) {
+        char *valueStr = JS_EncodeString(cx, JS_ValueToString(cx, ObjectValue(*obj)));
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+                JSMSG_BINARYDATA_NOT_BINARYSTRUCT, valueStr);
+        JS_free(cx, (void *) valueStr);
+        return false;
+    }
+
+    RootedObject type(cx, GetType(obj));
+    JS_ASSERT(IsStructType(type));
+
+    FieldList *fieldList = static_cast<FieldList *>(type->getPrivate());
+    JS_ASSERT(fieldList);
+
+    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
+BinaryStruct::obj_setProperty(JSContext *cx, HandleObject obj,
+                              HandlePropertyName name, MutableHandleValue vp,
+                              JSBool strict)
+{
+    RootedId id(cx, NON_INTEGER_ATOM_TO_JSID(&(*name)));
+    return obj_setGeneric(cx, obj, id, vp, strict);
+}
+
 static bool
 Reify(JSContext *cx, HandleObject type,
       HandleObject owner, size_t offset, MutableHandleValue to)
 {
     if (IsArrayType(type)) {
         return ArrayType::reify(cx, type, owner, offset, to);
+    } else if (IsStructType(type)) {
+        return StructType::reify(cx, type, owner, offset, to);
     }
-    if (type->hasClass(&StructTypeClass))
-        JS_ASSERT(0);
+
     JS_ASSERT(&NumericTypeClasses[NUMERICTYPE_UINT8] <= type->getClass() &&
               type->getClass() <= &NumericTypeClasses[NUMERICTYPE_FLOAT64]);
 
 #define REIFY_CASES(constant_, type_)\
         case constant_:\
             return NumericType<type_##_t>::reify(cx,\
                     ((uint8_t *) owner->getPrivate()) + offset, to);
 
@@ -1133,24 +1703,24 @@ Reify(JSContext *cx, HandleObject type,
 #undef REIFY_CASES
     return false;
 }
 
 static bool
 ConvertAndCopyTo(JSContext *cx, HandleObject type, HandleValue from, uint8_t *mem)
 {
     if (IsComplexType(type)) {
-        uint8_t *block = NULL;
         if (IsArrayType(type)) {
             if (!ArrayType::convertAndCopyTo(cx, type, from, mem))
                 return false;
-        } else if (type->hasClass(&StructTypeClass)) {
-            JS_ASSERT(0);
+        } else if (IsStructType(type)) {
+            if (!StructType::convertAndCopyTo(cx, type, from, mem))
+                return false;
         } else {
-            JS_ASSERT(0);
+            MOZ_ASSUME_UNREACHABLE("Unexpected complex BinaryData type!");
         }
 
         return true;
     }
 
     JS_ASSERT(&NumericTypeClasses[NUMERICTYPE_UINT8] <= type->getClass() &&
               type->getClass() <= &NumericTypeClasses[NUMERICTYPE_FLOAT64]);
 
@@ -1324,27 +1894,30 @@ InitArrayType(JSContext *cx, HandleObjec
     return proto;
 }
 
 static JSObject *
 InitStructType(JSContext *cx, HandleObject obj)
 {
     JS_ASSERT(obj->isNative());
     Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
-    RootedFunction StructTypeFun(cx,
-        global->createConstructor(cx, createStructType,
+    RootedFunction ctor(cx,
+        global->createConstructor(cx, StructType::construct,
                                   cx->names().StructType, 1));
 
-    if (!StructTypeFun)
+    if (!ctor)
         return NULL;
 
     RootedObject proto(cx);
     RootedObject protoProto(cx);
     if (!SetupComplexHeirarchy(cx, obj, JSProto_StructType,
-                               StructTypeFun, &proto, &protoProto))
+                               ctor, &proto, &protoProto))
+        return NULL;
+
+    if (!JS_DefineFunction(cx, proto, "toString", StructType::toString, 0, 0))
         return NULL;
 
     return proto;
 }
 
 JSObject *
 js_InitBinaryDataClasses(JSContext *cx, HandleObject obj)
 {
--- a/js/src/builtin/BinaryData.h
+++ b/js/src/builtin/BinaryData.h
@@ -270,22 +270,76 @@ class BinaryArray
                                     JSIterateOp enum_op,
                                     MutableHandleValue statep,
                                     MutableHandleId idp);
 
         static JSBool lengthGetter(JSContext *cx, unsigned int argc, jsval *vp);
 
 };
 
-static Class StructTypeClass = {
-    "StructType",
-    JSCLASS_HAS_CACHED_PROTO(JSProto_StructType),
-    JS_PropertyStub,
-    JS_DeletePropertyStub,
-    JS_PropertyStub,
-    JS_StrictPropertyStub,
-    JS_EnumerateStub,
-    JS_ResolveStub,
-    JS_ConvertStub
+class StructType : public JSObject
+{
+    private:
+        static JSObject *create(JSContext *cx, HandleObject structTypeGlobal,
+                                HandleObject fields);
+        /**
+         * Sets up structType slots based on calculated memory size
+         * and alignment and stores fieldmap as well.
+         */
+        static bool layout(JSContext *cx, HandleObject structType,
+                           HandleObject fields);
+
+    public:
+        static Class class_;
+
+        static JSBool construct(JSContext *cx, unsigned int argc, jsval *vp);
+        static JSBool toString(JSContext *cx, unsigned int argc, jsval *vp);
+
+        static bool convertAndCopyTo(JSContext *cx, HandleObject exemplar,
+                                     HandleValue from, uint8_t *mem);
+
+        static bool reify(JSContext *cx, HandleObject type, HandleObject owner,
+                          size_t offset, MutableHandleValue to);
+
+        static void finalize(js::FreeOp *op, JSObject *obj);
+};
+
+class BinaryStruct : public JSObject
+{
+    private:
+        static JSObject *createEmpty(JSContext *cx, HandleObject type);
+        static JSObject *create(JSContext *cx, HandleObject type);
+
+    public:
+        static Class class_;
+
+        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(js::FreeOp *op, JSObject *obj);
+        static void obj_trace(JSTracer *tracer, JSObject *obj);
+
+        static JSBool obj_getGeneric(JSContext *cx, HandleObject obj,
+                                     HandleObject receiver, HandleId id,
+                                     MutableHandleValue vp);
+
+        static JSBool obj_getProperty(JSContext *cx, HandleObject obj,
+                                      HandleObject receiver,
+                                      HandlePropertyName name,
+                                      MutableHandleValue vp);
+
+        static JSBool obj_getSpecial(JSContext *cx, HandleObject obj,
+                                     HandleObject receiver, HandleSpecialId sid,
+                                     MutableHandleValue vp);
+
+        static JSBool obj_setGeneric(JSContext *cx, HandleObject obj, HandleId id,
+                                     MutableHandleValue vp, JSBool strict);
+
+        static JSBool obj_setProperty(JSContext *cx, HandleObject obj,
+                                      HandlePropertyName name,
+                                      MutableHandleValue vp,
+                                      JSBool strict);
+
 };
 }
 
 #endif /* builtin_BinaryData_h */
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -399,8 +399,10 @@ MSG_DEF(JSMSG_BAD_ARROW_ARGS,         34
 MSG_DEF(JSMSG_YIELD_IN_ARROW,         346, 0, JSEXN_SYNTAXERR, "arrow function may not contain yield")
 MSG_DEF(JSMSG_WRONG_VALUE,            347, 2, JSEXN_ERR, "expected {0} but found {1}")
 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")
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1914,17 +1914,17 @@ static const JSStdName standard_class_na
 #ifdef ENABLE_BINARYDATA
     {js_InitBinaryDataClasses,          EAGER_ATOM_AND_CLASP(Type)},
     {js_InitBinaryDataClasses,          EAGER_ATOM_AND_CLASP(Data)},
 #define BINARYDATA_NUMERIC_NAMES(constant_, type_)\
     {js_InitBinaryDataClasses,          EAGER_CLASS_ATOM(type_),      &NumericTypeClasses[constant_]},
     BINARYDATA_FOR_EACH_NUMERIC_TYPES(BINARYDATA_NUMERIC_NAMES)
 #undef BINARYDATA_NUMERIC_NAMES
     {js_InitBinaryDataClasses,          EAGER_CLASS_ATOM(ArrayType),  &js::ArrayType::class_},
-    {js_InitBinaryDataClasses,          EAGER_ATOM_AND_CLASP(StructType)},
+    {js_InitBinaryDataClasses,          EAGER_CLASS_ATOM(StructType), &js::StructType::class_},
 #endif
     {NULL,                      0, NULL}
 };
 
 static const JSStdName object_prototype_names[] = {
     /* Object.prototype properties (global delegates to Object.prototype). */
     {js_InitObjectClass,        EAGER_ATOM(proto), &JSObject::class_},
 #if JS_HAS_TOSOURCE
--- a/js/src/tests/ecma_6/BinaryData/memory.js
+++ b/js/src/tests/ecma_6/BinaryData/memory.js
@@ -33,14 +33,32 @@ function runTests() {
     aaa[0] = [[0,1,2,3,4], [0,1,2,3,4], [0,1,2,3,4], [0,1,2,3,4], [0,1,2,3,4]];
 
     aaa = null;
     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 scopedType = function() {
+        var Point = new StructType({'x': int32, 'y': int32});
+        var aPoint = new Point();
+        aPoint.x = 4;
+        aPoint.y = 5;
+        return aPoint;
+    }
+
+    var point = scopedType();
+    gc();
+    spin();
+    gc();
+    assertEq(point.constructor.fields.x, int32);
+    assertEq(point.constructor.fields.y, int32);
+
     if (typeof reportCompare === "function")
         reportCompare(true, true);
     print("Tests complete");
 }
 
 runTests();
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/BinaryData/structtype.js
@@ -0,0 +1,91 @@
+// |reftest| skip-if(!this.hasOwnProperty("Type"))
+var BUGNUMBER = 578700;
+var summary = 'BinaryData StructType implementation';
+
+function assertThrows(f) {
+    var ok = false;
+    try {
+        f();
+    } catch (exc) {
+        ok = true;
+    }
+    if (!ok)
+        throw new TypeError("Assertion failed: " + f + " did not throw as expected");
+}
+
+function runTests() {
+    print(BUGNUMBER + ": " + summary);
+
+    var S = new StructType({x: int32, y: uint8, z: float64});
+    assertEq(S.__proto__, StructType.prototype);
+    assertEq(S.prototype.__proto__, StructType.prototype.prototype);
+    assertEq(S.toString(), "StructType({x: int32, y: uint8, z: float64})");
+
+    assertEq(S.bytes, 13);
+    assertEq(S.fields.x, int32);
+    assertEq(S.fields.y, uint8);
+    assertEq(S.fields.z, float64);
+
+    var s = new S();
+    assertEq(s.__proto__, S.prototype);
+    s.x = 2;
+    s.y = 255;
+    s.z = 12.342345;
+    assertEq(s.x, 2);
+    assertEq(s.y, 255);
+    assertEq(s.z, 12.342345);
+
+    var Color = new StructType({r: uint8, g: uint8, b: uint8});
+    var white = new Color();
+    white.r = white.g = white.b = 255;
+
+    var Car = new StructType({color: Color, weight: uint32});
+    assertEq(Car.toString(), "StructType({color: StructType({r: uint8, g: uint8, b: uint8}), weight: uint32})");
+
+    var civic = new Car();
+    civic.color = white;
+    civic.weight = 1000;
+
+    assertEq(civic.weight, 1000);
+    assertEq(civic.color.r, 255);
+    assertEq(civic.color.g, 255);
+    assertEq(civic.color.b, 255);
+
+    civic.color = {r: 255, g: 0, b: 0};
+    assertEq(civic.color.r, 255);
+    assertEq(civic.color.g, 0);
+    assertEq(civic.color.b, 0);
+
+    assertThrows(function() civic.color = 5);
+    assertThrows(function() civic.color = []);
+    assertThrows(function() civic.color = {});
+    assertThrows(function() civic.color = {r: 2, g: 2});
+    civic.color = {r: 2, g: 2, b: "foo"};
+    assertEq(civic.color.b, 0);
+    assertThrows(function() civic.color = {r: 2, x: 2, y: 255});
+
+    assertThrows(function() civic.transmission = "automatic");
+
+    // Test structural (not reference) equality
+    var OtherColor = new StructType({r: uint8, g: uint8, b: uint8});
+    var gray = new OtherColor();
+    gray.r = gray.g = gray.b = 0xEE;
+    civic.color = gray;
+    assertEq(civic.color.r, 0xEE);
+    assertEq(civic.color.g, 0xEE);
+    assertEq(civic.color.b, 0xEE);
+
+    var Showroom = new ArrayType(Car, 10);
+    assertEq(Showroom.toString(), "ArrayType(StructType({color: StructType({r: uint8, g: uint8, b: uint8}), weight: uint32}), 10)");
+    var mtvHonda = new Showroom();
+    mtvHonda[0] = {'color': {'r':0, 'g':255, 'b':255}, 'weight': 1300};
+
+    assertEq(mtvHonda[0].color.g, 255);
+    assertEq(mtvHonda[0].weight, 1300);
+
+    if (typeof reportCompare === "function")
+        reportCompare(true, true);
+    print("Tests complete");
+}
+
+runTests();