Skip to content

Refactor expression runner so it can be used via the C and JS APIs #2702

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

Merged
merged 30 commits into from
Apr 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
132cb1d
Derive standalone expression runner from precompute pass
dcodeIO Mar 18, 2020
bbe1dc5
handle traps, format
dcodeIO Mar 18, 2020
3f3c02d
fix?
dcodeIO Mar 18, 2020
461576d
update tests
dcodeIO Mar 18, 2020
f5e3837
refactor replaceExpresion to an enum
dcodeIO Mar 19, 2020
2ccc81d
fix expressions[0] in tracing, track temporary local values
dcodeIO Mar 19, 2020
30bd10c
simplify
dcodeIO Mar 20, 2020
ca3ed47
implement preset local/global values
dcodeIO Mar 20, 2020
a853db9
could need some format on save
dcodeIO Mar 20, 2020
66d23f1
address comments
dcodeIO Mar 20, 2020
35d09c2
more documentation for getValues
dcodeIO Mar 20, 2020
f57cbdc
refactor runner to its own cpp file
dcodeIO Mar 21, 2020
63d7520
traverse into simple functions
dcodeIO Mar 21, 2020
5432156
deal with non-determinism
dcodeIO Mar 21, 2020
ec0a93d
update API, add test
dcodeIO Mar 21, 2020
1ceb5b5
retrigger CI
dcodeIO Mar 21, 2020
d145cfc
fix comment
dcodeIO Mar 23, 2020
153d10f
address review comments
dcodeIO Mar 24, 2020
7d51f30
address review comments
dcodeIO Mar 25, 2020
cabf038
Merge branch 'master' into expressionrunner
dcodeIO Apr 1, 2020
ed62e30
refactor
dcodeIO Apr 1, 2020
803cd22
reuse existing runner
dcodeIO Apr 2, 2020
597c5fa
revert trap on invalid
dcodeIO Apr 2, 2020
b4ca977
address comments
dcodeIO Apr 3, 2020
dcbab6a
address comments
dcodeIO Apr 3, 2020
4c8fc7c
address (most) comments
dcodeIO Apr 7, 2020
8586c57
mention interaction between TraverseCalls and PreserveSideEffects
dcodeIO Apr 7, 2020
1184f58
Merge branch 'master' into expressionrunner
dcodeIO Apr 15, 2020
17e5de0
address comments
dcodeIO Apr 16, 2020
84c27ac
Merge branch 'master' into expressionrunner
dcodeIO Apr 17, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions src/binaryen-c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ std::map<BinaryenGlobalRef, size_t> globals;
std::map<BinaryenEventRef, size_t> events;
std::map<BinaryenExportRef, size_t> exports;
std::map<RelooperBlockRef, size_t> relooperBlocks;
std::map<ExpressionRunnerRef, size_t> expressionRunners;

static bool isBasicAPIType(BinaryenType type) {
return type == BinaryenTypeAuto() || type <= Type::_last_value_type;
Expand Down Expand Up @@ -208,6 +209,26 @@ size_t noteExpression(BinaryenExpressionRef expression) {
return id;
}

// Even though unlikely, it is possible that we are trying to use an id that is
// still in use after wrapping around, which we must prevent.
static std::unordered_set<size_t> usedExpressionRunnerIds;

size_t noteExpressionRunner(ExpressionRunnerRef runner) {
// We would normally use the size of `expressionRunners` as the next index,
// but since we are going to delete runners the same address can become
// reused, which would result in unpredictable sizes (indexes) due to
// undefined behavior. Use a sequential id instead.
static size_t nextId = 0;

size_t id;
do {
id = nextId++;
} while (usedExpressionRunnerIds.find(id) != usedExpressionRunnerIds.end());
expressionRunners[runner] = id;
usedExpressionRunnerIds.insert(id);
return id;
}

std::string getTemp() {
static size_t n = 0;
return "t" + std::to_string(n++);
Expand Down Expand Up @@ -604,6 +625,7 @@ void BinaryenModuleDispose(BinaryenModuleRef module) {
std::cout << " events.clear();\n";
std::cout << " exports.clear();\n";
std::cout << " relooperBlocks.clear();\n";
std::cout << " expressionRunners.clear();\n";
types.clear();
expressions.clear();
functions.clear();
Expand Down Expand Up @@ -4950,6 +4972,121 @@ BinaryenExpressionRef RelooperRenderAndDispose(RelooperRef relooper,
return BinaryenExpressionRef(ret);
}

//
// ========= ExpressionRunner =========
//

namespace wasm {

class CExpressionRunner final : public ExpressionRunner<CExpressionRunner> {
public:
CExpressionRunner(Module* module,
CExpressionRunner::Flags flags,
Index maxDepth,
Index maxLoopIterations)
: ExpressionRunner<CExpressionRunner>(
module, flags, maxDepth, maxLoopIterations) {}

void trap(const char* why) override { throw NonconstantException(); }
};

} // namespace wasm

ExpressionRunnerFlags ExpressionRunnerFlagsDefault() {
return CExpressionRunner::FlagValues::DEFAULT;
}

ExpressionRunnerFlags ExpressionRunnerFlagsPreserveSideeffects() {
return CExpressionRunner::FlagValues::PRESERVE_SIDEEFFECTS;
}

ExpressionRunnerFlags ExpressionRunnerFlagsTraverseCalls() {
return CExpressionRunner::FlagValues::TRAVERSE_CALLS;
}

ExpressionRunnerRef ExpressionRunnerCreate(BinaryenModuleRef module,
ExpressionRunnerFlags flags,
BinaryenIndex maxDepth,
BinaryenIndex maxLoopIterations) {
auto* wasm = (Module*)module;
auto* runner = ExpressionRunnerRef(
new CExpressionRunner(wasm, flags, maxDepth, maxLoopIterations));
if (tracing) {
auto id = noteExpressionRunner(runner);
std::cout << " expressionRunners[" << id
<< "] = ExpressionRunnerCreate(the_module, " << flags << ", "
<< maxDepth << ", " << maxLoopIterations << ");\n";
}
return runner;
}

int ExpressionRunnerSetLocalValue(ExpressionRunnerRef runner,
BinaryenIndex index,
BinaryenExpressionRef value) {
if (tracing) {
std::cout << " ExpressionRunnerSetLocalValue(expressionRunners["
<< expressionRunners[runner] << "], " << index << ", expressions["
<< expressions[value] << "]);\n";
}

auto* R = (CExpressionRunner*)runner;
auto setFlow = R->visit(value);
if (!setFlow.breaking()) {
R->setLocalValue(index, setFlow.values);
return 1;
}
return 0;
}

int ExpressionRunnerSetGlobalValue(ExpressionRunnerRef runner,
const char* name,
BinaryenExpressionRef value) {
if (tracing) {
std::cout << " ExpressionRunnerSetGlobalValue(expressionRunners["
<< expressionRunners[runner] << "], ";
traceNameOrNULL(name);
std::cout << ", expressions[" << expressions[value] << "]);\n";
}

auto* R = (CExpressionRunner*)runner;
auto setFlow = R->visit(value);
if (!setFlow.breaking()) {
R->setGlobalValue(name, setFlow.values);
return 1;
}
return 0;
}

BinaryenExpressionRef
ExpressionRunnerRunAndDispose(ExpressionRunnerRef runner,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the runner fails, it seems like it would be useful to expose more information to the caller about why it failed. That way the user could choose to increase the depth or loop count, if applicable. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, good question. Seems like this might be a bit too much, considering how it complicates the API. For instance, on the AssemblyScript side I expect to always use a reasonable maxDepth (or none) and give up otherwise as there is no reason to make an exception using larger limits. Would have used that limit right away then.

BinaryenExpressionRef expr) {
auto* R = (CExpressionRunner*)runner;
Expression* ret = nullptr;
try {
auto flow = R->visit(expr);
if (!flow.breaking() && !flow.values.empty()) {
ret = flow.getConstExpression(*R->getModule());
}
} catch (CExpressionRunner::NonconstantException&) {
}

if (tracing) {
if (ret != nullptr) {
auto id = noteExpression(ret);
std::cout << " expressions[" << id << "] = ";
} else {
std::cout << " ";
}
auto id = expressionRunners[runner];
std::cout << "ExpressionRunnerRunAndDispose(expressionRunners[" << id
<< "], expressions[" << expressions[expr] << "]);\n";
usedExpressionRunnerIds.erase(id);
}

delete R;
return ret;
}

//
// ========= Other APIs =========
//
Expand All @@ -4970,6 +5107,7 @@ void BinaryenSetAPITracing(int on) {
" std::map<size_t, BinaryenEventRef> events;\n"
" std::map<size_t, BinaryenExportRef> exports;\n"
" std::map<size_t, RelooperBlockRef> relooperBlocks;\n"
" std::map<size_t, ExpressionRunnerRef> expressionRunners;\n"
" BinaryenModuleRef the_module = NULL;\n"
" RelooperRef the_relooper = NULL;\n";
} else {
Expand Down
61 changes: 61 additions & 0 deletions src/binaryen-c.h
Original file line number Diff line number Diff line change
Expand Up @@ -1642,6 +1642,67 @@ BINARYEN_API void RelooperAddBranchForSwitch(RelooperBlockRef from,
BINARYEN_API BinaryenExpressionRef RelooperRenderAndDispose(
RelooperRef relooper, RelooperBlockRef entry, BinaryenIndex labelHelper);

//
// ========= ExpressionRunner ==========
//

#ifdef __cplusplus
namespace wasm {
class CExpressionRunner;
} // namespace wasm
typedef class wasm::CExpressionRunner* ExpressionRunnerRef;
#else
typedef struct CExpressionRunner* ExpressionRunnerRef;
#endif

typedef uint32_t ExpressionRunnerFlags;

// By default, just evaluate the expression, i.e. all we want to know is whether
// it computes down to a concrete value, where it is not necessary to preserve
// side effects like those of a `local.tee`.
BINARYEN_API ExpressionRunnerFlags ExpressionRunnerFlagsDefault();

// Be very careful to preserve any side effects. For example, if we are
// intending to replace the expression with a constant afterwards, even if we
// can technically evaluate down to a constant, we still cannot replace the
// expression if it also sets a local, which must be preserved in this scenario
// so subsequent code keeps functioning.
BINARYEN_API ExpressionRunnerFlags ExpressionRunnerFlagsPreserveSideeffects();

// Traverse through function calls, attempting to compute their concrete value.
// Must not be used in function-parallel scenarios, where the called function
// might be concurrently modified, leading to undefined behavior. Traversing
// another function reuses all of this runner's flags.
BINARYEN_API ExpressionRunnerFlags ExpressionRunnerFlagsTraverseCalls();

// Creates an ExpressionRunner instance
BINARYEN_API ExpressionRunnerRef
ExpressionRunnerCreate(BinaryenModuleRef module,
ExpressionRunnerFlags flags,
BinaryenIndex maxDepth,
BinaryenIndex maxLoopIterations);

// Sets a known local value to use. Order matters if expressions have side
// effects. For example, if the expression also sets a local, this side effect
// will also happen (not affected by any flags). Returns `true` if the
// expression actually evaluates to a constant.
BINARYEN_API int ExpressionRunnerSetLocalValue(ExpressionRunnerRef runner,
BinaryenIndex index,
BinaryenExpressionRef value);

// Sets a known global value to use. Order matters if expressions have side
// effects. For example, if the expression also sets a local, this side effect
// will also happen (not affected by any flags). Returns `true` if the
// expression actually evaluates to a constant.
BINARYEN_API int ExpressionRunnerSetGlobalValue(ExpressionRunnerRef runner,
const char* name,
BinaryenExpressionRef value);

// Runs the expression and returns the constant value expression it evaluates
// to, if any. Otherwise returns `NULL`. Also disposes the runner.
BINARYEN_API BinaryenExpressionRef ExpressionRunnerRunAndDispose(
ExpressionRunnerRef runner, BinaryenExpressionRef expr);

//
// ========= Other APIs =========
//
Expand Down
25 changes: 25 additions & 0 deletions src/js/binaryen.js-post.js
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,13 @@ function initializeConstants() {
].forEach(function(name) {
Module['SideEffects'][name] = Module['_BinaryenSideEffect' + name]();
});

// ExpressionRunner flags
Module['ExpressionRunner']['Flags'] = {
'Default': Module['_ExpressionRunnerFlagsDefault'](),
'PreserveSideeffects': Module['_ExpressionRunnerFlagsPreserveSideeffects'](),
'TraverseCalls': Module['_ExpressionRunnerFlagsTraverseCalls']()
};
}

// 'Module' interface
Expand Down Expand Up @@ -2427,6 +2434,24 @@ Module['Relooper'] = function(module) {
};
};

// 'ExpressionRunner' interface
Module['ExpressionRunner'] = function(module, flags, maxDepth, maxLoopIterations) {
var runner = Module['_ExpressionRunnerCreate'](module['ptr'], flags, maxDepth, maxLoopIterations);
this['ptr'] = runner;

this['setLocalValue'] = function(index, valueExpr) {
return Boolean(Module['_ExpressionRunnerSetLocalValue'](runner, index, valueExpr));
};
this['setGlobalValue'] = function(name, valueExpr) {
return preserveStack(function() {
return Boolean(Module['_ExpressionRunnerSetGlobalValue'](runner, strToStack(name), valueExpr));
});
};
this['runAndDispose'] = function(expr) {
return Module['_ExpressionRunnerRunAndDispose'](runner, expr);
};
};

function getAllNested(ref, numFn, getFn) {
var num = numFn(ref);
var ret = new Array(num);
Expand Down
Loading