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

emscripten::val fails in global scope constructors #23170

Open
slowriot opened this issue Dec 15, 2024 · 5 comments
Open

emscripten::val fails in global scope constructors #23170

slowriot opened this issue Dec 15, 2024 · 5 comments

Comments

@slowriot
Copy link
Contributor

Tested with 3.1.73.

Attempting to use val in constructors executed at global scope (before main begins) fails with emval::as has unknown type errors.

Minimal example:

#include <iostream>
#include <emscripten/emscripten.h>
#include <emscripten/val.h>

struct test_struct {
  test_struct() {
    std::cout << "nagivator.userAgent " << emscripten::val::global("navigator")["userAgent"].as<std::string>() << std::endl;
    std::cout << "window.innerWidth " << emscripten::val::global("window")["innerWidth"].as<unsigned int>() << std::endl;
  }
};

// fails when constructed in global scope:
test_struct test;

auto main()->int {
  // OK :
  //test_struct test;

  // also OK:
  //static test_struct test;

  std::cout << "Hello world" << std::endl;
  return EXIT_SUCCESS;
}

Build with --bind and -sENVIRONMENT=web.

Expected output, and output when test_struct is initialised inside main:
image

Output when trying to initialise at global scope, for the .as<std::string>():
image
and for the .as<unsigned int>():
image

I'm guessing that something gets initialised before main runs, but after static construction completes. Whereas ideally, everything should be ready before any C++ objects are constructed at static scope too.

@sbc100
Copy link
Collaborator

sbc100 commented Dec 15, 2024

Hmm, IIRC we do all the embind type registration because user-level constructors are called, but I looks like the ordering is wrong here.

Actually looking at the code it looks like we do use a C++ constructor to register the types:

// EMSCRIPTEN_BINDINGS creates a static struct to initialize the binding which
// will get included in the program if the translation unit in which it is
// defined gets linked into the program. Using a C++ constructor here ensures it
// occurs after any other C++ constructors in this file, which is not true for
// __attribute__((constructor)) (they run before C++ constructors in the same
// file).
#define EMSCRIPTEN_BINDINGS(name) \
static void embind_init_##name(); \
static struct EmBindInit_##name : emscripten::internal::InitFunc { \
EmBindInit_##name() : InitFunc(embind_init_##name) {} \
} EmBindInit_##name##_instance; \
static void embind_init_##name()
} // end namespace emscripten
. So because ordering of C++ constructors between object files is not something we can control I think that means the emval is not currently usable in C++ constructors (is that right @brendandahl ?)

@slowriot
Copy link
Contributor Author

To be clear, val works fine in the constructor as long as test_struct is initialised in main() or in objects initialised after main begins. It only fails for objects with static storage duration at global scope. Function-scope statics are fine too, presumably because they initialise after main() begins. So it's definitely not an issue for all constructors.

@sbc100
Copy link
Collaborator

sbc100 commented Dec 16, 2024

So it's definitely not an issue for all constructors.

Sorry I mean "static constructor" in the sense of "constructors that run before main runs". The reason is that embind is itself initialized by such a "static constructor", and you have no way of ensuring that it happens before you one does. You could get lucky, or try to play with object file ordering on the command line.. but that super fragile.

@brendandahl
Copy link
Collaborator

So because ordering of C++ constructors between object files is not something we can control I think that means the emval is not currently usable in C++ constructors (is that right @brendandahl ?)

Yeah, it's best to avoid using emval during static constructors. We should probably add a note in the documentation or I suppose we could add an assert.

@sbc100
Copy link
Collaborator

sbc100 commented Dec 16, 2024

We used to use a C static constructor to try to work around this, but it turned out that had its own issues. See #17182.

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

3 participants