From 5b05429bf0fa210fa8ac12ddff0d824b310da893 Mon Sep 17 00:00:00 2001 From: Mikael Bourges-Sevenier Date: Sat, 31 Dec 2011 00:30:42 -0800 Subject: [PATCH] typed arrays: add Buffer -> TypedArray constructor - create a typed array from a node::Buffer object - update TypedArray::set() to spec - add TypedArray::get() method --- src/node_extensions.h | 1 + src/v8_typed_array.cc | 191 ++++++++++++++++++++----------- test/simple/test-typed-arrays.js | 110 ++++++++++++++++++ 3 files changed, 238 insertions(+), 64 deletions(-) create mode 100644 test/simple/test-typed-arrays.js diff --git a/src/node_extensions.h b/src/node_extensions.h index 1d5b132238..c7833d43ad 100644 --- a/src/node_extensions.h +++ b/src/node_extensions.h @@ -22,6 +22,7 @@ NODE_EXT_LIST_START NODE_EXT_LIST_ITEM(node_buffer) +NODE_EXT_LIST_ITEM(node_typed_array) #if HAVE_OPENSSL NODE_EXT_LIST_ITEM(node_crypto) #endif diff --git a/src/v8_typed_array.cc b/src/v8_typed_array.cc index 00eff8705d..c25f5451e5 100644 --- a/src/v8_typed_array.cc +++ b/src/v8_typed_array.cc @@ -25,6 +25,7 @@ #include #include "v8_typed_array.h" +#include "node_buffer.h" namespace { @@ -151,6 +152,7 @@ class TypedArray { v8::Local default_signature = v8::Signature::New(ft_cache); static BatchedMethods methods[] = { + { "get", &TypedArray::get }, { "set", &TypedArray::set }, { "slice", &TypedArray::subarray }, { "subarray", &TypedArray::subarray }, @@ -183,14 +185,16 @@ class TypedArray { unsigned int length = 0; unsigned int byte_offset = 0; - if (ArrayBuffer::HasInstance(args[0])) { // ArrayBuffer constructor. + // [m1k3] added support for Buffer constructor + if (node::Buffer::HasInstance(args[0]) + || ArrayBuffer::HasInstance(args[0])) { // ArrayBuffer constructor. buffer = v8::Local::Cast(args[0]); unsigned int buflen = buffer->GetIndexedPropertiesExternalArrayDataLength(); - if (args[1]->Int32Value() < 0) + if (!args[1]->IsUndefined() && args[1]->Int32Value() < 0) return ThrowRangeError("Byte offset out of range."); - byte_offset = args[1]->Uint32Value(); + byte_offset = args[1]->IsUndefined() ? 0 : args[1]->Uint32Value(); if (!checkAlignment(byte_offset, TBytes)) return ThrowRangeError("Byte offset is not aligned."); @@ -214,10 +218,11 @@ class TypedArray { } // TODO(deanm): Error check. - void* buf = buffer->GetPointerFromInternalField(0); + void* buf = buffer->GetIndexedPropertiesExternalArrayData(); args.This()->SetIndexedPropertiesToExternalArrayData( reinterpret_cast(buf) + byte_offset, TEAType, length); - } else if (args[0]->IsObject()) { // TypedArray / type[] constructor. + } + else if (args[0]->IsObject()) { // TypedArray / type[] constructor. v8::Local obj = v8::Local::Cast(args[0]); length = obj->Get(v8::String::New("length"))->Uint32Value(); @@ -277,68 +282,121 @@ class TypedArray { return args.This(); } + static v8::Handle get(const v8::Arguments& args) { + if (args.Length() < 1) + return ThrowError("Wrong number of arguments."); + + if (args[0]->IsNumber()) { + unsigned int index = args[0]->Uint32Value(); + void* ptr = args.This()->GetIndexedPropertiesExternalArrayData(); + + if (TEAType == v8::kExternalByteArray) + return v8::Integer::New(reinterpret_cast(ptr)[index]); + else if (TEAType == v8::kExternalUnsignedByteArray) + return v8::Integer::New(reinterpret_cast(ptr)[index]); + else if (TEAType == v8::kExternalShortArray) + return v8::Integer::New(reinterpret_cast(ptr)[index]); + else if (TEAType == v8::kExternalUnsignedShortArray) + return v8::Integer::New(reinterpret_cast(ptr)[index]); + else if (TEAType == v8::kExternalIntArray) + return v8::Integer::New(reinterpret_cast(ptr)[index]); + else if (TEAType == v8::kExternalUnsignedIntArray) + return v8::Integer::New(reinterpret_cast(ptr)[index]); + else if (TEAType == v8::kExternalFloatArray) + return v8::Number::New(reinterpret_cast(ptr)[index]); + else if (TEAType == v8::kExternalDoubleArray) + return v8::Number::New(reinterpret_cast(ptr)[index]); + } + return v8::Undefined(); + } + static v8::Handle set(const v8::Arguments& args) { if (args.Length() < 1) return ThrowError("Wrong number of arguments."); - if (!args[0]->IsObject()) - return ThrowTypeError("Type error."); - - v8::Handle obj = v8::Handle::Cast(args[0]); - - if (TypedArray::HasInstance(obj)) { // ArrayBufferView. - v8::Handle src_buffer = v8::Handle::Cast( - obj->Get(v8::String::New("buffer"))); - v8::Handle dst_buffer = v8::Handle::Cast( - args.This()->Get(v8::String::New("buffer"))); - - if (args[1]->Int32Value() < 0) - return ThrowRangeError("Offset may not be negative."); - - unsigned int offset = args[1]->Uint32Value(); - unsigned int src_length = - obj->Get(v8::String::New("length"))->Uint32Value(); - unsigned int dst_length = - args.This()->Get(v8::String::New("length"))->Uint32Value(); - if (offset > dst_length) - return ThrowRangeError("Offset out of range."); - - if (src_length > dst_length - offset) - return ThrowRangeError("Offset/length out of range."); - - // We don't want to get the buffer pointer, because that means we'll have - // to just do the calculations for byteOffset / byteLength again. - // Instead just use the pointer on the external array data. - void* src_ptr = obj->GetIndexedPropertiesExternalArrayData(); - void* dst_ptr = args.This()->GetIndexedPropertiesExternalArrayData(); - - // From the spec: - // If the input array is a TypedArray, the two arrays may use the same - // underlying ArrayBuffer. In this situation, setting the values takes - // place as if all the data is first copied into a temporary buffer that - // does not overlap either of the arrays, and then the data from the - // temporary buffer is copied into the current array. - memmove(reinterpret_cast(dst_ptr) + offset * TBytes, - src_ptr, src_length * TBytes); - } else { // type[] - if (args[1]->Int32Value() < 0) - return ThrowRangeError("Offset may not be negative."); - - unsigned int src_length = - obj->Get(v8::String::New("length"))->Uint32Value(); - unsigned int dst_length = - args.This()->Get(v8::String::New("length"))->Uint32Value(); - unsigned int offset = args[1]->Uint32Value(); - - if (offset > dst_length) - return ThrowRangeError("Offset out of range."); - - if (src_length > dst_length - offset) - return ThrowRangeError("Offset/length out of range."); - - for (uint32_t i = 0; i < src_length; ++i) { - // Use the v8 setter to deal with typing. Maybe slow? - args.This()->Set(i + offset, obj->Get(i)); + //if (!args[0]->IsObject()) + // return ThrowTypeError("Type error."); + + if (args[0]->IsNumber()) { + // index, value + unsigned int index = args[0]->Uint32Value(); + void* ptr = args.This()->GetIndexedPropertiesExternalArrayData(); + if (TEAType == v8::kExternalByteArray) + reinterpret_cast(ptr)[index] = (char) args[1]->Int32Value(); + else if (TEAType == v8::kExternalUnsignedByteArray) + reinterpret_cast(ptr)[index] = + (unsigned char) args[1]->Int32Value(); + else if (TEAType == v8::kExternalShortArray) + reinterpret_cast(ptr)[index] = (short) args[1]->Int32Value(); + else if (TEAType == v8::kExternalUnsignedShortArray) + reinterpret_cast(ptr)[index] = + (unsigned short) args[1]->Int32Value(); + else if (TEAType == v8::kExternalIntArray) + reinterpret_cast(ptr)[index] = (int) args[1]->Int32Value(); + else if (TEAType == v8::kExternalUnsignedIntArray) + reinterpret_cast(ptr)[index] = + (unsigned int) args[1]->Int32Value(); + else if (TEAType == v8::kExternalFloatArray) + reinterpret_cast(ptr)[index] = (float) args[1]->NumberValue(); + else if (TEAType == v8::kExternalDoubleArray) + reinterpret_cast(ptr)[index] = (double) args[1]->NumberValue(); + } else if (args[0]->IsObject()) { + v8::Handle obj = v8::Handle::Cast(args[0]); + + if (TypedArray::HasInstance(obj)) { // ArrayBufferView. + v8::Handle src_buffer = v8::Handle::Cast( + obj->Get(v8::String::New("buffer"))); + v8::Handle dst_buffer = v8::Handle::Cast( + args.This()->Get(v8::String::New("buffer"))); + + if (args[1]->Int32Value() < 0) + return ThrowRangeError("Offset may not be negative."); + + unsigned int offset = args[1]->Uint32Value(); + unsigned int src_length = + obj->Get(v8::String::New("length"))->Uint32Value(); + unsigned int dst_length = + args.This()->Get(v8::String::New("length"))->Uint32Value(); + if (offset > dst_length) + return ThrowRangeError("Offset out of range."); + + if (src_length > dst_length - offset) + return ThrowRangeError("Offset/length out of range."); + + // We don't want to get the buffer pointer, because that means we'll have + // to just do the calculations for byteOffset / byteLength again. + // Instead just use the pointer on the external array data. + void* src_ptr = obj->GetIndexedPropertiesExternalArrayData(); + void* dst_ptr = args.This()->GetIndexedPropertiesExternalArrayData(); + + // From the spec: + // If the input array is a TypedArray, the two arrays may use the same + // underlying ArrayBuffer. In this situation, setting the values takes + // place as if all the data is first copied into a temporary buffer that + // does not overlap either of the arrays, and then the data from the + // temporary buffer is copied into the current array. + memmove(reinterpret_cast(dst_ptr) + offset * TBytes, src_ptr, + src_length * TBytes); + } else { // type[] + if (args[1]->Int32Value() < 0) + return ThrowRangeError("Offset may not be negative."); + + unsigned int src_length = + obj->Get(v8::String::New("length"))->Uint32Value(); + unsigned int dst_length = + args.This()->Get(v8::String::New("length"))->Uint32Value(); + unsigned int offset = args[1]->Uint32Value(); + + if (offset > dst_length) + return ThrowRangeError("Offset out of range."); + + if (src_length > dst_length - offset) + return ThrowRangeError("Offset/length out of range."); + + for (uint32_t i = 0; i < src_length; ++i) { + // Use the v8 setter to deal with typing. Maybe slow? + args.This()->Set(i + offset, obj->Get(i)); + } } } @@ -545,7 +603,8 @@ class DataView { unsigned int byte_length = buffer->GetIndexedPropertiesExternalArrayDataLength(); - unsigned int byte_offset = args[1]->Uint32Value(); + unsigned int byte_offset = + args[1]->IsUndefined() ? 0 : args[1]->Uint32Value(); if (args[1]->Int32Value() < 0 || byte_offset >= byte_length) return ThrowRangeError("byteOffset out of range."); @@ -724,6 +783,8 @@ class DataView { namespace v8_typed_array { void AttachBindings(v8::Handle obj) { + v8::HandleScope scope; + obj->Set(v8::String::New("ArrayBuffer"), ArrayBuffer::GetTemplate()->GetFunction()); obj->Set(v8::String::New("Int8Array"), @@ -766,3 +827,5 @@ int SizeOfArrayElementForType(v8::ExternalArrayType type) { } } // namespace v8_typed_array + +NODE_MODULE(node_typed_array, v8_typed_array::AttachBindings) diff --git a/test/simple/test-typed-arrays.js b/test/simple/test-typed-arrays.js new file mode 100644 index 0000000000..d3be0ddef2 --- /dev/null +++ b/test/simple/test-typed-arrays.js @@ -0,0 +1,110 @@ +// Copyright Joyent, Inc. and other Node contributors. + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: + +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +/* + * Test to verify we are using Typed Arrays + * (http://www.khronos.org/registry/typedarray/specs/latest/) correctly Test to + * verify Buffer can used in Typed Arrays + */ + +var assert = require('assert'); +var SlowBuffer = process.binding('buffer').SlowBuffer; +var ArrayBuffer = process.binding('typed_array').ArrayBuffer; +var Int32Array = process.binding('typed_array').Int32Array; +var Int16Array = process.binding('typed_array').Int16Array; +var Uint8Array = process.binding('typed_array').Uint8Array; + +function test(clazz) { + var size = clazz.length; + var b = clazz; + + // create a view v1 referring to b, of type Int32, starting at + // the default byte index (0) and extending until the end of the buffer + var v1 = new Int32Array(b); + assert(4, v1.BYTES_PER_ELEMENT); + + // create a view v2 referring to b, of type Uint8, starting at + // byte index 2 and extending until the end of the buffer + var v2 = new Uint8Array(b, 2); + assert(1, v1.BYTES_PER_ELEMENT); + + // create a view v3 referring to b, of type Int16, starting at + // byte index 2 and having a length of 2 + var v3 = new Int16Array(b, 2, 2); + assert(2, v1.BYTES_PER_ELEMENT); + + // The layout is now + // var index + // b = |0|1|2|3|4|5|6|7| bytes (not indexable) + // v1 = |0 |1 | indices (indexable) + // v2 = |0|1|2|3|4|5| + // v3 = |0 |1 | + + // testing values + v1[0] = 0x1234; + v1[1] = 0x5678; + + assert(0x1234, v1[0]); + assert(0x5678, v1[1]); + + assert(0x3, v2[0]); + assert(0x4, v2[1]); + assert(0x5, v2[2]); + assert(0x6, v2[3]); + assert(0x7, v2[4]); + assert(0x8, v2[5]); + + assert(0x34, v3[0]); + assert(0x56, v3[1]); + + // test get/set + v2.set(1, 0x8); + v2.set(2, 0xF); + assert(0x8, v2.get(1)); + assert(0xF, v2.get(2)); + assert(0x38, v3.get(0)); + assert(0xF6, v3.get(1)); + + // test subarray + var v4 = v1.subarray(1); + assert(Int32Array, typeof v4); + assert(0xF678, v4[0]); + + // test set with typed array and [] + v2.set([ 1, 2, 3, 4 ], 2); + assert(0x1234, v1[0]); + + var sub = new Int32Array(4); + sub[0] = 0xabcd; + v2.set(sub, 1); + assert(0x3a, v3[0]); + assert(0xbc, v3[1]); +} + +// basic Typed Arrays tests +var size = 8; +var ab = new ArrayBuffer(size); +assert.equal(size, ab.byteLength); +test(ab); + +// testing sharing Buffer object +var buffer = new Buffer(size); +test(buffer);