Browse Source

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.
v0.11.6-release
Trevor Norris 12 years ago
parent
commit
cec81593d7
  1. 31
      doc/api/smalloc.markdown
  2. 41
      lib/smalloc.js
  3. 165
      src/smalloc.cc
  4. 22
      src/smalloc.h
  5. 80
      test/simple/test-smalloc.js

31
doc/api/smalloc.markdown

@ -2,10 +2,11 @@
Stability: 1 - Experimental Stability: 1 - Experimental
## smalloc.alloc(length[, receiver]) ## smalloc.alloc(length[, receiver][, type])
* `length` Number `<= smalloc.kMaxLength` * `length` {Number} `<= smalloc.kMaxLength`
* `receiver` Object, Optional, Default: `new Object` * `receiver` {Object}, Optional, Default: `new Object`
* `type` {Enum}, Optional, Default: `Uint8`
Returns `receiver` with allocated external array data. If no `receiver` is Returns `receiver` with allocated external array data. If no `receiver` is
passed then a new Object will be created and returned. 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 v8 does not support allocating external array data to an Array, and if passed
will throw. 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); ## smalloc.copyOnto(source, sourceStart, dest, destStart, copyLength);
* `source` Object with external array allocation * `source` Object with external array allocation
@ -99,3 +110,17 @@ careful. Cryptic errors may arise in applications that are difficult to trace.
## smalloc.kMaxLength ## smalloc.kMaxLength
Size of maximum allocation. This is also applicable to Buffer creation. 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`

41
lib/smalloc.js

@ -33,22 +33,49 @@ Object.defineProperty(exports, 'kMaxLength', {
enumerable: true, value: kMaxLength, writable: false 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; n = n >>> 0;
if (util.isUndefined(obj)) if (util.isUndefined(obj))
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'); 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)) if (util.isArray(obj))
throw new TypeError('Arrays are not supported'); throw new TypeError('Arrays are not supported');
if (obj && !(obj instanceof Object)) if (n > kMaxLength)
throw new TypeError('obj must be an Object'); throw new RangeError('n > kMaxLength');
return smalloc.alloc(obj, n); return smalloc.alloc(obj, n, type);
} }

165
src/smalloc.cc

@ -35,6 +35,7 @@ namespace node {
namespace smalloc { namespace smalloc {
using v8::External; using v8::External;
using v8::ExternalArrayType;
using v8::FunctionCallbackInfo; using v8::FunctionCallbackInfo;
using v8::Handle; using v8::Handle;
using v8::HandleScope; using v8::HandleScope;
@ -67,6 +68,32 @@ Cached<String> smalloc_sym;
static bool using_alloc_cb; 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) // copyOnto(source, source_start, dest, dest_start, copy_length)
void CopyOnto(const FunctionCallbackInfo<Value>& args) { void CopyOnto(const FunctionCallbackInfo<Value>& args) {
HandleScope scope(node_isolate); HandleScope scope(node_isolate);
@ -87,13 +114,40 @@ void CopyOnto(const FunctionCallbackInfo<Value>& args) {
size_t source_start = args[1]->Uint32Value(); size_t source_start = args[1]->Uint32Value();
size_t dest_start = args[3]->Uint32Value(); size_t dest_start = args[3]->Uint32Value();
size_t copy_length = args[4]->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<char*>( char* source_data = static_cast<char*>(
source->GetIndexedPropertiesExternalArrayData()); source->GetIndexedPropertiesExternalArrayData());
char* dest_data = static_cast<char*>( char* dest_data = static_cast<char*>(
dest->GetIndexedPropertiesExternalArrayData()); 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 // necessary to check in case (source|dest)_start _and_ copy_length overflow
if (copy_length > source_length) if (copy_length > source_length)
return ThrowRangeError("copy_length > source_length"); return ThrowRangeError("copy_length > source_length");
@ -114,7 +168,9 @@ void CopyOnto(const FunctionCallbackInfo<Value>& 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<Value>& args) { void SliceOnto(const FunctionCallbackInfo<Value>& args) {
HandleScope scope(node_isolate); HandleScope scope(node_isolate);
@ -127,60 +183,92 @@ void SliceOnto(const FunctionCallbackInfo<Value>& args) {
char* source_data = static_cast<char*>( char* source_data = static_cast<char*>(
source->GetIndexedPropertiesExternalArrayData()); source->GetIndexedPropertiesExternalArrayData());
size_t source_len = source->GetIndexedPropertiesExternalArrayDataLength(); 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 start = args[2]->Uint32Value();
size_t end = args[3]->Uint32Value(); size_t end = args[3]->Uint32Value();
size_t length = end - start; size_t length = end - start;
if (source_size > 1) {
assert(length * source_size >= length);
length *= source_size;
}
assert(source_data != NULL || length == 0); assert(source_data != NULL || length == 0);
assert(end <= source_len); assert(end <= source_len);
assert(start <= end); assert(start <= end);
dest->SetIndexedPropertiesToExternalArrayData(source_data + start, dest->SetIndexedPropertiesToExternalArrayData(source_data + start,
kExternalUnsignedByteArray, source_type,
length); length);
args.GetReturnValue().Set(source); args.GetReturnValue().Set(source);
} }
// for internal use: alloc(obj, n); // for internal use:
// alloc(obj, n[, type]);
void Alloc(const FunctionCallbackInfo<Value>& args) { void Alloc(const FunctionCallbackInfo<Value>& args) {
HandleScope scope(node_isolate); HandleScope scope(node_isolate);
Local<Object> obj = args[0].As<Object>(); Local<Object> obj = args[0].As<Object>();
size_t length = args[1]->Uint32Value();
// can't perform this check in JS // can't perform this check in JS
if (obj->HasIndexedPropertiesInExternalArrayData()) if (obj->HasIndexedPropertiesInExternalArrayData())
return ThrowTypeError("object already has external array data"); 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<ExternalArrayType>(args[2]->Uint32Value());
Alloc(obj, length, array_type);
args.GetReturnValue().Set(obj); args.GetReturnValue().Set(obj);
} }
void Alloc(Handle<Object> obj, size_t length) { void Alloc(Handle<Object> obj, size_t length, enum ExternalArrayType type) {
assert(length <= kMaxLength); assert(length <= kMaxLength);
size_t type_size = ExternalArraySize(type);
assert(type_size > 0);
assert(length * type_size >= length);
length *= type_size;
if (length == 0) if (length == 0)
return Alloc(obj, NULL, length); return Alloc(obj, NULL, length, type);
char* data = static_cast<char*>(malloc(length)); char* data = static_cast<char*>(malloc(length));
if (data == NULL) if (data == NULL) {
FatalError("node::smalloc::Alloc(Handle<Object>, size_t)", "Out Of Memory"); FatalError("node::smalloc::Alloc(v8::Handle<v8::Object>, size_t,"
Alloc(obj, data, length); " ExternalArrayType)", "Out Of Memory");
} }
Alloc(obj, data, length, type);
}
void Alloc(Handle<Object> obj, char* data, size_t length) {
void Alloc(Handle<Object> obj,
char* data,
size_t length,
enum ExternalArrayType type) {
assert(!obj->HasIndexedPropertiesInExternalArrayData()); assert(!obj->HasIndexedPropertiesInExternalArrayData());
Persistent<Object> p_obj(node_isolate, obj); Persistent<Object> p_obj(node_isolate, obj);
node_isolate->AdjustAmountOfExternalAllocatedMemory(length); node_isolate->AdjustAmountOfExternalAllocatedMemory(length);
p_obj.MakeWeak(data, TargetCallback); p_obj.MakeWeak(data, TargetCallback);
p_obj.MarkIndependent(); p_obj.MarkIndependent();
p_obj.SetWrapperClassId(ALLOC_ID); p_obj.SetWrapperClassId(ALLOC_ID);
obj->SetIndexedPropertiesToExternalArrayData(data, size_t size = length / ExternalArraySize(type);
kExternalUnsignedByteArray, obj->SetIndexedPropertiesToExternalArrayData(data, type, size);
length);
} }
@ -190,6 +278,12 @@ void TargetCallback(Isolate* isolate,
HandleScope handle_scope(isolate); HandleScope handle_scope(isolate);
Local<Object> obj = PersistentToLocal(isolate, *target); Local<Object> obj = PersistentToLocal(isolate, *target);
size_t len = obj->GetIndexedPropertiesExternalArrayDataLength(); 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) { if (data != NULL && len > 0) {
isolate->AdjustAmountOfExternalAllocatedMemory(-len); isolate->AdjustAmountOfExternalAllocatedMemory(-len);
free(data); free(data);
@ -216,6 +310,14 @@ void AllocDispose(Handle<Object> obj) {
char* data = static_cast<char*>(obj->GetIndexedPropertiesExternalArrayData()); char* data = static_cast<char*>(obj->GetIndexedPropertiesExternalArrayData());
size_t length = obj->GetIndexedPropertiesExternalArrayDataLength(); 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) { if (data != NULL) {
obj->SetIndexedPropertiesToExternalArrayData(NULL, obj->SetIndexedPropertiesToExternalArrayData(NULL,
@ -229,11 +331,22 @@ void AllocDispose(Handle<Object> obj) {
} }
void Alloc(Handle<Object> obj, size_t length, FreeCallback fn, void* hint) { void Alloc(Handle<Object> obj,
size_t length,
FreeCallback fn,
void* hint,
enum ExternalArrayType type) {
assert(length <= kMaxLength); 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]; 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<Object> obj,
char* data, char* data,
size_t length, size_t length,
FreeCallback fn, FreeCallback fn,
void* hint) { void* hint,
enum ExternalArrayType type) {
assert(!obj->HasIndexedPropertiesInExternalArrayData()); assert(!obj->HasIndexedPropertiesInExternalArrayData());
if (smalloc_sym.IsEmpty()) { if (smalloc_sym.IsEmpty()) {
@ -260,9 +374,8 @@ void Alloc(Handle<Object> obj,
cb_info->p_obj.MakeWeak(cb_info, TargetFreeCallback); cb_info->p_obj.MakeWeak(cb_info, TargetFreeCallback);
cb_info->p_obj.MarkIndependent(); cb_info->p_obj.MarkIndependent();
cb_info->p_obj.SetWrapperClassId(ALLOC_ID); cb_info->p_obj.SetWrapperClassId(ALLOC_ID);
obj->SetIndexedPropertiesToExternalArrayData(data, size_t size = length / ExternalArraySize(type);
kExternalUnsignedByteArray, obj->SetIndexedPropertiesToExternalArrayData(data, type, size);
length);
} }
@ -273,6 +386,14 @@ void TargetFreeCallback(Isolate* isolate,
Local<Object> obj = PersistentToLocal(isolate, *target); Local<Object> obj = PersistentToLocal(isolate, *target);
char* data = static_cast<char*>(obj->GetIndexedPropertiesExternalArrayData()); char* data = static_cast<char*>(obj->GetIndexedPropertiesExternalArrayData());
size_t len = obj->GetIndexedPropertiesExternalArrayDataLength(); 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))); isolate->AdjustAmountOfExternalAllocatedMemory(-(len + sizeof(*cb_info)));
cb_info->p_obj.Dispose(); cb_info->p_obj.Dispose();
cb_info->cb(data, cb_info->hint); cb_info->cb(data, cb_info->hint);

22
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 * Allocate external memory and set to passed object. If data is passed then
* will use that instead of allocating new. * 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<v8::Object> obj, size_t length); NODE_EXTERN void Alloc(v8::Handle<v8::Object> obj,
NODE_EXTERN void Alloc(v8::Handle<v8::Object> obj, char* data, size_t length); size_t length,
enum v8::ExternalArrayType type =
v8::kExternalUnsignedByteArray);
NODE_EXTERN void Alloc(v8::Handle<v8::Object> obj,
char* data,
size_t length,
enum v8::ExternalArrayType type =
v8::kExternalUnsignedByteArray);
NODE_EXTERN void Alloc(v8::Handle<v8::Object> obj, NODE_EXTERN void Alloc(v8::Handle<v8::Object> obj,
size_t length, size_t length,
FreeCallback fn, FreeCallback fn,
void* hint); void* hint,
enum v8::ExternalArrayType type =
v8::kExternalUnsignedByteArray);
NODE_EXTERN void Alloc(v8::Handle<v8::Object> obj, NODE_EXTERN void Alloc(v8::Handle<v8::Object> obj,
char* data, char* data,
size_t length, size_t length,
FreeCallback fn, FreeCallback fn,
void* hint); void* hint,
enum v8::ExternalArrayType type =
v8::kExternalUnsignedByteArray);
/** /**
* Free memory associated with an externally allocated object. If no external * Free memory associated with an externally allocated object. If no external

80
test/simple/test-smalloc.js

@ -28,6 +28,7 @@ var alloc = smalloc.alloc;
var dispose = smalloc.dispose; var dispose = smalloc.dispose;
var copyOnto = smalloc.copyOnto; var copyOnto = smalloc.copyOnto;
var kMaxLength = smalloc.kMaxLength; var kMaxLength = smalloc.kMaxLength;
var Types = smalloc.Types;
// sliceOnto is volatile and cannot be exposed to users. // sliceOnto is volatile and cannot be exposed to users.
var sliceOnto = process.binding('smalloc').sliceOnto; var sliceOnto = process.binding('smalloc').sliceOnto;
@ -63,6 +64,60 @@ for (var i = 0; i < 5; i++)
assert.equal(b[i], 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 b = alloc(6, {});
var c0 = {}; var c0 = {};
var c1 = {}; 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 // verify alloc throws properly
// arrays are not supported // arrays are not supported
@ -123,9 +186,6 @@ assert.throws(function() {
assert.throws(function() { assert.throws(function() {
alloc(1, 'a'); alloc(1, 'a');
}, TypeError); }, TypeError);
assert.throws(function() {
alloc(1, 1);
}, TypeError);
assert.throws(function() { assert.throws(function() {
alloc(1, true); alloc(1, true);
}, TypeError); }, TypeError);
@ -140,6 +200,14 @@ alloc(1, /abc/);
alloc(1, new Date()); 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 // very copyOnto throws properly
// source must have data // source must have data
@ -190,6 +258,12 @@ assert.throws(function() {
}, RangeError); }, 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 // test disposal
var b = alloc(5, {}); var b = alloc(5, {});
dispose(b); dispose(b);

Loading…
Cancel
Save