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 5namespace array { 6// Given {source}, we want to create a non-zero length array of type 7// FixedArrayType with the specified {result_capacity}. Starting from 8// {startIndex}, {count} number of elements are copied to the newly 9// created result array. Most of this behavior is outsourced to 10// ExtractFixedArray(). We handle the case where the {source} is 11// EmptyFixedArray but result is expected to be a FixedDoubleArray. 12macro Extract(implicit context: Context)( 13 source: FixedArray, startIndex: Smi, count: Smi, 14 resultCapacity: Smi): FixedArray { 15 return ExtractFixedArray( 16 source, Convert<intptr>(startIndex), Convert<intptr>(count), 17 Convert<intptr>(resultCapacity)); 18} 19 20macro Extract(implicit context: Context)( 21 source: FixedDoubleArray|EmptyFixedArray, startIndex: Smi, count: Smi, 22 resultCapacity: Smi): FixedDoubleArray|EmptyFixedArray { 23 typeswitch (source) { 24 case (EmptyFixedArray): { 25 // ExtractFixedDoubleArray expects {source} to be a FixedDoubleArray. 26 // Handle the case where {source} is empty here. 27 return AllocateFixedDoubleArrayWithHoles( 28 Convert<intptr>(resultCapacity), 29 AllocationFlag::kAllowLargeObjectAllocation); 30 } 31 case (source: FixedDoubleArray): { 32 return ExtractFixedDoubleArray( 33 source, Convert<intptr>(startIndex), Convert<intptr>(count), 34 Convert<intptr>(resultCapacity)); 35 } 36 } 37} 38 39macro DoMoveElements<FixedArrayType : type extends FixedArrayBase>( 40 elements: FixedArrayType, dstIndex: Smi, srcIndex: Smi, count: Smi): void { 41 TorqueMoveElements( 42 elements, Convert<intptr>(dstIndex), Convert<intptr>(srcIndex), 43 Convert<intptr>(count)); 44} 45 46macro StoreHoles<FixedArrayType : type extends FixedArrayBase>( 47 elements: FixedArrayType, holeStartIndex: Smi, holeEndIndex: Smi): void { 48 for (let i: Smi = holeStartIndex; i < holeEndIndex; i++) { 49 array::StoreArrayHole(elements, i); 50 } 51} 52 53macro DoCopyElements<FixedArrayType : type extends FixedArrayBase>( 54 dstElements: FixedArrayType, dstIndex: Smi, srcElements: FixedArrayType, 55 srcIndex: Smi, count: Smi): void { 56 TorqueCopyElements( 57 dstElements, Convert<intptr>(dstIndex), srcElements, 58 Convert<intptr>(srcIndex), Convert<intptr>(count)); 59} 60 61macro 62FastSplice<FixedArrayType : type extends FixedArrayBase, ElementType: type>( 63 implicit context: Context)( 64 args: Arguments, a: JSArray, length: Smi, newLength: Smi, actualStart: Smi, 65 insertCount: Smi, actualDeleteCount: Smi): void { 66 // Make sure elements are writable. 67 array::EnsureWriteableFastElements(a); 68 69 if (insertCount != actualDeleteCount) { 70 const elements = UnsafeCast<(FixedArrayType | EmptyFixedArray)>(a.elements); 71 const dstIndex: Smi = actualStart + insertCount; 72 const srcIndex: Smi = actualStart + actualDeleteCount; 73 const count: Smi = length - actualDeleteCount - actualStart; 74 if (insertCount < actualDeleteCount) { 75 // Shrink. 76 DoMoveElements( 77 UnsafeCast<FixedArrayType>(elements), dstIndex, srcIndex, count); 78 StoreHoles(UnsafeCast<FixedArrayType>(elements), newLength, length); 79 } else if (insertCount > actualDeleteCount) { 80 // If the backing store is big enough, then moving elements is enough. 81 if (newLength <= elements.length) { 82 DoMoveElements( 83 UnsafeCast<FixedArrayType>(elements), dstIndex, srcIndex, count); 84 } else { 85 // Grow. 86 const capacity: Smi = CalculateNewElementsCapacity(newLength); 87 const newElements: FixedArrayType = UnsafeCast<FixedArrayType>( 88 Extract(elements, 0, actualStart, capacity)); 89 a.elements = newElements; 90 if (elements.length > 0) { 91 DoCopyElements( 92 newElements, dstIndex, UnsafeCast<FixedArrayType>(elements), 93 srcIndex, count); 94 } 95 } 96 } 97 } 98 99 // Copy arguments. 100 let k: Smi = actualStart; 101 if (insertCount > 0) { 102 const typedNewElements: FixedArrayType = 103 UnsafeCast<FixedArrayType>(a.elements); 104 for (let i: intptr = 2; i < args.length; ++i) { 105 const e: JSAny = args[i]; 106 // The argument elements were already validated to be an appropriate 107 // {ElementType} to store in {FixedArrayType}. 108 typedNewElements[k++] = UnsafeCast<ElementType>(e); 109 } 110 } 111 112 // Update the array's length after all the FixedArray shuffling is done. 113 a.length = newLength; 114} 115 116transitioning macro FastArraySplice( 117 context: Context, args: Arguments, o: JSReceiver, 118 originalLengthNumber: Number, actualStartNumber: Number, insertCount: Smi, 119 actualDeleteCountNumber: Number): JSAny 120 labels Bailout { 121 const originalLength: Smi = Cast<Smi>(originalLengthNumber) otherwise Bailout; 122 const actualStart: Smi = Cast<Smi>(actualStartNumber) otherwise Bailout; 123 const actualDeleteCount: Smi = 124 Cast<Smi>(actualDeleteCountNumber) otherwise Bailout; 125 const lengthDelta: Smi = insertCount - actualDeleteCount; 126 const newLength: Smi = originalLength + lengthDelta; 127 128 const a: JSArray = Cast<JSArray>(o) otherwise Bailout; 129 130 const map: Map = a.map; 131 if (!IsPrototypeInitialArrayPrototype(map)) goto Bailout; 132 if (IsNoElementsProtectorCellInvalid()) goto Bailout; 133 if (IsArraySpeciesProtectorCellInvalid()) goto Bailout; 134 135 // Fast path only works on fast elements kind and with writable length. 136 let elementsKind: ElementsKind = EnsureArrayPushable(map) otherwise Bailout; 137 if (!IsFastElementsKind(elementsKind)) goto Bailout; 138 139 const oldElementsKind: ElementsKind = elementsKind; 140 for (let i: intptr = 2; i < args.length; ++i) { 141 const e: JSAny = args[i]; 142 if (IsFastSmiElementsKind(elementsKind)) { 143 if (TaggedIsNotSmi(e)) { 144 const heapObject: HeapObject = UnsafeCast<HeapObject>(e); 145 elementsKind = IsHeapNumber(heapObject) ? 146 AllowDoubleElements(elementsKind) : 147 AllowNonNumberElements(elementsKind); 148 } 149 } else if (IsDoubleElementsKind(elementsKind)) { 150 if (!IsNumber(e)) { 151 elementsKind = AllowNonNumberElements(elementsKind); 152 } 153 } 154 } 155 156 if (elementsKind != oldElementsKind) { 157 const smiElementsKind: Smi = Convert<Smi>(Convert<int32>(elementsKind)); 158 TransitionElementsKindWithKind(context, a, smiElementsKind); 159 } 160 161 // Make sure that the length hasn't been changed by side-effect. 162 const length: Smi = Cast<Smi>(a.length) otherwise Bailout; 163 if (originalLength != length) goto Bailout; 164 165 const deletedResult: JSArray = 166 ExtractFastJSArray(context, a, actualStart, actualDeleteCount); 167 168 if (newLength == 0) { 169 a.elements = kEmptyFixedArray; 170 a.length = 0; 171 return deletedResult; 172 } 173 174 if (IsFastSmiOrTaggedElementsKind(elementsKind)) { 175 FastSplice<FixedArray, JSAny>( 176 args, a, length, newLength, actualStart, insertCount, 177 actualDeleteCount); 178 } else { 179 FastSplice<FixedDoubleArray, Number>( 180 args, a, length, newLength, actualStart, insertCount, 181 actualDeleteCount); 182 } 183 184 return deletedResult; 185} 186 187transitioning macro FillDeletedElementsArray( 188 context: Context, o: JSReceiver, actualStart: Number, 189 actualDeleteCount: Number, a: JSReceiver): JSAny { 190 // 10. Let k be 0. 191 let k: Number = 0; 192 193 // 11. Repeat, while k < actualDeleteCount 194 while (k < actualDeleteCount) { 195 // a. Let from be ! ToString(actualStart + k). 196 const from: Number = actualStart + k; 197 198 // b. Let fromPresent be ? HasProperty(O, from). 199 const fromPresent: Boolean = HasProperty(o, from); 200 201 // c. If fromPresent is true, then 202 if (fromPresent == True) { 203 // i. Let fromValue be ? Get(O, from). 204 const fromValue: JSAny = GetProperty(o, from); 205 206 // ii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(k), fromValue). 207 FastCreateDataProperty(a, k, fromValue); 208 } 209 210 // d. Increment k by 1. 211 k++; 212 } 213 // 12. Perform ? Set(A, "length", actualDeleteCount, true). 214 SetProperty(a, kLengthString, actualDeleteCount); 215 return a; 216} 217 218// HandleForwardCase implements step 15. "If itemCount < actualDeleteCount, 219// then..."" 220transitioning macro HandleForwardCase( 221 context: Context, o: JSReceiver, len: Number, itemCount: Number, 222 actualStart: Number, actualDeleteCount: Number): void { 223 // 15. If itemCount < actualDeleteCount, then 224 // a. Let k be actualStart. 225 let k: Number = actualStart; 226 227 // b. Repeat, while k < (len - actualDeleteCount) 228 while (k < (len - actualDeleteCount)) { 229 // i. Let from be ! ToString(k + actualDeleteCount). 230 const from: Number = k + actualDeleteCount; 231 // ii. Let to be ! ToString(k + itemCount). 232 const to: Number = k + itemCount; 233 234 // iii. Let fromPresent be ? HasProperty(O, from). 235 const fromPresent: Boolean = HasProperty(o, from); 236 237 // iv. If fromPresent is true, then 238 if (fromPresent == True) { 239 // 1. Let fromValue be ? Get(O, from). 240 const fromValue: JSAny = GetProperty(o, from); 241 242 // 2. Perform ? Set(O, to, fromValue, true). 243 SetProperty(o, to, fromValue); 244 245 // v. Else fromPresent is false, 246 } else { 247 // 1. Perform ? DeletePropertyOrThrow(O, to). 248 DeleteProperty(o, to, LanguageMode::kStrict); 249 } 250 // vi. Increase k by 1. 251 k++; 252 } 253 254 // c. Let k be len. 255 k = len; 256 257 // d. Repeat, while k > (len - actualDeleteCount + itemCount) 258 while (k > (len - actualDeleteCount + itemCount)) { 259 // i. Perform ? DeletePropertyOrThrow(O, ! ToString(k - 1)). 260 DeleteProperty(o, k - 1, LanguageMode::kStrict); 261 // ii. Decrease k by 1. 262 k--; 263 } 264} 265 266// HandleBackwardCase implements step 16. "Else if itemCount > 267// actualDeleteCount, then..." 268transitioning macro HandleBackwardCase( 269 context: Context, o: JSReceiver, len: Number, itemCount: Number, 270 actualStart: Number, actualDeleteCount: Number): void { 271 // 16. Else if itemCount > actualDeleteCount, then 272 // a. Let k be (len - actualDeleteCount). 273 let k: Number = len - actualDeleteCount; 274 275 // b. Repeat, while k > actualStart 276 while (k > actualStart) { 277 // i. Let from be ! ToString(k + actualDeleteCount - 1). 278 const from: Number = k + actualDeleteCount - 1; 279 280 // ii. Let to be ! ToString(k + itemCount - 1). 281 const to: Number = k + itemCount - 1; 282 283 // iii. Let fromPresent be ? HasProperty(O, from). 284 const fromPresent: Boolean = HasProperty(o, from); 285 286 // iv. If fromPresent is true, then 287 if (fromPresent == True) { 288 // 1. Let fromValue be ? Get(O, from). 289 const fromValue: JSAny = GetProperty(o, from); 290 291 // 2. Perform ? Set(O, to, fromValue, true). 292 SetProperty(o, to, fromValue); 293 294 // v. Else fromPresent is false, 295 } else { 296 // 1. Perform ? DeletePropertyOrThrow(O, to). 297 DeleteProperty(o, to, LanguageMode::kStrict); 298 } 299 300 // vi. Decrease k by 1. 301 k--; 302 } 303} 304 305transitioning macro SlowSplice( 306 context: Context, arguments: Arguments, o: JSReceiver, len: Number, 307 actualStart: Number, insertCount: Smi, actualDeleteCount: Number): JSAny { 308 // 9. Let A be ? ArraySpeciesCreate(O, actualDeleteCount). 309 const a: JSReceiver = ArraySpeciesCreate(context, o, actualDeleteCount); 310 const itemCount: Number = insertCount; 311 312 // Steps 9 through 12: creating the array of deleted elements. 313 FillDeletedElementsArray(context, o, actualStart, actualDeleteCount, a); 314 315 // 13. Let items be a List whose elements are, in left-to-right order, 316 // the portion of the actual argument list starting with the third 317 // argument. The list is empty if fewer than three arguments were 318 // passed. 319 // 14. Let itemCount be the Number of elements in items. 320 // (done above). 321 322 // 15. If itemCount < actualDeleteCount, then 323 if (itemCount < actualDeleteCount) { 324 HandleForwardCase( 325 context, o, len, itemCount, actualStart, actualDeleteCount); 326 // 16. Else if itemCount > actualDeleteCount, then 327 } else if (itemCount > actualDeleteCount) { 328 HandleBackwardCase( 329 context, o, len, itemCount, actualStart, actualDeleteCount); 330 } 331 332 // 17. Let k be actualStart. 333 let k: Number = actualStart; 334 335 // 18. Repeat, while items is not empty 336 // a. Remove the first element from items and let E be the value of that 337 // element. 338 if (arguments.length > 2) { 339 for (let i: intptr = 2; i < arguments.length; ++i) { 340 const e: JSAny = arguments[i]; 341 // b. Perform ? Set(O, ! ToString(k), E, true). 342 SetProperty(o, k, e); 343 344 // c. Increase k by 1. 345 k = k + 1; 346 } 347 } 348 349 // 19. Perform ? Set(O, "length", len - actualDeleteCount + itemCount, 350 // true). 351 SetProperty(o, kLengthString, len - actualDeleteCount + itemCount); 352 353 return a; 354} 355 356// https://tc39.github.io/ecma262/#sec-array.prototype.splice 357transitioning javascript builtin 358ArrayPrototypeSplice( 359 js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny { 360 // 1. Let O be ? ToObject(this value). 361 const o: JSReceiver = ToObject(context, receiver); 362 363 // 2. Let len be ? ToLength(? Get(O, "length")). 364 const len: Number = GetLengthProperty(o); 365 366 // 3. Let relativeStart be ? ToInteger(start). 367 const start: JSAny = arguments[0]; 368 const relativeStart: Number = ToInteger_Inline(start); 369 370 // 4. If relativeStart < 0, let actualStart be max((len + relativeStart), 371 // 0); 372 // else let actualStart be min(relativeStart, len). 373 const actualStart: Number = relativeStart < 0 ? 374 Max((len + relativeStart), 0) : 375 Min(relativeStart, len); 376 377 let insertCount: Smi; 378 let actualDeleteCount: Number; 379 // 5. If the Number of actual arguments is 0, then 380 if (arguments.length == 0) { 381 // a. Let insertCount be 0. 382 insertCount = 0; 383 // b. Let actualDeleteCount be 0. 384 actualDeleteCount = 0; 385 // 6. Else if the Number of actual arguments is 1, then 386 } else if (arguments.length == 1) { 387 // a. Let insertCount be 0. 388 insertCount = 0; 389 // b. Let actualDeleteCount be len - actualStart. 390 actualDeleteCount = len - actualStart; 391 // 7. Else, 392 } else { 393 // a. Let insertCount be the Number of actual arguments minus 2. 394 insertCount = Convert<Smi>(arguments.length) - 2; 395 // b. Let dc be ? ToInteger(deleteCount). 396 const deleteCount: JSAny = arguments[1]; 397 const dc: Number = ToInteger_Inline(deleteCount); 398 // c. Let actualDeleteCount be min(max(dc, 0), len - actualStart). 399 actualDeleteCount = Min(Max(dc, 0), len - actualStart); 400 } 401 402 // 8. If len + insertCount - actualDeleteCount > 2^53-1, throw a 403 // Bailout exception. 404 const newLength: Number = len + insertCount - actualDeleteCount; 405 if (newLength > kMaxSafeInteger) { 406 ThrowTypeError(MessageTemplate::kInvalidArrayLength, start); 407 } 408 409 try { 410 return FastArraySplice( 411 context, arguments, o, len, actualStart, insertCount, actualDeleteCount) 412 otherwise Bailout; 413 } label Bailout {} 414 415 // If the fast case fails, just continue with the slow, correct, 416 // spec-compliant case. 417 return SlowSplice( 418 context, arguments, o, len, actualStart, insertCount, actualDeleteCount); 419} 420} 421