typedarray.js 14.6 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 6

"use strict";

7 8 9
// This file relies on the fact that the following declaration has been made
// in runtime.js:
// var $Array = global.Array;
10
var $ArrayBuffer = global.ArrayBuffer;
11 12


13
// --------------- Typed Arrays ---------------------
14 15 16 17 18 19 20 21 22 23 24 25
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
26

27
macro TYPED_ARRAY_CONSTRUCTOR(ARRAY_ID, NAME, ELEMENT_SIZE)
28 29 30 31 32 33 34 35
function NAMEConstructByArrayBuffer(obj, buffer, byteOffset, length) {
  if (!IS_UNDEFINED(byteOffset)) {
      byteOffset =
          ToPositiveInteger(byteOffset,  "invalid_typed_array_length");
  }
  if (!IS_UNDEFINED(length)) {
      length = ToPositiveInteger(length, "invalid_typed_array_length");
  }
36

37 38 39 40 41 42
  var bufferByteLength = %_ArrayBufferGetByteLength(buffer);
  var offset;
  if (IS_UNDEFINED(byteOffset)) {
    offset = 0;
  } else {
    offset = byteOffset;
43

44 45 46
    if (offset % ELEMENT_SIZE !== 0) {
      throw MakeRangeError("invalid_typed_array_alignment",
          ["start offset", "NAME", ELEMENT_SIZE]);
47
    }
48 49
    if (offset > bufferByteLength) {
      throw MakeRangeError("invalid_typed_array_offset");
50
    }
51
  }
52

53 54 55 56 57 58
  var newByteLength;
  var newLength;
  if (IS_UNDEFINED(length)) {
    if (bufferByteLength % ELEMENT_SIZE !== 0) {
      throw MakeRangeError("invalid_typed_array_alignment",
        ["byte length", "NAME", ELEMENT_SIZE]);
59
    }
60 61 62 63 64
    newByteLength = bufferByteLength - offset;
    newLength = newByteLength / ELEMENT_SIZE;
  } else {
    var newLength = length;
    newByteLength = newLength * ELEMENT_SIZE;
65
  }
66 67 68 69 70 71
  if ((offset + newByteLength > bufferByteLength)
      || (newLength > %_MaxSmi())) {
    throw MakeRangeError("invalid_typed_array_length");
  }
  %_TypedArrayInitialize(obj, ARRAY_ID, buffer, offset, newByteLength);
}
72

73 74 75 76 77 78 79 80 81 82 83 84 85 86
function NAMEConstructByLength(obj, length) {
  var l = IS_UNDEFINED(length) ?
    0 : ToPositiveInteger(length, "invalid_typed_array_length");
  if (l > %_MaxSmi()) {
    throw MakeRangeError("invalid_typed_array_length");
  }
  var byteLength = l * ELEMENT_SIZE;
  if (byteLength > %_TypedArrayMaxSizeInHeap()) {
    var buffer = new $ArrayBuffer(byteLength);
    %_TypedArrayInitialize(obj, ARRAY_ID, buffer, 0, byteLength);
  } else {
    %_TypedArrayInitialize(obj, ARRAY_ID, null, 0, byteLength);
  }
}
87

88 89 90 91 92 93 94 95 96 97 98 99
function NAMEConstructByArrayLike(obj, arrayLike) {
  var length = arrayLike.length;
  var l = ToPositiveInteger(length, "invalid_typed_array_length");

  if (l > %_MaxSmi()) {
    throw MakeRangeError("invalid_typed_array_length");
  }
  if(!%TypedArrayInitializeFromArrayLike(obj, ARRAY_ID, arrayLike, l)) {
    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];
100
    }
101
  }
102
}
103

104 105 106 107 108 109 110
function NAMEConstructor(arg1, arg2, arg3) {
  if (%_IsConstructCall()) {
    if (IS_ARRAYBUFFER(arg1)) {
      NAMEConstructByArrayBuffer(this, arg1, arg2, arg3);
    } else if (IS_NUMBER(arg1) || IS_STRING(arg1) ||
               IS_BOOLEAN(arg1) || IS_UNDEFINED(arg1)) {
      NAMEConstructByLength(this, arg1);
111
    } else {
112
      NAMEConstructByArrayLike(this, arg1);
113
    }
114 115
  } else {
    throw MakeTypeError("constructor_not_function", ["NAME"])
116
  }
117
}
118

