Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Add Support for Wasm Component Model and WASIp2 #5121

Open
@tanishiking

Description

@tanishiking

PoC implementation is going on here: https://github.com/scala-wasm/scala-wasm

Background

This issue is part of #4991 to support "server-side Wasm"

To support "server-side Wasm", we have to remove all the JS dependencies. WASI (WebAssembly System Interface) allows us to implement standard libraries that interact with systems (e.g. println using get-stdout from WASIp2) without JS.

This issue discusses how do we support WASI Preview2 (WASIp2) and Wasm Component Model in Scala.js Wasm backend.

Wasm Component Model

The Wasm Component Model provides:

  1. A new binary format called component that encapsulates traditional Wasm modules.
  2. A WIT (WebAssembly Interface Type) IDL for defining interfaces and types in a language-agnostic way.
  3. A CanonicalABI for standardizing how complex types like strings and structs are represented and exchanged between components.

Once we write a WIT, tools like wit-bindgen would generate bindings for any languages that supports Component Model. (people say "SDKs for free").

Additionally, unlike the traditional interop approach in Wasm, components avoid directly exposing linear memory, and thus it leads reducing the attack surface.

WASI preview2 (WASIp2)

WASI is a set of standardized APIs that allow Wasm modules to interact with system resources (e.g., files, networks, environment variables) in a portable way. WASI Preview2 (WASIp2) is the next version of WASI, designed based on Wasm Component Model.

**Design choice: why WASIp2 instead of WASIp1?**

One of the challenges of adopting WASIp2 is their current immaturity, as not many VMs and tools support them yet1. It might be safer to stick with WASI Preview1 (WASIp1), which is very stable and widely supported by many VMs and tools?

Why we didn't go with WASIp1
Previously, we prototyped server-side Wasm support using WASIp1 in this issue.
We realized that supporting WASIp1 leads to introducing memory-related APIs (e.g., allocate/free and load/store operations on Wasm linear memory) at Scala.js source level and/or hard-code every WASIp1 functions in standard libraries since we couldn't find clear instruction how the "high-level structs" should be serialized into core Wasm values.

However, we don't like to expose such "unsafe" memory APIs in the Scala.js language, even if the "unsafe" parts of the code would typically be generated by tools like wit-bindgen. At the source language and Scala.js IR level, these low-level details should be abstracted away and handled by the linker backend, rather than being exposed to developers.

WASIp2
The Wasm Component Model provides clear instructions on how high-level interface types should be serialized and deserialized through the Canonical ABI. This allows us to implement the serialization and deserialization logic for linear memory (or on the stack) within the Wasm linker backend, without exposing memory APIs to developers.

We never support WASIp1?
Depending on the future status of WASIp2 support, we might also want to support WASIp1. In that case, we would need to carefully examine the WASIp1 witx definitions, and it might be possible to identify high-level types and serialization/deserialization mechanisms similar to those in the Component Model.
https://github.com/WebAssembly/WASI/blob/40019a6181352388397b5b903740f29b26742146/legacy/preview1/witx/wasi_snapshot_preview1.witx

TL;DR: WIP demo

I have a PoC implementation for Wasm Component Model support in the forked repository: scala-wasm#5

package tanishiking:test@0.0.1;

// Scala
world socket {
    import test;
    import wasi:cli/stdout@0.2.0;
    export wasi:cli/run@0.2.0;
}

// Rust
world plug {
    export test;
}

interface test {
    ferris-say: func(content: string, width: u32) -> string;
}
// Cli.scala
object Run {
  @ComponentExport("wasi:cli/[email protected]", "run")
  def run(): component.Result[Unit, Unit] = {
    val out = Stdio.getStdout()
    val ferris = Test.ferrisSay("Hello Scala!", 80)
    out.blockingWriteAndFlush(ferris.getBytes())
    component.Ok(())
  }
}

object Test {
  @ComponentImport("tanishiking:test/[email protected]", "ferris-say")
  def ferrisSay(content: String, width: UInt): String = component.native
}

// Stdio.scala
object Stdio {
  @ComponentImport("wasi:cli/[email protected]", "get-stdout")
  def getStdout(): OutputStream = component.native
}

@ComponentResourceImport("wasi:io/[email protected]")
trait OutputStream extends component.Resource {
  @ComponentResourceMethod("blocking-write-and-flush")
  def blockingWriteAndFlush(contents: Array[UByte]): component.Result[Unit, StreamError]
}
#[allow(warnings)]
mod bindings;
use crate::bindings::exports::tanishiking::test::test::Guest;
use ferris_says::say;
struct Component;
impl Guest for Component {
    fn ferris_say(content: String, width: u32) -> String {
        let mut buf = Vec::new();
        say(content.as_str(), width.try_into().unwrap(), &mut buf).unwrap();
        return String::from_utf8(buf).unwrap();
    }
}
bindings::export!(Component with_types_in bindings);
$ wasm-tools component embed wit examples/helloworld/.2.12/target/scala-2.12/hello-world-scalajs-example-fastopt/main.wasm -o main.wasm -w socket --encoding utf16
$ wasm-tools component new main.wasm -o main.wasm
$ wac plug --plug plugin/target/wasm32-wasip1/release/plugin.wasm main.wasm -o out.wasm
$ wasmtime -W function-references,gc out.wasm
 ______________
< Hello Scala! >
 --------------
        \
         \
            _~^~^~_
        \) /  o o  \ (/
          '_   -   _'
          / '-----' \

(Note that the wasm-tools, wasmtime, and wac might need to be updated or built from source 2.)

Footnotes

  1. wasmtime supports it / WasmEdge is WIP / WAMR seems to be willing to support but not yet / wazero won't support it until it's standardized / WasmKit is WIP

  2. wasmtime needs to be compiled from source or use v30>= in future? for https://github.com/bytecodealliance/wasmtime/pull/9952 that includes a fix in wasm-tools https://github.com/bytecodealliance/wasm-tools/pull/1968. And wac also needs to be compiled from source or 0.6.2>= for https://github.com/bytecodealliance/wac/pull/146

Metadata

Metadata

Assignees

No one assigned

    Labels

    wasmApplies to the WebAssembly backend only

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions