1// Copyright 2018 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 5module array { 6 macro FastArraySplice( 7 context: Context, args: Arguments, o: Object, 8 originalLengthNumber: Number, actualStartNumber: Number, insertCount: Smi, 9 actualDeleteCountNumber: Number): Object 10 labels Bailout { 11 let originalLength: Smi = cast<Smi>(originalLengthNumber) otherwise Bailout; 12 let actualStart: Smi = cast<Smi>(actualStartNumber) otherwise Bailout; 13 let actualDeleteCount: Smi = 14 cast<Smi>(actualDeleteCountNumber) otherwise Bailout; 15 let lengthDelta: Smi = insertCount - actualDeleteCount; 16 let newLength: Smi = originalLength + lengthDelta; 17 18 let a: JSArray = cast<JSArray>(o) otherwise Bailout; 19 20 let map: Map = a.map; 21 if (!IsPrototypeInitialArrayPrototype(context, map)) goto Bailout; 22 if (IsNoElementsProtectorCellInvalid()) goto Bailout; 23 if (IsArraySpeciesProtectorCellInvalid()) goto Bailout; 24 25 // Fast path only works on fast elements kind and with writable length. 26 let elementsKind: ElementsKind = EnsureArrayPushable(map) otherwise Bailout; 27 if (!IsFastElementsKind(elementsKind)) goto Bailout; 28 29 // For now, only support non-double fast elements 30 if (!IsFastSmiOrTaggedElementsKind(elementsKind)) goto Bailout; 31 32 if (IsFastSmiElementsKind(elementsKind)) { 33 for (let e: Object of args [2: ]) { 34 if (TaggedIsNotSmi(e)) goto Bailout; 35 } 36 } 37 38 // Make sure that the length hasn't been changed by side-effect. 39 let length: Smi = cast<Smi>(a.length) otherwise Bailout; 40 if (originalLength != length) goto Bailout; 41 42 let deletedResult: JSArray = 43 ExtractFastJSArray(context, a, actualStart, actualDeleteCount); 44 45 if (newLength == 0) { 46 a.elements = kEmptyFixedArray; 47 a.length = 0; 48 return deletedResult; 49 } 50 51 let elements: FixedArray = cast<FixedArray>(a.elements) otherwise Bailout; 52 let elementsMap: Map = elements.map; 53 54 // If the source is a COW array or the spliced array is larger then the 55 // source array, then allocate a new FixedArray to hold the result. 56 let newElements: FixedArray = elements; 57 if ((elementsMap == kCOWMap) || (lengthDelta > 0)) { 58 newElements = ExtractFixedArray( 59 elements, 0, actualStart, newLength, kAllFixedArrays); 60 newElements.map = elementsMap; 61 a.elements = newElements; 62 } 63 64 // Double check that the array is still in fast elements mode 65 assert(IsFastSmiElementsKind(a.map.elements_kind)); 66 67 // Copy over inserted elements. 68 let k: Smi = actualStart; 69 if (insertCount > 0) { 70 for (let e: Object of args [2: ]) { 71 newElements[k++] = e; 72 } 73 } 74 75 // Copy over elements after deleted elements. 76 let count: Smi = length - actualStart - actualDeleteCount; 77 while (count > 0) { 78 let e: Object = elements[k - lengthDelta]; 79 newElements[k++] = e; 80 count--; 81 } 82 83 // Fill rest of spliced FixedArray with the hole, but only if the 84 // destination FixedArray is the original array's, since otherwise the array 85 // is pre-filled with holes. 86 if (elements == newElements) { 87 let limit: Smi = elements.length; 88 while (k < limit) { 89 newElements[k++] = Hole; 90 } 91 } 92 93 // Update the array's length after all the FixedArray shuffling is done. 94 a.length = newLength; 95 96 return deletedResult; 97 } 98 99 // https://tc39.github.io/ecma262/#sec-array.prototype.splice 100 javascript builtin ArraySpliceTorque( 101 context: Context, receiver: Object, ...arguments): Object { 102 // 1. Let O be ? ToObject(this value). 103 let o: Object = ToObject(context, receiver); 104 105 // 2. Let len be ? ToLength(? Get(O, "length")). 106 let len: Number = 107 ToLength_Inline(context, GetProperty(context, o, 'length')); 108 109 // 3. Let relativeStart be ? ToInteger(start). 110 let start: Object = arguments[0]; 111 let relativeStart: Number = ToInteger_Inline(context, start); 112 113 // 4. If relativeStart < 0, let actualStart be max((len + relativeStart), 114 // 0); 115 // else let actualStart be min(relativeStart, len). 116 let actualStart: Number = relativeStart < 0 ? 117 max((len + relativeStart), 0) : 118 min(relativeStart, len); 119 120 let insertCount: Smi; 121 let actualDeleteCount: Number; 122 // 5. If the Number of actual arguments is 0, then 123 if (arguments.length == 0) { 124 // a. Let insertCount be 0. 125 insertCount = 0; 126 // b. Let actualDeleteCount be 0. 127 actualDeleteCount = 0; 128 // 6. Else if the Number of actual arguments is 1, then 129 } else if (arguments.length == 1) { 130 // a. Let insertCount be 0. 131 insertCount = 0; 132 // b. Let actualDeleteCount be len - actualStart. 133 actualDeleteCount = len - actualStart; 134 // 7. Else, 135 } else { 136 // a. Let insertCount be the Number of actual arguments minus 2. 137 insertCount = convert<Smi>(arguments.length) - 2; 138 // b. Let dc be ? ToInteger(deleteCount). 139 let deleteCount: Object = arguments[1]; 140 let dc: Number = ToInteger_Inline(context, deleteCount); 141 // c. Let actualDeleteCount be min(max(dc, 0), len - actualStart). 142 actualDeleteCount = min(max(dc, 0), len - actualStart); 143 } 144 145 // 8. If len + insertCount - actualDeleteCount > 2^53-1, throw a 146 // Bailout exception. 147 if (len + insertCount - actualDeleteCount > kMaxSafeInteger) { 148 ThrowRangeError(context, kInvalidArrayLengthMessage); 149 } 150 151 try { 152 return FastArraySplice( 153 context, arguments, o, len, actualStart, insertCount, 154 actualDeleteCount) otherwise Bailout; 155 } 156 label Bailout {} 157 // If the fast case fails, just continue with the slow, correct, 158 // spec-compliant case. 159 160 // 9. Let A be ? ArraySpeciesCreate(O, actualDeleteCount). 161 let a: Object = ArraySpeciesCreate(context, o, actualDeleteCount); 162 163 // 10. Let k be 0. 164 let k: Number = 0; 165 166 // 11. Repeat, while k < actualDeleteCount 167 while (k < actualDeleteCount) { 168 // a. Let from be ! ToString(actualStart + k). 169 let from: String = ToString_Inline(context, actualStart + k); 170 171 // b. Let fromPresent be ? HasProperty(O, from). 172 let fromPresent: Oddball = 173 HasPropertyObject(o, from, context, kHasProperty); 174 175 // c. If fromPresent is true, then 176 if (fromPresent == True) { 177 // i. Let fromValue be ? Get(O, from). 178 let fromValue: Object = GetProperty(context, o, from); 179 180 // ii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(k), fromValue). 181 CreateDataProperty(context, a, ToString_Inline(context, k), fromValue); 182 } 183 184 // d. Increment k by 1. 185 k = k + 1; 186 } 187 188 // 12. Perform ? Set(A, "length", actualDeleteCount, true). 189 SetProperty(context, a, 'length', actualDeleteCount, strict); 190 191 // 13. Let items be a List whose elements are, in left-to-right order, 192 // the portion of the actual argument list starting with the third 193 // argument. The list is empty if fewer than three arguments were 194 // passed. 195 // 14. Let itemCount be the Number of elements in items. 196 let itemCount: Number = insertCount; 197 198 // 15. If itemCount < actualDeleteCount, then 199 if (itemCount < actualDeleteCount) { 200 // a. Let k be actualStart. 201 let k: Number = actualStart; 202 203 // b. Repeat, while k < (len - actualDeleteCount) 204 while (k < (len - actualDeleteCount)) { 205 // i. Let from be ! ToString(k + actualDeleteCount). 206 let from: String = ToString_Inline(context, k + actualDeleteCount); 207 // ii. Let to be ! ToString(k + itemCount). 208 let to: String = ToString_Inline(context, k + itemCount); 209 210 // iii. Let fromPresent be ? HasProperty(O, from). 211 let fromPresent: Oddball = 212 HasPropertyObject(o, from, context, kHasProperty); 213 214 // iv. If fromPresent is true, then 215 if (fromPresent == True) { 216 // 1. Let fromValue be ? Get(O, from). 217 let fromValue: Object = GetProperty(context, o, from); 218 219 // 2. Perform ? Set(O, to, fromValue, true). 220 SetProperty(context, o, to, fromValue, strict); 221 222 // v. Else fromPresent is false, 223 } else { 224 // 1. Perform ? DeletePropertyOrThrow(O, to). 225 DeleteProperty(context, o, to, strict); 226 } 227 // vi. Increase k by 1. 228 k = k + 1; 229 } 230 231 // c. Let k be len. 232 k = len; 233 // d. Repeat, while k > (len - actualDeleteCount + itemCount) 234 while (k > (len - actualDeleteCount + itemCount)) { 235 // i. Perform ? DeletePropertyOrThrow(O, ! ToString(k - 1)). 236 DeleteProperty(context, o, ToString_Inline(context, k - 1), strict); 237 238 // ii. Decrease k by 1. 239 k = k - 1; 240 } 241 // 16. Else if itemCount > actualDeleteCount, then 242 } else if (itemCount > actualDeleteCount) { 243 // a. Let k be (len - actualDeleteCount). 244 let k: Number = len - actualDeleteCount; 245 246 // b. Repeat, while k > actualStart 247 while (k > actualStart) { 248 // i. Let from be ! ToString(k + actualDeleteCount - 1). 249 let from: String = ToString_Inline(context, k + actualDeleteCount - 1); 250 251 // ii. Let to be ! ToString(k + itemCount - 1). 252 let to: String = ToString_Inline(context, k + itemCount - 1); 253 254 // iii. Let fromPresent be ? HasProperty(O, from). 255 let fromPresent: Oddball = 256 HasPropertyObject(o, from, context, kHasProperty); 257 258 // iv. If fromPresent is true, then 259 if (fromPresent == True) { 260 // 1. Let fromValue be ? Get(O, from). 261 let fromValue: Object = GetProperty(context, o, from); 262 263 // 2. Perform ? Set(O, to, fromValue, true). 264 SetProperty(context, o, to, fromValue, strict); 265 266 // v. Else fromPresent is false, 267 } else { 268 // 1. Perform ? DeletePropertyOrThrow(O, to). 269 DeleteProperty(context, o, to, strict); 270 } 271 272 // vi. Decrease k by 1. 273 k = k - 1; 274 } 275 } 276 277 // 17. Let k be actualStart. 278 k = actualStart; 279 280 // 18. Repeat, while items is not empty 281 // a. Remove the first element from items and let E be the value of that 282 // element. 283 if (arguments.length > 2) { 284 for (let e: Object of arguments [2: ]) { 285 // b. Perform ? Set(O, ! ToString(k), E, true). 286 SetProperty(context, o, ToString_Inline(context, k), e, strict); 287 288 // c. Increase k by 1. 289 k = k + 1; 290 } 291 } 292 293 // 19. Perform ? Set(O, "length", len - actualDeleteCount + itemCount, 294 // true). 295 SetProperty( 296 context, o, 'length', len - actualDeleteCount + itemCount, strict); 297 298 return a; 299 } 300 301 macro ArrayForEachTorqueContinuation( 302 context: Context, o: Object, len: Number, callbackfn: Callable, 303 thisArg: Object, initial_k: Number): Object { 304 // 5. Let k be 0. 305 // 6. Repeat, while k < len 306 for (let k: Number = initial_k; k < len; k = k + 1) { 307 // 6a. Let Pk be ! ToString(k). 308 // k is guaranteed to be a positive integer, hence ToString is 309 // side-effect free and HasProperty/GetProperty do the conversion inline. 310 311 // 6b. Let kPresent be ? HasProperty(O, Pk). 312 let kPresent: Oddball = HasPropertyObject(o, k, context, kHasProperty); 313 314 // 6c. If kPresent is true, then 315 if (kPresent == True) { 316 // 6c. i. Let kValue be ? Get(O, Pk). 317 let kValue: Object = GetProperty(context, o, k); 318 319 // 6c. ii. Perform ? Call(callbackfn, T, <kValue, k, O>). 320 Call(context, callbackfn, thisArg, kValue, k, o); 321 } 322 323 // 6d. Increase k by 1. (done by the loop). 324 } 325 return Undefined; 326 } 327 328 javascript builtin ArrayForEachLoopEagerDeoptContinuation( 329 context: Context, receiver: Object, callback: Object, thisArg: Object, 330 initialK: Object, length: Object): Object { 331 return ArrayForEachLoopContinuation( 332 context, receiver, callback, thisArg, Undefined, receiver, initialK, 333 length, Undefined); 334 } 335 336 javascript builtin ArrayForEachLoopLazyDeoptContinuation( 337 context: Context, receiver: Object, callback: Object, thisArg: Object, 338 initialK: Object, length: Object, result: Object): Object { 339 return ArrayForEachLoopContinuation( 340 context, receiver, callback, thisArg, Undefined, receiver, initialK, 341 length, Undefined); 342 } 343 344 builtin ArrayForEachLoopContinuation( 345 context: Context, receiver: Object, callback: Object, thisArg: Object, 346 array: Object, object: Object, initialK: Object, length: Object, 347 to: Object): Object { 348 try { 349 let callbackfn: Callable = cast<Callable>(callback) otherwise Unexpected; 350 let k: Number = cast<Number>(initialK) otherwise Unexpected; 351 let number_length: Number = cast<Number>(length) otherwise Unexpected; 352 353 return ArrayForEachTorqueContinuation( 354 context, object, number_length, callbackfn, thisArg, k); 355 } 356 label Unexpected { 357 unreachable; 358 } 359 } 360 361 macro VisitAllElements<FixedArrayType : type>( 362 context: Context, a: JSArray, len: Smi, callbackfn: Callable, 363 thisArg: Object): void labels 364 Bailout(Smi) { 365 let k: Smi = 0; 366 let map: Map = a.map; 367 368 try { 369 // Build a fast loop over the smi array. 370 for (; k < len; k = k + 1) { 371 // Ensure that the map didn't change. 372 if (map != a.map) goto Slow; 373 // Ensure that we haven't walked beyond a possibly updated length. 374 if (k >= a.length) goto Slow; 375 376 try { 377 let value: Object = 378 LoadElementNoHole<FixedArrayType>(a, k) otherwise FoundHole; 379 Call(context, callbackfn, thisArg, value, k, a); 380 } 381 label FoundHole { 382 // If we found the hole, we need to bail out if the initial 383 // array prototype has had elements inserted. This is preferable 384 // to walking the prototype chain looking for elements. 385 386 if (IsNoElementsProtectorCellInvalid()) goto Bailout(k); 387 } 388 } 389 } 390 label Slow { 391 goto Bailout(k); 392 } 393 } 394 395 macro FastArrayForEach( 396 context: Context, o: Object, len: Number, callbackfn: Callable, 397 thisArg: Object): Object labels 398 Bailout(Smi) { 399 let k: Smi = 0; 400 try { 401 let smi_len: Smi = cast<Smi>(len) otherwise Slow; 402 let a: JSArray = cast<JSArray>(o) otherwise Slow; 403 let map: Map = a.map; 404 405 if (!IsPrototypeInitialArrayPrototype(context, map)) goto Slow; 406 let elementsKind: ElementsKind = map.elements_kind; 407 if (!IsFastElementsKind(elementsKind)) goto Slow; 408 409 if (IsElementsKindGreaterThan(elementsKind, HOLEY_ELEMENTS)) { 410 VisitAllElements<FixedDoubleArray>( 411 context, a, smi_len, callbackfn, thisArg) 412 otherwise Bailout; 413 } else { 414 VisitAllElements<FixedArray>(context, a, smi_len, callbackfn, thisArg) 415 otherwise Bailout; 416 } 417 } 418 label Slow { 419 goto Bailout(k); 420 } 421 return Undefined; 422 } 423 424 // https://tc39.github.io/ecma262/#sec-array.prototype.foreach 425 javascript builtin ArrayForEach( 426 context: Context, receiver: Object, ...arguments): Object { 427 try { 428 if (IsNullOrUndefined(receiver)) { 429 goto NullOrUndefinedError; 430 } 431 432 // 1. Let O be ? ToObject(this value). 433 let o: Object = ToObject(context, receiver); 434 435 // 2. Let len be ? ToLength(? Get(O, "length")). 436 let len: Number = 437 ToLength_Inline(context, GetProperty(context, o, 'length')); 438 439 // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. 440 if (arguments.length == 0) { 441 goto TypeError; 442 } 443 let callbackfn: Callable = 444 cast<Callable>(arguments[0]) otherwise TypeError; 445 446 // 4. If thisArg is present, let T be thisArg; else let T be undefined. 447 let thisArg: Object = arguments.length > 1 ? arguments[1] : Undefined; 448 449 // Special cases. 450 let k: Number = 0; 451 try { 452 return FastArrayForEach(context, o, len, callbackfn, thisArg) 453 otherwise Bailout; 454 } 455 label Bailout(k_value: Smi) { 456 k = k_value; 457 } 458 459 return ArrayForEachTorqueContinuation( 460 context, o, len, callbackfn, thisArg, k); 461 } 462 label TypeError { 463 ThrowTypeError(context, kCalledNonCallable, arguments[0]); 464 } 465 label NullOrUndefinedError { 466 ThrowTypeError( 467 context, kCalledOnNullOrUndefined, 'Array.prototype.forEach'); 468 } 469 } 470} 471