119 120 121 122
function NAME_GetBuffer() {
  if (!(%_ClassOf(this) === 'NAME')) {
    throw MakeTypeError('incompatible_method_receiver',
                        ["NAME.buffer", this]);
123
  }
124 125
  return %TypedArrayGetBuffer(this);
}
126

127 128 129 130
function NAME_GetByteLength() {
  if (!(%_ClassOf(this) === 'NAME')) {
    throw MakeTypeError('incompatible_method_receiver',
                        ["NAME.byteLength", this]);
131
  }
132 133
  return %_ArrayBufferViewGetByteLength(this);
}
134

135 136 137 138
function NAME_GetByteOffset() {
  if (!(%_ClassOf(this) === 'NAME')) {
    throw MakeTypeError('incompatible_method_receiver',
                        ["NAME.byteOffset", this]);
139
  }
140 141
  return %_ArrayBufferViewGetByteOffset(this);
}
142

143 144 145 146
function NAME_GetLength() {
  if (!(%_ClassOf(this) === 'NAME')) {
    throw MakeTypeError('incompatible_method_receiver',
                        ["NAME.length", this]);
147
  }
148 149
  return %_TypedArrayGetLength(this);
}
150

151
var $NAME = global.NAME;
152

153 154 155 156 157 158 159 160 161
function NAMESubArray(begin, end) {
  if (!(%_ClassOf(this) === 'NAME')) {
    throw MakeTypeError('incompatible_method_receiver',
                        ["NAME.subarray", this]);
  }
  var beginInt = TO_INTEGER(begin);
  if (!IS_UNDEFINED(end)) {
    end = TO_INTEGER(end);
  }
162

163 164
  var srcLength = %_TypedArrayGetLength(this);
  if (beginInt < 0) {
165
    beginInt = $max(0, srcLength + beginInt);
166
  } else {
167
    beginInt = $min(srcLength, beginInt);
168
  }
169

170 171
  var endInt = IS_UNDEFINED(end) ? srcLength : end;
  if (endInt < 0) {
172
    endInt = $max(0, srcLength + endInt);
173
  } else {
174
    endInt = $min(endInt, srcLength);
175 176 177
  }
  if (endInt < beginInt) {
    endInt = beginInt;
178
  }
179 180 181 182 183 184
  var newLength = endInt - beginInt;
  var beginByteOffset =
      %_ArrayBufferViewGetByteOffset(this) + beginInt * ELEMENT_SIZE;
  return new $NAME(%TypedArrayGetBuffer(this),
                   beginByteOffset, newLength);
}
185 186 187 188
endmacro

TYPED_ARRAYS(TYPED_ARRAY_CONSTRUCTOR)

189

190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
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();

  var temp = new $Array(rightIndex + 1 - leftIndex);
  for (var i = leftIndex; i <= rightIndex; i++) {
    temp[i - leftIndex] = source[i];
  }
  for (i = leftIndex; i <= rightIndex; i++) {
    target[offset + i] = temp[i - leftIndex];
  }
}

255
function TypedArraySet(obj, offset) {
256 257 258 259
  var intOffset = IS_UNDEFINED(offset) ? 0 : TO_INTEGER(offset);
  if (intOffset < 0) {
    throw MakeTypeError("typed_array_set_negative_offset");
  }
260

261
  if (intOffset > %_MaxSmi()) {
262 263
    throw MakeRangeError("typed_array_set_source_too_large");
  }
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
  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)
            throw MakeTypeError("invalid_argument");
        }
        return;
      }
      if (intOffset + l > this.length) {
        throw MakeRangeError("typed_array_set_source_too_large");
      }
      TypedArraySetFromArrayLike(this, obj, l, intOffset);
      return;
291 292
  }
}
293

