Changeset 195878 in webkit


Ignore:
Timestamp:
Jan 29, 2016 6:45:25 PM (8 years ago)
Author:
keith_miller@apple.com
Message:

Array.prototype native functions should use Symbol.species to construct the result
https://bugs.webkit.org/show_bug.cgi?id=153660

Reviewed by Saam Barati.

This patch adds support for Symbol.species in the Array.prototype native functions.
We make an optimization to avoid regressions on some benchmarks by using an
adaptive watchpoint to check if Array.prototype.constructor is ever changed.

  • runtime/ArrayPrototype.cpp:

(JSC::putLength):
(JSC::setLength):
(JSC::speciesConstructArray):
(JSC::arrayProtoFuncConcat):
(JSC::arrayProtoFuncSlice):
(JSC::arrayProtoFuncSplice):
(JSC::ArrayPrototype::setConstructor):
(JSC::ArrayPrototypeAdaptiveInferredPropertyWatchpoint::ArrayPrototypeAdaptiveInferredPropertyWatchpoint):
(JSC::ArrayPrototypeAdaptiveInferredPropertyWatchpoint::handleFire):

  • runtime/ArrayPrototype.h:

(JSC::ArrayPrototype::didChangeConstructorProperty):

  • runtime/ConstructData.cpp:

(JSC::construct):

  • runtime/ConstructData.h:
  • runtime/JSGlobalObject.cpp:

(JSC::JSGlobalObject::init):

  • tests/es6.yaml:
  • tests/stress/array-species-functions.js: Added.

(Symbol.species):
(funcThrows):
(test.species):
(test):

