-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
implement Array.prototype.filter in javascript #5137
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JS implementation looks good.
|
||
let kValue = o[k]; | ||
if (boundCallback(kValue, k, o)) | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: curly should probably go on previous line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Would be good to run test262.
|
||
let o; | ||
let len | ||
if (__chakraLibrary.isArray(this)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not do Array.isArray()?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assert [](start = 8, length = 6)
AssertOfFailFast
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0 [](start = 62, length = 1)
should this be args.Values[2]?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, it should be. thanks
} else { | ||
o = __chakraLibrary.Object(this); | ||
len = __chakraLibrary.GetLenth(o); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is CreateDataPropertyOrThrow? will it perform the same as the spec wants it to?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this will, but @zenparsing can hopefully confirm that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we remove the if part, as you have initialized boundCallback already?
test/es6/ES6ArrayAPI.js
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this shouldn't change, no? This may be happening due to CreateDataPropertyOrThrow in not there?
@@ -453,5 +460,34 @@ namespace Js | |||
return obj; | |||
} | |||
|
|||
Var JsBuiltInEngineInterfaceExtensionObject::EntryJsBuiltIn_Internal_ArraySpeciesCreate(RecyclableObject *function, CallInfo callInfo, ...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be returning newArr
? if not, whats the point of newArr
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
@@ -35,6 +36,9 @@ | |||
__chakraLibrary.ArrayIterator.prototype = CreateObject(iteratorPrototype); | |||
__chakraLibrary.raiseNeedObjectOfType = platform.raiseNeedObjectOfType; | |||
__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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortinately [](start = 11, length = 13)
nit: spelling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks
RegenAllByteCodeNoBuild.cmd
Outdated
REM pushd %_reporoot%\lib\Runtime\Library\InJavascript | ||
REM call GenByteCode.cmd | ||
REM call GenByteCode.cmd -nojit | ||
REM popd |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops?
if (k in o) { | ||
let kValue = o[k]; | ||
if (callbackfn(kValue, k, o)) { | ||
a[to] = kValue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you not still have an array that passes Array.isArray
but has a non-writable property?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.