294 295 296 297 298 299 300
function TypedArrayGetToStringTag() {
  if (!%IsTypedArray(this)) return;
  var name = %_ClassOf(this);
  if (IS_UNDEFINED(name)) return;
  return name;
}

301 302
// -------------------------------------------------------------------

303 304
function SetupTypedArrays() {
macro SETUP_TYPED_ARRAY(ARRAY_ID, NAME, ELEMENT_SIZE)
305
  %CheckIsBootstrapping();
306 307
  %SetCode(global.NAME, NAMEConstructor);
  %FunctionSetPrototype(global.NAME, new $Object());
308

309 310 311 312 313 314 315
  %AddNamedProperty(global.NAME, "BYTES_PER_ELEMENT", ELEMENT_SIZE,
                    READ_ONLY | DONT_ENUM | DONT_DELETE);
  %AddNamedProperty(global.NAME.prototype,
                    "constructor", global.NAME, DONT_ENUM);
  %AddNamedProperty(global.NAME.prototype,
                    "BYTES_PER_ELEMENT", ELEMENT_SIZE,
                    READ_ONLY | DONT_ENUM | DONT_DELETE);
316
  InstallGetter(global.NAME.prototype, "buffer", NAME_GetBuffer);
317 318 319 320 321 322
  InstallGetter(global.NAME.prototype, "byteOffset", NAME_GetByteOffset,
      DONT_ENUM | DONT_DELETE);
  InstallGetter(global.NAME.prototype, "byteLength", NAME_GetByteLength,
      DONT_ENUM | DONT_DELETE);
  InstallGetter(global.NAME.prototype, "length", NAME_GetLength,
      DONT_ENUM | DONT_DELETE);
323 324
  InstallGetter(global.NAME.prototype, symbolToStringTag,
                TypedArrayGetToStringTag);
325 326
  InstallFunctions(global.NAME.prototype, DONT_ENUM, $Array(
        "subarray", NAMESubArray,
327
        "set", TypedArraySet
328
  ));
329 330 331
endmacro

TYPED_ARRAYS(SETUP_TYPED_ARRAY)
332 333 334
}

SetupTypedArrays();
335 336 337 338 339 340 341 342 343 344

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

var $DataView = global.DataView;

function DataViewConstructor(buffer, byteOffset, byteLength) { // length = 3
  if (%_IsConstructCall()) {
    if (!IS_ARRAYBUFFER(buffer)) {
      throw MakeTypeError('data_view_not_array_buffer', []);
    }
345 346 347 348 349 350 351
    if (!IS_UNDEFINED(byteOffset)) {
        byteOffset = ToPositiveInteger(byteOffset, 'invalid_data_view_offset');
    }
    if (!IS_UNDEFINED(byteLength)) {
        byteLength = TO_INTEGER(byteLength);
    }

352
    var bufferByteLength = %_ArrayBufferGetByteLength(buffer);
353 354

    var offset = IS_UNDEFINED(byteOffset) ?  0 : byteOffset;
355
    if (offset > bufferByteLength) {
356
      throw MakeRangeError('invalid_data_view_offset');
357
    }
358 359 360 361

    var length = IS_UNDEFINED(byteLength)
        ? bufferByteLength - offset
        : byteLength;
362
    if (length < 0 || offset + length > bufferByteLength) {
363 364
      throw new MakeRangeError('invalid_data_view_length');
    }
365
    %_DataViewInitialize(this, buffer, offset, length);
366
  } else {
367
    throw MakeTypeError('constructor_not_function', ["DataView"]);
368 369 370
  }
}

371
function DataViewGetBufferJS() {
372
  if (!IS_DATAVIEW(this)) {
373
    throw MakeTypeError('incompatible_method_receiver',
374 375 376 377 378 379 380
                        ['DataView.buffer', this]);
  }
  return %DataViewGetBuffer(this);
}

function DataViewGetByteOffset() {
  if (!IS_DATAVIEW(this)) {
381
    throw MakeTypeError('incompatible_method_receiver',
382 383
                        ['DataView.byteOffset', this]);
  }
384
  return %_ArrayBufferViewGetByteOffset(this);
385 386 387 388
}

