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

Implement the Wasm GC instructions for converting between anyref and externref #9435

Merged
merged 3 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/c-api/src/ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ pub unsafe extern "C" fn wasmtime_externref_data(
externref
.and_then(|e| e.as_wasmtime())
.and_then(|e| {
let data = e.data(cx).ok()?;
let data = e.data(cx).ok()??;
Some(data.downcast_ref::<crate::ForeignData>().unwrap().data)
})
.unwrap_or(ptr::null_mut())
Expand Down
12 changes: 9 additions & 3 deletions crates/cranelift/src/translate/code_translator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2755,13 +2755,19 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
)?;
state.push1(result);
}
Operator::AnyConvertExtern => {
// Pop an `externref`, push an `anyref`. But they have the same
// representation, so we don't actually need to do anything.
}
Operator::ExternConvertAny => {
// Pop an `anyref`, push an `externref`. But they have the same
// representation, so we don't actually need to do anything.
}

Operator::RefCastNonNull { .. }
| Operator::RefCastNullable { .. }
| Operator::BrOnCast { .. }
| Operator::BrOnCastFail { .. }
| Operator::AnyConvertExtern
| Operator::ExternConvertAny => {
| Operator::BrOnCastFail { .. } => {
return Err(wasm_unsupported!("GC operator not yet implemented: {op:?}"));
}

Expand Down
34 changes: 10 additions & 24 deletions crates/environ/src/gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,25 +225,23 @@ impl GcStructLayout {
/// VMGcKind::EqRef`.
///
/// Furthermore, this type only uses the highest 6 bits of its `u32`
/// representation, allowing the lower 26 bytes to be bitpacked with other stuff
/// representation, allowing the lower 27 bytes to be bitpacked with other stuff
/// as users see fit.
#[repr(u32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[rustfmt::skip]
#[allow(missing_docs)]
pub enum VMGcKind {
ExternRef = 0b010000 << 26,
ExternOfAnyRef = 0b011000 << 26,
AnyRef = 0b100000 << 26,
AnyOfExternRef = 0b100100 << 26,
EqRef = 0b101000 << 26,
ArrayRef = 0b101001 << 26,
StructRef = 0b101010 << 26,
ExternRef = 0b01000 << 27,
AnyRef = 0b10000 << 27,
EqRef = 0b10100 << 27,
ArrayRef = 0b10101 << 27,
StructRef = 0b10110 << 27,
}

impl VMGcKind {
/// Mask this value with a `u32` to get just the bits that `VMGcKind` uses.
pub const MASK: u32 = 0b111111 << 26;
pub const MASK: u32 = 0b11111 << 27;

/// Mask this value with a `u32` that potentially contains a `VMGcKind` to
/// get the bits that `VMGcKind` doesn't use.
Expand All @@ -262,9 +260,7 @@ impl VMGcKind {
let masked = val & Self::MASK;
match masked {
x if x == Self::ExternRef.as_u32() => Self::ExternRef,
x if x == Self::ExternOfAnyRef.as_u32() => Self::ExternOfAnyRef,
x if x == Self::AnyRef.as_u32() => Self::AnyRef,
x if x == Self::AnyOfExternRef.as_u32() => Self::AnyOfExternRef,
x if x == Self::EqRef.as_u32() => Self::EqRef,
x if x == Self::ArrayRef.as_u32() => Self::ArrayRef,
x if x == Self::StructRef.as_u32() => Self::StructRef,
Expand Down Expand Up @@ -294,21 +290,11 @@ mod tests {

#[test]
fn kind_matches() {
let all = [
ExternRef,
ExternOfAnyRef,
AnyRef,
AnyOfExternRef,
EqRef,
ArrayRef,
StructRef,
];
let all = [ExternRef, AnyRef, EqRef, ArrayRef, StructRef];

for (sup, subs) in [
(ExternRef, vec![ExternOfAnyRef]),
(ExternOfAnyRef, vec![]),
(AnyRef, vec![AnyOfExternRef, EqRef, ArrayRef, StructRef]),
(AnyOfExternRef, vec![]),
(ExternRef, vec![]),
(AnyRef, vec![EqRef, ArrayRef, StructRef]),
(EqRef, vec![ArrayRef, StructRef]),
(ArrayRef, vec![]),
(StructRef, vec![]),
Expand Down
18 changes: 15 additions & 3 deletions crates/fuzzing/src/oracles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -707,15 +707,27 @@ pub fn table_ops(
// run into a use-after-free bug with one of these refs we
// are more likely to trigger a segfault.
if let Some(a) = a {
let a = a.data(&caller)?.downcast_ref::<CountDrops>().unwrap();
let a = a
.data(&caller)?
.unwrap()
.downcast_ref::<CountDrops>()
.unwrap();
assert!(a.0.load(SeqCst) <= expected_drops.load(SeqCst));
}
if let Some(b) = b {
let b = b.data(&caller)?.downcast_ref::<CountDrops>().unwrap();
let b = b
.data(&caller)?
.unwrap()
.downcast_ref::<CountDrops>()
.unwrap();
assert!(b.0.load(SeqCst) <= expected_drops.load(SeqCst));
}
if let Some(c) = c {
let c = c.data(&caller)?.downcast_ref::<CountDrops>().unwrap();
let c = c
.data(&caller)?
.unwrap()
.downcast_ref::<CountDrops>()
.unwrap();
assert!(c.0.load(SeqCst) <= expected_drops.load(SeqCst));
}
Ok(())
Expand Down
71 changes: 71 additions & 0 deletions crates/wasmtime/src/runtime/gc/enabled/anyref.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Implementation of `anyref` in Wasmtime.

use super::{ExternRef, RootedGcRefImpl};
use crate::prelude::*;
use crate::runtime::vm::VMGcRef;
use crate::{
Expand Down Expand Up @@ -183,6 +184,64 @@ impl AnyRef {
Rooted::new(store, gc_ref)
}

/// Convert an `externref` into an `anyref`.
///
/// This is equivalent to the `any.convert_extern` instruction in Wasm.
///
/// You can recover the underlying `externref` again via the
/// [`ExternRef::convert_any`] method or the `extern.convert_any` Wasm
/// instruction.
///
/// Returns an error if the `externref` GC reference has been unrooted (eg
/// if you attempt to use a `Rooted<ExternRef>` after exiting the scope it
/// was rooted within). See the documentation for
/// [`Rooted<T>`][crate::Rooted] for more details.
///
/// # Example
///
/// ```
/// use wasmtime::*;
/// # fn foo() -> Result<()> {
/// let engine = Engine::default();
/// let mut store = Store::new(&engine, ());
///
/// // Create an `externref`.
/// let externref = ExternRef::new(&mut store, "hello")?;
///
/// // Convert that `externref` into an `anyref`.
/// let anyref = AnyRef::convert_extern(&mut store, externref)?;
///
/// // The converted value is an `anyref` but is not an `eqref`.
/// assert_eq!(anyref.matches_ty(&store, &HeapType::Any)?, true);
/// assert_eq!(anyref.matches_ty(&store, &HeapType::Eq)?, false);
///
/// // We can convert it back to the original `externref` and get its
/// // associated host data again.
/// let externref = ExternRef::convert_any(&mut store, anyref)?;
/// let data = externref
/// .data(&store)?
/// .expect("externref should have host data")
/// .downcast_ref::<&str>()
/// .expect("host data should be a str");
/// assert_eq!(*data, "hello");
/// # Ok(()) }
/// # foo().unwrap();
pub fn convert_extern(
mut store: impl AsContextMut,
externref: Rooted<ExternRef>,
) -> Result<Rooted<Self>> {
let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
Self::_convert_extern(&mut store, externref)
}

pub(crate) fn _convert_extern(
store: &mut AutoAssertNoGc<'_>,
externref: Rooted<ExternRef>,
) -> Result<Rooted<Self>> {
let gc_ref = externref.try_clone_gc_ref(store)?;
Ok(Self::from_cloned_gc_ref(store, gc_ref))
}

/// Creates a new strongly-owned [`AnyRef`] from the raw value provided.
///
/// This is intended to be used in conjunction with [`Func::new_unchecked`],
Expand Down Expand Up @@ -238,6 +297,11 @@ impl AnyRef {
.header(&gc_ref)
.kind()
.matches(VMGcKind::AnyRef)
|| store
.unwrap_gc_store()
.header(&gc_ref)
.kind()
.matches(VMGcKind::ExternRef)
);
Rooted::new(store, gc_ref)
}
Expand Down Expand Up @@ -292,6 +356,13 @@ impl AnyRef {

let header = store.gc_store()?.header(gc_ref);

if header.kind().matches(VMGcKind::ExternRef) {
return Ok(HeapType::Any);
}

debug_assert!(header.kind().matches(VMGcKind::AnyRef));
debug_assert!(header.kind().matches(VMGcKind::EqRef));

if header.kind().matches(VMGcKind::StructRef) {
return Ok(HeapType::ConcreteStruct(
StructType::from_shared_type_index(store.engine(), header.ty().unwrap()),
Expand Down
Loading