Open Klayflash opened 5 years ago
no - it isn't
Thanks for the fast answer!
Is it possible? If possible then I think it's good idea to add support.
I've found https://github.com/WebAssembly/design/blob/master/FutureFeatures.md
Coroutines will eventually be part of C++ and is already popular in other programming languages that WebAssembly will support.
I think it can be possible via emscripten_coroutine_create, emscripten_coroutine_next and emscripten_yield. I've implemented a small class Context with two tests. Compile flags:
-s ASYNCIFY=1 -s WASM=0 -std=c++17 -s DISABLE_EXCEPTION_CATCHING=0
#include <cstdio>
#include <string>
#include <cassert>
#include <sstream>
#include <boost/noncopyable.hpp>
#include <boost/current_function.hpp>
#include <emscripten.h>
using std::string;
void trace_funct(int line,const char* funct, const string& str)
{
emscripten_log( EM_LOG_CONSOLE, "%03d %s %s", line, funct, str.c_str());
}
string tostr(int v)
{
std::ostringstream ss;
ss << v;
return ss.str();
}
#define TRACE(str) trace_funct(__LINE__,BOOST_CURRENT_FUNCTION,str)
class Context
: public boost::noncopyable
{
public:
typedef void (*Funct)(void* arg);
public:
//! empty constructor gets thread context
Context()
{
assert(!s_InsideCoroutine);
assert(!s_ThreadContextPresent);
m_ThreadContext = true;
s_ThreadContextPresent = true;
m_Coroutine = 0;
}
Context(Funct funct,void* arg)
{
m_Coroutine = emscripten_coroutine_create(funct, arg, 0);
}
~Context()
{
if ( m_ThreadContext ) {
TRACE("thread context");
assert(s_ThreadContextPresent);
s_ThreadContextPresent = false;
}
else {
TRACE("coroutine context");
s_SwitchAllowed = false;
int res = emscripten_coroutine_next(m_Coroutine); // free memory
TRACE("next done");
assert(res==0);
s_SwitchAllowed = true;
}
}
void switchTo()
{
TRACE("");
assert(s_SwitchAllowed);
if ( s_InsideCoroutine ) {
TRACE("insideCoroutine");
if ( m_ThreadContext ) {
TRACE("preparing yeld to thread context");
s_Next = nullptr;
}
else {
TRACE("preparing yeld to antother coroutine");
s_Next = this;
s_SwitchRequired = true;
}
TRACE("yelding...");
emscripten_yield();
TRACE("yelding done");
}
else {
TRACE("outsideCoroutine");
assert(!m_ThreadContext);
s_Next = this;
for(;;) {
s_InsideCoroutine = true;
s_SwitchRequired = false;
TRACE("calling coroutine_next...");
int res = emscripten_coroutine_next(s_Next->m_Coroutine);
TRACE("calling coroutine_next done res="+tostr(res));
s_InsideCoroutine = false;
assert(res != 0);
if ( !s_SwitchRequired ) {
assert(!s_Next);
TRACE("exiting to thread context");
break;
}
}
}
}
private:
emscripten_coroutine m_Coroutine;
bool m_ThreadContext=false;
static bool s_ThreadContextPresent; // TODO: make thread local
static bool s_InsideCoroutine; // TODO: make thread local
static bool s_SwitchRequired; // TODO: make thread local
static bool s_SwitchAllowed; // TODO: make thread local
static Context* s_Next; // TODO: make thread local
};
// static
bool Context::s_ThreadContextPresent = false;
// static
bool Context::s_InsideCoroutine = false;
// static
bool Context::s_SwitchRequired = false;
// static
bool Context::s_SwitchAllowed = true;
// static
Context* Context::s_Next = nullptr;
//////////////////////////////////////////////////////
namespace switching_between_child_and_thread {
struct TestParams
{
Context* ctxThread;
Context* ctx1;
};
void fun1(void* arg)
{
TestParams& tp = *(TestParams*)arg;
for(int i=0;i<100;++i) {
TRACE("switching to ctxThread...");
tp.ctxThread->switchTo();
TRACE("switching to ctxThread done");
}
}
void test()
{
TRACE("");
TestParams tp;
Context ctxThread;
Context ctx1(fun1,&tp);
tp.ctxThread = &ctxThread;
tp.ctx1 = &ctx1;
for(int i=0;i<100;++i) {
TRACE("switching to ctx1...");
tp.ctx1->switchTo();
TRACE("switching to ctx1 done");
}
TRACE("return");
}
} // ns
namespace switch_between_child_contexts {
struct TestParams
{
Context* ctxThread;
Context* ctx1;
Context* ctx2;
};
void fun1(void* arg)
{
TestParams& tp = *(TestParams*)arg;
TRACE("");
for(int i=0;i<100;++i) {
TRACE("switching to ctx2...");
tp.ctx2->switchTo();
TRACE("switching to ctx2 done");
}
TRACE("switching to ctxThread...");
tp.ctxThread->switchTo();
TRACE("switching to ctxThread done");
TRACE("return");
}
void fun2(void* arg)
{
TestParams& tp = *(TestParams*)arg;
TRACE("");
for(int i=0;i<100;++i) {
TRACE("switching to ctx1...");
tp.ctx1->switchTo();
TRACE("switching to ctx1 done");
}
TRACE("return");
}
void test()
{
TRACE("");
TestParams tp;
Context ctxThread;
Context ctx1(fun1,&tp);
Context ctx2(fun2,&tp);
tp.ctxThread = &ctxThread;
tp.ctx1 = &ctx1;
tp.ctx2 = &ctx2;
TRACE("switching to ctx1");
tp.ctx1->switchTo();
TRACE("return");
}
} // ns
int main()
{
TRACE("");
switching_between_child_and_thread::test();
switch_between_child_contexts::test();
TRACE("return");
return 0;
}
clang can now target webassembly, does that change things on this front?
If we could easily make boost fibers work on clang/webassembly, a plethora of opportunities would be opened up. In addition, I think the emscripten ASYNCIFY stuff might have issues.
I'm not familiar with webassembly... boost.context does use the calling convention and does some tricks like swapping stacks. I don't know if this is applicable to webassembly.
Any idea who we could ping? I'm trying to reach Gor Nishanov, not sure if anyone else might know?
Perhaps we could write a test using emscripten?
It's hard for me to put in words what a huge deal this could be, all kinds of projects become possible within the web browser, all legacy code bases containing threads can be leveraged in the browser using fibers.
Supporitng WebAssembly requires patching LLVM and Emscripten - I think this hughe amount of work is only justified if fcontext's std-equivalent in P0876R10 has been accepted.
I wonder if it would be easier to implement initially in wasm3?
This can now be implemented for Emscripten with the new fibers API.
you are welcome to provide a patch
I think this is not possible.
The feature is not planned more?
I think it is not possible to implement support for WebAssembly.
Can we leave this open for some brave soul who might come along later?
boost.context accesses/uses the registers of the CPU while webassembly is bytecode running in a virtual machine - therefore your request makes no sense.
@olk the vm doesn't have a way to mimick registers?
It's possible with the Asyncify transform and some help from the embedder/runtime — which is what the emscripten fibers feature is all about. An emscripten-specific backend is perfectly feasible. Here is the implementation in my own C coroutine library.
I'll take a look at it.
emscripten fiber seams not to be used - at least Google couldn't find examples/usage of emscripten fibers.
I literally just linked you a usage example (we use it in the web port of Taisei Project). Here is another one from an emscripten port of the byuu emulator. Another emulator. Here is another multi-backend coroutine library that uses emscripten fibers. And another one. And here it is used in Ruby.
I also linked the documentation earlier.
Is WebAssembly (WASM) (https://webassembly.org/) supported?
C++ to WASM can be used via https://emscripten.org/.