#wasm-module #reference #anyref #shim #host #data

no-std externref

Low-cost reference type shims for WASM modules

3 releases (breaking)

0.3.0-beta.1 Sep 29, 2024
0.2.0 Jun 3, 2023
0.1.0 Oct 29, 2022

#42 in WebAssembly

Download history 16/week @ 2024-07-25 31/week @ 2024-08-01 28/week @ 2024-08-08 32/week @ 2024-08-15 6/week @ 2024-08-22 17/week @ 2024-09-19 112/week @ 2024-09-26 26/week @ 2024-10-03 10/week @ 2024-10-10 8/week @ 2024-10-17 1/week @ 2024-10-24 20/week @ 2024-10-31 25/week @ 2024-11-07

55 downloads per month
Used in 3 crates

MIT/Apache

94KB
1.5K SLoC

Low-Cost Reference Type Shims For WASM Modules

Build Status License: MIT OR Apache-2.0 rust 1.76+ required no_std supported

Documentation: Docs.rs crate docs (main)

A reference type (aka externref or anyref) is an opaque reference made available to a WASM module by the host environment. Such references cannot be forged in the WASM code and can be associated with arbitrary host data, thus making them a good alternative to ad-hoc handles (e.g., numeric ones). References cannot be stored in WASM linear memory; they are confined to the stack and tables with externref elements.

Rust does not support reference types natively; there is no way to produce an import / export that has externref as an argument or a return type. wasm-bindgen patches WASM if externrefs are enabled. This library strives to accomplish the same goal for generic low-level WASM ABIs (wasm-bindgen is specialized for browser hosts).

externref use cases

Since externrefs are completely opaque from the module perspective, the only way to use them is to send an externref back to the host as an argument of an imported function. (Depending on the function semantics, the call may or may not consume the externref and may or may not modify the underlying data; this is not reflected by the WASM function signature.) An externref cannot be dereferenced by the module, thus, the module cannot directly access or modify the data behind the reference. Indeed, the module cannot even be sure which kind of data is being referenced.

It may seem that this limits externref utility significantly, but externrefs can still be useful, e.g. to model capability-based security tokens or resource handles in the host environment. Another potential use case is encapsulating complex data that would be impractical to transfer across the WASM API boundary (especially if the data shape may evolve over time), and/or if interactions with data must be restricted from the module side.

Usage

Add this to your Crate.toml:

[dependencies]
externref = "0.3.0-beta.1"
  1. Use Resources as arguments / return results for imported and/or exported functions in a WASM module in place of externrefs. Reference args (including mutable references) and the Option<_> wrapper are supported as well.
  2. Add the #[externref] proc macro on the imported / exported functions.
  3. Transform the generated WASM module with the module processor from the corresponding module of the crate.

As an alternative for the final step, there is a CLI app that can process WASM modules with slightly less fine-grained control.

Important. The processor should run before WASM optimization tools such as wasm-opt from binaryen.

Limitations

If you compile WASM without compilation optimizations, you might get "incorrectly placed externref guard" errors during WASM processing. Currently, the only workaround is to switch off some debug info for the compiled WASM module, e.g. using a workspace manifest:

[profile.dev.package.your-wasm-module]
debug = 1 # or "limited" if you're targeting MSRV 1.71+

These errors shouldn't occur if WASM is compiled in the release mode.

Examples

Using the #[externref] macro and Resources in WASM-targeting code:

use externref::{externref, Resource};

// Two marker types for different resources.
pub struct Arena(());
pub struct Bytes(());

#[cfg(target_arch = "wasm32")]
#[externref]
#[link(wasm_import_module = "arena")]
extern "C" {
    // This import will have signature `(externref, i32) -> externref`
    // on host.
    fn alloc(arena: &Resource<Arena>, size: usize) 
        -> Option<Resource<Bytes>>;
}

// Fallback for non-WASM targets.
#[cfg(not(target_arch = "wasm32"))]
unsafe fn alloc(_: &Resource<Arena>, _: usize) 
    -> Option<Resource<Bytes>> { None }

// This export will have signature `(externref) -> ()` on host.
#[externref]
#[export_name = "test_export"]
pub extern "C" fn test_export(arena: &Resource<Arena>) {
    let bytes = unsafe { alloc(arena, 42) }.expect("cannot allocate");
    // Do something with `bytes`...
}

See crate docs for more examples of usage and implementation details.

Project status 🚧

Experimental; it may be the case that the processor produces invalid WASM in some corner cases (please report this as an issue if it does).

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in externref by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Dependencies

~0–1MB
~19K SLoC