-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Optimize rest parameter emit #20307
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
Closed
charlespierce
wants to merge
3
commits into
microsoft:master
from
charlespierce:rest_parameter_optimization
Closed
Optimize rest parameter emit #20307
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -944,7 +944,6 @@ namespace ts { | |
|
||
if (constructor) { | ||
addDefaultValueAssignmentsIfNeeded(statements, constructor); | ||
addRestParameterIfNeeded(statements, constructor, hasSynthesizedSuper); | ||
if (!hasSynthesizedSuper) { | ||
// If no super call has been synthesized, emit custom prologue directives. | ||
statementOffset = addCustomPrologue(statements, constructor.body.statements, statementOffset, visitor); | ||
|
@@ -969,6 +968,7 @@ namespace ts { | |
} | ||
|
||
addRange(statements, visitNodes(constructor.body.statements, visitor, isStatement, /*start*/ statementOffset)); | ||
insertRestParameterIfNeeded(statements, constructor, hasSynthesizedSuper); | ||
} | ||
|
||
// Return `_this` unless we're sure enough that it would be pointless to add a return statement. | ||
|
@@ -1344,6 +1344,38 @@ namespace ts { | |
return node && node.dotDotDotToken && node.name.kind === SyntaxKind.Identifier && !inConstructorWithSynthesizedSuper; | ||
} | ||
|
||
/** | ||
* Gets the index of the first statement that contains a reference to a given parameter | ||
* | ||
* @param restParameter The ParameterDeclaration to which we are finding references. | ||
* @param statements An array of Statement nodes to search | ||
*/ | ||
function findRestParameterReferenceIndex(restParameter: ParameterDeclaration, statements: Statement[]) { | ||
const originalRestParameter = getOriginalNode(restParameter); | ||
|
||
return findIndex(statements, containsReferenceToParameter); | ||
|
||
function containsReferenceToParameter(statement: Statement): boolean { | ||
return (isIdentifier(statement) && originalRestParameter === resolver.getReferencedValueDeclaration(statement)) || | ||
forEachChild(statement, containsReferenceToParameter); | ||
} | ||
} | ||
|
||
/** | ||
* Gets the index of the first statement that contains a re-definition of "arguments" | ||
* | ||
* @param statements An array of Statement nodes to search | ||
*/ | ||
function findArgumentsRedefinitionIndex(statements: Statement[]) { | ||
return findIndex(statements, containsArgumentsRedeclaration); | ||
|
||
function containsArgumentsRedeclaration(statement: Statement): boolean { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As above, this is yet another expensive recursive walk of the entire subtree and is better handled in the checker. |
||
return (isAssignmentExpression(statement) && isIdentifier(statement.left) && statement.left.escapedText === "arguments") || | ||
(isVariableDeclaration(statement) && statement.initializer && (<Identifier>statement.name).escapedText === "arguments") || | ||
forEachChild(statement, containsArgumentsRedeclaration); | ||
} | ||
} | ||
|
||
/** | ||
* Adds statements to the body of a function-like node if it contains a rest parameter. | ||
* | ||
|
@@ -1353,12 +1385,23 @@ namespace ts { | |
* part of a constructor declaration with a | ||
* synthesized call to `super` | ||
*/ | ||
function addRestParameterIfNeeded(statements: Statement[], node: FunctionLikeDeclaration, inConstructorWithSynthesizedSuper: boolean): void { | ||
function insertRestParameterIfNeeded(statements: Statement[], node: FunctionLikeDeclaration, inConstructorWithSynthesizedSuper: boolean): void { | ||
const parameter = lastOrUndefined(node.parameters); | ||
if (!shouldAddRestParameter(parameter, inConstructorWithSynthesizedSuper)) { | ||
return; | ||
} | ||
|
||
let referenceIndex = findRestParameterReferenceIndex(parameter, statements); | ||
if (referenceIndex === -1) { | ||
return; | ||
} | ||
|
||
const argumentsRedefinitionIndex = findArgumentsRedefinitionIndex(statements); | ||
if (argumentsRedefinitionIndex > -1) { | ||
// If the "arguments" variable is redefined, need to make sure to emit the rest parameter initialization before then. | ||
referenceIndex = Math.min(referenceIndex, argumentsRedefinitionIndex); | ||
} | ||
|
||
// `declarationName` is the name of the local declaration for the parameter. | ||
const declarationName = getMutableClone(<Identifier>parameter.name); | ||
setEmitFlags(declarationName, EmitFlags.NoSourceMap); | ||
|
@@ -1369,9 +1412,7 @@ namespace ts { | |
const temp = createLoopVariable(); | ||
|
||
// var param = []; | ||
statements.push( | ||
setEmitFlags( | ||
setTextRange( | ||
const variableStatement = setTextRange( | ||
createVariableStatement( | ||
/*modifiers*/ undefined, | ||
createVariableDeclarationList([ | ||
|
@@ -1383,10 +1424,7 @@ namespace ts { | |
]) | ||
), | ||
/*location*/ parameter | ||
), | ||
EmitFlags.CustomPrologue | ||
) | ||
); | ||
); | ||
|
||
// for (var _i = restIndex; _i < arguments.length; _i++) { | ||
// param[_i - restIndex] = arguments[_i]; | ||
|
@@ -1426,9 +1464,8 @@ namespace ts { | |
]) | ||
); | ||
|
||
setEmitFlags(forStatement, EmitFlags.CustomPrologue); | ||
startOnNewLine(forStatement); | ||
statements.push(forStatement); | ||
statements.splice(referenceIndex, 0, variableStatement, forStatement); | ||
} | ||
|
||
/** | ||
|
@@ -1846,7 +1883,6 @@ namespace ts { | |
|
||
addCaptureThisForNodeIfNeeded(statements, node); | ||
addDefaultValueAssignmentsIfNeeded(statements, node); | ||
addRestParameterIfNeeded(statements, node, /*inConstructorWithSynthesizedSuper*/ false); | ||
|
||
// If we added any generated statements, this must be a multi-line block. | ||
if (!multiLine && statements.length > 0) { | ||
|
@@ -1895,13 +1931,16 @@ namespace ts { | |
closeBraceLocation = body; | ||
} | ||
|
||
const statementBodyLength = statements.length; | ||
insertRestParameterIfNeeded(statements, node, /*inConstructorWithSynthesizedSuper*/ false); | ||
|
||
const lexicalEnvironment = context.endLexicalEnvironment(); | ||
addRange(statements, lexicalEnvironment); | ||
|
||
prependCaptureNewTargetIfNeeded(statements, node, /*copyOnWrite*/ false); | ||
|
||
// If we added any final generated statements, this must be a multi-line block | ||
if (!multiLine && lexicalEnvironment && lexicalEnvironment.length) { | ||
if (!multiLine && statements.length !== statementBodyLength) { | ||
multiLine = true; | ||
} | ||
|
||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 results in a recursive walk of the entire subtree of every statement of a function or method, which we try to avoid in the transforms. It seems like this is best handled in the checker when checking an Identifier. At that point you can look up its declaration, determine whether the declaration is a rest parameter, and walk up from there to the topmost containing statement and set something like a
NodeCheckFlags.ContainsRestParameterReference
on the statement. Walking up the spine is far cheaper than walking down to each descendant.Note that you would also need to verify here in the transformer that the rest parameter was type checked, as its possible that a transformation added a previously non-existent rest-parameter. In that case, I would err on the side of caution and emit at the top of the function.
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, I wasn't aware of trying to avoid recursive walks in transforms. I'll take a look at flagging this info (and the below walk as well) during type checking.
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.
While a tree walk is generally cheap, each transform is run in succession resulting in multiple tree walks per file. In the compiler we attempt to limit this as much as possible through various optimizations. One optimization we regularly employ (though it doesn't apply here) are transformation flags that we calculate during our bind step (since bind is a guaranteed top-down full walk of the tree), or check flags that we calculate in the checker (which can randomly access the tree). These flags allow us to avoid full walks of the tree because we can use that information to determine whether a tree walk is necessary for each case.