Add ArrayBuffer with user specified buffer

New API functions:
 - jerry_create_arraybuffer_external
 - jerry_get_arraybuffer_pointer

JerryScript-DCO-1.0-Signed-off-by: Peter Gal pgal.u-szeged@partner.samsung.com
This commit is contained in:
Peter Gal 2018-01-08 15:35:18 +01:00 committed by yichoi
parent 7acd688513
commit 4b699e997a
8 changed files with 416 additions and 5 deletions

View File

@ -1030,6 +1030,7 @@ jerry_value_is_arraybuffer (const jerry_value_t value)
**See also**
- [jerry_create_arraybuffer](#jerry_create_arraybuffer)
- [jerry_create_arraybuffer_external](#jerry_create_arraybuffer_external)
## jerry_value_is_boolean
@ -2465,6 +2466,56 @@ jerry_create_arraybuffer (jerry_length_t size);
- [jerry_release_value](#jerry_release_value)
## jerry_create_arraybuffer_external
**Summary**
Creates a jerry_value_t representing an ArrayBuffer object with
user specified back-buffer.
User must pass a buffer pointer which is at least `size` big.
After the object is not needed the GC will call the `free_cb`
so the user can release the buffer which was provided.
**Prototype**
```c
jerry_value_t
jerry_create_arraybuffer_external (const jerry_length_t size
uint8_t *buffer_p,
jerry_object_native_free_callback_t free_cb);
```
- `size` - size of the buffer to use **in bytes** (should not be 0)
- `buffer_p` - the buffer used for the Array Buffer object (should not be a null pointer)
- `free_cb` - the callback function called when the object is released
- return value
- the new ArrayBuffer as a `jerry_value_t`
- if the `size` is zero or `buffer_p` is a null pointer will return RangeError
**Example**
```c
{
uint8_t buffer_p[15];
jerry_value_t buffer_value = jerry_create_arraybuffer_external (15, buffer_p, NULL);
... // use the array buffer
jerry_release_value (buffer_value);
}
```
**See also**
- [jerry_get_arraybuffer_pointer](#jerry_get_arraybuffer_pointer)
- [jerry_arraybuffer_read](#jerry_arraybuffer_read)
- [jerry_arraybuffer_write](#jerry_arraybuffer_write)
- [jerry_value_is_arraybuffer](#jerry_value_is_arraybuffer)
- [jerry_release_value](#jerry_release_value)
- [jerry_object_native_free_callback_t](#jerry_object_native_free_callback_t)
## jerry_create_boolean
**Summary**
@ -4921,3 +4972,61 @@ jerry_arraybuffer_write (const jerry_value_t value,
- [jerry_create_arraybuffer](#jerry_create_arraybuffer)
- [jerry_arraybuffer_write](#jerry_arraybuffer_write)
- [jerry_get_arraybuffer_byte_length](#jerry_get_arraybuffer_byte_length)
## jerry_get_arraybuffer_pointer
**Summary**
The function allows access to the contents of the Array Buffer directly.
Only allowed for Array Buffers which were created with
[jerry_create_arraybuffer_external](#jerry_create_arraybuffer_external)
function calls. In any other case this function will return `NULL`.
After using the pointer the [jerry_release_value](#jerry_release_value)
function must be called.
**WARNING!** This operation is for expert use only! The programmer must
ensure that the returned memory area is used correctly. That is
there is no out of bounds reads or writes.
**Prototype**
```c
uint8_t *
jerry_get_arraybuffer_pointer (const jerry_value_t value);
```
- `value` - Array Buffer object.
- return value
- pointer to the Array Buffer's data area.
- NULL if the `value` is not an Array Buffer object with external memory.
**Example**
```c
{
jerry_value_t buffer;
// acquire buffer somewhere which was created by a jerry_create_array_buffer_external call.
uint8_t *const data = jerry_get_arraybuffer_pointer (buffer);
for (int i = 0; i < 22; i++)
{
data[i] = (uint8_t) (i + 4);
}
// required after jerry_get_arraybuffer_pointer call.
jerry_release_value (buffer);
// use the Array Buffer
// release buffer as it is not needed after this point
jerry_release_value (buffer);
}
```
**See also**
- [jerry_create_arraybuffer_external](#jerry_create_arraybuffer_external)

View File

@ -2670,6 +2670,42 @@ jerry_create_arraybuffer (const jerry_length_t size) /**< size of the ArrayBuffe
#endif /* !CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN */
} /* jerry_create_arraybuffer */
/**
* Creates an ArrayBuffer object with user specified buffer.
*
* Notes:
* * the size is specified in bytes.
* * the buffer passed should be at least the specified bytes big.
* * if the typed arrays are disabled this will return a TypeError.
* * if the size is zero or the buffer_p is a null pointer this will return a RangeError.
*
* @return value of the construced ArrayBuffer object
*/
jerry_value_t
jerry_create_arraybuffer_external (const jerry_length_t size, /**< size of the buffer to used */
uint8_t *buffer_p, /**< buffer to use as the ArrayBuffer's backing */
jerry_object_native_free_callback_t free_cb) /**< buffer free callback */
{
jerry_assert_api_available ();
#ifndef CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN
if (size == 0 || buffer_p == NULL)
{
return jerry_throw (ecma_raise_range_error (ECMA_ERR_MSG ("invalid buffer size or storage reference")));
}
ecma_object_t *arraybuffer = ecma_arraybuffer_new_object_external (size,
buffer_p,
(ecma_object_native_free_callback_t) free_cb);
return jerry_return (ecma_make_object_value (arraybuffer));
#else /* CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN */
JERRY_UNUSED (size);
JERRY_UNUSED (buffer_p);
JERRY_UNUSED (free_cb);
return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG ("ArrayBuffer not supported.")));
#endif /* !CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN */
} /* jerry_create_arraybuffer_external */
/**
* Copy bytes into the ArrayBuffer from a buffer.
*
@ -2798,6 +2834,45 @@ jerry_get_arraybuffer_byte_length (const jerry_value_t value) /**< ArrayBuffer *
return 0;
} /* jerry_get_arraybuffer_byte_length */
/**
* Get a pointer for the start of the ArrayBuffer.
*
* Note:
* * Only valid for ArrayBuffers created with jerry_create_arraybuffer_external.
* * This is a high-risk operation as the bounds are not checked
* when accessing the pointer elements.
* * jerry_release_value must be called on the ArrayBuffer when the pointer is no longer needed.
*
* @return pointer to the back-buffer of the ArrayBuffer.
* pointer is NULL if the parameter is not an ArrayBuffer with external memory
or it is not an ArrayBuffer at all.
*/
uint8_t *
jerry_get_arraybuffer_pointer (const jerry_value_t value) /**< Array Buffer to use */
{
jerry_assert_api_available ();
#ifndef CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN
jerry_value_t buffer = jerry_get_arg_value (value);
if (!ecma_is_arraybuffer (buffer))
{
return NULL;
}
ecma_object_t *buffer_p = ecma_get_object_from_value (buffer);
if (ECMA_ARRAYBUFFER_HAS_EXTERNAL_MEMORY (buffer_p))
{
jerry_acquire_value (value);
lit_utf8_byte_t *mem_buffer_p = ecma_arraybuffer_get_buffer (buffer_p);
return (uint8_t *const) mem_buffer_p;
}
#else /* CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN */
JERRY_UNUSED (value);
#endif /* !CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN */
return NULL;
} /* jerry_get_arraybuffer_pointer */
/**
* @}
*/

View File

@ -570,11 +570,29 @@ ecma_gc_free_object (ecma_object_t *object_p) /**< object to free */
case LIT_MAGIC_STRING_ARRAY_BUFFER_UL:
{
ecma_length_t arraybuffer_length = ext_object_p->u.class_prop.u.length;
size_t size = sizeof (ecma_extended_object_t) + arraybuffer_length;
size_t size;
if (ECMA_ARRAYBUFFER_HAS_EXTERNAL_MEMORY (ext_object_p))
{
size = sizeof (ecma_arraybuffer_external_info);
/* Call external free callback if any. */
ecma_arraybuffer_external_info *array_p = (ecma_arraybuffer_external_info *) ext_object_p;
JERRY_ASSERT (array_p != NULL);
if (array_p->free_cb != NULL)
{
(array_p->free_cb) (array_p->buffer_p);
}
}
else
{
size = sizeof (ecma_extended_object_t) + arraybuffer_length;
}
ecma_dealloc_extended_object (object_p, size);
return;
}
#endif /* !CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN */
#ifndef CONFIG_DISABLE_ES2015_PROMISE_BUILTIN
case LIT_MAGIC_STRING_PROMISE_UL:

View File

@ -712,6 +712,8 @@ typedef struct
struct
{
uint16_t class_id; /**< class id of the object */
uint16_t extra_info; /**< extra information for the object
e.g. array buffer type info (external/internal) */
/*
* Description of extra fields. These extra fields depends on the class_id.
@ -1243,6 +1245,33 @@ typedef struct
#ifndef CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN
/**
* Extra information for ArrayBuffers.
*/
typedef enum
{
ECMA_ARRAYBUFFER_INTERNAL_MEMORY = 0u, /* ArrayBuffer memory is handled internally. */
ECMA_ARRAYBUFFER_EXTERNAL_MEMORY = (1u << 0), /* ArrayBuffer created via jerry_create_arraybuffer_external. */
} ecma_arraybuffer_extra_flag_t;
#define ECMA_ARRAYBUFFER_HAS_EXTERNAL_MEMORY(object_p) \
((((ecma_extended_object_t *) object_p)->u.class_prop.extra_info & ECMA_ARRAYBUFFER_EXTERNAL_MEMORY) != 0)
/**
* Struct to store information for ArrayBuffers with external memory.
*
* The following elements are stored in Jerry memory.
*
* buffer_p - pointer to the external memory.
* free_cb - pointer to a callback function which is called when the ArrayBuffer is freed.
*/
typedef struct
{
ecma_extended_object_t extended_object; /**< extended object part */
void *buffer_p; /**< external buffer pointer */
ecma_object_native_free_callback_t free_cb; /**< the free callback for the above buffer pointer */
} ecma_arraybuffer_external_info;
/**
* Some internal properties of TypedArray object.
* It is only used when the offset is not 0, and

View File

@ -51,6 +51,7 @@ ecma_arraybuffer_new_object (ecma_length_t length) /**< length of the arraybuffe
ECMA_OBJECT_TYPE_CLASS);
ecma_deref_object (prototype_obj_p);
ecma_extended_object_t *ext_object_p = (ecma_extended_object_t *) object_p;
ext_object_p->u.class_prop.extra_info = ECMA_ARRAYBUFFER_INTERNAL_MEMORY;
ext_object_p->u.class_prop.class_id = LIT_MAGIC_STRING_ARRAY_BUFFER_UL;
ext_object_p->u.class_prop.u.length = length;
@ -60,6 +61,38 @@ ecma_arraybuffer_new_object (ecma_length_t length) /**< length of the arraybuffe
return object_p;
} /* ecma_arraybuffer_new_object */
/**
* Helper function: create arraybuffer object with external buffer backing.
*
* The struct of external arraybuffer object:
* ecma_object_t
* extend_part
* arraybuffer external info part
*
* @return ecma_object_t *, pointer to the created ArrayBuffer object
*/
ecma_object_t *
ecma_arraybuffer_new_object_external (ecma_length_t length, /**< length of the buffer_p to use */
void *buffer_p, /**< pointer for ArrayBuffer's buffer backing */
ecma_object_native_free_callback_t free_cb) /**< buffer free callback */
{
ecma_object_t *prototype_obj_p = ecma_builtin_get (ECMA_BUILTIN_ID_ARRAYBUFFER_PROTOTYPE);
ecma_object_t *object_p = ecma_create_object (prototype_obj_p,
sizeof (ecma_arraybuffer_external_info),
ECMA_OBJECT_TYPE_CLASS);
ecma_deref_object (prototype_obj_p);
ecma_arraybuffer_external_info *array_object_p = (ecma_arraybuffer_external_info *) object_p;
array_object_p->extended_object.u.class_prop.extra_info = ECMA_ARRAYBUFFER_EXTERNAL_MEMORY;
array_object_p->extended_object.u.class_prop.class_id = LIT_MAGIC_STRING_ARRAY_BUFFER_UL;
array_object_p->extended_object.u.class_prop.u.length = length;
array_object_p->buffer_p = buffer_p;
array_object_p->free_cb = free_cb;
return object_p;
} /* ecma_arraybuffer_new_object_external */
/**
* ArrayBuffer object creation operation.
*
@ -158,7 +191,16 @@ ecma_arraybuffer_get_buffer (ecma_object_t *object_p) /**< pointer to the ArrayB
JERRY_ASSERT (ecma_object_class_is (object_p, LIT_MAGIC_STRING_ARRAY_BUFFER_UL));
ecma_extended_object_t *ext_object_p = (ecma_extended_object_t *) object_p;
return (lit_utf8_byte_t *) (ext_object_p + 1);
if (ECMA_ARRAYBUFFER_HAS_EXTERNAL_MEMORY (ext_object_p))
{
ecma_arraybuffer_external_info *array_p = (ecma_arraybuffer_external_info *) ext_object_p;
return (lit_utf8_byte_t *) array_p->buffer_p;
}
else
{
return (lit_utf8_byte_t *) (ext_object_p + 1);
}
} /* ecma_arraybuffer_get_buffer */
/**

View File

@ -34,6 +34,10 @@ ecma_op_create_arraybuffer_object (const ecma_value_t *, ecma_length_t);
*/
ecma_object_t *
ecma_arraybuffer_new_object (ecma_length_t lengh);
ecma_object_t *
ecma_arraybuffer_new_object_external (ecma_length_t length,
void *buffer_p,
ecma_object_native_free_callback_t free_cb);
lit_utf8_byte_t *
ecma_arraybuffer_get_buffer (ecma_object_t *obj_p) __attr_pure___;
ecma_length_t

View File

@ -446,6 +446,9 @@ void jerry_set_vm_exec_stop_callback (jerry_vm_exec_stop_callback_t stop_cb, voi
*/
bool jerry_value_is_arraybuffer (const jerry_value_t value);
jerry_value_t jerry_create_arraybuffer (const jerry_length_t size);
jerry_value_t jerry_create_arraybuffer_external (const jerry_length_t size,
uint8_t *buffer_p,
jerry_object_native_free_callback_t free_cb);
jerry_length_t jerry_arraybuffer_write (const jerry_value_t value,
jerry_length_t offset,
const uint8_t *buf_p,
@ -455,6 +458,7 @@ jerry_length_t jerry_arraybuffer_read (const jerry_value_t value,
uint8_t *buf_p,
jerry_length_t buf_size);
jerry_length_t jerry_get_arraybuffer_byte_length (const jerry_value_t value);
uint8_t *jerry_get_arraybuffer_pointer (const jerry_value_t value);
/**
* @}

View File

@ -129,7 +129,6 @@ static void test_write_with_offset (uint8_t offset) /**< offset for buffer write
jerry_value_t offset_val = jerry_create_number (offset);
register_js_value ("offset", offset_val);
jerry_release_value (offset_val);
}
const char *eval_arraybuffer_src_p = "var array = new Uint8Array (15); array.buffer";
@ -145,7 +144,7 @@ static void test_write_with_offset (uint8_t offset) /**< offset for buffer write
for (uint8_t i = 0; i < 20; i++)
{
buffer[i] = (uint8_t)(i * 3);
buffer[i] = (uint8_t) (i * 3);
}
/* Intentionally copy more than the allowed space. */
@ -170,6 +169,14 @@ static void test_write_with_offset (uint8_t offset) /**< offset for buffer write
jerry_release_value (arraybuffer);
} /* test_write_with_offset */
static bool callback_called = false;
static void test_free_cb (void *buffer) /**< buffer to free (if needed) */
{
(void) buffer;
callback_called = true;
} /* test_free_cb */
int
main (void)
{
@ -259,5 +266,128 @@ main (void)
jerry_release_value (arraybuffer);
}
/* Test ArrayBuffer with buffer allocated externally */
{
const uint32_t buffer_size = 15;
const uint8_t base_value = 51;
uint8_t buffer_p[buffer_size];
memset (buffer_p, base_value, buffer_size);
jerry_value_t arrayb = jerry_create_arraybuffer_external (buffer_size, buffer_p, test_free_cb);
uint8_t new_value = 123;
jerry_length_t copied = jerry_arraybuffer_write (arrayb, 0, &new_value, 1);
TEST_ASSERT (copied == 1);
TEST_ASSERT (buffer_p[0] == new_value);
TEST_ASSERT (jerry_get_arraybuffer_byte_length (arrayb) == buffer_size);
for (uint32_t i = 1; i < buffer_size; i++)
{
TEST_ASSERT (buffer_p[i] == base_value);
}
uint8_t test_buffer[buffer_size];
jerry_length_t read = jerry_arraybuffer_read (arrayb, 0, test_buffer, buffer_size);
TEST_ASSERT (read == buffer_size);
TEST_ASSERT (test_buffer[0] == new_value);
for (uint32_t i = 1; i < buffer_size; i++)
{
TEST_ASSERT (test_buffer[i] == base_value);
}
TEST_ASSERT (jerry_value_is_arraybuffer (arrayb));
jerry_release_value (arrayb);
}
/* Test ArrayBuffer external memory map/unmap */
{
const uint32_t buffer_size = 20;
uint8_t buffer_p[buffer_size];
{
jerry_value_t input_buffer = jerry_create_arraybuffer_external (buffer_size, buffer_p, NULL);
register_js_value ("input_buffer", input_buffer);
jerry_release_value (input_buffer);
}
const char *eval_arraybuffer_src_p = (
"var array = new Uint8Array(input_buffer);"
"for (var i = 0; i < array.length; i++)"
"{"
" array[i] = i * 2;"
"};"
"array.buffer");
jerry_value_t buffer = jerry_eval ((jerry_char_t *) eval_arraybuffer_src_p,
strlen (eval_arraybuffer_src_p),
true);
TEST_ASSERT (!jerry_value_has_error_flag (buffer));
TEST_ASSERT (jerry_value_is_arraybuffer (buffer));
TEST_ASSERT (jerry_get_arraybuffer_byte_length (buffer) == 20);
uint8_t *const data = jerry_get_arraybuffer_pointer (buffer);
/* test memory read */
for (int i = 0; i < 20; i++)
{
TEST_ASSERT (data[i] == (uint8_t) (i * 2));
}
/* "upload" new data */
double sum = 0;
for (int i = 0; i < 20; i++)
{
data[i] = (uint8_t)(i * 3);
sum += data[i];
}
jerry_release_value (buffer);
const char *eval_test_arraybuffer_p = (
"var sum = 0;"
"for (var i = 0; i < array.length; i++)"
"{"
" var expected = i * 3;"
" assert(array[i] == expected, 'Array at index ' + i + ' was: ' + array[i] + ' should be: ' + expected);"
" sum += array[i]"
"};"
"sum");
jerry_value_t res = jerry_eval ((jerry_char_t *) eval_test_arraybuffer_p,
strlen (eval_test_arraybuffer_p),
true);
TEST_ASSERT (jerry_value_is_number (res));
TEST_ASSERT (jerry_get_number_value (res) == sum);
jerry_release_value (res);
jerry_release_value (buffer);
}
/* Test ArrayBuffer external with invalid arguments */
{
jerry_value_t input_buffer = jerry_create_arraybuffer_external (0, NULL, NULL);
TEST_ASSERT (jerry_value_has_error_flag (input_buffer));
jerry_value_clear_error_flag (&input_buffer);
if (jerry_is_feature_enabled (JERRY_FEATURE_ERROR_MESSAGES))
{
jerry_char_t error_str[15];
jerry_char_t expected_error_str[15] = "RangeError";
jerry_char_t *name_str_p = (jerry_char_t *) "name";
jerry_value_t name_key = jerry_create_string (name_str_p);
jerry_value_t name_value = jerry_get_property (input_buffer, name_key);
jerry_size_t name_size = jerry_string_to_char_buffer (name_value, error_str, sizeof (error_str));
TEST_ASSERT (name_size == strlen ((char *) expected_error_str));
TEST_ASSERT (strncmp ((char *) error_str, (char *) expected_error_str, name_size) == 0);
jerry_release_value (name_value);
jerry_release_value (name_key);
}
jerry_release_value (input_buffer);
}
jerry_cleanup ();
TEST_ASSERT (callback_called == true);
} /* main */