function DataViewGetByteLength() {
  if (!IS_DATAVIEW(this)) {
389
    throw MakeTypeError('incompatible_method_receiver',
390 391
                        ['DataView.byteLength', this]);
  }
392
  return %_ArrayBufferViewGetByteLength(this);
393 394
}

395 396 397 398 399 400 401 402 403 404 405
macro DATA_VIEW_TYPES(FUNCTION)
  FUNCTION(Int8)
  FUNCTION(Uint8)
  FUNCTION(Int16)
  FUNCTION(Uint16)
  FUNCTION(Int32)
  FUNCTION(Uint32)
  FUNCTION(Float32)
  FUNCTION(Float64)
endmacro

406 407 408 409
function ToPositiveDataViewOffset(offset) {
  return ToPositiveInteger(offset, 'invalid_data_view_accessor_offset');
}

410 411

macro DATA_VIEW_GETTER_SETTER(TYPENAME)
412
function DataViewGetTYPENAMEJS(offset, little_endian) {
413
  if (!IS_DATAVIEW(this)) {
414
    throw MakeTypeError('incompatible_method_receiver',
415
                        ['DataView.getTYPENAME', this]);
416
  }
417 418 419
  if (%_ArgumentsLength() < 1) {
    throw MakeTypeError('invalid_argument');
  }
420
  return %DataViewGetTYPENAME(this,
421 422
                          ToPositiveDataViewOffset(offset),
                          !!little_endian);
423 424
}

425
function DataViewSetTYPENAMEJS(offset, value, little_endian) {
426
  if (!IS_DATAVIEW(this)) {
427
    throw MakeTypeError('incompatible_method_receiver',
428
                        ['DataView.setTYPENAME', this]);
429
  }
430
  if (%_ArgumentsLength() < 2) {
431 432
    throw MakeTypeError('invalid_argument');
  }
433
  %DataViewSetTYPENAME(this,
434
                   ToPositiveDataViewOffset(offset),
435 436 437
                   TO_NUMBER_INLINE(value),
                   !!little_endian);
}
438
endmacro
439

440
DATA_VIEW_TYPES(DATA_VIEW_GETTER_SETTER)
441 442 443 444 445 446 447 448 449

function SetupDataView() {
  %CheckIsBootstrapping();

  // Setup the DataView constructor.
  %SetCode($DataView, DataViewConstructor);
  %FunctionSetPrototype($DataView, new $Object);

  // Set up constructor property on the DataView prototype.
450
  %AddNamedProperty($DataView.prototype, "constructor", $DataView, DONT_ENUM);
451 452
  %AddNamedProperty(
      $DataView.prototype, symbolToStringTag, "DataView", READ_ONLY|DONT_ENUM);
453

454
  InstallGetter($DataView.prototype, "buffer", DataViewGetBufferJS);
455 456 457 458
  InstallGetter($DataView.prototype, "byteOffset", DataViewGetByteOffset);
  InstallGetter($DataView.prototype, "byteLength", DataViewGetByteLength);

  InstallFunctions($DataView.prototype, DONT_ENUM, $Array(
459 460
      "getInt8", DataViewGetInt8JS,
      "setInt8", DataViewSetInt8JS,
461

462 463
      "getUint8", DataViewGetUint8JS,
      "setUint8", DataViewSetUint8JS,
464

465 466
      "getInt16", DataViewGetInt16JS,
      "setInt16", DataViewSetInt16JS,
467

468 469
      "getUint16", DataViewGetUint16JS,
      "setUint16", DataViewSetUint16JS,
470

471 472
      "getInt32", DataViewGetInt32JS,
      "setInt32", DataViewSetInt32JS,
473

474 475
      "getUint32", DataViewGetUint32JS,
      "setUint32", DataViewSetUint32JS,
476

477 478
      "getFloat32", DataViewGetFloat32JS,
      "setFloat32", DataViewSetFloat32JS,
479

480 481
      "getFloat64", DataViewGetFloat64JS,
      "setFloat64", DataViewSetFloat64JS
482 483 484 485
  ));
}

SetupDataView();