1// Copyright 2013 the V8 project authors. All rights reserved.
2// Redistribution and use in source and binary forms, with or without
3// modification, are permitted provided that the following conditions are
4// met:
5//
6//     * Redistributions of source code must retain the above copyright
7//       notice, this list of conditions and the following disclaimer.
8//     * Redistributions in binary form must reproduce the above
9//       copyright notice, this list of conditions and the following
10//       disclaimer in the documentation and/or other materials provided
11//       with the distribution.
12//     * Neither the name of Google Inc. nor the names of its
13//       contributors may be used to endorse or promote products derived
14//       from this software without specific prior written permission.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28"use strict";
29
30// This file relies on the fact that the following declaration has been made
31// in runtime.js:
32// var $Array = global.Array;
33var $ArrayBuffer = global.ArrayBuffer;
34
35
36// --------------- Typed Arrays ---------------------
37macro TYPED_ARRAYS(FUNCTION)
38// arrayIds below should be synchronized with Runtime_TypedArrayInitialize.
39FUNCTION(1, Uint8Array, 1)
40FUNCTION(2, Int8Array, 1)
41FUNCTION(3, Uint16Array, 2)
42FUNCTION(4, Int16Array, 2)
43FUNCTION(5, Uint32Array, 4)
44FUNCTION(6, Int32Array, 4)
45FUNCTION(7, Float32Array, 4)
46FUNCTION(8, Float64Array, 8)
47FUNCTION(9, Uint8ClampedArray, 1)
48endmacro
49
50macro TYPED_ARRAY_CONSTRUCTOR(ARRAY_ID, NAME, ELEMENT_SIZE)
51  function NAMEConstructByArrayBuffer(obj, buffer, byteOffset, length) {
52    var bufferByteLength = %ArrayBufferGetByteLength(buffer);
53    var offset;
54    if (IS_UNDEFINED(byteOffset)) {
55      offset = 0;
56    } else {
57      offset = ToPositiveInteger(byteOffset, "invalid_typed_array_length");
58
59      if (offset % ELEMENT_SIZE !== 0) {
60        throw MakeRangeError("invalid_typed_array_alignment",
61            "start offset", "NAME", ELEMENT_SIZE);
62      }
63      if (offset > bufferByteLength) {
64        throw MakeRangeError("invalid_typed_array_offset");
65      }
66    }
67
68    var newByteLength;
69    var newLength;
70    if (IS_UNDEFINED(length)) {
71      if (bufferByteLength % ELEMENT_SIZE !== 0) {
72        throw MakeRangeError("invalid_typed_array_alignment",
73          "byte length", "NAME", ELEMENT_SIZE);
74      }
75      newByteLength = bufferByteLength - offset;
76      newLength = newByteLength / ELEMENT_SIZE;
77    } else {
78      var newLength = ToPositiveInteger(length, "invalid_typed_array_length");
79      newByteLength = newLength * ELEMENT_SIZE;
80    }
81    if ((offset + newByteLength > bufferByteLength)
82        || (newLength > %MaxSmi())) {
83      throw MakeRangeError("invalid_typed_array_length");
84    }
85    %TypedArrayInitialize(obj, ARRAY_ID, buffer, offset, newByteLength);
86  }
87
88  function NAMEConstructByLength(obj, length) {
89    var l = IS_UNDEFINED(length) ?
90      0 : ToPositiveInteger(length, "invalid_typed_array_length");
91    if (l > %MaxSmi()) {
92      throw MakeRangeError("invalid_typed_array_length");
93    }
94    var byteLength = l * ELEMENT_SIZE;
95    var buffer = new $ArrayBuffer(byteLength);
96    %TypedArrayInitialize(obj, ARRAY_ID, buffer, 0, byteLength);
97  }
98
99  function NAMEConstructByArrayLike(obj, arrayLike) {
100    var length = arrayLike.length;
101    var l = ToPositiveInteger(length, "invalid_typed_array_length");
102    if (l > %MaxSmi()) {
103      throw MakeRangeError("invalid_typed_array_length");
104    }
105    if(!%TypedArrayInitializeFromArrayLike(obj, ARRAY_ID, arrayLike, l)) {
106      for (var i = 0; i < l; i++) {
107        // It is crucial that we let any execptions from arrayLike[i]
108        // propagate outside the function.
109        obj[i] = arrayLike[i];
110      }
111    }
112  }
113
114  function NAMEConstructor(arg1, arg2, arg3) {
115
116    if (%_IsConstructCall()) {
117      if (IS_ARRAYBUFFER(arg1)) {
118        NAMEConstructByArrayBuffer(this, arg1, arg2, arg3);
119      } else if (IS_NUMBER(arg1) || IS_STRING(arg1) ||
120                 IS_BOOLEAN(arg1) || IS_UNDEFINED(arg1)) {
121        NAMEConstructByLength(this, arg1);
122      } else {
123        NAMEConstructByArrayLike(this, arg1);
124      }
125    } else {
126      throw MakeTypeError("constructor_not_function", ["NAME"])
127    }
128  }
129endmacro
130
131TYPED_ARRAYS(TYPED_ARRAY_CONSTRUCTOR)
132
133function TypedArrayGetBuffer() {
134  return %TypedArrayGetBuffer(this);
135}
136
137function TypedArrayGetByteLength() {
138  return %TypedArrayGetByteLength(this);
139}
140
141function TypedArrayGetByteOffset() {
142  return %TypedArrayGetByteOffset(this);
143}
144
145function TypedArrayGetLength() {
146  return %TypedArrayGetLength(this);
147}
148
149function CreateSubArray(elementSize, constructor) {
150  return function(begin, end) {
151    var srcLength = %TypedArrayGetLength(this);
152    var beginInt = TO_INTEGER(begin);
153    if (beginInt < 0) {
154      beginInt = MathMax(0, srcLength + beginInt);
155    } else {
156      beginInt = MathMin(srcLength, beginInt);
157    }
158
159    var endInt = IS_UNDEFINED(end) ? srcLength : TO_INTEGER(end);
160    if (endInt < 0) {
161      endInt = MathMax(0, srcLength + endInt);
162    } else {
163      endInt = MathMin(endInt, srcLength);
164    }
165    if (endInt < beginInt) {
166      endInt = beginInt;
167    }
168    var newLength = endInt - beginInt;
169    var beginByteOffset =
170        %TypedArrayGetByteOffset(this) + beginInt * elementSize;
171    return new constructor(%TypedArrayGetBuffer(this),
172                           beginByteOffset, newLength);
173  }
174}
175
176function TypedArraySetFromArrayLike(target, source, sourceLength, offset) {
177  if (offset > 0) {
178    for (var i = 0; i < sourceLength; i++) {
179      target[offset + i] = source[i];
180    }
181  }
182  else {
183    for (var i = 0; i < sourceLength; i++) {
184      target[i] = source[i];
185    }
186  }
187}
188
189function TypedArraySetFromOverlappingTypedArray(target, source, offset) {
190  var sourceElementSize = source.BYTES_PER_ELEMENT;
191  var targetElementSize = target.BYTES_PER_ELEMENT;
192  var sourceLength = source.length;
193
194  // Copy left part.
195  function CopyLeftPart() {
196    // First un-mutated byte after the next write
197    var targetPtr = target.byteOffset + (offset + 1) * targetElementSize;
198    // Next read at sourcePtr. We do not care for memory changing before
199    // sourcePtr - we have already copied it.
200    var sourcePtr = source.byteOffset;
201    for (var leftIndex = 0;
202         leftIndex < sourceLength && targetPtr <= sourcePtr;
203         leftIndex++) {
204      target[offset + leftIndex] = source[leftIndex];
205      targetPtr += targetElementSize;
206      sourcePtr += sourceElementSize;
207    }
208    return leftIndex;
209  }
210  var leftIndex = CopyLeftPart();
211
212  // Copy rigth part;
213  function CopyRightPart() {
214    // First unmutated byte before the next write
215    var targetPtr =
216      target.byteOffset + (offset + sourceLength - 1) * targetElementSize;
217    // Next read before sourcePtr. We do not care for memory changing after
218    // sourcePtr - we have already copied it.
219    var sourcePtr =
220      source.byteOffset + sourceLength * sourceElementSize;
221    for(var rightIndex = sourceLength - 1;
222        rightIndex >= leftIndex && targetPtr >= sourcePtr;
223        rightIndex--) {
224      target[offset + rightIndex] = source[rightIndex];
225      targetPtr -= targetElementSize;
226      sourcePtr -= sourceElementSize;
227    }
228    return rightIndex;
229  }
230  var rightIndex = CopyRightPart();
231
232  var temp = new $Array(rightIndex + 1 - leftIndex);
233  for (var i = leftIndex; i <= rightIndex; i++) {
234    temp[i - leftIndex] = source[i];
235  }
236  for (i = leftIndex; i <= rightIndex; i++) {
237    target[offset + i] = temp[i - leftIndex];
238  }
239}
240
241function TypedArraySet(obj, offset) {
242  var intOffset = IS_UNDEFINED(offset) ? 0 : TO_INTEGER(offset);
243  if (intOffset < 0) {
244    throw MakeTypeError("typed_array_set_negative_offset");
245  }
246  switch (%TypedArraySetFastCases(this, obj, intOffset)) {
247    // These numbers should be synchronized with runtime.cc.
248    case 0: // TYPED_ARRAY_SET_TYPED_ARRAY_SAME_TYPE
249      return;
250    case 1: // TYPED_ARRAY_SET_TYPED_ARRAY_OVERLAPPING
251      TypedArraySetFromOverlappingTypedArray(this, obj, intOffset);
252      return;
253    case 2: // TYPED_ARRAY_SET_TYPED_ARRAY_NONOVERLAPPING
254      TypedArraySetFromArrayLike(this, obj, obj.length, intOffset);
255      return;
256    case 3: // TYPED_ARRAY_SET_NON_TYPED_ARRAY
257      var l = obj.length;
258      if (IS_UNDEFINED(l)) {
259        if (IS_NUMBER(obj)) {
260            // For number as a first argument, throw TypeError
261            // instead of silently ignoring the call, so that
262            // the user knows (s)he did something wrong.
263            // (Consistent with Firefox and Blink/WebKit)
264            throw MakeTypeError("invalid_argument");
265        }
266        return;
267      }
268      if (intOffset + l > this.length) {
269        throw MakeRangeError("typed_array_set_source_too_large");
270      }
271      TypedArraySetFromArrayLike(this, obj, l, intOffset);
272      return;
273  }
274}
275
276// -------------------------------------------------------------------
277
278function SetupTypedArray(constructor, fun, elementSize) {
279  %CheckIsBootstrapping();
280  %SetCode(constructor, fun);
281  %FunctionSetPrototype(constructor, new $Object());
282
283  %SetProperty(constructor, "BYTES_PER_ELEMENT", elementSize,
284               READ_ONLY | DONT_ENUM | DONT_DELETE);
285  %SetProperty(constructor.prototype,
286               "constructor", constructor, DONT_ENUM);
287  %SetProperty(constructor.prototype,
288               "BYTES_PER_ELEMENT", elementSize,
289               READ_ONLY | DONT_ENUM | DONT_DELETE);
290  InstallGetter(constructor.prototype, "buffer", TypedArrayGetBuffer);
291  InstallGetter(constructor.prototype, "byteOffset", TypedArrayGetByteOffset);
292  InstallGetter(constructor.prototype, "byteLength", TypedArrayGetByteLength);
293  InstallGetter(constructor.prototype, "length", TypedArrayGetLength);
294
295  InstallFunctions(constructor.prototype, DONT_ENUM, $Array(
296        "subarray", CreateSubArray(elementSize, constructor),
297        "set", TypedArraySet
298  ));
299}
300
301macro SETUP_TYPED_ARRAY(ARRAY_ID, NAME, ELEMENT_SIZE)
302  SetupTypedArray (global.NAME, NAMEConstructor, ELEMENT_SIZE);
303endmacro
304
305TYPED_ARRAYS(SETUP_TYPED_ARRAY)
306
307// --------------------------- DataView -----------------------------
308
309var $DataView = global.DataView;
310
311function DataViewConstructor(buffer, byteOffset, byteLength) { // length = 3
312  if (%_IsConstructCall()) {
313    if (!IS_ARRAYBUFFER(buffer)) {
314      throw MakeTypeError('data_view_not_array_buffer', []);
315    }
316    var bufferByteLength = %ArrayBufferGetByteLength(buffer);
317    var offset = IS_UNDEFINED(byteOffset) ?
318      0 : ToPositiveInteger(byteOffset, 'invalid_data_view_offset');
319    if (offset > bufferByteLength) {
320      throw MakeRangeError('invalid_data_view_offset');
321    }
322    var length = IS_UNDEFINED(byteLength) ?
323      bufferByteLength - offset : TO_INTEGER(byteLength);
324    if (length < 0 || offset + length > bufferByteLength) {
325      throw new MakeRangeError('invalid_data_view_length');
326    }
327    %DataViewInitialize(this, buffer, offset, length);
328  } else {
329    throw MakeTypeError('constructor_not_function', ["DataView"]);
330  }
331}
332
333function DataViewGetBuffer() {
334  if (!IS_DATAVIEW(this)) {
335    throw MakeTypeError('incompatible_method_receiver',
336                        ['DataView.buffer', this]);
337  }
338  return %DataViewGetBuffer(this);
339}
340
341function DataViewGetByteOffset() {
342  if (!IS_DATAVIEW(this)) {
343    throw MakeTypeError('incompatible_method_receiver',
344                        ['DataView.byteOffset', this]);
345  }
346  return %DataViewGetByteOffset(this);
347}
348
349function DataViewGetByteLength() {
350  if (!IS_DATAVIEW(this)) {
351    throw MakeTypeError('incompatible_method_receiver',
352                        ['DataView.byteLength', this]);
353  }
354  return %DataViewGetByteLength(this);
355}
356
357macro DATA_VIEW_TYPES(FUNCTION)
358  FUNCTION(Int8)
359  FUNCTION(Uint8)
360  FUNCTION(Int16)
361  FUNCTION(Uint16)
362  FUNCTION(Int32)
363  FUNCTION(Uint32)
364  FUNCTION(Float32)
365  FUNCTION(Float64)
366endmacro
367
368function ToPositiveDataViewOffset(offset) {
369  return ToPositiveInteger(offset, 'invalid_data_view_accessor_offset');
370}
371
372
373macro DATA_VIEW_GETTER_SETTER(TYPENAME)
374function DataViewGetTYPENAME(offset, little_endian) {
375  if (!IS_DATAVIEW(this)) {
376    throw MakeTypeError('incompatible_method_receiver',
377                        ['DataView.getTYPENAME', this]);
378  }
379  if (%_ArgumentsLength() < 1) {
380    throw MakeTypeError('invalid_argument');
381  }
382  return %DataViewGetTYPENAME(this,
383                          ToPositiveDataViewOffset(offset),
384                          !!little_endian);
385}
386
387function DataViewSetTYPENAME(offset, value, little_endian) {
388  if (!IS_DATAVIEW(this)) {
389    throw MakeTypeError('incompatible_method_receiver',
390                        ['DataView.setTYPENAME', this]);
391  }
392  if (%_ArgumentsLength() < 2) {
393    throw MakeTypeError('invalid_argument');
394  }
395  %DataViewSetTYPENAME(this,
396                   ToPositiveDataViewOffset(offset),
397                   TO_NUMBER_INLINE(value),
398                   !!little_endian);
399}
400endmacro
401
402DATA_VIEW_TYPES(DATA_VIEW_GETTER_SETTER)
403
404function SetupDataView() {
405  %CheckIsBootstrapping();
406
407  // Setup the DataView constructor.
408  %SetCode($DataView, DataViewConstructor);
409  %FunctionSetPrototype($DataView, new $Object);
410
411  // Set up constructor property on the DataView prototype.
412  %SetProperty($DataView.prototype, "constructor", $DataView, DONT_ENUM);
413
414  InstallGetter($DataView.prototype, "buffer", DataViewGetBuffer);
415  InstallGetter($DataView.prototype, "byteOffset", DataViewGetByteOffset);
416  InstallGetter($DataView.prototype, "byteLength", DataViewGetByteLength);
417
418  InstallFunctions($DataView.prototype, DONT_ENUM, $Array(
419      "getInt8", DataViewGetInt8,
420      "setInt8", DataViewSetInt8,
421
422      "getUint8", DataViewGetUint8,
423      "setUint8", DataViewSetUint8,
424
425      "getInt16", DataViewGetInt16,
426      "setInt16", DataViewSetInt16,
427
428      "getUint16", DataViewGetUint16,
429      "setUint16", DataViewSetUint16,
430
431      "getInt32", DataViewGetInt32,
432      "setInt32", DataViewSetInt32,
433
434      "getUint32", DataViewGetUint32,
435      "setUint32", DataViewSetUint32,
436
437      "getFloat32", DataViewGetFloat32,
438      "setFloat32", DataViewSetFloat32,
439
440      "getFloat64", DataViewGetFloat64,
441      "setFloat64", DataViewSetFloat64
442  ));
443}
444
445SetupDataView();
446