-
-
Couldn't load subscription status.
- Fork 33.6k
Description
In this issue I've missed a case which is much more difficult to trace. That report has caused some fixes, so maybe this is also worth reporting.
However, some disclaimer is needed: I could get the theory or its application wrong here; I could miss or detect wrongly some cases; I am not sure if optimization is worth refactoring in each case. Moreover, spread is increasingly gaining speed now, so maybe upgrade to it is more worth efforts.
I. Definition
Leaking arguments is considered safe only within func.apply(context, arguments). However, this call site must be monomorphic. See the last warning in this chapter.
To be monomorphic, func must have the same hidden class. According to this comment, these functions have different hidden classes:
- function with an additional property / function without this property
- common function / bound function (via
bind()) - common function / arrow function
- strict mode function / sloppy mode function
So, each func.apply(context, arguments) fragment should be checked for func monomorphism.
II. Example of deoptimization
This is the code to compare two cases with monomorphic and polymorphic call sites. In the comment below the code, you can see the time difference and deoptimization warning.
III. Types of cases
If I get it right, there are three group of cases here (from the most dangerous to the least dangerous):
-
funcis an argument gotten from a userland via public API (directly or via a call chain). We can't guarantee monomorphism here (for example, a user can give both common and arrow functions as arguments). So maybe in these casesargumentsshould be copied before the call, as in many other lib places. -
funcis a declared public method. Here is a small danger still: a user can add a property to the public function at any time. Therefore, maybe these cases are worth to be refactored as well. -
funcis a private function (declared or given as direct/indirect argument). The monomorphism of thisfuncshould be checked across all the code base chains.
IV. Occurrences in Node.js libs
funcis an argument gotten from a userland via public API
(fixed).dns.js:callbackinmakeAsync()/asyncCallback()is given fromexports.lookup(),exports.lookupService()and possibly via manyexports.resolve***()functions(fixing seems to cause performance downgrading)events.js:this.listenerinonceWrapper()is given from this chain:_onceWrap()<-EventEmitter.prototype.once()orEventEmitter.prototype.prependOnceListener().(this anonymous ones are one-time unoptimizable functions)fs.js:cbin anonymous /makeCallback()is given from many (more than 20)fs.***()(i.e.exports.***()) functions.repl.js:this.completerinREPLServer.prototype.complete()is given from this chain:REPLServer()<-exports.start()._stream_readable.js:stream[method]inReadable.prototype.wrap(stream)is given directly.(under consideration).internal/util.js:exports.deprecate(fn, msg, code)andfninexports.deprecate()is given fromutil.deprecate()
funcis a declared public method
net.js:stream.Duplex.prototype.writeinSocket.prototype.write()util.js:exports.formatindebugs[set]()/exports.debuglog()util.js:-exports.formatinexports.log()_http_server.js:this.writeHeadinServerResponse.prototype.writeHeader()internal/process.js:require('util').formatinprocess._rawDebug()/setupRawDebug()
funcis a private function. Most detected cases seem to be OK.
In addition, there are some cases I could not define which group they belong to and if they are monomorphic. I can't trace all the chain here (got out of memory and stack overflow in my mind).
(fixed)_tls_wrap.js:this._parent[name]intls_wrap.TLSWrap.prototype[name]()internal/cluster/utils.js:fninmodule.exports.internal()internal/cluster/child.js:closeinhandle.close()/shared()internal/cluster/worker.js:this.destroyinWorker.prototype.kill().internal/cluster/worker.js:this.process.sendinWorker.prototype.send()
Sorry for my bad English an overall mess in explanation.