1// Copyright 2013 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5"use strict";
6
7// This file relies on the fact that the following declaration has been made
8// in runtime.js:
9// var $Array = global.Array;
10var $ArrayBuffer = global.ArrayBuffer;
11
12
13// --------------- Typed Arrays ---------------------
14macro TYPED_ARRAYS(FUNCTION)
15// arrayIds below should be synchronized with Runtime_TypedArrayInitialize.
16FUNCTION(1, Uint8Array, 1)
17FUNCTION(2, Int8Array, 1)
18FUNCTION(3, Uint16Array, 2)
19FUNCTION(4, Int16Array, 2)
20FUNCTION(5, Uint32Array, 4)
21FUNCTION(6, Int32Array, 4)
22FUNCTION(7, Float32Array, 4)
23FUNCTION(8, Float64Array, 8)
24FUNCTION(9, Uint8ClampedArray, 1)
25endmacro
26
27macro TYPED_ARRAY_CONSTRUCTOR(ARRAY_ID, NAME, ELEMENT_SIZE)
28function NAMEConstructByArrayBuffer(obj, buffer, byteOffset, length) {
29  if (!IS_UNDEFINED(byteOffset)) {
30      byteOffset =
31          ToPositiveInteger(byteOffset,  "invalid_typed_array_length");
32  }
33  if (!IS_UNDEFINED(length)) {
34      length = ToPositiveInteger(length, "invalid_typed_array_length");
35  }
36
37  var bufferByteLength = %_ArrayBufferGetByteLength(buffer);
38  var offset;
39  if (IS_UNDEFINED(byteOffset)) {
40    offset = 0;
41  } else {
42    offset = byteOffset;
43
44    if (offset % ELEMENT_SIZE !== 0) {
45      throw MakeRangeError("invalid_typed_array_alignment",
46          ["start offset", "NAME", ELEMENT_SIZE]);
47    }
48    if (offset > bufferByteLength) {
49      throw MakeRangeError("invalid_typed_array_offset");
50    }
51  }
52
53  var newByteLength;
54  var newLength;
55  if (IS_UNDEFINED(length)) {
56    if (bufferByteLength % ELEMENT_SIZE !== 0) {
57      throw MakeRangeError("invalid_typed_array_alignment",
58        ["byte length", "NAME", ELEMENT_SIZE]);
59    }
60    newByteLength = bufferByteLength - offset;
61    newLength = newByteLength / ELEMENT_SIZE;
62  } else {
63    var newLength = length;
64    newByteLength = newLength * ELEMENT_SIZE;
65  }
66  if ((offset + newByteLength > bufferByteLength)
67      || (newLength > %_MaxSmi())) {
68    throw MakeRangeError("invalid_typed_array_length");
69  }
70  %_TypedArrayInitialize(obj, ARRAY_ID, buffer, offset, newByteLength);
71}
72
73function NAMEConstructByLength(obj, length) {
74  var l = IS_UNDEFINED(length) ?
75    0 : ToPositiveInteger(length, "invalid_typed_array_length");
76  if (l > %_MaxSmi()) {
77    throw MakeRangeError("invalid_typed_array_length");
78  }
79  var byteLength = l * ELEMENT_SIZE;
80  if (byteLength > %_TypedArrayMaxSizeInHeap()) {
81    var buffer = new $ArrayBuffer(byteLength);
82    %_TypedArrayInitialize(obj, ARRAY_ID, buffer, 0, byteLength);
83  } else {
84    %_TypedArrayInitialize(obj, ARRAY_ID, null, 0, byteLength);
85  }
86}
87
88function NAMEConstructByArrayLike(obj, arrayLike) {
89  var length = arrayLike.length;
90  var l = ToPositiveInteger(length, "invalid_typed_array_length");
91
92  if (l > %_MaxSmi()) {
93    throw MakeRangeError("invalid_typed_array_length");
94  }
95  if(!%TypedArrayInitializeFromArrayLike(obj, ARRAY_ID, arrayLike, l)) {
96    for (var i = 0; i < l; i++) {
97      // It is crucial that we let any execptions from arrayLike[i]
98      // propagate outside the function.
99      obj[i] = arrayLike[i];
100    }
101  }
102}
103
104function NAMEConstructor(arg1, arg2, arg3) {
105  if (%_IsConstructCall()) {
106    if (IS_ARRAYBUFFER(arg1)) {
107      NAMEConstructByArrayBuffer(this, arg1, arg2, arg3);
108    } else if (IS_NUMBER(arg1) || IS_STRING(arg1) ||
109               IS_BOOLEAN(arg1) || IS_UNDEFINED(arg1)) {
110      NAMEConstructByLength(this, arg1);
111    } else {
112      NAMEConstructByArrayLike(this, arg1);
113    }
114  } else {
115    throw MakeTypeError("constructor_not_function", ["NAME"])
116  }
117}
118
119function NAME_GetBuffer() {
120  if (!(%_ClassOf(this) === 'NAME')) {
121    throw MakeTypeError('incompatible_method_receiver',
122                        ["NAME.buffer", this]);
123  }
124  return %TypedArrayGetBuffer(this);
125}
126
127function NAME_GetByteLength() {
128  if (!(%_ClassOf(this) === 'NAME')) {
129    throw MakeTypeError('incompatible_method_receiver',
130                        ["NAME.byteLength", this]);
131  }
132  return %_ArrayBufferViewGetByteLength(this);
133}
134
135function NAME_GetByteOffset() {
136  if (!(%_ClassOf(this) === 'NAME')) {
137    throw MakeTypeError('incompatible_method_receiver',
138                        ["NAME.byteOffset", this]);
139  }
140  return %_ArrayBufferViewGetByteOffset(this);
141}
142
143function NAME_GetLength() {
144  if (!(%_ClassOf(this) === 'NAME')) {
145    throw MakeTypeError('incompatible_method_receiver',
146                        ["NAME.length", this]);
147  }
148  return %_TypedArrayGetLength(this);
149}
150
151var $NAME = global.NAME;
152
153function NAMESubArray(begin, end) {
154  if (!(%_ClassOf(this) === 'NAME')) {
155    throw MakeTypeError('incompatible_method_receiver',
156                        ["NAME.subarray", this]);
157  }
158  var beginInt = TO_INTEGER(begin);
159  if (!IS_UNDEFINED(end)) {
160    end = TO_INTEGER(end);
161  }
162
163  var srcLength = %_TypedArrayGetLength(this);
164  if (beginInt < 0) {
165    beginInt = MathMax(0, srcLength + beginInt);
166  } else {
167    beginInt = MathMin(srcLength, beginInt);
168  }
169
170  var endInt = IS_UNDEFINED(end) ? srcLength : end;
171  if (endInt < 0) {
172    endInt = MathMax(0, srcLength + endInt);
173  } else {
174    endInt = MathMin(endInt, srcLength);
175  }
176  if (endInt < beginInt) {
177    endInt = beginInt;
178  }
179  var newLength = endInt - beginInt;
180  var beginByteOffset =
181      %_ArrayBufferViewGetByteOffset(this) + beginInt * ELEMENT_SIZE;
182  return new $NAME(%TypedArrayGetBuffer(this),
183                   beginByteOffset, newLength);
184}
185endmacro
186
187TYPED_ARRAYS(TYPED_ARRAY_CONSTRUCTOR)
188
189
190function TypedArraySetFromArrayLike(target, source, sourceLength, offset) {
191  if (offset > 0) {
192    for (var i = 0; i < sourceLength; i++) {
193      target[offset + i] = source[i];
194    }
195  }
196  else {
197    for (var i = 0; i < sourceLength; i++) {
198      target[i] = source[i];
199    }
200  }
201}
202
203function TypedArraySetFromOverlappingTypedArray(target, source, offset) {
204  var sourceElementSize = source.BYTES_PER_ELEMENT;
205  var targetElementSize = target.BYTES_PER_ELEMENT;
206  var sourceLength = source.length;
207
208  // Copy left part.
209  function CopyLeftPart() {
210    // First un-mutated byte after the next write
211    var targetPtr = target.byteOffset + (offset + 1) * targetElementSize;
212    // Next read at sourcePtr. We do not care for memory changing before
213    // sourcePtr - we have already copied it.
214    var sourcePtr = source.byteOffset;
215    for (var leftIndex = 0;
216         leftIndex < sourceLength && targetPtr <= sourcePtr;
217         leftIndex++) {
218      target[offset + leftIndex] = source[leftIndex];
219      targetPtr += targetElementSize;
220      sourcePtr += sourceElementSize;
221    }
222    return leftIndex;
223  }
224  var leftIndex = CopyLeftPart();
225
226  // Copy rigth part;
227  function CopyRightPart() {
228    // First unmutated byte before the next write
229    var targetPtr =
230      target.byteOffset + (offset + sourceLength - 1) * targetElementSize;
231    // Next read before sourcePtr. We do not care for memory changing after
232    // sourcePtr - we have already copied it.
233    var sourcePtr =
234      source.byteOffset + sourceLength * sourceElementSize;
235    for(var rightIndex = sourceLength - 1;
236        rightIndex >= leftIndex && targetPtr >= sourcePtr;
237        rightIndex--) {
238      target[offset + rightIndex] = source[rightIndex];
239      targetPtr -= targetElementSize;
240      sourcePtr -= sourceElementSize;
241    }
242    return rightIndex;
243  }
244  var rightIndex = CopyRightPart();
245
246  var temp = new $Array(rightIndex + 1 - leftIndex);
247  for (var i = leftIndex; i <= rightIndex; i++) {
248    temp[i - leftIndex] = source[i];
249  }
250  for (i = leftIndex; i <= rightIndex; i++) {
251    target[offset + i] = temp[i - leftIndex];
252  }
253}
254
255function TypedArraySet(obj, offset) {
256  var intOffset = IS_UNDEFINED(offset) ? 0 : TO_INTEGER(offset);
257  if (intOffset < 0) {
258    throw MakeTypeError("typed_array_set_negative_offset");
259  }
260
261  if (intOffset > %_MaxSmi()) {
262    throw MakeRangeError("typed_array_set_source_too_large");
263  }
264  switch (%TypedArraySetFastCases(this, obj, intOffset)) {
265    // These numbers should be synchronized with runtime.cc.
266    case 0: // TYPED_ARRAY_SET_TYPED_ARRAY_SAME_TYPE
267      return;
268    case 1: // TYPED_ARRAY_SET_TYPED_ARRAY_OVERLAPPING
269      TypedArraySetFromOverlappingTypedArray(this, obj, intOffset);
270      return;
271    case 2: // TYPED_ARRAY_SET_TYPED_ARRAY_NONOVERLAPPING
272      TypedArraySetFromArrayLike(this, obj, obj.length, intOffset);
273      return;
274    case 3: // TYPED_ARRAY_SET_NON_TYPED_ARRAY
275      var l = obj.length;
276      if (IS_UNDEFINED(l)) {
277        if (IS_NUMBER(obj)) {
278            // For number as a first argument, throw TypeError
279            // instead of silently ignoring the call, so that
280            // the user knows (s)he did something wrong.
281            // (Consistent with Firefox and Blink/WebKit)
282            throw MakeTypeError("invalid_argument");
283        }
284        return;
285      }
286      if (intOffset + l > this.length) {
287        throw MakeRangeError("typed_array_set_source_too_large");
288      }
289      TypedArraySetFromArrayLike(this, obj, l, intOffset);
290      return;
291  }
292}
293
294// -------------------------------------------------------------------
295
296function SetupTypedArrays() {
297macro SETUP_TYPED_ARRAY(ARRAY_ID, NAME, ELEMENT_SIZE)
298  %CheckIsBootstrapping();
299  %SetCode(global.NAME, NAMEConstructor);
300  %FunctionSetPrototype(global.NAME, new $Object());
301
302  %AddNamedProperty(global.NAME, "BYTES_PER_ELEMENT", ELEMENT_SIZE,
303                    READ_ONLY | DONT_ENUM | DONT_DELETE);
304  %AddNamedProperty(global.NAME.prototype,
305                    "constructor", global.NAME, DONT_ENUM);
306  %AddNamedProperty(global.NAME.prototype,
307                    "BYTES_PER_ELEMENT", ELEMENT_SIZE,
308                    READ_ONLY | DONT_ENUM | DONT_DELETE);
309  InstallGetter(global.NAME.prototype, "buffer", NAME_GetBuffer);
310  InstallGetter(global.NAME.prototype, "byteOffset", NAME_GetByteOffset);
311  InstallGetter(global.NAME.prototype, "byteLength", NAME_GetByteLength);
312  InstallGetter(global.NAME.prototype, "length", NAME_GetLength);
313
314  InstallFunctions(global.NAME.prototype, DONT_ENUM, $Array(
315        "subarray", NAMESubArray,
316        "set", TypedArraySet
317  ));
318endmacro
319
320TYPED_ARRAYS(SETUP_TYPED_ARRAY)
321}
322
323SetupTypedArrays();
324
325// --------------------------- DataView -----------------------------
326
327var $DataView = global.DataView;
328
329function DataViewConstructor(buffer, byteOffset, byteLength) { // length = 3
330  if (%_IsConstructCall()) {
331    if (!IS_ARRAYBUFFER(buffer)) {
332      throw MakeTypeError('data_view_not_array_buffer', []);
333    }
334    if (!IS_UNDEFINED(byteOffset)) {
335        byteOffset = ToPositiveInteger(byteOffset, 'invalid_data_view_offset');
336    }
337    if (!IS_UNDEFINED(byteLength)) {
338        byteLength = TO_INTEGER(byteLength);
339    }
340
341    var bufferByteLength = %_ArrayBufferGetByteLength(buffer);
342
343    var offset = IS_UNDEFINED(byteOffset) ?  0 : byteOffset;
344    if (offset > bufferByteLength) {
345      throw MakeRangeError('invalid_data_view_offset');
346    }
347
348    var length = IS_UNDEFINED(byteLength)
349        ? bufferByteLength - offset
350        : byteLength;
351    if (length < 0 || offset + length > bufferByteLength) {
352      throw new MakeRangeError('invalid_data_view_length');
353    }
354    %_DataViewInitialize(this, buffer, offset, length);
355  } else {
356    throw MakeTypeError('constructor_not_function', ["DataView"]);
357  }
358}
359
360function DataViewGetBufferJS() {
361  if (!IS_DATAVIEW(this)) {
362    throw MakeTypeError('incompatible_method_receiver',
363                        ['DataView.buffer', this]);
364  }
365  return %DataViewGetBuffer(this);
366}
367
368function DataViewGetByteOffset() {
369  if (!IS_DATAVIEW(this)) {
370    throw MakeTypeError('incompatible_method_receiver',
371                        ['DataView.byteOffset', this]);
372  }
373  return %_ArrayBufferViewGetByteOffset(this);
374}
375
376function DataViewGetByteLength() {
377  if (!IS_DATAVIEW(this)) {
378    throw MakeTypeError('incompatible_method_receiver',
379                        ['DataView.byteLength', this]);
380  }
381  return %_ArrayBufferViewGetByteLength(this);
382}
383
384macro DATA_VIEW_TYPES(FUNCTION)
385  FUNCTION(Int8)
386  FUNCTION(Uint8)
387  FUNCTION(Int16)
388  FUNCTION(Uint16)
389  FUNCTION(Int32)
390  FUNCTION(Uint32)
391  FUNCTION(Float32)
392  FUNCTION(Float64)
393endmacro
394
395function ToPositiveDataViewOffset(offset) {
396  return ToPositiveInteger(offset, 'invalid_data_view_accessor_offset');
397}
398
399
400macro DATA_VIEW_GETTER_SETTER(TYPENAME)
401function DataViewGetTYPENAMEJS(offset, little_endian) {
402  if (!IS_DATAVIEW(this)) {
403    throw MakeTypeError('incompatible_method_receiver',
404                        ['DataView.getTYPENAME', this]);
405  }
406  if (%_ArgumentsLength() < 1) {
407    throw MakeTypeError('invalid_argument');
408  }
409  return %DataViewGetTYPENAME(this,
410                          ToPositiveDataViewOffset(offset),
411                          !!little_endian);
412}
413
414function DataViewSetTYPENAMEJS(offset, value, little_endian) {
415  if (!IS_DATAVIEW(this)) {
416    throw MakeTypeError('incompatible_method_receiver',
417                        ['DataView.setTYPENAME', this]);
418  }
419  if (%_ArgumentsLength() < 2) {
420    throw MakeTypeError('invalid_argument');
421  }
422  %DataViewSetTYPENAME(this,
423                   ToPositiveDataViewOffset(offset),
424                   TO_NUMBER_INLINE(value),
425                   !!little_endian);
426}
427endmacro
428
429DATA_VIEW_TYPES(DATA_VIEW_GETTER_SETTER)
430
431function SetupDataView() {
432  %CheckIsBootstrapping();
433
434  // Setup the DataView constructor.
435  %SetCode($DataView, DataViewConstructor);
436  %FunctionSetPrototype($DataView, new $Object);
437
438  // Set up constructor property on the DataView prototype.
439  %AddNamedProperty($DataView.prototype, "constructor", $DataView, DONT_ENUM);
440
441  InstallGetter($DataView.prototype, "buffer", DataViewGetBufferJS);
442  InstallGetter($DataView.prototype, "byteOffset", DataViewGetByteOffset);
443  InstallGetter($DataView.prototype, "byteLength", DataViewGetByteLength);
444
445  InstallFunctions($DataView.prototype, DONT_ENUM, $Array(
446      "getInt8", DataViewGetInt8JS,
447      "setInt8", DataViewSetInt8JS,
448
449      "getUint8", DataViewGetUint8JS,
450      "setUint8", DataViewSetUint8JS,
451
452      "getInt16", DataViewGetInt16JS,
453      "setInt16", DataViewSetInt16JS,
454
455      "getUint16", DataViewGetUint16JS,
456      "setUint16", DataViewSetUint16JS,
457
458      "getInt32", DataViewGetInt32JS,
459      "setInt32", DataViewSetInt32JS,
460
461      "getUint32", DataViewGetUint32JS,
462      "setUint32", DataViewSetUint32JS,
463
464      "getFloat32", DataViewGetFloat32JS,
465      "setFloat32", DataViewSetFloat32JS,
466
467      "getFloat64", DataViewGetFloat64JS,
468      "setFloat64", DataViewSetFloat64JS
469  ));
470}
471
472SetupDataView();
473