emscripten-core / emscripten

Emscripten: An LLVM-to-WebAssembly Compiler
Other
25.64k stars 3.29k forks source link

Problems calling main / imported functions with Node environment [EAGAIN exception] #21313

Open netolcc06 opened 7 months ago

netolcc06 commented 7 months ago

Hello,

I am trying to build a wasm version of the cvc5 application using Emscripten 3.1.18. I have two main objectives:

Currently I am trying to export the main function only, but I am having issues to call it implicitly passing arguments to the Module or calling cwrap/ccall. The flags I used:

-s MODULARIZE -s EXPORTED_RUNTIME_METHODS=ccall,cwrap -s ENVIRONMENT=node -s EXPORTED_FUNCTIONS=_main -s INVOKE_RUN=1 -s EXIT_RUNTIME=0 -s INCOMING_MODULE_JS_API=arguments'

For comparison purposes, if I generate a html page without the -s MODULARIZE, I can see that the input I am passing to the page is being used by the get_char function and the application works as intended. For instance, in the end you can see the sat/unsat result for the operation (check-sat).

cvc5_web_call cvc5_web_call_result

However, I couldn't make it to work with the Node version. Things I've tried:

index.js

var m = require('../cvc5.js')

let input = '(set-logic QF_LRA)\
(declare-fun x () Real)\
(declare-fun y () Real)\
(declare-fun z () Real)\
(assert (= z 0))\
(assert (= (* 3 x) y))\
(assert (= (+ 1 (* 5 x)) y))\
(assert (= (+ 7 (* 4 x)) y))\
(check-sat)';

m({
    "input": input
})

cvc5.js

var TTY = {
              ttys: [],
              init: function () {},
              shutdown: function () {},
              register: function (dev, ops) {
                  TTY.ttys[dev] = {
                      input: [Module["input"]],
                      output: [],
                      ops: ops
                  };
                  FS.registerDevice(dev, TTY.stream_ops)
              },
...

or directly in the get_char

default_tty_ops: {
              get_char: function (tty) {
                  if (!tty.input.length) {
                      var result = Module["input"];
                      if (ENVIRONMENT_IS_NODE) {
                          console.log("input 1")
                          var BUFSIZE = 256;
...

In both cases I get the exception EAGAIN: resource temporarily unavailable, read which is thrown and caught at the get_char function. Program still exits with 0.

var m = require('../cvc5.js')

// argc = 2
// argv = path to a file containing the content I was passing directly as input in the previous method.
m().then((instance) => {
    instance.cwrap('main', 'number', [2, arg])
    instance.ccall('main', 'number' , ['number', 'string'], [2, arg])
})

I also tried:

f = instance.cwrap('main', 'number', ['number', 'array'])
f([3, ['node', 'index.js', input]])

With that said, my questions are:

sbc100 commented 7 months ago

Technically, in C/C++, you should only call the main function once, so you would need to re-instantiate the module for each call to main. If you want to call a function more than once per instantiation I would create a C function with a different name. Then you could have the function take just a simple string instead of the argc, argv pair which (as you have seen) is non-trivial to pass from JS.

Having said that there are two well supported ways to pass arguments to main.

  1. Build with -sINVOKE_RUN=0 and then call .callMain([foo, bar]) on the instance.
  2. Build without -sINVOKE_RUN=0 and pass the arguments as a property on the incoming module API object:
m({'arguments': [foo, bar]).then((instance) => {
   // At this point main has already been run for you
})