How to make coroutines in Wasm?

Viewed 759

I'm trying to allow users to call "main" in C and have "main" wait for wait for a requestAnimationFrame to continue execution. I.e. main could have a while(1) loop, and in main, there would be a "waitForAnimationFrame" ... or allow a function like sleep(...) to be implemented.

I've been digging through the innerworkings of how emscripten actually does this and it doesn't quite look suitable for my purposes. This page goes into a number of things which are PAINFULLY close to what I want to do, but none of them seem to actually work when implemented this way. https://kripken.github.io/blog/wasm/2019/07/16/asyncify.html

More specifically, it seems the best way would be to save the stack, and "return" back through a dummy function in Wasm, then when the event happens in javascript, I could call a dummy function but return back to the correct place. But, this would require the call stack which seems not to be part of actual STACKTOP, or available through stackSave and stackRestore. Most notably, no matter what I seem to do setThrew doesn't seem to work at all. I.e. it just immediately returns.

I've also tried to use some of the start/stop unwind functions but have been unable to make progress there as I get errors when returning back into Wasm from Javascript.

Any ideas here? I can't really make main asynchronous the way it seems some people want and expect it to still work the same.

EDIT: Ok, here are some of the examples of what I'm testing.

EXAMPLE 1: SAVE/RESTORE STACK

Having these two functions in C:

void waittask()
{
    volatile int sentinel = 85755555;
    consolelog("waittask1");
    waitout();
    consolelog("waittask2");
}

void waitret()
{
    volatile int sentinel = 12994423;
    consolelog("waitret1");
    stacksetup(sentinel);
    stacksetup(sentinel);
    consolelog("waitret2");
}

With JavaScript functions:


            stacksetup : function() {
                console.log( "STACK SETUP" );
                STACK_QUICKRET = g_wa.instance.exports.stackSave();
                memory_words = new Int32Array(g_wa.instance.exports.memory.buffer);
                console.log( "VALUE: " + memory_words[(STACK_QUICKRET>>2)] );
                console.log( "VALUE: " + memory_words[(STACK_QUICKRET>>2)-1] );
                console.log( "STACK:" + STACK_QUICKRET);
                g_wa.instance.exports.setThrew(0,1);
            },
            waitout : function()
            {
                console.log( "WAIT OUT" );
                setTimeout( function() {
                    console.log( "Restoring" );
                    g_wa.instance.exports.stackRestore( STACK_DORET );
                    g_wa.instance.exports.waitret();
                }, 1000 );
                STACK_DORET = g_wa.instance.exports.stackSave();
                g_wa.instance.exports.stackRestore( STACK_QUICKRET );
                g_wa.instance.exports.setThrew(0,1);
            },

Where "waitret()" is called first to provide an entry point for quick exit, where the stack is saved by "stacksetup"... And waittask() which calls "waitout()" in JavaScript, in an effort to change the stack and shortcut out of waitret.

It is important to note that I've called stacksetup twice so I can examine the stack to see if there is any difference in the times called. Since it was called from two different locations, one would expect the stack to be different, but that is not the case, as it seems Wasm stores the callstack separately from the data stack.

The above code will always return back through the function from whence it came, instead of calling back to where I want it to return.

Other attempt:

Using the C functions


void mymain()
{
    debug(0);
    sprintf( ct, "PART 1 %f\n", OGGetAbsoluteTime() );
    writestr( ct );
    debug(1);
    jssleep(1000,&DATA_ADDR, _STACKTOP, &DATA_ADDR);
    debug(2);
}


void Wmain( int argc, char ** argv )
{
    int ss;
    stack_start = &ss;
    mymain();
}

And the JavaScript function

            jssleep : function(ms, DATA_ADDR, stack_start, stack_end )
            {
              var sp = wasmExports.stackSave();
              if (!sleeping) {
                // We are called in order to start a sleep/unwind.
                console.log('sleep... ('+ms+','+DATA_ADDR+',<' + sp + ',' + stacktop + '>' + stack_start + "," + stack_end + ')');
                console.log( instance );
                // Fill in the data structure. The first value has the stack location,
                // which for simplicity we can start right after the data structure itself.
                memory_words[DATA_ADDR >> 2] = sp;
                // The end of the stack will not be reached here anyhow.
                memory_words[DATA_ADDR + 4 >> 2] = stacktop;
                wasmExports.asyncify_start_unwind(DATA_ADDR);
                sleeping = true;
                // Resume after the proper delay.
                setTimeout(function() {
                  console.log('timeout ended, starting to rewind the stack');
                  wasmExports.asyncify_start_rewind(DATA_ADDR);
                    console.log('calling back into main' );
                  // The code is now ready to rewind; to start the process, enter the
                  // first function that should be on the call stack.
                    wasmExports.stackRestore(sp);
            //      wasmExports.setThrew(0,1);
                      wasmExports.mymain(0);
                    console.log( "should not not get called\n" );
                }, ms);
              } else {
                // We are called as part of a resume/rewind. Stop sleeping.
                console.log('...resume');
                wasmExports.asyncify_stop_rewind();
                sleeping = false;
              }
            },

I get similar behavior - the mymain() function flies right through, then when the callback is called, I get this error:

Uncaught RuntimeError: indirect call to null
    jssleep file:///home/cnlohr/git/wasm_integrated/c-test.html:793
0 Answers
Related