Skip to content

Conversation

@AnthonyCalandra
Copy link
Contributor

@AnthonyCalandra AnthonyCalandra commented Jun 19, 2018

This patch allows developers to set a typedarray given a source typedarray. This patch
attempts to follow section 22.2.3.22.2 in the ECMAScript spec as closely as possible.

JerryScript-DCO-1.0-Signed-off-by: AnthonyCalandra anthony@anthony-calandra.com

@AnthonyCalandra AnthonyCalandra force-pushed the typedarray-set-support branch 3 times, most recently from d3d16c5 to 444e774 Compare June 19, 2018 17:26
Copy link
Member

@zherczeg zherczeg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quite good patch.

/* 6.~ 8. targetOffset */
ecma_value_t ret_val = ECMA_VALUE_EMPTY;
ECMA_OP_TO_NUMBER_TRY_CATCH (target_offset_num, offset_val, ret_val);
if (ecma_number_is_nan (target_offset_num))
Copy link
Member

@zherczeg zherczeg Jun 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a newline before this if and another below the second if.


/* 23. */
uint32_t target_offset_uint32 = ecma_number_to_uint32 (target_offset_num);
if ((int64_t) src_length_uint32 + target_offset_uint32 > target_length)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain the purpose of this if?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to make sure that the offset is within range of the target typedarray. For example, if the target has length 3 and the offset is 1000, it should throw a RangeError. I cast it to int64_t to account for overflow.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I checked that partial copy is not allowed. That is strange. Anyway this is an ok check then.

ecma_object_t *src_arraybuffer_clone_p = NULL;
bool src_target_same_value = ecma_op_same_value (ecma_make_object_value (src_arraybuffer_p),
ecma_make_object_value (target_arraybuffer_p));
if (src_target_same_value)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is for "in-place-copy" just use memmove(), that works with any buffers, and you don'y need src_arraybuffer_clone_p.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true, I could probably just copy the buffer and operate on that instead of cloning an entire typedarray. I'll give it a try.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried to do this but I'm running into an assertion error. The code I have looks like:

size_t target_clone_heap_size = (sizeof (lit_utf8_byte_t)) * target_element_size * target_length;
lit_utf8_byte_t *target_clone_buffer_p = (lit_utf8_byte_t *) jmem_heap_alloc_block (target_clone_heap_size);
memmove (target_clone_buffer_p, ecma_arraybuffer_get_buffer (target_arraybuffer_p), target_length);
src_buffer_p = target_clone_buffer_p + src_byte_offset;
// ...

And the assertion error is:

ICE: Assertion '(uintptr_t) data_space_p % JMEM_ALIGNMENT == 0' failed at /Users/acalandra/jerryscript/jerry-core/jmem/jmem-heap.c(jmem_heap_alloc_block_internal):335.
Error: ERR_FAILED_INTERNAL_ASSERTION

Any idea how I can resolve this?


I would almost prefer the clone approach because it's closer to the spec and the code definitely looks cleaner.

Copy link
Member

@zherczeg zherczeg Jun 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this a nop? The check ensures above that only a.set(a,0) is valid, and that is effectively do nothing.

I suspect the error is caused by memory overwrite, asan or valgrind can tell you cause. The size of memove is definitely wrong, it should be target_clone_heap_size.

Regardless for in-place copy never clone a buffer. Allocating memory may increase peak memory consumption, CPU consuming, definitely against the aims of an engine for low-end systems.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that this seems like a nop. When I read the spec it added the cloning step because of side-effects. I've been trying to think of a case where this would be an issue but I haven't come up with an example.

@LaszloLango LaszloLango added development Feature implementation ES2015 Related to ES2015 features labels Jun 20, 2018
@AnthonyCalandra AnthonyCalandra force-pushed the typedarray-set-support branch from 444e774 to 023da39 Compare June 21, 2018 15:47

