// Copyright 2018 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. namespace array_map { transitioning javascript builtin ArrayMapLoopEagerDeoptContinuation( js-implicit context: Context, receiver: Object)( callback: Object, thisArg: Object, array: Object, initialK: Object, length: Object): Object { // All continuation points in the optimized filter implementation are // after the ToObject(O) call that ensures we are dealing with a // JSReceiver. // // Also, this great mass of casts is necessary because the signature // of Torque javascript builtins requires Object type for all parameters // other than {context}. const jsreceiver = Cast<JSReceiver>(receiver) otherwise unreachable; const callbackfn = Cast<Callable>(callback) otherwise unreachable; const outputArray = Cast<JSReceiver>(array) otherwise unreachable; const numberK = Cast<Number>(initialK) otherwise unreachable; const numberLength = Cast<Number>(length) otherwise unreachable; return ArrayMapLoopContinuation( jsreceiver, callbackfn, thisArg, outputArray, jsreceiver, numberK, numberLength); } transitioning javascript builtin ArrayMapLoopLazyDeoptContinuation( js-implicit context: Context, receiver: Object)( callback: Object, thisArg: Object, array: Object, initialK: Object, length: Object, result: Object): Object { // All continuation points in the optimized filter implementation are // after the ToObject(O) call that ensures we are dealing with a // JSReceiver. const jsreceiver = Cast<JSReceiver>(receiver) otherwise unreachable; const callbackfn = Cast<Callable>(callback) otherwise unreachable; const outputArray = Cast<JSReceiver>(array) otherwise unreachable; let numberK = Cast<Number>(initialK) otherwise unreachable; const numberLength = Cast<Number>(length) otherwise unreachable; // This custom lazy deopt point is right after the callback. map() needs // to pick up at the next step, which is setting the callback result in // the output array. After incrementing k, we can glide into the loop // continuation builtin. // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue). FastCreateDataProperty(outputArray, numberK, result); // 7d. Increase k by 1. numberK = numberK + 1; return ArrayMapLoopContinuation( jsreceiver, callbackfn, thisArg, outputArray, jsreceiver, numberK, numberLength); } transitioning builtin ArrayMapLoopContinuation(implicit context: Context)( _receiver: JSReceiver, callbackfn: Callable, thisArg: Object, array: JSReceiver, o: JSReceiver, initialK: Number, length: Number): Object { // 6. Let k be 0. // 7. Repeat, while k < len for (let k: Number = initialK; k < length; k++) { // 7a. Let Pk be ! ToString(k). // k is guaranteed to be a positive integer, hence ToString is // side-effect free and HasProperty/GetProperty do the conversion inline. // 7b. Let kPresent be ? HasProperty(O, Pk). const kPresent: Boolean = HasProperty_Inline(o, k); // 7c. If kPresent is true, then: if (kPresent == True) { // i. Let kValue be ? Get(O, Pk). const kValue: Object = GetProperty(o, k); // ii. Let mapped_value be ? Call(callbackfn, T, kValue, k, O). const mappedValue: Object = Call(context, callbackfn, thisArg, kValue, k, o); // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mapped_value). FastCreateDataProperty(array, k, mappedValue); } // 7d. Increase k by 1. (done by the loop). } // 8. Return A. return array; } struct Vector { ReportSkippedElement() { this.skippedElements = true; } CreateJSArray(implicit context: Context)(validLength: Smi): JSArray { const length: Smi = this.fixedArray.length; assert(validLength <= length); let kind: ElementsKind = PACKED_SMI_ELEMENTS; if (!this.onlySmis) { if (this.onlyNumbers) { kind = PACKED_DOUBLE_ELEMENTS; } else { kind = PACKED_ELEMENTS; } } if (this.skippedElements || validLength < length) { // We also need to create a holey output array if we are // bailing out of the fast path partway through the array. // This is indicated by {validLength} < {length}. // Who knows if the bailout condition will continue to fill in // every element? kind = FastHoleyElementsKind(kind); } const map: Map = LoadJSArrayElementsMap(kind, LoadNativeContext(context)); let a: JSArray; if (IsDoubleElementsKind(kind)) { // We need to allocate and copy. // First, initialize the elements field before allocation to prevent // heap corruption. const elements: FixedDoubleArray = AllocateFixedDoubleArrayWithHoles( SmiUntag(length), kAllowLargeObjectAllocation); a = NewJSArray(map, this.fixedArray); for (let i: Smi = 0; i < validLength; i++) { typeswitch (this.fixedArray.objects[i]) { case (n: Number): { elements.floats[i] = Convert<float64>(n); } case (h: HeapObject): { assert(h == Hole); } } } a.elements = elements; } else { // Simply install the given fixedArray in {vector}. a = NewJSArray(map, this.fixedArray); } // Paranoia. the FixedArray now "belongs" to JSArray {a}. this.fixedArray = kEmptyFixedArray; return a; } StoreResult(implicit context: Context)(index: Smi, result: Object) { typeswitch (result) { case (s: Smi): { this.fixedArray.objects[index] = s; } case (s: HeapNumber): { this.onlySmis = false; this.fixedArray.objects[index] = s; } case (s: HeapObject): { this.onlySmis = false; this.onlyNumbers = false; this.fixedArray.objects[index] = s; } } } fixedArray: FixedArray; onlySmis: bool; // initially true. onlyNumbers: bool; // initially true. skippedElements: bool; // initially false. } macro NewVector(implicit context: Context)(length: Smi): Vector { const fixedArray = length > 0 ? AllocateFixedArrayWithHoles( SmiUntag(length), kAllowLargeObjectAllocation) : kEmptyFixedArray; return Vector{ fixedArray, onlySmis: true, onlyNumbers: true, skippedElements: false }; } transitioning macro FastArrayMap(implicit context: Context)( fastO: FastJSArrayForRead, len: Smi, callbackfn: Callable, thisArg: Object): JSArray labels Bailout(JSArray, Smi) { let k: Smi = 0; let fastOW = NewFastJSArrayForReadWitness(fastO); let vector = NewVector(len); // Build a fast loop over the smi array. // 7. Repeat, while k < len. try { for (; k < len; k++) { fastOW.Recheck() otherwise goto PrepareBailout(k); // Ensure that we haven't walked beyond a possibly updated length. if (k >= fastOW.Get().length) goto PrepareBailout(k); try { const value: Object = fastOW.LoadElementNoHole(k) otherwise FoundHole; const result: Object = Call(context, callbackfn, thisArg, value, k, fastOW.Get()); vector.StoreResult(k, result); } label FoundHole { // Our output array must necessarily be holey because of holes in // the input array. vector.ReportSkippedElement(); } } } label PrepareBailout(k: Smi) deferred { // Transform {vector} into a JSArray and bail out. goto Bailout(vector.CreateJSArray(k), k); } return vector.CreateJSArray(len); } // https://tc39.github.io/ecma262/#sec-array.prototype.map transitioning javascript builtin ArrayMap(js-implicit context: Context, receiver: Object)(...arguments): Object { try { if (IsNullOrUndefined(receiver)) goto NullOrUndefinedError; // 1. Let O be ? ToObject(this value). const o: JSReceiver = ToObject_Inline(context, receiver); // 2. Let len be ? ToLength(? Get(O, "length")). const len: Number = GetLengthProperty(o); // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. if (arguments.length == 0) goto TypeError; const callbackfn = Cast<Callable>(arguments[0]) otherwise TypeError; // 4. If thisArg is present, let T be thisArg; else let T be undefined. const thisArg: Object = arguments.length > 1 ? arguments[1] : Undefined; let array: JSReceiver; let k: Number = 0; try { // 5. Let A be ? ArraySpeciesCreate(O, len). if (IsArraySpeciesProtectorCellInvalid()) goto SlowSpeciesCreate; const o: FastJSArrayForRead = Cast<FastJSArrayForRead>(receiver) otherwise SlowSpeciesCreate; const smiLength: Smi = Cast<Smi>(len) otherwise SlowSpeciesCreate; return FastArrayMap(o, smiLength, callbackfn, thisArg) otherwise Bailout; } label SlowSpeciesCreate { array = ArraySpeciesCreate(context, receiver, len); } label Bailout(output: JSArray, kValue: Smi) deferred { array = output; k = kValue; } return ArrayMapLoopContinuation(o, callbackfn, thisArg, array, o, k, len); } label TypeError deferred { ThrowTypeError(kCalledNonCallable, arguments[0]); } label NullOrUndefinedError deferred { ThrowTypeError(kCalledOnNullOrUndefined, 'Array.prototype.map'); } } }