Skip to content

Commit

Permalink
feat: add binding crate integrations (#89)
Browse files Browse the repository at this point in the history
* feat(io): implement `Read` and `Write` for streams

Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>

* feat(rand): add `rand` crate integration

Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>

* chore: split examples into `std` and `no_std`

Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>

* ci: build all std and no_std examples

Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>

* refactor(rand): use `writeln` to write to stdout

Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>

* feat(wasi-ext): add workspace crate

Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>

* ci: build in `--workspace` mode

Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>

---------

Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>
  • Loading branch information
rvolosatovs committed May 20, 2024
1 parent 13e01d5 commit 4c9ff82
Show file tree
Hide file tree
Showing 15 changed files with 290 additions and 19 deletions.
34 changes: 28 additions & 6 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ jobs:
- name: Install Rust
run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} && rustup component add rustfmt
- run: rustup target add wasm32-wasi wasm32-unknown-unknown
- run: cargo build
- run: cargo build --no-default-features
- run: cargo build --target wasm32-wasi
- run: cargo build --target wasm32-wasi --no-default-features
- run: cargo test --doc
- run: cargo build --workspace
- run: cargo build --workspace --no-default-features
- run: cargo build --workspace --target wasm32-wasi
- run: cargo build --workspace --target wasm32-wasi --no-default-features
- run: cargo test --workspace --doc
- name: Install Wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1
with:
Expand All @@ -26,16 +26,38 @@ jobs:
uses: bytecodealliance/actions/wasm-tools/setup@v1
with:
version: "1.202.0"
- run: cargo build --examples --target wasm32-wasi
- run: curl -LO https://github.com/bytecodealliance/wasmtime/releases/download/v19.0.0/wasi_snapshot_preview1.command.wasm

- run: cargo build --examples --target wasm32-wasi --no-default-features

- run: wasm-tools component new ./target/wasm32-wasi/debug/examples/hello-world-no_std.wasm --adapt ./wasi_snapshot_preview1.command.wasm -o component.wasm
- run: wasmtime run component.wasm

- run: cargo build --examples --target wasm32-unknown-unknown --no-default-features

- run: wasm-tools component new ./target/wasm32-unknown-unknown/debug/examples/cli_command_no_std.wasm -o component.wasm
- run: wasmtime run component.wasm

- run: wasm-tools component new ./target/wasm32-unknown-unknown/debug/examples/http_proxy_no_std.wasm -o component.wasm
- run: wasm-tools component targets wit component.wasm -w wasi:http/proxy

- run: cargo build --examples --target wasm32-wasi

- run: wasm-tools component new ./target/wasm32-wasi/debug/examples/hello-world.wasm --adapt ./wasi_snapshot_preview1.command.wasm -o component.wasm
- run: wasmtime run component.wasm

- run: cargo build --examples --target wasm32-unknown-unknown

- run: wasm-tools component new ./target/wasm32-unknown-unknown/debug/examples/cli_command.wasm -o component.wasm
- run: wasmtime run component.wasm

- run: wasm-tools component new ./target/wasm32-unknown-unknown/debug/examples/http_proxy.wasm -o component.wasm
- run: wasm-tools component targets wit component.wasm -w wasi:http/proxy

- run: cargo build --examples --workspace --target wasm32-wasi --features rand

- run: wasm-tools component new ./target/wasm32-wasi/debug/examples/rand.wasm --adapt ./wasi_snapshot_preview1.command.wasm -o component.wasm
- run: wasmtime run component.wasm

rustfmt:
name: Rustfmt
Expand Down
32 changes: 29 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,26 @@
name = "wasi"
version = "0.13.0+wasi-0.2.0"
authors = ["The Cranelift Project Developers"]
license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT"
description = "WASI API bindings for Rust"
edition = "2021"
categories = ["no-std", "wasm"]
keywords = ["webassembly", "wasm"]
repository = "https://github.com/bytecodealliance/wasi-rs"
readme = "README.md"
documentation = "https://docs.rs/wasi"
license.workspace = true
edition.workspace = true
repository.workspace = true

[workspace.package]
edition = "2021"
license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT"
repository = "https://github.com/bytecodealliance/wasi-rs"

[workspace.dependencies]
rand = { version = "0.8.5", default-features = false }
wasi = { version = "0.13", path = ".", default-features = false }

[workspace]
members = ["./crates/*"]

[dependencies]
wit-bindgen-rt = { version = "0.23.0", features = ["bitflags"] }
Expand All @@ -25,10 +37,24 @@ std = []
# Unstable feature to support being a libstd dependency
rustc-dep-of-std = ["compiler_builtins", "core", "rustc-std-workspace-alloc"]