Location:
trunk/Source/JavaScriptCore
Files:
1 added
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/ChangeLog

    r195877 r195878  
     12016-01-29  Keith Miller  <keith_miller@apple.com>
     2
     3        Array.prototype native functions should use Symbol.species to construct the result
     4        https://bugs.webkit.org/show_bug.cgi?id=153660
     5
     6        Reviewed by Saam Barati.
     7
     8        This patch adds support for Symbol.species in the Array.prototype native functions.
     9        We make an optimization to avoid regressions on some benchmarks by using an
     10        adaptive watchpoint to check if Array.prototype.constructor is ever changed.
     11
     12        * runtime/ArrayPrototype.cpp:
     13        (JSC::putLength):
     14        (JSC::setLength):
     15        (JSC::speciesConstructArray):
     16        (JSC::arrayProtoFuncConcat):
     17        (JSC::arrayProtoFuncSlice):
     18        (JSC::arrayProtoFuncSplice):
     19        (JSC::ArrayPrototype::setConstructor):
     20        (JSC::ArrayPrototypeAdaptiveInferredPropertyWatchpoint::ArrayPrototypeAdaptiveInferredPropertyWatchpoint):
     21        (JSC::ArrayPrototypeAdaptiveInferredPropertyWatchpoint::handleFire):
     22        * runtime/ArrayPrototype.h:
     23        (JSC::ArrayPrototype::didChangeConstructorProperty):
     24        * runtime/ConstructData.cpp:
     25        (JSC::construct):
     26        * runtime/ConstructData.h:
     27        * runtime/JSGlobalObject.cpp:
     28        (JSC::JSGlobalObject::init):
     29        * tests/es6.yaml:
     30        * tests/stress/array-species-functions.js: Added.
     31        (Symbol.species):
     32        (funcThrows):
     33        (test.species):
     34        (test):
     35
    1362016-01-29  Filip Pizlo  <fpizlo@apple.com>
    237
  • trunk/Source/JavaScriptCore/runtime/ArrayPrototype.cpp

    r195528 r195878  
    2525#include "ArrayPrototype.h"
    2626
     27#include "AdaptiveInferredPropertyValueWatchpointBase.h"
     28#include "ArrayConstructor.h"
    2729#include "BuiltinNames.h"
    2830#include "ButterflyInlines.h"
     
    156158}
    157159
    158 static void putLength(ExecState* exec, JSObject* obj, JSValue value)
     160static ALWAYS_INLINE void putLength(ExecState* exec, JSObject* obj, JSValue value)
    159161{
    160162    PutPropertySlot slot(obj);
    161163    obj->methodTable()->put(obj, exec, exec->propertyNames().length, value, slot);
     164}
     165
     166static ALWAYS_INLINE void setLength(ExecState* exec, JSObject* obj, unsigned value)
     167{
     168    if (isJSArray(obj))
     169        jsCast<JSArray*>(obj)->setLength(exec, value);
     170    putLength(exec, obj, jsNumber(value));
     171}
     172
     173enum class SpeciesConstructResult {
     174    FastPath,
     175    Exception,
     176    CreatedObject
     177};
     178
     179static ALWAYS_INLINE std::pair<SpeciesConstructResult, JSObject*> speciesConstructArray(ExecState* exec, JSObject* thisObject, unsigned length)
     180{
     181    // ECMA 9.4.2.3: https://tc39.github.io/ecma262/#sec-arrayspeciescreate
     182    JSValue constructor = jsUndefined();
     183    if (LIKELY(isJSArray(thisObject))) {
     184        // Fast path in the normal case where the user has not set an own constructor and the Array.prototype.constructor is normal.
     185        // We need prototype check for subclasses of Array, which are Array objects but have a different prototype by default.
     186        if (LIKELY(!thisObject->hasCustomProperties()
     187            && thisObject->globalObject()->arrayPrototype() == thisObject->prototype()
     188            && !thisObject->globalObject()->arrayPrototype()->didChangeConstructorProperty()))
     189            return std::make_pair(SpeciesConstructResult::FastPath, nullptr);
     190
     191        constructor = thisObject->get(exec, exec->propertyNames().constructor);
     192        if (exec->hadException())
     193            return std::make_pair(SpeciesConstructResult::Exception, nullptr);
     194        if (constructor.isConstructor()) {
     195            JSObject* constructorObject = jsCast<JSObject*>(constructor);
     196            if (exec->lexicalGlobalObject() != constructorObject->globalObject())
     197                return std::make_pair(SpeciesConstructResult::FastPath, nullptr);;
     198        }
     199        if (constructor.isObject()) {
     200            constructor = constructor.get(exec, exec->propertyNames().speciesSymbol);
     201            if (exec->hadException())
     202                return std::make_pair(SpeciesConstructResult::Exception, nullptr);
     203            if (constructor.isNull())
     204                return std::make_pair(SpeciesConstructResult::FastPath, nullptr);;
     205        }
     206    }
     207    if (constructor.isUndefined())
     208        return std::make_pair(SpeciesConstructResult::FastPath, nullptr);;
     209
     210    MarkedArgumentBuffer args;
     211    args.append(jsNumber(length));
     212    JSObject* newObject = construct(exec, constructor, args, "Species construction did not get a valid constructor");
     213    if (exec->hadException())
     214        return std::make_pair(SpeciesConstructResult::Exception, nullptr);
     215    return std::make_pair(SpeciesConstructResult::CreatedObject, newObject);
    162216}
    163217
     
    533587    Checked<unsigned, RecordOverflow> finalArraySize = 0;
    534588
     589    // We need to do species construction before geting the rest of the elements.
     590    std::pair<SpeciesConstructResult, JSObject*> speciesResult = speciesConstructArray(exec, curArg.getObject(), 0);
     591    if (speciesResult.first == SpeciesConstructResult::Exception)
     592        return JSValue::encode(jsUndefined());
     593
    535594    JSArray* currentArray = nullptr;
    536595    JSArray* previousArray = nullptr;
     
    553612        return JSValue::encode(throwOutOfMemoryError(exec));
    554613
    555     if (argCount == 1 && previousArray && currentArray && finalArraySize.unsafeGet() < MIN_SPARSE_ARRAY_INDEX) {
     614    if (speciesResult.first == SpeciesConstructResult::FastPath && argCount == 1 && previousArray && currentArray && finalArraySize.unsafeGet() < MIN_SPARSE_ARRAY_INDEX) {
    556615        IndexingType type = JSArray::fastConcatType(exec->vm(), *previousArray, *currentArray);
    557616        if (type != NonArray)
     
    559618    }
    560619
    561     JSArray* arr = constructEmptyArray(exec, nullptr, finalArraySize.unsafeGet());
    562     if (exec->hadException())
    563         return JSValue::encode(jsUndefined());
     620    ASSERT(speciesResult.first != SpeciesConstructResult::Exception);
     621
     622    JSObject* result;
     623    if (speciesResult.first == SpeciesConstructResult::CreatedObject)
     624        result = speciesResult.second;
     625    else {
     626        // We add the newTarget because the compiler gets confused between 0 being a number and a pointer.
     627        result = constructEmptyArray(exec, nullptr, 0, JSValue());
     628    }
    564629
    565630    curArg = thisValue.toObject(exec);
     
    576641                    return JSValue::encode(jsUndefined());
    577642                if (v)
    578                     arr->putDirectIndex(exec, n, v);
     643                    result->putDirectIndex(exec, n, v);
    579644                n++;
    580645            }
    581646        } else {
    582             arr->putDirectIndex(exec, n, curArg);
     647            result->putDirectIndex(exec, n, curArg);
    583648            n++;
    584649        }
     
    587652        curArg = exec->uncheckedArgument(i);
    588653    }
    589     arr->setLength(exec, n);
    590     return JSValue::encode(arr);
     654    setLength(exec, result, n);
     655    return JSValue::encode(result);
    591656}
    592657
     
    758823    unsigned end = argumentClampedIndexFromStartOrEnd(exec, 1, length, length);
    759824
    760     if (isJSArray(thisObj)) {
     825    std::pair<SpeciesConstructResult, JSObject*> speciesResult = speciesConstructArray(exec, thisObj, end - begin);
     826    // We can only get an exception if we call some user function.
     827    if (UNLIKELY(speciesResult.first == SpeciesConstructResult::Exception))
     828        return JSValue::encode(jsUndefined());
     829
     830    if (LIKELY(speciesResult.first == SpeciesConstructResult::FastPath && isJSArray(thisObj))) {
    761831        if (JSArray* result = asArray(thisObj)->fastSlice(*exec, begin, end - begin))
    762832            return JSValue::encode(result);
    763833    }
    764834
    765     JSArray* result = constructEmptyArray(exec, nullptr, end - begin);
     835    JSObject* result;
     836    if (speciesResult.first == SpeciesConstructResult::CreatedObject)
     837        result = speciesResult.second;
     838    else
     839        result = constructEmptyArray(exec, nullptr, end - begin);
    766840
    767841    unsigned n = 0;
     
    773847            result->putDirectIndex(exec, n, v);
    774848    }
    775     result->setLength(exec, n);
     849    setLength(exec, result, n);
    776850    return JSValue::encode(result);
    777851}
     
    787861    if (exec->hadException())
    788862        return JSValue::encode(jsUndefined());
    789    
    790     if (!exec->argumentCount())
    791         return JSValue::encode(constructEmptyArray(exec, nullptr));
     863
     864    if (!exec->argumentCount()) {
     865        std::pair<SpeciesConstructResult, JSObject*> speciesResult = speciesConstructArray(exec, thisObj, 0);
     866        if (speciesResult.first == SpeciesConstructResult::Exception)
     867            return JSValue::encode(jsUndefined());
     868
     869        JSObject* result;
     870        if (speciesResult.first == SpeciesConstructResult::CreatedObject)
     871            result = speciesResult.second;
     872        else
     873            result = constructEmptyArray(exec, nullptr);
     874
     875        setLength(exec, result, 0);
     876        return JSValue::encode(result);
     877    }
    792878
    793879    unsigned begin = argumentClampedIndexFromStartOrEnd(exec, 0, length);
     
    804890    }
    805891
    806     JSArray* result = nullptr;
    807 
    808     if (isJSArray(thisObj))
     892    std::pair<SpeciesConstructResult, JSObject*> speciesResult = speciesConstructArray(exec, thisObj, deleteCount);
     893    if (speciesResult.first == SpeciesConstructResult::Exception)
     894        return JSValue::encode(jsUndefined());
     895
     896    JSObject* result = nullptr;
     897    if (speciesResult.first == SpeciesConstructResult::FastPath && isJSArray(thisObj))
    809898        result = asArray(thisObj)->fastSlice(*exec, begin, deleteCount);
    810899
    811900    if (!result) {
    812         result = JSArray::tryCreateUninitialized(vm, exec->lexicalGlobalObject()->arrayStructureForIndexingTypeDuringAllocation(ArrayWithUndecided), deleteCount);
    813         if (!result)
    814             return JSValue::encode(throwOutOfMemoryError(exec));
     901        if (speciesResult.first == SpeciesConstructResult::CreatedObject)
     902            result = speciesResult.second;
     903        else {
     904            result = JSArray::tryCreateUninitialized(vm, exec->lexicalGlobalObject()->arrayStructureForIndexingTypeDuringAllocation(ArrayWithUndecided), deleteCount);
     905            if (!result)
     906                return JSValue::encode(throwOutOfMemoryError(exec));
     907        }
    815908
    816909        for (unsigned k = 0; k < deleteCount; ++k) {
     
    838931    }
    839932
    840     putLength(exec, thisObj, jsNumber(length - deleteCount + additionalArgs));
     933    setLength(exec, thisObj, length - deleteCount + additionalArgs);
    841934    return JSValue::encode(result);
    842935}
     
    9441037}
    9451038
     1039// -------------------- ArrayPrototype.constructor Watchpoint ------------------
     1040
     1041class ArrayPrototypeAdaptiveInferredPropertyWatchpoint : public AdaptiveInferredPropertyValueWatchpointBase {
     1042public:
     1043    typedef AdaptiveInferredPropertyValueWatchpointBase Base;
     1044    ArrayPrototypeAdaptiveInferredPropertyWatchpoint(const ObjectPropertyCondition&, ArrayPrototype*);
     1045
     1046private:
     1047    virtual void handleFire(const FireDetail&) override;
     1048
     1049    ArrayPrototype* m_arrayPrototype;
     1050};
     1051
     1052void ArrayPrototype::setConstructor(VM& vm, JSObject* constructorProperty, unsigned attributes)
     1053{
     1054    putDirectWithoutTransition(vm, vm.propertyNames->constructor, constructorProperty, attributes);
     1055
     1056    PropertyOffset offset = this->structure()->get(vm, vm.propertyNames->constructor);
     1057    ASSERT(isValidOffset(offset));
     1058    this->structure()->startWatchingPropertyForReplacements(vm, offset);
     1059
     1060    ObjectPropertyCondition condition = ObjectPropertyCondition::equivalence(vm, this, this, vm.propertyNames->constructor.impl(), constructorProperty);
     1061    ASSERT(condition.isWatchable());
     1062
     1063    m_constructorWatchpoint = std::make_unique<ArrayPrototypeAdaptiveInferredPropertyWatchpoint>(condition, this);
     1064    m_constructorWatchpoint->install();
     1065}
     1066
     1067ArrayPrototypeAdaptiveInferredPropertyWatchpoint::ArrayPrototypeAdaptiveInferredPropertyWatchpoint(const ObjectPropertyCondition& key, ArrayPrototype* prototype)
     1068    : Base(key)
     1069    , m_arrayPrototype(prototype)
     1070{
     1071}
     1072
     1073void ArrayPrototypeAdaptiveInferredPropertyWatchpoint::handleFire(const FireDetail& detail)
     1074{
     1075    StringPrintStream out;
     1076    out.print("ArrayPrototype adaption of ", key(), " failed: ", detail);
     1077
     1078    StringFireDetail stringDetail(out.toCString().data());
     1079
     1080    m_arrayPrototype->m_didChangeConstructorProperty = true;
     1081}
     1082
    9461083} // namespace JSC
  • trunk/Source/JavaScriptCore/runtime/ArrayPrototype.h

    r190429 r195878  
    2727namespace JSC {
    2828
     29class ArrayPrototypeAdaptiveInferredPropertyWatchpoint;
     30
    2931class ArrayPrototype : public JSArray {
    3032private:
     
    4345    }
    4446
     47    void setConstructor(VM&, JSObject* constructorProperty, unsigned attributes);
     48
     49    bool didChangeConstructorProperty() const { return m_didChangeConstructorProperty; }
     50
    4551protected:
    4652    void finishCreation(VM&, JSGlobalObject*);
     53
     54private:
     55    // This bit is set if any user modifies the constructor property Array.prototype. This is used to optimize species creation for JSArrays.
     56    friend ArrayPrototypeAdaptiveInferredPropertyWatchpoint;
     57    std::unique_ptr<ArrayPrototypeAdaptiveInferredPropertyWatchpoint> m_constructorWatchpoint;
     58    bool m_didChangeConstructorProperty = false;
    4759};
    4860
  • trunk/Source/JavaScriptCore/runtime/ConstructData.cpp

    r194242 r195878  
    3636namespace JSC {
    3737
     38JSObject* construct(ExecState* exec, JSValue constructorObject, const ArgList& args, const String& errorMessage)
     39{
     40    ConstructData constructData;
     41    ConstructType constructType = getConstructData(constructorObject, constructData);
     42    if (constructType == ConstructTypeNone)
     43        return throwTypeError(exec, errorMessage);
     44
     45    return construct(exec, constructorObject, constructType, constructData, args, constructorObject);
     46}
     47
     48
    3849JSObject* construct(ExecState* exec, JSValue constructorObject, ConstructType constructType, const ConstructData& constructData, const ArgList& args, JSValue newTarget)
    3950{
  • trunk/Source/JavaScriptCore/runtime/ConstructData.h

    r194242 r195878  
    5757};
    5858
     59// Convenience wrapper so you don't need to deal with CallData and CallType unless you are going to use them.
     60JSObject* construct(ExecState*, JSValue functionObject, const ArgList&, const String& errorMessage);
    5961JS_EXPORT_PRIVATE JSObject* construct(ExecState*, JSValue constructor, ConstructType, const ConstructData&, const ArgList&, JSValue newTarget);
    6062
  • trunk/Source/JavaScriptCore/runtime/JSCJSValue.h

    r194175 r195878  
    220220    bool isEmpty() const;
    221221    bool isFunction() const;
     222    bool isConstructor() const;
    222223    bool isUndefined() const;
    223224    bool isNull() const;
  • trunk/Source/JavaScriptCore/runtime/JSCJSValueInlines.h

    r194996 r195878  
    683683}
    684684
     685// FIXME: We could do this in a smarter way. See: https://bugs.webkit.org/show_bug.cgi?id=153670
     686inline bool JSValue::isConstructor() const
     687{
     688    if (isFunction()) {
     689        ConstructData data;
     690        return getConstructData(*this, data) != ConstructTypeNone;
     691    }
     692    return false;
     693}
     694
    685695// this method is here to be after the inline declaration of JSCell::inherits
    686696inline bool JSValue::inherits(const ClassInfo* classInfo) const
  • trunk/Source/JavaScriptCore/runtime/JSGlobalObject.cpp

    r195799 r195878  
    405405
    406406    JSCell* functionConstructor = FunctionConstructor::create(vm, FunctionConstructor::createStructure(vm, this, m_functionPrototype.get()), m_functionPrototype.get());
    407     JSCell* arrayConstructor = ArrayConstructor::create(vm, ArrayConstructor::createStructure(vm, this, m_functionPrototype.get()), m_arrayPrototype.get(), speciesGetterSetter);
     407    JSObject* arrayConstructor = ArrayConstructor::create(vm, ArrayConstructor::createStructure(vm, this, m_functionPrototype.get()), m_arrayPrototype.get(), speciesGetterSetter);
    408408   
    409409    m_regExpConstructor.set(vm, this, RegExpConstructor::create(vm, RegExpConstructor::createStructure(vm, this, m_functionPrototype.get()), m_regExpPrototype.get(), speciesGetterSetter));
     
    440440    m_objectPrototype->putDirectWithoutTransition(vm, vm.propertyNames->constructor, objectConstructor, DontEnum);
    441441    m_functionPrototype->putDirectWithoutTransition(vm, vm.propertyNames->constructor, functionConstructor, DontEnum);
    442     m_arrayPrototype->putDirectWithoutTransition(vm, vm.propertyNames->constructor, arrayConstructor, DontEnum);
     442    m_arrayPrototype->setConstructor(vm, arrayConstructor, DontEnum);
    443443    m_regExpPrototype->putDirectWithoutTransition(vm, vm.propertyNames->constructor, m_regExpConstructor.get(), DontEnum);
    444444   
  • trunk/Source/JavaScriptCore/tests/es6.yaml

    r195581 r195878  
    718718  cmd: runES6 :normal
    719719- path: es6/Array_is_subclassable_Array.prototype.concat.js
    720   cmd: runES6 :fail
     720  cmd: runES6 :normal
    721721- path: es6/Array_is_subclassable_Array.prototype.filter.js
    722722  cmd: runES6 :fail
     
    724724  cmd: runES6 :fail
    725725- path: es6/Array_is_subclassable_Array.prototype.slice.js
    726   cmd: runES6 :fail
     726  cmd: runES6 :normal
    727727- path: es6/Array_is_subclassable_Array.prototype.splice.js
    728   cmd: runES6 :fail
     728  cmd: runES6 :normal
    729729- path: es6/Array_is_subclassable_correct_prototype_chain.js
    730730  cmd: runES6 :normal
     
    11961196  cmd: runES6 :fail
    11971197- path: es6/well-known_symbols_Symbol.species_Array.prototype.concat.js
    1198   cmd: runES6 :fail
     1198  cmd: runES6 :normal
    11991199- path: es6/well-known_symbols_Symbol.species_Array.prototype.filter.js
    12001200  cmd: runES6 :fail
     
    12021202  cmd: runES6 :fail
    12031203- path: es6/well-known_symbols_Symbol.species_Array.prototype.slice.js
    1204   cmd: runES6 :fail
     1204  cmd: runES6 :normal
    12051205- path: es6/well-known_symbols_Symbol.species_Array.prototype.splice.js
    1206   cmd: runES6 :fail
     1206  cmd: runES6 :normal
    12071207- path: es6/well-known_symbols_Symbol.species_existence.js
    12081208  cmd: runES6 :normal
Note: See TracChangeset for help on using the changeset viewer.