implement Array.prototype.filter in javascript#5137
Conversation
zenparsing
left a comment
There was a problem hiding this comment.
JS implementation looks good.
|
|
||
| let kValue = o[k]; | ||
| if (boundCallback(kValue, k, o)) | ||
| { |
There was a problem hiding this comment.
nit: curly should probably go on previous line.
meg-gupta
left a comment
There was a problem hiding this comment.
LGTM. Would be good to run test262.
|
|
||
| let o; | ||
| let len | ||
| if (__chakraLibrary.isArray(this)) { |
There was a problem hiding this comment.
why not do Array.isArray()?
There was a problem hiding this comment.
This calls the same function, but from a safe location so that we are unaffected if the user replaces Array.isArray
There was a problem hiding this comment.
| Var JsBuiltInEngineInterfaceExtensionObject::EntryJsBuiltIn_Internal_ArraySpeciesCreate(RecyclableObject *function, CallInfo callInfo, ...) | ||
| { | ||
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); | ||
| Assert(args.Info.Count == 3); |
There was a problem hiding this comment.
Assert [](start = 8, length = 6)
AssertOfFailFast
There was a problem hiding this comment.
I chose a normal Assert because the function is callable only through the JS built-ins and not in any user controlled way. If you think it needs to be a FailFast I'll change it.
There was a problem hiding this comment.
It should be better be failfast it is just a check.
In reply to: 187739964 [](ancestors = 187739964)
|
|
||
| if (newObj == nullptr) | ||
| { | ||
| newArr = scriptContext->GetLibrary()->CreateArray(0); |
There was a problem hiding this comment.
0 [](start = 62, length = 1)
should this be args.Values[2]?
There was a problem hiding this comment.
yeah, it should be. thanks
| len = o.length; | ||
| } else { | ||
| o = __chakraLibrary.Object(this); | ||
| len = __chakraLibrary.GetLenth(o); |
There was a problem hiding this comment.
GetLenth [](start = 34, length = 8)
+g
| let kValue = o[k]; | ||
| if (boundCallback(kValue, k, o)) | ||
| { | ||
| a[to] = kValue; |
There was a problem hiding this comment.
this is CreateDataPropertyOrThrow? will it perform the same as the spec wants it to?
There was a problem hiding this comment.
I believe this will, but @zenparsing can hopefully confirm that.
There was a problem hiding this comment.
@akroshg Good call, it's not quite the same. Assignment will utilize the prototype chain, whereas filter creates a data property directly on the output array.
Object.defineProperty(Array.prototype, '0', {
get() { return 980 },
set(value) { console.log('called setter') },
});
// Does not log "called setter"
[1].filter(() => true);
// Logs "called setter"
let a = [];
a[0] = 1;There was a problem hiding this comment.
I think the best course of action here is to use the CreateDataPropertyOrThrow in the case when species returns anything other than an Array.
In reply to: 187750568 [](ancestors = 187750568)
| let boundCallback = callbackfn; | ||
| if (thisArg === undefined) { | ||
| boundCallback = callbackfn; | ||
| } else { |
There was a problem hiding this comment.
should we remove the if part, as you have initialized boundCallback already?
| var errorBuiltIn = "Assignment to read-only properties is not allowed in strict mode" | ||
| assert.throws(function () { Array.prototype.map.call(arr, function (a) { return a;}); }, TypeError, desc + "map", error); | ||
| assert.throws(function () { Array.prototype.filter.call(arr, function (a) { return true;}); }, TypeError,desc + "filter", error); | ||
| assert.throws(function () { Array.prototype.filter.call(arr, function (a) { return true;}); }, TypeError,desc + "filter", errorBuiltIn); |
There was a problem hiding this comment.
this shouldn't change, no? This may be happening due to CreateDataPropertyOrThrow in not there?
| return obj; | ||
| } | ||
|
|
||
| Var JsBuiltInEngineInterfaceExtensionObject::EntryJsBuiltIn_Internal_ArraySpeciesCreate(RecyclableObject *function, CallInfo callInfo, ...) |
There was a problem hiding this comment.
future todo : I think ArraySpeciesCreate should be implemented entirely in javascript. May be in future releases
|
I believe the description should stats what is the motivation behind the change. Please share improvements on the micro-benchmark (crafted filter scenario should be fine). |
| } | ||
| } | ||
|
|
||
| return newObj; |
There was a problem hiding this comment.
Should this be returning newArr? if not, whats the point of newArr?
There was a problem hiding this comment.
This code is essentially copied from JavascriptArray::FilterHelper. I don't have a good understanding of under what circumstances ArraySpeciesCreate would return a JavascriptCopyOnAccessNativeIntArray that needs conversion so I left that part of the logic the same. On closer inspection though, yeah, newArr is redundant and unnecessary.
| __chakraLibrary.raiseThis_NullOrUndefined = platform.raiseThis_NullOrUndefined; | ||
| __chakraLibrary.raiseFunctionArgument_NeedFunction = platform.raiseFunctionArgument_NeedFunction; | ||
| __chakraLibrary.callInstanceFunc = platform.builtInCallInstanceFunction; | ||
| __chakraLibrary.functionBind = platform.builtInJavascriptFunctionEntryBind; |
There was a problem hiding this comment.
What exactly is the goal here? Why does __chakraLibrary need these if you already have access to them through platform
There was a problem hiding this comment.
We don't allow access to any functions that are not on __chakraLibrary from within the built in functions.
There was a problem hiding this comment.
why not? Intl definitely doesn't have this restriction since it doesn't use __chakraLibrary at all.
| let to = 0; | ||
|
|
||
| // Binding: If thisArg is provided, we need to do the equivalent of callbackfn.call(thisArg, ...), but we cannot use .call because Function.prototype.call can be overriden. | ||
| // Unfortinately, locally recording the original Function.protoype.call would need to call originalCall.call(callbackfn, thisArg, ...), which has the same .call problem. |
There was a problem hiding this comment.
Unfortinately [](start = 11, length = 13)
nit: spelling.
| REM pushd %_reporoot%\lib\Runtime\Library\InJavascript | ||
| REM call GenByteCode.cmd | ||
| REM call GenByteCode.cmd -nojit | ||
| REM popd |
| if (k in o) { | ||
| let kValue = o[k]; | ||
| if (callbackfn(kValue, k, o)) { | ||
| a[to] = kValue; |
There was a problem hiding this comment.
Could you not still have an array that passes Array.isArray but has a non-writable property?
There was a problem hiding this comment.
I believe you are correct, at the very least through modifications of Array.prototype.
|
Thought on the slow path is it functionally equivalent to the fast path when in strict mode? Could there be a strict mode only optimisation to always use the fast path? Possibly blocked by: #5046 |
| if (k in o) { | ||
| let kValue = o[k]; | ||
| if (callbackfn(kValue, k, o)) { | ||
| a[to] = kValue; |
There was a problem hiding this comment.
this will still have the problem as Kevin mentioned before - it will call setter when we execute this, no?
There was a problem hiding this comment.
Yeah, I'm about to update the PR with a version that always calls the __chakraLibrary helper, but I'm running test262 and perf results for it first
d849073 to
1ff7409
Compare
Merge pull request #5137 from sigatrev:jsFilter Implementing built-ins in JavaScript allows the JIT to optimize calls that must otherwise behave generically. This can provide significant performance improvements, particularly for array operations that loop over the array. In a microbenchmark calling filter 1000 times on a 10000 element array, the JS implementation was 13x faster in the best case, and 20% faster in the worst case. ``` for (var i = 0; i < 1000; ++i) { a.filter(()=>false) // best case, array is never grown } // js: 30 ms // c++: 400ms for (var i = 0; i < 1000; ++i) { a.filter(()=>true) // worst case } // js: 550 ms // c++: 700 ms ``` Adding a JIT fast path for __chakraLibrary.arrayCreateDataPropertyOrThrow similar to normal array assignment would drop the always-true case to 230 ms.
Implementing built-ins in JavaScript allows the JIT to optimize calls that must otherwise behave generically. This can provide significant performance improvements, particularly for array operations that loop over the array.
In a microbenchmark calling filter 1000 times on a 10000 element array, the JS implementation was 13x faster in the best case, and 20% faster in the worst case.
Adding a JIT fast path for __chakraLibrary.arrayCreateDataPropertyOrThrow similar to normal array assignment would drop the always-true case to 230 ms.