# HG changeset patch # Parent 5f58728b03c271157daddd91f3ab04940bade270 # User Nikhil Marathe Bug 578700 - binary array method implementations diff --git a/js/src/jsbinarydata.cpp b/js/src/jsbinarydata.cpp --- a/js/src/jsbinarydata.cpp +++ b/js/src/jsbinarydata.cpp @@ -75,16 +75,27 @@ ReportTypeError(JSContext *cx, Value fro static bool ReportTypeError(JSContext *cx, Value fromValue, HandleObject exemplar) { RootedValue v(cx, ObjectValue(*exemplar)); ReportTypeError(cx, fromValue, ToString(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 @@ -340,17 +351,18 @@ NumericType::convert(JSContext *cx, H { if (val.isBoolean()) { *converted = val.toBoolean() ? 1 : 0; return true; } if (val.isNumber()) { // NOTE is this the right way to do it? - // Clarify semantics. + // TODO needs exact precision checks + // i.e. should uint8(3.14) error or truncate to 3? if (val.isInt32()) { int num = val.toInt32(); if (InRange(num)) { *converted = T(num); return true; } } else { double num = val.toDouble(); @@ -397,17 +409,17 @@ NumericType::cast(JSContext *cx, Hand double d; JS_ValueToNumber(cx, val, &d); if (!mozilla::IsNaN(d)) { // [[CCast]] *casted = (T) (d); return true; } - + // If the string is non-numeric, execution continues to the type error // below. } Class *typeClass = typeToClass(); ReportTypeError(cx, val, typeClass->name); return false; } @@ -704,19 +716,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) @@ -764,23 +782,76 @@ 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) + return false; + + RootedObject thisObj(cx, args.thisv().toObjectOrNull()); + if (!IsBlock(thisObj)) + 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, "ArrayType"); // TODO use toString + return false; + } + + args.rval().setUndefined(); + return true; } JSBool ArrayType::repeat(JSContext *cx, unsigned int argc, Value *vp) { - return false; + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() < 1) + 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; + } + + JSObject *binaryArray = BinaryArray::create(cx, thisObj); + // TODO refactor with BinaryArray::fill() + RootedValue val(cx, args[0]); + RootedObject elementType(cx, ArrayType::elementType(cx, thisObj)); + size_t elementSize = GetMemSize(cx, elementType); + uint8_t *base = (uint8_t*) binaryArray->getPrivate(); + + // set a[0] = [[Convert]](val) + if (!ConvertAndCopyTo(cx, elementType, val, base)) { + ReportTypeError(cx, args[0], "XXX"); // TODO use toString + return false; + } + + for (uint32_t i = 1; i < ArrayType::length(cx, thisObj); i++) { + uint8_t *dest = base + elementSize * i; + memcpy(dest, base, elementSize); + } + 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()); @@ -922,35 +993,136 @@ 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 + * + * TODO: should this duplicate elements or should underlying buffer be shared? + * For now it's duplicated + */ +JSBool BinaryArray::subarray(JSContext *cx, unsigned int argc, Value *vp) { JS_ASSERT(0); return false; -} + /* + CallArgs args = CallArgsFromVp(argc, vp); -JSBool -BinaryArray::subarray(JSContext *cx, unsigned int argc, Value *vp) -{ - JS_ASSERT(0); - return false; + if (args.length() < 1) + return false; // TODO error about arguments? + + if (!args[0].isInt32()) + return false; + + RootedObject thisObj(cx, &args.thisv().toObject()); + if (!IsBinaryArray(thisObj)) + return false; // TODO error + + 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; + + if (args.length() >= 2) { + if (!args[1].isInt32()) + 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 subArrayType(cx, ArrayType::create(cx, elementType, sublength)); + if (!subArrayType) + return false; // TODO error + + RootedObject subarray(cx, BinaryArray::create(cx, subArrayType)); + if (!subarray) + return false; // TODO error + + int32_t elementSize = GetMemSize(cx, elementType); + uint8_t *thisOffset = ((uint8_t*) thisObj->getPrivate()) + elementSize * begin; + uint8_t *subarrayOffset = ((uint8_t*) subarray->getPrivate()); + memcpy(subarrayOffset, thisOffset, elementSize * sublength); + + 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) + 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 (!IsSame(cx, funArrayType, type)) { + ReportTypeError(cx, ObjectValue(*thisObj), "XXX"); // TODO use toString + return false; + } + + RootedValue val(cx, args[0]); + RootedObject elementType(cx, ArrayType::elementType(cx, type)); + for (uint32_t i = 0; i < ArrayType::length(cx, type); i++) { + uint32_t offset = GetMemSize(cx, elementType) * i; + if (!ConvertAndCopyTo(cx, elementType, val, ((uint8_t*) thisObj->getPrivate()) + offset)) { + ReportTypeError(cx, args[0], "Binary data"); // TODO use toString and point out exact elementType + return false; + } + } + args.rval().setUndefined(); + return true; } JSBool BinaryArray::obj_lookupGeneric(JSContext *cx, HandleObject obj, HandleId id, MutableHandleObject objp, MutableHandleShape propp) { JS_ASSERT(IsBinaryArray(obj)); RootedObject type(cx, GetType(obj)); @@ -1730,17 +1902,17 @@ BinaryStruct::obj_setGeneric(JSContext * FieldInfo fieldInfo; if (!LookupFieldList(fieldList, id, &fieldInfo)) return false; // TODO SPEC FIX: are extra properties allowed? JSString *str = JSID_TO_STRING(fieldInfo.name); 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 @@ -1953,17 +2125,27 @@ InitArrayType(JSContext *cx, HandleObjec 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; } diff --git a/js/src/jsbinarydata.h b/js/src/jsbinarydata.h --- a/js/src/jsbinarydata.h +++ b/js/src/jsbinarydata.h @@ -141,21 +141,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, @@ -165,34 +165,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, diff --git a/js/src/tests/ecma_6/BinaryData/arraytype.js b/js/src/tests/ecma_6/BinaryData/arraytype.js --- a/js/src/tests/ecma_6/BinaryData/arraytype.js +++ b/js/src/tests/ecma_6/BinaryData/arraytype.js @@ -28,37 +28,44 @@ assertEq(A.__proto__, ArrayType.prototyp 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); +}); assertThrows(function() a[i] = 257); 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/)); @@ -87,17 +94,103 @@ assertThrows(function() mario[1][1] = [] // ok this is just for kicks var AllSprites = new ArrayType(Sprite, 65536); var as = new AllSprites(); assertEq(as.length, 65536); -as.foo = "bar"; +var Vec3 = new ArrayType(float32, 3); +var Sprite = new ArrayType(Vec3, 3); // say for position, velocity, and direction +assertEq(Sprite.elementType, Vec3); +assertEq(Sprite.elementType.elementType, float32); + +var mario = new Sprite(); +// setting using binary data +mario[0] = new Vec3([1, 0, 0]); +// setting using JS array conversion +mario[1] = [1, 1.414, 3.14]; + +assertEq(mario[0].length, 3); +assertEq(mario[0][0], 1); +assertEq(mario[0][1], 0); +assertEq(mario[0][2], 0); + +assertThrows(function() mario[1] = 5); +assertThrows(function() mario[1][1] = []); + +// ok this is just for kicks +var AllSprites = new ArrayType(Sprite, 65536); +var as = new AllSprites(); + +// test methods +var c = new A(); +c.fill(3); +for (var i = 0; i < c.length; i++) + assertEq(c[i], 3); + +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() A.repeat(256)); +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); + +// 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 indexPropDesc = Object.getOwnPropertyDescriptor(as, '0'); assertEq(typeof indexPropDesc == "undefined", false); assertEq(indexPropDesc.configurable, false); assertEq(indexPropDesc.enumerable, true); assertEq(indexPropDesc.writable, true); diff --git a/js/src/tests/ecma_6/BinaryData/memory.js b/js/src/tests/ecma_6/BinaryData/memory.js --- a/js/src/tests/ecma_6/BinaryData/memory.js +++ b/js/src/tests/ecma_6/BinaryData/memory.js @@ -33,16 +33,27 @@ aaa[0] = [[0,1,2,3,4], [0,1,2,3,4], [0,1 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 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; }