Skip to content

Commit

Permalink
napi: Typed arrays (nodejs#90)
Browse files Browse the repository at this point in the history

* napi: Separate API for external ArrayBuffer
  • Loading branch information
jasongin authored Feb 8, 2017
1 parent 8803b97 commit 6f9070d
Show file tree
Hide file tree
Showing 6 changed files with 409 additions and 43 deletions.
195 changes: 193 additions & 2 deletions src/node_jsvmapi.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*******************************************************************************
/*******************************************************************************
* Experimental prototype for demonstrating VM agnostic and ABI stable API
* for native modules to use instead of using Nan and V8 APIs directly.
*
Expand Down Expand Up @@ -517,7 +517,7 @@ namespace v8impl {
napi_ok : napi_set_last_error(napi_pending_exception))

// Static last error returned from napi_get_last_error_info
napi_extended_error_info static_last_error;
napi_extended_error_info static_last_error = {};

// Warning: Keep in-sync with napi_status enum
const char* error_messages[] = {
Expand Down Expand Up @@ -1881,3 +1881,194 @@ napi_status napi_buffer_length(napi_env e, napi_value v, size_t* result) {
*result = node::Buffer::Length(v8impl::V8LocalValueFromJsValue(v));
return GET_RETURN_STATUS();
}

napi_status napi_is_arraybuffer(napi_env e, napi_value value, bool* result) {
NAPI_PREAMBLE(e);
CHECK_ARG(result);

v8::Local<v8::Value> v8value = v8impl::V8LocalValueFromJsValue(value);
*result = v8value->IsArrayBuffer();

return GET_RETURN_STATUS();
}

napi_status napi_create_arraybuffer(napi_env e,
size_t byte_length,
void** data,
napi_value* result) {
NAPI_PREAMBLE(e);
CHECK_ARG(result);

v8::Isolate *isolate = v8impl::V8IsolateFromJsEnv(e);
v8::Local<v8::ArrayBuffer> buffer = v8::ArrayBuffer::New(isolate, byte_length);

// Optionally return a pointer to the buffer's data, to avoid another call to retreive it.
if (data != nullptr) {
*data = buffer->GetContents().Data();
}

*result = v8impl::JsValueFromV8LocalValue(buffer);
return GET_RETURN_STATUS();
}

napi_status napi_create_external_arraybuffer(napi_env e,
void* external_data,
size_t byte_length,
napi_value* result) {
NAPI_PREAMBLE(e);
CHECK_ARG(result);

v8::Isolate *isolate = v8impl::V8IsolateFromJsEnv(e);
v8::Local<v8::ArrayBuffer> buffer =
v8::ArrayBuffer::New(isolate, external_data, byte_length);

*result = v8impl::JsValueFromV8LocalValue(buffer);
return GET_RETURN_STATUS();
}

napi_status napi_get_arraybuffer_info(napi_env e,
napi_value arraybuffer,
void** data,
size_t* byte_length) {
NAPI_PREAMBLE(e);

v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(arraybuffer);
RETURN_STATUS_IF_FALSE(value->IsArrayBuffer(), napi_invalid_arg);

v8::ArrayBuffer::Contents contents = value.As<v8::ArrayBuffer>()->GetContents();

if (data != nullptr) {
*data = contents.Data();
}

if (byte_length != nullptr) {
*byte_length = contents.ByteLength();
}

return GET_RETURN_STATUS();
}

napi_status napi_is_typedarray(napi_env e, napi_value value, bool* result) {
NAPI_PREAMBLE(e);
CHECK_ARG(result);

v8::Local<v8::Value> v8value = v8impl::V8LocalValueFromJsValue(value);
*result = v8value->IsTypedArray();

return GET_RETURN_STATUS();
}

napi_status napi_create_typedarray(napi_env e,
napi_typedarray_type type,
size_t length,
napi_value arraybuffer,
size_t byte_offset,
napi_value* result) {
NAPI_PREAMBLE(e);
CHECK_ARG(result);

v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(arraybuffer);
RETURN_STATUS_IF_FALSE(value->IsArrayBuffer(), napi_invalid_arg);

v8::Local<v8::ArrayBuffer> buffer = value.As<v8::ArrayBuffer>();
v8::Local<v8::TypedArray> typedArray;

switch (type) {
case napi_int8:
typedArray = v8::Int8Array::New(buffer, byte_offset, length);
break;
case napi_uint8:
typedArray = v8::Uint8Array::New(buffer, byte_offset, length);
break;
case napi_uint8_clamped:
typedArray = v8::Uint8ClampedArray::New(buffer, byte_offset, length);
break;
case napi_int16:
typedArray = v8::Int16Array::New(buffer, byte_offset, length);
break;
case napi_uint16:
typedArray = v8::Uint16Array::New(buffer, byte_offset, length);
break;
case napi_int32:
typedArray = v8::Int32Array::New(buffer, byte_offset, length);
break;
case napi_uint32:
typedArray = v8::Uint32Array::New(buffer, byte_offset, length);
break;
case napi_float32:
typedArray = v8::Float32Array::New(buffer, byte_offset, length);
break;
case napi_float64:
typedArray = v8::Float64Array::New(buffer, byte_offset, length);
break;
default:
return napi_set_last_error(napi_invalid_arg);
}

*result = v8impl::JsValueFromV8LocalValue(typedArray);
return GET_RETURN_STATUS();
}

napi_status napi_get_typedarray_info(napi_env e,
napi_value typedarray,
napi_typedarray_type* type,
size_t* length,
void** data,
napi_value* arraybuffer,
size_t* byte_offset) {
NAPI_PREAMBLE(e);

v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(typedarray);
RETURN_STATUS_IF_FALSE(value->IsTypedArray(), napi_invalid_arg);

v8::Local<v8::TypedArray> array = value.As<v8::TypedArray>();

if (type != nullptr) {
if (value->IsInt8Array()) {
*type = napi_int8;
}
else if (value->IsUint8Array()) {
*type = napi_uint8;
}
else if (value->IsUint8ClampedArray()) {
*type = napi_uint8_clamped;
}
else if (value->IsInt16Array()) {
*type = napi_int16;
}
else if (value->IsUint16Array()) {
*type = napi_uint16;
}
else if (value->IsInt32Array()) {
*type = napi_int32;
}
else if (value->IsUint32Array()) {
*type = napi_uint32;
}
else if (value->IsFloat32Array()) {
*type = napi_float32;
}
else if (value->IsFloat64Array()) {
*type = napi_float64;
}
}

if (length != nullptr) {
*length = array->Length();
}

v8::Local<v8::ArrayBuffer> buffer = array->Buffer();
if (data != nullptr) {
*data = static_cast<uint8_t*>(buffer->GetContents().Data()) + array->ByteOffset();
}

if (arraybuffer != nullptr) {
*arraybuffer = v8impl::JsValueFromV8LocalValue(buffer);
}

if (byte_offset != nullptr) {
*byte_offset = array->ByteOffset();
}

return GET_RETURN_STATUS();
}
59 changes: 18 additions & 41 deletions src/node_jsvmapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
#define SRC_NODE_JSVMAPI_H_

#include "node_jsvmapi_types.h"
#include <stdlib.h>
#include <stdint.h>

#ifndef NODE_EXTERN
# ifdef _WIN32
Expand Down Expand Up @@ -51,7 +49,7 @@ struct napi_module_struct {
void* nm_context_register_func;
const char* nm_modname;
void* nm_priv;
struct node_module* nm_link;
void* nm_link;
};

NODE_EXTERN void napi_module_register(void* mod);
Expand Down Expand Up @@ -103,44 +101,6 @@ NODE_EXTERN void napi_module_register(void* mod);
// TODO(ianhall): We're using C linkage for the API but we're also using the
// bool type in these exports. Is that safe and stable?
extern "C" {
enum napi_valuetype {
// ES6 types (corresponds to typeof)
napi_undefined,
napi_null,
napi_boolean,
napi_number,
napi_string,
napi_symbol,
napi_object,
napi_function,
};

enum napi_status {
napi_ok,
napi_invalid_arg,
napi_object_expected,
napi_string_expected,
napi_function_expected,
napi_number_expected,
napi_boolean_expected,
napi_generic_failure,
napi_pending_exception,
napi_status_last
};

struct napi_extended_error_info {
const char* error_message;
void* engine_reserved;
uint32_t engine_error_code;
napi_status error_code;

napi_extended_error_info() :
error_message(NULL),
engine_reserved(NULL),
engine_error_code(0),
error_code(napi_ok)
{ }
};

NODE_EXTERN const napi_extended_error_info* napi_get_last_error_info();

Expand Down Expand Up @@ -354,6 +314,23 @@ NODE_EXTERN napi_status napi_buffer_copy(napi_env e,
NODE_EXTERN napi_status napi_buffer_has_instance(napi_env e, napi_value v, bool* result);
NODE_EXTERN napi_status napi_buffer_data(napi_env e, napi_value v, char** result);
NODE_EXTERN napi_status napi_buffer_length(napi_env e, napi_value v, size_t* result);

// Methods to work with array buffers and typed arrays
NODE_EXTERN napi_status napi_is_arraybuffer(napi_env env, napi_value value, bool* result);
NODE_EXTERN napi_status napi_create_arraybuffer(napi_env env, size_t byte_length, void** data,
napi_value* result);
NODE_EXTERN napi_status napi_create_external_arraybuffer(napi_env env, void* external_data,
size_t byte_length, napi_value* result);
NODE_EXTERN napi_status napi_get_arraybuffer_info(napi_env env, napi_value arraybuffer,
void** data, size_t* byte_length);
NODE_EXTERN napi_status napi_is_typedarray(napi_env env, napi_value value, bool* result);
NODE_EXTERN napi_status napi_create_typedarray(napi_env env, napi_typedarray_type type,
size_t length, napi_value arraybuffer,
size_t byte_offset, napi_value* result);
NODE_EXTERN napi_status napi_get_typedarray_info(napi_env env, napi_value typedarray,
napi_typedarray_type* type, size_t* length,
void** data, napi_value* arraybuffer,
size_t* byte_offset);
} // extern "C"

#endif // SRC_NODE_JSVMAPI_H__
46 changes: 46 additions & 0 deletions src/node_jsvmapi_types.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#ifndef SRC_NODE_JSVMAPI_TYPES_H_
#define SRC_NODE_JSVMAPI_TYPES_H_

#include <stdint.h>

// JSVM API types are all opaque pointers for ABI stability
// typedef undefined structs instead of void* for compile time type safety
typedef struct napi_env__ *napi_env;
Expand Down Expand Up @@ -34,4 +36,48 @@ struct napi_property_descriptor {
void* data;
};

enum napi_valuetype {
// ES6 types (corresponds to typeof)
napi_undefined,
napi_null,
napi_boolean,
napi_number,
napi_string,
napi_symbol,
napi_object,
napi_function,
};

enum napi_typedarray_type {
napi_int8,
napi_uint8,
napi_uint8_clamped,
napi_int16,
napi_uint16,
napi_int32,
napi_uint32,
napi_float32,
napi_float64,
};

enum napi_status {
napi_ok,
napi_invalid_arg,
napi_object_expected,
napi_string_expected,
napi_function_expected,
napi_number_expected,
napi_boolean_expected,
napi_generic_failure,
napi_pending_exception,
napi_status_last
};

struct napi_extended_error_info {
const char* error_message;
void* engine_reserved;
uint32_t engine_error_code;
napi_status error_code;
};

#endif // SRC_NODE_JSVMAPI_TYPES_H_
8 changes: 8 additions & 0 deletions test/addons-abi/test_typedarray/binding.gyp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"targets": [
{
"target_name": "test_typedarray",
"sources": [ "test_typedarray.cc" ]
}
]
}
30 changes: 30 additions & 0 deletions test/addons-abi/test_typedarray/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict';
require('../../common');
var assert = require('assert');

// Testing api calls for arrays
var test_typedarray = require('./build/Release/test_typedarray');

var byteArray = new Uint8Array(3);
byteArray[0] = 0;
byteArray[1] = 1;
byteArray[2] = 2;
assert.equal(byteArray.length, 3);

var doubleArray = new Float64Array(3);
doubleArray[0] = 0.0;
doubleArray[1] = 1.1;
doubleArray[2] = 2.2;
assert.equal(doubleArray.length, 3);

var byteResult = test_typedarray.Multiply(byteArray, 3);
assert.equal(byteResult.length, 3);
assert.equal(byteResult[0], 0);
assert.equal(byteResult[1], 3);
assert.equal(byteResult[2], 6);

var doubleResult = test_typedarray.Multiply(doubleArray, -3);
assert.equal(doubleResult.length, 3);
assert.equal(doubleResult[0], 0);
assert.equal(Math.round(10 * doubleResult[1]) / 10, -3.3);
assert.equal(Math.round(10 * doubleResult[2]) / 10, -6.6);
Loading

0 comments on commit 6f9070d

Please sign in to comment.