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 7// Continuation for lazy deopt triggered by allocation of the result array. 8transitioning javascript builtin 9ArrayMapPreLoopLazyDeoptContinuation( 10 js-implicit context: NativeContext, receiver: JSAny)( 11 callback: JSAny, thisArg: JSAny, length: JSAny, result: JSAny): JSAny { 12 const jsreceiver = Cast<JSReceiver>(receiver) otherwise unreachable; 13 const outputArray = Cast<JSReceiver>(result) otherwise unreachable; 14 const numberLength = Cast<Number>(length) otherwise unreachable; 15 16 const callbackfn = Cast<Callable>(callback) 17 otherwise ThrowTypeError(MessageTemplate::kCalledNonCallable, callback); 18 return ArrayMapLoopContinuation( 19 jsreceiver, callbackfn, thisArg, outputArray, jsreceiver, kZero, 20 numberLength); 21} 22 23transitioning javascript builtin 24ArrayMapLoopEagerDeoptContinuation( 25 js-implicit context: NativeContext, receiver: JSAny)( 26 callback: JSAny, thisArg: JSAny, array: JSAny, initialK: JSAny, 27 length: JSAny): JSAny { 28 const jsreceiver = Cast<JSReceiver>(receiver) otherwise unreachable; 29 const callbackfn = Cast<Callable>(callback) otherwise unreachable; 30 const outputArray = Cast<JSReceiver>(array) otherwise unreachable; 31 const numberK = Cast<Number>(initialK) otherwise unreachable; 32 const numberLength = Cast<Number>(length) otherwise unreachable; 33 34 return ArrayMapLoopContinuation( 35 jsreceiver, callbackfn, thisArg, outputArray, jsreceiver, numberK, 36 numberLength); 37} 38 39transitioning javascript builtin 40ArrayMapLoopLazyDeoptContinuation( 41 js-implicit context: NativeContext, receiver: JSAny)( 42 callback: JSAny, thisArg: JSAny, array: JSAny, initialK: JSAny, 43 length: JSAny, result: JSAny): JSAny { 44 const jsreceiver = Cast<JSReceiver>(receiver) otherwise unreachable; 45 const callbackfn = Cast<Callable>(callback) otherwise unreachable; 46 const outputArray = Cast<JSReceiver>(array) otherwise unreachable; 47 let numberK = Cast<Number>(initialK) otherwise unreachable; 48 const numberLength = Cast<Number>(length) otherwise unreachable; 49 50 // This custom lazy deopt point is right after the callback. The continuation 51 // needs to pick up at the next step, which is setting the callback result in 52 // the output array. After incrementing k, we can glide into the loop 53 // continuation builtin. 54 55 // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue). 56 FastCreateDataProperty(outputArray, numberK, result); 57 58 // 7d. Increase k by 1. 59 numberK = numberK + 1; 60 61 return ArrayMapLoopContinuation( 62 jsreceiver, callbackfn, thisArg, outputArray, jsreceiver, numberK, 63 numberLength); 64} 65 66transitioning builtin ArrayMapLoopContinuation(implicit context: Context)( 67 _receiver: JSReceiver, callbackfn: Callable, thisArg: JSAny, 68 array: JSReceiver, o: JSReceiver, initialK: Number, length: Number): JSAny { 69 // 6. Let k be 0. 70 // 7. Repeat, while k < len 71 for (let k: Number = initialK; k < length; k++) { 72 // 7a. Let Pk be ! ToString(k). 73 // k is guaranteed to be a positive integer, hence ToString is 74 // side-effect free and HasProperty/GetProperty do the conversion inline. 75 76 // 7b. Let kPresent be ? HasProperty(O, Pk). 77 const kPresent: Boolean = HasProperty_Inline(o, k); 78 79 // 7c. If kPresent is true, then: 80 if (kPresent == True) { 81 // i. Let kValue be ? Get(O, Pk). 82 const kValue: JSAny = GetProperty(o, k); 83 84 // ii. Let mapped_value be ? Call(callbackfn, T, kValue, k, O). 85 const mappedValue: JSAny = 86 Call(context, callbackfn, thisArg, kValue, k, o); 87 88 // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mapped_value). 89 FastCreateDataProperty(array, k, mappedValue); 90 } 91 92 // 7d. Increase k by 1. (done by the loop). 93 } 94 95 // 8. Return A. 96 return array; 97} 98 99struct Vector { 100 macro ReportSkippedElement(): void { 101 this.skippedElements = true; 102 } 103 104 macro CreateJSArray(implicit context: Context)(validLength: Smi): JSArray { 105 const length: Smi = this.fixedArray.length; 106 dcheck(validLength <= length); 107 let kind: ElementsKind = ElementsKind::PACKED_SMI_ELEMENTS; 108 if (!this.onlySmis) { 109 if (this.onlyNumbers) { 110 kind = ElementsKind::PACKED_DOUBLE_ELEMENTS; 111 } else { 112 kind = ElementsKind::PACKED_ELEMENTS; 113 } 114 } 115 116 if (this.skippedElements || validLength < length) { 117 // We also need to create a holey output array if we are 118 // bailing out of the fast path partway through the array. 119 // This is indicated by {validLength} < {length}. 120 // Who knows if the bailout condition will continue to fill in 121 // every element? 122 kind = FastHoleyElementsKind(kind); 123 } 124 125 const map: Map = LoadJSArrayElementsMap(kind, LoadNativeContext(context)); 126 let a: JSArray; 127 128 if (IsDoubleElementsKind(kind)) { 129 // We need to allocate and copy. 130 // First, initialize the elements field before allocation to prevent 131 // heap corruption. 132 const elements: FixedDoubleArray = AllocateFixedDoubleArrayWithHoles( 133 SmiUntag(length), AllocationFlag::kAllowLargeObjectAllocation); 134 a = NewJSArray(map, this.fixedArray); 135 for (let i: Smi = 0; i < validLength; i++) { 136 typeswitch ( 137 UnsafeCast<(Number | TheHole)>(this.fixedArray.objects[i])) { 138 case (n: Number): { 139 elements.floats[i] = Convert<float64_or_hole>(n); 140 } 141 case (TheHole): { 142 } 143 } 144 } 145 a.elements = elements; 146 } else { 147 // Simply install the given fixedArray in {vector}. 148 a = NewJSArray(map, this.fixedArray); 149 } 150 151 // Paranoia. the FixedArray now "belongs" to JSArray {a}. 152 this.fixedArray = kEmptyFixedArray; 153 return a; 154 } 155 156 macro StoreResult(implicit context: Context)( 157 index: Smi, result: JSAny): void { 158 typeswitch (result) { 159 case (s: Smi): { 160 this.fixedArray.objects[index] = s; 161 } 162 case (s: HeapNumber): { 163 this.onlySmis = false; 164 this.fixedArray.objects[index] = s; 165 } 166 case (s: JSAnyNotNumber): { 167 this.onlySmis = false; 168 this.onlyNumbers = false; 169 this.fixedArray.objects[index] = s; 170 } 171 } 172 } 173 174 fixedArray: FixedArray; 175 onlySmis: bool; // initially true. 176 onlyNumbers: bool; // initially true. 177 skippedElements: bool; // initially false. 178} 179 180macro NewVector(implicit context: Context)(length: Smi): Vector { 181 const fixedArray = length > 0 ? 182 AllocateFixedArrayWithHoles( 183 SmiUntag(length), AllocationFlag::kAllowLargeObjectAllocation) : 184 kEmptyFixedArray; 185 return Vector{ 186 fixedArray, 187 onlySmis: true, 188 onlyNumbers: true, 189 skippedElements: false 190 }; 191} 192 193transitioning macro FastArrayMap(implicit context: Context)( 194 fastO: FastJSArrayForRead, len: Smi, callbackfn: Callable, 195 thisArg: JSAny): JSArray 196 labels Bailout(JSArray, Smi) { 197 let k: Smi = 0; 198 let fastOW = NewFastJSArrayForReadWitness(fastO); 199 let vector = NewVector(len); 200 201 // Build a fast loop over the smi array. 202 // 7. Repeat, while k < len. 203 try { 204 for (; k < len; k++) { 205 fastOW.Recheck() otherwise goto PrepareBailout(k); 206 207 // Ensure that we haven't walked beyond a possibly updated length. 208 if (k >= fastOW.Get().length) goto PrepareBailout(k); 209 210 try { 211 const value: JSAny = fastOW.LoadElementNoHole(k) 212 otherwise FoundHole; 213 const result: JSAny = 214 Call(context, callbackfn, thisArg, value, k, fastOW.Get()); 215 vector.StoreResult(k, result); 216 } label FoundHole { 217 // Our output array must necessarily be holey because of holes in 218 // the input array. 219 vector.ReportSkippedElement(); 220 } 221 } 222 } label PrepareBailout(k: Smi) deferred { 223 // Transform {vector} into a JSArray and bail out. 224 goto Bailout(vector.CreateJSArray(k), k); 225 } 226 227 return vector.CreateJSArray(len); 228} 229 230// https://tc39.github.io/ecma262/#sec-array.prototype.map 231transitioning javascript builtin 232ArrayMap( 233 js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny { 234 try { 235 RequireObjectCoercible(receiver, 'Array.prototype.map'); 236 237 // 1. Let O be ? ToObject(this value). 238 const o: JSReceiver = ToObject_Inline(context, receiver); 239 240 // 2. Let len be ? ToLength(? Get(O, "length")). 241 const len: Number = GetLengthProperty(o); 242 243 // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. 244 if (arguments.length == 0) goto TypeError; 245 246 const callbackfn = Cast<Callable>(arguments[0]) otherwise TypeError; 247 248 // 4. If thisArg is present, let T be thisArg; else let T be undefined. 249 const thisArg: JSAny = arguments[1]; 250 251 let array: JSReceiver; 252 let k: Number = 0; 253 try { 254 // 5. Let A be ? ArraySpeciesCreate(O, len). 255 if (IsArraySpeciesProtectorCellInvalid()) goto SlowSpeciesCreate; 256 const o: FastJSArrayForRead = Cast<FastJSArrayForRead>(receiver) 257 otherwise SlowSpeciesCreate; 258 const smiLength: Smi = Cast<Smi>(len) 259 otherwise SlowSpeciesCreate; 260 261 return FastArrayMap(o, smiLength, callbackfn, thisArg) 262 otherwise Bailout; 263 } label SlowSpeciesCreate { 264 array = ArraySpeciesCreate(context, receiver, len); 265 } label Bailout(output: JSArray, kValue: Smi) deferred { 266 array = output; 267 k = kValue; 268 } 269 270 return ArrayMapLoopContinuation(o, callbackfn, thisArg, array, o, k, len); 271 } label TypeError deferred { 272 ThrowTypeError(MessageTemplate::kCalledNonCallable, arguments[0]); 273 } 274} 275} 276