Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebAssembly support is required #109

Open
Klayflash opened this issue Jul 16, 2019 · 23 comments
Open

WebAssembly support is required #109

Klayflash opened this issue Jul 16, 2019 · 23 comments

Comments

@Klayflash
Copy link

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

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

@olk
Copy link
Member

olk commented Jul 16, 2019

no - it isn't

@Klayflash
Copy link
Author

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
Copy link
Author

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
Copy link

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

@unicomp21
Copy link

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
Copy link
Member

olk commented Jan 27, 2020

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
Copy link

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

@unicomp21
Copy link

Perhaps we could write a test using emscripten?

@unicomp21
Copy link

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
Copy link
Member

olk commented Jan 29, 2020

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
Copy link

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

https://github.com/wasm3/wasm3

@Akaricchi
Copy link

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

@olk
Copy link
Member

olk commented Apr 24, 2020

you are welcome to provide a patch

@olk
Copy link
Member

olk commented Jul 13, 2021

I think this is not possible.

@olk olk closed this as completed Jul 13, 2021
@Klayflash
Copy link
Author

The feature is not planned more?

@olk
Copy link
Member

olk commented Jul 14, 2021

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

@unicomp21
Copy link

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

@olk
Copy link
Member

olk commented Jul 14, 2021

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
Copy link

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

@Akaricchi
Copy link

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
Copy link
Member

olk commented Jul 15, 2021

I'll take a look at it.

@olk olk reopened this Jul 15, 2021
@olk
Copy link
Member

olk commented Jul 15, 2021

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

@Akaricchi
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants