-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Open
Description
Bug: Passing std::optional<T>
to emscripten::val
causes marshalling error unless <emscripten/bind.h>
is included
I encountered what appears to be a marshalling bug when passing an std::optional<T>
to a emscripten::val
callback function in Emscripten.
Emscripten version: 4.0.11
Minimal repro
Files
test.h
#pragma once
#include <emscripten/val.h>
class MyType {
public:
void RunCallback(emscripten::val callback);
};
test.cpp
#include "test.h"
#include <emscripten/val.h>
// #include <emscripten/bind.h> // Adding this header resolves the behaviour
#include <optional>
#include <string>
void MyType::RunCallback(emscripten::val cb) {
cb(std::make_optional(std::string{"Hey"}));
}
embind.cpp
#include <emscripten/bind.h>
#include "test.h"
#include <string>
using namespace emscripten;
EMSCRIPTEN_BINDINGS(my_module) {
register_optional<std::string>();
class_<MyType>("MyType")
.constructor<>()
.function("RunCallback", &MyType::RunCallback);
}
test.html
<!DOCTYPE html>
<html>
<body>
<script src="test.js"></script>
<script>
MyModule().then(Module => {
let value = new Module.MyType();
value.RunCallback((e) => {
console.log(e);
});
});
</script>
</body>
</html>
I compile the whole thing with:
emcc -std=c++17 test.cpp embind.cpp -o test.js \
-O3 \
-s MODULARIZE=1 \
-s EXPORT_NAME="MyModule" \
-s ENVIRONMENT=web \
-s WASM=1 \
-s ALLOW_MEMORY_GROWTH=1 \
--bind
Results
I expected the script in test.html
to log "Hey"
to the console, but instead it logs undefined
.
When I compile everything in debug mode:
emcc -std=c++17 test.cpp embind.cpp -o test.js \
-g -O0 \
-s SAFE_HEAP=1 \
-s ASSERTIONS=2 \
-s MODULARIZE=1 \
-s EXPORT_NAME="MyModule" \
-s ENVIRONMENT=web \
-s WASM=1 \
-s ALLOW_MEMORY_GROWTH=1 \
--bind
a runtime assertion failure occurs:
test.js:531 Uncaught (in promise) RuntimeError: Aborted(Assertion failed: invalid handle: 73080)
at abort (http://localhost:8080/test.js:531:41)
at assert (http://localhost:8080/test.js:154:5)
at Object.toValue (http://localhost:8080/test.js:1955:5)
at Object.fromWireType (http://localhost:8080/test.js:1986:20)
at Object.readPointer [as readValueFromPointer] (http://localhost:8080/test.js:1484:15)
at methodCaller<(emscripten::val) => emscripten::val> (eval at __emval_create_invoker (http://localhost:8080/test.js:2549:25), <anonymous>:4:74)
at __emval_invoke (http://localhost:8080/test.js:2554:103)
at test.wasm.emscripten::val emscripten::val::internalCall<(emscripten::internal::EM_INVOKER_KIND)0, emscripten::internal::WithPolicies<>, emscripten::val, std::__2::optional<std::__2::basic_string<char, std::__2::char_traits<char>, std::__2::allocator<char>>>>(emscripten::_EM_VAL*, char const*, std::__2::optional<std::__2::basic_string<char, std::__2::char_traits<char>, std::__2::allocator<char>>>&&) (http://localhost:8080/test.wasm:wasm-function[33]:0xb43)
at test.wasm.emscripten::val emscripten::val::operator()<std::__2::optional<std::__2::basic_string<char, std::__2::char_traits<char>, std::__2::allocator<char>>>>(std::__2::optional<std::__2::basic_string<char, std::__2::char_traits<char>, std::__2::allocator<char>>>&&) const (http://localhost:8080/test.wasm:wasm-function[29]:0x911)
at test.wasm.MyType::RunCallback(emscripten::val) (http://localhost:8080/test.wasm:wasm-function[26]:0x797)
Workaround
As written in test.cpp
a workaround is including the <emscripten/bind.h>
header in the places where the callback gets called with an optional.
I'm open to contributing a fix, but would appreciate any guidance on where to start investigating, particularly around how optional types are handled in val marshalling.
Metadata
Metadata
Assignees
Labels
No labels