From cec81593d7e42c83b9498be5835defb24d1743bd Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Fri, 19 Jul 2013 16:15:25 -0700 Subject: [PATCH] smalloc: allow different external array types smalloc.alloc now accepts an optional third argument which allows specifying the type of array that should be allocated. All available types are now located on smalloc.Types. --- doc/api/smalloc.markdown | 31 ++++++- lib/smalloc.js | 41 +++++++-- src/smalloc.cc | 165 +++++++++++++++++++++++++++++++----- src/smalloc.h | 22 ++++- test/simple/test-smalloc.js | 80 ++++++++++++++++- 5 files changed, 300 insertions(+), 39 deletions(-) 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);