owned this note
owned this note
Published
Linked with GitHub
Build [quickjs-wasi](https://github.com/second-state/wasmedge-quickjs)
===
# 1. clone project
```shell
# 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
```
## wasmedge-quickjs
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.
## quickjs-wasi
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
* Static library
`wasmedge-quickjs/lib/libquickjs.a`
* Rust binging of quickjs's C api
`wasmedge-quickjs/lib/binding.rs`
### build
> Usually this is not needed.
> This section can be skipped.
**1、Generate qjs.c and qjscalc.c**
```shell
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
```shell
quickjs-wasi$ ./build_wasi.sh
```
This is a standard wasm compiled by quickjs. It can run directly.
```shell
# 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
```shell
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
* Packaged static functions of C
* Packaged some macro into functions
`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 to `libquickjs.a` and `binding.rs`.
# 2. build [quickjs-wasi.wasm]
> cargo-wasi extension needs to be installed
> `cargo install cargo-wasi`
> an easy-to-compile command
## Compile a standard quickjs-wasi.wasm
```shell
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/$
```
## Compile quickjs-wasi.wasm + http
This is a http-client feautre extended with [wasmedge_wasi_socket](https://github.com/second-state/wasmedge_wasi_socket) .
```shell
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`
## Compile quickjs-wasi.wasm + img
This is an image feature extended with [image-rs](https://github.com/image-rs/image).
Core code: wasmedge-quickjs/src/quickjs_sys/img_module.rs
```shell
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.
## Compile quickjs-wasi.wasm + tensorflow
This is an extension of tensorflow feature with [wasmedge_tensorflow_interface](https://github.com/second-state/wasmedge_tensorflow_interface) as reference.
Core code: wasmedge-quickjs/src/quickjs_sys/tensorflow_module.rs
```shell
wasmedge-quickjs/$ cargo wasi build --features=tensorflow --release
```
**tensorflow classify**
```shell
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**
```shell
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).
# 3. Extend quickjs
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,
## How to extend HTTP to quickjs
> Core code: wasmedge-quickjs/src/quickjs_sys/http_module.rs
This part shows how to write a quickjs module using javascript and rust.
```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 {...}
```
## How to extend Tensorflow to quickjs
> Core code: wasmedge-quickjs/src/quickjs_sys/tensorflow_module.rs
```rust
// 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;
}
}
```