End Goal: Provide practical, sound interop between programming languages on top of WebAssembly.
Exploration Goal: What does WebAssembly currently provide for sound interop?
demo*/
demo01
- Embed a WebAssembly inside a Rust program an run a trivial WebAssembly script (hello.wat
).demo02
- Compile a trivial addition function (i.e.(a: i32, b: i32) => a + b
) in Zig to WebAssembly and run it via a JavaScript host runtime.demo03
- Use thedemo02
addition function, compile a trivial subtraction function (i.e.(a: i32, b: i32) => a - b
) in Rust to WebAssembly, and compose them in a JavaScript host runtime.demo04
- WIP 3-way w/ Zig, Lys, Rustdemo05
- Compile a simple stack implementation in Zig to WebAssembly and use it via a JavaScript host runtime.demo06
- Consume thedemo05
stack implementation with a Rust function compiled to WebAssembly and running on a JavaScript host runtime.
test01
- Trivial identify function in WebAssembly for testing signature types and runtime conversions.
Each of the demo*
folders should be usable via make
. Simply running make
will build anything required using the appropriate toolchain (see [Dependencies]) and then run the demo.
NOTE: this syntax was inspired by proposals that have not been accepted: GC (reference types, type imports, typed function references).
Lib.wat
(exported by some higher-level source language OR written by hand)
TODO: represent in a higher-level source language for better end-to-end understanding.
(module
(type $Stack_i32 i32)
(func $Stack_i32.constructor (result (ref $Stack_i32)) ...)
(func $Stack_i32.destruct (param (ref $Stack_i32)) ...)
(func $Stack_i32.push (param (ref $Stack_i32)) (param i32) ...)
(func $Stack_i32.pop (param (ref $Stack_i32)) (result i32) ...)
(export ...))
Consumer.rs
#[extern_adt]
impl Stack_i32 {
fn constructor() -> Stack_i32;
fn destruct(&self) -> Stack_i32;
fn push(&self, i32); // NOTE: this could throw an error if insufficient memory is available
fn pop(&self) -> i32;
}
Lib.wat
NOTE: This module will have to be reinstantiated for each unique Stack__T
type.
(module
(import "env" "Stack__T" (type $Stack__T))
(type $Stack i32)
(func $Stack.constructor (result (ref $Stack)) ...)
(func $Stack.destruct (param (ref $Stack)) ...)
(func $Stack.push (param (ref $Stack)) (param (ref $Stack__T)) ...)
(func $Stack.pop (param (ref $Stack)) (result (ref $Stack__T)) ...)
(export ...))
Consumer.rs
#[extern_adt]
impl Stack<T> {
fn constructor() -> Stack<T>;
fn destruct(&self) -> Stack<T>;
fn push(&self, T); // NOTE: this could throw an error if insufficient memory is available
fn pop(&self) -> T;
}
pub struct Person {
age: u8,
name: String,
}
- Define destructor naming convention.
- Motivation: Rust has a
Drop
trait that will called (when available) when a reference exits scope. Having a standard convention for referring to destructors would allow this trait to be implemented automatically.- Alternative (Rust): Expand macro to optionally take the name of a destructor function with the signature
(&self) -> nil
.
- Alternative (Rust): Expand macro to optionally take the name of a destructor function with the signature
- Existing Work: GC proposal briefly mentions a possible extension Weak References and Finalization.
- Motivation: Rust has a
- Define name-mangling conventions so that names are usable by all source languages.
* Motivation: Rust can't reference extern functions that have
.
in the name. - Define host environment conventions for when to reinstantiate a module vs. when to reuse an existing one.
- This is a performance optimization, the safe approach is to reinstantiate with every use.
- Define host environment conventions for namespacing modules.
- Existing Work: Passing remark from core team at tool-conventions#135
- How should Generics/Paramaterized Types be handled?
- Existing Work: GC proposal has an unfinished section about a possible extension Type Parameters and Fields.
- Where should ADT instances be stored?
- Within Wasm Modules private memory?
- Pro: Abstraction is easier to enforce.
- Pro: Less work on the caller.
- Con: Poor cache performance when working with instances from multiple libraries.
- Con: If library uses GC and consumer doesn't, intermixes different memory management conventions.
- The GC proposal appears to want to integrate ADTs with the GC system.
- Within Wasm Modules private memory?
- How to propagate errors between languages?
- Wasm Modules and abstract types are the primitives needed to implement this.
- Wasm Modules enforced interface adherence and (mostly) hides underyling memory.
- Abstrac types enable opaque object instance identification without having to refer to memory locations.
Could be circumvented with a mapping table, but abstract types are more convenient due to export/import capabilities and param references (to strengthen signature type checking).See Section 2.1 in my thesis on why mapping tables are insufficient.
Focused on configuration for a macOS development machine, but all toolchain componenets should be available on Linux and Windows as well.
- Runtimes
- Compilers
- General Wasm/Wat (wabt | macos: brew)
- CPP (emscripten | macos: emsdk)
- Deprecated approach:
brew install emscripten
- Also need binaryen (macos: yarn)
- Update
~/.emscripten
to link to:- llvm from brew (LLVM_ROOT =
/usr/local/Cellar/llvm/9.0.1/bin
) - binaryen from yarn/npm global (BINARYEN_ROOT =
/usr/local/bin
)
- llvm from brew (LLVM_ROOT =
- FIX: unable to find
wasm-emscripten-finalize
when installed this way
- Deprecated approach:
- Rust (rustup | macos: brew)
rustup target add wasm32-unknown-unknown
- Zig (zig | macos: brew)