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

feat: add binding crate integrations #89

Merged
merged 7 commits into from
May 20, 2024
Merged
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
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