owned this note changed 4 years ago
Published Linked with GitHub

Build quickjs-wasi

1. clone project

# 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

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

  • 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

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 .

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.

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.

Compile quickjs-wasi.wasm + 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).

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.

// 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

// 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;
	}
}
Select a repo