diff --git a/doc/api/smalloc.markdown b/doc/api/smalloc.markdown index abe4472f5b..96b8546a16 100644 --- a/doc/api/smalloc.markdown +++ b/doc/api/smalloc.markdown @@ -2,10 +2,11 @@ Stability: 1 - Experimental -## smalloc.alloc(length[, receiver]) +## smalloc.alloc(length[, receiver][, type]) -* `length` Number `<= smalloc.kMaxLength` -* `receiver` Object, Optional, Default: `new Object` +* `length` {Number} `<= smalloc.kMaxLength` +* `receiver` {Object}, Optional, Default: `new Object` +* `type` {Enum}, Optional, Default: `Uint8` Returns `receiver` with allocated external array data. If no `receiver` is passed then a new Object will be created and returned. @@ -35,6 +36,16 @@ this it is possible to allocate external array data to more than a plain Object. v8 does not support allocating external array data to an Array, and if passed will throw. +It's possible is to specify the type of external array data you would like. All +possible options are listed in `smalloc.Types`. Example usage: + + var doubleArr = smalloc.alloc(3, smalloc.Types.Double); + + for (var i = 0; i < 3; i++) + doubleArr = i / 10; + + // { '0': 0, '1': 0.1, '2': 0.2 } + ## smalloc.copyOnto(source, sourceStart, dest, destStart, copyLength); * `source` Object with external array allocation @@ -99,3 +110,17 @@ careful. Cryptic errors may arise in applications that are difficult to trace. ## smalloc.kMaxLength Size of maximum allocation. This is also applicable to Buffer creation. + +## smalloc.Types + +Enum of possible external array types. Contains: + +* `Int8` +* `Uint8` +* `Int16` +* `Uint16` +* `Int32` +* `Uint32` +* `Float` +* `Double` +* `Uint8Clamped` diff --git a/lib/smalloc.js b/lib/smalloc.js index 6d68ae3a8b..cc6220161c 100644 --- a/lib/smalloc.js +++ b/lib/smalloc.js @@ -33,22 +33,49 @@ Object.defineProperty(exports, 'kMaxLength', { enumerable: true, value: kMaxLength, writable: false }); +// enumerated values for different external array types +var Types = {}; -function alloc(n, obj) { +Object.defineProperties(Types, { + 'Int8': { enumerable: true, value: 1, writable: false }, + 'Uint8': { enumerable: true, value: 2, writable: false }, + 'Int16': { enumerable: true, value: 3, writable: false }, + 'Uint16': { enumerable: true, value: 4, writable: false }, + 'Int32': { enumerable: true, value: 5, writable: false }, + 'Uint32': { enumerable: true, value: 6, writable: false }, + 'Float': { enumerable: true, value: 7, writable: false }, + 'Double': { enumerable: true, value: 8, writable: false }, + 'Uint8Clamped': { enumerable: true, value: 9, writable: false } +}); + +Object.defineProperty(exports, 'Types', { + enumerable: true, value: Types, writable: false +}); + + +// usage: obj = alloc(n[, obj][, type]); +function alloc(n, obj, type) { n = n >>> 0; if (util.isUndefined(obj)) obj = {}; - else if (util.isPrimitive(obj)) + + if (util.isNumber(obj)) { + type = obj >>> 0; + obj = {}; + } else if (util.isPrimitive(obj)) { throw new TypeError('obj must be an Object'); - if (n > kMaxLength) - throw new RangeError('n > kMaxLength'); + } + + // 1 == v8::kExternalByteArray, 9 == v8::kExternalPixelArray + if (type < 1 || type > 9) + throw new TypeError('unknown external array type: ' + type); if (util.isArray(obj)) throw new TypeError('Arrays are not supported'); - if (obj && !(obj instanceof Object)) - throw new TypeError('obj must be an Object'); + if (n > kMaxLength) + throw new RangeError('n > kMaxLength'); - return smalloc.alloc(obj, n); + return smalloc.alloc(obj, n, type); } diff --git a/src/smalloc.cc b/src/smalloc.cc index 79ef5021bc..e2ce32448c 100644 --- a/src/smalloc.cc +++ b/src/smalloc.cc @@ -35,6 +35,7 @@ namespace node { namespace smalloc { using v8::External; +using v8::ExternalArrayType; using v8::FunctionCallbackInfo; using v8::Handle; using v8::HandleScope; @@ -67,6 +68,32 @@ Cached smalloc_sym; static bool using_alloc_cb; +// return size of external array type, or 0 if unrecognized +static inline size_t ExternalArraySize(enum ExternalArrayType type) { + switch (type) { + case v8::kExternalUnsignedByteArray: + return sizeof(uint8_t); + case v8::kExternalByteArray: + return sizeof(int8_t); + case v8::kExternalShortArray: + return sizeof(int16_t); + case v8::kExternalUnsignedShortArray: + return sizeof(uint16_t); + case v8::kExternalIntArray: + return sizeof(int32_t); + case v8::kExternalUnsignedIntArray: + return sizeof(uint32_t); + case v8::kExternalFloatArray: + return sizeof(float); // NOLINT(runtime/sizeof) + case v8::kExternalDoubleArray: + return sizeof(double); // NOLINT(runtime/sizeof) + case v8::kExternalPixelArray: + return sizeof(uint8_t); + } + return 0; +} + + // copyOnto(source, source_start, dest, dest_start, copy_length) void CopyOnto(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); @@ -87,13 +114,40 @@ void CopyOnto(const FunctionCallbackInfo& args) { size_t source_start = args[1]->Uint32Value(); size_t dest_start = args[3]->Uint32Value(); size_t copy_length = args[4]->Uint32Value(); - size_t source_length = source->GetIndexedPropertiesExternalArrayDataLength(); - size_t dest_length = dest->GetIndexedPropertiesExternalArrayDataLength(); char* source_data = static_cast( source->GetIndexedPropertiesExternalArrayData()); char* dest_data = static_cast( dest->GetIndexedPropertiesExternalArrayData()); + size_t source_length = source->GetIndexedPropertiesExternalArrayDataLength(); + enum ExternalArrayType source_type = + source->GetIndexedPropertiesExternalArrayDataType(); + size_t source_size = ExternalArraySize(source_type); + + size_t dest_length = dest->GetIndexedPropertiesExternalArrayDataLength(); + enum ExternalArrayType dest_type = + dest->GetIndexedPropertiesExternalArrayDataType(); + size_t dest_size = ExternalArraySize(dest_type); + + // optimization for Uint8 arrays (i.e. Buffers) + if (source_size != 1 && dest_size != 1) { + if (source_size == 0) + return ThrowTypeError("unknown source external array type"); + if (dest_size == 0) + return ThrowTypeError("unknown dest external array type"); + + if (source_length * source_size < source_length) + return ThrowRangeError("source_length * source_size overflow"); + if (copy_length * source_size < copy_length) + return ThrowRangeError("copy_length * source_size overflow"); + if (dest_length * dest_size < dest_length) + return ThrowRangeError("dest_length * dest_size overflow"); + + source_length *= source_size; + copy_length *= source_size; + dest_length *= dest_size; + } + // necessary to check in case (source|dest)_start _and_ copy_length overflow if (copy_length > source_length) return ThrowRangeError("copy_length > source_length"); @@ -114,7 +168,9 @@ void CopyOnto(const FunctionCallbackInfo& args) { } -// for internal use: dest._data = sliceOnto(source, dest, start, end); +// dest will always be same type as source +// for internal use: +// dest._data = sliceOnto(source, dest, start, end); void SliceOnto(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); @@ -127,60 +183,92 @@ void SliceOnto(const FunctionCallbackInfo& args) { char* source_data = static_cast( source->GetIndexedPropertiesExternalArrayData()); size_t source_len = source->GetIndexedPropertiesExternalArrayDataLength(); + enum ExternalArrayType source_type = + source->GetIndexedPropertiesExternalArrayDataType(); + size_t source_size = ExternalArraySize(source_type); + + assert(source_size != 0); + size_t start = args[2]->Uint32Value(); size_t end = args[3]->Uint32Value(); size_t length = end - start; + if (source_size > 1) { + assert(length * source_size >= length); + length *= source_size; + } + assert(source_data != NULL || length == 0); assert(end <= source_len); assert(start <= end); dest->SetIndexedPropertiesToExternalArrayData(source_data + start, - kExternalUnsignedByteArray, + source_type, length); args.GetReturnValue().Set(source); } -// for internal use: alloc(obj, n); +// for internal use: +// alloc(obj, n[, type]); void Alloc(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); Local obj = args[0].As(); - size_t length = args[1]->Uint32Value(); // can't perform this check in JS if (obj->HasIndexedPropertiesInExternalArrayData()) return ThrowTypeError("object already has external array data"); - Alloc(obj, length); + size_t length = args[1]->Uint32Value(); + enum ExternalArrayType array_type; + + // it's faster to not pass the default argument then use Uint32Value + if (args[2]->IsUndefined()) + array_type = kExternalUnsignedByteArray; + else + array_type = static_cast(args[2]->Uint32Value()); + + Alloc(obj, length, array_type); args.GetReturnValue().Set(obj); } -void Alloc(Handle obj, size_t length) { +void Alloc(Handle obj, size_t length, enum ExternalArrayType type) { assert(length <= kMaxLength); + size_t type_size = ExternalArraySize(type); + + assert(type_size > 0); + assert(length * type_size >= length); + + length *= type_size; + if (length == 0) - return Alloc(obj, NULL, length); + return Alloc(obj, NULL, length, type); char* data = static_cast(malloc(length)); - if (data == NULL) - FatalError("node::smalloc::Alloc(Handle, size_t)", "Out Of Memory"); - Alloc(obj, data, length); + if (data == NULL) { + FatalError("node::smalloc::Alloc(v8::Handle, size_t," + " ExternalArrayType)", "Out Of Memory"); + } + + Alloc(obj, data, length, type); } -void Alloc(Handle obj, char* data, size_t length) { +void Alloc(Handle obj, + char* data, + size_t length, + enum ExternalArrayType type) { assert(!obj->HasIndexedPropertiesInExternalArrayData()); Persistent p_obj(node_isolate, obj); node_isolate->AdjustAmountOfExternalAllocatedMemory(length); p_obj.MakeWeak(data, TargetCallback); p_obj.MarkIndependent(); p_obj.SetWrapperClassId(ALLOC_ID); - obj->SetIndexedPropertiesToExternalArrayData(data, - kExternalUnsignedByteArray, - length); + size_t size = length / ExternalArraySize(type); + obj->SetIndexedPropertiesToExternalArrayData(data, type, size); } @@ -190,6 +278,12 @@ void TargetCallback(Isolate* isolate, HandleScope handle_scope(isolate); Local obj = PersistentToLocal(isolate, *target); size_t len = obj->GetIndexedPropertiesExternalArrayDataLength(); + enum ExternalArrayType array_type = + obj->GetIndexedPropertiesExternalArrayDataType(); + size_t array_size = ExternalArraySize(array_type); + assert(array_size > 0); + assert(array_size * len >= len); + len *= array_size; if (data != NULL && len > 0) { isolate->AdjustAmountOfExternalAllocatedMemory(-len); free(data); @@ -216,6 +310,14 @@ void AllocDispose(Handle obj) { char* data = static_cast(obj->GetIndexedPropertiesExternalArrayData()); size_t length = obj->GetIndexedPropertiesExternalArrayDataLength(); + enum ExternalArrayType array_type = + obj->GetIndexedPropertiesExternalArrayDataType(); + size_t array_size = ExternalArraySize(array_type); + + assert(array_size > 0); + assert(length * array_size >= length); + + length *= array_size; if (data != NULL) { obj->SetIndexedPropertiesToExternalArrayData(NULL, @@ -229,11 +331,22 @@ void AllocDispose(Handle obj) { } -void Alloc(Handle obj, size_t length, FreeCallback fn, void* hint) { +void Alloc(Handle obj, + size_t length, + FreeCallback fn, + void* hint, + enum ExternalArrayType type) { assert(length <= kMaxLength); + size_t type_size = ExternalArraySize(type); + + assert(type_size > 0); + assert(length * type_size >= length); + + length *= type_size; + char* data = new char[length]; - Alloc(obj, data, length, fn, hint); + Alloc(obj, data, length, fn, hint, type); } @@ -241,7 +354,8 @@ void Alloc(Handle obj, char* data, size_t length, FreeCallback fn, - void* hint) { + void* hint, + enum ExternalArrayType type) { assert(!obj->HasIndexedPropertiesInExternalArrayData()); if (smalloc_sym.IsEmpty()) { @@ -260,9 +374,8 @@ void Alloc(Handle obj, cb_info->p_obj.MakeWeak(cb_info, TargetFreeCallback); cb_info->p_obj.MarkIndependent(); cb_info->p_obj.SetWrapperClassId(ALLOC_ID); - obj->SetIndexedPropertiesToExternalArrayData(data, - kExternalUnsignedByteArray, - length); + size_t size = length / ExternalArraySize(type); + obj->SetIndexedPropertiesToExternalArrayData(data, type, size); } @@ -273,6 +386,14 @@ void TargetFreeCallback(Isolate* isolate, Local obj = PersistentToLocal(isolate, *target); char* data = static_cast(obj->GetIndexedPropertiesExternalArrayData()); size_t len = obj->GetIndexedPropertiesExternalArrayDataLength(); + enum ExternalArrayType array_type = + obj->GetIndexedPropertiesExternalArrayDataType(); + size_t array_size = ExternalArraySize(array_type); + assert(array_size > 0); + if (array_size > 1) { + assert(len * array_size > len); + len *= array_size; + } isolate->AdjustAmountOfExternalAllocatedMemory(-(len + sizeof(*cb_info))); cb_info->p_obj.Dispose(); cb_info->cb(data, cb_info->hint); diff --git a/src/smalloc.h b/src/smalloc.h index 47ab4f2d8c..d1b04eb897 100644 --- a/src/smalloc.h +++ b/src/smalloc.h @@ -44,18 +44,32 @@ NODE_EXTERN typedef void (*FreeCallback)(char* data, void* hint); /** * Allocate external memory and set to passed object. If data is passed then * will use that instead of allocating new. + * + * When you pass an ExternalArrayType and data, Alloc assumes data length is + * the same as data length * ExternalArrayType length. */ -NODE_EXTERN void Alloc(v8::Handle obj, size_t length); -NODE_EXTERN void Alloc(v8::Handle obj, char* data, size_t length); +NODE_EXTERN void Alloc(v8::Handle obj, + size_t length, + enum v8::ExternalArrayType type = + v8::kExternalUnsignedByteArray); +NODE_EXTERN void Alloc(v8::Handle obj, + char* data, + size_t length, + enum v8::ExternalArrayType type = + v8::kExternalUnsignedByteArray); NODE_EXTERN void Alloc(v8::Handle obj, size_t length, FreeCallback fn, - void* hint); + void* hint, + enum v8::ExternalArrayType type = + v8::kExternalUnsignedByteArray); NODE_EXTERN void Alloc(v8::Handle obj, char* data, size_t length, FreeCallback fn, - void* hint); + void* hint, + enum v8::ExternalArrayType type = + v8::kExternalUnsignedByteArray); /** * Free memory associated with an externally allocated object. If no external diff --git a/test/simple/test-smalloc.js b/test/simple/test-smalloc.js index ceba0a4f5d..112afc14c6 100644 --- a/test/simple/test-smalloc.js +++ b/test/simple/test-smalloc.js @@ -28,6 +28,7 @@ var alloc = smalloc.alloc; var dispose = smalloc.dispose; var copyOnto = smalloc.copyOnto; var kMaxLength = smalloc.kMaxLength; +var Types = smalloc.Types; // sliceOnto is volatile and cannot be exposed to users. var sliceOnto = process.binding('smalloc').sliceOnto; @@ -63,6 +64,60 @@ for (var i = 0; i < 5; i++) assert.equal(b[i], i); +var b = alloc(1, Types.Uint8); +b[0] = 256; +assert.equal(b[0], 0); +assert.equal(b[1], undefined); + + +var b = alloc(1, Types.Int8); +b[0] = 128; +assert.equal(b[0], -128); +assert.equal(b[1], undefined); + + +var b = alloc(1, Types.Uint16); +b[0] = 65536; +assert.equal(b[0], 0); +assert.equal(b[1], undefined); + + +var b = alloc(1, Types.Int16); +b[0] = 32768; +assert.equal(b[0], -32768); +assert.equal(b[1], undefined); + + +var b = alloc(1, Types.Uint32); +b[0] = 4294967296; +assert.equal(b[0], 0); +assert.equal(b[1], undefined); + + +var b = alloc(1, Types.Int32); +b[0] = 2147483648; +assert.equal(b[0], -2147483648); +assert.equal(b[1], undefined); + + +var b = alloc(1, Types.Float); +b[0] = 0.1111111111111111; +assert.equal(b[0], 0.1111111119389534); +assert.equal(b[1], undefined); + + +var b = alloc(1, Types.Double); +b[0] = 0.1111111111111111; +assert.equal(b[0], 0.1111111111111111); +assert.equal(b[1], undefined); + + +var b = alloc(1, Types.Uint8Clamped); +b[0] = 300; +assert.equal(b[0], 255); +assert.equal(b[1], undefined); + + var b = alloc(6, {}); var c0 = {}; var c1 = {}; @@ -93,6 +148,14 @@ for (var i = 0; i < 6; i++) { } +var b = alloc(1, Types.Double); +var c = alloc(2, Types.Uint32); +c[0] = 2576980378; +c[1] = 1069128089; +copyOnto(c, 0, b, 0, 2); +assert.equal(b[0], 0.1); + + // verify alloc throws properly // arrays are not supported @@ -123,9 +186,6 @@ assert.throws(function() { assert.throws(function() { alloc(1, 'a'); }, TypeError); -assert.throws(function() { - alloc(1, 1); -}, TypeError); assert.throws(function() { alloc(1, true); }, TypeError); @@ -140,6 +200,14 @@ alloc(1, /abc/); alloc(1, new Date()); +// range check on external array enumeration +assert.throws(function() { + alloc(1, 0); +}, TypeError); +assert.throws(function() { + alloc(1, 10); +}, TypeError); + // very copyOnto throws properly // source must have data @@ -190,6 +258,12 @@ assert.throws(function() { }, RangeError); +// copy_length * array_size <= dest_length +assert.throws(function() { + copyOnto(alloc(2, Types.Double), 0, alloc(2, Types.Uint32), 0, 2); +}, RangeError); + + // test disposal var b = alloc(5, {}); dispose(b);