Repository navigation
Conversation
When evalCtor partially evaluates a function, non-parameter locals start in FunctionScope with Literal::makeZeros(type), which produces a null reference placeholder for non-nullable reference locals. If a non-nullable local was not yet written before partial evaluation stopped, emitting a local.set with that serialized null expression produced invalid Wasm. Furthermore, wrapping the initial local.sets in a separate inner block before the copied function body prevented initialized non-nullable locals from structurally dominating uses in the remaining body. Only emit initial local.sets when the serialized expression is a valid subtype of the local type, and insert them directly at the start of the copied body block.
There was a problem hiding this comment.
Code Review
This pull request updates wasm-ctor-eval.cpp to skip uninitialized non-nullable locals when prepending local sets to the function body, avoiding invalid default null values. A corresponding test case has been added to verify this behavior. Feedback on the changes highlights a performance concern where repeatedly calling insertAt on copyBody->list results in O(N * M) complexity, and suggests optimizing this to O(N + M) by constructing a new vector and assigning it in a single operation.
| Index insertIndex = 0; | ||
| for (Index i = 0; i < localExprs.size(); ++i) { | ||
| setsBlock->list.push_back(builder.makeLocalSet(i, localExprs[i])); | ||
| if (Type::isSubType(localExprs[i]->type, func->getLocalType(i))) { | ||
| copyBody->list.insertAt(insertIndex++, | ||
| builder.makeLocalSet(i, localExprs[i])); | ||
| } | ||
| } |
There was a problem hiding this comment.
Inserting elements one by one at the beginning of copyBody->list using insertAt has
Instead, we can construct a new vector, reserve the total required capacity, populate it, and set it on copyBody->list in a single
std::vector<Expression*> newList;
newList.reserve(localExprs.size() + copyBody->list.size());
for (Index i = 0; i < localExprs.size(); ++i) {
if (Type::isSubType(localExprs[i]->type, func->getLocalType(i))) {
newList.push_back(builder.makeLocalSet(i, localExprs[i]));
}
}
newList.insert(newList.end(), copyBody->list.begin(), copyBody->list.end());
copyBody->list.set(newList);
When evalCtor partially evaluates a function, non-parameter locals start in FunctionScope with Literal::makeZeros(type), which produces a null reference placeholder for non-nullable reference locals. If a non-nullable local was not yet written before partial evaluation stopped, emitting a local.set with that serialized null expression produced invalid Wasm. Furthermore, wrapping the initial local.sets in a separate inner block before the copied function body prevented initialized non-nullable locals from structurally dominating uses in the remaining body. Only emit initial local.sets when the serialized expression is a valid subtype of the local type, and insert them directly at the start of the copied body block.