# new a workspace
$ makedir wasm && cd wasm
# quickjs-wasi rust binding
wasm/$ git clone https://github.com/second-state/wasmedge-quickjs.git
# quickjs-wasi
wasm/$ git clone https://github.com/second-state/quickjs-wasi.git
This is quickjs-wasi's rust binding. It is a rust project.
It is majorly using wasi's libquickjs.a generated by quickjs-wasi to compile rust code to qjs.wasm to use javascript in wasm. This is majorly to meet the need of Rust developers who are unfamiliar with C to extend quickjs in wasm.
This library is quickjs being changed based on wasi and some features of quickjs have been deleted.
It is majorly used to provide wasmedge-quickjs with
wasmedge-quickjs/lib/libquickjs.a
wasmedge-quickjs/lib/binding.rs
Usually this is not needed.
This section can be skipped.
1、Generate qjs.c and qjscalc.c
quickjs-wasi$ make
This repl.c and qjscalc.c is the bytecode generated with the corresponding javascript file, mainly working as the repl feature of quickjs. This will be needed in the following compilation.
2、Generate qjs.wasm
wasi-sdk(12) should be installed
quickjs-wasi$ ./build_wasi.sh
This is a standard wasm compiled by quickjs. It can run directly.
# run quickjs repl
quickjs-wasi$ wasmedge --dir=.:. qjs.wasm
QuickJS - Type "\h" for help
qjs > print('hello')
print('hello')
hello
undefined
qjs >
3. Generate libquickjs.a & binding.rs
Rust bindgen needs to be installed
quickjs-wasi$ cd lib
quickjs-wasi/lib$ ./build_lib.sh
# move to wasmedge-quickjs/lib/
cp libquickjs.a ../../wasmedge-quickjs/lib/
cp binding.rs ../../wasmedge-quickjs/lib/
wapper.c
has custom function. Majorly
wapper.h
define exported function.
It will be used by bindgen
in ./build_lib.sh to generate binding.rs
.
If a certain program is better to be implemented by C instead of rust, we can tweak
wapper.*
then re-generate tolibquickjs.a
andbinding.rs
.
cargo-wasi extension needs to be installed
cargo install cargo-wasi
an easy-to-compile command
wasmedge-quickjs/$ cargo wasi build --release
wasmedge-quickjs/$ echo "print('hello')" > example_js/hello.js
wasmedge-quickjs/$ wasmedge --dir=.:. target/wasm32-wasi/release/quickjs-rs-wasi.wasm example_js/hello.js
hello
wasmedge-quickjs/$
This is a http-client feautre extended with wasmedge_wasi_socket .
wasmedge-quickjs/$ cargo wasi build --features=http --release
wasmedge-quickjs/$ wasmedge --dir=.:. target/wasm32-wasi/release/quickjs-rs-wasi.wasm example_js/http.js
haha
add: 3
get-test
200
{"Connection":"close","Server":"gunicorn/19.9.0","Content-Length":"310","Content-Type":"application/json","Access-Control-Allow-Origin":"*","Access-Control-Allow-Credentials":"true","D
ate":"Sat, 28 Aug 2021 21:50:49 GMT"}
{
"args": {
"a": "123"
},
"headers": {
"A": "b",
"C": "1,2,3",
"Host": "54.198.109.87",
"Referer": "http://54.198.109.87/get?a=123",
"X-Amzn-Trace-Id": "Root=1-612aafb9-74d3c8cc45407da9317ca34d"
},
"origin": "36.155.117.30",
"url": "http://54.198.109.87/get?a=123"
}
delete-test
200
{"Access-Control-Allow-Origin":"*","Date":"Sat, 28 Aug 2021 21:50:50 GMT","Content-Length":"379","Server":"gunicorn/19.9.0","Content-Type":"application/json","Access-Control-Allow-Cred
entials":"true","Connection":"close"}
{
"args": {
"a": "123"
},
"data": "haha=1",
"files": {},
"form": {},
"headers": {
"Content-Length": "6",
"Host": "54.198.109.87",
"Referer": "http://54.198.109.87/delete?a=123",
"X-Amzn-Trace-Id": "Root=1-612aafba-3b5865be294e90da4ea88cb4"
},
"json": null,
"origin": "36.155.117.30",
"url": "http://54.198.109.87/delete?a=123"
}
wasmedge-quickjs/$
For specific user guide for http-client, please refer to
wasmedge-quickjs/example_js/http.js
This is an image feature extended with image-rs.
Core code: wasmedge-quickjs/src/quickjs_sys/img_module.rs
wasmedge-quickjs/$ cargo wasi build --features=img --release
wasmedge-quickjs/$ wasmedge --dir=.:. target/wasm32-wasi/release/quickjs-rs-wasi.wasm example_js/img_demo.js
start load image
buf: [object ArrayBuffer]
279434
new image from memory
rgba pixels len 40000
rgb pixels len 30000
luma pixels len 10000
wasmedge-quickjs/$ ls example_js/
add.js bird.png bird_luma.png bird_new_1.png bird_new_2.jpg http_demo.js img_demo.js tensorflow_demo tensorflow_lite_demo
wasmedge-quickjs/$
Among these, bird_luma.png bird_new_1.png bird_new_2.jpg
are the newly generated images.
This image library is the dependency of tensorflow.
This is an extension of tensorflow feature with wasmedge_tensorflow_interface as reference.
Core code: wasmedge-quickjs/src/quickjs_sys/tensorflow_module.rs
wasmedge-quickjs/$ cargo wasi build --features=tensorflow --release
tensorflow classify
wasmedge-quickjs/$ wasmedge-tensorflow --dir=.:. target/wasm32-wasi/release/quickjs-rs-wasi.wasm example_js/tensorflow_demo/main.js
label index: 23
confidence: 0.6699743270874023
wasmedge-quickjs/$
This is a tensorflow model for animal image classification.
Index 23 is labeled bald eagle with a confidence of 0.6699 (max=1.0).
tensorflow-lite classify
wasmedge-quickjs/$ wasmedge-tensorflow --dir=.:. target/wasm32-wasi/release/quickjs-rs-wasi.wasm example_js/tensorflow_lite_demo/main.js
label index: 258
confidence: 0.8941176470588236
wasmedge-quickjs/$
This is a tensorflow model for animal image classification.
Index 258 is labeled Hot dog with a confidence of 0.8941176470588236 (max=1.0).
This part mainly explains how quickjs can be extended. How to deliver what C can achieve with rust. Developers can refer to image_module and tensorflow module to extend,
Core code: wasmedge-quickjs/src/quickjs_sys/http_module.rs
This part shows how to write a quickjs module using javascript and rust.
// http_module.rs
// This function will add a http module to ctx
pub(super) fn init_module_http(ctx: &mut Context) {
unsafe {
let ctx = ctx.ctx;
// The javascript part of http module is used to wrap the code written by rust
let init_js = include_str!("../../js_lib/http.js");
// Get globalThis object
let global = get_global(ctx);
// Generate a new javascript function from http_module::get
// And set it to global as http_get attribute
// So you can use globalThis['http_get'](...) in javascript
// to call the http_module::get function.
// This is mainly for js_lib/http.js to use.
set(ctx,"http_get",global,new_function(ctx, "http_get", Some(get)));
...
// Execute javascript code in ctx
let mut val = JS_Eval(
ctx,
// The javascript code to be executed
// here is our package code
make_c_string(init_js).as_ptr(),
init_js.len(),
// filename, which is in the code of the javascript user
// import * from'http' 'http' in the end
make_c_string("http").as_ptr() as *const i8,
// Use Module type to execute, it will generate a module
JS_EVAL_TYPE_MODULE as i32,
);
// This way, the javascript code executed in the following ctx,
// You can import * from'http' to use the http-client function packaged in http_module.rs.
// Delete the function we set up earlier from globalThis.
// Otherwise, javascript users can also use globalThis['http_get'](...)
super::delete(ctx, "http_get", global);
super::delete(ctx, "http_get", global);
...
// If the execution result of JS_Eval is an exception
if JS_IsException_real(val) > 0 {
// dump this exception
js_std_dump_error(ctx);
}
// Because js is the gc mechanism of reference counting
// We need to free memory in this function
// The corresponding function to increase the reference count is JS_DupV
JS_FreeValue_real(ctx, val);
JS_FreeValue_real(ctx, global);
}
}
unsafe fn parse_response(
ctx: *mut JSContext,
r: Result<http_req::response::Response, http_req::error::Error>,
body: &[u8],
) -> JSValue {...}
unsafe fn parse_headers(
ctx: *mut JSContext,
req: &mut http_req::request::Request,
header: JSValue,
) -> Result<(), JSValue> {...}
unsafe fn parse_body(ctx: *mut JSContext, body: JSValue) -> Vec<u8> {...}
pub extern "C" fn get(
ctx: *mut JSContext,
this_val: JSValue,
argc: ::std::os::raw::c_int,
argv: *mut JSValue,
) -> JSValue {...}
pub extern "C" fn post(
ctx: *mut JSContext,
this_val: JSValue,
argc: ::std::os::raw::c_int,
argv: *mut JSValue,
) -> JSValue {...}
pub extern "C" fn put(
ctx: *mut JSContext,
this_val: JSValue,
argc: ::std::os::raw::c_int,
argv: *mut JSValue,
) -> JSValue {...}
pub extern "C" fn patch(
ctx: *mut JSContext,
this_val: JSValue,
argc: ::std::os::raw::c_int,
argv: *mut JSValue,
) -> JSValue {...}
pub extern "C" fn delete(
ctx: *mut JSContext,
this_val: JSValue,
argc: ::std::os::raw::c_int,
argv: *mut JSValue,
) -> JSValue {...}
Core code: wasmedge-quickjs/src/quickjs_sys/tensorflow_module.rs
// tensorflow_module.rs
// import wasm host function
mod wasmedge_tensorflow {...}
pub mod tensorflow {...}
pub mod tensorflow_lite {
use super::wasmedge_tensorflow::*;
use crate::quickjs_sys::*;
use std::path::Path;
struct TensorflowLiteSession {
context: u64,
data: Vec<u8>,
}
impl Drop for TensorflowLiteSession {
fn drop(&mut self) {
unsafe {
wasmedge_tensorflowlite_delete_session(self.context);
}
}
}
impl TensorflowLiteSession {
pub fn new_from_path<T: AsRef<Path>>(path: T) -> Result<Self, String> {...}
pub unsafe fn add_input(&mut self, name: &str, tensor_buf: *const u8, tensor_buf_len: u32) {...}
pub unsafe fn run(&mut self) {...}
pub unsafe fn get_output(&self, name: &str) -> Vec<u8> {...}
}
// bind function
// Wrap the rust function into the form of javascript native input and output parameters.
// Complete the extraction of parameters and the wrapping of return value here
unsafe extern "C" fn bind_add_input(
ctx: *mut JSContext,
this_val: JSValue,
argc: ::std::os::raw::c_int,
argv: *mut JSValue,
) -> JSValue {...}
unsafe extern "C" fn bind_run(
ctx: *mut JSContext,
this_val: JSValue,
argc: ::std::os::raw::c_int,
argv: *mut JSValue,
) -> JSValue {...}
unsafe extern "C" fn bind_get_output(
ctx: *mut JSContext,
this_val: JSValue,
argc: ::std::os::raw::c_int,
argv: *mut JSValue,
) -> JSValue {...}
// bind to quickjs
// Important function
// This function is called when our newly created Class object is gc, that is, when the reference count returns to zero.
// We need to free memory in this function
unsafe extern "C" fn js_finalizer(rt: *mut JSRuntime, val: JSValue) {
// Additional memory data of this object will be obtained, a *void
let s = JS_GetOpaque(val, JS_CLASS_ID) as *mut TensorflowLiteSession;
if !s.is_null() {
// Back to the life cycle of rust and be released.
Box::from_raw(s);
}
}
// Important function
// This function implements how to convert a rust object to JSValue
unsafe fn Session_to_JSValue(
ctx: *mut JSContext,
session: Box<TensorflowLiteSession>,
) -> JSValue {
let obj = JS_NewObjectClass(ctx, JS_CLASS_ID as i32);
if JS_IsException_real(obj) > 0 {
return obj;
}
// Important! Leak the memory of rust and escape the life cycle of rust.
let ptr_data = Box::leak(session);
JS_SetOpaque(obj, (ptr_data as *mut TensorflowLiteSession).cast());
obj
}
// Important function!
// This function will be registered as the constructor of our custom class
unsafe extern "C" fn js_ctor(
ctx: *mut JSContext,
new_target: JSValue,
argc: ::std::os::raw::c_int,
argv: *mut JSValue,
) -> JSValue {
if argv.is_null() || argc == 0 {
return js_throw_error(ctx, "argv is empty");
}
let param = *argv;
let session = if JS_IsString_real(param) > 0 {
let path = match to_string(ctx, param) {
Ok(path) => path,
Err(e) => return js_throw_error(ctx, e),
};
let session = match TensorflowLiteSession::new_from_path(path) {
Ok(s) => s,
Err(e) => return js_throw_error(ctx, e),
};
Box::new(session)
} else {
return js_throw_error(ctx, "param error");
};
return Session_to_JSValue(ctx, session);
}
// For storing class_id
pub static mut JS_CLASS_ID: JSClassID = 0;
// For storing class's constructor, majorly used as JS_IsInstanceOf
pub static mut JS_CLASS: JSValue = 0;
// class definition
static mut JS_CLASS_DEF: JSClassDef = JSClassDef {
class_name: "TensorflowSession\0".as_ptr() as *const i8,
finalizer: Some(js_finalizer),
gc_mark: None,
call: None,
exotic: ::std::ptr::null_mut(),
};
// class method list
// Defined the class method name the javascript user can see
static mut JS_PROTO_FUNCS: [JSCFunctionListEntry; 3] = [
CFUNC_DEF!("add_input\0", bind_add_input, 2),
CFUNC_DEF!("run\0", bind_run, 0),
CFUNC_DEF!("get_output\0", bind_get_output, 1),
];
// module init
unsafe extern "C" fn js_module_init(
ctx: *mut JSContext,
m: *mut JSModuleDef,
) -> ::std::os::raw::c_int {
// Create a class_id in js
JS_NewClassID(&mut JS_CLASS_ID);
// Bind a JSClassDef to this class_id
JS_NewClass(JS_GetRuntime(ctx), JS_CLASS_ID, &JS_CLASS_DEF);
let proto = JS_NewObject(ctx);
JS_SetPropertyFunctionList(
ctx,
proto,
JS_PROTO_FUNCS.as_ptr(),
JS_PROTO_FUNCS.len() as i32,
);
// Generate a constructor
JS_CLASS = JS_NewCFunction2(
ctx,
Some(js_ctor),
make_c_string("TensorflowLiteSession").as_ptr(),
1,
JSCFunctionEnum_JS_CFUNC_constructor,
0,
);
// Set up custom constructor of class
JS_SetConstructor(ctx, JS_CLASS, proto);
// Set up custom proto of class
JS_SetClassProto(ctx, JS_CLASS_ID, proto);
// Set up export in module
// Here, the exported is the constructor exported of Class
JS_SetModuleExport(
ctx,
m,
make_c_string("TensorflowLiteSession").as_ptr(),
JS_CLASS,
);
0
}
// new Module
// Create a new JSModule in CTX
pub unsafe fn init_module_tensorflow_lite(ctx: *mut JSContext) -> *mut JSModuleDef {
let name = make_c_string("tensorflow_lite");
let m = JS_NewCModule(ctx, name.as_ptr(), Some(js_module_init));
if m.is_null() {
return m;
}
JS_AddModuleExport(ctx, m, make_c_string("TensorflowLiteSession").as_ptr());
return m;
}
}