use crate::quickjsrealmadapter::QuickJsRealmAdapter;
use crate::quickjsruntimeadapter::make_cstring;
use crate::valueref::JSValueRef;
use hirofa_utils::js_utils::JsError;
use hirofa_utils::js_utils::Script;
use libquickjs_sys as q;
use std::os::raw::c_void;
pub unsafe fn compile(context: *mut q::JSContext, script: Script) -> Result<JSValueRef, JsError> {
let filename_c = make_cstring(script.get_path())?;
let code_c = make_cstring(script.get_code())?;
log::debug!("q_js_rt.compile file {}", script.get_path());
let value_raw = q::JS_Eval(
context,
code_c.as_ptr(),
script.get_code().len() as _,
filename_c.as_ptr(),
q::JS_EVAL_FLAG_COMPILE_ONLY as i32,
);
log::trace!("after compile, checking error");
let ret = JSValueRef::new(
context,
value_raw,
false,
true,
format!("eval result of {}", script.get_path()).as_str(),
);
if ret.is_exception() {
let ex_opt = QuickJsRealmAdapter::get_exception(context);
if let Some(ex) = ex_opt {
Err(ex)
} else {
Err(JsError::new_str(
"compile failed and could not get exception",
))
}
} else {
Ok(ret)
}
}
pub unsafe fn run_compiled_function(
context: *mut q::JSContext,
compiled_func: &JSValueRef,
) -> Result<JSValueRef, JsError> {
assert!(compiled_func.is_compiled_function());
let val = q::JS_EvalFunction(context, compiled_func.clone_value_incr_rc());
let val_ref = JSValueRef::new(context, val, false, true, "run_compiled_function result");
if val_ref.is_exception() {
let ex_opt = QuickJsRealmAdapter::get_exception(context);
if let Some(ex) = ex_opt {
Err(ex)
} else {
Err(JsError::new_str(
"run_compiled_function failed and could not get exception",
))
}
} else {
Ok(val_ref)
}
}
pub unsafe fn to_bytecode(context: *mut q::JSContext, compiled_func: &JSValueRef) -> Vec<u8> {
assert!(compiled_func.is_compiled_function() || compiled_func.is_module());
let mut len = 0;
let slice_u8 = q::JS_WriteObject(
context,
&mut len,
*compiled_func.borrow_value(),
q::JS_WRITE_OBJ_BYTECODE as i32,
);
let slice = std::slice::from_raw_parts(slice_u8, len as usize);
let ret = slice.to_vec();
q::js_free(context, slice_u8 as *mut c_void);
ret
}
pub unsafe fn from_bytecode(
context: *mut q::JSContext,
bytecode: &[u8],
) -> Result<JSValueRef, JsError> {
assert!(!bytecode.is_empty());
{
let len = bytecode.len();
let buf = bytecode.as_ptr();
let raw = q::JS_ReadObject(context, buf, len as _, q::JS_READ_OBJ_BYTECODE as i32);
let func_ref = JSValueRef::new(context, raw, false, true, "from_bytecode result");
if func_ref.is_exception() {
let ex_opt = QuickJsRealmAdapter::get_exception(context);
if let Some(ex) = ex_opt {
Err(ex)
} else {
Err(JsError::new_str(
"from_bytecode failed and could not get exception",
))
}
} else {
Ok(func_ref)
}
}
}
#[cfg(test)]
pub mod tests {
use crate::builder::QuickJsRuntimeBuilder;
use crate::facades::tests::init_test_rt;
use crate::quickjs_utils::compile::{
compile, from_bytecode, run_compiled_function, to_bytecode,
};
use crate::quickjs_utils::modules::compile_module;
use crate::quickjs_utils::primitives;
use crate::quickjsrealmadapter::QuickJsRealmAdapter;
use backtrace::Backtrace;
use futures::executor::block_on;
use hirofa_utils::js_utils::facades::values::JsValueFacade;
use hirofa_utils::js_utils::facades::{JsRuntimeBuilder, JsRuntimeFacade};
use hirofa_utils::js_utils::modules::CompiledModuleLoader;
use hirofa_utils::js_utils::Script;
use log::LevelFilter;
use std::panic;
use std::sync::Arc;
#[test]
fn test_compile() {
let rt = init_test_rt();
rt.exe_rt_task_in_event_loop(|q_js_rt| {
let q_ctx = q_js_rt.get_main_context();
let func_res = unsafe {
compile(
q_ctx.context,
Script::new(
"test_func.es",
"let a_tb3 = 7; let b_tb3 = 5; a_tb3 * b_tb3;",
),
)
};
let func = func_res.expect("func compile failed");
let bytecode: Vec<u8> = unsafe { to_bytecode(q_ctx.context, &func) };
drop(func);
assert!(!bytecode.is_empty());
let func2_res = unsafe { from_bytecode(q_ctx.context, &bytecode) };
let func2 = func2_res.expect("could not read bytecode");
let run_res = unsafe { run_compiled_function(q_ctx.context, &func2) };
match run_res {
Ok(res) => {
let i_res = primitives::to_i32(&res);
let i = i_res.expect("could not convert to i32");
assert_eq!(i, 7 * 5);
}
Err(e) => {
panic!("run failed1: {}", e);
}
}
});
}
#[test]
fn test_bytecode() {
let rt = init_test_rt();
rt.exe_rt_task_in_event_loop(|q_js_rt| unsafe {
let q_ctx = q_js_rt.get_main_context();
let func_res = compile(
q_ctx.context,
Script::new(
"test_func.es",
"let a_tb4 = 7; let b_tb4 = 5; a_tb4 * b_tb4;",
),
);
let func = func_res.expect("func compile failed");
let bytecode: Vec<u8> = to_bytecode(q_ctx.context, &func);
drop(func);
assert!(!bytecode.is_empty());
let func2_res = from_bytecode(q_ctx.context, &bytecode);
let func2 = func2_res.expect("could not read bytecode");
let run_res = run_compiled_function(q_ctx.context, &func2);
match run_res {
Ok(res) => {
let i_res = primitives::to_i32(&res);
let i = i_res.expect("could not convert to i32");
assert_eq!(i, 7 * 5);
}
Err(e) => {
panic!("run failed: {}", e);
}
}
});
}
#[test]
fn test_bytecode_bad_compile() {
let rt = QuickJsRuntimeBuilder::new().build();
rt.exe_rt_task_in_event_loop(|q_js_rt| {
let q_ctx = q_js_rt.get_main_context();
let func_res = unsafe {
compile(
q_ctx.context,
Script::new(
"test_func_fail.es",
"{the changes of me compil1ng a're slim to 0-0}",
),
)
};
func_res.expect_err("func compiled unexpectedly");
})
}
#[test]
fn test_bytecode_bad_run() {
let rt = QuickJsRuntimeBuilder::new().build();
rt.exe_rt_task_in_event_loop(|q_js_rt| unsafe {
let q_ctx = q_js_rt.get_main_context();
let func_res = compile(
q_ctx.context,
Script::new("test_func_runfail.es", "let abcdef = 1;"),
);
let func = func_res.expect("func compile failed");
assert_eq!(1, func.get_ref_count());
let bytecode: Vec<u8> = to_bytecode(q_ctx.context, &func);
assert_eq!(1, func.get_ref_count());
drop(func);
assert!(!bytecode.is_empty());
let func2_res = from_bytecode(q_ctx.context, &bytecode);
let func2 = func2_res.expect("could not read bytecode");
assert_eq!(1, func2.get_ref_count());
let run_res1 =
run_compiled_function(q_ctx.context, &func2).expect("run 1 failed unexpectedly");
drop(run_res1);
assert_eq!(1, func2.get_ref_count());
let _run_res2 = run_compiled_function(q_ctx.context, &func2)
.expect_err("run 2 succeeded unexpectedly");
assert_eq!(1, func2.get_ref_count());
});
}
lazy_static! {
static ref COMPILED_BYTES: Arc<Vec<u8>> = init_bytes();
}
fn init_bytes() -> Arc<Vec<u8>> {
let rt = QuickJsRuntimeBuilder::new().build();
rt.js_loop_realm_sync(None, |_rt, realm| unsafe {
let script = Script::new(
"test_module.js",
"export function someFunction(a, b){return a*b;};",
);
let module = compile_module(realm.context, script).expect("compile failed");
Arc::new(to_bytecode(realm.context, &module))
})
}
struct Cml {}
impl CompiledModuleLoader<QuickJsRealmAdapter> for Cml {
fn normalize_path(
&self,
_q_ctx: &QuickJsRealmAdapter,
_ref_path: &str,
path: &str,
) -> Option<String> {
Some(path.to_string())
}
fn load_module(&self, _q_ctx: &QuickJsRealmAdapter, _absolute_path: &str) -> Arc<Vec<u8>> {
COMPILED_BYTES.clone()
}
}
#[test]
fn test_bytecode_module() {
panic::set_hook(Box::new(|panic_info| {
let backtrace = Backtrace::new();
println!(
"thread panic occurred: {}\nbacktrace: {:?}",
panic_info, backtrace
);
log::error!(
"thread panic occurred: {}\nbacktrace: {:?}",
panic_info,
backtrace
);
}));
simple_logging::log_to_file("quickjs_runtime.log", LevelFilter::max())
.expect("could not init logger");
let rt = QuickJsRuntimeBuilder::new()
.js_compiled_module_loader(Cml {})
.build();
let test_script = Script::new(
"test_bytecode_module.js",
"import('testcompiledmodule').then((mod) => {return mod.someFunction(3, 5);})",
);
let res_fut = rt.js_eval(None, test_script);
let res_prom = block_on(res_fut).expect("script failed");
let rti_weak = rt.js_get_runtime_facade_inner();
if let JsValueFacade::JsPromise { cached_promise } = res_prom {
let rti = rti_weak.upgrade().expect("invalid state");
let prom_res_fut = cached_promise.js_get_promise_result(&*rti);
let prom_res = block_on(prom_res_fut)
.expect("prom failed")
.expect("prom was rejected");
assert!(prom_res.is_i32());
assert_eq!(prom_res.get_i32(), 15);
} else {
panic!("did not get a prom");
}
}
}