Skip to content

Commit 483d759

Browse files
authored
Refactor expression runner so it can be used via the C and JS APIs (#2702)
Refactors most of the precompute pass's expression runner into its base class so it can also be used via the C and JS APIs. Also adds the option to populate the runner with known constant local and global values upfront, and remembers assigned intermediate values as well as traversing into functions if requested.
1 parent 1dc820c commit 483d759

16 files changed

+882
-103
lines changed

src/binaryen-c.cpp

+138
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ std::map<BinaryenGlobalRef, size_t> globals;
135135
std::map<BinaryenEventRef, size_t> events;
136136
std::map<BinaryenExportRef, size_t> exports;
137137
std::map<RelooperBlockRef, size_t> relooperBlocks;
138+
std::map<ExpressionRunnerRef, size_t> expressionRunners;
138139

139140
static bool isBasicAPIType(BinaryenType type) {
140141
return type == BinaryenTypeAuto() || type <= Type::_last_value_type;
@@ -208,6 +209,26 @@ size_t noteExpression(BinaryenExpressionRef expression) {
208209
return id;
209210
}
210211

212+
// Even though unlikely, it is possible that we are trying to use an id that is
213+
// still in use after wrapping around, which we must prevent.
214+
static std::unordered_set<size_t> usedExpressionRunnerIds;
215+
216+
size_t noteExpressionRunner(ExpressionRunnerRef runner) {
217+
// We would normally use the size of `expressionRunners` as the next index,
218+
// but since we are going to delete runners the same address can become
219+
// reused, which would result in unpredictable sizes (indexes) due to
220+
// undefined behavior. Use a sequential id instead.
221+
static size_t nextId = 0;
222+
223+
size_t id;
224+
do {
225+
id = nextId++;
226+
} while (usedExpressionRunnerIds.find(id) != usedExpressionRunnerIds.end());
227+
expressionRunners[runner] = id;
228+
usedExpressionRunnerIds.insert(id);
229+
return id;
230+
}
231+
211232
std::string getTemp() {
212233
static size_t n = 0;
213234
return "t" + std::to_string(n++);
@@ -604,6 +625,7 @@ void BinaryenModuleDispose(BinaryenModuleRef module) {
604625
std::cout << " events.clear();\n";
605626
std::cout << " exports.clear();\n";
606627
std::cout << " relooperBlocks.clear();\n";
628+
std::cout << " expressionRunners.clear();\n";
607629
types.clear();
608630
expressions.clear();
609631
functions.clear();
@@ -4950,6 +4972,121 @@ BinaryenExpressionRef RelooperRenderAndDispose(RelooperRef relooper,
49504972
return BinaryenExpressionRef(ret);
49514973
}
49524974

4975+
//
4976+
// ========= ExpressionRunner =========
4977+
//
4978+
4979+
namespace wasm {
4980+
4981+
class CExpressionRunner final : public ExpressionRunner<CExpressionRunner> {
4982+
public:
4983+
CExpressionRunner(Module* module,
4984+
CExpressionRunner::Flags flags,
4985+
Index maxDepth,
4986+
Index maxLoopIterations)
4987+
: ExpressionRunner<CExpressionRunner>(
4988+
module, flags, maxDepth, maxLoopIterations) {}
4989+
4990+
void trap(const char* why) override { throw NonconstantException(); }
4991+
};
4992+
4993+
} // namespace wasm
4994+
4995+
ExpressionRunnerFlags ExpressionRunnerFlagsDefault() {
4996+
return CExpressionRunner::FlagValues::DEFAULT;
4997+
}
4998+
4999+
ExpressionRunnerFlags ExpressionRunnerFlagsPreserveSideeffects() {
5000+
return CExpressionRunner::FlagValues::PRESERVE_SIDEEFFECTS;
5001+
}
5002+
5003+
ExpressionRunnerFlags ExpressionRunnerFlagsTraverseCalls() {
5004+
return CExpressionRunner::FlagValues::TRAVERSE_CALLS;
5005+
}
5006+
5007+
ExpressionRunnerRef ExpressionRunnerCreate(BinaryenModuleRef module,
5008+
ExpressionRunnerFlags flags,
5009+
BinaryenIndex maxDepth,
5010+
BinaryenIndex maxLoopIterations) {
5011+
auto* wasm = (Module*)module;
5012+
auto* runner = ExpressionRunnerRef(
5013+
new CExpressionRunner(wasm, flags, maxDepth, maxLoopIterations));
5014+
if (tracing) {
5015+
auto id = noteExpressionRunner(runner);
5016+
std::cout << " expressionRunners[" << id
5017+
<< "] = ExpressionRunnerCreate(the_module, " << flags << ", "
5018+
<< maxDepth << ", " << maxLoopIterations << ");\n";
5019+
}
5020+
return runner;
5021+
}
5022+
5023+
int ExpressionRunnerSetLocalValue(ExpressionRunnerRef runner,
5024+
BinaryenIndex index,
5025+
BinaryenExpressionRef value) {
5026+
if (tracing) {
5027+
std::cout << " ExpressionRunnerSetLocalValue(expressionRunners["
5028+
<< expressionRunners[runner] << "], " << index << ", expressions["
5029+
<< expressions[value] << "]);\n";
5030+
}
5031+
5032+
auto* R = (CExpressionRunner*)runner;
5033+
auto setFlow = R->visit(value);
5034+
if (!setFlow.breaking()) {
5035+
R->setLocalValue(index, setFlow.values);
5036+
return 1;
5037+
}
5038+
return 0;
5039+
}
5040+
5041+
int ExpressionRunnerSetGlobalValue(ExpressionRunnerRef runner,
5042+
const char* name,
5043+
BinaryenExpressionRef value) {
5044+
if (tracing) {
5045+
std::cout << " ExpressionRunnerSetGlobalValue(expressionRunners["
5046+
<< expressionRunners[runner] << "], ";
5047+
traceNameOrNULL(name);
5048+
std::cout << ", expressions[" << expressions[value] << "]);\n";
5049+
}
5050+
5051+
auto* R = (CExpressionRunner*)runner;
5052+
auto setFlow = R->visit(value);
5053+
if (!setFlow.breaking()) {
5054+
R->setGlobalValue(name, setFlow.values);
5055+
return 1;
5056+
}
5057+
return 0;
5058+
}
5059+
5060+
BinaryenExpressionRef
5061+
ExpressionRunnerRunAndDispose(ExpressionRunnerRef runner,
5062+
BinaryenExpressionRef expr) {
5063+
auto* R = (CExpressionRunner*)runner;
5064+
Expression* ret = nullptr;
5065+
try {
5066+
auto flow = R->visit(expr);
5067+
if (!flow.breaking() && !flow.values.empty()) {
5068+
ret = flow.getConstExpression(*R->getModule());
5069+
}
5070+
} catch (CExpressionRunner::NonconstantException&) {
5071+
}
5072+
5073+
if (tracing) {
5074+
if (ret != nullptr) {
5075+
auto id = noteExpression(ret);
5076+
std::cout << " expressions[" << id << "] = ";
5077+
} else {
5078+
std::cout << " ";
5079+
}
5080+
auto id = expressionRunners[runner];
5081+
std::cout << "ExpressionRunnerRunAndDispose(expressionRunners[" << id
5082+
<< "], expressions[" << expressions[expr] << "]);\n";
5083+
usedExpressionRunnerIds.erase(id);
5084+
}
5085+
5086+
delete R;
5087+
return ret;
5088+
}
5089+
49535090
//
49545091
// ========= Other APIs =========
49555092
//
@@ -4970,6 +5107,7 @@ void BinaryenSetAPITracing(int on) {
49705107
" std::map<size_t, BinaryenEventRef> events;\n"
49715108
" std::map<size_t, BinaryenExportRef> exports;\n"
49725109
" std::map<size_t, RelooperBlockRef> relooperBlocks;\n"
5110+
" std::map<size_t, ExpressionRunnerRef> expressionRunners;\n"
49735111
" BinaryenModuleRef the_module = NULL;\n"
49745112
" RelooperRef the_relooper = NULL;\n";
49755113
} else {

src/binaryen-c.h

+61
Original file line numberDiff line numberDiff line change
@@ -1642,6 +1642,67 @@ BINARYEN_API void RelooperAddBranchForSwitch(RelooperBlockRef from,
16421642
BINARYEN_API BinaryenExpressionRef RelooperRenderAndDispose(
16431643
RelooperRef relooper, RelooperBlockRef entry, BinaryenIndex labelHelper);
16441644

1645+
//
1646+
// ========= ExpressionRunner ==========
1647+
//
1648+
1649+
#ifdef __cplusplus
1650+
namespace wasm {
1651+
class CExpressionRunner;
1652+
} // namespace wasm
1653+
typedef class wasm::CExpressionRunner* ExpressionRunnerRef;
1654+
#else
1655+
typedef struct CExpressionRunner* ExpressionRunnerRef;
1656+
#endif
1657+
1658+
typedef uint32_t ExpressionRunnerFlags;
1659+
1660+
// By default, just evaluate the expression, i.e. all we want to know is whether
1661+
// it computes down to a concrete value, where it is not necessary to preserve
1662+
// side effects like those of a `local.tee`.
1663+
BINARYEN_API ExpressionRunnerFlags ExpressionRunnerFlagsDefault();
1664+
1665+
// Be very careful to preserve any side effects. For example, if we are
1666+
// intending to replace the expression with a constant afterwards, even if we
1667+
// can technically evaluate down to a constant, we still cannot replace the
1668+
// expression if it also sets a local, which must be preserved in this scenario
1669+
// so subsequent code keeps functioning.
1670+
BINARYEN_API ExpressionRunnerFlags ExpressionRunnerFlagsPreserveSideeffects();
1671+
1672+
// Traverse through function calls, attempting to compute their concrete value.
1673+
// Must not be used in function-parallel scenarios, where the called function
1674+
// might be concurrently modified, leading to undefined behavior. Traversing
1675+
// another function reuses all of this runner's flags.
1676+
BINARYEN_API ExpressionRunnerFlags ExpressionRunnerFlagsTraverseCalls();
1677+
1678+
// Creates an ExpressionRunner instance
1679+
BINARYEN_API ExpressionRunnerRef
1680+
ExpressionRunnerCreate(BinaryenModuleRef module,
1681+
ExpressionRunnerFlags flags,
1682+
BinaryenIndex maxDepth,
1683+
BinaryenIndex maxLoopIterations);
1684+
1685+
// Sets a known local value to use. Order matters if expressions have side
1686+
// effects. For example, if the expression also sets a local, this side effect
1687+
// will also happen (not affected by any flags). Returns `true` if the
1688+
// expression actually evaluates to a constant.
1689+
BINARYEN_API int ExpressionRunnerSetLocalValue(ExpressionRunnerRef runner,
1690+
BinaryenIndex index,
1691+
BinaryenExpressionRef value);
1692+
1693+
// Sets a known global value to use. Order matters if expressions have side
1694+
// effects. For example, if the expression also sets a local, this side effect
1695+
// will also happen (not affected by any flags). Returns `true` if the
1696+
// expression actually evaluates to a constant.
1697+
BINARYEN_API int ExpressionRunnerSetGlobalValue(ExpressionRunnerRef runner,
1698+
const char* name,
1699+
BinaryenExpressionRef value);
1700+
1701+
// Runs the expression and returns the constant value expression it evaluates
1702+
// to, if any. Otherwise returns `NULL`. Also disposes the runner.
1703+
BINARYEN_API BinaryenExpressionRef ExpressionRunnerRunAndDispose(
1704+
ExpressionRunnerRef runner, BinaryenExpressionRef expr);
1705+
16451706
//
16461707
// ========= Other APIs =========
16471708
//

src/js/binaryen.js-post.js

+25
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,13 @@ function initializeConstants() {
485485
].forEach(function(name) {
486486
Module['SideEffects'][name] = Module['_BinaryenSideEffect' + name]();
487487
});
488+
489+
// ExpressionRunner flags
490+
Module['ExpressionRunner']['Flags'] = {
491+
'Default': Module['_ExpressionRunnerFlagsDefault'](),
492+
'PreserveSideeffects': Module['_ExpressionRunnerFlagsPreserveSideeffects'](),
493+
'TraverseCalls': Module['_ExpressionRunnerFlagsTraverseCalls']()
494+
};
488495
}
489496

490497
// 'Module' interface
@@ -2427,6 +2434,24 @@ Module['Relooper'] = function(module) {
24272434
};
24282435
};
24292436

2437+
// 'ExpressionRunner' interface
2438+
Module['ExpressionRunner'] = function(module, flags, maxDepth, maxLoopIterations) {
2439+
var runner = Module['_ExpressionRunnerCreate'](module['ptr'], flags, maxDepth, maxLoopIterations);
2440+
this['ptr'] = runner;
2441+
2442+
this['setLocalValue'] = function(index, valueExpr) {
2443+
return Boolean(Module['_ExpressionRunnerSetLocalValue'](runner, index, valueExpr));
2444+
};
2445+
this['setGlobalValue'] = function(name, valueExpr) {
2446+
return preserveStack(function() {
2447+
return Boolean(Module['_ExpressionRunnerSetGlobalValue'](runner, strToStack(name), valueExpr));
2448+
});
2449+
};
2450+
this['runAndDispose'] = function(expr) {
2451+
return Module['_ExpressionRunnerRunAndDispose'](runner, expr);
2452+
};
2453+
};
2454+
24302455
function getAllNested(ref, numFn, getFn) {
24312456
var num = numFn(ref);
24322457
var ret = new Array(num);

0 commit comments

Comments
 (0)