Skip to content

Commit 546986e

Browse files
committed
Handle return calls in wasm-ctor-eval
When an evaluated export ends in a return call, continue evaluating the return-called function. This requires propagating the parameters, handling the case that the return-called function might be an import, and fixing up local indices in case the final function has different parameters than the original function.
1 parent f711fdc commit 546986e

File tree

3 files changed

+632
-68
lines changed

3 files changed

+632
-68
lines changed

src/tools/wasm-ctor-eval.cpp

Lines changed: 121 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <memory>
2626

2727
#include "asmjs/shared-constants.h"
28+
#include "ir/find_all.h"
2829
#include "ir/gc-type-utils.h"
2930
#include "ir/global-utils.h"
3031
#include "ir/import-utils.h"
@@ -1061,40 +1062,45 @@ EvalCtorOutcome evalCtor(EvallingModuleRunner& instance,
10611062
params.push_back(Literal::makeZero(type));
10621063
}
10631064

1064-
// We want to handle the form of the global constructor function in LLVM. That
1065-
// looks like this:
1066-
//
1067-
// (func $__wasm_call_ctors
1068-
// (call $ctor.1)
1069-
// (call $ctor.2)
1070-
// (call $ctor.3)
1071-
// )
1072-
//
1073-
// Some of those ctors may be inlined, however, which would mean that the
1074-
// function could have locals, control flow, etc. However, we assume for now
1075-
// that it does not have parameters at least (whose values we can't tell).
1076-
// And for now we look for a toplevel block and process its children one at a
1077-
// time. This allows us to eval some of the $ctor.* functions (or their
1078-
// inlined contents) even if not all.
1079-
//
1080-
// TODO: Support complete partial evalling, that is, evaluate parts of an
1081-
// arbitrary function, and not just a sequence in a single toplevel
1082-
// block.
1065+
// After we successfully eval a line we will store the operations to set up
1066+
// the locals here. That is, we need to save the local state in the function,
1067+
// which we do by setting up at the entry. We update this list of expressions
1068+
// at the same time as applyToModule() - we must only do it after an entire
1069+
// atomic "chunk" has been processed succesfully, we do not want partial
1070+
// updates from an item in the block that we only partially evalled. When we
1071+
// construct the (partially) evalled function, we will create local.sets of
1072+
// these expressions at the beginning.
1073+
std::vector<Expression*> localExprs;
1074+
1075+
// We might have to evaluate multiple functions due to return calls.
1076+
start_eval:
1077+
while (true) {
1078+
// We want to handle the form of the global constructor function in LLVM.
1079+
// That looks like this:
1080+
//
1081+
// (func $__wasm_call_ctors
1082+
// (call $ctor.1)
1083+
// (call $ctor.2)
1084+
// (call $ctor.3)
1085+
// )
1086+
//
1087+
// Some of those ctors may be inlined, however, which would mean that the
1088+
// function could have locals, control flow, etc. However, we assume for now
1089+
// that it does not have parameters at least (whose values we can't tell).
1090+
// And for now we look for a toplevel block and process its children one at
1091+
// a time. This allows us to eval some of the $ctor.* functions (or their
1092+
// inlined contents) even if not all.
1093+
//
1094+
// TODO: Support complete partial evalling, that is, evaluate parts of an
1095+
// arbitrary function, and not just a sequence in a single toplevel
1096+
// block.
1097+
Builder builder(wasm);
1098+
auto* block = builder.blockify(func->body);
10831099

1084-
if (auto* block = func->body->dynCast<Block>()) {
10851100
// Go through the items in the block and try to execute them. We do all this
10861101
// in a single function scope for all the executions.
10871102
EvallingModuleRunner::FunctionScope scope(func, params, instance);
10881103

1089-
// After we successfully eval a line we will store the operations to set up
1090-
// the locals here. That is, we need to save the local state in the
1091-
// function, which we do by setting up at the entry. We update this list of
1092-
// local.sets at the same time as applyToModule() - we must only do it after
1093-
// an entire atomic "chunk" has been processed succesfully, we do not want
1094-
// partial updates from an item in the block that we only partially evalled.
1095-
std::vector<Expression*> localSets;
1096-
1097-
Builder builder(wasm);
10981104
Literals results;
10991105
Index successes = 0;
11001106

@@ -1116,6 +1122,22 @@ EvalCtorOutcome evalCtor(EvallingModuleRunner& instance,
11161122
break;
11171123
}
11181124

1125+
if (flow.breakTo == RETURN_CALL_FLOW) {
1126+
// The return-called function is stored in the last value.
1127+
func = wasm.getFunction(flow.values.back().getFunc());
1128+
flow.values.pop_back();
1129+
params = std::move(flow.values);
1130+
1131+
// Serialize the arguments for the new function and save the module
1132+
// state in case we fail to eval the new function.
1133+
localExprs.clear();
1134+
for (auto& param : params) {
1135+
localExprs.push_back(interface.getSerialization(param));
1136+
}
1137+
interface.applyToModule();
1138+
goto start_eval;
1139+
}
1140+
11191141
// So far so good! Serialize the values of locals, and apply to the
11201142
// module. Note that we must serialize the locals now as doing so may
11211143
// cause changes that must be applied to the module (e.g. GC data may
@@ -1128,11 +1150,9 @@ EvalCtorOutcome evalCtor(EvallingModuleRunner& instance,
11281150
// of them, and leave it to the optimizer to remove redundant or
11291151
// unnecessary operations. We just recompute the entire local
11301152
// serialization sets from scratch each time here, for all locals.
1131-
localSets.clear();
1153+
localExprs.clear();
11321154
for (Index i = 0; i < func->getNumLocals(); i++) {
1133-
auto value = scope.locals[i];
1134-
localSets.push_back(
1135-
builder.makeLocalSet(i, interface.getSerialization(value)));
1155+
localExprs.push_back(interface.getSerialization(scope.locals[i]));
11361156
}
11371157
interface.applyToModule();
11381158
successes++;
@@ -1144,41 +1164,92 @@ EvalCtorOutcome evalCtor(EvallingModuleRunner& instance,
11441164
if (flow.breaking()) {
11451165
// We are returning out of the function (either via a return, or via a
11461166
// break to |block|, which has the same outcome. That means we don't
1147-
// need to execute any more lines, and can consider them to be executed.
1167+
// need to execute any more lines, and can consider them to be
1168+
// executed.
11481169
if (!quiet) {
11491170
std::cout << " ...stopping in block due to break\n";
11501171
}
11511172

11521173
// Mark us as having succeeded on the entire block, since we have: we
1153-
// are skipping the rest, which means there is no problem there. We must
1154-
// set this here so that lower down we realize that we've evalled
1174+
// are skipping the rest, which means there is no problem there. We
1175+
// must set this here so that lower down we realize that we've evalled
11551176
// everything.
11561177
successes = block->list.size();
11571178
break;
11581179
}
11591180
}
11601181

1161-
if (successes > 0 && successes < block->list.size()) {
1162-
// We managed to eval some but not all. That means we can't just remove
1163-
// the entire function, but need to keep parts of it - the parts we have
1164-
// not evalled - around. To do so, we create a copy of the function with
1165-
// the partially-evalled contents and make the export use that (as the
1166-
// function may be used in other places than the export, which we do not
1167-
// want to affect).
1182+
if ((localExprs.size() && func->getParams() != Type::none) ||
1183+
func->name != funcName ||
1184+
(successes > 0 && successes < block->list.size())) {
1185+
auto originalFuncType = wasm.getFunction(funcName)->type;
11681186
auto copyName = Names::getValidFunctionName(wasm, funcName);
1169-
auto* copyFunc = ModuleUtils::copyFunction(func, wasm, copyName);
11701187
wasm.getExport(exportName)->value = copyName;
11711188

1189+
if (func->imported()) {
1190+
// We must have return-called this imported function. Generate a new
1191+
// function that return-calls the import with the arguments we have
1192+
// evalled.
1193+
auto copyFunc = builder.makeFunction(
1194+
copyName,
1195+
originalFuncType,
1196+
{},
1197+
builder.makeCall(func->name, localExprs, func->getResults(), true));
1198+
wasm.addFunction(std::move(copyFunc));
1199+
return EvalCtorOutcome();
1200+
}
1201+
1202+
// We may have managed to eval some but not all. That means we can't just
1203+
// remove the entire function, but need to keep parts of it - the parts we
1204+
// have not evalled - around. To do so, we create a copy of the function
1205+
// with the partially-evalled contents and make the export use that (as
1206+
// the function may be used in other places than the export, which we do
1207+
// not want to affect).
1208+
auto* copyBody =
1209+
builder.blockify(ExpressionManipulator::copy(func->body, wasm));
1210+
11721211
// Remove the items we've evalled.
1173-
auto* copyBlock = copyFunc->body->cast<Block>();
11741212
for (Index i = 0; i < successes; i++) {
1175-
copyBlock->list[i] = builder.makeNop();
1213+
copyBody->list[i] = builder.makeNop();
11761214
}
11771215

1178-
// Put the local sets at the front of the block. We know there must be a
1179-
// nop in that position (since we've evalled at least one item in the
1180-
// block, and replaced it with a nop), so we can overwrite it.
1181-
copyBlock->list[0] = builder.makeBlock(localSets);
1216+
// Put the local sets at the front of the function body.
1217+
auto* setsBlock = builder.makeBlock();
1218+
for (Index i = 0; i < localExprs.size(); ++i) {
1219+
setsBlock->list.push_back(builder.makeLocalSet(i, localExprs[i]));
1220+
}
1221+
copyBody = builder.makeSequence(setsBlock, copyBody, copyBody->type);
1222+
1223+
// We may have return-called into a function with different parameter
1224+
// types, but we ultimately need to export a function with the original
1225+
// signature. If there is a mismatch, shift the local indices to make room
1226+
// for the unused parameters.
1227+
std::vector<Type> localTypes;
1228+
auto originalParams = originalFuncType.getSignature().params;
1229+
if (originalParams != func->getParams()) {
1230+
// Add locals for the body to use instead of using the params.
1231+
for (auto type : func->getParams()) {
1232+
localTypes.push_back(type);
1233+
}
1234+
1235+
// Shift indices in the body so they will refer to the new locals.
1236+
auto localShift = originalParams.size();
1237+
if (localShift != 0) {
1238+
for (auto* get : FindAll<LocalGet>(copyBody).list) {
1239+
get->index += localShift;
1240+
}
1241+
for (auto* set : FindAll<LocalSet>(copyBody).list) {
1242+
set->index += localShift;
1243+
}
1244+
}
1245+
}
1246+
1247+
// Add vars from current function.
1248+
localTypes.insert(localTypes.end(), func->vars.begin(), func->vars.end());
1249+
1250+
// Create and add the new function.
1251+
auto* copyFunc = wasm.addFunction(builder.makeFunction(
1252+
copyName, originalFuncType, std::move(localTypes), copyBody));
11821253

11831254
// Interesting optimizations may be possible both due to removing some but
11841255
// not all of the code, and due to the locals we just added.
@@ -1196,24 +1267,6 @@ EvalCtorOutcome evalCtor(EvallingModuleRunner& instance,
11961267
return EvalCtorOutcome();
11971268
}
11981269
}
1199-
1200-
// Otherwise, we don't recognize a pattern that allows us to do partial
1201-
// evalling. So simply call the entire function at once and see if we can
1202-
// optimize that.
1203-
1204-
Literals results;
1205-
try {
1206-
results = instance.callFunction(funcName, params);
1207-
} catch (FailToEvalException& fail) {
1208-
if (!quiet) {
1209-
std::cout << " ...stopping since could not eval: " << fail.why << "\n";
1210-
}
1211-
return EvalCtorOutcome();
1212-
}
1213-
1214-
// Success! Apply the results.
1215-
interface.applyToModule();
1216-
return EvalCtorOutcome(results);
12171270
}
12181271

12191272
// Eval all ctors in a module.

src/wasm-builder.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ class Builder {
269269
call->target = target;
270270
call->operands.set(args);
271271
call->isReturn = isReturn;
272+
call->finalize();
272273
return call;
273274
}
274275
template<typename T>

0 commit comments

Comments
 (0)