return object_p;
} /* ecma_arraybuffer_clone_object */

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waiting to see if cloning an ArrayBuffer is necessary -- will remove if not.

@AnthonyCalandra AnthonyCalandra force-pushed the typedarray-set-support branch 3 times, most recently from 8ade614 to 11ea3b8 Compare June 22, 2018 19:47
@AnthonyCalandra
Copy link
Contributor Author

I've added the memmove optimization and added a test to account for overlapping setting of elements.


if ((int64_t) src_length_uint32 + target_offset_uint32 > target_length)
{
ret_val = ecma_raise_range_error (ECMA_ERR_MSG ("Invalid range of index"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could return here as well.

/* 27. limit */
uint32_t limit = target_byte_index + target_element_size * src_length_uint32;

if (src_class_id == target_class_id && target_byte_index < limit && ecma_is_value_empty (ret_val))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need |ecma_is_value_empty (ret_val).
What is the purpose of target_byte_index < limit ? It is used in the loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, checking target_byte_index < limit is not needed here.

}
else
{
while (target_byte_index < limit && ecma_is_value_empty (ret_val))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add an assert that src and target buffers cannot be the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this required? The assertion fails if I want to do the following:

var ab = new ArrayBuffer(8);
var a1 = new Uint8Array(ab);
var a2 = new Uint32Array(ab, 4);
a1.set(a2, 2);

a1 and a2 have different class names/IDs so the while loop gets executed even though they have the same buffer. Seems reasonable to me.

@zherczeg
Copy link
Member

Ok, I get now where the buffer cloning comes from. They wanted to simplify the pseudo code in the standard so they only need forward direction data copy. This is how you write a standard. But not how you write code. Fortunately for in-buffer copy the type of src and target must match obviously, and memmove handles the forward/backward copy. This is its key difference from memcpy, where the result is unspecified for in-buffer copy.

@AnthonyCalandra AnthonyCalandra force-pushed the typedarray-set-support branch from 11ea3b8 to 74c5037 Compare June 25, 2018 02:29
@AnthonyCalandra
Copy link
Contributor Author

Addressed all comments and I added a unit test for the case of forward/backward copying when the two buffers have different class IDs (thus using the while-loop algorithm).

@AnthonyCalandra AnthonyCalandra force-pushed the typedarray-set-support branch from 74c5037 to 4cbe695 Compare June 25, 2018 17:05
@AnthonyCalandra
Copy link
Contributor Author

AnthonyCalandra commented Jun 28, 2018

Sorry to pester, but can I get some looks at this? Our goal at work is to have this available to us by the end of the week.

@zherczeg
Copy link
Member

Who is 'our'?

Copy link
Member

@zherczeg zherczeg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like there is a code duplication for computing offset. Please merge the two (keep yours, that is better).

{
/* 6.~ 8. targetOffset */
ecma_number_t target_offset_num;
if (!ecma_is_value_empty (ecma_get_number (offset_val, &target_offset_num)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct. Put the result into a temporary value, and if it is an error, return with it. Do not create another error. Otherwise do nothing, so no need to free ECMA_VALUE_xxx values.

target_offset_num = 0;
}

if (target_offset_num <= -1.0 || target_offset_num >= (ecma_number_t) UINT32_MAX + 0.5)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rounding question: you check <= -1 and MAX+0.5. For consistency, it should be:

if (target_offset_num <= -0.5 || target_offset_num >= (ecma_number_t) UINT32_MAX + 0.5)

or

if (target_offset_num <= -1.0 || target_offset_num >= (ecma_number_t) UINT32_MAX + 1.0)

It depends on the rounding rules specified by the standard. I think C rounding is truncating by default.

Copy link
Contributor Author

@AnthonyCalandra AnthonyCalandra Jun 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same rounding check as https://github.com/jerryscript-project/jerryscript/pull/2405/files#diff-f81c66825ff663e22d61a8e1fb0fa8a9R797, so I just copied it over. I don't know much about rounding rules so I left it alone.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried with latest clang and gcc on godbolt to try out the rounding rules and I think if (target_offset_num <= -1.0 || target_offset_num >= (ecma_number_t) UINT32_MAX + 1.0) is what we want. Not sure why the original author used 0.5 for the latter condition though.


/* 21. srcLength */
ecma_length_t src_length = ecma_typedarray_get_length (src_typedarray_p);
uint32_t src_length_uint32 = ecma_number_to_uint32 (src_length);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ecma_length_t is uint32_t. No need a conversion here. Definitely not a to double and back to int, which is rather costly.


if ((ecma_number_t) src_length_uint32 != src_length)
{
return ecma_raise_range_error (ECMA_ERR_MSG ("Invalid source length"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is impossible.

}

/* 22. srcByteOffset */
ecma_length_t src_byte_offset = ecma_typedarray_get_offset (src_typedarray_p);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is used later, please move it before use.

/* 24.d, 25. srcByteIndex */
ecma_length_t src_byte_index = 0;

if (src_arraybuffer_p != target_arraybuffer_p)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is src_byte_index is always 0 if the two array buffers are the same? I don't think this is true because of the subarray mechanics. This is probably not covered by a test, so please test it.

Copy link
Contributor Author

@AnthonyCalandra AnthonyCalandra Jun 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so what happened was when we decided to go with the memmove approach and ditch the cloning, the spec sets srcByteIndex to zero because it clones the buffer starting at srcByteOffset. Since we don't do the cloning, we always need to start at srcByteOffset bytes in the buffer, and the tests didn't cover that edge-case.

@AnthonyCalandra AnthonyCalandra force-pushed the typedarray-set-support branch 4 times, most recently from 75d1e07 to 09c21cf Compare June 28, 2018 17:40
Copy link
Member

@zherczeg zherczeg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only minor changes remained.

lit_utf8_byte_t *target_buffer_p = ecma_arraybuffer_get_buffer (target_arraybuffer_p);

/* 11. targetLength */
ecma_length_t target_length = ecma_typedarray_get_length (target_typedarray_p);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move target_length after target_length definition.

Copy link
Contributor Author

@AnthonyCalandra AnthonyCalandra Jun 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After which definition? I'll assume you meant src_length and move it there.

ecma_length_t src_byte_offset = ecma_typedarray_get_offset (src_typedarray_p);

/* 24.d, 25. srcByteIndex */
ecma_length_t src_byte_index = src_byte_offset;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think src_byte_offset is unnecessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean src_byte_index? I was on the fence about that one because it gets mutated in the while loop but I guess it doesn't matter at this point.

@AnthonyCalandra AnthonyCalandra force-pushed the typedarray-set-support branch from 09c21cf to 1d2137d Compare June 29, 2018 14:29
@AnthonyCalandra
Copy link
Contributor Author

Addressed all remaining comments.

Copy link
Member

@zherczeg zherczeg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@LaszloLango LaszloLango left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix the comment, good to me after that.

* See also:
* ES2015, 22.2.3.22, 22.2.3.22.2
*
* @return ecma value of undefined.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is inaccurate. Please update.

 * @return ecma value of undefined if success, error otherwise.
 *         Returned value must be freed with ecma_free_value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

This patch allows developers to set a typedarray given a source typedarray. This patch
attempts to follow section 22.2.3.22.2 in the ECMAScript spec as closely as possible.

JerryScript-DCO-1.0-Signed-off-by: AnthonyCalandra anthony@anthony-calandra.com
@AnthonyCalandra AnthonyCalandra force-pushed the typedarray-set-support branch from 1d2137d to d2b070a Compare July 3, 2018 01:39
Copy link
Contributor

@LaszloLango LaszloLango left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update, LGTM

@yichoi yichoi merged commit d5cd32b into jerryscript-project:master Jul 3, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

development Feature implementation ES2015 Related to ES2015 features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants