241241//
242242// As with --asyncify-imports, you can use a response file here.
243243//
244+ // --pass-arg=asyncify-asserts
245+ //
246+ // This enables extra asserts in the output, like checking if we in
247+ // an unwind/rewind in an invalid place (this can be helpful for manual
248+ // tweaking of the whitelist/blacklist).
249+ //
244250// TODO When wasm has GC, extending the live ranges of locals can keep things
245251// alive unnecessarily. We may want to set locals to null at the end
246252// of their original range.
@@ -363,9 +369,10 @@ class ModuleAnalyzer {
363369 std::function<bool (Name, Name)> canImportChangeState,
364370 bool canIndirectChangeState,
365371 const String::Split& blacklistInput,
366- const String::Split& whitelistInput)
372+ const String::Split& whitelistInput,
373+ bool asserts)
367374 : module (module ), canIndirectChangeState(canIndirectChangeState),
368- globals (module ) {
375+ globals (module ), asserts(asserts) {
369376
370377 blacklist.insert (blacklistInput.begin (), blacklistInput.end ());
371378 whitelist.insert (whitelistInput.begin (), whitelistInput.end ());
@@ -561,6 +568,7 @@ class ModuleAnalyzer {
561568 GlobalHelper globals;
562569 std::set<Name> blacklist;
563570 std::set<Name> whitelist;
571+ bool asserts;
564572};
565573
566574// Checks if something performs a call: either a direct or indirect call,
@@ -606,6 +614,10 @@ class AsyncifyBuilder : public Builder {
606614 makeGlobalGet (ASYNCIFY_STATE, i32 ),
607615 makeConst (Literal (int32_t (value))));
608616 }
617+
618+ Expression* makeNegatedStateCheck (State value) {
619+ return makeUnary (EqZInt32, makeStateCheck (value));
620+ }
609621};
610622
611623// Instrument control flow, around calls and adding skips for rewinding.
@@ -622,13 +634,16 @@ struct AsyncifyFlow : public Pass {
622634 runOnFunction (PassRunner* runner, Module* module_, Function* func_) override {
623635 module = module_;
624636 func = func_;
637+ builder = make_unique<AsyncifyBuilder>(*module );
625638 // If the function cannot change our state, we have nothing to do -
626639 // we will never unwind or rewind the stack here.
627640 if (!analyzer->needsInstrumentation (func)) {
641+ if (analyzer->asserts ) {
642+ addAssertsInNonInstrumented (func);
643+ }
628644 return ;
629645 }
630646 // Rewrite the function body.
631- builder = make_unique<AsyncifyBuilder>(*module );
632647 // Each function we enter will pop one from the stack, which is the index
633648 // of the next call to make.
634649 auto * block = builder->makeBlock (
@@ -841,6 +856,65 @@ struct AsyncifyFlow : public Pass {
841856 // don't want it to be seen by asyncify itself.
842857 return builder->makeCall (ASYNCIFY_GET_CALL_INDEX, {}, none);
843858 }
859+
860+ // Given a function that is not instrumented - because we proved it doesn't
861+ // need it, or depending on the whitelist/blacklist - add assertions that
862+ // verify that property at runtime.
863+ // Note that it is ok to run code while sleeping (if you are careful not
864+ // to break assumptions in the program!) - so what is actually
865+ // checked here is if the state *changes* in an uninstrumented function.
866+ // That is, if in an uninstrumented function, a sleep should not begin
867+ // from any call.
868+ void addAssertsInNonInstrumented (Function* func) {
869+ auto oldState = builder->addVar (func, i32 );
870+ // Add a check at the function entry.
871+ func->body = builder->makeSequence (
872+ builder->makeLocalSet (oldState,
873+ builder->makeGlobalGet (ASYNCIFY_STATE, i32 )),
874+ func->body );
875+ // Add a check around every call.
876+ struct Walker : PostWalker<Walker> {
877+ void visitCall (Call* curr) {
878+ // Tail calls will need another type of check, as they wouldn't reach
879+ // this assertion.
880+ assert (!curr->isReturn );
881+ handleCall (curr);
882+ }
883+ void visitCallIndirect (CallIndirect* curr) {
884+ // Tail calls will need another type of check, as they wouldn't reach
885+ // this assertion.
886+ assert (!curr->isReturn );
887+ handleCall (curr);
888+ }
889+ void handleCall (Expression* call) {
890+ auto * check = builder->makeIf (
891+ builder->makeBinary (NeInt32,
892+ builder->makeGlobalGet (ASYNCIFY_STATE, i32 ),
893+ builder->makeLocalGet (oldState, i32 )),
894+ builder->makeUnreachable ());
895+ Expression* rep;
896+ if (isConcreteType (call->type )) {
897+ auto temp = builder->addVar (func, call->type );
898+ rep = builder->makeBlock ({
899+ builder->makeLocalSet (temp, call),
900+ check,
901+ builder->makeLocalGet (temp, call->type ),
902+ });
903+ } else {
904+ rep = builder->makeSequence (call, check);
905+ }
906+ replaceCurrent (rep);
907+ }
908+ Function* func;
909+ AsyncifyBuilder* builder;
910+ Index oldState;
911+ };
912+ Walker walker;
913+ walker.func = func;
914+ walker.builder = builder.get ();
915+ walker.oldState = oldState;
916+ walker.walk (func->body );
917+ }
844918};
845919
846920// Instrument local saving/restoring.
@@ -1048,8 +1122,8 @@ struct Asyncify : public Pass {
10481122 bool allImportsCanChangeState =
10491123 stateChangingImports == " " && ignoreImports == " " ;
10501124 String::Split listedImports (stateChangingImports, " ," );
1051- auto ignoreIndirect =
1052- runner-> options . getArgumentOrDefault ( " asyncify-ignore-indirect" , " " );
1125+ auto ignoreIndirect = runner-> options . getArgumentOrDefault (
1126+ " asyncify-ignore-indirect" , " " ) == " " ;
10531127 String::Split blacklist (
10541128 String::trim (read_possible_response_file (
10551129 runner->options .getArgumentOrDefault (" asyncify-blacklist" , " " ))),
@@ -1058,6 +1132,8 @@ struct Asyncify : public Pass {
10581132 String::trim (read_possible_response_file (
10591133 runner->options .getArgumentOrDefault (" asyncify-whitelist" , " " ))),
10601134 " ," );
1135+ auto asserts =
1136+ runner->options .getArgumentOrDefault (" asyncify-asserts" , " " ) != " " ;
10611137
10621138 blacklist = handleBracketingOperators (blacklist);
10631139 whitelist = handleBracketingOperators (whitelist);
@@ -1105,9 +1181,10 @@ struct Asyncify : public Pass {
11051181 // Scan the module.
11061182 ModuleAnalyzer analyzer (*module ,
11071183 canImportChangeState,
1108- ignoreIndirect == " " ,
1184+ ignoreIndirect,
11091185 blacklist,
1110- whitelist);
1186+ whitelist,
1187+ asserts);
11111188
11121189 // Add necessary globals before we emit code to use them.
11131190 addGlobals (module );
0 commit comments