Repository: Geal/serverless-wasm Branch: master Commit: 1fab6e2fdb41 Files: 105 Total size: 80.3 KB Directory structure: gitextract_dnc2u41d/ ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── rustfmt.toml ├── samples/ │ ├── config.toml │ ├── testbackend/ │ │ ├── Cargo.toml │ │ ├── build.sh │ │ ├── config.toml │ │ └── src/ │ │ └── lib.rs │ ├── testbackend.wasm │ ├── testfunc/ │ │ ├── Cargo.toml │ │ ├── build.sh │ │ ├── config.toml │ │ └── src/ │ │ └── lib.rs │ └── testfunc.wasm ├── serverless-api/ │ ├── Cargo.toml │ ├── src/ │ │ └── lib.rs │ └── target/ │ ├── .rustc_info.json │ └── debug/ │ ├── .cargo-lock │ ├── .fingerprint/ │ │ ├── serverless-api-58a0200541263288/ │ │ │ ├── dep-lib-serverless_api-58a0200541263288 │ │ │ ├── lib-serverless_api-58a0200541263288 │ │ │ └── lib-serverless_api-58a0200541263288.json │ │ └── serverless-api-81a9cad5d43bde63/ │ │ ├── dep-lib-serverless_api-81a9cad5d43bde63 │ │ ├── lib-serverless_api-81a9cad5d43bde63 │ │ └── lib-serverless_api-81a9cad5d43bde63.json │ ├── deps/ │ │ ├── libserverless_api-58a0200541263288.rlib │ │ ├── libserverless_api-81a9cad5d43bde63.rmeta │ │ ├── serverless_api-58a0200541263288.d │ │ └── serverless_api-81a9cad5d43bde63.d │ ├── incremental/ │ │ └── serverless_api-101jg2fs4zen3/ │ │ └── s-f15by5smxq-136cfpy-1gc3dqcx796yx/ │ │ ├── 16u6js6g0l3k1ic6.bc.z │ │ ├── 16u6js6g0l3k1ic6.o │ │ ├── 181cuta0v63atwcm.bc.z │ │ ├── 181cuta0v63atwcm.o │ │ ├── 1im38lueib99jsk0.bc.z │ │ ├── 1im38lueib99jsk0.o │ │ ├── 1mvmz58owquyropc.bc.z │ │ ├── 1mvmz58owquyropc.o │ │ ├── 1w44dretk843l467.bc.z │ │ ├── 1w44dretk843l467.o │ │ ├── 1y16o1qfye96o7m0.bc.z │ │ ├── 1y16o1qfye96o7m0.o │ │ ├── 23tqyymcb18u96mb.bc.z │ │ ├── 23tqyymcb18u96mb.o │ │ ├── 2jqywn86b2gsqohu.bc.z │ │ ├── 2jqywn86b2gsqohu.o │ │ ├── 2lyh15q6cjwzy18c.bc.z │ │ ├── 2lyh15q6cjwzy18c.o │ │ ├── 2q5257pdh5222n7q.bc.z │ │ ├── 2q5257pdh5222n7q.o │ │ ├── 3ayaeypdcro9d6yk.bc.z │ │ ├── 3ayaeypdcro9d6yk.o │ │ ├── 3wq0rk2lqn1zrv77.bc.z │ │ ├── 3wq0rk2lqn1zrv77.o │ │ ├── 3wta9ctgdrpkmlpr.bc.z │ │ ├── 3wta9ctgdrpkmlpr.o │ │ ├── 40mv2bo8bxf7ur21.bc.z │ │ ├── 40mv2bo8bxf7ur21.o │ │ ├── 43v6g0y2xsxoggnt.bc.z │ │ ├── 43v6g0y2xsxoggnt.o │ │ ├── 48721dc4k5qxei0u.bc.z │ │ ├── 48721dc4k5qxei0u.o │ │ ├── 49a7n47po4ttqjl7.bc.z │ │ ├── 49a7n47po4ttqjl7.o │ │ ├── 49lx1q7cxvpykyv0.bc.z │ │ ├── 49lx1q7cxvpykyv0.o │ │ ├── 4ezmh1vbs95c5ack.bc.z │ │ ├── 4ezmh1vbs95c5ack.o │ │ ├── 4i7tkntav6hyd03k.bc.z │ │ ├── 4i7tkntav6hyd03k.o │ │ ├── 4xq48u46a1pwiqn7.bc.z │ │ ├── 4xq48u46a1pwiqn7.o │ │ ├── 4yh8x2b62dcih00t.bc.z │ │ ├── 4yh8x2b62dcih00t.o │ │ ├── 4ypvbwho0bu5tnww.bc.z │ │ ├── 4ypvbwho0bu5tnww.o │ │ ├── 56dly8q07ws8ucdq.bc.z │ │ ├── 56dly8q07ws8ucdq.o │ │ ├── 8xzrsc1ux72v29j.bc.z │ │ ├── 8xzrsc1ux72v29j.o │ │ ├── 98g0d9x8aw3akpe.bc.z │ │ ├── 98g0d9x8aw3akpe.o │ │ ├── 9elsx31vb4it187.bc.z │ │ ├── 9elsx31vb4it187.o │ │ ├── c6lbtaiefvx3wya.bc.z │ │ ├── c6lbtaiefvx3wya.o │ │ ├── mz7vgmcf23rofcc.bc.z │ │ ├── mz7vgmcf23rofcc.o │ │ ├── y08g5q2x813c4wx.bc.z │ │ ├── y08g5q2x813c4wx.o │ │ ├── z9ox7biyn1otfln.bc.z │ │ └── z9ox7biyn1otfln.o │ ├── libserverless_api.d │ ├── libserverless_api.rlib │ └── libserverless_api.rmeta └── src/ ├── async/ │ ├── host.rs │ ├── mod.rs │ └── session.rs ├── config.rs ├── interpreter.rs ├── jit/ │ ├── env.rs │ └── mod.rs ├── main.rs └── sync/ ├── host.rs └── mod.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ /target **/*.rs.bk ================================================ FILE: Cargo.toml ================================================ [package] name = "serverless-wasm" version = "0.1.0" license = "MIT" repository = "https://github.com/Geal/serverless-wasm" readme = "README.md" authors = ["Geoffroy Couprie "] [dependencies] parity-wasm = "^0.27" rouille = "^2.1" slab = "^0.3" toml = "^0.4" serde = "^1.0" serde_derive = "^1.0" mio = "^0.6" httparse = "^1.2" #wasmi = "^0.1" wasmi = { git = "https://github.com/geal/wasmi" } #wasmi = { path = "../wasmi" } cretonne = "*" cretonne-wasm = "^0.8" cretonne-module = "*" cretonne-simplejit = "^0.8" ================================================ FILE: LICENSE ================================================ Copyright (c) 2018 Geoffroy Couprie Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Serverless Web Assembly framework ![Serverless WASM](https://raw.githubusercontent.com/geal/serverless-wasm/master/assets/serverless-wasm.jpg) ## Why? For fun. ## But why? ![but why?](https://raw.githubusercontent.com/geal/serverless-wasm/master/assets/butwhy.gif) This is a small demo of Web Assembly's potential outside of browsers. It has been designed with client side execution in mind, but there's nothing preventing it from running in other platforms. There are people working on running WASM binaries from a shell, and putting WASM code inside kernels. Here are some benefits of Web Assembly's design: - maps well to CPU assembly (with Just In Time compilation in mind) - the virtual machine does not require garbage collection, only small memory areas that will be handled by the guest code - meant to be sandboxed And here's the best part: it is meant to be a target language, a lot of other languages will compile to WASM. You can already write C or C++ and compile to WASM with emscripten. Rust's compiler natively supports it. There are demos in Go, Haskell and Ruby. The network effects are huge: the major browsers implement it and can run any WASM app, and every language wants to run on the client side. Now, what happens when you leverage these advantages to build a server platform? You get a system that can run a lot of small, sandboxed, resource limited applications, written in a lot of different languages. You do not care about how to start it, you don't need to map it to filesystems and common runtimes like containers do. You just have some bytecode that imports a few functions, and runs well isolated. This is a bit like the serverless promise, the ability to run arbitrarily small functions, but without even caring about the startup time or the state size, this will be the smallest you can think of. ## What works Currently, the server is able to load a pre built web assembly binary, exports some function that it can use for logging, to build a response and connect to other servers, and handle requests using that wasm file (as long as it exports a "handle" function). ## How to run it ### Requirements for WASM applications the WASM application must export a `handle` function that takes no arguments and returns no arguments. The virtual machine currently exposes the following functions, that you can use to build your response: ```rust extern { fn log(ptr: *const u8, size: u64); fn response_set_status_line(status: u32, ptr: *const u8, size: u64); fn response_set_header(name_ptr: *const u8, name_size: u64, value_ptr: *const u8, value_size: u64); fn response_set_body(ptr: *const u8, size: u64); fn tcp_connect(ptr: *const u8, size: u64) -> i32; fn tcp_read(fd: i32, ptr: *mut u8, size: u64) -> i64; fn tcp_write(fd: i32, ptr: *const u8, size: u64) -> i64; } ``` ### Configuration file You define which WASM binary will handle which requests through a TOML configuration file: ```toml listen_address = "127.0.0.1:8080" [[applications]] file_path = "./samples/testfunc.wasm" method = "GET" url_path = "/hello" [[applications]] file_path = "./samples/testbackend.wasm" method = "GET" url_path = "/backend" ``` ### Running it You can build and launch the server as follows: ```rust cargo build && ./target/debug/serverless-wasm ./samples/config.toml ``` ## Current features - [x] load web assembly file to handle requests - [x] logging function available from WASM - [x] API to build a response from WASM - [x] (blocking) TCP connections to backend servers or databases - [x] routing to mutiple apps depending on the request - [x] set up initial state via "environment variables" - [ ] proper error handling (the server will panic even if you give it the side eye) - [ ] (in progress) asynchronous event loop to receive connections and handle backend TCP connections - [ ] file system abstraction (loading files from S3 or other providers?) - [ ] (in progress) "standard API" for functions exported by the VM ## Prior art While I was building this, I heard of [IceCore](https://github.com/losfair/IceCore), which looks quite cool, with JIT support, etc. It's quite nice to see multiple platforms attempting this. Maybe we'll be able to agree onthe "web assembly standard API" so WASM apps can run on any of those :) ================================================ FILE: rustfmt.toml ================================================ tab_spaces = 2 struct_field_align_threshold = 100 max_width = 140 ================================================ FILE: samples/config.toml ================================================ listen_address = "127.0.0.1:8080" [[applications]] file_path = "./samples/testfunc.wasm" method = "GET" url_path = "/hello" function = "hello" [[applications]] file_path = "./samples/testfunc.wasm" method = "GET" url_path = "/bonjour" function = "bonjour" [[applications]] file_path = "./samples/testbackend.wasm" method = "GET" url_path = "/backend" function = "handle" env = { "/env/backend"= "127.0.0.1:8181"} ================================================ FILE: samples/testbackend/Cargo.toml ================================================ [package] name = "testbackend" version = "0.1.0" authors = ["Geoffroy Couprie "] [lib] path = "src/lib.rs" crate-type = ["cdylib"] [dependencies] serverless-api = { path = "../../serverless-api/" } ================================================ FILE: samples/testbackend/build.sh ================================================ #!/bin/sh RUSTFLAGS="-Clink-args=--import-memory" cargo +nightly build -v --target wasm32-unknown-unknown --release cp target/wasm32-unknown-unknown/release/testbackend.wasm .. ================================================ FILE: samples/testbackend/config.toml ================================================ rustflags = ["-Clink-args=--import-memory"] ================================================ FILE: samples/testbackend/src/lib.rs ================================================ use std::str; extern crate serverless_api as api; #[no_mangle] pub extern "C" fn handle() { api::log("Hello world with api!"); let body; let key = "/env/backend"; match api::db::get(key) { None => { body = format!("could not get value for key {}", key); }, Some(address) => { api::log(&format!("connecting to backend at {}", address)); match api::TcpStream::connect(&address) { None => { body = "could not connect to backend".to_string(); }, Some(mut socket) => { match socket.write(b"hello\n") { None => { body = "could not write to backend server".to_string(); }, Some(_) => { let mut res: [u8; 100] = [0u8; 100]; match socket.read(&mut res) { None => { body = "could not read from backend server".to_string(); }, Some(sz) => { api::log(&format!("read data from backend: \"{:?}\"", str::from_utf8(&res[..sz]).unwrap())); body = format!("Hello world from wasm!\nanswer from backend:\n{}\n", str::from_utf8(&res[..sz]).unwrap()); api::response::set_status(200, "Ok"); api::response::set_header("Content-length", &body.len().to_string()); api::response::set_body(body.as_bytes()); } } } } } } } } api::log(&body); api::response::set_status(500, "Server error"); api::response::set_header("Content-length", &body.len().to_string()); api::response::set_body(body.as_bytes()); } ================================================ FILE: samples/testfunc/Cargo.toml ================================================ [package] name = "testfunc" version = "0.1.0" authors = ["Geoffroy Couprie "] [lib] path = "src/lib.rs" crate-type = ["cdylib"] [dependencies] serverless-api = { path = "../../serverless-api/" } ================================================ FILE: samples/testfunc/build.sh ================================================ #!/bin/sh RUSTFLAGS="-Clink-args=--import-memory" cargo +nightly build -v --target wasm32-unknown-unknown --release cp target/wasm32-unknown-unknown/release/testfunc.wasm .. ================================================ FILE: samples/testfunc/config.toml ================================================ rustflags = ["-Clink-args=--import-memory"] ================================================ FILE: samples/testfunc/src/lib.rs ================================================ use std::ptr; use std::str; extern crate serverless_api as api; #[no_mangle] pub extern "C" fn hello() { api::log("Hello world with api!"); api::response::set_status(200, "Ok"); let body = "Hello world from wasm!\n"; api::response::set_header("Content-length", &body.len().to_string()); api::response::set_body(body.as_bytes()); } #[no_mangle] pub extern "C" fn bonjour() { api::log("Bonjour tout le monde!"); api::response::set_status(200, "Ok"); let body = "Bonjour tout le monde depuis le monde merveilleux de WASM!\n"; api::response::set_header("Content-length", &body.len().to_string()); api::response::set_body(body.as_bytes()); } ================================================ FILE: serverless-api/Cargo.toml ================================================ [package] name = "serverless-api" version = "0.1.0" authors = ["Geoffroy Couprie "] [dependencies] ================================================ FILE: serverless-api/src/lib.rs ================================================ use std::str; mod sys { extern { pub fn log(ptr: *const u8, size: u64); pub fn response_set_status_line(status: u32, ptr: *const u8, size: u64); pub fn response_set_header(name_ptr: *const u8, name_size: u64, value_ptr: *const u8, value_size: u64); pub fn response_set_body(ptr: *const u8, size: u64); pub fn tcp_connect(ptr: *const u8, size: u64) -> i32; pub fn tcp_read(fd: i32, ptr: *mut u8, size: u64) -> i64; pub fn tcp_write(fd: i32, ptr: *const u8, size: u64) -> i64; pub fn db_get(key_ptr: *const u8, key_size: u64, value_ptr: *const u8, value_size: u64) -> i64; } } pub fn log(s: &str) { unsafe { sys::log(s.as_ptr(), s.len() as u64) }; } pub mod db { use super::sys; use std::iter::repeat; pub fn get(key: &str) -> Option { let mut empty = vec![]; let read_sz = unsafe { sys::db_get(key.as_ptr(), key.len() as u64, (&mut empty).as_mut_ptr(), empty.len() as u64) }; if read_sz < 0 { return None; } else if read_sz == 0 { return Some(String::new()); } let mut v = Vec::with_capacity(read_sz as usize); v.extend(repeat(0).take(read_sz as usize)); let sz = unsafe { sys::db_get(key.as_ptr(), key.len() as u64, v.as_mut_ptr(), v.len() as u64) }; if sz < 0 { return None; } else if sz == 0 { return Some(String::new()); } if sz as usize != v.len() { None } else { String::from_utf8(v).ok() } } } pub mod response { use super::sys; pub fn set_status(status: u16, reason: &str) { unsafe { sys::response_set_status_line(status.into(), reason.as_ptr(), reason.len() as u64); } } pub fn set_header(name: &str, value: &str) { unsafe { sys::response_set_header(name.as_ptr(), name.len() as u64, value.as_ptr(), value.len() as u64); } } pub fn set_body(body: &[u8]) { unsafe { sys::response_set_body(body.as_ptr(), body.len() as u64); } } } pub struct TcpStream { fd: i32 } impl TcpStream { pub fn connect(address: &str) -> Option { let fd = unsafe { sys::tcp_connect(address.as_ptr(), address.len() as u64) }; if fd < 0 { None } else { Some(TcpStream { fd }) } } pub fn write(&mut self, data: &[u8]) -> Option { let res = unsafe { sys::tcp_write(self.fd, data.as_ptr(), data.len() as u64) }; if res < 0 { None } else { Some(res as usize) } } pub fn read(&mut self, data: &mut [u8]) -> Option { let res = unsafe { sys::tcp_read(self.fd, data.as_mut_ptr(), data.len() as u64) }; if res < 0 { None } else { Some(res as usize) } } } ================================================ FILE: serverless-api/target/.rustc_info.json ================================================ {"rustc_fingerprint":10175240907518990213,"outputs":{"1617349019360157463":["___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/geal/.rustup/toolchains/nightly-x86_64-apple-darwin\ndebug_assertions\nproc_macro\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"mmx\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_feature=\"ssse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_thread_local\ntarget_vendor=\"apple\"\nunix\n",""],"1164083562126845933":["rustc 1.27.0-nightly (9fae15374 2018-05-13)\nbinary: rustc\ncommit-hash: 9fae1537462bb10fd17d07816efc17cfe4786806\ncommit-date: 2018-05-13\nhost: x86_64-apple-darwin\nrelease: 1.27.0-nightly\nLLVM version: 6.0\n",""],"3144802570395919623":["___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/geal/.rustup/toolchains/nightly-x86_64-apple-darwin\ndebug_assertions\nproc_macro\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"mmx\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_feature=\"ssse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_thread_local\ntarget_vendor=\"apple\"\nunix\n",""]}} ================================================ FILE: serverless-api/target/debug/.cargo-lock ================================================ ================================================ FILE: serverless-api/target/debug/.fingerprint/serverless-api-58a0200541263288/lib-serverless_api-58a0200541263288 ================================================ 68e06764be0d3fe0 ================================================ FILE: serverless-api/target/debug/.fingerprint/serverless-api-58a0200541263288/lib-serverless_api-58a0200541263288.json ================================================ {"rustc":2983502972076547984,"features":"[]","target":12616003758275913516,"profile":16965165444893579269,"path":10872709659218687626,"deps":[],"local":[{"MtimeBased":[[1526638291,222510886],".fingerprint/serverless-api-58a0200541263288/dep-lib-serverless_api-58a0200541263288"]}],"rustflags":[],"edition":"Edition2015"} ================================================ FILE: serverless-api/target/debug/.fingerprint/serverless-api-81a9cad5d43bde63/lib-serverless_api-81a9cad5d43bde63 ================================================ d109624b85f6a4ab ================================================ FILE: serverless-api/target/debug/.fingerprint/serverless-api-81a9cad5d43bde63/lib-serverless_api-81a9cad5d43bde63.json ================================================ {"rustc":2983502972076547984,"features":"[]","target":12616003758275913516,"profile":4101608828254088483,"path":10872709659218687626,"deps":[],"local":[{"MtimeBased":[[1526637383,987549318],".fingerprint/serverless-api-81a9cad5d43bde63/dep-lib-serverless_api-81a9cad5d43bde63"]}],"rustflags":[],"edition":"Edition2015"} ================================================ FILE: serverless-api/target/debug/deps/serverless_api-58a0200541263288.d ================================================ /Users/geal/dev/rust/projects/serverless-wasm/serverless-api/target/debug/deps/libserverless_api-58a0200541263288.rlib: src/lib.rs /Users/geal/dev/rust/projects/serverless-wasm/serverless-api/target/debug/deps/serverless_api-58a0200541263288.d: src/lib.rs src/lib.rs: ================================================ FILE: serverless-api/target/debug/deps/serverless_api-81a9cad5d43bde63.d ================================================ /Users/geal/dev/rust/projects/serverless-wasm/serverless-api/target/debug/deps/serverless_api-81a9cad5d43bde63.rmeta: src/lib.rs /Users/geal/dev/rust/projects/serverless-wasm/serverless-api/target/debug/deps/serverless_api-81a9cad5d43bde63.d: src/lib.rs src/lib.rs: ================================================ FILE: serverless-api/target/debug/libserverless_api.d ================================================ /Users/geal/dev/rust/projects/serverless-wasm/serverless-api/target/debug/libserverless_api.rlib: /Users/geal/dev/rust/projects/serverless-wasm/serverless-api/src/lib.rs ================================================ FILE: src/async/host.rs ================================================ //! from https://github.com/paritytech/wasmi/blob/master/src/tests/host.rs use slab::Slab; use std::collections::HashMap; use std::io::{Read, Write}; use std::iter::repeat; use mio::net::TcpStream; use std::net::SocketAddr; use std::str; use std::cmp; use std::rc::Rc; use std::cell::RefCell; use wasmi::memory_units::Pages; use wasmi::*; use interpreter::Host; #[derive(Debug)] pub enum AsyncHostError { Connecting(SocketAddr), TcpRead(i32, u32, u64), TcpWrite(i32, u32, u64, usize), } impl ::std::fmt::Display for AsyncHostError { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { write!(f, "{:?}", self) } } impl HostError for AsyncHostError {} #[derive(Clone, Debug)] pub struct PreparedResponse { pub status_code: Option, pub reason: Option, pub headers: Vec<(String, String)>, pub body: Option>, } impl PreparedResponse { pub fn new() -> PreparedResponse { PreparedResponse { status_code: None, reason: None, headers: Vec::new(), body: None, } } } pub struct State { pub memory: Option, pub instance: Option, pub prepared_response: PreparedResponse, pub connections: Slab, pub db: HashMap, } impl State { pub fn new() -> State { State { memory: None,//Some(MemoryInstance::alloc(Pages(3), Some(Pages(100))).unwrap()), instance: None, prepared_response: PreparedResponse::new(), connections: Slab::with_capacity(100), db: HashMap::new(), } } } impl State { pub fn get_buf(&mut self, ptr: u32, size: usize) -> Option> { self.memory.as_ref().and_then(|mref| { mref.get(ptr, size).map_err(|e| println!("get buf error: {:?}", e)).ok() }) } pub fn write_buf(&mut self, ptr: u32, data: &[u8]) { self.memory.as_ref().map(|m| m.set(ptr, data)); } } pub struct AsyncHost { pub inner: Rc>, } impl Host for AsyncHost { type State = State; fn build(s: Rc>) -> Self { AsyncHost { inner: s } } } /// log(ptr: *mut u8, size: u64) /// /// Returns value at the given address in memory. This function /// requires attached memory. const LOG_INDEX: usize = 0; const RESPONSE_SET_STATUS_LINE: usize = 1; const RESPONSE_SET_HEADER: usize = 2; const RESPONSE_SET_BODY: usize = 3; const TCP_CONNECT: usize = 4; const TCP_READ: usize = 5; const TCP_WRITE: usize = 6; const DB_GET: usize = 7; impl Externals for AsyncHost { fn invoke_index(&mut self, index: usize, args: RuntimeArgs) -> Result, Trap> { match index { LOG_INDEX => { let ptr: u32 = args.nth(0); let sz: u64 = args.nth(1); let v = self .inner .borrow() .memory .as_ref() .expect("Function 'inc_mem' expects attached memory") .get(ptr, sz as usize) .unwrap(); println!("log({} bytes): {}", v.len(), str::from_utf8(&v).unwrap()); Ok(None) } RESPONSE_SET_STATUS_LINE => { let status: u32 = args.nth(0); let ptr: u32 = args.nth(1); let sz: u64 = args.nth(2); let reason = self .inner .borrow() .memory .as_ref() .expect("Function 'inc_mem' expects attached memory") .get(ptr, sz as usize) .unwrap(); self.inner.borrow_mut().prepared_response.status_code = Some(status as u16); self.inner.borrow_mut().prepared_response.reason = Some(String::from_utf8(reason).unwrap()); Ok(None) } RESPONSE_SET_HEADER => { let ptr1: u32 = args.nth(0); let sz1: u64 = args.nth(1); let ptr2: u32 = args.nth(2); let sz2: u64 = args.nth(3); let header_name = { self .inner .borrow() .memory .as_ref() .expect("Function 'inc_mem' expects attached memory") .get(ptr1, sz1 as usize) .unwrap() }; let header_value = { self .inner .borrow() .memory .as_ref() .expect("Function 'inc_mem' expects attached memory") .get(ptr2, sz2 as usize) .unwrap() }; self.inner.borrow_mut().prepared_response.headers.push(( String::from_utf8(header_name).unwrap(), String::from_utf8(header_value).unwrap(), )); Ok(None) } RESPONSE_SET_BODY => { let ptr: u32 = args.nth(0); let sz: u64 = args.nth(1); let body = self .inner .borrow() .memory .as_ref() .expect("Function 'inc_mem' expects attached memory") .get(ptr, sz as usize) .unwrap(); self.inner.borrow_mut().prepared_response.body = Some(body); Ok(None) } TCP_CONNECT => { let ptr: u32 = args.nth(0); let sz: u64 = args.nth(1); let v = self .inner .borrow() .memory .as_ref() .expect("Function 'inc_mem' expects attached memory") .get(ptr, sz as usize) .unwrap(); let address = String::from_utf8(v).unwrap(); println!("received tcp_connect for {:?}", address); let error = AsyncHostError::Connecting(address.parse().unwrap()); Err(Trap::new(TrapKind::Host(Box::new(error)))) } TCP_READ => { let fd: i32 = args.nth(0); let ptr: u32 = args.nth(1); let sz: u64 = args.nth(2); let error = AsyncHostError::TcpRead(fd, ptr, sz); Err(Trap::new(TrapKind::Host(Box::new(error)))) /* let mut v = Vec::with_capacity(sz as usize); v.extend(repeat(0).take(sz as usize)); let mut state = self.inner.borrow_mut(); if let Ok(sz) = state.connections[fd as usize].read(&mut v) { state.memory.as_ref().map(|m| m.set(ptr, &v[..sz])); Ok(Some(RuntimeValue::I64(sz as i64))) } else { Ok(Some(RuntimeValue::I64(-1))) } */ } TCP_WRITE => { let fd: i32 = args.nth(0); let ptr: u32 = args.nth(1); let sz: u64 = args.nth(2); let error = AsyncHostError::TcpWrite(fd, ptr, sz, 0); Err(Trap::new(TrapKind::Host(Box::new(error)))) /* let buf = self .inner .borrow() .memory .as_ref() .expect("Function 'inc_mem' expects attached memory") .get(ptr, sz as usize) .unwrap(); if let Ok(sz) = self.inner.borrow_mut().connections[fd as usize].write(&buf) { Ok(Some(RuntimeValue::I64(sz as i64))) } else { Ok(Some(RuntimeValue::I64(-1))) } */ } DB_GET => { let key_ptr: u32 = args.nth(0); let key_sz: u64 = args.nth(1); let value_ptr: u32 = args.nth(2); let value_sz: u64 = args.nth(3); let v = self .inner .borrow() .memory .as_ref() .expect("Function 'inc_mem' expects attached memory") .get(key_ptr, key_sz as usize) .unwrap(); let key = String::from_utf8(v).unwrap(); println!("requested value for key {}", key); match self.inner.borrow().db.get(&key) { None => Ok(Some(RuntimeValue::I64(-1))), Some(value) => { let to_write = cmp::min(value.len(), value_sz as usize); self .inner .borrow() .memory .as_ref() .map(|m| m.set(value_ptr, (&value[..to_write]).as_bytes())); Ok(Some(RuntimeValue::I64(value.len() as i64))) } } } _ => panic!("env doesn't provide function at index {}", index), } } } impl State { fn check_signature(&self, index: usize, signature: &Signature) -> bool { let (params, ret_ty): (&[ValueType], Option) = match index { LOG_INDEX => (&[ValueType::I32, ValueType::I64], None), RESPONSE_SET_STATUS_LINE => (&[ValueType::I32, ValueType::I32, ValueType::I64], None), RESPONSE_SET_HEADER => ( &[ ValueType::I32, ValueType::I64, ValueType::I32, ValueType::I64, ], None, ), RESPONSE_SET_BODY => (&[ValueType::I32, ValueType::I64], None), TCP_CONNECT => (&[ValueType::I32, ValueType::I64], Some(ValueType::I32)), TCP_READ => ( &[ValueType::I32, ValueType::I32, ValueType::I64], Some(ValueType::I64), ), TCP_WRITE => ( &[ValueType::I32, ValueType::I32, ValueType::I64], Some(ValueType::I64), ), DB_GET => ( &[ ValueType::I32, ValueType::I64, ValueType::I32, ValueType::I64, ], Some(ValueType::I64), ), _ => return false, }; signature.params() == params && signature.return_type() == ret_ty } } impl ModuleImportResolver for State { fn resolve_func(&self, field_name: &str, signature: &Signature) -> Result { let index = match field_name { "log" => LOG_INDEX, "response_set_status_line" => RESPONSE_SET_STATUS_LINE, "response_set_header" => RESPONSE_SET_HEADER, "response_set_body" => RESPONSE_SET_BODY, "tcp_connect" => TCP_CONNECT, "tcp_read" => TCP_READ, "tcp_write" => TCP_WRITE, "db_get" => DB_GET, _ => { return Err(Error::Instantiation(format!( "Export {} not found", field_name ))) } }; if !self.check_signature(index, signature) { return Err(Error::Instantiation(format!( "Export `{}` doesnt match expected type {:?}", field_name, signature ))); } Ok(FuncInstance::alloc_host(signature.clone(), index)) } fn resolve_memory(&self, _field_name: &str, _memory_type: &MemoryDescriptor) -> Result { let Pages(initial1) = self.memory.as_ref().map(|m| m.initial()).unwrap(); let initial2 = _memory_type.initial() as usize; //println!("requested {} pages", initial2); if initial2 > initial1 { self.memory.as_ref().map(|_m| { //println!("grow res: {:?}", m.grow(Pages(initial2 - initial1)).unwrap()); }); } let Pages(_initial) = self.memory.as_ref().map(|m| m.current_size()).unwrap(); //println!("current number of pages: {}", initial); //println!("resolving memory at name: {}", field_name); let res = self.memory.as_ref().unwrap().clone(); Ok(res) } } pub struct StateResolver { pub inner: Rc>, } impl ModuleImportResolver for StateResolver { fn resolve_func(&self, field_name: &str, signature: &Signature) -> Result { let index = match field_name { "log" => LOG_INDEX, "response_set_status_line" => RESPONSE_SET_STATUS_LINE, "response_set_header" => RESPONSE_SET_HEADER, "response_set_body" => RESPONSE_SET_BODY, "tcp_connect" => TCP_CONNECT, "tcp_read" => TCP_READ, "tcp_write" => TCP_WRITE, "db_get" => DB_GET, _ => { return Err(Error::Instantiation(format!( "Export {} not found", field_name ))) } }; if !self.inner.borrow().check_signature(index, signature) { return Err(Error::Instantiation(format!( "Export `{}` doesnt match expected type {:?}", field_name, signature ))); } Ok(FuncInstance::alloc_host(signature.clone(), index)) } fn resolve_memory(&self, _field_name: &str, _memory_type: &MemoryDescriptor) -> Result { self.inner.borrow_mut().memory = Some(MemoryInstance::alloc(Pages(_memory_type.initial() as usize), Some(Pages(100))).unwrap()); Ok(self.inner.borrow().memory.as_ref().unwrap().clone()) } } ================================================ FILE: src/async/mod.rs ================================================ use config::{ApplicationState, Config}; use mio::*; use mio::net::{TcpListener, TcpStream}; use mio::unix::UnixReady; use std::rc::Rc; use std::cell::RefCell; use std::collections::VecDeque; use slab::Slab; mod host; mod session; const SERVER: Token = Token(0); pub fn server(config: Config) { let state = ApplicationState::new(&config); let addr = (&config.listen_address).parse().unwrap(); let server = TcpListener::bind(&addr).unwrap(); let mut poll = Poll::new().unwrap(); poll .register(&server, SERVER, Ready::readable(), PollOpt::edge()) .unwrap(); let mut events = Events::with_capacity(1024); let state = Rc::new(RefCell::new(state)); let mut connections = Slab::with_capacity(1024); let mut ready = VecDeque::new(); loop { poll.poll(&mut events, None).unwrap(); println!("got events: {:?}", events); for event in events.iter() { match event.token() { SERVER => { if let Ok((sock, addr)) = server.accept() { match connections.vacant_entry() { None => { println!("error: no more room for new connections"); } Some(entry) => { let index = entry.index(); poll.register( &sock, Token(index + 1), Ready::readable() | Ready::writable() | Ready::from(UnixReady::hup() | UnixReady::error()), PollOpt::edge(), ); let client = Rc::new(RefCell::new(session::Session::new( state.clone(), sock, index, ))); entry.insert(client); } } } } Token(i) => { let client_token = i - 1; if let Some(ref mut client) = connections.get_mut(client_token) { if client .borrow_mut() .process_events(client_token, event.readiness()) { ready.push_back(client_token); } } else { println!( "non existing token {:?} got events {:?}", client_token, event.readiness() ); } } _ => unreachable!(), } } for client_token in ready.drain(..) { let mut cont = session::ExecutionResult::Continue; if let Some(ref mut client) = connections.get_mut(client_token) { cont = client.borrow_mut().execute(); } else { println!("non existing token {:?} was marked as ready", client_token); } match cont { session::ExecutionResult::Close(tokens) => { for t in tokens.iter() { connections.remove(client_token); } }, session::ExecutionResult::ConnectBackend(address) => { let client = connections.get(client_token).unwrap().clone(); match connections.vacant_entry() { None => { println!("error: no more room for new connections"); } Some(entry) => { let index = entry.index(); let stream = TcpStream::connect(&address).unwrap(); poll.register( &stream, Token(index + 1), Ready::readable() | Ready::writable() | Ready::from(UnixReady::hup() | UnixReady::error()), PollOpt::edge(), ); client.borrow_mut().add_backend(stream, index); entry.insert(client); } } }, _ => {} } } } } ================================================ FILE: src/async/session.rs ================================================ use mio::unix::UnixReady; use mio::net::TcpStream; use mio::{Poll, Ready}; use std::collections::HashMap; use std::iter::repeat; use std::rc::Rc; use std::io::{ErrorKind, Read, Write}; use std::cell::RefCell; use std::net::{SocketAddr, Shutdown}; use slab::Slab; use interpreter::WasmInstance; use super::host; use config::ApplicationState; use httparse; use wasmi::{ExternVal, ImportsBuilder, ModuleInstance, TrapKind, RuntimeValue}; #[derive(Debug, Clone, PartialEq)] pub enum ExecutionResult { WouldBlock, Close(Vec), Continue, ConnectBackend(SocketAddr), //Register(usize), //Remove(Vec), } #[derive(Debug)] pub struct Stream { pub readiness: UnixReady, pub interest: UnixReady, pub stream: TcpStream, pub index: usize, } pub struct Buf { buf: Vec, offset: usize, len: usize, } #[derive(Debug,Clone,PartialEq)] pub enum SessionState { WaitingForRequest, WaitingForBackendConnect(usize), TcpRead(i32, u32, usize), TcpWrite(i32, Vec, usize), Executing, Done, } pub struct Session { client: Stream, backends: HashMap, instance: Option>, config: Rc>, buffer: Buf, pub state: Option, method: Option, path: Option, env: Option>>, } impl Session { pub fn new(config: Rc>, stream: TcpStream, index: usize) -> Session { let client = Stream { readiness: UnixReady::from(Ready::empty()), interest: UnixReady::from(Ready::readable()) | UnixReady::hup() | UnixReady::error(), stream, index, }; let capacity = 8192; let mut v = Vec::with_capacity(capacity); v.extend(repeat(0).take(capacity)); let buffer = Buf { buf: v, offset: 0, len: 0, }; Session { client, backends: HashMap::new(), instance: None, config, buffer, state: Some(SessionState::WaitingForRequest), method: None, path: None, env: None, } } pub fn add_backend(&mut self, stream: TcpStream, index: usize) { let s = Stream { readiness: UnixReady::from(Ready::empty()), interest: UnixReady::from(Ready::writable()) | UnixReady::hup() | UnixReady::error(), stream, index, }; self.backends.insert(index, s); self.state = Some(SessionState::WaitingForBackendConnect(index)); } pub fn resume(&mut self) -> ExecutionResult { let res = self.instance.as_mut().map(|instance| instance.resume()).unwrap(); println!("resume result: {:?}", res); match res { Err(t) => match t.kind() { TrapKind::Host(ref err) => { match err.as_ref().downcast_ref() { Some(host::AsyncHostError::Connecting(address)) => { println!("returning connect to backend server: {}", address); return ExecutionResult::ConnectBackend(address.clone()); }, Some(host::AsyncHostError::TcpWrite(fd, ptr, sz, written)) => { self.backends.get_mut(&(*fd as usize)).map(|backend| backend.interest.insert(UnixReady::from(Ready::writable()))); let buf = self.env.as_mut().and_then(|env| env.borrow_mut().get_buf(*ptr, *sz as usize)).unwrap(); self.state = Some(SessionState::TcpWrite(*fd, buf, *written)); return ExecutionResult::Continue; }, Some(host::AsyncHostError::TcpRead(fd, ptr, sz)) => { self.backends.get_mut(&(*fd as usize)).map(|backend| backend.interest.insert(UnixReady::from(Ready::readable()))); self.state = Some(SessionState::TcpRead(*fd, *ptr, *sz as usize)); return ExecutionResult::Continue; }, _ => { panic!("got host error: {:?}", err) } } }, _ => { panic!("got trap: {:?}", t); } }, Ok(_) => if self .instance .as_mut() .map(|instance| { println!( "set up response: {:?}", instance.state.borrow().prepared_response ); instance .state .borrow() .prepared_response .status_code .is_some() && instance.state.borrow().prepared_response.body.is_some() }) .unwrap_or(false) { self.client.interest.insert(Ready::writable()); return ExecutionResult::Continue } } ExecutionResult::Continue } pub fn create_instance(&mut self) -> ExecutionResult { let method = self.method.as_ref().unwrap(); let path = self.path.as_ref().unwrap(); if let Some((func_name, module, ref opt_env)) = self.config.borrow().route(method, path) { let mut env = host::State::new(); if let Some(h) = opt_env { env.db.extend( h.iter() .map(|(ref k, ref v)| (k.to_string(), v.to_string())), ); } let env = Rc::new(RefCell::new(env)); self.env = Some(env.clone()); let resolver = host::StateResolver { inner: env.clone() }; let main = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &resolver)) .expect("Failed to instantiate module") .assert_no_start(); if let Some(ExternVal::Func(func_ref)) = main.export_by_name(func_name) { let instance = WasmInstance::new(env, &func_ref, &[]); self.instance = Some(instance); ExecutionResult::Continue } else { println!("function not found"); self .client .stream .write(b"HTTP/1.1 404 Not Found\r\nContent-length: 19\r\n\r\nFunction not found\n"); self.client.stream.shutdown(Shutdown::Both); self.client.interest = UnixReady::from(Ready::empty()); ExecutionResult::Close(vec![self.client.index]) } } else { println!("route not found"); self .client .stream .write(b"HTTP/1.1 404 Not Found\r\nContent-length: 16\r\n\r\nRoute not found\n"); self.client.stream.shutdown(Shutdown::Both); self.client.interest = UnixReady::from(Ready::empty()); ExecutionResult::Close(vec![self.client.index]) } } pub fn process_events(&mut self, token: usize, events: Ready) -> bool { println!("client[{}]: token {} got events {:?}", self.client.index, token, events); if token == self.client.index { self.client.readiness = self.client.readiness | UnixReady::from(events); self.client.readiness & self.client.interest != UnixReady::from(Ready::empty()) } else { if let Some(ref mut stream) = self.backends.get_mut(&token) { println!("state: {:?}", self.state); if self.state == Some(SessionState::WaitingForBackendConnect(token)) { self.instance.as_mut().map(|instance| instance.add_function_result(RuntimeValue::I32(token as i32))); self.state = Some(SessionState::Executing); } stream.readiness.insert(UnixReady::from(events)); stream.readiness & stream.interest != UnixReady::from(Ready::empty()) } else { println!("non existing backend {} got events {:?}", token, events); false } } } pub fn execute(&mut self) -> ExecutionResult { loop { let front_readiness = self.client.readiness & self.client.interest; if front_readiness.is_readable() { let res = self.front_readable(); if res != ExecutionResult::Continue { return res; } } if front_readiness.is_writable() { let res = self.front_writable(); if res != ExecutionResult::Continue { return res; } } let res = self.process(); if res != ExecutionResult::Continue { return res; } } } fn front_readable(&mut self) -> ExecutionResult { if self.state == Some(SessionState::WaitingForRequest) { loop { if self.buffer.offset + self.buffer.len == self.buffer.buf.len() { break; } match self .client .stream .read(&mut self.buffer.buf[self.buffer.offset + self.buffer.len..]) { Ok(0) => { return ExecutionResult::Close(vec![self.client.index]); } Ok(sz) => { self.buffer.len += sz; } Err(e) => { if e.kind() == ErrorKind::WouldBlock { self.client.readiness.remove(Ready::readable()); break; } } } } ExecutionResult::Continue } else { ExecutionResult::Close(vec![self.client.index]) } } fn process(&mut self) -> ExecutionResult { println!("[{}] process", self.client.index); let state = self.state.take().unwrap(); match state { SessionState::WaitingForRequest => { let (method, path) = { let mut headers = [httparse::Header { name: "", value: &[], }; 16]; let mut req = httparse::Request::new(&mut headers); match req.parse(&self.buffer.buf[self.buffer.offset..self.buffer.len]) { Err(e) => { println!("http parsing error: {:?}", e); self.state = Some(SessionState::WaitingForRequest); return ExecutionResult::Close(vec![self.client.index]); } Ok(httparse::Status::Partial) => { self.state = Some(SessionState::WaitingForRequest); return ExecutionResult::Continue; } Ok(httparse::Status::Complete(sz)) => { self.buffer.offset += sz; println!("got request: {:?}", req); ( req.method.unwrap().to_string(), req.path.unwrap().to_string(), ) } } }; self.client.interest.remove(Ready::readable()); self.method = Some(method); self.path = Some(path); self.state = Some(SessionState::Executing); ExecutionResult::Continue }, SessionState::Executing => { if self.instance.is_none() { let res = self.create_instance(); if res != ExecutionResult::Continue { self.state = Some(SessionState::Executing); return res; } } println!("resuming"); self.state = Some(SessionState::Executing); self.resume() }, SessionState::TcpRead(fd, ptr, sz) => { let readiness = self.backends[&(fd as usize)].readiness & self.backends[&(fd as usize)].interest; println!("tcpread({}): readiness: {:?}", fd, readiness); if readiness.is_readable() { let mut buffer = Vec::with_capacity(sz as usize); buffer.extend(repeat(0).take(sz as usize)); let mut read = 0usize; loop { match self.backends.get_mut(&(fd as usize)).unwrap().stream.read(&mut buffer[read..]) { Ok(0) => { println!("read 0"); self.backends.get_mut(&(fd as usize)).map(|backend| backend.readiness.remove(Ready::readable())); self.env.as_mut().map(|env| env.borrow_mut().write_buf(ptr, &buffer[..read])); self.instance.as_mut().map(|instance| instance.add_function_result(RuntimeValue::I64(read as i64))); self.state = Some(SessionState::Executing); return ExecutionResult::Continue; }, Ok(sz) => { read += sz; println!("read {} bytes", read); if read == sz { //FIXME: return result self.env.as_mut().map(|env| env.borrow_mut().write_buf(ptr, &buffer[..read])); self.instance.as_mut().map(|instance| instance.add_function_result(RuntimeValue::I64(read as i64))); self.state = Some(SessionState::Executing); return ExecutionResult::Continue; } }, Err(e) => match e.kind() { ErrorKind::WouldBlock => { println!("wouldblock"); self.backends.get_mut(&(fd as usize)).map(|backend| backend.readiness.remove(Ready::readable())); self.env.as_mut().map(|env| env.borrow_mut().write_buf(ptr, &buffer[..read])); self.instance.as_mut().map(|instance| instance.add_function_result(RuntimeValue::I64(read as i64))); self.state = Some(SessionState::Executing); return ExecutionResult::Continue; }, e => { println!("backend socket error: {:?}", e); self.instance.as_mut().map(|instance| instance.add_function_result(RuntimeValue::I64(-1))); self.state = Some(SessionState::Executing); //FIXME return ExecutionResult::Continue; } } } } } else { self.state = Some(SessionState::TcpRead(fd, ptr, sz)); ExecutionResult::WouldBlock } }, SessionState::TcpWrite(fd, buffer, mut written) => { let readiness = self.backends[&(fd as usize)].readiness & self.backends[&(fd as usize)].interest; if readiness.is_writable() { loop { match self.backends.get_mut(&(fd as usize)).unwrap().stream.write(&buffer[written..]) { Ok(0) => { self.backends.get_mut(&(fd as usize)).map(|backend| backend.readiness.remove(Ready::writable())); self.instance.as_mut().map(|instance| instance.add_function_result(RuntimeValue::I64(written as i64))); self.state = Some(SessionState::Executing); return ExecutionResult::Continue; }, Ok(sz) => { written += sz; println!("wrote {} bytes", sz); if written == buffer.len() { //FIXME: return result self.state = Some(SessionState::Executing); self.instance.as_mut().map(|instance| instance.add_function_result(RuntimeValue::I64(written as i64))); return ExecutionResult::Continue; } }, Err(e) => match e.kind() { ErrorKind::WouldBlock => { println!("wouldblock"); self.backends.get_mut(&(fd as usize)).map(|backend| backend.readiness.remove(Ready::writable())); self.state = Some(SessionState::TcpWrite(fd, buffer, written)); return ExecutionResult::Continue; }, e => { println!("backend socket error: {:?}", e); self.instance.as_mut().map(|instance| instance.add_function_result(RuntimeValue::I64(-1))); self.state = Some(SessionState::Executing); //FIXME return ExecutionResult::Continue; } } } } } else { self.state = Some(SessionState::TcpWrite(fd, buffer, written)); ExecutionResult::WouldBlock } //FIXME: handle error and hup }, SessionState::WaitingForBackendConnect(_) => { panic!("should not have called execute() in WaitingForBackendConnect"); }, SessionState::Done => { panic!("done"); } } } fn front_writable(&mut self) -> ExecutionResult { println!("[{}] front writable", self.client.index); let response = self .instance .as_mut() .map(|instance| instance.state.borrow().prepared_response.clone()) .unwrap(); self .client .stream .write_fmt(format_args!("HTTP/1.1 {} {}\r\n", response.status_code.unwrap(), response.reason.unwrap())); for header in response.headers.iter() { self .client .stream .write_fmt(format_args!("{}: {}\r\n", header.0, header.1)); } self.client.stream.write(b"\r\n"); self.client.stream.write(&response.body.unwrap()[..]); ExecutionResult::Close(vec![self.client.index]) } } ================================================ FILE: src/config.rs ================================================ use interpreter::load_module; use std::collections::HashMap; use std::fs::File; use std::io::Read; use toml; use wasmi::Module; #[derive(Deserialize, Debug)] pub struct WasmApp { pub file_path: String, pub method: String, pub url_path: String, pub function: String, pub env: Option>, } #[derive(Deserialize, Debug)] pub struct Config { pub listen_address: String, pub applications: Vec, } pub fn load(file: &str) -> Option { if let Ok(mut file) = File::open(file) { let mut contents = String::new(); if let Ok(_) = file.read_to_string(&mut contents) { return toml::from_str(&contents) .map_err(|e| { println!("configuration deserialization error: {:?}", e); e }) .ok(); } } None } pub struct ApplicationState { /// (method, url path) -> (function name, module path, env) pub routes: HashMap<(String, String), (String, String, Option>)>, /// module path -> Module pub modules: HashMap, } impl ApplicationState { pub fn new(config: &Config) -> ApplicationState { let mut routes = HashMap::new(); let mut modules = HashMap::new(); for app in config.applications.iter() { //FIXME: it might be good to not panic when we don't find the function in the module let module = load_module(&app.file_path, &app.function); if !modules.contains_key(&app.file_path) { modules.insert(app.file_path.clone(), module); } routes.insert( (app.method.clone(), app.url_path.clone()), (app.function.clone(), app.file_path.clone(), app.env.clone()), ); } ApplicationState { routes: routes, modules: modules, } } pub fn route(&self, method: &str, url: &str) -> Option<(&str, &Module, &Option>)> { if let Some((func_name, module_path, ref opt_env)) = self.routes.get(&(method.to_string(), url.to_string())) { if let Some(module) = self.modules.get(module_path) { return Some((func_name, module, opt_env)); } } None } } ================================================ FILE: src/interpreter.rs ================================================ use parity_wasm; use parity_wasm::elements::{External, FunctionType, Internal, Type, ValueType}; use std::collections::VecDeque; use wasmi::{self, Module}; use wasmi::{BlockFrameType, Externals, FuncInstance, FuncRef, FunctionContext, Interpreter, RunResult, RuntimeValue, Trap, TrapKind}; use std::marker; use std::rc::Rc; use std::cell::RefCell; pub const DEFAULT_VALUE_STACK_LIMIT: usize = 16384; pub const DEFAULT_FRAME_STACK_LIMIT: usize = 16384; pub trait HostBuilder<'a, S> { fn build(s: &'a mut S) -> Self; } pub trait Host { type State; fn build(s: Rc>) -> Self; } pub struct WasmInstance> { pub state: Rc>, pub stack: VecDeque, _marker: marker::PhantomData, } impl> WasmInstance { pub fn new(state: Rc>, func_ref: &FuncRef, args: &[RuntimeValue]) -> WasmInstance { let stack = create_stack(&func_ref, args); WasmInstance { state: state, stack, _marker: marker::PhantomData, } } pub fn resume(&mut self) -> Result, Trap> { let mut host = E::build(self.state.clone()); let mut interpreter = Interpreter::new(&mut host); println!("WasmInstance::resume: stack\n{:?}", self.stack); my_run_interpreter_loop(&mut interpreter, &mut self.stack) } pub fn add_function_result(&mut self, return_value: RuntimeValue) { self.stack.back_mut().map(|function_context| { function_context.value_stack_mut().push(return_value).expect("should have pushed the return value"); println!("adding return value to {:?} initialized: {}", function_context.function, function_context.is_initialized); }); println!("added function result {:?}, stack len:{}", return_value, self.stack.len()); } } pub fn create_stack(func: &FuncRef, args: &[RuntimeValue]) -> VecDeque { let context = FunctionContext::new( func.clone(), DEFAULT_VALUE_STACK_LIMIT, DEFAULT_FRAME_STACK_LIMIT, func.signature(), args.into_iter().cloned().collect(), ); let mut function_stack = VecDeque::new(); function_stack.push_back(context); function_stack } pub fn my_run_interpreter_loop( interpreter: &mut Interpreter, function_stack: &mut VecDeque, ) -> Result, Trap> where E: Externals, { loop { let mut function_context = function_stack .pop_back() .expect("on loop entry - not empty; on loop continue - checking for emptiness; qed"); let function_ref = function_context.function.clone(); let function_body = function_ref .body() .expect("Host functions checked in function_return below; Internal functions always have a body; qed"); if !function_context.is_initialized() { let return_type = function_context.return_type; function_context.initialize(&function_body.locals); function_context .push_frame(&function_body.labels, BlockFrameType::Function, return_type) .map_err(Trap::new)?; } let function_return = interpreter .do_run_function( &mut function_context, function_body.opcodes.elements(), &function_body.labels, ) .map_err(Trap::new)?; match function_return { RunResult::Return(return_value) => match function_stack.back_mut() { Some(caller_context) => if let Some(return_value) = return_value { caller_context .value_stack_mut() .push(return_value) .map_err(Trap::new)?; }, None => return Ok(return_value), }, RunResult::NestedCall(nested_func) => { //println!("calling nested func, stack len={}", function_stack.len()); match FuncInstance::invoke_context(&nested_func, &mut function_context, interpreter.externals) { Err(t) => { if let TrapKind::Host(_) = t.kind() { //function_context.value_stack_mut().push(RuntimeValue::I32(42)).expect("should have pushed the return value"); function_stack.push_back(function_context); println!("got host trapkind"); return Err(t); } else { println!("resume got error: {:?}", t); return Err(t); } }, Ok(None) => { function_stack.push_back(function_context); //println!("got ok(none) stack len={}", function_stack.len()); } Ok(Some(nested_context)) => { function_stack.push_back(function_context); function_stack.push_back(nested_context); //println!("got ok(some(nested_context)) stack len={}", function_stack.len()); } } } } } } pub fn load_module(file: &str, func_name: &str) -> Module { let module = parity_wasm::deserialize_file(file).expect("File to be deserialized"); // Extracts call arguments from command-line arguments let _args = { // Export section has an entry with a func_name with an index inside a module let export_section = module.export_section().expect("No export section found"); // It's a section with function declarations (which are references to the type section entries) let function_section = module .function_section() .expect("No function section found"); // Type section stores function types which are referenced by function_section entries let type_section = module.type_section().expect("No type section found"); // Given function name used to find export section entry which contains // an `internal` field which points to the index in the function index space let found_entry = export_section .entries() .iter() .find(|entry| func_name == entry.field()) .expect(&format!("No export with name {} found", func_name)); // Function index in the function index space (internally-defined + imported) let function_index: usize = match found_entry.internal() { &Internal::Function(index) => index as usize, _ => panic!("Founded export is not a function"), }; // We need to count import section entries (functions only!) to subtract it from function_index // and obtain the index within the function section let import_section_len: usize = match module.import_section() { Some(import) => import .entries() .iter() .map(|entry| { //println!("importing entry {:?}", entry); entry }) .filter(|entry| match entry.external() { &External::Function(_) => true, _ => false, }) .count(), None => 0, }; // Calculates a function index within module's function section let function_index_in_section = function_index - import_section_len; // Getting a type reference from a function section entry let func_type_ref: usize = function_section.entries()[function_index_in_section].type_ref() as usize; // Use the reference to get an actual function type let function_type: &FunctionType = match &type_section.types()[func_type_ref] { &Type::Function(ref func_type) => func_type, }; // Parses arguments and constructs runtime values in correspondence of their types function_type .params() .iter() .enumerate() .map(|(_i, value)| match value { &ValueType::I32 => RuntimeValue::I32( 0, /* program_args[i] .parse::() .expect(&format!("Can't parse arg #{} as i32", program_args[i])),*/ ), &ValueType::I64 => RuntimeValue::I64( 0, /* program_args[i] .parse::() .expect(&format!("Can't parse arg #{} as i64", program_args[i])),*/ ), &ValueType::F32 => RuntimeValue::F32( 0.0, /* program_args[i] .parse::() .expect(&format!("Can't parse arg #{} as f32", program_args[i])),*/ ), &ValueType::F64 => RuntimeValue::F64( 0.0, /* program_args[i] .parse::() .expect(&format!("Can't parse arg #{} as f64", program_args[i])),*/ ), }) .collect::>() }; wasmi::Module::from_parity_wasm_module(module).expect("Module to be valid") } ================================================ FILE: src/jit/env.rs ================================================ use cretonne_wasm::{ ModuleEnvironment, GlobalIndex, MemoryIndex, TableIndex, FunctionIndex, Table, Memory, Global, SignatureIndex, FuncTranslator, FuncEnvironment, GlobalValue }; use cretonne::prelude::{settings::{self, Flags}, types::*, InstBuilder, Signature}; use cretonne::codegen::{ ir::{self, ExternalName, Function}, cursor::FuncCursor }; pub struct Exportable { /// A wasm entity. pub entity: T, /// Names under which the entity is exported. pub export_names: Vec, } impl Exportable { pub fn new(entity: T) -> Self { Self { entity, export_names: Vec::new(), } } } pub struct ModuleInfo { pub flags: Flags, pub signatures: Vec, pub imported_funcs: Vec<(String, String)>, pub functions: Vec>, pub function_bodies: Vec, pub memories: Vec>, pub tables: Vec>, pub globals: Vec>, pub start_func: Option, } impl ModuleInfo { pub fn new() -> ModuleInfo { ModuleInfo { flags: settings::Flags::new(settings::builder()), signatures: Vec::new(), imported_funcs: Vec::new(), functions: Vec::new(), function_bodies: Vec::new(), memories: Vec::new(), tables: Vec::new(), globals: Vec::new(), start_func: None, } } } pub struct Env { pub info: ModuleInfo, trans: FuncTranslator, } impl Env { pub fn new() -> Env { Env { info: ModuleInfo::new(), trans: FuncTranslator::new(), } } } fn get_func_name(func_index: FunctionIndex) -> ir::ExternalName { ExternalName::user(0, func_index as u32) } impl<'data> ModuleEnvironment<'data> for Env { fn flags(&self) -> &Flags { &self.info.flags } fn get_func_name(&self, func_index: FunctionIndex) -> ExternalName { get_func_name(func_index) } fn declare_signature(&mut self, sig: &Signature) { self.info.signatures.push(sig.clone()); } fn get_signature(&self, sig_index: SignatureIndex) -> &Signature { &self.info.signatures[sig_index] } fn declare_func_import( &mut self, sig_index: SignatureIndex, module: &'data str, field: &'data str ) { assert_eq!( self.info.functions.len(), self.info.imported_funcs.len(), "Imported functions must be declared first" ); self.info.functions.push(Exportable::new(sig_index)); self.info.imported_funcs.push(( String::from(module), String::from(field), )); println!("declared function import {}:{}", module, field); } fn get_num_func_imports(&self) -> usize { self.info.imported_funcs.len() } fn declare_func_type(&mut self, sig_index: SignatureIndex) { self.info.functions.push(Exportable::new(sig_index)); } fn get_func_type(&self, func_index: FunctionIndex) -> SignatureIndex { self.info.functions[func_index].entity } fn declare_global(&mut self, global: Global) { self.info.globals.push(Exportable::new(global)); } fn get_global(&self, global_index: GlobalIndex) -> &Global { &self.info.globals[global_index].entity } fn declare_table(&mut self, table: Table) { self.info.tables.push(Exportable::new(table)); } fn declare_table_elements( &mut self, table_index: TableIndex, base: Option, offset: usize, elements: Vec ) { //println!("declaring table elements at table n°{} base {:?} offset {}:{:?}", table_index, base, offset, elements); } fn declare_memory(&mut self, memory: Memory) { println!("declaring new memory zone, min: {}, max: {:?}, shared: {}", memory.pages_count, memory.maximum, memory.shared); self.info.memories.push(Exportable::new(memory)); } fn declare_data_initialization( &mut self, memory_index: MemoryIndex, base: Option, offset: usize, data: &'data [u8] ) { println!("declaring data init for memory n°{}, base {:?}, offset {}, data: {:?}", memory_index, base, offset, data.len()); } fn declare_func_export( &mut self, func_index: FunctionIndex, name: &'data str ) { println!("exporting function n°{} at '{}'", func_index, name); self.info.functions[func_index].export_names.push( String::from(name) ) } fn declare_table_export( &mut self, table_index: TableIndex, name: &'data str ) { unimplemented!() } fn declare_memory_export( &mut self, memory_index: MemoryIndex, name: &'data str ) { unimplemented!() } fn declare_global_export( &mut self, global_index: GlobalIndex, name: &'data str ) { unimplemented!() } fn declare_start_func(&mut self, index: FunctionIndex) { debug_assert!(self.info.start_func.is_none()); self.info.start_func = Some(index); } fn define_function_body( &mut self, body_bytes: &'data [u8] ) -> Result<(), String> { let func = { let mut func_environ = FuncEnv::new(&self.info); let function_index = self.get_num_func_imports() + self.info.function_bodies.len(); let name = get_func_name(function_index); let sig = func_environ.vmctx_sig(self.get_func_type(function_index)); let mut func = Function::with_name_signature(name, sig); self.trans .translate(body_bytes, &mut func, &mut func_environ) .map_err(|e| format!("{}", e))?; func }; self.info.function_bodies.push(func); Ok(()) } } pub struct FuncEnv<'env> { pub mod_info: &'env ModuleInfo, } impl<'env> FuncEnv<'env> { pub fn new(mod_info: &'env ModuleInfo) -> Self { Self { mod_info } } // Create a signature for `sigidx` amended with a `vmctx` argument after the standard wasm // arguments. fn vmctx_sig(&self, sigidx: SignatureIndex) -> ir::Signature { let mut sig = self.mod_info.signatures[sigidx].clone(); sig.params.push(ir::AbiParam::special( self.native_pointer(), ir::ArgumentPurpose::VMContext, )); sig } } impl<'env> FuncEnvironment for FuncEnv<'env> { fn flags(&self) -> &settings::Flags { &self.mod_info.flags } fn make_global(&mut self, func: &mut ir::Function, index: GlobalIndex) -> GlobalValue { // Just create a dummy `vmctx` global. let offset = ((index * 8) as i32 + 8).into(); let gv = func.create_global_var(ir::GlobalVarData::VMContext { offset }); GlobalValue::Memory { gv, ty: self.mod_info.globals[index].entity.ty, } } fn make_heap(&mut self, func: &mut ir::Function, _index: MemoryIndex) -> ir::Heap { // Create a static heap whose base address is stored at `vmctx+0`. let gv = func.create_global_var(ir::GlobalVarData::VMContext { offset: 0.into() }); func.create_heap(ir::HeapData { base: ir::HeapBase::GlobalVar(gv), min_size: 0.into(), guard_size: 0x8000_0000.into(), style: ir::HeapStyle::Static { bound: 0x1_0000_0000.into() }, }) } fn make_indirect_sig(&mut self, func: &mut ir::Function, index: SignatureIndex) -> ir::SigRef { // A real implementation would probably change the calling convention and add `vmctx` and // signature index arguments. func.import_signature(self.vmctx_sig(index)) } fn make_direct_func(&mut self, func: &mut ir::Function, index: FunctionIndex) -> ir::FuncRef { let sigidx = self.mod_info.functions[index].entity; // A real implementation would probably add a `vmctx` argument. // And maybe attempt some signature de-duplication. let signature = func.import_signature(self.vmctx_sig(sigidx)); let name = get_func_name(index); func.import_function(ir::ExtFuncData { name, signature, colocated: false, }) } fn translate_call_indirect( &mut self, mut pos: FuncCursor, _table_index: TableIndex, _sig_index: SignatureIndex, sig_ref: ir::SigRef, callee: ir::Value, call_args: &[ir::Value], ) -> ir::Inst { // Pass the current function's vmctx parameter on to the callee. let vmctx = pos.func .special_param(ir::ArgumentPurpose::VMContext) .expect("Missing vmctx parameter"); // The `callee` value is an index into a table of function pointers. // Apparently, that table is stored at absolute address 0 in this dummy environment. // TODO: Generate bounds checking code. let ptr = self.native_pointer(); let callee_offset = if ptr == I32 { pos.ins().imul_imm(callee, 4) } else { let ext = pos.ins().uextend(I64, callee); pos.ins().imul_imm(ext, 4) }; let mut mflags = ir::MemFlags::new(); mflags.set_notrap(); mflags.set_aligned(); let func_ptr = pos.ins().load(ptr, mflags, callee_offset, 0); // Build a value list for the indirect call instruction containing the callee, call_args, // and the vmctx parameter. let mut args = ir::ValueList::default(); args.push(func_ptr, &mut pos.func.dfg.value_lists); args.extend(call_args.iter().cloned(), &mut pos.func.dfg.value_lists); args.push(vmctx, &mut pos.func.dfg.value_lists); pos.ins() .CallIndirect(ir::Opcode::CallIndirect, VOID, sig_ref, args) .0 } fn translate_call( &mut self, mut pos: FuncCursor, _callee_index: FunctionIndex, callee: ir::FuncRef, call_args: &[ir::Value], ) -> ir::Inst { // Pass the current function's vmctx parameter on to the callee. let vmctx = pos.func .special_param(ir::ArgumentPurpose::VMContext) .expect("Missing vmctx parameter"); // Build a value list for the call instruction containing the call_args and the vmctx // parameter. let mut args = ir::ValueList::default(); args.extend(call_args.iter().cloned(), &mut pos.func.dfg.value_lists); args.push(vmctx, &mut pos.func.dfg.value_lists); pos.ins().Call(ir::Opcode::Call, VOID, callee, args).0 } fn translate_grow_memory( &mut self, mut pos: FuncCursor, _index: MemoryIndex, _heap: ir::Heap, _val: ir::Value, ) -> ir::Value { pos.ins().iconst(I32, -1) } fn translate_current_memory( &mut self, mut pos: FuncCursor, _index: MemoryIndex, _heap: ir::Heap, ) -> ir::Value { pos.ins().iconst(I32, -1) } } ================================================ FILE: src/jit/mod.rs ================================================ use config::Config; use cretonne_wasm::{translate_module, DummyEnvironment}; use std::fs::File; use std::io::Read; mod env; pub fn server(config: Config) { for app in config.applications.iter() { println!("loading {}:{} at '{} {}'", app.file_path, app.function, app.method, app.url_path); if let Ok(mut file) = File::open(&app.file_path) { let mut data = Vec::new(); file.read_to_end(&mut data); //let mut env = DummyEnvironment::default(); let mut env = env::Env::new(); translate_module(&data, &mut env).unwrap(); //let func_env = env.func_env(); //println!("bytecode:\n{:?}", env.func_bytecode_sizes); } } } ================================================ FILE: src/main.rs ================================================ extern crate httparse; extern crate mio; extern crate parity_wasm; extern crate rouille; extern crate slab; extern crate toml; extern crate wasmi; extern crate cretonne; extern crate cretonne_wasm; extern crate cretonne_module; extern crate cretonne_simplejit; #[macro_use] extern crate serde_derive; use std::env::args; mod async; mod config; mod interpreter; mod sync; mod jit; fn main() { let args: Vec<_> = args().collect(); if args.len() != 2 { println!("Usage: {} ", args[0]); return; } if let Some(config) = config::load(&args[1]) { async::server(config); } else { println!("invalid configuration"); } } ================================================ FILE: src/sync/host.rs ================================================ //! from https://github.com/paritytech/wasmi/blob/master/src/tests/host.rs use slab::Slab; use std::collections::HashMap; use std::io::{Read, Write}; use std::iter::repeat; use std::net::TcpStream; use std::str; use std::cmp; use std::rc::Rc; use std::cell::RefCell; use wasmi::memory_units::Pages; use wasmi::*; use interpreter::Host; #[derive(Debug, Clone, PartialEq)] struct HostErrorWithCode { error_code: u32, } impl ::std::fmt::Display for HostErrorWithCode { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { write!(f, "{}", self.error_code) } } impl HostError for HostErrorWithCode {} #[derive(Clone)] pub struct PreparedResponse { pub status_code: Option, pub headers: Vec<(String, String)>, pub body: Option>, } impl PreparedResponse { pub fn new() -> PreparedResponse { PreparedResponse { status_code: None, headers: Vec::new(), body: None, } } } pub struct State { memory: Option, instance: Option, pub prepared_response: PreparedResponse, connections: Slab, pub db: HashMap, } impl State { pub fn new() -> State { State { memory: Some(MemoryInstance::alloc(Pages(3), Some(Pages(10))).unwrap()), instance: None, prepared_response: PreparedResponse::new(), connections: Slab::with_capacity(100), db: HashMap::new(), } } } pub struct SyncHost { pub inner: Rc>, } impl Host for SyncHost { type State = State; fn build(s: Rc>) -> Self { SyncHost { inner: s } } } /// log(ptr: *mut u8, size: u64) /// /// Returns value at the given address in memory. This function /// requires attached memory. const LOG_INDEX: usize = 0; const RESPONSE_SET_STATUS_LINE: usize = 1; const RESPONSE_SET_HEADER: usize = 2; const RESPONSE_SET_BODY: usize = 3; const TCP_CONNECT: usize = 4; const TCP_READ: usize = 5; const TCP_WRITE: usize = 6; const DB_GET: usize = 7; impl Externals for SyncHost { fn invoke_index(&mut self, index: usize, args: RuntimeArgs) -> Result, Trap> { match index { LOG_INDEX => { let ptr: u32 = args.nth(0); let sz: u64 = args.nth(1); let v = self .inner .borrow() .memory .as_ref() .expect("Function 'inc_mem' expects attached memory") .get(ptr, sz as usize) .unwrap(); println!("log({} bytes): {}", v.len(), str::from_utf8(&v).unwrap()); Ok(None) } RESPONSE_SET_STATUS_LINE => { let status: u32 = args.nth(0); let ptr: u32 = args.nth(1); let sz: u64 = args.nth(2); let _reason = self .inner .borrow() .memory .as_ref() .expect("Function 'inc_mem' expects attached memory") .get(ptr, sz as usize) .unwrap(); self.inner.borrow_mut().prepared_response.status_code = Some(status as u16); Ok(None) } RESPONSE_SET_HEADER => { let ptr1: u32 = args.nth(0); let sz1: u64 = args.nth(1); let ptr2: u32 = args.nth(2); let sz2: u64 = args.nth(3); let header_name = { self .inner .borrow() .memory .as_ref() .expect("Function 'inc_mem' expects attached memory") .get(ptr1, sz1 as usize) .unwrap() }; let header_value = { self .inner .borrow() .memory .as_ref() .expect("Function 'inc_mem' expects attached memory") .get(ptr2, sz2 as usize) .unwrap() }; self.inner.borrow_mut().prepared_response.headers.push(( String::from_utf8(header_name).unwrap(), String::from_utf8(header_value).unwrap(), )); Ok(None) } RESPONSE_SET_BODY => { let ptr: u32 = args.nth(0); let sz: u64 = args.nth(1); let body = self .inner .borrow() .memory .as_ref() .expect("Function 'inc_mem' expects attached memory") .get(ptr, sz as usize) .unwrap(); self.inner.borrow_mut().prepared_response.body = Some(body); Ok(None) } TCP_CONNECT => { let ptr: u32 = args.nth(0); let sz: u64 = args.nth(1); let v = self .inner .borrow() .memory .as_ref() .expect("Function 'inc_mem' expects attached memory") .get(ptr, sz as usize) .unwrap(); let address = String::from_utf8(v).unwrap(); if let Ok(socket) = TcpStream::connect(&address) { if let Ok(fd) = self.inner.borrow_mut().connections.insert(socket) { Ok(Some(RuntimeValue::I32(fd as i32))) } else { Ok(Some(RuntimeValue::I32(-2))) } } else { Ok(Some(RuntimeValue::I32(-1))) } } TCP_READ => { let fd: i32 = args.nth(0); let ptr: u32 = args.nth(1); let sz: u64 = args.nth(2); let mut v = Vec::with_capacity(sz as usize); v.extend(repeat(0).take(sz as usize)); let mut state = self.inner.borrow_mut(); if let Ok(sz) = state.connections[fd as usize].read(&mut v) { state.memory.as_ref().map(|m| m.set(ptr, &v[..sz])); Ok(Some(RuntimeValue::I64(sz as i64))) } else { Ok(Some(RuntimeValue::I64(-1))) } } TCP_WRITE => { let fd: i32 = args.nth(0); let ptr: u32 = args.nth(1); let sz: u64 = args.nth(2); let buf = self .inner .borrow() .memory .as_ref() .expect("Function 'inc_mem' expects attached memory") .get(ptr, sz as usize) .unwrap(); if let Ok(sz) = self.inner.borrow_mut().connections[fd as usize].write(&buf) { Ok(Some(RuntimeValue::I64(sz as i64))) } else { Ok(Some(RuntimeValue::I64(-1))) } } DB_GET => { let key_ptr: u32 = args.nth(0); let key_sz: u64 = args.nth(1); let value_ptr: u32 = args.nth(2); let value_sz: u64 = args.nth(3); let v = self .inner .borrow() .memory .as_ref() .expect("Function 'inc_mem' expects attached memory") .get(key_ptr, key_sz as usize) .unwrap(); let key = String::from_utf8(v).unwrap(); println!("requested value for key {}", key); match self.inner.borrow().db.get(&key) { None => Ok(Some(RuntimeValue::I64(-1))), Some(value) => { let to_write = cmp::min(value.len(), value_sz as usize); self .inner .borrow() .memory .as_ref() .map(|m| m.set(value_ptr, (&value[..to_write]).as_bytes())); Ok(Some(RuntimeValue::I64(value.len() as i64))) } } } _ => panic!("env doesn't provide function at index {}", index), } } } impl State { fn check_signature(&self, index: usize, signature: &Signature) -> bool { let (params, ret_ty): (&[ValueType], Option) = match index { LOG_INDEX => (&[ValueType::I32, ValueType::I64], None), RESPONSE_SET_STATUS_LINE => (&[ValueType::I32, ValueType::I32, ValueType::I64], None), RESPONSE_SET_HEADER => ( &[ ValueType::I32, ValueType::I64, ValueType::I32, ValueType::I64, ], None, ), RESPONSE_SET_BODY => (&[ValueType::I32, ValueType::I64], None), TCP_CONNECT => (&[ValueType::I32, ValueType::I64], Some(ValueType::I32)), TCP_READ => ( &[ValueType::I32, ValueType::I32, ValueType::I64], Some(ValueType::I64), ), TCP_WRITE => ( &[ValueType::I32, ValueType::I32, ValueType::I64], Some(ValueType::I64), ), DB_GET => ( &[ ValueType::I32, ValueType::I64, ValueType::I32, ValueType::I64, ], Some(ValueType::I64), ), _ => return false, }; signature.params() == params && signature.return_type() == ret_ty } } impl ModuleImportResolver for State { fn resolve_func(&self, field_name: &str, signature: &Signature) -> Result { let index = match field_name { "log" => LOG_INDEX, "response_set_status_line" => RESPONSE_SET_STATUS_LINE, "response_set_header" => RESPONSE_SET_HEADER, "response_set_body" => RESPONSE_SET_BODY, "tcp_connect" => TCP_CONNECT, "tcp_read" => TCP_READ, "tcp_write" => TCP_WRITE, "db_get" => DB_GET, _ => { return Err(Error::Instantiation(format!( "Export {} not found", field_name ))) } }; if !self.check_signature(index, signature) { return Err(Error::Instantiation(format!( "Export `{}` doesnt match expected type {:?}", field_name, signature ))); } Ok(FuncInstance::alloc_host(signature.clone(), index)) } fn resolve_memory(&self, _field_name: &str, _memory_type: &MemoryDescriptor) -> Result { let Pages(initial1) = self.memory.as_ref().map(|m| m.initial()).unwrap(); let initial2 = _memory_type.initial() as usize; //println!("requested {} pages", initial2); if initial2 > initial1 { self.memory.as_ref().map(|_m| { //println!("grow res: {:?}", m.grow(Pages(initial2 - initial1)).unwrap()); }); } let Pages(_initial) = self.memory.as_ref().map(|m| m.current_size()).unwrap(); //println!("current number of pages: {}", initial); //println!("resolving memory at name: {}", field_name); let res = self.memory.as_ref().unwrap().clone(); Ok(res) } } ================================================ FILE: src/sync/mod.rs ================================================ use rouille; use wasmi::{Error, ExternVal, ImportsBuilder, ModuleInstance}; use std::rc::Rc; use std::cell::RefCell; use config::{ApplicationState, Config}; use interpreter::WasmInstance; mod host; pub fn server(config: Config) { let state = ApplicationState::new(&config); rouille::start_server(&config.listen_address, move |request| { if let Some((func_name, module, ref opt_env)) = state.route(request.method(), &request.url()) { let mut env = host::State::new(); if let Some(h) = opt_env { env.db.extend( h.iter() .map(|(ref k, ref v)| (k.to_string(), v.to_string())), ); } let main = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env)) .expect("Failed to instantiate module") .assert_no_start(); let mut response = env.prepared_response.clone(); if let Some(ExternVal::Func(func_ref)) = main.export_by_name(func_name) { let mut instance: WasmInstance = WasmInstance::new(Rc::new(RefCell::new(env)), &func_ref, &[]); let res = instance.resume().map_err(|t| Error::Trap(t)); println!("invocation result: {:?}", res); response = instance.state.borrow().prepared_response.clone(); } else { panic!("handle error here"); }; if let host::PreparedResponse { status_code: Some(status), headers, body: Some(body), } = response { rouille::Response { status_code: status, headers: Vec::new(), data: rouille::ResponseBody::from_data(body), upgrade: None, } } else { rouille::Response::text("wasm failed").with_status_code(500) } } else { rouille::Response::empty_404() } }); } /* pub fn start(file: &str) { let module = load_module(file, "handle"); let mut env = host::SyncHost::new(); let main = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env)) .expect("Failed to instantiate module") .assert_no_start(); println!( "Result: {:?}", main.invoke_export("handle", &[], &mut env) ); } */