Add removing unused parameter bytecode process to parser#1543
Add removing unused parameter bytecode process to parser#1543ksh8281 merged 9 commits intoSamsung:masterfrom
Conversation
src/parser/CodeBlock.h
Outdated
| #endif | ||
|
|
||
| #ifndef ESCARGOT_DEBUGGER | ||
| uint16_t m_parameterUsed : 16; |
There was a problem hiding this comment.
Please add comment about meaning
like 0xFFFF means the function have many parameters(>16)
....
3345bfa to
72bba12
Compare
src/parser/esprima_cpp/esprima.cpp
Outdated
| for (size_t j = 0; j < ret->m_childBlockScopes.size(); j++) { | ||
| ASTBlockContext* block = ret->m_childBlockScopes[j]; | ||
| if (VectorUtil::findInVector(block->m_usingNames, paramName) != VectorUtil::invalidIndex) { | ||
| scopeCtx->m_parameterUsed |= (1 << i); |
There was a problem hiding this comment.
The scopeCtx->m_parameterUsed |= (1 << i); line may shift 1 by i bits, which can overflow if i is greater than the bit width of m_parameterUsed, leading to undefined behavior. Ensure i is within bounds or use a wider type or bitset.
src/parser/ast/ASTContext.h
Outdated
| #ifndef ESCARGOT_DEBUGGER | ||
| uint16_t m_parameterUsed : 16; // 0xFFFF means all parameters are used or function has more than 16 parameters | ||
|
|
||
| ASTScopeContext *m_parentScope; |
There was a problem hiding this comment.
This approach searches every use case of parameter in bottom-up method, so this additional m_parentScope is required.
But have you tried top-down method instead?
It seems possible to search from top to bottom without this additional info
There was a problem hiding this comment.
I tried that already, and it is possible. But that way uses almost equal memory size compared to this way, and gets more complicated code because of adding process which dealing with arguments object.
Additionaly, top-down search implementation itself is more complicated than bottom-up search.
So I think bottom-up search is better than top-down.
There was a problem hiding this comment.
@hyukwoo-park
I rewrited to top-down method, and it looks better than my thought.
Please check again!
src/parser/esprima_cpp/esprima.cpp
Outdated
| void setParameterUsedValue(ASTScopeContext* scopeCtx) | ||
| { | ||
| while (scopeCtx) { | ||
| for (size_t i = 0; i < this->currentScopeContext->m_parameters.size(); i++) { |
There was a problem hiding this comment.
this->currentScopeContext->m_parameters is used in the loop header of setParameterUsedValue, but the function receives scopeCtx. As a result, when setParameterUsedValue is called recursively on child scopes, the loop still iterates over the original currentScopeContext's parameters, leaving child scopes' parameters unprocessed and potentially mis-marking used parameters. This logic flaw can lead to incorrect parameter usage detection in the parser. The minimal fix is to replace this->currentScopeContext with scopeCtx in the loop header and all subsequent accesses inside the function.
src/parser/esprima_cpp/esprima.cpp
Outdated
| } | ||
|
|
||
| #ifndef ESCARGOT_DEBUGGER | ||
| void setParameterUsedValue(ASTScopeContext* scopeCtx) |
There was a problem hiding this comment.
Use the function argument scopeCtx consistently inside setParameterUsedValue instead of this->currentScopeContext. The current loop accesses this->currentScopeContext->m_parameters and this->currentScopeContext->m_parameterUsed, which can be confusing because the function receives scopeCtx. Switching to scopeCtx clarifies that the function operates on the passed context and prevents accidental reliance on the outer currentScopeContext. This small change improves readability and maintainability without altering the overall logic. It also reduces the risk of subtle bugs when the function is called recursively on child scopes.
src/parser/esprima_cpp/esprima.cpp
Outdated
| } | ||
|
|
||
| #ifndef ESCARGOT_DEBUGGER | ||
| void setParameterUsedValue(ASTScopeContext* scopeCtx) |
There was a problem hiding this comment.
minor review) IMO setParameterUsedValue is not intuitive, what about another name such as checkParameterUsage or others?
src/parser/esprima_cpp/esprima.cpp
Outdated
| #endif | ||
| #ifndef ESCARGOT_DEBUGGER | ||
| if (UNLIKELY(paramNames.size() > 16)) { | ||
| this->currentScopeContext->m_parameterUsed = 0xFFFF; |
There was a problem hiding this comment.
0xFFFF might be confusing, so It would be better to use macro variable like below (just an example)
#define DISABLE_PARAMETER_REMOVE 0xFFFFb28e581 to
a07c5f4
Compare
src/parser/esprima_cpp/esprima.cpp
Outdated
| if (this->currentScopeContext->m_parameterUsed == DISABLE_PARAM_CHECK) { | ||
| break; | ||
| } |
There was a problem hiding this comment.
Please move this check code out of the outermost while statement (with UNLIKELY macro), and it seems possible to return directly inside this check statement
src/parser/esprima_cpp/esprima.cpp
Outdated
| break; | ||
| } | ||
|
|
||
| AtomicString name = this->currentScopeContext->m_parameters[i]; |
There was a problem hiding this comment.
This line could be improved as below (using a reference)
// parameter list is fixed, so using a reference out of the while statement to shorten the code
AtomicStringTightVector& parameters = this->currentScopeContext->m_parameters;
while (...) {
...
AtomicString& name = parameters[i];
There was a problem hiding this comment.
And in this approach, the outermost while could be removed
src/parser/esprima_cpp/esprima.cpp
Outdated
| if (scopeCtx->firstChild()) { | ||
| checkUsedParameters(scopeCtx->firstChild()); | ||
| } | ||
|
|
||
| if (scopeCtx != this->currentScopeContext) { | ||
| scopeCtx = scopeCtx->nextSibling(); | ||
| } else { | ||
| break; | ||
| } | ||
| } |
There was a problem hiding this comment.
This part could be improved(refactored) below
size_t childCount = scopeCtx->childCount();
if (childCount > 0) {
ASTScopeContext* childScope = ScopeCtx->firstChild();
for (size_t i = 0; i < childCount; i++) {
checkUsedParameters(childScope);
childScope = childScope->nextSibling();
}
}I think that this approach is better for maintenance.
src/parser/esprima_cpp/esprima.cpp
Outdated
| ASTScopeContext* popScopeContext(ASTScopeContext* lastPushedScopeContext) | ||
| { | ||
| #ifndef ESCARGOT_DEBUGGER | ||
| checkUsedParameters(this->currentScopeContext); |
There was a problem hiding this comment.
checkUsedParameters is invoked at each popScopeContext; this means that parameter check is done after each scan of the function completes.
That is, at this moment, we can know whether this function uses eval or has more than 16 parameters.
So, we can check the above cases just before invoking checkUsedParameters here.
| for (size_t j = 0; j < scopeCtx->m_childBlockScopes.size(); j++) { | ||
| ASTBlockContext& blockCtx = *scopeCtx->m_childBlockScopes[j]; | ||
| if (VectorUtil::findInVector(blockCtx.m_usingNames, name) != VectorUtil::invalidIndex) { | ||
| this->currentScopeContext->m_parameterUsed |= (1 << i); |
There was a problem hiding this comment.
The expression 1 << i in this->currentScopeContext->m_parameterUsed |= (1 << i); performs a left shift by the loop index i. If i is greater than or equal to the bit width of m_parameterUsed, the shift is undefined and can corrupt the bitmask. Cast the shift to a wider type or guard against i exceeding the width of m_parameterUsed before setting the bit.
src/parser/esprima_cpp/esprima.cpp
Outdated
| if (VectorUtil::findInVector(blockCtx.m_usingNames, name) != VectorUtil::invalidIndex) { | ||
| this->currentScopeContext->m_parameterUsed |= (1 << i); | ||
| break; | ||
| } else if (scopeCtx->m_parameterUsed == DISABLE_PARAM_CHECK && VectorUtil::findInVector(blockCtx.m_usingNames, stringArguments) != VectorUtil::invalidIndex) { |
There was a problem hiding this comment.
The condition checks scopeCtx->m_parameterUsed == DISABLE_PARAM_CHECK but the assignment later sets this->currentScopeContext->m_parameterUsed = DISABLE_PARAM_CHECK. This mismatch means the disable flag may never be detected, causing incorrect parameter usage checks. Change the condition to test this->currentScopeContext->m_parameterUsed instead of scopeCtx->m_parameterUsed to ensure the flag is correctly evaluated.
src/parser/ast/ASTContext.h
Outdated
| #endif | ||
| #ifndef ESCARGOT_DEBUGGER | ||
| , m_hasStringArguments(false) | ||
| , m_parameterUsed(0) |
There was a problem hiding this comment.
Remove the duplicate initialization of m_parameterUsed(0) inside the second #ifndef ESCARGOT_DEBUGGER block. The struct already initializes m_parameterUsed in the first conditional block, so the second occurrence causes a duplicate member initialization error when ESCARGOT_DEBUGGER is not defined. Keeping a single initialization ensures the constructor remains well-formed and avoids compilation failures. If the intention was to add a new member, consider renaming or moving the initialization to a separate block.
src/parser/esprima_cpp/esprima.cpp
Outdated
| if (VectorUtil::findInVector(blockCtx.m_usingNames, name) != VectorUtil::invalidIndex) { | ||
| this->currentScopeContext->m_parameterUsed |= (1 << i); | ||
| break; | ||
| } else if (scopeCtx->m_parameterUsed == DISABLE_PARAM_CHECK) { |
There was a problem hiding this comment.
The else if now triggers whenever scopeCtx->m_parameterUsed == DISABLE_PARAM_CHECK, causing an early return that skips checking remaining parameters. This can lead to incorrect m_parameterUsed state. Restore the original condition or add a guard for stringArguments.
src/parser/ast/ASTContext.h
Outdated
| bool m_hasStringArguments : 1; | ||
| uint16_t m_parameterUsed : 16; |
There was a problem hiding this comment.
Considering padding, please locate each boolean and uint16_t member together with other boolean and 16-bit sized members
|
Looks much better 👍 |
9d23dc6 to
c26915d
Compare
|
@ksh8281 |
|
Please add comment about meaning of m_parameterUsed |
This PR adds some processes to parser to pass creation of bytecode for unused function parameters to optimize memory usage.
To implement this feature, we need to add some variables in
ASTScopeContextandInterpretedCodeBlockwhich checking parameter using. And actual check process is executed when closing function scope.When
evalfunction andargumentsis in function body, all parameters in this scope(all parent scopes too forarguments) are considered as used because of complexity of implementation.Also, if statement starting at 3806 in
esprima.cppneeds to caretest/vendortest/v8/test/mjsunit/es6/regress/regress-4395.jstest.I test this feature with web-tooling-benchmark to watch peak memory usage, but GC was running irregularly so result shows good performance sometime, or not. Differance is about 10 to 100 KB up or down.
Here is some result of benchmark tests: