2929#include " ir/import-utils.h"
3030#include " ir/literal-utils.h"
3131#include " ir/memory-utils.h"
32- #include " ir/module-utils .h"
32+ #include " ir/names .h"
3333#include " pass.h"
3434#include " support/colors.h"
3535#include " support/file.h"
4141
4242using namespace wasm ;
4343
44+ namespace {
45+
4446struct FailToEvalException {
4547 std::string why;
4648 FailToEvalException (std::string why) : why(why) {}
4749};
4850
51+ // The prefix for a recommendation, so it is aligned properly with the rest of
52+ // the output.
53+ #define RECOMMENDATION " \n recommendation: "
54+
4955// We do not have access to imported globals
5056class EvallingGlobalManager {
5157 // values of globals
@@ -66,8 +72,9 @@ class EvallingGlobalManager {
6672 if (dangerousGlobals.count (name) > 0 ) {
6773 std::string extra;
6874 if (name == " ___dso_handle" ) {
69- extra = " \n recommendation: build with -s NO_EXIT_RUNTIME=1 so that "
70- " calls to atexit that use ___dso_handle are not emitted" ;
75+ extra = RECOMMENDATION
76+ " build with -s NO_EXIT_RUNTIME=1 so that "
77+ " calls to atexit that use ___dso_handle are not emitted" ;
7178 }
7279 throw FailToEvalException (
7380 std::string (
@@ -302,10 +309,10 @@ struct CtorEvalExternalInterface : EvallingModuleInstance::ExternalInterface {
302309
303310 std::string extra;
304311 if (import ->module == ENV && import ->base == " ___cxa_atexit" ) {
305- extra = " \n recommendation: build with -s NO_EXIT_RUNTIME=1 so that calls "
306- " to atexit are not emitted" ;
312+ extra = RECOMMENDATION " build with -s NO_EXIT_RUNTIME=1 so that calls "
313+ " to atexit are not emitted" ;
307314 } else if (import ->module == WASI && !ignoreExternalInput) {
308- extra = " \n recommendation: consider --ignore-external-input" ;
315+ extra = RECOMMENDATION " consider --ignore-external-input" ;
309316 }
310317 throw FailToEvalException (std::string (" call import: " ) +
311318 import ->module .str + " ." + import ->base .str +
@@ -475,6 +482,163 @@ struct CtorEvalExternalInterface : EvallingModuleInstance::ExternalInterface {
475482 }
476483};
477484
485+ // Eval a single ctor function. Returns whether we succeeded to completely
486+ // evaluate the ctor, which means that the caller can proceed to try to eval
487+ // further ctors if there are any.
488+ bool evalCtor (EvallingModuleInstance& instance,
489+ CtorEvalExternalInterface& interface,
490+ Name funcName,
491+ Name exportName) {
492+ auto & wasm = instance.wasm ;
493+ auto * func = wasm.getFunction (funcName);
494+
495+ // We don't know the values of parameters, so give up if there are any.
496+ // TODO: Maybe use ignoreExternalInput?
497+ if (func->getNumParams () > 0 ) {
498+ std::cout << " ...stopping due to params\n " ;
499+ return false ;
500+ }
501+
502+ // TODO: Handle a return value by emitting a proper constant.
503+ if (func->getResults () != Type::none) {
504+ std::cout << " ...stopping due to results\n " ;
505+ return false ;
506+ }
507+
508+ // We want to handle the form of the global constructor function in LLVM. That
509+ // looks like this:
510+ //
511+ // (func $__wasm_call_ctors
512+ // (call $ctor.1)
513+ // (call $ctor.2)
514+ // (call $ctor.3)
515+ // )
516+ //
517+ // Some of those ctors may be inlined, however, which would mean that the
518+ // function could have locals, control flow, etc. However, we assume for now
519+ // that it does not have parameters at least (whose values we can't tell),
520+ // or results. And for now we look for a toplevel block and process its
521+ // children one at a time. This allows us to eval some of the $ctor.*
522+ // functions (or their inlined contents) even if not all.
523+ //
524+ // TODO: Support complete partial evalling, that is, evaluate parts of an
525+ // arbitrary function, and not just a sequence in a single toplevel
526+ // block.
527+
528+ if (auto * block = func->body ->dynCast <Block>()) {
529+ // Go through the items in the block and try to execute them. We do all this
530+ // in a single function scope for all the executions.
531+ EvallingModuleInstance::FunctionScope scope (func, LiteralList ());
532+
533+ EvallingModuleInstance::RuntimeExpressionRunner expressionRunner (
534+ instance, scope, instance.maxDepth );
535+
536+ // After we successfully eval a line we will apply the changes here. This is
537+ // the same idea as applyToModule() - we must only do it after an entire
538+ // atomic "chunk" has been processed, we do not want partial updates from
539+ // an item in the block that we only partially evalled.
540+ EvallingModuleInstance::FunctionScope appliedScope (func, LiteralList ());
541+
542+ Index successes = 0 ;
543+ for (auto * curr : block->list ) {
544+ Flow flow;
545+ try {
546+ flow = expressionRunner.visit (curr);
547+ } catch (FailToEvalException& fail) {
548+ if (successes == 0 ) {
549+ std::cout << " ...stopping (in block) since could not eval: "
550+ << fail.why << " \n " ;
551+ } else {
552+ std::cout << " ...partial evalling successful, but stopping since "
553+ " could not eval: "
554+ << fail.why << " \n " ;
555+ }
556+ break ;
557+ }
558+
559+ // So far so good! Apply the results.
560+ interface.applyToModule ();
561+ appliedScope = scope;
562+ successes++;
563+
564+ if (flow.breaking ()) {
565+ // We are returning out of the function (either via a return, or via a
566+ // break to |block|, which has the same outcome. That means we don't
567+ // need to execute any more lines, and can consider them to be executed.
568+ std::cout << " ...stopping in block due to break\n " ;
569+
570+ // Mark us as having succeeded on the entire block, since we have: we
571+ // are skipping the rest, which means there is no problem there. We must
572+ // set this here so that lower down we realize that we've evalled
573+ // everything.
574+ successes = block->list .size ();
575+ break ;
576+ }
577+ }
578+
579+ if (successes > 0 && successes < block->list .size ()) {
580+ // We managed to eval some but not all. That means we can't just remove
581+ // the entire function, but need to keep parts of it - the parts we have
582+ // not evalled - around. To do so, we create a copy of the function with
583+ // the partially-evalled contents and make the export use that (as the
584+ // function may be used in other places than the export, which we do not
585+ // want to affect).
586+ auto copyName = Names::getValidFunctionName (wasm, funcName);
587+ auto * copyFunc = ModuleUtils::copyFunction (func, wasm, copyName);
588+ wasm.getExport (exportName)->value = copyName;
589+
590+ // Remove the items we've evalled.
591+ Builder builder (wasm);
592+ auto * copyBlock = copyFunc->body ->cast <Block>();
593+ for (Index i = 0 ; i < successes; i++) {
594+ copyBlock->list [i] = builder.makeNop ();
595+ }
596+
597+ // Write out the values of locals, that is the local state after evalling
598+ // the things we've just nopped. For simplicity we just write out all of
599+ // locals, and leave it to the optimizer to remove redundant or
600+ // unnecessary operations.
601+ std::vector<Expression*> localSets;
602+ for (Index i = 0 ; i < copyFunc->getNumLocals (); i++) {
603+ auto value = appliedScope.locals [i];
604+ localSets.push_back (
605+ builder.makeLocalSet (i, builder.makeConstantExpression (value)));
606+ }
607+
608+ // Put the local sets at the front of the block. We know there must be a
609+ // nop in that position (since we've evalled at least one item in the
610+ // block, and replaced it with a nop), so we can overwrite it.
611+ copyBlock->list [0 ] = builder.makeBlock (localSets);
612+
613+ // Interesting optimizations may be possible both due to removing some but
614+ // not all of the code, and due to the locals we just added.
615+ PassRunner passRunner (&wasm,
616+ PassOptions::getWithDefaultOptimizationOptions ());
617+ passRunner.addDefaultFunctionOptimizationPasses ();
618+ passRunner.runOnFunction (copyFunc);
619+ }
620+
621+ // Return true if we evalled the entire block. Otherwise, even if we evalled
622+ // some of it, the caller must stop trying to eval further things.
623+ return successes == block->list .size ();
624+ }
625+
626+ // Otherwise, we don't recognize a pattern that allows us to do partial
627+ // evalling. So simply call the entire function at once and see if we can
628+ // optimize that.
629+ try {
630+ instance.callFunction (funcName, LiteralList ());
631+ } catch (FailToEvalException& fail) {
632+ std::cout << " ...stopping since could not eval: " << fail.why << " \n " ;
633+ return false ;
634+ }
635+
636+ // Success! Apply the results.
637+ interface.applyToModule ();
638+ return true ;
639+ }
640+
641+ // Eval all ctors in a module.
478642void evalCtors (Module& wasm, std::vector<std::string> ctors) {
479643 std::map<Name, std::shared_ptr<EvallingModuleInstance>> linkedInstances;
480644
@@ -505,26 +669,15 @@ void evalCtors(Module& wasm, std::vector<std::string> ctors) {
505669 if (!ex) {
506670 Fatal () << " export not found: " << ctor;
507671 }
508- try {
509- instance.callFunction (ex->value , LiteralList ());
510- } catch (FailToEvalException& fail) {
511- // that's it, we failed, so stop here, cleaning up partial
512- // memory changes first
513- std::cout << " ...stopping since could not eval: " << fail.why << " \n " ;
672+ auto funcName = ex->value ;
673+ if (!evalCtor (instance, interface, funcName, ctor)) {
674+ std::cout << " ...stopping\n " ;
514675 return ;
515676 }
516- std::cout << " ...success on " << ctor << " .\n " ;
517677
518- // Success, the entire function was evalled! Apply the results of
519- // execution to the module.
520- interface.applyToModule ();
521-
522- // we can nop the function (which may be used elsewhere)
523- // and remove the export
524- auto * exp = wasm.getExport (ctor);
525- auto * func = wasm.getFunction (exp->value );
526- func->body = wasm.allocator .alloc <Nop>();
527- wasm.removeExport (exp->name );
678+ // Success! Remove the export, and continue.
679+ std::cout << " ...success on " << ctor << " .\n " ;
680+ wasm.removeExport (ctor);
528681 }
529682 } catch (FailToEvalException& fail) {
530683 // that's it, we failed to even create the instance
@@ -534,6 +687,8 @@ void evalCtors(Module& wasm, std::vector<std::string> ctors) {
534687 }
535688}
536689
690+ } // anonymous namespace
691+
537692//
538693// main
539694//
@@ -629,6 +784,7 @@ int main(int argc, const char* argv[]) {
629784 {
630785 PassRunner passRunner (&wasm);
631786 passRunner.add (" memory-packing" ); // we flattened it, so re-optimize
787+ // TODO: just do -Os for the one function
632788 passRunner.add (" remove-unused-names" );
633789 passRunner.add (" dce" );
634790 passRunner.add (" merge-blocks" );
0 commit comments