1// Copyright 2019 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#include 'src/builtins/builtins-constructor-gen.h' 6 7namespace typed_array { 8 extern builtin IterableToListMayPreserveHoles(Context, Object, Callable): 9 JSArray; 10 11 extern macro TypedArrayBuiltinsAssembler::AllocateEmptyOnHeapBuffer( 12 implicit context: Context)(uintptr): JSArrayBuffer; 13 extern macro CodeStubAssembler::AllocateByteArray(uintptr): ByteArray; 14 extern macro TypedArrayBuiltinsAssembler::GetDefaultConstructor( 15 implicit context: Context)(JSTypedArray): JSFunction; 16 extern macro TypedArrayBuiltinsAssembler::SetupTypedArrayEmbedderFields( 17 JSTypedArray): void; 18 19 extern runtime ThrowInvalidTypedArrayAlignment(implicit context: Context)( 20 Map, String): never; 21 22 transitioning macro AllocateTypedArray(implicit context: Context)( 23 isOnHeap: constexpr bool, map: Map, buffer: JSArrayBuffer, 24 byteOffset: uintptr, byteLength: uintptr, length: uintptr): JSTypedArray { 25 let elements: ByteArray; 26 if constexpr (isOnHeap) { 27 elements = AllocateByteArray(byteLength); 28 } else { 29 elements = kEmptyByteArray; 30 31 // The max byteOffset is 8 * MaxSmi on the particular platform. 32 bit 32 // platforms are self-limiting, because we can't allocate an array bigger 33 // than our 32-bit arithmetic range anyway. 64 bit platforms could 34 // theoretically have an offset up to 2^35 - 1. 35 const backingStore: uintptr = Convert<uintptr>(buffer.backing_store); 36 37 // Assert no overflow has occurred. Only assert if the mock array buffer 38 // allocator is NOT used. When the mock array buffer is used, impossibly 39 // large allocations are allowed that would erroneously cause an overflow 40 // and this assertion to fail. 41 assert( 42 IsMockArrayBufferAllocatorFlag() || 43 (backingStore + byteOffset) >= backingStore); 44 } 45 46 // We can't just build the new object with "new JSTypedArray" here because 47 // Torque doesn't know its full size including embedder fields, so use CSA 48 // for the allocation step. 49 const typedArray = 50 UnsafeCast<JSTypedArray>(AllocateFastOrSlowJSObjectFromMap(map)); 51 typedArray.elements = elements; 52 typedArray.buffer = buffer; 53 typedArray.byte_offset = byteOffset; 54 typedArray.byte_length = byteLength; 55 typedArray.length = length; 56 if constexpr (isOnHeap) { 57 typed_array::SetJSTypedArrayOnHeapDataPtr( 58 typedArray, elements, byteOffset); 59 } else { 60 typed_array::SetJSTypedArrayOffHeapDataPtr( 61 typedArray, buffer.backing_store, byteOffset); 62 assert( 63 typedArray.data_ptr == 64 (buffer.backing_store + Convert<intptr>(byteOffset))); 65 } 66 SetupTypedArrayEmbedderFields(typedArray); 67 return typedArray; 68 } 69 70 transitioning macro TypedArrayInitialize(implicit context: Context)( 71 initialize: constexpr bool, map: Map, length: uintptr, 72 elementsInfo: typed_array::TypedArrayElementsInfo, 73 bufferConstructor: JSReceiver): JSTypedArray labels IfRangeError { 74 const byteLength = elementsInfo.CalculateByteLength(length) 75 otherwise IfRangeError; 76 const byteLengthNum = Convert<Number>(byteLength); 77 const defaultConstructor = GetArrayBufferFunction(); 78 const byteOffset: uintptr = 0; 79 80 try { 81 if (bufferConstructor != defaultConstructor) { 82 goto AttachOffHeapBuffer(ConstructWithTarget( 83 defaultConstructor, bufferConstructor, byteLengthNum)); 84 } 85 86 if (byteLength > kMaxTypedArrayInHeap) goto AllocateOffHeap; 87 88 const buffer = AllocateEmptyOnHeapBuffer(byteLength); 89 90 const isOnHeap: constexpr bool = true; 91 const typedArray = AllocateTypedArray( 92 isOnHeap, map, buffer, byteOffset, byteLength, length); 93 94 if constexpr (initialize) { 95 const backingStore = typedArray.data_ptr; 96 typed_array::CallCMemset(backingStore, 0, byteLength); 97 } 98 99 return typedArray; 100 } 101 label AllocateOffHeap { 102 if constexpr (initialize) { 103 goto AttachOffHeapBuffer(Construct(defaultConstructor, byteLengthNum)); 104 } else { 105 goto AttachOffHeapBuffer(Call( 106 context, GetArrayBufferNoInitFunction(), Undefined, byteLengthNum)); 107 } 108 } 109 label AttachOffHeapBuffer(bufferObj: Object) { 110 const buffer = Cast<JSArrayBuffer>(bufferObj) otherwise unreachable; 111 const isOnHeap: constexpr bool = false; 112 return AllocateTypedArray( 113 isOnHeap, map, buffer, byteOffset, byteLength, length); 114 } 115 } 116 117 // 22.2.4.2 TypedArray ( length ) 118 // ES #sec-typedarray-length 119 transitioning macro ConstructByLength(implicit context: Context)( 120 map: Map, lengthObj: JSAny, 121 elementsInfo: typed_array::TypedArrayElementsInfo): JSTypedArray { 122 try { 123 const length: uintptr = ToIndex(lengthObj) otherwise RangeError; 124 const defaultConstructor: Constructor = GetArrayBufferFunction(); 125 const initialize: constexpr bool = true; 126 return TypedArrayInitialize( 127 initialize, map, length, elementsInfo, defaultConstructor) 128 otherwise RangeError; 129 } 130 label RangeError deferred { 131 ThrowRangeError(MessageTemplate::kInvalidTypedArrayLength, lengthObj); 132 } 133 } 134 135 // 22.2.4.4 TypedArray ( object ) 136 // ES #sec-typedarray-object 137 transitioning macro ConstructByArrayLike(implicit context: Context)( 138 map: Map, arrayLike: HeapObject, length: uintptr, 139 elementsInfo: typed_array::TypedArrayElementsInfo, 140 bufferConstructor: JSReceiver): JSTypedArray { 141 try { 142 const initialize: constexpr bool = false; 143 const typedArray = TypedArrayInitialize( 144 initialize, map, length, elementsInfo, bufferConstructor) 145 otherwise RangeError; 146 147 try { 148 const src: JSTypedArray = 149 Cast<JSTypedArray>(arrayLike) otherwise IfSlow; 150 151 if (IsDetachedBuffer(src.buffer)) { 152 ThrowTypeError(MessageTemplate::kDetachedOperation, 'Construct'); 153 154 } else if (src.elements_kind != elementsInfo.kind) { 155 goto IfSlow; 156 157 } else if (length > 0) { 158 const byteLength = typedArray.byte_length; 159 assert(byteLength <= kArrayBufferMaxByteLength); 160 typed_array::CallCMemcpy( 161 typedArray.data_ptr, src.data_ptr, byteLength); 162 } 163 } 164 label IfSlow deferred { 165 if (length > 0) { 166 TypedArrayCopyElements( 167 context, typedArray, arrayLike, Convert<Number>(length)); 168 } 169 } 170 return typedArray; 171 } 172 label RangeError deferred { 173 ThrowRangeError( 174 MessageTemplate::kInvalidTypedArrayLength, Convert<Number>(length)); 175 } 176 } 177 178 // 22.2.4.4 TypedArray ( object ) 179 // ES #sec-typedarray-object 180 transitioning macro ConstructByIterable(implicit context: Context)( 181 iterable: JSReceiver, iteratorFn: Callable): never 182 labels IfConstructByArrayLike(JSArray, uintptr, JSReceiver) { 183 const array: JSArray = 184 IterableToListMayPreserveHoles(context, iterable, iteratorFn); 185 // Max JSArray length is a valid JSTypedArray length so we just use it. 186 goto IfConstructByArrayLike( 187 array, array.length_uintptr, GetArrayBufferFunction()); 188 } 189 190 // 22.2.4.3 TypedArray ( typedArray ) 191 // ES #sec-typedarray-typedarray 192 transitioning macro ConstructByTypedArray(implicit context: Context)( 193 srcTypedArray: JSTypedArray): never 194 labels IfConstructByArrayLike(JSTypedArray, uintptr, JSReceiver) { 195 let bufferConstructor: JSReceiver = GetArrayBufferFunction(); 196 const srcBuffer: JSArrayBuffer = srcTypedArray.buffer; 197 // TODO(petermarshall): Throw on detached typedArray. 198 let length: uintptr = 199 IsDetachedBuffer(srcBuffer) ? 0 : srcTypedArray.length; 200 201 // The spec requires that constructing a typed array using a SAB-backed 202 // typed array use the ArrayBuffer constructor, not the species constructor. 203 // See https://tc39.github.io/ecma262/#sec-typedarray-typedarray. 204 if (!IsSharedArrayBuffer(srcBuffer)) { 205 bufferConstructor = SpeciesConstructor(srcBuffer, bufferConstructor); 206 // TODO(petermarshall): Throw on detached typedArray. 207 if (IsDetachedBuffer(srcBuffer)) length = 0; 208 } 209 goto IfConstructByArrayLike(srcTypedArray, length, bufferConstructor); 210 } 211 212 // 22.2.4.5 TypedArray ( buffer, byteOffset, length ) 213 // ES #sec-typedarray-buffer-byteoffset-length 214 transitioning macro ConstructByArrayBuffer(implicit context: Context)( 215 map: Map, buffer: JSArrayBuffer, byteOffset: JSAny, length: JSAny, 216 elementsInfo: typed_array::TypedArrayElementsInfo): JSTypedArray { 217 try { 218 // 6. Let offset be ? ToIndex(byteOffset). 219 const offset: uintptr = ToIndex(byteOffset) otherwise IfInvalidOffset; 220 221 // 7. If offset modulo elementSize ≠ 0, throw a RangeError exception. 222 if (elementsInfo.IsUnaligned(offset)) { 223 goto IfInvalidAlignment('start offset'); 224 } 225 226 // 8. If length is present and length is not undefined, then 227 // a. Let newLength be ? ToIndex(length). 228 let newLength: uintptr = ToIndex(length) otherwise IfInvalidLength; 229 let newByteLength: uintptr; 230 231 // 9. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. 232 if (IsDetachedBuffer(buffer)) { 233 ThrowTypeError(MessageTemplate::kDetachedOperation, 'Construct'); 234 } 235 236 // 10. Let bufferByteLength be buffer.[[ArrayBufferByteLength]]. 237 const bufferByteLength: uintptr = buffer.byte_length; 238 239 // 11. If length is either not present or undefined, then 240 if (length == Undefined) { 241 // a. If bufferByteLength modulo elementSize ≠ 0, throw a RangeError 242 // exception. 243 if (elementsInfo.IsUnaligned(bufferByteLength)) { 244 goto IfInvalidAlignment('byte length'); 245 } 246 247 // b. Let newByteLength be bufferByteLength - offset. 248 // c. If newByteLength < 0, throw a RangeError exception. 249 if (bufferByteLength < offset) goto IfInvalidOffset; 250 251 // Spec step 16 length calculated here to avoid recalculating the length 252 // in the step 12 branch. 253 newByteLength = bufferByteLength - offset; 254 newLength = elementsInfo.CalculateLength(newByteLength) 255 otherwise IfInvalidOffset; 256 257 // 12. Else, 258 } else { 259 // a. Let newByteLength be newLength × elementSize. 260 newByteLength = elementsInfo.CalculateByteLength(newLength) 261 otherwise IfInvalidLength; 262 263 // b. If offset + newByteLength > bufferByteLength, throw a RangeError 264 // exception. 265 if ((bufferByteLength < newByteLength) || 266 (offset > bufferByteLength - newByteLength)) 267 goto IfInvalidLength; 268 } 269 270 const isOnHeap: constexpr bool = false; 271 return AllocateTypedArray( 272 isOnHeap, map, buffer, offset, newByteLength, newLength); 273 } 274 label IfInvalidAlignment(problemString: String) deferred { 275 ThrowInvalidTypedArrayAlignment(map, problemString); 276 } 277 label IfInvalidLength deferred { 278 ThrowRangeError(MessageTemplate::kInvalidTypedArrayLength, length); 279 } 280 label IfInvalidOffset deferred { 281 ThrowRangeError(MessageTemplate::kInvalidOffset, byteOffset); 282 } 283 } 284 285 // 22.2.4.6 TypedArrayCreate ( constructor, argumentList ) 286 // ES #typedarray-create 287 @export 288 transitioning macro TypedArrayCreateByLength(implicit context: Context)( 289 constructor: Constructor, length: Number, methodName: constexpr string): 290 JSTypedArray { 291 assert(IsSafeInteger(length)); 292 293 // 1. Let newTypedArray be ? Construct(constructor, argumentList). 294 const newTypedArrayObj = Construct(constructor, length); 295 296 // 2. Perform ? ValidateTypedArray(newTypedArray). 297 // ValidateTypedArray currently returns the array, not the ViewBuffer. 298 const newTypedArray: JSTypedArray = 299 ValidateTypedArray(context, newTypedArrayObj, methodName); 300 301 if (IsDetachedBuffer(newTypedArray.buffer)) deferred { 302 ThrowTypeError(MessageTemplate::kDetachedOperation, methodName); 303 } 304 305 // 3. If argumentList is a List of a single Number, then 306 // a. If newTypedArray.[[ArrayLength]] < argumentList[0], throw a 307 // TypeError exception. 308 if (newTypedArray.length < Convert<uintptr>(length)) deferred { 309 ThrowTypeError(MessageTemplate::kTypedArrayTooShort); 310 } 311 312 // 4. Return newTypedArray. 313 return newTypedArray; 314 } 315 316 transitioning macro ConstructByJSReceiver(implicit context: 317 Context)(obj: JSReceiver): never 318 labels IfConstructByArrayLike(JSReceiver, uintptr, JSReceiver) { 319 try { 320 // TODO(v8:8906): Use iterator::GetIteratorMethod() once it supports 321 // labels. 322 const iteratorMethod = GetMethod(obj, IteratorSymbolConstant()) 323 otherwise IfIteratorUndefined, IfIteratorNotCallable; 324 ConstructByIterable(obj, iteratorMethod) 325 otherwise IfConstructByArrayLike; 326 } 327 label IfIteratorUndefined { 328 const lengthObj: JSAny = GetProperty(obj, kLengthString); 329 const lengthNumber: Number = ToLength_Inline(lengthObj); 330 // Throw RangeError here if the length does not fit in uintptr because 331 // such a length will not pass bounds checks in ConstructByArrayLike() 332 // anyway. 333 const length: uintptr = ChangeSafeIntegerNumberToUintPtr(lengthNumber) 334 otherwise goto IfInvalidLength(lengthNumber); 335 goto IfConstructByArrayLike(obj, length, GetArrayBufferFunction()); 336 } 337 label IfInvalidLength(length: Number) { 338 ThrowRangeError(MessageTemplate::kInvalidTypedArrayLength, length); 339 } 340 label IfIteratorNotCallable(_value: JSAny) deferred { 341 ThrowTypeError(MessageTemplate::kIteratorSymbolNonCallable); 342 } 343 } 344 345 // 22.2.4 The TypedArray Constructors 346 // ES #sec-typedarray-constructors 347 transitioning builtin CreateTypedArray( 348 context: Context, target: JSFunction, newTarget: JSReceiver, arg1: JSAny, 349 arg2: JSAny, arg3: JSAny): JSTypedArray { 350 assert(IsConstructor(target)); 351 // 4. Let O be ? AllocateTypedArray(constructorName, NewTarget, 352 // "%TypedArrayPrototype%"). 353 const map = GetDerivedMap(target, newTarget); 354 355 // 5. Let elementSize be the Number value of the Element Size value in Table 356 // 56 for constructorName. 357 const elementsInfo = GetTypedArrayElementsInfo(map); 358 359 try { 360 typeswitch (arg1) { 361 case (length: Smi): { 362 goto IfConstructByLength(length); 363 } 364 case (buffer: JSArrayBuffer): { 365 return ConstructByArrayBuffer(map, buffer, arg2, arg3, elementsInfo); 366 } 367 case (typedArray: JSTypedArray): { 368 ConstructByTypedArray(typedArray) otherwise IfConstructByArrayLike; 369 } 370 case (obj: JSReceiver): { 371 ConstructByJSReceiver(obj) otherwise IfConstructByArrayLike; 372 } 373 // The first argument was a number or fell through and is treated as 374 // a number. https://tc39.github.io/ecma262/#sec-typedarray-length 375 case (lengthObj: JSAny): { 376 goto IfConstructByLength(lengthObj); 377 } 378 } 379 } 380 label IfConstructByLength(length: JSAny) { 381 return ConstructByLength(map, length, elementsInfo); 382 } 383 label IfConstructByArrayLike( 384 arrayLike: JSReceiver, length: uintptr, bufferConstructor: JSReceiver) { 385 return ConstructByArrayLike( 386 map, arrayLike, length, elementsInfo, bufferConstructor); 387 } 388 } 389 390 transitioning macro TypedArraySpeciesCreate(implicit context: Context)( 391 methodName: constexpr string, numArgs: constexpr int31, 392 exemplar: JSTypedArray, arg0: JSAny, arg1: JSAny, 393 arg2: JSAny): JSTypedArray { 394 const defaultConstructor = GetDefaultConstructor(exemplar); 395 396 try { 397 if (!IsPrototypeTypedArrayPrototype(exemplar.map)) goto IfSlow; 398 if (IsTypedArraySpeciesProtectorCellInvalid()) goto IfSlow; 399 400 const typedArray = CreateTypedArray( 401 context, defaultConstructor, defaultConstructor, arg0, arg1, arg2); 402 403 // It is assumed that the CreateTypedArray builtin does not produce a 404 // typed array that fails ValidateTypedArray 405 assert(!IsDetachedBuffer(typedArray.buffer)); 406 407 return typedArray; 408 } 409 label IfSlow deferred { 410 const constructor = 411 Cast<Constructor>(SpeciesConstructor(exemplar, defaultConstructor)) 412 otherwise unreachable; 413 414 // TODO(pwong): Simplify and remove numArgs when varargs are supported in 415 // macros. 416 let newObj: JSAny = Undefined; 417 if constexpr (numArgs == 1) { 418 newObj = Construct(constructor, arg0); 419 } else { 420 assert(numArgs == 3); 421 newObj = Construct(constructor, arg0, arg1, arg2); 422 } 423 424 return ValidateTypedArray(context, newObj, methodName); 425 } 426 } 427 428 @export 429 transitioning macro TypedArraySpeciesCreateByLength(implicit context: 430 Context)( 431 methodName: constexpr string, exemplar: JSTypedArray, length: uintptr): 432 JSTypedArray { 433 const numArgs: constexpr int31 = 1; 434 // TODO(v8:4153): pass length further as uintptr. 435 const typedArray: JSTypedArray = TypedArraySpeciesCreate( 436 methodName, numArgs, exemplar, Convert<Number>(length), Undefined, 437 Undefined); 438 if (typedArray.length < length) deferred { 439 ThrowTypeError(MessageTemplate::kTypedArrayTooShort); 440 } 441 return typedArray; 442 } 443 444 transitioning macro TypedArraySpeciesCreateByBuffer(implicit context: 445 Context)( 446 methodName: constexpr string, exemplar: JSTypedArray, 447 buffer: JSArrayBuffer, beginByteOffset: uintptr, 448 newLength: uintptr): JSTypedArray { 449 const numArgs: constexpr int31 = 3; 450 // TODO(v8:4153): pass length further as uintptr. 451 const typedArray: JSTypedArray = TypedArraySpeciesCreate( 452 methodName, numArgs, exemplar, buffer, Convert<Number>(beginByteOffset), 453 Convert<Number>(newLength)); 454 return typedArray; 455 } 456} 457