boostorg / context

299 stars 147 forks source link

WebAssembly support is required #109

Open Klayflash opened 5 years ago

Klayflash commented 5 years ago

Is WebAssembly (WASM) (https://webassembly.org/) supported?

C++ to WASM can be used via https://emscripten.org/.

olk commented 5 years ago

no - it isn't

Klayflash commented 5 years ago

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.

Klayflash commented 5 years ago

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;
}
unicomp21 commented 4 years ago

clang can now target webassembly, does that change things on this front?

unicomp21 commented 4 years ago

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.

olk commented 4 years ago

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.

unicomp21 commented 4 years ago

Any idea who we could ping? I'm trying to reach Gor Nishanov, not sure if anyone else might know?

unicomp21 commented 4 years ago

Perhaps we could write a test using emscripten?

unicomp21 commented 4 years ago

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.

olk commented 4 years ago

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.

unicomp21 commented 4 years ago

I wonder if it would be easier to implement initially in wasm3?

https://github.com/wasm3/wasm3

Akaricchi commented 4 years ago

This can now be implemented for Emscripten with the new fibers API.

olk commented 4 years ago

you are welcome to provide a patch

olk commented 3 years ago

I think this is not possible.

Klayflash commented 3 years ago

The feature is not planned more?

olk commented 3 years ago

I think it is not possible to implement support for WebAssembly.

unicomp21 commented 3 years ago

Can we leave this open for some brave soul who might come along later?

olk commented 3 years ago

boost.context accesses/uses the registers of the CPU while webassembly is bytecode running in a virtual machine - therefore your request makes no sense.

unicomp21 commented 3 years ago

@olk the vm doesn't have a way to mimick registers?

Akaricchi commented 3 years ago

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.

olk commented 3 years ago

I'll take a look at it.

olk commented 3 years ago

emscripten fiber seams not to be used - at least Google couldn't find examples/usage of emscripten fibers.

Akaricchi commented 3 years ago

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.