emscripten-core / emscripten

Emscripten: An LLVM-to-WebAssembly Compiler
Other
25.37k stars 3.25k forks source link

Emscripten does not read values from C properly #22058

Closed wilyJ80 closed 4 weeks ago

wilyJ80 commented 1 month ago

Please include the following in your bug report:

Version of emscripten/emsdk: Please include the output emcc -v here

[victor@victor-82mf issue]$ emcc -v
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.55-gi
t (165133b1cc977f0b9a277e42ef809b823157189c)
clang version 19.0.0git (/startdir/llvm-project 6c7805d5d186a6d1263f90b8033ad85e
2d2633d7)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: /opt/emscripten-llvm/bin

Failing command line in full: If this is compile or link-time failure please include the full failing command along with its entire output. Not a compile or link-time failure, scanf or prompt do not work Video describing the issue

Full link command and output with -v appended: Even for runtime issues it helps a lot if you can include the full link command. Adding -v to the link command will show all of the sub-commands run which can help us diagnose your issue.

Code used in the video (with commented out sections, and replaced emscripten header path for portability:


#include <stdio.h>
#include <emscripten.h>

int main(void)
{
    // This printf is not displayed on page startup
    /* printf("Type your number: \n");
    int number; */

    // The JavaScript alert() "equivalent" of this is shown instead at startup. It is buggy: I need to press Cancel in the alert after submitting the number with OK
    //scanf("%d", &number);

    // Initial prompt for number and this message are both shown together
    //printf("You typed: %d\n", number);

    // This just doesn't work. Let's try directly calling alert() */

    /* int number = EM_ASM_INT({
        prompt('Type your number: ');
    }); */

    // This is not shown in the Emscripten template console.
    // printf("You typed: %d", number);

    // Let's try alert()
     /* EM_ASM({
        alert("JavaScript recognizes this instead. Here's the number: " + $0);
    }, number);  */
    // Passing the value of the number directly does not work.

    // At this point, what's the point? I'm just calling JavaScript
    return 0;

}
sbc100 commented 1 month ago
int number = EM_ASM_INT({
        prompt('Type your number: ');
    });

The reason this doesn't work is that the JavaScript prompt function returns a JavaScript which cannot be simply return to C/C++. You would either need to store that string in memory and then return a point to it, or you would need to convert it to a number in JavaScript before returning it (e.g. By doing Number(prompt(..))).

wilyJ80 commented 1 month ago

Thanks for the continued support. I have a lot of faith in this project. I tried the Number() trick, shown in this video, still no luck. Am I misunderstanding how easy it should be for the C and JS code to communicate?

sbc100 commented 1 month ago

What is the value returned by EM_ASM_INT in your case? You also probably need to add a return keyword before the Number(...).

For more information on communication between C and JS see: https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html

wilyJ80 commented 1 month ago

Dam, I feel kind of dumb now :rofl: kind of worked now but only with alert and prompt. But why does the printf not work? Is it kind of a hack around that console in the provided HTML template? Fixed the console errors by appending a newline to the printf strings, but I assume something funky is going on with JavaScript async execution or something like that, which is skipping the execution of the C printf()? Here's the video.

sbc100 commented 1 month ago

You always need to add a newline to your printf statements otherwise they get buffered. If you are still not seeing you printf statements in the output windows place share the full example.

wilyJ80 commented 1 month ago

Sorry, forgot to share the example from the video, here it is. Like shown in the video, it does seem the printf() statement (with correctly added newline characters) only show up after the execution of this whole code.

#include <stdio.h>
#include <emscripten.h>

int main(void)
{
    printf("DEBUG 1\n");

    int number = EM_ASM_INT({
        return (prompt('Type your number: \n'));
    });

    printf("DEBUG 2: %d\n", number);

     EM_ASM({
        alert("JavaScript recognizes this instead. Here's the number: " + $0);
    }, number); 

    printf("DEBUG 3: %d\n", number);

    return 0;

}
sbc100 commented 1 month ago

Sorry, forgot to share the example from the video, here it is. Like shown in the video, it does seem the printf() statement (with correctly added newline characters) only show up after the execution of this whole code.

#include <stdio.h>
#include <emscripten.h>

int main(void)
{
  printf("DEBUG 1\n");

  int number = EM_ASM_INT({
      return (prompt('Type your number: \n'));
  });

  printf("DEBUG 2: %d\n", number);

   EM_ASM({
      alert("JavaScript recognizes this instead. Here's the number: " + $0);
  }, number); 

  printf("DEBUG 3: %d\n", number);

  return 0;

}

I suppose that would make sense since the prompt and alert APIs block the main thread, preventing the output window from being updated. i.e. the output window requires a return to the event loop before it updates. If you write to stderr you should see it in the debug console syncronously.

wilyJ80 commented 1 month ago

From a closer inspection, just for clarification, it seems that output seems to be working fine on the JavaScript console (except when using scanf(), forget that, probably a deeper issue, which from some other issues, seems closed as wontfix). Here's shell_minimal.html which is on the installed emscripten directory. Do I need to do some JavaScript gymnastics to properly synchronize that Emscripten template page console? Because I can't write to that synchronously, not even if writing to stderr. My initial goal was just to compile a simple C program with both input and output to WASM and study the generated code, but I suppose dealing with stdin and stuff seems extra tricky. Here are previous issues dealing with the same problems related to scanf() and general stdin stuff, maybe it lights up a lightbulb or something. Maybe I'll close this issue later if no simple way is currently provided. But thanks for the help anyway!

issue 1

issue 2

<html lang="en-us">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Emscripten-Generated Code</title>
    <style>
      .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
      textarea.emscripten { font-family: monospace; width: 80%; }
      div.emscripten { text-align: center; }
      div.emscripten_border { border: 1px solid black; }
      /* the canvas *must not* have any border or padding, or mouse coords will be wrong */
      canvas.emscripten { border: 0px none; background-color: black; }

      .spinner {
        height: 50px;
        width: 50px;
        margin: 0px auto;
        -webkit-animation: rotation .8s linear infinite;
        -moz-animation: rotation .8s linear infinite;
        -o-animation: rotation .8s linear infinite;
        animation: rotation 0.8s linear infinite;
        border-left: 10px solid rgb(0,150,240);
        border-right: 10px solid rgb(0,150,240);
        border-bottom: 10px solid rgb(0,150,240);
        border-top: 10px solid rgb(100,0,200);
        border-radius: 100%;
        background-color: rgb(200,100,250);
      }
      @-webkit-keyframes rotation {
        from {-webkit-transform: rotate(0deg);}
        to {-webkit-transform: rotate(360deg);}
      }
      @-moz-keyframes rotation {
        from {-moz-transform: rotate(0deg);}
        to {-moz-transform: rotate(360deg);}
      }
      @-o-keyframes rotation {
        from {-o-transform: rotate(0deg);}
        to {-o-transform: rotate(360deg);}
      }
      @keyframes rotation {
        from {transform: rotate(0deg);}
        to {transform: rotate(360deg);}
      }

    </style>
  </head>
  <body>
    <hr/>
    <figure style="overflow:visible;" id="spinner"><div class="spinner"></div><center style="margin-top:0.5em"><strong>emscripten</strong></center></figure>
    <div class="emscripten" id="status">Downloading...</div>
    <div class="emscripten">
      <progress value="0" max="100" id="progress" hidden=1></progress>  
    </div>
    <div class="emscripten_border">
      <canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()" tabindex=-1></canvas>
    </div>
    <hr/>
    <div class="emscripten">
      <input type="checkbox" id="resize">Resize canvas
      <input type="checkbox" id="pointerLock" checked>Lock/hide mouse pointer
      &nbsp;&nbsp;&nbsp;
      <input type="button" value="Fullscreen" onclick="Module.requestFullscreen(document.getElementById('pointerLock').checked, 
                                                                                document.getElementById('resize').checked)">
    </div>

    <hr/>
    <textarea class="emscripten" id="output" rows="8"></textarea>
    <hr>
    <script type='text/javascript'>
      var statusElement = document.getElementById('status');
      var progressElement = document.getElementById('progress');
      var spinnerElement = document.getElementById('spinner');

      var Module = {
        print: (function() {
          var element = document.getElementById('output');
          if (element) element.value = ''; // clear browser cache
          return (...args) => {
            var text = args.join(' ');
            // These replacements are necessary if you render to raw HTML
            //text = text.replace(/&/g, "&amp;");
            //text = text.replace(/</g, "&lt;");
            //text = text.replace(/>/g, "&gt;");
            //text = text.replace('\n', '<br>', 'g');
            console.log(text);
            if (element) {
              element.value += text + "\n";
              element.scrollTop = element.scrollHeight; // focus on bottom
            }
          };
        })(),
        canvas: (() => {
          var canvas = document.getElementById('canvas');

          // As a default initial behavior, pop up an alert when webgl context is lost. To make your
          // application robust, you may want to override this behavior before shipping!
          // See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
          canvas.addEventListener("webglcontextlost", (e) => { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);

          return canvas;
        })(),
        setStatus: (text) => {
          if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };
          if (text === Module.setStatus.last.text) return;
          var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
          var now = Date.now();
          if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon
          Module.setStatus.last.time = now;
          Module.setStatus.last.text = text;
          if (m) {
            text = m[1];
            progressElement.value = parseInt(m[2])*100;
            progressElement.max = parseInt(m[4])*100;
            progressElement.hidden = false;
            spinnerElement.hidden = false;
          } else {
            progressElement.value = null;
            progressElement.max = null;
            progressElement.hidden = true;
            if (!text) spinnerElement.hidden = true;
          }
          statusElement.innerHTML = text;
        },
        totalDependencies: 0,
        monitorRunDependencies: (left) => {
          this.totalDependencies = Math.max(this.totalDependencies, left);
          Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
        }
      };
      Module.setStatus('Downloading...');
      window.onerror = () => {
        Module.setStatus('Exception thrown, see JavaScript console');
        spinnerElement.style.display = 'none';
        Module.setStatus = (text) => {
          if (text) console.error('[post-exception status] ' + text);
        };
      };
    </script>
    {{{ SCRIPT }}}
  </body>
</html>
sbc100 commented 4 weeks ago

If you want to build and interactive web app you cannot use a single main function that never returns.

You either need to break up the main function using emscripten_set_main_loop or using something like -sASYNCIFY. See https://emscripten.org/docs/porting/emscripten-runtime-environment.html#browser-main-loop for more information.