[[example]]
name = "cli-command-no_std"
crate-type = ["cdylib"]

[[example]]
name = "cli-command"
crate-type = ["cdylib"]
required-features = ["std"]

[[example]]
name = "hello-world"
required-features = ["std"]

[[example]]
name = "http-proxy-no_std"
crate-type = ["cdylib"]

[[example]]
name = "http-proxy"
crate-type = ["cdylib"]
required-features = ["std"]
21 changes: 21 additions & 0 deletions crates/wasi-ext/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "wasi-ext"
version = "0.1.0"
authors = ["Roman Volosatovs <rvolosatovs@riseup.net>"]
description = "Third-party crate integrations for WASI"

license.workspace = true
edition.workspace = true
repository.workspace = true

[features]
default = ["std"]
std = ["wasi/std"]

[dependencies]
rand = { workspace = true, optional = true }
wasi = { workspace = true }

[[example]]
name = "rand"
required-features = ["rand", "std"]
16 changes: 16 additions & 0 deletions crates/wasi-ext/examples/rand.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use std::io::Write as _;

use wasi_ext::rand::rand::Rng as _;
use wasi_ext::rand::{HostInsecureRng, HostRng};

fn main() {
let mut stdout = wasi::cli::stdout::get_stdout();

let r: u64 = HostRng.gen();
writeln!(stdout, "Cryptographically-secure random u64 is {r}").unwrap();

let r: u64 = HostInsecureRng.gen();
writeln!(stdout, "Pseudo-random u64 is {r}").unwrap();

stdout.flush().unwrap();
}
2 changes: 2 additions & 0 deletions crates/wasi-ext/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#[cfg(feature = "rand")]
pub mod rand;
69 changes: 69 additions & 0 deletions crates/wasi-ext/src/rand.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
pub use rand;

use rand::{CryptoRng, RngCore};

/// The secure interface for cryptographically-secure random numbers
pub struct HostRng;

impl CryptoRng for HostRng {}

impl RngCore for HostRng {
#[inline]
fn next_u32(&mut self) -> u32 {
wasi::random::random::get_random_u64() as _
}

#[inline]
fn next_u64(&mut self) -> u64 {
wasi::random::random::get_random_u64()
}

fn fill_bytes(&mut self, dest: &mut [u8]) {
let n = dest.len();
if usize::BITS <= u64::BITS || n <= u64::MAX as _ {
dest.copy_from_slice(&wasi::random::random::get_random_bytes(n as _));
} else {
let (head, tail) = dest.split_at_mut(u64::MAX as _);
head.copy_from_slice(&wasi::random::random::get_random_bytes(u64::MAX));
self.fill_bytes(tail);
}
}

#[inline]
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
self.fill_bytes(dest);
Ok(())
}
}

/// The insecure interface for insecure pseudo-random numbers
pub struct HostInsecureRng;

impl RngCore for HostInsecureRng {
#[inline]
fn next_u32(&mut self) -> u32 {
wasi::random::insecure::get_insecure_random_u64() as _
}

#[inline]
fn next_u64(&mut self) -> u64 {
wasi::random::insecure::get_insecure_random_u64()
}

fn fill_bytes(&mut self, dest: &mut [u8]) {
let n = dest.len();
if usize::BITS <= u64::BITS || n <= u64::MAX as _ {
dest.copy_from_slice(&wasi::random::insecure::get_insecure_random_bytes(n as _));
} else {
let (head, tail) = dest.split_at_mut(u64::MAX as _);
head.copy_from_slice(&wasi::random::insecure::get_insecure_random_bytes(u64::MAX));
self.fill_bytes(tail);
}
}

#[inline]
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
self.fill_bytes(dest);
Ok(())
}
}
11 changes: 11 additions & 0 deletions examples/cli-command-no_std.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
wasi::cli::command::export!(Example);

struct Example;

impl wasi::exports::cli::run::Guest for Example {
fn run() -> Result<(), ()> {
let stdout = wasi::cli::stdout::get_stdout();
stdout.blocking_write_and_flush(b"Hello, WASI!").unwrap();
Ok(())
}
}
7 changes: 5 additions & 2 deletions examples/cli-command.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use std::io::Write as _;

wasi::cli::command::export!(Example);

struct Example;

