Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

Commit

Permalink
Browse files Browse the repository at this point in the history
typed arrays: add Buffer -> TypedArray constructor
- create a typed array from a node::Buffer object
- update TypedArray::set() to spec
- add TypedArray::get() method
  • Loading branch information
Mikael Bourges-Sevenier authored and bnoordhuis committed Jan 5, 2012
1 parent 55c2197 commit 5b05429
Show file tree
Hide file tree
Showing 3 changed files with 238 additions and 64 deletions.
1 change: 1 addition & 0 deletions src/node_extensions.h
Expand Up @@ -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
Expand Down
191 changes: 127 additions & 64 deletions src/v8_typed_array.cc
Expand Up @@ -25,6 +25,7 @@
#include <v8.h>

#include "v8_typed_array.h"
#include "node_buffer.h"

namespace {

Expand Down Expand Up @@ -151,6 +152,7 @@ class TypedArray {
v8::Local<v8::Signature> default_signature = v8::Signature::New(ft_cache);

static BatchedMethods methods[] = {
{ "get", &TypedArray<TBytes, TEAType>::get },
{ "set", &TypedArray<TBytes, TEAType>::set },
{ "slice", &TypedArray<TBytes, TEAType>::subarray },
{ "subarray", &TypedArray<TBytes, TEAType>::subarray },
Expand Down Expand Up @@ -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<v8::Object>::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.");
Expand All @@ -214,10 +218,11 @@ class TypedArray {
}

// TODO(deanm): Error check.
void* buf = buffer->GetPointerFromInternalField(0);
void* buf = buffer->GetIndexedPropertiesExternalArrayData();
args.This()->SetIndexedPropertiesToExternalArrayData(
reinterpret_cast<char*>(buf) + byte_offset, TEAType, length);
} else if (args[0]->IsObject()) { // TypedArray / type[] constructor.
}
else if (args[0]->IsObject()) { // TypedArray / type[] constructor.
v8::Local<v8::Object> obj = v8::Local<v8::Object>::Cast(args[0]);
length = obj->Get(v8::String::New("length"))->Uint32Value();

Expand Down Expand Up @@ -277,68 +282,121 @@ class TypedArray {
return args.This();
}

static v8::Handle<v8::Value> 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<char*>(ptr)[index]);
else if (TEAType == v8::kExternalUnsignedByteArray)
return v8::Integer::New(reinterpret_cast<unsigned char*>(ptr)[index]);
else if (TEAType == v8::kExternalShortArray)
return v8::Integer::New(reinterpret_cast<short*>(ptr)[index]);
else if (TEAType == v8::kExternalUnsignedShortArray)
return v8::Integer::New(reinterpret_cast<unsigned short*>(ptr)[index]);
else if (TEAType == v8::kExternalIntArray)
return v8::Integer::New(reinterpret_cast<int*>(ptr)[index]);
else if (TEAType == v8::kExternalUnsignedIntArray)
return v8::Integer::New(reinterpret_cast<unsigned int*>(ptr)[index]);
else if (TEAType == v8::kExternalFloatArray)
return v8::Number::New(reinterpret_cast<float*>(ptr)[index]);
else if (TEAType == v8::kExternalDoubleArray)
return v8::Number::New(reinterpret_cast<double*>(ptr)[index]);
}
return v8::Undefined();
}

static v8::Handle<v8::Value> 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<v8::Object> obj = v8::Handle<v8::Object>::Cast(args[0]);

if (TypedArray<TBytes, TEAType>::HasInstance(obj)) { // ArrayBufferView.
v8::Handle<v8::Object> src_buffer = v8::Handle<v8::Object>::Cast(
obj->Get(v8::String::New("buffer")));
v8::Handle<v8::Object> dst_buffer = v8::Handle<v8::Object>::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<char*>(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, <type> value
unsigned int index = args[0]->Uint32Value();
void* ptr = args.This()->GetIndexedPropertiesExternalArrayData();
if (TEAType == v8::kExternalByteArray)
reinterpret_cast<char*>(ptr)[index] = (char) args[1]->Int32Value();
else if (TEAType == v8::kExternalUnsignedByteArray)
reinterpret_cast<unsigned char*>(ptr)[index] =
(unsigned char) args[1]->Int32Value();
else if (TEAType == v8::kExternalShortArray)
reinterpret_cast<short*>(ptr)[index] = (short) args[1]->Int32Value();
else if (TEAType == v8::kExternalUnsignedShortArray)
reinterpret_cast<unsigned short*>(ptr)[index] =
(unsigned short) args[1]->Int32Value();
else if (TEAType == v8::kExternalIntArray)
reinterpret_cast<int*>(ptr)[index] = (int) args[1]->Int32Value();
else if (TEAType == v8::kExternalUnsignedIntArray)
reinterpret_cast<unsigned int*>(ptr)[index] =
(unsigned int) args[1]->Int32Value();
else if (TEAType == v8::kExternalFloatArray)
reinterpret_cast<float*>(ptr)[index] = (float) args[1]->NumberValue();
else if (TEAType == v8::kExternalDoubleArray)
reinterpret_cast<double*>(ptr)[index] = (double) args[1]->NumberValue();
} else if (args[0]->IsObject()) {
v8::Handle<v8::Object> obj = v8::Handle<v8::Object>::Cast(args[0]);

if (TypedArray<TBytes, TEAType>::HasInstance(obj)) { // ArrayBufferView.
v8::Handle<v8::Object> src_buffer = v8::Handle<v8::Object>::Cast(
obj->Get(v8::String::New("buffer")));
v8::Handle<v8::Object> dst_buffer = v8::Handle<v8::Object>::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<char*>(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));
}
}
}

Expand Down Expand Up @@ -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.");
Expand Down Expand Up @@ -724,6 +783,8 @@ class DataView {
namespace v8_typed_array {

void AttachBindings(v8::Handle<v8::Object> obj) {
v8::HandleScope scope;

obj->Set(v8::String::New("ArrayBuffer"),
ArrayBuffer::GetTemplate()->GetFunction());
obj->Set(v8::String::New("Int8Array"),
Expand Down Expand Up @@ -766,3 +827,5 @@ int SizeOfArrayElementForType(v8::ExternalArrayType type) {
}

} // namespace v8_typed_array

NODE_MODULE(node_typed_array, v8_typed_array::AttachBindings)
110 changes: 110 additions & 0 deletions 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);

0 comments on commit 5b05429

Please sign in to comment.