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

Support WASM std targets. #106

Closed
wants to merge 9 commits into from
Closed
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
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ used_linker = ["linkme-impl/used_linker"]
[dependencies]
linkme-impl = { version = "=0.3.31", path = "impl" }

[target.'cfg(target_family = "wasm")'.dependencies]
once_cell = "1.20.2"


[dev-dependencies]
once_cell = "1.16"
rustversion = "1.0"
Expand Down
9 changes: 9 additions & 0 deletions WASM.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# WASM Support

To support WASM, two requirements are needed: `std`, and a custom linker.
The custom linker must do the following:
For each `import` module starting with `@@linkme`, rewrite its functions as follows:
Find all `export`s whose name starts with the `import`'s module. The number of them
should be the result of the `_len` function.
The `_init` function should call all of the exports in any order by calling each
with the output of the previous (if the first, the argument), then returning the output of the last.
47 changes: 37 additions & 10 deletions impl/src/declaration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ pub fn expand(input: TokenStream) -> TokenStream {
let mut ty = decl.ty;
let name = ident.to_string();

let xname = format!("@@linkme/{name}");

let linkme_path = match attr::linkme_path(&mut attrs) {
Ok(path) => path,
Err(err) => return err.to_compile_error(),
Expand Down Expand Up @@ -119,20 +121,40 @@ pub fn expand(input: TokenStream) -> TokenStream {
Some(Token![unsafe](call_site))
};

let (unsafe_attr, link_section_attr) = if cfg!(no_unsafe_attributes) {
// #[cfg_attr(all(), link_section = ...)]
(
Ident::new("cfg_attr", call_site),
quote!(all(), link_section),
)
} else {
// #[unsafe(link_section = ...)]
(Ident::new("unsafe", call_site), quote!(link_section))
};
let (unsafe_attr, link_section_attr, wasm_import_attr) =
if cfg!(no_unsafe_attributes) {
// #[cfg_attr(all(), link_section = ...)]
(
Ident::new("cfg_attr", call_site),
quote!(all(), link_section),
quote!(all(), wasm_import_module),
)
} else {
// #[unsafe(link_section = ...)]
(
Ident::new("unsafe", call_site),
quote!(link_section),
quote!(wasm_import_module),
)
};

quote! {
#(#attrs)*
#vis static #ident: #linkme_path::DistributedSlice<#ty> = {
#[cfg(target_family = "wasm")]
{
#[#unsafe_attr(#wasm_import_attr = #xname)]
#unsafe_extern extern "C"{
fn _init(a: *mut <#ty as #linkme_path::__private::Slice>::Element) -> *mut <#ty as #linkme_path::__private::Slice>::Element;
fn _len() -> usize;
}
static lazy: #linkme_path::__private::once_cell::sync::OnceCell<#linkme_path::__private::StaticPtr<<#ty as #linkme_path::__private::Slice>::Element>> = #linkme_path::__private::std::sync::OnceLock::new();
unsafe{
#linkme_path::DistributedSlice::private_new(#name,_init,_len,&lazy)
}
}
#[cfg(not(target_family = "wasm"))]
{
#[cfg(any(
target_os = "none",
target_os = "linux",
Expand Down Expand Up @@ -242,6 +264,7 @@ pub fn expand(input: TokenStream) -> TokenStream {
#linkme_path::__private::ptr::addr_of!(DUPCHECK_STOP),
)
}
}
};

#[doc(hidden)]
Expand All @@ -258,6 +281,7 @@ pub fn expand(input: TokenStream) -> TokenStream {
#![linkme_windows_section = concat!(#windows_section, $key)]
#![linkme_illumos_section = concat!(#illumos_section, $key)]
#![linkme_bsd_section = concat!(#bsd_section, $key)]
#![linkme_wasm_export = concat!(#xname,"/",$key)]
$item
}
};
Expand All @@ -267,6 +291,7 @@ pub fn expand(input: TokenStream) -> TokenStream {
#![linkme_windows_section = $windows_section:expr]
#![linkme_illumos_section = $illumos_section:expr]
#![linkme_bsd_section = $bsd_section:expr]
#![linkme_wasm_export = $wasm_export:expr]
$item:item
) => {
#used
Expand All @@ -275,6 +300,7 @@ pub fn expand(input: TokenStream) -> TokenStream {
#[cfg_attr(any(target_os = "uefi", target_os = "windows"), #unsafe_attr(#link_section_attr = $windows_section))]
#[cfg_attr(target_os = "illumos", #unsafe_attr(#link_section_attr = $illumos_section))]
#[cfg_attr(any(target_os = "freebsd", target_os = "openbsd"), #unsafe_attr(#link_section_attr = $bsd_section))]
#[cfg_attr(target_family = "wasm",#unsafe_attr(export_name = $wasm_export))]
$item
};
($item:item) => {
Expand All @@ -284,6 +310,7 @@ pub fn expand(input: TokenStream) -> TokenStream {
#[cfg_attr(any(target_os = "uefi", target_os = "windows"), #unsafe_attr(#link_section_attr = #windows_section))]
#[cfg_attr(target_os = "illumos", #unsafe_attr(#link_section_attr = #illumos_section))]
#[cfg_attr(any(target_os = "freebsd", target_os = "openbsd"), #unsafe_attr(#link_section_attr = #bsd_section))]
#[cfg_attr(target_family = "wasm",#unsafe_attr(export_name = #xname))]
$item
};
}
Expand Down
28 changes: 27 additions & 1 deletion impl/src/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,12 +215,38 @@ fn do_expand(path: Path, pos: Option<usize>, input: Element) -> TokenStream {
Err(err) => return err.to_compile_error(),
};

let sort_key = pos.into_iter().map(|pos| format!("{:04}", pos));
let sort_key = pos
.into_iter()
.map(|pos| format!("{:04}", pos))
.collect::<Vec<_>>();

let factory = quote_spanned!(input.start_span=> __new);
let get = quote_spanned!(input.end_span=> #factory());

let uid = crate::hash(&ident).to_string();

quote! {
#[cfg(target_family = "wasm")]
#path ! {
#![linkme_macro = #path]
#![linkme_sort_key = concat!(#uid,#(#sort_key),*)]
#(#attrs)*
#vis unsafe extern "C" fn #ident(a: *mut #ty) -> *mut #ty{
#[allow(clippy::no_effect_underscore_binding)]
unsafe fn __typecheck(_: #linkme_path::__private::Void) {
#[allow(clippy::ref_option_ref)]
let #factory = || -> fn() -> &'static #ty { || &#ident };
unsafe {
#linkme_path::DistributedSlice::private_typecheck(#path, #get);
}
}
unsafe{
a.store(#expr);
};
return a.add(1);
};
}
#[cfg(not(target_family = "wasm"))]
#path ! {
#(
#![linkme_macro = #path]
Expand Down
91 changes: 69 additions & 22 deletions src/distributed_slice.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
#[allow(unused_imports)]
use core::alloc::Layout;
#[allow(unused_imports)]
use core::cell::UnsafeCell;
use core::fmt::{self, Debug};
use core::hint;
use core::mem;
Expand Down Expand Up @@ -131,13 +135,23 @@ use crate::__private::Slice;
/// ```
pub struct DistributedSlice<T: ?Sized + Slice> {
name: &'static str,
#[cfg(not(target_family = "wasm"))]
section_start: StaticPtr<T::Element>,
#[cfg(not(target_family = "wasm"))]
section_stop: StaticPtr<T::Element>,
#[cfg(not(target_family = "wasm"))]
dupcheck_start: StaticPtr<usize>,
#[cfg(not(target_family = "wasm"))]
dupcheck_stop: StaticPtr<usize>,
#[cfg(target_family = "wasm")]
lazy: &'static ::once_cell::sync::OnceCell<StaticPtr<T::Element>>,
#[cfg(target_family = "wasm")]
init: unsafe fn(*mut T::Element) -> *mut T::Element,
#[cfg(target_family = "wasm")]
len: unsafe fn() -> usize,
}

struct StaticPtr<T> {
#[doc(hidden)]
pub struct StaticPtr<T> {
ptr: *const T,
}

Expand All @@ -154,6 +168,21 @@ impl<T> Clone for StaticPtr<T> {
}

impl<T> DistributedSlice<[T]> {
#[doc(hidden)]
#[cfg(target_family = "wasm")]
pub const unsafe fn private_new(
name: &'static str,
init: unsafe fn(*mut T::Element) -> *mut T::Element,
len: unsafe fn() -> usize,
lazy: &'static ::std::sync::OnceLock<StaticPtr<T::Element>>,
) -> Self {
DistributedSlice {
name,
init,
len,
lazy,
}
}
#[doc(hidden)]
#[cfg(any(
target_os = "none",
Expand Down Expand Up @@ -254,29 +283,47 @@ impl<T> DistributedSlice<[T]> {
/// }
/// ```
pub fn static_slice(self) -> &'static [T] {
if self.dupcheck_start.ptr.wrapping_add(1) < self.dupcheck_stop.ptr {
panic!("duplicate #[distributed_slice] with name \"{}\"", self.name);
}
#[cfg(not(target_family = "wasm"))]
{
if self.dupcheck_start.ptr.wrapping_add(1) < self.dupcheck_stop.ptr {
panic!("duplicate #[distributed_slice] with name \"{}\"", self.name);
}

let stride = mem::size_of::<T>();
let start = self.section_start.ptr;
let stop = self.section_stop.ptr;
let byte_offset = stop as usize - start as usize;
let len = match byte_offset.checked_div(stride) {
Some(len) => len,
// The #[distributed_slice] call checks `size_of::<T>() > 0` before
// using the unsafe `private_new`.
None => unsafe { hint::unreachable_unchecked() },
};
let stride = mem::size_of::<T>();
let start = self.section_start.ptr;
let stop = self.section_stop.ptr;
let byte_offset = stop as usize - start as usize;
let len = match byte_offset.checked_div(stride) {
Some(len) => len,
// The #[distributed_slice] call checks `size_of::<T>() > 0` before
// using the unsafe `private_new`.
None => unsafe { hint::unreachable_unchecked() },
};

// On Windows, the implementation involves growing a &[T; 0] to
// encompass elements that we have asked the linker to place immediately
// after that location. The compiler sees this as going "out of bounds"
// based on provenance, so we must conceal what is going on.
#[cfg(any(target_os = "uefi", target_os = "windows"))]
let start = hint::black_box(start);
// On Windows, the implementation involves growing a &[T; 0] to
// encompass elements that we have asked the linker to place immediately
// after that location. The compiler sees this as going "out of bounds"
// based on provenance, so we must conceal what is going on.
#[cfg(any(target_os = "uefi", target_os = "windows"))]
let start = hint::black_box(start);

unsafe { slice::from_raw_parts(start, len) }
return unsafe { slice::from_raw_parts(start, len) };
};

#[cfg(target_family = "wasm")]
{
let len = unsafe { (self.len)() };
let start = *self.lazy.get_or_init(|| {
let start = unsafe { ::std::alloc::alloc(Layout::array::<T::Element>(len)) }
as *mut T::Element;
let end = unsafe { (self.init)(start) };
if end != start.offset(len) {
panic!("invariant violated: `init` must fill the whole array")
}
return start;
});
return unsafe { slice::from_raw_parts(start, len) };
};
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@
clippy::unused_self
)]

#[cfg(target_family = "wasm")]
extern crate std;

mod distributed_slice;

// Not public API.
Expand Down
12 changes: 12 additions & 0 deletions src/private.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ pub use core::primitive::usize;
#[doc(hidden)]
pub use core::ptr;

#[cfg(target_family = "wasm")]
#[doc(hidden)]
pub use std;

#[cfg(target_family = "wasm")]
#[doc(hidden)]
pub use once_cell;

#[doc(hidden)]
pub trait Slice {
type Element;
Expand All @@ -18,3 +26,7 @@ impl<T> Slice for [T] {

#[doc(hidden)]
pub enum Void {}


#[doc(hidden)]
pub use crate::distributed_slice::StaticPtr;
Loading