impl wasi::exports::cli::run::Guest for Example {
fn run() -> Result<(), ()> {
let stdout = wasi::cli::stdout::get_stdout();
stdout.blocking_write_and_flush(b"Hello, WASI!").unwrap();
let mut stdout = wasi::cli::stdout::get_stdout();
stdout.write_all(b"Hello, WASI!").unwrap();
stdout.flush().unwrap();
Ok(())
}
}
4 changes: 4 additions & 0 deletions examples/hello-world-no_std.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fn main() {
let stdout = wasi::cli::stdout::get_stdout();
stdout.blocking_write_and_flush(b"Hello, world!\n").unwrap();
}
7 changes: 5 additions & 2 deletions examples/hello-world.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::io::Write as _;

fn main() {
let stdout = wasi::cli::stdout::get_stdout();
stdout.blocking_write_and_flush(b"Hello, world!\n").unwrap();
let mut stdout = wasi::cli::stdout::get_stdout();
stdout.write_all(b"Hello, world!\n").unwrap();
stdout.flush().unwrap();
}
22 changes: 22 additions & 0 deletions examples/http-proxy-no_std.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use wasi::http::types::{
Fields, IncomingRequest, OutgoingBody, OutgoingResponse, ResponseOutparam,
};

wasi::http::proxy::export!(Example);

struct Example;

impl wasi::exports::http::incoming_handler::Guest for Example {
fn handle(_request: IncomingRequest, response_out: ResponseOutparam) {
let resp = OutgoingResponse::new(Fields::new());
let body = resp.body().unwrap();

ResponseOutparam::set(response_out, Ok(resp));

let out = body.write().unwrap();
out.blocking_write_and_flush(b"Hello, WASI!").unwrap();
drop(out);

OutgoingBody::finish(body, None).unwrap();
}
}
7 changes: 5 additions & 2 deletions examples/http-proxy.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::io::Write as _;

use wasi::http::types::{
Fields, IncomingRequest, OutgoingBody, OutgoingResponse, ResponseOutparam,
};
Expand All @@ -13,8 +15,9 @@ impl wasi::exports::http::incoming_handler::Guest for Example {

ResponseOutparam::set(response_out, Ok(resp));

let out = body.write().unwrap();
out.blocking_write_and_flush(b"Hello, WASI!").unwrap();
let mut out = body.write().unwrap();
out.write_all(b"Hello, WASI!").unwrap();
out.flush().unwrap();
drop(out);

OutgoingBody::finish(body, None).unwrap();
Expand Down
6 changes: 3 additions & 3 deletions src/errors.rs → src/ext/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#[cfg(feature = "std")]
mod std;

impl core::fmt::Display for crate::io::error::Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.to_debug_string())
}
}

#[cfg(feature = "std")]
impl std::error::Error for crate::io::error::Error {}
69 changes: 69 additions & 0 deletions src/ext/std.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use std::error::Error;
use std::io;
use std::num::NonZeroU64;

use crate::io::streams::StreamError;

impl Error for crate::io::error::Error {}

impl io::Read for crate::io::streams::InputStream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let n = buf
.len()
.try_into()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
match self.blocking_read(n) {
Ok(chunk) => {
let n = chunk.len();
if n > buf.len() {
return Err(io::Error::new(
io::ErrorKind::Other,
"more bytes read than requested",
));
}
buf[..n].copy_from_slice(&chunk);
Ok(n)
}
Err(StreamError::Closed) => Ok(0),
Err(StreamError::LastOperationFailed(e)) => {
Err(io::Error::new(io::ErrorKind::Other, e.to_debug_string()))
}
}
}
}

impl io::Write for crate::io::streams::OutputStream {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let n = loop {
match self.check_write().map(NonZeroU64::new) {
Ok(Some(n)) => {
break n;
}
Ok(None) => {
self.subscribe().block();
}
Err(StreamError::Closed) => return Ok(0),
Err(StreamError::LastOperationFailed(e)) => {
return Err(io::Error::new(io::ErrorKind::Other, e.to_debug_string()))
}
};
};
let n = n
.get()
.try_into()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
let n = buf.len().min(n);
crate::io::streams::OutputStream::write(self, &buf[..n]).map_err(|e| match e {
StreamError::Closed => io::ErrorKind::UnexpectedEof.into(),
StreamError::LastOperationFailed(e) => {
io::Error::new(io::ErrorKind::Other, e.to_debug_string())
}
})?;
Ok(n)
}

fn flush(&mut self) -> io::Result<()> {
self.blocking_flush()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
}
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@
#[cfg(feature = "std")]
extern crate std;

mod errors;
pub mod ext;

// These modules are all auto-generated by `./ci/regenerate.sh`
mod bindings;
Expand Down

0 comments on commit 4c9ff82

Please sign in to comment.