Bug 211279 - Merge putLength() into setLength()
Summary: Merge putLength() into setLength()
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: JavaScriptCore (show other bugs)
Version: WebKit Nightly Build
Hardware: All All
: P2 Enhancement
Assignee: Alexey Shvayka
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2020-04-30 23:04 PDT by Saam Barati
Modified: 2020-08-26 18:56 PDT (History)
16 users (show)

See Also:


Attachments
Patch (9.70 KB, patch)
2020-08-26 04:03 PDT, Alexey Shvayka
darin: review+
Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Saam Barati 2020-04-30 23:04:32 PDT
Seems superfluous and costly to do the method table dispatch.
Comment 1 Alexey Shvayka 2020-08-26 04:03:22 PDT
Created attachment 407289 [details]
Patch
Comment 2 Alexey Shvayka 2020-08-26 04:05:19 PDT
(In reply to Alexey Shvayka from comment #1)
> Created attachment 407289 [details]
> Patch

Warmed-up runs, --outer 50:

                                    r266157                   patch

array-shift-unshift-empty       64.8823+-1.0893     ^     45.5567+-1.4411        ^ definitely 1.4242x faster
Comment 3 Saam Barati 2020-08-26 08:37:35 PDT
Comment on attachment 407289 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=407289&action=review

> Source/JavaScriptCore/runtime/ArrayPrototype.cpp:874
> +    JSValue result = thisObj->get(globalObject, index);

Isn’t the whole point of the previous code that we need the Identifier when at the MAX_ARRAY_INDEX boundary?
Comment 4 Darin Adler 2020-08-26 11:53:08 PDT
Comment on attachment 407289 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=407289&action=review

Ah, the runtime. One area of JavaScriptCore that I still have enough expertise in that I can review.

> Source/JavaScriptCore/runtime/ArrayPrototype.cpp:166
> +    if (LIKELY(isJSArray(obj))) {

Obviously this *is* likely, but I do like to follow the principle of only adding these when we know they’re beneficial so not 100% sure I would have added it. Hard to argue against it strongly, though.

>> Source/JavaScriptCore/runtime/ArrayPrototype.cpp:874
>> +    JSValue result = thisObj->get(globalObject, index);
> 
> Isn’t the whole point of the previous code that we need the Identifier when at the MAX_ARRAY_INDEX boundary?

Perhaps, but since Object::get(JSObject*, uint64_t) takes care of creating the identifier when it’s needed, this code will work properly without separate branches for valid array index values and other values. Object::deleteProperty(JSGlobalObject*, uint64_t) also does. A bit unfortunate that the two functions will do the work to make the Identifier string twice, but I suppose it’s an exotic case that we don’t need to optimize carefully. Similarly, we will end up calling jsNumber on the same number repeatedly, which would waste a bit of heap, but again only in exotic cases.
Comment 5 Saam Barati 2020-08-26 12:43:31 PDT
Comment on attachment 407289 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=407289&action=review

r=me too

>>> Source/JavaScriptCore/runtime/ArrayPrototype.cpp:874
>>> +    JSValue result = thisObj->get(globalObject, index);
>> 
>> Isn’t the whole point of the previous code that we need the Identifier when at the MAX_ARRAY_INDEX boundary?
> 
> Perhaps, but since Object::get(JSObject*, uint64_t) takes care of creating the identifier when it’s needed, this code will work properly without separate branches for valid array index values and other values. Object::deleteProperty(JSGlobalObject*, uint64_t) also does. A bit unfortunate that the two functions will do the work to make the Identifier string twice, but I suppose it’s an exotic case that we don’t need to optimize carefully. Similarly, we will end up calling jsNumber on the same number repeatedly, which would waste a bit of heap, but again only in exotic cases.

Ah! I forgot I added the uint64_t version of this method. I thought we were implicit casting to the uint32_t version and was worried.

jsNumber on the same number repeatedly is cheap, and doesn't allocate anything in the heap.
Comment 6 Darin Adler 2020-08-26 14:14:03 PDT
Comment on attachment 407289 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=407289&action=review

>>>> Source/JavaScriptCore/runtime/ArrayPrototype.cpp:874
>>>> +    JSValue result = thisObj->get(globalObject, index);
>>> 
>>> Isn’t the whole point of the previous code that we need the Identifier when at the MAX_ARRAY_INDEX boundary?
>> 
>> Perhaps, but since Object::get(JSObject*, uint64_t) takes care of creating the identifier when it’s needed, this code will work properly without separate branches for valid array index values and other values. Object::deleteProperty(JSGlobalObject*, uint64_t) also does. A bit unfortunate that the two functions will do the work to make the Identifier string twice, but I suppose it’s an exotic case that we don’t need to optimize carefully. Similarly, we will end up calling jsNumber on the same number repeatedly, which would waste a bit of heap, but again only in exotic cases.
> 
> Ah! I forgot I added the uint64_t version of this method. I thought we were implicit casting to the uint32_t version and was worried.
> 
> jsNumber on the same number repeatedly is cheap, and doesn't allocate anything in the heap.

Oh, right, they all get converted to double, and a double always fits in a double. Some values lose precision but they all fit.

When we added the uint64_t version it did create one subtle poor design thing. The 32-bit version has a required precondition that the number is an array index; you must not pass it the value 0xFFFFFFFF. The 64-bit version does not have that precondition, nor do other overloads. Although maybe there is an Identifier one that you must not call with a string that just happens to be a serialized array index. Requiring one number be avoided a strange difference to based only on the passed integer’s width. So I might have introduced a difference in name by renaming the 32-bit integer functions.
Comment 7 Saam Barati 2020-08-26 14:44:57 PDT
(In reply to Darin Adler from comment #6)
> Comment on attachment 407289 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=407289&action=review
> 
> >>>> Source/JavaScriptCore/runtime/ArrayPrototype.cpp:874
> >>>> +    JSValue result = thisObj->get(globalObject, index);
> >>> 
> >>> Isn’t the whole point of the previous code that we need the Identifier when at the MAX_ARRAY_INDEX boundary?
> >> 
> >> Perhaps, but since Object::get(JSObject*, uint64_t) takes care of creating the identifier when it’s needed, this code will work properly without separate branches for valid array index values and other values. Object::deleteProperty(JSGlobalObject*, uint64_t) also does. A bit unfortunate that the two functions will do the work to make the Identifier string twice, but I suppose it’s an exotic case that we don’t need to optimize carefully. Similarly, we will end up calling jsNumber on the same number repeatedly, which would waste a bit of heap, but again only in exotic cases.
> > 
> > Ah! I forgot I added the uint64_t version of this method. I thought we were implicit casting to the uint32_t version and was worried.
> > 
> > jsNumber on the same number repeatedly is cheap, and doesn't allocate anything in the heap.
> 
> Oh, right, they all get converted to double, and a double always fits in a
> double. Some values lose precision but they all fit.
> 
> When we added the uint64_t version it did create one subtle poor design
> thing. The 32-bit version has a required precondition that the number is an
> array index; you must not pass it the value 0xFFFFFFFF. The 64-bit version
> does not have that precondition, nor do other overloads. Although maybe
> there is an Identifier one that you must not call with a string that just
> happens to be a serialized array index. Requiring one number be avoided a
> strange difference to based only on the passed integer’s width. So I might
> have introduced a difference in name by renaming the 32-bit integer
> functions.

We could rename them. Or invent a new type to pass in as a wrapper to the index. Something like ArrayIndex, and it asserts the value is in range, and it's not implicitly constructible.
Comment 8 Darin Adler 2020-08-26 14:50:23 PDT
(In reply to Saam Barati from comment #7)
> We could rename them. Or invent a new type to pass in as a wrapper to the
> index. Something like ArrayIndex, and it asserts the value is in range, and
> it's not implicitly constructible.

We are thinking similarly: I literally considered that option too when writing the comment above, but thought it was too complicated to explain so left it out.
Comment 9 Saam Barati 2020-08-26 14:53:43 PDT
(In reply to Darin Adler from comment #8)
> (In reply to Saam Barati from comment #7)
> > We could rename them. Or invent a new type to pass in as a wrapper to the
> > index. Something like ArrayIndex, and it asserts the value is in range, and
> > it's not implicitly constructible.
> 
> We are thinking similarly: I literally considered that option too when
> writing the comment above, but thought it was too complicated to explain so
> left it out.

I think the right way to do this is to change those helper methods, but also to change the method table methods of:
- putByIndex
- deletePropertyByIndex
- getOwnPropertySlotByIndex
Comment 10 Saam Barati 2020-08-26 14:55:40 PDT
(In reply to Saam Barati from comment #9)
> (In reply to Darin Adler from comment #8)
> > (In reply to Saam Barati from comment #7)
> > > We could rename them. Or invent a new type to pass in as a wrapper to the
> > > index. Something like ArrayIndex, and it asserts the value is in range, and
> > > it's not implicitly constructible.
> > 
> > We are thinking similarly: I literally considered that option too when
> > writing the comment above, but thought it was too complicated to explain so
> > left it out.
> 
> I think the right way to do this is to change those helper methods, but also
> to change the method table methods of:
> - putByIndex
> - deletePropertyByIndex
> - getOwnPropertySlotByIndex

I might actually be wrong. Reading some more code.
Comment 11 Saam Barati 2020-08-26 15:01:54 PDT
(In reply to Darin Adler from comment #6)
> Comment on attachment 407289 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=407289&action=review
> 
> >>>> Source/JavaScriptCore/runtime/ArrayPrototype.cpp:874
> >>>> +    JSValue result = thisObj->get(globalObject, index);
> >>> 
> >>> Isn’t the whole point of the previous code that we need the Identifier when at the MAX_ARRAY_INDEX boundary?
> >> 
> >> Perhaps, but since Object::get(JSObject*, uint64_t) takes care of creating the identifier when it’s needed, this code will work properly without separate branches for valid array index values and other values. Object::deleteProperty(JSGlobalObject*, uint64_t) also does. A bit unfortunate that the two functions will do the work to make the Identifier string twice, but I suppose it’s an exotic case that we don’t need to optimize carefully. Similarly, we will end up calling jsNumber on the same number repeatedly, which would waste a bit of heap, but again only in exotic cases.
> > 
> > Ah! I forgot I added the uint64_t version of this method. I thought we were implicit casting to the uint32_t version and was worried.
> > 
> > jsNumber on the same number repeatedly is cheap, and doesn't allocate anything in the heap.
> 
> Oh, right, they all get converted to double, and a double always fits in a
> double. Some values lose precision but they all fit.
> 
> When we added the uint64_t version it did create one subtle poor design
> thing. The 32-bit version has a required precondition that the number is an
> array index; you must not pass it the value 0xFFFFFFFF. The 64-bit version
> does not have that precondition, nor do other overloads. Although maybe
> there is an Identifier one that you must not call with a string that just
> happens to be a serialized array index. Requiring one number be avoided a
> strange difference to based only on the passed integer’s width. So I might
> have introduced a difference in name by renaming the 32-bit integer
> functions.

After reading some code, it doesn't appear that there are restrictions on the range of the unsigned passed into these methods. For example, 0xFFFFFFFF should be handled just fine:

```
    ALWAYS_INLINE bool putByIndexInline(JSGlobalObject* globalObject, unsigned propertyName, JSValue value, bool shouldThrow)
    {
        VM& vm = getVM(globalObject);
        if (canSetIndexQuickly(propertyName, value)) {
            setIndexQuickly(vm, propertyName, value);
            return true;
        }
        return methodTable(vm)->putByIndex(this, globalObject, propertyName, value, shouldThrow);
    }
```

canSetIndexQuickly for 0xFFFFFFFF should return false. This will lead us to taking the putByIndex path. Let's look at one example implementation:

```
bool JSObject::putByIndex(JSCell* cell, JSGlobalObject* globalObject, unsigned propertyName, JSValue value, bool shouldThrow)
{
    VM& vm = globalObject->vm();
    JSObject* thisObject = jsCast<JSObject*>(cell);

    if (propertyName > MAX_ARRAY_INDEX) {
        PutPropertySlot slot(cell, shouldThrow);
        return thisObject->methodTable(vm)->put(thisObject, globalObject, Identifier::from(vm, propertyName), value, slot);
    }
...
...
```

So it's expected that the various method table methods I listed properly handle all ranges of values passed into the various byIndex MethodTable methods.
Comment 12 Darin Adler 2020-08-26 15:09:11 PDT
(In reply to Saam Barati from comment #11)
> it doesn't appear that there are restrictions on
> the range of the unsigned passed into these methods

OK, glad to hear it. Guess I remembered wrong. I’m pretty sure I’m the one who created these separate overloads so that integers could pass through the runtime without being converted to and from Identifier strings, but I must have forgotten some of the details over the years.
Comment 13 Saam Barati 2020-08-26 15:17:31 PDT
(In reply to Darin Adler from comment #12)
> (In reply to Saam Barati from comment #11)
> > it doesn't appear that there are restrictions on
> > the range of the unsigned passed into these methods
> 
> OK, glad to hear it. Guess I remembered wrong. I’m pretty sure I’m the one
> who created these separate overloads so that integers could pass through the
> runtime without being converted to and from Identifier strings, but I must
> have forgotten some of the details over the years.

Yeah, the code itself is a bit subtle, and I have to re-read the code to convince myself of its correctness. It took me a second, just now, to realize that our "isIndex" function and MAX_ARRAY_INDEX are indeed in sync. One nit I have with the code base is to rewrite isIndex in terms of MAX_ARRAY_INDEX, since we have code that relies on > MAX_ARRAY_INDEX having "isIndex" return false. Currently, we just hard code numbers in two different places.
Comment 14 Darin Adler 2020-08-26 15:27:14 PDT
(In reply to Saam Barati from comment #13)
> One nit I have with the code base is to rewrite isIndex in terms of
> MAX_ARRAY_INDEX, since we have code that relies on > MAX_ARRAY_INDEX having
> "isIndex" return false. Currently, we just hard code numbers in two
> different places.

Compared to MAX_ARRAY_INDEX, isIndex is new. Yusuke added it in 2015:

https://trac.webkit.org/changeset/182452

MAX_ARRAY_INDEX is many years older and used many more places.
Comment 15 Saam Barati 2020-08-26 16:16:10 PDT
(In reply to Darin Adler from comment #14)
> (In reply to Saam Barati from comment #13)
> > One nit I have with the code base is to rewrite isIndex in terms of
> > MAX_ARRAY_INDEX, since we have code that relies on > MAX_ARRAY_INDEX having
> > "isIndex" return false. Currently, we just hard code numbers in two
> > different places.
> 
> Compared to MAX_ARRAY_INDEX, isIndex is new. Yusuke added it in 2015:
> 
> https://trac.webkit.org/changeset/182452
> 
> MAX_ARRAY_INDEX is many years older and used many more places.

Will do a small cleanup in:
https://bugs.webkit.org/show_bug.cgi?id=215872
Comment 16 Alexey Shvayka 2020-08-26 18:55:12 PDT
Committed r266215: <https://trac.webkit.org/changeset/266215>
Comment 17 Radar WebKit Bug Importer 2020-08-26 18:56:18 PDT
<rdar://problem/67843783>