155155// around that, you can create imports to bysyncify.start_unwind,
156156// bysyncify.stop_unwind, bysyncify.start_rewind, and bysyncify.stop_rewind;
157157// if those exist when this pass runs then it will turn those into direct
158- // calls to the functions that it creates.
158+ // calls to the functions that it creates. Note that when doing everything
159+ // in wasm like this, Bysyncify must not instrument your "runtime" support
160+ // code, that is, the code that initiates unwinds and rewinds and stops them.
161+ // If it did, the unwind would not stop until you left the wasm module
162+ // entirely, etc. Therefore we do not instrument a function if it has
163+ // a call to the four bysyncify_* methods. Note that you may need to disable
164+ // inlining if that would cause code that does need to be instrumented
165+ // show up in that runtime code.
159166//
160167// To use this API, call bysyncify_start_unwind when you want to. The call
161168// stack will then be unwound, and so execution will resume in the JS or
@@ -237,7 +244,19 @@ class ModuleAnalyzer {
237244 bool canIndirectChangeState;
238245
239246 struct Info {
247+ // If this function can start an unwind/rewind.
240248 bool canChangeState = false ;
249+ // If this function is part of the runtime that receives an unwinding
250+ // and starts a rewinding. If so, we do not instrument it, see above.
251+ // This is only relevant when handling things entirely inside wasm,
252+ // as opposed to imports.
253+ bool isBottomMostRuntime = false ;
254+ // If this function is part of the runtime that starts an unwinding
255+ // and stops a rewinding. If so, we do not instrument it, see above.
256+ // The difference between the top-most and bottom-most runtime is that
257+ // the top-most part is still marked as changing the state; so things
258+ // that call it are instrumented. This is not done for the bottom.
259+ bool isTopMostRuntime = false ;
241260 std::set<Function*> callsTo;
242261 std::set<Function*> calledBy;
243262 };
@@ -257,10 +276,9 @@ class ModuleAnalyzer {
257276 ModuleUtils::ParallelFunctionMap<Info> scanner (
258277 module , [&](Function* func, Info& info) {
259278 if (func->imported ()) {
260- // The bysyncify imports can definitely change the state.
279+ // The relevant bysyncify imports can definitely change the state.
261280 if (func->module == BYSYNCIFY &&
262- (func->base == START_UNWIND || func->base == STOP_UNWIND ||
263- func->base == START_REWIND || func->base == STOP_REWIND)) {
281+ (func->base == START_UNWIND || func->base == STOP_REWIND)) {
264282 info.canChangeState = true ;
265283 } else {
266284 info.canChangeState =
@@ -275,18 +293,22 @@ class ModuleAnalyzer {
275293 // Redirect the imports to the functions we'll add later.
276294 if (target->base == START_UNWIND) {
277295 curr->target = BYSYNCIFY_START_UNWIND;
296+ info->canChangeState = true ;
297+ info->isTopMostRuntime = true ;
278298 } else if (target->base == STOP_UNWIND) {
279299 curr->target = BYSYNCIFY_STOP_UNWIND;
300+ info->isBottomMostRuntime = true ;
280301 } else if (target->base == START_REWIND) {
281302 curr->target = BYSYNCIFY_START_REWIND;
303+ info->isBottomMostRuntime = true ;
282304 } else if (target->base == STOP_REWIND) {
283305 curr->target = BYSYNCIFY_STOP_REWIND;
284- // TODO: in theory, this does not change the state
306+ info->canChangeState = true ;
307+ info->isTopMostRuntime = true ;
285308 } else {
286309 Fatal () << " call to unidenfied bysyncify import: "
287310 << target->base ;
288311 }
289- info->canChangeState = true ;
290312 return ;
291313 }
292314 info->callsTo .insert (target);
@@ -306,7 +328,15 @@ class ModuleAnalyzer {
306328 walker.module = &module ;
307329 walker.canIndirectChangeState = canIndirectChangeState;
308330 walker.walk (func->body );
331+
332+ if (info.isBottomMostRuntime ) {
333+ info.canChangeState = false ;
334+ // TODO: issue warnings on suspicious things, like a function in
335+ // the bottom-most runtime also doing top-most runtime stuff
336+ // like starting and unwinding.
337+ }
309338 });
339+
310340 map.swap (scanner.map );
311341
312342 // Remove the bysyncify imports, if any.
@@ -338,15 +368,18 @@ class ModuleAnalyzer {
338368 while (!work.empty ()) {
339369 auto * func = work.pop ();
340370 for (auto * caller : map[func].calledBy ) {
341- if (!map[caller].canChangeState ) {
371+ if (!map[caller].canChangeState && !map[caller]. isBottomMostRuntime ) {
342372 map[caller].canChangeState = true ;
343373 work.push (caller);
344374 }
345375 }
346376 }
347377 }
348378
349- bool canChangeState (Function* func) { return map[func].canChangeState ; }
379+ bool needsInstrumentation (Function* func) {
380+ auto & info = map[func];
381+ return info.canChangeState && !info.isTopMostRuntime ;
382+ }
350383
351384 bool canChangeState (Expression* curr) {
352385 // Look inside to see if we call any of the things we know can change the
@@ -357,14 +390,17 @@ class ModuleAnalyzer {
357390 // We only implement these at the very end, but we know that they
358391 // definitely change the state.
359392 if (curr->target == BYSYNCIFY_START_UNWIND ||
360- curr->target == BYSYNCIFY_STOP_UNWIND ||
361- curr->target == BYSYNCIFY_START_REWIND ||
362393 curr->target == BYSYNCIFY_STOP_REWIND ||
363394 curr->target == BYSYNCIFY_GET_CALL_INDEX ||
364395 curr->target == BYSYNCIFY_CHECK_CALL_INDEX) {
365396 canChangeState = true ;
366397 return ;
367398 }
399+ if (curr->target == BYSYNCIFY_STOP_UNWIND ||
400+ curr->target == BYSYNCIFY_START_REWIND) {
401+ isBottomMostRuntime = true ;
402+ return ;
403+ }
368404 // The target may not exist if it is one of our temporary intrinsics.
369405 auto * target = module ->getFunctionOrNull (curr->target );
370406 if (target && (*map)[target].canChangeState ) {
@@ -381,12 +417,16 @@ class ModuleAnalyzer {
381417 Map* map;
382418 bool canIndirectChangeState;
383419 bool canChangeState = false ;
420+ bool isBottomMostRuntime = false ;
384421 };
385422 Walker walker;
386423 walker.module = &module ;
387424 walker.map = ↦
388425 walker.canIndirectChangeState = canIndirectChangeState;
389426 walker.walk (curr);
427+ if (walker.isBottomMostRuntime ) {
428+ walker.canChangeState = false ;
429+ }
390430 return walker.canChangeState ;
391431 }
392432};
@@ -451,7 +491,7 @@ struct BysyncifyFlow : public Pass {
451491 module = module_;
452492 // If the function cannot change our state, we have nothing to do -
453493 // we will never unwind or rewind the stack here.
454- if (!analyzer->canChangeState (func)) {
494+ if (!analyzer->needsInstrumentation (func)) {
455495 return ;
456496 }
457497 // Rewrite the function body.
@@ -670,7 +710,7 @@ struct BysyncifyLocals : public WalkerPass<PostWalker<BysyncifyLocals>> {
670710 void doWalkFunction (Function* func) {
671711 // If the function cannot change our state, we have nothing to do -
672712 // we will never unwind or rewind the stack here.
673- if (!analyzer->canChangeState (func-> body )) {
713+ if (!analyzer->needsInstrumentation (func)) {
674714 return ;
675715 }
676716 // Note the locals we want to preserve, before we add any more helpers.
0 commit comments