Home | History | Annotate | Download | only in runtime
      1 // Copyright 2014 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/runtime/runtime-utils.h"
      6 
      7 #include "src/arguments.h"
      8 #include "src/conversions-inl.h"
      9 #include "src/elements.h"
     10 #include "src/factory.h"
     11 #include "src/isolate-inl.h"
     12 #include "src/key-accumulator.h"
     13 #include "src/messages.h"
     14 #include "src/prototype.h"
     15 
     16 namespace v8 {
     17 namespace internal {
     18 
     19 RUNTIME_FUNCTION(Runtime_FinishArrayPrototypeSetup) {
     20   HandleScope scope(isolate);
     21   DCHECK(args.length() == 1);
     22   CONVERT_ARG_HANDLE_CHECKED(JSArray, prototype, 0);
     23   Object* length = prototype->length();
     24   RUNTIME_ASSERT(length->IsSmi() && Smi::cast(length)->value() == 0);
     25   RUNTIME_ASSERT(prototype->HasFastSmiOrObjectElements());
     26   // This is necessary to enable fast checks for absence of elements
     27   // on Array.prototype and below.
     28   prototype->set_elements(isolate->heap()->empty_fixed_array());
     29   return Smi::FromInt(0);
     30 }
     31 
     32 
     33 static void InstallBuiltin(Isolate* isolate, Handle<JSObject> holder,
     34                            const char* name, Builtins::Name builtin_name) {
     35   Handle<String> key = isolate->factory()->InternalizeUtf8String(name);
     36   Handle<Code> code(isolate->builtins()->builtin(builtin_name));
     37   Handle<JSFunction> optimized =
     38       isolate->factory()->NewFunctionWithoutPrototype(key, code);
     39   optimized->shared()->DontAdaptArguments();
     40   JSObject::AddProperty(holder, key, optimized, NONE);
     41 }
     42 
     43 
     44 RUNTIME_FUNCTION(Runtime_SpecialArrayFunctions) {
     45   HandleScope scope(isolate);
     46   DCHECK(args.length() == 0);
     47   Handle<JSObject> holder =
     48       isolate->factory()->NewJSObject(isolate->object_function());
     49 
     50   InstallBuiltin(isolate, holder, "pop", Builtins::kArrayPop);
     51   InstallBuiltin(isolate, holder, "push", Builtins::kArrayPush);
     52   InstallBuiltin(isolate, holder, "shift", Builtins::kArrayShift);
     53   InstallBuiltin(isolate, holder, "unshift", Builtins::kArrayUnshift);
     54   InstallBuiltin(isolate, holder, "slice", Builtins::kArraySlice);
     55   InstallBuiltin(isolate, holder, "splice", Builtins::kArraySplice);
     56 
     57   return *holder;
     58 }
     59 
     60 
     61 RUNTIME_FUNCTION(Runtime_FixedArrayGet) {
     62   SealHandleScope shs(isolate);
     63   DCHECK(args.length() == 2);
     64   CONVERT_ARG_CHECKED(FixedArray, object, 0);
     65   CONVERT_SMI_ARG_CHECKED(index, 1);
     66   return object->get(index);
     67 }
     68 
     69 
     70 RUNTIME_FUNCTION(Runtime_FixedArraySet) {
     71   SealHandleScope shs(isolate);
     72   DCHECK(args.length() == 3);
     73   CONVERT_ARG_CHECKED(FixedArray, object, 0);
     74   CONVERT_SMI_ARG_CHECKED(index, 1);
     75   CONVERT_ARG_CHECKED(Object, value, 2);
     76   object->set(index, value);
     77   return isolate->heap()->undefined_value();
     78 }
     79 
     80 
     81 RUNTIME_FUNCTION(Runtime_TransitionElementsKind) {
     82   HandleScope scope(isolate);
     83   RUNTIME_ASSERT(args.length() == 2);
     84   CONVERT_ARG_HANDLE_CHECKED(JSArray, array, 0);
     85   CONVERT_ARG_HANDLE_CHECKED(Map, map, 1);
     86   JSObject::TransitionElementsKind(array, map->elements_kind());
     87   return *array;
     88 }
     89 
     90 
     91 // Push an object unto an array of objects if it is not already in the
     92 // array.  Returns true if the element was pushed on the stack and
     93 // false otherwise.
     94 RUNTIME_FUNCTION(Runtime_PushIfAbsent) {
     95   HandleScope scope(isolate);
     96   DCHECK(args.length() == 2);
     97   CONVERT_ARG_HANDLE_CHECKED(JSArray, array, 0);
     98   CONVERT_ARG_HANDLE_CHECKED(JSReceiver, element, 1);
     99   RUNTIME_ASSERT(array->HasFastSmiOrObjectElements());
    100   int length = Smi::cast(array->length())->value();
    101   FixedArray* elements = FixedArray::cast(array->elements());
    102   for (int i = 0; i < length; i++) {
    103     if (elements->get(i) == *element) return isolate->heap()->false_value();
    104   }
    105 
    106   // Strict not needed. Used for cycle detection in Array join implementation.
    107   RETURN_FAILURE_ON_EXCEPTION(
    108       isolate, JSObject::AddDataElement(array, length, element, NONE));
    109   JSObject::ValidateElements(array);
    110   return isolate->heap()->true_value();
    111 }
    112 
    113 
    114 // Moves all own elements of an object, that are below a limit, to positions
    115 // starting at zero. All undefined values are placed after non-undefined values,
    116 // and are followed by non-existing element. Does not change the length
    117 // property.
    118 // Returns the number of non-undefined elements collected.
    119 // Returns -1 if hole removal is not supported by this method.
    120 RUNTIME_FUNCTION(Runtime_RemoveArrayHoles) {
    121   HandleScope scope(isolate);
    122   DCHECK(args.length() == 2);
    123   CONVERT_ARG_HANDLE_CHECKED(JSObject, object, 0);
    124   CONVERT_NUMBER_CHECKED(uint32_t, limit, Uint32, args[1]);
    125   return *JSObject::PrepareElementsForSort(object, limit);
    126 }
    127 
    128 
    129 // Move contents of argument 0 (an array) to argument 1 (an array)
    130 RUNTIME_FUNCTION(Runtime_MoveArrayContents) {
    131   HandleScope scope(isolate);
    132   DCHECK(args.length() == 2);
    133   CONVERT_ARG_HANDLE_CHECKED(JSArray, from, 0);
    134   CONVERT_ARG_HANDLE_CHECKED(JSArray, to, 1);
    135   JSObject::ValidateElements(from);
    136   JSObject::ValidateElements(to);
    137 
    138   Handle<FixedArrayBase> new_elements(from->elements());
    139   ElementsKind from_kind = from->GetElementsKind();
    140   Handle<Map> new_map = JSObject::GetElementsTransitionMap(to, from_kind);
    141   JSObject::SetMapAndElements(to, new_map, new_elements);
    142   to->set_length(from->length());
    143 
    144   JSObject::ResetElements(from);
    145   from->set_length(Smi::FromInt(0));
    146 
    147   JSObject::ValidateElements(to);
    148   return *to;
    149 }
    150 
    151 
    152 // How many elements does this object/array have?
    153 RUNTIME_FUNCTION(Runtime_EstimateNumberOfElements) {
    154   HandleScope scope(isolate);
    155   DCHECK(args.length() == 1);
    156   CONVERT_ARG_HANDLE_CHECKED(JSArray, array, 0);
    157   Handle<FixedArrayBase> elements(array->elements(), isolate);
    158   SealHandleScope shs(isolate);
    159   if (elements->IsDictionary()) {
    160     int result =
    161         Handle<SeededNumberDictionary>::cast(elements)->NumberOfElements();
    162     return Smi::FromInt(result);
    163   } else {
    164     DCHECK(array->length()->IsSmi());
    165     // For packed elements, we know the exact number of elements
    166     int length = elements->length();
    167     ElementsKind kind = array->GetElementsKind();
    168     if (IsFastPackedElementsKind(kind)) {
    169       return Smi::FromInt(length);
    170     }
    171     // For holey elements, take samples from the buffer checking for holes
    172     // to generate the estimate.
    173     const int kNumberOfHoleCheckSamples = 97;
    174     int increment = (length < kNumberOfHoleCheckSamples)
    175                         ? 1
    176                         : static_cast<int>(length / kNumberOfHoleCheckSamples);
    177     ElementsAccessor* accessor = array->GetElementsAccessor();
    178     int holes = 0;
    179     for (int i = 0; i < length; i += increment) {
    180       if (!accessor->HasElement(array, i, elements)) {
    181         ++holes;
    182       }
    183     }
    184     int estimate = static_cast<int>((kNumberOfHoleCheckSamples - holes) /
    185                                     kNumberOfHoleCheckSamples * length);
    186     return Smi::FromInt(estimate);
    187   }
    188 }
    189 
    190 
    191 // Returns an array that tells you where in the [0, length) interval an array
    192 // might have elements.  Can either return an array of keys (positive integers
    193 // or undefined) or a number representing the positive length of an interval
    194 // starting at index 0.
    195 // Intervals can span over some keys that are not in the object.
    196 RUNTIME_FUNCTION(Runtime_GetArrayKeys) {
    197   HandleScope scope(isolate);
    198   DCHECK(args.length() == 2);
    199   CONVERT_ARG_HANDLE_CHECKED(JSObject, array, 0);
    200   CONVERT_NUMBER_CHECKED(uint32_t, length, Uint32, args[1]);
    201 
    202   if (!array->elements()->IsDictionary()) {
    203     RUNTIME_ASSERT(array->HasFastSmiOrObjectElements() ||
    204                    array->HasFastDoubleElements());
    205     uint32_t actual_length = static_cast<uint32_t>(array->elements()->length());
    206     return *isolate->factory()->NewNumberFromUint(Min(actual_length, length));
    207   }
    208 
    209   KeyAccumulator accumulator(isolate, ALL_PROPERTIES);
    210   // No need to separate protoype levels since we only get numbers/element keys
    211   for (PrototypeIterator iter(isolate, array,
    212                               PrototypeIterator::START_AT_RECEIVER);
    213        !iter.IsAtEnd(); iter.Advance()) {
    214     if (PrototypeIterator::GetCurrent(iter)->IsJSProxy() ||
    215         PrototypeIterator::GetCurrent<JSObject>(iter)
    216             ->HasIndexedInterceptor()) {
    217       // Bail out if we find a proxy or interceptor, likely not worth
    218       // collecting keys in that case.
    219       return *isolate->factory()->NewNumberFromUint(length);
    220     }
    221     accumulator.NextPrototype();
    222     Handle<JSObject> current = PrototypeIterator::GetCurrent<JSObject>(iter);
    223     JSObject::CollectOwnElementKeys(current, &accumulator, ALL_PROPERTIES);
    224   }
    225   // Erase any keys >= length.
    226   // TODO(adamk): Remove this step when the contract of %GetArrayKeys
    227   // is changed to let this happen on the JS side.
    228   Handle<FixedArray> keys = accumulator.GetKeys(KEEP_NUMBERS);
    229   for (int i = 0; i < keys->length(); i++) {
    230     if (NumberToUint32(keys->get(i)) >= length) keys->set_undefined(i);
    231   }
    232   return *isolate->factory()->NewJSArrayWithElements(keys);
    233 }
    234 
    235 
    236 namespace {
    237 
    238 Object* ArrayConstructorCommon(Isolate* isolate, Handle<JSFunction> constructor,
    239                                Handle<JSReceiver> new_target,
    240                                Handle<AllocationSite> site,
    241                                Arguments* caller_args) {
    242   Factory* factory = isolate->factory();
    243 
    244   // If called through new, new.target can be:
    245   // - a subclass of constructor,
    246   // - a proxy wrapper around constructor, or
    247   // - the constructor itself.
    248   // If called through Reflect.construct, it's guaranteed to be a constructor by
    249   // REFLECT_CONSTRUCT_PREPARE.
    250   DCHECK(new_target->IsConstructor());
    251 
    252   bool holey = false;
    253   bool can_use_type_feedback = !site.is_null();
    254   bool can_inline_array_constructor = true;
    255   if (caller_args->length() == 1) {
    256     Handle<Object> argument_one = caller_args->at<Object>(0);
    257     if (argument_one->IsSmi()) {
    258       int value = Handle<Smi>::cast(argument_one)->value();
    259       if (value < 0 ||
    260           JSArray::SetLengthWouldNormalize(isolate->heap(), value)) {
    261         // the array is a dictionary in this case.
    262         can_use_type_feedback = false;
    263       } else if (value != 0) {
    264         holey = true;
    265         if (value >= JSArray::kInitialMaxFastElementArray) {
    266           can_inline_array_constructor = false;
    267         }
    268       }
    269     } else {
    270       // Non-smi length argument produces a dictionary
    271       can_use_type_feedback = false;
    272     }
    273   }
    274 
    275   Handle<Map> initial_map;
    276   ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
    277       isolate, initial_map,
    278       JSFunction::GetDerivedMap(isolate, constructor, new_target));
    279 
    280   ElementsKind to_kind = can_use_type_feedback ? site->GetElementsKind()
    281                                                : initial_map->elements_kind();
    282   if (holey && !IsFastHoleyElementsKind(to_kind)) {
    283     to_kind = GetHoleyElementsKind(to_kind);
    284     // Update the allocation site info to reflect the advice alteration.
    285     if (!site.is_null()) site->SetElementsKind(to_kind);
    286   }
    287 
    288   // We should allocate with an initial map that reflects the allocation site
    289   // advice. Therefore we use AllocateJSObjectFromMap instead of passing
    290   // the constructor.
    291   if (to_kind != initial_map->elements_kind()) {
    292     initial_map = Map::AsElementsKind(initial_map, to_kind);
    293   }
    294 
    295   // If we don't care to track arrays of to_kind ElementsKind, then
    296   // don't emit a memento for them.
    297   Handle<AllocationSite> allocation_site;
    298   if (AllocationSite::GetMode(to_kind) == TRACK_ALLOCATION_SITE) {
    299     allocation_site = site;
    300   }
    301 
    302   Handle<JSArray> array = Handle<JSArray>::cast(
    303       factory->NewJSObjectFromMap(initial_map, NOT_TENURED, allocation_site));
    304 
    305   factory->NewJSArrayStorage(array, 0, 0, DONT_INITIALIZE_ARRAY_ELEMENTS);
    306 
    307   ElementsKind old_kind = array->GetElementsKind();
    308   RETURN_FAILURE_ON_EXCEPTION(
    309       isolate, ArrayConstructInitializeElements(array, caller_args));
    310   if (!site.is_null() &&
    311       (old_kind != array->GetElementsKind() || !can_use_type_feedback ||
    312        !can_inline_array_constructor)) {
    313     // The arguments passed in caused a transition. This kind of complexity
    314     // can't be dealt with in the inlined hydrogen array constructor case.
    315     // We must mark the allocationsite as un-inlinable.
    316     site->SetDoNotInlineCall();
    317   }
    318 
    319   return *array;
    320 }
    321 
    322 }  // namespace
    323 
    324 
    325 RUNTIME_FUNCTION(Runtime_NewArray) {
    326   HandleScope scope(isolate);
    327   DCHECK_LE(3, args.length());
    328   int const argc = args.length() - 3;
    329   // TODO(bmeurer): Remove this Arguments nonsense.
    330   Arguments argv(argc, args.arguments() - 1);
    331   CONVERT_ARG_HANDLE_CHECKED(JSFunction, constructor, 0);
    332   CONVERT_ARG_HANDLE_CHECKED(JSReceiver, new_target, argc + 1);
    333   CONVERT_ARG_HANDLE_CHECKED(HeapObject, type_info, argc + 2);
    334   // TODO(bmeurer): Use MaybeHandle to pass around the AllocationSite.
    335   Handle<AllocationSite> site = type_info->IsAllocationSite()
    336                                     ? Handle<AllocationSite>::cast(type_info)
    337                                     : Handle<AllocationSite>::null();
    338   return ArrayConstructorCommon(isolate, constructor, new_target, site, &argv);
    339 }
    340 
    341 
    342 RUNTIME_FUNCTION(Runtime_ArrayConstructor) {
    343   HandleScope scope(isolate);
    344   // If we get 2 arguments then they are the stub parameters (constructor, type
    345   // info).  If we get 4, then the first one is a pointer to the arguments
    346   // passed by the caller, and the last one is the length of the arguments
    347   // passed to the caller (redundant, but useful to check on the deoptimizer
    348   // with an assert).
    349   Arguments empty_args(0, NULL);
    350   bool no_caller_args = args.length() == 2;
    351   DCHECK(no_caller_args || args.length() == 4);
    352   int parameters_start = no_caller_args ? 0 : 1;
    353   Arguments* caller_args =
    354       no_caller_args ? &empty_args : reinterpret_cast<Arguments*>(args[0]);
    355   CONVERT_ARG_HANDLE_CHECKED(JSFunction, constructor, parameters_start);
    356   CONVERT_ARG_HANDLE_CHECKED(Object, type_info, parameters_start + 1);
    357 #ifdef DEBUG
    358   if (!no_caller_args) {
    359     CONVERT_SMI_ARG_CHECKED(arg_count, parameters_start + 2);
    360     DCHECK(arg_count == caller_args->length());
    361   }
    362 #endif
    363 
    364   Handle<AllocationSite> site;
    365   if (!type_info.is_null() &&
    366       *type_info != isolate->heap()->undefined_value()) {
    367     site = Handle<AllocationSite>::cast(type_info);
    368     DCHECK(!site->SitePointsToLiteral());
    369   }
    370 
    371   return ArrayConstructorCommon(isolate, constructor, constructor, site,
    372                                 caller_args);
    373 }
    374 
    375 
    376 RUNTIME_FUNCTION(Runtime_InternalArrayConstructor) {
    377   HandleScope scope(isolate);
    378   Arguments empty_args(0, NULL);
    379   bool no_caller_args = args.length() == 1;
    380   DCHECK(no_caller_args || args.length() == 3);
    381   int parameters_start = no_caller_args ? 0 : 1;
    382   Arguments* caller_args =
    383       no_caller_args ? &empty_args : reinterpret_cast<Arguments*>(args[0]);
    384   CONVERT_ARG_HANDLE_CHECKED(JSFunction, constructor, parameters_start);
    385 #ifdef DEBUG
    386   if (!no_caller_args) {
    387     CONVERT_SMI_ARG_CHECKED(arg_count, parameters_start + 1);
    388     DCHECK(arg_count == caller_args->length());
    389   }
    390 #endif
    391   return ArrayConstructorCommon(isolate, constructor, constructor,
    392                                 Handle<AllocationSite>::null(), caller_args);
    393 }
    394 
    395 
    396 RUNTIME_FUNCTION(Runtime_NormalizeElements) {
    397   HandleScope scope(isolate);
    398   DCHECK(args.length() == 1);
    399   CONVERT_ARG_HANDLE_CHECKED(JSObject, array, 0);
    400   RUNTIME_ASSERT(!array->HasFixedTypedArrayElements() &&
    401                  !array->IsJSGlobalProxy());
    402   JSObject::NormalizeElements(array);
    403   return *array;
    404 }
    405 
    406 
    407 // GrowArrayElements returns a sentinel Smi if the object was normalized.
    408 RUNTIME_FUNCTION(Runtime_GrowArrayElements) {
    409   HandleScope scope(isolate);
    410   DCHECK(args.length() == 2);
    411   CONVERT_ARG_HANDLE_CHECKED(JSObject, object, 0);
    412   CONVERT_NUMBER_CHECKED(int, key, Int32, args[1]);
    413 
    414   if (key < 0) {
    415     return object->elements();
    416   }
    417 
    418   uint32_t capacity = static_cast<uint32_t>(object->elements()->length());
    419   uint32_t index = static_cast<uint32_t>(key);
    420 
    421   if (index >= capacity) {
    422     if (object->WouldConvertToSlowElements(index)) {
    423       // We don't want to allow operations that cause lazy deopt. Return a Smi
    424       // as a signal that optimized code should eagerly deoptimize.
    425       return Smi::FromInt(0);
    426     }
    427 
    428     uint32_t new_capacity = JSObject::NewElementsCapacity(index + 1);
    429     object->GetElementsAccessor()->GrowCapacityAndConvert(object, new_capacity);
    430   }
    431 
    432   // On success, return the fixed array elements.
    433   return object->elements();
    434 }
    435 
    436 
    437 RUNTIME_FUNCTION(Runtime_HasComplexElements) {
    438   HandleScope scope(isolate);
    439   DCHECK(args.length() == 1);
    440   CONVERT_ARG_HANDLE_CHECKED(JSObject, array, 0);
    441   for (PrototypeIterator iter(isolate, array,
    442                               PrototypeIterator::START_AT_RECEIVER);
    443        !iter.IsAtEnd(); iter.Advance()) {
    444     if (PrototypeIterator::GetCurrent(iter)->IsJSProxy()) {
    445       return isolate->heap()->true_value();
    446     }
    447     Handle<JSObject> current = PrototypeIterator::GetCurrent<JSObject>(iter);
    448     if (current->HasIndexedInterceptor()) {
    449       return isolate->heap()->true_value();
    450     }
    451     if (!current->HasDictionaryElements()) continue;
    452     if (current->element_dictionary()->HasComplexElements()) {
    453       return isolate->heap()->true_value();
    454     }
    455   }
    456   return isolate->heap()->false_value();
    457 }
    458 
    459 
    460 RUNTIME_FUNCTION(Runtime_IsArray) {
    461   SealHandleScope shs(isolate);
    462   DCHECK(args.length() == 1);
    463   CONVERT_ARG_CHECKED(Object, obj, 0);
    464   return isolate->heap()->ToBoolean(obj->IsJSArray());
    465 }
    466 
    467 
    468 RUNTIME_FUNCTION(Runtime_HasCachedArrayIndex) {
    469   SealHandleScope shs(isolate);
    470   DCHECK(args.length() == 1);
    471   return isolate->heap()->false_value();
    472 }
    473 
    474 
    475 RUNTIME_FUNCTION(Runtime_GetCachedArrayIndex) {
    476   // This can never be reached, because Runtime_HasCachedArrayIndex always
    477   // returns false.
    478   UNIMPLEMENTED();
    479   return nullptr;
    480 }
    481 
    482 
    483 RUNTIME_FUNCTION(Runtime_FastOneByteArrayJoin) {
    484   SealHandleScope shs(isolate);
    485   DCHECK(args.length() == 2);
    486   // Returning undefined means that this fast path fails and one has to resort
    487   // to a slow path.
    488   return isolate->heap()->undefined_value();
    489 }
    490 
    491 
    492 RUNTIME_FUNCTION(Runtime_ArraySpeciesConstructor) {
    493   HandleScope scope(isolate);
    494   DCHECK(args.length() == 1);
    495   CONVERT_ARG_HANDLE_CHECKED(Object, original_array, 0);
    496   Handle<Object> constructor;
    497   ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
    498       isolate, constructor,
    499       Object::ArraySpeciesConstructor(isolate, original_array));
    500   return *constructor;
    501 }
    502 
    503 }  // namespace internal
    504 }  // namespace v8
    505