corelib: Remove the extern call in the vtable for wasm

In fact, remove it for non-C++

Because wasm doesn't support C-unwind. And actually warns about
incompatible ABI for the rest

Fixes #8449
This commit is contained in:
Olivier Goffart 2025-06-05 16:18:25 +02:00
parent f39b3ab888
commit f91b61c8ee
8 changed files with 78 additions and 18 deletions

View file

@ -68,7 +68,7 @@ allowing to access methods from the trait directly from VRef.
This macro does the following transformation:
For function type fields:
- The ABI of each functions is changed to `extern "C"`
- If the function has no ABI, it is changed to `extern "C"` (unless `no_extern` is passed as `#[vtable(no_extern)]`)
- `unsafe` is added to the signature, since it is unsafe to call these functions directly from
the vtable without having a valid pointer to the actual object. But if the original function was
marked unsafe, the unsafety is forwarded to the trait.
@ -165,7 +165,9 @@ assert_eq!(dog.is_hungry, true);
*/
#[proc_macro_attribute]
pub fn vtable(_attr: TokenStream, item: TokenStream) -> TokenStream {
pub fn vtable(attr: TokenStream, item: TokenStream) -> TokenStream {
let no_extern = attr.to_string().trim() == "no_extern";
let mut input = parse_macro_input!(item as ItemStruct);
let fields = if let Fields::Named(fields) = &mut input.fields {
@ -394,7 +396,7 @@ pub fn vtable(_attr: TokenStream, item: TokenStream) -> TokenStream {
{
return Error::new(a.span(), "invalid ABI").to_compile_error().into();
}
} else {
} else if !no_extern {
f.abi = Some(parse_str("extern \"C\"").unwrap());
}
sig_extern.abi.clone_from(&f.abi);
@ -478,9 +480,10 @@ pub fn vtable(_attr: TokenStream, item: TokenStream) -> TokenStream {
continue;
}
if ident == "dealloc" {
let abi = &sig_extern.abi;
vtable_ctor.push(quote!(#ident: {
#[allow(unsafe_code)]
unsafe extern "C" fn #ident(_: &#vtable_name, ptr: *mut u8, layout: vtable::Layout) {
unsafe #abi fn #ident(_: &#vtable_name, ptr: *mut u8, layout: vtable::Layout) {
use ::core::convert::TryInto;
vtable::internal::dealloc(ptr, layout.try_into().unwrap())
}

View file

@ -26,6 +26,7 @@ struct AnimalVTable {
/// the self object.
///
/// Note: the #[vtable] macro will automatically add `extern "C"` if that is missing.
/// (unless `no_extern` is specified)
make_noise: fn(VRef<AnimalVTable>, i32) -> i32,
/// if there is a 'drop' member, it is considered as the destructor.

View file

@ -207,3 +207,30 @@ pub fn slint_doc_str(input: TokenStream) -> TokenStream {
assert!(visitor.1, "No slint link found");
quote!(#doc).into()
}
/// Attribute macro that removes `extern "..."` from the function signatures
///
/// This is useful because wasm does not support `extern "C-unwind"` and also
/// warn about ABI incompatibilities we wouldn't care about.
///
/// (can be applied to a function or a vtable struct)
#[proc_macro_attribute]
pub fn remove_extern(_attr: TokenStream, item: TokenStream) -> TokenStream {
let mut input = syn::parse_macro_input!(item as syn::Item);
match &mut input {
syn::Item::Fn(item_fn) => {
item_fn.sig.abi.take();
}
syn::Item::Struct(item_struct) => {
for f in item_struct.fields.iter_mut() {
if let syn::Type::BareFn(f) = &mut f.ty {
f.abi.take();
}
}
}
_ => (),
}
quote!(#input).into()
}

View file

@ -20,15 +20,16 @@ mod htmlimage;
mod svg;
#[allow(missing_docs)]
#[vtable::vtable]
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
#[vtable::vtable(no_extern)]
#[repr(C)]
pub struct OpaqueImageVTable {
drop_in_place: fn(VRefMut<OpaqueImageVTable>) -> Layout,
dealloc: fn(&OpaqueImageVTable, ptr: *mut u8, layout: Layout),
drop_in_place: extern "C-unwind" fn(VRefMut<OpaqueImageVTable>) -> Layout,
dealloc: extern "C-unwind" fn(&OpaqueImageVTable, ptr: *mut u8, layout: Layout),
/// Returns the image size
size: fn(VRef<OpaqueImageVTable>) -> IntSize,
size: extern "C-unwind" fn(VRef<OpaqueImageVTable>) -> IntSize,
/// Returns a cache key
cache_key: fn(VRef<OpaqueImageVTable>) -> ImageCacheKey,
cache_key: extern "C-unwind" fn(VRef<OpaqueImageVTable>) -> ImageCacheKey,
}
#[cfg(feature = "svg")]

View file

@ -41,7 +41,8 @@ impl From<IndexRange> for core::ops::Range<usize> {
}
/// A ItemTree is representing an unit that is allocated together
#[vtable]
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
#[vtable(no_extern)]
#[repr(C)]
pub struct ItemTreeVTable {
/// Visit the children of the item at index `index`.
@ -150,10 +151,10 @@ pub struct ItemTreeVTable {
),
/// in-place destructor (for VRc)
pub drop_in_place: unsafe fn(VRefMut<ItemTreeVTable>) -> vtable::Layout,
pub drop_in_place: unsafe extern "C-unwind" fn(VRefMut<ItemTreeVTable>) -> vtable::Layout,
/// dealloc function (for VRc)
pub dealloc: unsafe fn(&ItemTreeVTable, ptr: *mut u8, layout: vtable::Layout),
pub dealloc: unsafe extern "C-unwind" fn(&ItemTreeVTable, ptr: *mut u8, layout: vtable::Layout),
}
#[cfg(test)]
@ -1047,8 +1048,9 @@ impl<'a> From<&'a [ItemTreeNode]> for ItemTreeNodeArray<'a> {
}
}
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
#[vtable(no_extern)]
#[repr(C)]
#[vtable]
/// Object to be passed in visit_item_children method of the ItemTree.
pub struct ItemVisitorVTable {
/// Called for each child of the visited item
@ -1057,14 +1059,14 @@ pub struct ItemVisitorVTable {
/// as the parent's ItemTree.
/// `index` is to be used again in the visit_item_children function of the ItemTree (the one passed as parameter)
/// and `item` is a reference to the item itself
visit_item: fn(
visit_item: extern "C-unwind" fn(
VRefMut<ItemVisitorVTable>,
item_tree: &VRc<ItemTreeVTable, vtable::Dyn>,
index: u32,
item: Pin<VRef<ItemVTable>>,
) -> VisitChildrenResult,
/// Destructor
drop: fn(VRefMut<ItemVisitorVTable>),
drop: extern "C-unwind" fn(VRefMut<ItemVisitorVTable>),
}
/// Type alias to `vtable::VRefMut<ItemVisitorVTable>`

View file

@ -112,7 +112,8 @@ pub enum RenderingResult {
}
/// Items are the nodes in the render tree.
#[vtable]
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
#[vtable(no_extern)]
#[repr(C)]
pub struct ItemVTable {
/// This function is called by the run-time after the memory for the item

View file

@ -135,6 +135,7 @@ internal-json = ["dep:serde_json"]
i-slint-compiler = { workspace = true }
i-slint-common = { workspace = true }
i-slint-core = { workspace = true, features = ["default", "rtti"] }
i-slint-core-macros = { workspace = true }
i-slint-backend-selector = { workspace = true, features = ["rtti"] }
vtable = { workspace = true }

View file

@ -705,6 +705,7 @@ impl ItemTreeDescription<'_> {
}
}
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C-unwind" fn visit_children_item(
component: ItemTreeRefPin,
index: isize,
@ -1845,6 +1846,7 @@ pub fn get_repeater_by_name<'a, 'id>(
(rep_in_comp.offset.apply_pin(instance_ref.instance), rep_in_comp.item_tree_to_repeat.clone())
}
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C-unwind" fn layout_info(
component: ItemTreeRefPin,
orientation: Orientation,
@ -1878,6 +1880,7 @@ extern "C-unwind" fn layout_info(
result
}
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
unsafe extern "C-unwind" fn get_item_ref(component: ItemTreeRefPin, index: u32) -> Pin<ItemRef> {
let tree = get_item_tree(component);
match &tree[index as usize] {
@ -1893,6 +1896,7 @@ unsafe extern "C-unwind" fn get_item_ref(component: ItemTreeRefPin, index: u32)
}
}
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C-unwind" fn get_subtree_range(component: ItemTreeRefPin, index: u32) -> IndexRange {
generativity::make_guard!(guard);
let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) };
@ -1922,6 +1926,7 @@ extern "C-unwind" fn get_subtree_range(component: ItemTreeRefPin, index: u32) ->
}
}
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C-unwind" fn get_subtree(
component: ItemTreeRefPin,
index: u32,
@ -1960,6 +1965,7 @@ extern "C-unwind" fn get_subtree(
}
}
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C-unwind" fn get_item_tree(component: ItemTreeRefPin) -> Slice<ItemTreeNode> {
generativity::make_guard!(guard);
let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) };
@ -1967,6 +1973,7 @@ extern "C-unwind" fn get_item_tree(component: ItemTreeRefPin) -> Slice<ItemTreeN
unsafe { core::mem::transmute::<&[ItemTreeNode], &[ItemTreeNode]>(tree) }.into()
}
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C-unwind" fn subtree_index(component: ItemTreeRefPin) -> usize {
generativity::make_guard!(guard);
let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) };
@ -1977,6 +1984,7 @@ extern "C-unwind" fn subtree_index(component: ItemTreeRefPin) -> usize {
}
}
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
unsafe extern "C-unwind" fn parent_node(component: ItemTreeRefPin, result: &mut ItemWeak) {
generativity::make_guard!(guard);
let instance_ref = InstanceRef::from_pin_ref(component, guard);
@ -2016,6 +2024,7 @@ unsafe extern "C-unwind" fn parent_node(component: ItemTreeRefPin, result: &mut
}
}
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
unsafe extern "C-unwind" fn embed_component(
component: ItemTreeRefPin,
parent_component: &ItemTreeWeak,
@ -2046,6 +2055,7 @@ unsafe extern "C-unwind" fn embed_component(
extra_data.embedding_position.set((parent_component.clone(), parent_item_tree_index)).is_ok()
}
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C-unwind" fn item_geometry(component: ItemTreeRefPin, item_index: u32) -> LogicalRect {
generativity::make_guard!(guard);
let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) };
@ -2068,6 +2078,7 @@ extern "C-unwind" fn item_geometry(component: ItemTreeRefPin, item_index: u32) -
// silence the warning despite `AccessibleRole` is a `#[non_exhaustive]` enum from another crate.
#[allow(improper_ctypes_definitions)]
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C-unwind" fn accessible_role(component: ItemTreeRefPin, item_index: u32) -> AccessibleRole {
generativity::make_guard!(guard);
let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) };
@ -2086,6 +2097,7 @@ extern "C-unwind" fn accessible_role(component: ItemTreeRefPin, item_index: u32)
}
}
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C-unwind" fn accessible_string_property(
component: ItemTreeRefPin,
item_index: u32,
@ -2115,6 +2127,7 @@ extern "C-unwind" fn accessible_string_property(
}
}
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C-unwind" fn accessibility_action(
component: ItemTreeRefPin,
item_index: u32,
@ -2150,6 +2163,7 @@ extern "C-unwind" fn accessibility_action(
};
}
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C-unwind" fn supported_accessibility_actions(
component: ItemTreeRefPin,
item_index: u32,
@ -2172,6 +2186,7 @@ extern "C-unwind" fn supported_accessibility_actions(
val
}
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C-unwind" fn item_element_infos(
component: ItemTreeRefPin,
item_index: u32,
@ -2186,6 +2201,7 @@ extern "C-unwind" fn item_element_infos(
true
}
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
extern "C-unwind" fn window_adapter(
component: ItemTreeRefPin,
do_create: bool,
@ -2200,14 +2216,22 @@ extern "C-unwind" fn window_adapter(
}
}
unsafe extern "C" fn drop_in_place(component: vtable::VRefMut<ItemTreeVTable>) -> vtable::Layout {
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
unsafe extern "C-unwind" fn drop_in_place(
component: vtable::VRefMut<ItemTreeVTable>,
) -> vtable::Layout {
let instance_ptr = component.as_ptr() as *mut Instance<'static>;
let layout = (*instance_ptr).type_info().layout();
dynamic_type::TypeInfo::drop_in_place(instance_ptr);
layout.into()
}
unsafe extern "C" fn dealloc(_vtable: &ItemTreeVTable, ptr: *mut u8, layout: vtable::Layout) {
#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
unsafe extern "C-unwind" fn dealloc(
_vtable: &ItemTreeVTable,
ptr: *mut u8,
layout: vtable::Layout,
) {
std::alloc::dealloc(ptr, layout.try_into().unwrap());
}