context icon indicating copy to clipboard operation
context copied to clipboard

WebAssembly support is required

Open Klayflash opened this issue 6 years ago • 23 comments

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

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

Klayflash avatar Jul 16 '19 12:07 Klayflash

no - it isn't

olk avatar Jul 16 '19 13:07 olk

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 avatar Jul 16 '19 13:07 Klayflash

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;
}

Klayflash avatar Jul 21 '19 14:07 Klayflash

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

unicomp21 avatar Jan 26 '20 19:01 unicomp21

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.

unicomp21 avatar Jan 26 '20 19:01 unicomp21

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.

olk avatar Jan 27 '20 05:01 olk

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

unicomp21 avatar Jan 28 '20 00:01 unicomp21

Perhaps we could write a test using emscripten?

unicomp21 avatar Jan 28 '20 00:01 unicomp21

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.

unicomp21 avatar Jan 28 '20 00:01 unicomp21

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.

olk avatar Jan 29 '20 05:01 olk

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

https://github.com/wasm3/wasm3

unicomp21 avatar Jan 29 '20 11:01 unicomp21

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

Akaricchi avatar Mar 06 '20 22:03 Akaricchi

you are welcome to provide a patch

olk avatar Apr 24 '20 09:04 olk

I think this is not possible.

olk avatar Jul 13 '21 19:07 olk

The feature is not planned more?

Klayflash avatar Jul 14 '21 06:07 Klayflash

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

olk avatar Jul 14 '21 08:07 olk

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

unicomp21 avatar Jul 14 '21 11:07 unicomp21

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 avatar Jul 14 '21 13:07 olk

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

unicomp21 avatar Jul 14 '21 13:07 unicomp21

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.

Akaricchi avatar Jul 15 '21 03:07 Akaricchi

I'll take a look at it.

olk avatar Jul 15 '21 04:07 olk

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

olk avatar Jul 15 '21 05:07 olk

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.

Akaricchi avatar Jul 15 '21 23:07 Akaricchi