Skip to content

Commit 0126a94

Browse files
authored
Add anyref downcast methods to the C and C++ APIs (#12917)
1 parent 5c68fe6 commit 0126a94

File tree

5 files changed

+274
-0
lines changed

5 files changed

+274
-0
lines changed

crates/c-api/include/wasmtime/gc.h

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,60 @@ WASM_API_EXTERN bool wasmtime_eqref_as_array(wasmtime_context_t *context,
535535
const wasmtime_eqref_t *eqref,
536536
wasmtime_arrayref_t *out);
537537

538+
/**
539+
* \brief Test whether an `anyref` is an `eqref`.
540+
*
541+
* Returns `false` for null references.
542+
*/
543+
WASM_API_EXTERN bool wasmtime_anyref_is_eqref(wasmtime_context_t *context,
544+
const wasmtime_anyref_t *anyref);
545+
546+
/**
547+
* \brief Downcast an `anyref` to an `eqref`.
548+
*
549+
* If the given `anyref` is an `eqref`, a new root is stored in `out` and
550+
* `true` is returned. Otherwise `false` is returned and `out` is set to null.
551+
*/
552+
WASM_API_EXTERN bool wasmtime_anyref_as_eqref(wasmtime_context_t *context,
553+
const wasmtime_anyref_t *anyref,
554+
wasmtime_eqref_t *out);
555+
556+
/**
557+
* \brief Test whether an `anyref` is a `structref`.
558+
*
559+
* Returns `false` for null references.
560+
*/
561+
WASM_API_EXTERN bool wasmtime_anyref_is_struct(wasmtime_context_t *context,
562+
const wasmtime_anyref_t *anyref);
563+
564+
/**
565+
* \brief Downcast an `anyref` to a `structref`.
566+
*
567+
* If the given `anyref` is a `structref`, a new root is stored in `out` and
568+
* `true` is returned. Otherwise `false` is returned and `out` is set to null.
569+
*/
570+
WASM_API_EXTERN bool wasmtime_anyref_as_struct(wasmtime_context_t *context,
571+
const wasmtime_anyref_t *anyref,
572+
wasmtime_structref_t *out);
573+
574+
/**
575+
* \brief Test whether an `anyref` is an `arrayref`.
576+
*
577+
* Returns `false` for null references.
578+
*/
579+
WASM_API_EXTERN bool wasmtime_anyref_is_array(wasmtime_context_t *context,
580+
const wasmtime_anyref_t *anyref);
581+
582+
/**
583+
* \brief Downcast an `anyref` to an `arrayref`.
584+
*
585+
* If the given `anyref` is an `arrayref`, a new root is stored in `out` and
586+
* `true` is returned. Otherwise `false` is returned and `out` is set to null.
587+
*/
588+
WASM_API_EXTERN bool wasmtime_anyref_as_array(wasmtime_context_t *context,
589+
const wasmtime_anyref_t *anyref,
590+
wasmtime_arrayref_t *out);
591+
538592
#ifdef __cplusplus
539593
} // extern "C"
540594
#endif

crates/c-api/include/wasmtime/gc.hh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,35 @@ inline ArrayRef EqRef::as_array(Store::Context cx) const {
445445
return ArrayRef(out);
446446
}
447447

448+
// AnyRef downcast method definitions (declared in val.hh)
449+
inline bool AnyRef::is_eqref(Store::Context cx) const {
450+
return wasmtime_anyref_is_eqref(cx.capi(), &val);
451+
}
452+
inline bool AnyRef::is_struct(Store::Context cx) const {
453+
return wasmtime_anyref_is_struct(cx.capi(), &val);
454+
}
455+
inline bool AnyRef::is_array(Store::Context cx) const {
456+
return wasmtime_anyref_is_array(cx.capi(), &val);
457+
}
458+
inline std::optional<EqRef> AnyRef::as_eqref(Store::Context cx) const {
459+
wasmtime_eqref_t out;
460+
if (wasmtime_anyref_as_eqref(cx.capi(), &val, &out))
461+
return EqRef(out);
462+
return std::nullopt;
463+
}
464+
inline std::optional<StructRef> AnyRef::as_struct(Store::Context cx) const {
465+
wasmtime_structref_t out;
466+
if (wasmtime_anyref_as_struct(cx.capi(), &val, &out))
467+
return StructRef(out);
468+
return std::nullopt;
469+
}
470+
inline std::optional<ArrayRef> AnyRef::as_array(Store::Context cx) const {
471+
wasmtime_arrayref_t out;
472+
if (wasmtime_anyref_as_array(cx.capi(), &val, &out))
473+
return ArrayRef(out);
474+
return std::nullopt;
475+
}
476+
448477
} // namespace wasmtime
449478

450479
#endif // WASMTIME_GC_HH

crates/c-api/include/wasmtime/val.hh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313

1414
namespace wasmtime {
1515

16+
class EqRef;
17+
class StructRef;
18+
class ArrayRef;
19+
1620
/**
1721
* \brief Representation of a WebAssembly `externref` value.
1822
*
@@ -181,6 +185,24 @@ public:
181185
bool is_i31(Store::Context cx) const {
182186
return wasmtime_anyref_is_i31(cx.ptr, &val);
183187
}
188+
189+
/// \brief Returns `true` if this anyref is an eqref.
190+
inline bool is_eqref(Store::Context cx) const;
191+
192+
/// \brief Returns `true` if this anyref is a structref.
193+
inline bool is_struct(Store::Context cx) const;
194+
195+
/// \brief Returns `true` if this anyref is an arrayref.
196+
inline bool is_array(Store::Context cx) const;
197+
198+
/// \brief Downcast to eqref. Returns null eqref if not an eqref.
199+
inline std::optional<EqRef> as_eqref(Store::Context cx) const;
200+
201+
/// \brief Downcast to structref. Returns null structref if not a structref.
202+
inline std::optional<StructRef> as_struct(Store::Context cx) const;
203+
204+
/// \brief Downcast to arrayref. Returns null arrayref if not an arrayref.
205+
inline std::optional<ArrayRef> as_array(Store::Context cx) const;
184206
};
185207

186208
/// \brief Container for the `v128` WebAssembly type.

crates/c-api/src/ref.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,3 +937,93 @@ pub unsafe extern "C" fn wasmtime_eqref_as_array(
937937
crate::initialize(out, None::<OwnedRooted<ArrayRef>>.into());
938938
false
939939
}
940+
941+
#[unsafe(no_mangle)]
942+
pub unsafe extern "C" fn wasmtime_anyref_is_eqref(
943+
cx: WasmtimeStoreContextMut<'_>,
944+
anyref: Option<&wasmtime_anyref_t>,
945+
) -> bool {
946+
match anyref.and_then(|a| a.as_wasmtime()) {
947+
Some(anyref) => anyref.is_eqref(&cx).expect("OwnedRooted always in scope"),
948+
None => false,
949+
}
950+
}
951+
952+
#[unsafe(no_mangle)]
953+
pub unsafe extern "C" fn wasmtime_anyref_as_eqref(
954+
mut cx: WasmtimeStoreContextMut<'_>,
955+
anyref: Option<&wasmtime_anyref_t>,
956+
out: &mut MaybeUninit<wasmtime_eqref_t>,
957+
) -> bool {
958+
if let Some(anyref) = anyref.and_then(|a| a.as_wasmtime()) {
959+
let mut scope = RootScope::new(&mut cx);
960+
let rooted = anyref.to_rooted(&mut scope);
961+
if let Ok(Some(eqref)) = rooted.as_eqref(&mut scope) {
962+
let owned = eqref.to_owned_rooted(&mut scope).expect("in scope");
963+
crate::initialize(out, Some(owned).into());
964+
return true;
965+
}
966+
}
967+
crate::initialize(out, None::<OwnedRooted<EqRef>>.into());
968+
false
969+
}
970+
971+
#[unsafe(no_mangle)]
972+
pub unsafe extern "C" fn wasmtime_anyref_is_struct(
973+
cx: WasmtimeStoreContextMut<'_>,
974+
anyref: Option<&wasmtime_anyref_t>,
975+
) -> bool {
976+
match anyref.and_then(|a| a.as_wasmtime()) {
977+
Some(anyref) => anyref.is_struct(&cx).expect("OwnedRooted always in scope"),
978+
None => false,
979+
}
980+
}
981+
982+
#[unsafe(no_mangle)]
983+
pub unsafe extern "C" fn wasmtime_anyref_as_struct(
984+
mut cx: WasmtimeStoreContextMut<'_>,
985+
anyref: Option<&wasmtime_anyref_t>,
986+
out: &mut MaybeUninit<wasmtime_structref_t>,
987+
) -> bool {
988+
if let Some(anyref) = anyref.and_then(|a| a.as_wasmtime()) {
989+
let mut scope = RootScope::new(&mut cx);
990+
let rooted = anyref.to_rooted(&mut scope);
991+
if let Ok(Some(structref)) = rooted.as_struct(&scope) {
992+
let owned = structref.to_owned_rooted(&mut scope).expect("in scope");
993+
crate::initialize(out, Some(owned).into());
994+
return true;
995+
}
996+
}
997+
crate::initialize(out, None::<OwnedRooted<StructRef>>.into());
998+
false
999+
}
1000+
1001+
#[unsafe(no_mangle)]
1002+
pub unsafe extern "C" fn wasmtime_anyref_is_array(
1003+
cx: WasmtimeStoreContextMut<'_>,
1004+
anyref: Option<&wasmtime_anyref_t>,
1005+
) -> bool {
1006+
match anyref.and_then(|a| a.as_wasmtime()) {
1007+
Some(anyref) => anyref.is_array(&cx).expect("OwnedRooted always in scope"),
1008+
None => false,
1009+
}
1010+
}
1011+
1012+
#[unsafe(no_mangle)]
1013+
pub unsafe extern "C" fn wasmtime_anyref_as_array(
1014+
mut cx: WasmtimeStoreContextMut<'_>,
1015+
anyref: Option<&wasmtime_anyref_t>,
1016+
out: &mut MaybeUninit<wasmtime_arrayref_t>,
1017+
) -> bool {
1018+
if let Some(anyref) = anyref.and_then(|a| a.as_wasmtime()) {
1019+
let mut scope = RootScope::new(&mut cx);
1020+
let rooted = anyref.to_rooted(&mut scope);
1021+
if let Ok(Some(arrayref)) = rooted.as_array(&scope) {
1022+
let owned = arrayref.to_owned_rooted(&mut scope).expect("in scope");
1023+
crate::initialize(out, Some(owned).into());
1024+
return true;
1025+
}
1026+
}
1027+
crate::initialize(out, None::<OwnedRooted<ArrayRef>>.into());
1028+
false
1029+
}

crates/c-api/tests/gc.cc

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,82 @@ TEST(ArrayRef, UpcastAndDowncast) {
232232
AnyRef any = arr.to_anyref();
233233
EXPECT_FALSE(any.is_i31(cx));
234234
}
235+
236+
TEST(AnyRef, DowncastI31) {
237+
Config config;
238+
config.wasm_gc(true);
239+
Engine engine(std::move(config));
240+
Store store(engine);
241+
auto cx = store.context();
242+
243+
AnyRef any = AnyRef::i31(cx, 42);
244+
EXPECT_TRUE(any.is_i31(cx));
245+
EXPECT_TRUE(any.is_eqref(cx));
246+
EXPECT_FALSE(any.is_struct(cx));
247+
EXPECT_FALSE(any.is_array(cx));
248+
249+
// Downcast to eqref.
250+
auto opt_eq = any.as_eqref(cx);
251+
EXPECT_TRUE(opt_eq);
252+
EqRef eq = *opt_eq;
253+
EXPECT_TRUE(eq.is_i31(cx));
254+
auto val = eq.i31_get_u(cx);
255+
ASSERT_TRUE(val.has_value());
256+
EXPECT_EQ(*val, 42u);
257+
}
258+
259+
TEST(AnyRef, DowncastStruct) {
260+
Config config;
261+
config.wasm_gc(true);
262+
Engine engine(std::move(config));
263+
Store store(engine);
264+
auto cx = store.context();
265+
266+
auto ty = StructType::create(engine, {FieldType::const_(WASMTIME_I32)});
267+
auto pre = StructRefPre::create(cx, ty);
268+
auto result = StructRef::create(cx, pre, {Val(int32_t(77))});
269+
ASSERT_TRUE(result);
270+
StructRef s = result.ok();
271+
272+
AnyRef any = s.to_anyref();
273+
EXPECT_TRUE(any.is_eqref(cx));
274+
EXPECT_TRUE(any.is_struct(cx));
275+
EXPECT_FALSE(any.is_array(cx));
276+
EXPECT_FALSE(any.is_i31(cx));
277+
278+
// Downcast back to struct.
279+
auto opt_s2 = any.as_struct(cx);
280+
EXPECT_TRUE(opt_s2);
281+
StructRef s2 = *opt_s2;
282+
auto v = s2.field(cx, 0);
283+
ASSERT_TRUE(v);
284+
EXPECT_EQ(v.ok().i32(), 77);
285+
}
286+
287+
TEST(AnyRef, DowncastArray) {
288+
Config config;
289+
config.wasm_gc(true);
290+
Engine engine(std::move(config));
291+
Store store(engine);
292+
auto cx = store.context();
293+
294+
auto ty = ArrayType::create(engine, FieldType::const_(WASMTIME_I32));
295+
auto pre = ArrayRefPre::create(cx, ty);
296+
auto result = ArrayRef::create(cx, pre, Val(int32_t(55)), 2);
297+
ASSERT_TRUE(result);
298+
ArrayRef arr = result.ok();
299+
300+
AnyRef any = arr.to_anyref();
301+
EXPECT_TRUE(any.is_eqref(cx));
302+
EXPECT_FALSE(any.is_struct(cx));
303+
EXPECT_TRUE(any.is_array(cx));
304+
EXPECT_FALSE(any.is_i31(cx));
305+
306+
// Downcast back to array.
307+
auto opt_arr2 = any.as_array(cx);
308+
EXPECT_TRUE(opt_arr2);
309+
ArrayRef arr2 = *opt_arr2;
310+
auto len = arr2.len(cx);
311+
ASSERT_TRUE(len);
312+
EXPECT_EQ(len.ok(), 2u);
313+
}

0 commit comments

Comments
 (0)