Skip to content

Commit dd75f6a

Browse files
committed
[emval] Support dynamically-typed conversion to/from val
...for cases where a dynamically given user-defined type is known to be ABI-compatible with a statically given wire type (e.g., an enum type being compatible with its underlying type, or a struct type being compatible with `const void*`; for the latter, a returned `const void*` needs to be transformed somehow into the desired dynamic type before destructors are run and the pointed-to object is destroyed).
1 parent ec93a04 commit dd75f6a

File tree

2 files changed

+76
-20
lines changed

2 files changed

+76
-20
lines changed

src/lib/libemval.js

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -248,23 +248,33 @@ var LibraryEmVal = {
248248
249249
// Leave id 0 undefined. It's not a big deal, but might be confusing
250250
// to have null be a valid method caller.
251-
$emval_methodCallers: [undefined],
251+
$emval_methodCallers: {list: [undefined], lookup: {}},
252252
253253
$emval_addMethodCaller__deps: ['$emval_methodCallers'],
254-
$emval_addMethodCaller: (caller) => {
255-
var id = emval_methodCallers.length;
256-
emval_methodCallers.push(caller);
254+
$emval_addMethodCaller: (sig, caller) => {
255+
var id = emval_methodCallers.list.length;
256+
emval_methodCallers.list.push(caller);
257+
emval_methodCallers.lookup[sig] = id;
257258
return id;
258259
},
259260
260261
_emval_create_invoker__deps: [
261-
'$emval_addMethodCaller', '$emval_lookupTypes',
262+
'$emval_methodCallers', '$emval_addMethodCaller', '$emval_lookupTypes',
262263
'$createNamedFunction', '$emval_returnValue',
263264
'$Emval', '$getStringOrSymbol',
264265
],
265266
_emval_create_invoker: (argCount, argTypesPtr, kind) => {
266267
var GenericWireTypeSize = {{{ 2 * POINTER_SIZE }}};
267268
269+
var sig = kind.toString();
270+
for (var i = 0; i < argCount; ++i) {
271+
sig += ',' + {{{ makeGetValue('argTypesPtr', `i*${POINTER_SIZE}`, '*') }}};
272+
}
273+
var id = emval_methodCallers.lookup[sig];
274+
if (id) {
275+
return id;
276+
}
277+
268278
var [retType, ...argTypes] = emval_lookupTypes(argCount, argTypesPtr);
269279
var toReturnWire = retType.toWireType.bind(retType);
270280
var argFromPtr = argTypes.map(type => type.readValueFromPointer.bind(type));
@@ -332,12 +342,12 @@ ${functionBody}
332342
var invokerFunction = new Function(Object.keys(captures), functionBody)(...Object.values(captures));
333343
#endif
334344
var functionName = `methodCaller<(${argTypes.map(t => t.name)}) => ${retType.name}>`;
335-
return emval_addMethodCaller(createNamedFunction(functionName, invokerFunction));
345+
return emval_addMethodCaller(sig, createNamedFunction(functionName, invokerFunction));
336346
},
337347
338348
_emval_invoke__deps: ['$getStringOrSymbol', '$emval_methodCallers', '$Emval'],
339349
_emval_invoke: (caller, handle, methodName, destructorsRef, args) => {
340-
return emval_methodCallers[caller](handle, methodName, destructorsRef, args);
350+
return emval_methodCallers.list[caller](handle, methodName, destructorsRef, args);
341351
},
342352
343353
// Same as `_emval_invoke`, just imported into Wasm under a different return type.

system/include/emscripten/val.h

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <array>
1616
#include <climits>
1717
#include <emscripten/wire.h>
18+
#include <functional>
1819
#include <cstdint> // uintptr_t
1920
#include <vector>
2021
#include <type_traits>
@@ -24,6 +25,9 @@
2425
#include <exception>
2526
#include <variant>
2627
#endif
28+
#if __has_feature(cxx_rtti)
29+
#include <typeinfo>
30+
#endif
2731

2832
namespace emscripten {
2933

@@ -341,6 +345,14 @@ class EMBIND_VISIBILITY_DEFAULT val {
341345
return val(internal::_emval_get_module_property(name));
342346
}
343347

348+
#if __has_feature(cxx_rtti)
349+
// Create a `val` from a user-defined dynamic `type` that is ABI-compatible with static type `T`:
350+
template<typename T>
351+
static val dyn(const std::type_info& type, T&& value) {
352+
return internalDynCast<val>(internal::TypeID<val>::get(), &type, nullptr, std::forward<T>(value), std::identity());
353+
}
354+
#endif
355+
344356
template<typename T, typename... Policies>
345357
explicit val(T&& value, Policies...) {
346358
using namespace internal;
@@ -516,6 +528,14 @@ class EMBIND_VISIBILITY_DEFAULT val {
516528
return internalCall<EM_INVOKER_KIND::CAST, WithPolicies<Policies...>, T>(as_handle(), nullptr, *this);
517529
}
518530

531+
#if __has_feature(cxx_rtti)
532+
// Retrieve a user-defined dynamic `type` that is ABI-compatible with static type `T`:
533+
template<typename T, typename TransformFn>
534+
auto dyn_as(const std::type_info& type, TransformFn transform) const {
535+
return internalDynCast<T>(&type, internal::TypeID<val>::get(), as_handle(), *this, transform);
536+
}
537+
#endif
538+
519539
// Prefer calling val::typeOf() over val::typeof(), since this form works in both C++11 and GNU++11 build modes. "typeof" is a reserved word in GNU++11 extensions.
520540
val typeOf() const {
521541
return val(internal::_emval_typeof(as_handle()));
@@ -573,42 +593,68 @@ class EMBIND_VISIBILITY_DEFAULT val {
573593
template<typename WrapperType>
574594
friend val internal::wrapped_extend(const std::string& , const val& );
575595

576-
template<internal::EM_INVOKER_KIND Kind, typename Policy, typename Ret, typename... Args>
577-
static Ret internalCall(EM_VAL handle, const char *methodName, Args&&... args) {
578-
static_assert(!std::is_lvalue_reference<Ret>::value,
579-
"Cannot create a lvalue reference out of a JS value.");
580-
596+
template<typename Ret, typename... Args>
597+
static auto internalDoCall(internal::EM_INVOKER caller, EM_VAL handle, const char* methodName, internal::EM_DESTRUCTORS* destructors, Args&&... args) {
581598
using namespace internal;
582599

583600
using RetWire = BindingType<Ret>::WireType;
584601

585-
static constexpr typename Policy::template ArgTypeList<Ret, Args...> argTypes;
586-
thread_local EM_INVOKER mc = _emval_create_invoker(argTypes.getCount(), argTypes.getTypes(), Kind);
587-
588602
WireTypePack<Args...> argv(std::forward<Args>(args)...);
589-
EM_DESTRUCTORS destructors = nullptr;
590603

591604
RetWire result;
592605
if constexpr (std::is_integral<RetWire>::value && sizeof(RetWire) == 8) {
593606
// 64-bit integers can't go through "generic wire type" because double and int64 have different ABI.
594607
result = static_cast<RetWire>(_emval_invoke_i64(
595-
mc,
608+
caller,
596609
handle,
597610
methodName,
598-
&destructors,
611+
destructors,
599612
argv));
600613
} else {
601614
result = GenericWireTypeConverter<RetWire>::from(_emval_invoke(
602-
mc,
615+
caller,
603616
handle,
604617
methodName,
605-
&destructors,
618+
destructors,
606619
argv));
607620
}
621+
return result;
622+
}
623+
624+
template<internal::EM_INVOKER_KIND Kind, typename Policy, typename Ret, typename... Args>
625+
static Ret internalCall(EM_VAL handle, const char *methodName, Args&&... args) {
626+
static_assert(!std::is_lvalue_reference<Ret>::value,
627+
"Cannot create a lvalue reference out of a JS value.");
628+
629+
using namespace internal;
630+
631+
static constexpr typename Policy::template ArgTypeList<Ret, Args...> argTypes;
632+
thread_local EM_INVOKER mc = _emval_create_invoker(argTypes.getCount(), argTypes.getTypes(), Kind);
633+
634+
EM_DESTRUCTORS destructors = nullptr;
635+
636+
auto result = internalDoCall<Ret>(mc, handle, methodName, &destructors, std::forward<Args>(args)...);
608637
DestructorsRunner rd(destructors);
609638
return BindingType<Ret>::fromWireType(result);
610639
}
611640

641+
template<typename Ret, typename Arg, typename TransformFn>
642+
static auto internalDynCast(internal::TYPEID retType, internal::TYPEID argType, EM_VAL handle, Arg&& arg, TransformFn transform) {
643+
static_assert(!std::is_lvalue_reference<Ret>::value,
644+
"Cannot create a lvalue reference out of a JS value.");
645+
646+
using namespace internal;
647+
648+
TYPEID const argTypes[2] { retType, argType };
649+
EM_INVOKER mc = _emval_create_invoker(2, argTypes, EM_INVOKER_KIND::CAST);
650+
651+
EM_DESTRUCTORS destructors = nullptr;
652+
653+
auto result = internalDoCall<Ret>(mc, handle, nullptr, &destructors, std::forward<Arg>(arg));
654+
DestructorsRunner rd(destructors);
655+
return transform(BindingType<Ret>::fromWireType(result));
656+
}
657+
612658
template<typename T>
613659
val val_ref(const T& v) const {
614660
return val(v);

0 commit comments

Comments
 (0)