typedarray.js 15.9 KB
Newer Older
1
// Copyright 2013 the V8 project authors. All rights reserved.
2 3
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
4

5
(function(global, utils) {
6

7 8
"use strict";

9
%CheckIsBootstrapping();
10

11 12 13
// -------------------------------------------------------------------
// Imports

14 15
var GlobalArray = global.Array;
var GlobalArrayBuffer = global.ArrayBuffer;
16 17
var GlobalDataView = global.DataView;
var GlobalObject = global.Object;
18 19
var iteratorSymbol = utils.ImportNow("iterator_symbol");
var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol");
20

21 22 23 24 25 26 27 28 29 30 31 32
macro TYPED_ARRAYS(FUNCTION)
// arrayIds below should be synchronized with Runtime_TypedArrayInitialize.
FUNCTION(1, Uint8Array, 1)
FUNCTION(2, Int8Array, 1)
FUNCTION(3, Uint16Array, 2)
FUNCTION(4, Int16Array, 2)
FUNCTION(5, Uint32Array, 4)
FUNCTION(6, Int32Array, 4)
FUNCTION(7, Float32Array, 4)
FUNCTION(8, Float64Array, 8)
FUNCTION(9, Uint8ClampedArray, 1)
endmacro
33

34 35 36 37 38 39
macro DECLARE_GLOBALS(INDEX, NAME, SIZE)
var GlobalNAME = global.NAME;
endmacro

TYPED_ARRAYS(DECLARE_GLOBALS)

40 41
var InternalArray = utils.InternalArray;

42 43
// --------------- Typed Arrays ---------------------

44
macro TYPED_ARRAY_CONSTRUCTOR(ARRAY_ID, NAME, ELEMENT_SIZE)
45 46 47
function NAMEConstructByArrayBuffer(obj, buffer, byteOffset, length) {
  if (!IS_UNDEFINED(byteOffset)) {
      byteOffset =
48
          $toPositiveInteger(byteOffset, kInvalidTypedArrayLength);
49 50
  }
  if (!IS_UNDEFINED(length)) {
51
      length = $toPositiveInteger(length, kInvalidTypedArrayLength);
52
  }
53

54 55 56 57 58 59
  var bufferByteLength = %_ArrayBufferGetByteLength(buffer);
  var offset;
  if (IS_UNDEFINED(byteOffset)) {
    offset = 0;
  } else {
    offset = byteOffset;
60

61
    if (offset % ELEMENT_SIZE !== 0) {
62 63
      throw MakeRangeError(kInvalidTypedArrayAlignment,
                           "start offset", "NAME", ELEMENT_SIZE);
64
    }
65
    if (offset > bufferByteLength) {
66
      throw MakeRangeError(kInvalidTypedArrayOffset);
67
    }
68
  }
69

70 71 72 73
  var newByteLength;
  var newLength;
  if (IS_UNDEFINED(length)) {
    if (bufferByteLength % ELEMENT_SIZE !== 0) {
74 75
      throw MakeRangeError(kInvalidTypedArrayAlignment,
                           "byte length", "NAME", ELEMENT_SIZE);
76
    }
77 78 79 80 81
    newByteLength = bufferByteLength - offset;
    newLength = newByteLength / ELEMENT_SIZE;
  } else {
    var newLength = length;
    newByteLength = newLength * ELEMENT_SIZE;
82
  }
83 84
  if ((offset + newByteLength > bufferByteLength)
      || (newLength > %_MaxSmi())) {
85
    throw MakeRangeError(kInvalidTypedArrayLength);
86
  }
87
  %_TypedArrayInitialize(obj, ARRAY_ID, buffer, offset, newByteLength, true);
88
}
89

90 91
function NAMEConstructByLength(obj, length) {
  var l = IS_UNDEFINED(length) ?
92
    0 : $toPositiveInteger(length, kInvalidTypedArrayLength);
93
  if (l > %_MaxSmi()) {
94
    throw MakeRangeError(kInvalidTypedArrayLength);
95 96 97
  }
  var byteLength = l * ELEMENT_SIZE;
  if (byteLength > %_TypedArrayMaxSizeInHeap()) {
98
    var buffer = new GlobalArrayBuffer(byteLength);
99
    %_TypedArrayInitialize(obj, ARRAY_ID, buffer, 0, byteLength, true);
100
  } else {
101
    %_TypedArrayInitialize(obj, ARRAY_ID, null, 0, byteLength, true);
102 103
  }
}
104

105 106
function NAMEConstructByArrayLike(obj, arrayLike) {
  var length = arrayLike.length;
107
  var l = $toPositiveInteger(length, kInvalidTypedArrayLength);
108 109

  if (l > %_MaxSmi()) {
110
    throw MakeRangeError(kInvalidTypedArrayLength);
111
  }
112 113 114 115 116 117 118 119 120
  var initialized = false;
  var byteLength = l * ELEMENT_SIZE;
  if (byteLength <= %_TypedArrayMaxSizeInHeap()) {
    %_TypedArrayInitialize(obj, ARRAY_ID, null, 0, byteLength, false);
  } else {
    initialized =
        %TypedArrayInitializeFromArrayLike(obj, ARRAY_ID, arrayLike, l);
  }
  if (!initialized) {
121 122 123 124
    for (var i = 0; i < l; i++) {
      // It is crucial that we let any execptions from arrayLike[i]
      // propagate outside the function.
      obj[i] = arrayLike[i];
125
    }
126
  }
127
}
128

129 130 131 132 133 134 135
function NAMEConstructByIterable(obj, iterable, iteratorFn) {
  var list = new InternalArray();
  // Reading the Symbol.iterator property of iterable twice would be
  // observable with getters, so instead, we call the function which
  // was already looked up, and wrap it in another iterable. The
  // __proto__ of the new iterable is set to null to avoid any chance
  // of modifications to Object.prototype being observable here.
136
  var iterator = %_Call(iteratorFn, iterable);
137
  var newIterable = {
138
    __proto__: null
139
  };
140 141
  // TODO(littledan): Computed properties don't work yet in nosnap.
  // Rephrase when they do.
142
  newIterable[iteratorSymbol] = function() { return iterator; }
143 144 145 146 147 148
  for (var value of newIterable) {
    list.push(value);
  }
  NAMEConstructByArrayLike(obj, list);
}

149 150
function NAMEConstructor(arg1, arg2, arg3) {
  if (%_IsConstructCall()) {
binji's avatar
binji committed
151
    if (IS_ARRAYBUFFER(arg1) || IS_SHAREDARRAYBUFFER(arg1)) {
152 153 154 155
      NAMEConstructByArrayBuffer(this, arg1, arg2, arg3);
    } else if (IS_NUMBER(arg1) || IS_STRING(arg1) ||
               IS_BOOLEAN(arg1) || IS_UNDEFINED(arg1)) {
      NAMEConstructByLength(this, arg1);
156
    } else {
157
      var iteratorFn = arg1[iteratorSymbol];
158 159 160 161 162
      if (IS_UNDEFINED(iteratorFn) || iteratorFn === $arrayValues) {
        NAMEConstructByArrayLike(this, arg1);
      } else {
        NAMEConstructByIterable(this, arg1, iteratorFn);
      }
163
    }
164
  } else {
165
    throw MakeTypeError(kConstructorNotFunction, "NAME")
166
  }
167
}
168

169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
function NAME_GetBuffer() {
  if (!(%_ClassOf(this) === 'NAME')) {
    throw MakeTypeError(kIncompatibleMethodReceiver, "NAME.buffer", this);
  }
  return %TypedArrayGetBuffer(this);
}

function NAME_GetByteLength() {
  if (!(%_ClassOf(this) === 'NAME')) {
    throw MakeTypeError(kIncompatibleMethodReceiver, "NAME.byteLength", this);
  }
  return %_ArrayBufferViewGetByteLength(this);
}

function NAME_GetByteOffset() {
  if (!(%_ClassOf(this) === 'NAME')) {
    throw MakeTypeError(kIncompatibleMethodReceiver, "NAME.byteOffset", this);
  }
  return %_ArrayBufferViewGetByteOffset(this);
}

function NAME_GetLength() {
  if (!(%_ClassOf(this) === 'NAME')) {
    throw MakeTypeError(kIncompatibleMethodReceiver, "NAME.length", this);
  }
  return %_TypedArrayGetLength(this);
}

197 198
function NAMESubArray(begin, end) {
  if (!(%_ClassOf(this) === 'NAME')) {
199
    throw MakeTypeError(kIncompatibleMethodReceiver, "NAME.subarray", this);
200 201 202
  }
  var beginInt = TO_INTEGER(begin);
  if (!IS_UNDEFINED(end)) {
203 204 205 206 207
    var endInt = TO_INTEGER(end);
    var srcLength = %_TypedArrayGetLength(this);
  } else {
    var srcLength = %_TypedArrayGetLength(this);
    var endInt = srcLength;
208
  }
209

210
  if (beginInt < 0) {
211
    beginInt = MAX_SIMPLE(0, srcLength + beginInt);
212
  } else {
213
    beginInt = MIN_SIMPLE(beginInt, srcLength);
214
  }
215

216
  if (endInt < 0) {
217
    endInt = MAX_SIMPLE(0, srcLength + endInt);
218
  } else {
219
    endInt = MIN_SIMPLE(endInt, srcLength);
220
  }
221

222 223
  if (endInt < beginInt) {
    endInt = beginInt;
224
  }
225

226 227 228
  var newLength = endInt - beginInt;
  var beginByteOffset =
      %_ArrayBufferViewGetByteOffset(this) + beginInt * ELEMENT_SIZE;
229 230
  return new GlobalNAME(%TypedArrayGetBuffer(this),
                        beginByteOffset, newLength);
231
}
232 233 234 235
endmacro

TYPED_ARRAYS(TYPED_ARRAY_CONSTRUCTOR)

236

237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
function TypedArraySetFromArrayLike(target, source, sourceLength, offset) {
  if (offset > 0) {
    for (var i = 0; i < sourceLength; i++) {
      target[offset + i] = source[i];
    }
  }
  else {
    for (var i = 0; i < sourceLength; i++) {
      target[i] = source[i];
    }
  }
}

function TypedArraySetFromOverlappingTypedArray(target, source, offset) {
  var sourceElementSize = source.BYTES_PER_ELEMENT;
  var targetElementSize = target.BYTES_PER_ELEMENT;
  var sourceLength = source.length;

  // Copy left part.
  function CopyLeftPart() {
    // First un-mutated byte after the next write
    var targetPtr = target.byteOffset + (offset + 1) * targetElementSize;
    // Next read at sourcePtr. We do not care for memory changing before
    // sourcePtr - we have already copied it.
    var sourcePtr = source.byteOffset;
    for (var leftIndex = 0;
         leftIndex < sourceLength && targetPtr <= sourcePtr;
         leftIndex++) {
      target[offset + leftIndex] = source[leftIndex];
      targetPtr += targetElementSize;
      sourcePtr += sourceElementSize;
    }
    return leftIndex;
  }
  var leftIndex = CopyLeftPart();

  // Copy rigth part;
  function CopyRightPart() {
    // First unmutated byte before the next write
    var targetPtr =
      target.byteOffset + (offset + sourceLength - 1) * targetElementSize;
    // Next read before sourcePtr. We do not care for memory changing after
    // sourcePtr - we have already copied it.
    var sourcePtr =
      source.byteOffset + sourceLength * sourceElementSize;
    for(var rightIndex = sourceLength - 1;
        rightIndex >= leftIndex && targetPtr >= sourcePtr;
        rightIndex--) {
      target[offset + rightIndex] = source[rightIndex];
      targetPtr -= targetElementSize;
      sourcePtr -= sourceElementSize;
    }
    return rightIndex;
  }
  var rightIndex = CopyRightPart();

293
  var temp = new GlobalArray(rightIndex + 1 - leftIndex);
294 295 296 297 298 299 300 301
  for (var i = leftIndex; i <= rightIndex; i++) {
    temp[i - leftIndex] = source[i];
  }
  for (i = leftIndex; i <= rightIndex; i++) {
    target[offset + i] = temp[i - leftIndex];
  }
}

302
function TypedArraySet(obj, offset) {
303
  var intOffset = IS_UNDEFINED(offset) ? 0 : TO_INTEGER(offset);
304
  if (intOffset < 0) throw MakeTypeError(kTypedArraySetNegativeOffset);
305

306
  if (intOffset > %_MaxSmi()) {
307
    throw MakeRangeError(kTypedArraySetSourceTooLarge);
308
  }
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
  switch (%TypedArraySetFastCases(this, obj, intOffset)) {
    // These numbers should be synchronized with runtime.cc.
    case 0: // TYPED_ARRAY_SET_TYPED_ARRAY_SAME_TYPE
      return;
    case 1: // TYPED_ARRAY_SET_TYPED_ARRAY_OVERLAPPING
      TypedArraySetFromOverlappingTypedArray(this, obj, intOffset);
      return;
    case 2: // TYPED_ARRAY_SET_TYPED_ARRAY_NONOVERLAPPING
      TypedArraySetFromArrayLike(this, obj, obj.length, intOffset);
      return;
    case 3: // TYPED_ARRAY_SET_NON_TYPED_ARRAY
      var l = obj.length;
      if (IS_UNDEFINED(l)) {
        if (IS_NUMBER(obj)) {
            // For number as a first argument, throw TypeError
            // instead of silently ignoring the call, so that
            // the user knows (s)he did something wrong.
            // (Consistent with Firefox and Blink/WebKit)
327
            throw MakeTypeError(kInvalidArgument);
328 329 330
        }
        return;
      }
331
      l = TO_LENGTH(l);
332
      if (intOffset + l > this.length) {
333
        throw MakeRangeError(kTypedArraySetSourceTooLarge);
334 335 336
      }
      TypedArraySetFromArrayLike(this, obj, l, intOffset);
      return;
337 338
  }
}
339

340
function TypedArrayGetToStringTag() {
341
  if (!%_IsTypedArray(this)) return;
342 343 344 345 346
  var name = %_ClassOf(this);
  if (IS_UNDEFINED(name)) return;
  return name;
}

347 348
// -------------------------------------------------------------------

349
macro SETUP_TYPED_ARRAY(ARRAY_ID, NAME, ELEMENT_SIZE)
350
  %SetCode(GlobalNAME, NAMEConstructor);
351
  %FunctionSetPrototype(GlobalNAME, new GlobalObject());
352

353
  %AddNamedProperty(GlobalNAME, "BYTES_PER_ELEMENT", ELEMENT_SIZE,
354
                    READ_ONLY | DONT_ENUM | DONT_DELETE);
355
  %AddNamedProperty(GlobalNAME.prototype,
356
                    "constructor", global.NAME, DONT_ENUM);
357 358 359 360 361 362 363 364 365 366
  %AddNamedProperty(GlobalNAME.prototype,
                    "BYTES_PER_ELEMENT", ELEMENT_SIZE,
                    READ_ONLY | DONT_ENUM | DONT_DELETE);
  utils.InstallGetter(GlobalNAME.prototype, "buffer", NAME_GetBuffer);
  utils.InstallGetter(GlobalNAME.prototype, "byteOffset", NAME_GetByteOffset,
                      DONT_ENUM | DONT_DELETE);
  utils.InstallGetter(GlobalNAME.prototype, "byteLength", NAME_GetByteLength,
                      DONT_ENUM | DONT_DELETE);
  utils.InstallGetter(GlobalNAME.prototype, "length", NAME_GetLength,
                      DONT_ENUM | DONT_DELETE);
367
  utils.InstallGetter(GlobalNAME.prototype, toStringTagSymbol,
368
                      TypedArrayGetToStringTag);
369
  utils.InstallFunctions(GlobalNAME.prototype, DONT_ENUM, [
370 371
    "subarray", NAMESubArray,
    "set", TypedArraySet
372
  ]);
373 374 375
endmacro

TYPED_ARRAYS(SETUP_TYPED_ARRAY)
376 377 378 379 380

// --------------------------- DataView -----------------------------

function DataViewConstructor(buffer, byteOffset, byteLength) { // length = 3
  if (%_IsConstructCall()) {
binji's avatar
binji committed
381
    // TODO(binji): support SharedArrayBuffers?
382
    if (!IS_ARRAYBUFFER(buffer)) throw MakeTypeError(kDataViewNotArrayBuffer);
383
    if (!IS_UNDEFINED(byteOffset)) {
384
        byteOffset = $toPositiveInteger(byteOffset, kInvalidDataViewOffset);
385 386 387 388 389
    }
    if (!IS_UNDEFINED(byteLength)) {
        byteLength = TO_INTEGER(byteLength);
    }

390
    var bufferByteLength = %_ArrayBufferGetByteLength(buffer);
391 392

    var offset = IS_UNDEFINED(byteOffset) ?  0 : byteOffset;
393
    if (offset > bufferByteLength) throw MakeRangeError(kInvalidDataViewOffset);
394 395 396 397

    var length = IS_UNDEFINED(byteLength)
        ? bufferByteLength - offset
        : byteLength;
398
    if (length < 0 || offset + length > bufferByteLength) {
399
      throw new MakeRangeError(kInvalidDataViewLength);
400
    }
401
    %_DataViewInitialize(this, buffer, offset, length);
402
  } else {
403
    throw MakeTypeError(kConstructorNotFunction, "DataView");
404 405 406
  }
}

407
function DataViewGetBufferJS() {
408
  if (!IS_DATAVIEW(this)) {
409
    throw MakeTypeError(kIncompatibleMethodReceiver, 'DataView.buffer', this);
410 411 412 413 414 415
  }
  return %DataViewGetBuffer(this);
}

function DataViewGetByteOffset() {
  if (!IS_DATAVIEW(this)) {
416 417
    throw MakeTypeError(kIncompatibleMethodReceiver,
                        'DataView.byteOffset', this);
418
  }
419
  return %_ArrayBufferViewGetByteOffset(this);
420 421 422 423
}

function DataViewGetByteLength() {
  if (!IS_DATAVIEW(this)) {
424 425
    throw MakeTypeError(kIncompatibleMethodReceiver,
                        'DataView.byteLength', this);
426
  }
427
  return %_ArrayBufferViewGetByteLength(this);
428 429
}

430 431 432 433 434 435 436 437 438 439 440 441 442
macro DATA_VIEW_TYPES(FUNCTION)
  FUNCTION(Int8)
  FUNCTION(Uint8)
  FUNCTION(Int16)
  FUNCTION(Uint16)
  FUNCTION(Int32)
  FUNCTION(Uint32)
  FUNCTION(Float32)
  FUNCTION(Float64)
endmacro


macro DATA_VIEW_GETTER_SETTER(TYPENAME)
443
function DataViewGetTYPENAMEJS(offset, little_endian) {
444
  if (!IS_DATAVIEW(this)) {
445 446
    throw MakeTypeError(kIncompatibleMethodReceiver,
                        'DataView.getTYPENAME', this);
447
  }
448
  if (%_ArgumentsLength() < 1) throw MakeTypeError(kInvalidArgument);
449 450
  offset = $toPositiveInteger(offset, kInvalidDataViewAccessorOffset);
  return %DataViewGetTYPENAME(this, offset, !!little_endian);
451 452
}

453
function DataViewSetTYPENAMEJS(offset, value, little_endian) {
454
  if (!IS_DATAVIEW(this)) {
455 456
    throw MakeTypeError(kIncompatibleMethodReceiver,
                        'DataView.setTYPENAME', this);
457
  }
458
  if (%_ArgumentsLength() < 2) throw MakeTypeError(kInvalidArgument);
459
  offset = $toPositiveInteger(offset, kInvalidDataViewAccessorOffset);
460
  %DataViewSetTYPENAME(this, offset, TO_NUMBER(value), !!little_endian);
461
}
462
endmacro
463

464
DATA_VIEW_TYPES(DATA_VIEW_GETTER_SETTER)
465

466
// Setup the DataView constructor.
467 468
%SetCode(GlobalDataView, DataViewConstructor);
%FunctionSetPrototype(GlobalDataView, new GlobalObject);
469

470
// Set up constructor property on the DataView prototype.
471 472
%AddNamedProperty(GlobalDataView.prototype, "constructor", GlobalDataView,
                  DONT_ENUM);
473
%AddNamedProperty(GlobalDataView.prototype, toStringTagSymbol, "DataView",
474
                  READ_ONLY|DONT_ENUM);
475

476 477 478 479 480
utils.InstallGetter(GlobalDataView.prototype, "buffer", DataViewGetBufferJS);
utils.InstallGetter(GlobalDataView.prototype, "byteOffset",
                    DataViewGetByteOffset);
utils.InstallGetter(GlobalDataView.prototype, "byteLength",
                    DataViewGetByteLength);
481

482
utils.InstallFunctions(GlobalDataView.prototype, DONT_ENUM, [
483 484
  "getInt8", DataViewGetInt8JS,
  "setInt8", DataViewSetInt8JS,
485

486 487
  "getUint8", DataViewGetUint8JS,
  "setUint8", DataViewSetUint8JS,
488

489 490
  "getInt16", DataViewGetInt16JS,
  "setInt16", DataViewSetInt16JS,
491

492 493
  "getUint16", DataViewGetUint16JS,
  "setUint16", DataViewSetUint16JS,
494

495 496
  "getInt32", DataViewGetInt32JS,
  "setInt32", DataViewSetInt32JS,
497

498 499
  "getUint32", DataViewGetUint32JS,
  "setUint32", DataViewSetUint32JS,
500

501 502
  "getFloat32", DataViewGetFloat32JS,
  "setFloat32", DataViewSetFloat32JS,
503

504 505 506
  "getFloat64", DataViewGetFloat64JS,
  "setFloat64", DataViewSetFloat64JS
]);
507

508
})