quickjs_runtime/quickjs_utils/
compile.rs

1//! Utils to compile script to bytecode and run script from bytecode
2
3use crate::jsutils::JsError;
4use crate::jsutils::Script;
5use crate::quickjsrealmadapter::QuickJsRealmAdapter;
6use crate::quickjsruntimeadapter::make_cstring;
7use crate::quickjsvalueadapter::QuickJsValueAdapter;
8use libquickjs_sys as q;
9use std::os::raw::c_void;
10
11/// compile a script, will result in a JSValueRef with tag JS_TAG_FUNCTION_BYTECODE or JS_TAG_MODULE.
12///  It can be executed with run_compiled_function().
13/// # Example
14/// ```rust
15/// use quickjs_runtime::builder::QuickJsRuntimeBuilder;
16/// use quickjs_runtime::jsutils::Script;
17/// use quickjs_runtime::quickjs_utils::primitives;
18/// use quickjs_runtime::quickjs_utils::compile::{compile, run_compiled_function};
19/// let rt = QuickJsRuntimeBuilder::new().build();
20/// rt.exe_rt_task_in_event_loop(|q_js_rt| {
21///     unsafe {
22///         let q_ctx = q_js_rt.get_main_realm();
23///         let func_res = compile(q_ctx.context, Script::new("test_func.es", "let a = 7; let b = 5; a * b;"));
24///         let func = func_res.ok().expect("func compile failed");
25///         let run_res = run_compiled_function(q_ctx.context, &func);
26///         let res = run_res.ok().expect("run_compiled_function failed");
27///         let i_res = primitives::to_i32(&res);
28///         let i = i_res.ok().expect("could not convert to i32");
29///         assert_eq!(i, 7*5);
30///     }
31/// });
32/// ```
33/// # Safety
34/// When passing a context pointer please make sure the corresponding QuickJsContext is still valid
35pub unsafe fn compile(
36    context: *mut q::JSContext,
37    script: Script,
38) -> Result<QuickJsValueAdapter, JsError> {
39    let filename_c = make_cstring(script.get_path())?;
40    let code_str = script.get_runnable_code();
41    let code_c = make_cstring(code_str)?;
42
43    log::debug!("q_js_rt.compile file {}", script.get_path());
44
45    let value_raw = q::JS_Eval(
46        context,
47        code_c.as_ptr(),
48        code_str.len() as _,
49        filename_c.as_ptr(),
50        q::JS_EVAL_FLAG_COMPILE_ONLY as i32,
51    );
52
53    log::trace!("after compile, checking error");
54
55    // check for error
56    let ret = QuickJsValueAdapter::new(
57        context,
58        value_raw,
59        false,
60        true,
61        format!("eval result of {}", script.get_path()).as_str(),
62    );
63    if ret.is_exception() {
64        let ex_opt = QuickJsRealmAdapter::get_exception(context);
65        if let Some(ex) = ex_opt {
66            Err(ex)
67        } else {
68            Err(JsError::new_str(
69                "compile failed and could not get exception",
70            ))
71        }
72    } else {
73        Ok(ret)
74    }
75}
76
77/// run a compiled function, see compile for an example
78/// # Safety
79/// When passing a context pointer please make sure the corresponding QuickJsContext is still valid
80pub unsafe fn run_compiled_function(
81    context: *mut q::JSContext,
82    compiled_func: &QuickJsValueAdapter,
83) -> Result<QuickJsValueAdapter, JsError> {
84    assert!(compiled_func.is_compiled_function());
85    let val = q::JS_EvalFunction(context, compiled_func.clone_value_incr_rc());
86    let val_ref =
87        QuickJsValueAdapter::new(context, val, false, true, "run_compiled_function result");
88    if val_ref.is_exception() {
89        let ex_opt = QuickJsRealmAdapter::get_exception(context);
90        if let Some(ex) = ex_opt {
91            Err(ex)
92        } else {
93            Err(JsError::new_str(
94                "run_compiled_function failed and could not get exception",
95            ))
96        }
97    } else {
98        Ok(val_ref)
99    }
100}
101
102/// write a function to bytecode
103/// # Example
104/// ```rust
105/// use quickjs_runtime::builder::QuickJsRuntimeBuilder;
106/// use quickjs_runtime::jsutils::Script;
107/// use quickjs_runtime::quickjs_utils::primitives;
108/// use quickjs_runtime::quickjs_utils::compile::{compile, run_compiled_function, to_bytecode, from_bytecode};
109/// let rt = QuickJsRuntimeBuilder::new().build();
110/// rt.exe_rt_task_in_event_loop(|q_js_rt| {
111///     unsafe {
112///     let q_ctx = q_js_rt.get_main_realm();
113///     let func_res = compile(q_ctx.context, Script::new("test_func.es", "let a = 7; let b = 5; a * b;"));
114///     let func = func_res.ok().expect("func compile failed");
115///     let bytecode: Vec<u8> = to_bytecode(q_ctx.context, &func);
116///     drop(func);
117///     assert!(!bytecode.is_empty());
118///         let func2_res = from_bytecode(q_ctx.context, &bytecode);
119///         let func2 = func2_res.ok().expect("could not read bytecode");
120///         let run_res = run_compiled_function(q_ctx.context, &func2);
121///         let res = run_res.ok().expect("run_compiled_function failed");
122///         let i_res = primitives::to_i32(&res);
123///         let i = i_res.ok().expect("could not convert to i32");
124///         assert_eq!(i, 7*5);
125///     }
126/// });
127/// ```
128/// # Safety
129/// When passing a context pointer please make sure the corresponding QuickJsContext is still valid
130pub unsafe fn to_bytecode(
131    context: *mut q::JSContext,
132    compiled_func: &QuickJsValueAdapter,
133) -> Vec<u8> {
134    assert!(compiled_func.is_compiled_function() || compiled_func.is_module());
135
136    let mut len = 0;
137
138    let slice_u8 = q::JS_WriteObject(
139        context,
140        &mut len,
141        *compiled_func.borrow_value(),
142        q::JS_WRITE_OBJ_BYTECODE as i32,
143    );
144
145    let slice = std::slice::from_raw_parts(slice_u8, len as _);
146    // it's a shame to copy the vec here but the alternative is to create a wrapping struct which free's the ptr on drop
147    let ret = slice.to_vec();
148    q::js_free(context, slice_u8 as *mut c_void);
149    ret
150}
151
152/// read a compiled function from bytecode, see to_bytecode for an example
153/// # Safety
154/// When passing a context pointer please make sure the corresponding QuickJsContext is still valid
155pub unsafe fn from_bytecode(
156    context: *mut q::JSContext,
157    bytecode: &[u8],
158) -> Result<QuickJsValueAdapter, JsError> {
159    assert!(!bytecode.is_empty());
160    {
161        let len = bytecode.len();
162
163        let buf = bytecode.as_ptr();
164        let raw = q::JS_ReadObject(context, buf, len as _, q::JS_READ_OBJ_BYTECODE as i32);
165
166        let func_ref = QuickJsValueAdapter::new(context, raw, false, true, "from_bytecode result");
167        if func_ref.is_exception() {
168            let ex_opt = QuickJsRealmAdapter::get_exception(context);
169            if let Some(ex) = ex_opt {
170                Err(ex)
171            } else {
172                Err(JsError::new_str(
173                    "from_bytecode failed and could not get exception",
174                ))
175            }
176        } else {
177            Ok(func_ref)
178        }
179    }
180}
181
182#[cfg(test)]
183pub mod tests {
184    use crate::builder::QuickJsRuntimeBuilder;
185    use crate::facades::tests::init_test_rt;
186    use crate::jsutils::modules::CompiledModuleLoader;
187    use crate::jsutils::Script;
188    use crate::quickjs_utils::compile::{
189        compile, from_bytecode, run_compiled_function, to_bytecode,
190    };
191    use crate::quickjs_utils::modules::compile_module;
192    use crate::quickjs_utils::primitives;
193    use crate::quickjsrealmadapter::QuickJsRealmAdapter;
194    use crate::values::JsValueFacade;
195    //use backtrace::Backtrace;
196    use futures::executor::block_on;
197    use std::panic;
198    use std::sync::Arc;
199
200    #[test]
201    fn test_compile() {
202        let rt = init_test_rt();
203
204        rt.exe_rt_task_in_event_loop(|q_js_rt| {
205            let q_ctx = q_js_rt.get_main_realm();
206            let func_res = unsafe {
207                compile(
208                    q_ctx.context,
209                    Script::new(
210                        "test_func.es",
211                        "let a_tb3 = 7; let b_tb3 = 5; a_tb3 * b_tb3;",
212                    ),
213                )
214            };
215            let func = func_res.expect("func compile failed");
216            let bytecode: Vec<u8> = unsafe { to_bytecode(q_ctx.context, &func) };
217            drop(func);
218            assert!(!bytecode.is_empty());
219            let func2_res = unsafe { from_bytecode(q_ctx.context, &bytecode) };
220            let func2 = func2_res.expect("could not read bytecode");
221            let run_res = unsafe { run_compiled_function(q_ctx.context, &func2) };
222            match run_res {
223                Ok(res) => {
224                    let i_res = primitives::to_i32(&res);
225                    let i = i_res.expect("could not convert to i32");
226                    assert_eq!(i, 7 * 5);
227                }
228                Err(e) => {
229                    panic!("run failed1: {}", e);
230                }
231            }
232        });
233    }
234
235    #[test]
236    fn test_bytecode() {
237        let rt = init_test_rt();
238        rt.exe_rt_task_in_event_loop(|q_js_rt| unsafe {
239            let q_ctx = q_js_rt.get_main_realm();
240            let func_res = compile(
241                q_ctx.context,
242                Script::new(
243                    "test_func.es",
244                    "let a_tb4 = 7; let b_tb4 = 5; a_tb4 * b_tb4;",
245                ),
246            );
247            let func = func_res.expect("func compile failed");
248            let bytecode: Vec<u8> = to_bytecode(q_ctx.context, &func);
249            drop(func);
250            assert!(!bytecode.is_empty());
251            let func2_res = from_bytecode(q_ctx.context, &bytecode);
252            let func2 = func2_res.expect("could not read bytecode");
253            let run_res = run_compiled_function(q_ctx.context, &func2);
254
255            match run_res {
256                Ok(res) => {
257                    let i_res = primitives::to_i32(&res);
258                    let i = i_res.expect("could not convert to i32");
259                    assert_eq!(i, 7 * 5);
260                }
261                Err(e) => {
262                    panic!("run failed: {}", e);
263                }
264            }
265        });
266    }
267
268    #[test]
269    fn test_bytecode_bad_compile() {
270        let rt = QuickJsRuntimeBuilder::new().build();
271        rt.exe_rt_task_in_event_loop(|q_js_rt| {
272            let q_ctx = q_js_rt.get_main_realm();
273
274            let func_res = unsafe {
275                compile(
276                    q_ctx.context,
277                    Script::new(
278                        "test_func_fail.es",
279                        "{the changes of me compil1ng a're slim to 0-0}",
280                    ),
281                )
282            };
283            func_res.expect_err("func compiled unexpectedly");
284        })
285    }
286
287    #[test]
288    fn test_bytecode_bad_run() {
289        let rt = QuickJsRuntimeBuilder::new().build();
290        rt.exe_rt_task_in_event_loop(|q_js_rt| unsafe {
291            let q_ctx = q_js_rt.get_main_realm();
292
293            let func_res = compile(
294                q_ctx.context,
295                Script::new("test_func_runfail.es", "let abcdef = 1;"),
296            );
297            let func = func_res.expect("func compile failed");
298            #[cfg(feature = "bellard")]
299            assert_eq!(1, func.get_ref_count());
300
301            let bytecode: Vec<u8> = to_bytecode(q_ctx.context, &func);
302
303            #[cfg(feature = "bellard")]
304            assert_eq!(1, func.get_ref_count());
305
306            drop(func);
307
308            #[cfg(feature = "bellard")]
309            assert!(!bytecode.is_empty());
310
311            let func2_res = from_bytecode(q_ctx.context, &bytecode);
312            let func2 = func2_res.expect("could not read bytecode");
313            //should fail the second time you run this because abcdef is already defined
314
315            #[cfg(feature = "bellard")]
316            assert_eq!(1, func2.get_ref_count());
317
318            let run_res1 =
319                run_compiled_function(q_ctx.context, &func2).expect("run 1 failed unexpectedly");
320            drop(run_res1);
321
322            #[cfg(feature = "bellard")]
323            assert_eq!(1, func2.get_ref_count());
324
325            let _run_res2 = run_compiled_function(q_ctx.context, &func2)
326                .expect_err("run 2 succeeded unexpectedly");
327
328            #[cfg(feature = "bellard")]
329            assert_eq!(1, func2.get_ref_count());
330        });
331    }
332
333    lazy_static! {
334        static ref COMPILED_BYTES: Arc<Vec<u8>> = init_bytes();
335    }
336
337    fn init_bytes() -> Arc<Vec<u8>> {
338        // in order to init our bytes fgor our module we lazy init a rt
339        let rt = QuickJsRuntimeBuilder::new().build();
340        rt.loop_realm_sync(None, |_rt, realm| unsafe {
341            let script = Script::new(
342                "test_module.js",
343                "export function someFunction(a, b){return a*b;};",
344            );
345
346            let module = compile_module(realm.context, script).expect("compile failed");
347
348            Arc::new(to_bytecode(realm.context, &module))
349        })
350    }
351
352    struct Cml {}
353    impl CompiledModuleLoader for Cml {
354        fn normalize_path(
355            &self,
356            _q_ctx: &QuickJsRealmAdapter,
357            _ref_path: &str,
358            path: &str,
359        ) -> Option<String> {
360            Some(path.to_string())
361        }
362
363        fn load_module(&self, _q_ctx: &QuickJsRealmAdapter, _absolute_path: &str) -> Arc<Vec<u8>> {
364            COMPILED_BYTES.clone()
365        }
366    }
367
368    #[test]
369    fn test_bytecode_module() {
370        /*panic::set_hook(Box::new(|panic_info| {
371            let backtrace = Backtrace::new();
372            println!("thread panic occurred: {panic_info}\nbacktrace: {backtrace:?}");
373            log::error!(
374                "thread panic occurred: {}\nbacktrace: {:?}",
375                panic_info,
376                backtrace
377            );
378        }));*/
379
380        //simple_logging::log_to_file("quickjs_runtime.log", LevelFilter::max())
381        //            .expect("could not init logger");
382
383        let rt = QuickJsRuntimeBuilder::new()
384            .compiled_module_loader(Cml {})
385            .build();
386
387        let test_script = Script::new(
388            "test_bytecode_module.js",
389            "import('testcompiledmodule').then((mod) => {return mod.someFunction(3, 5);})",
390        );
391        let res_fut = rt.eval(None, test_script);
392        let res_prom = block_on(res_fut).expect("script failed");
393        if let JsValueFacade::JsPromise { cached_promise } = res_prom {
394            let prom_res_fut = cached_promise.get_promise_result();
395            let prom_res = block_on(prom_res_fut)
396                .expect("prom failed")
397                .expect("prom was rejected");
398            assert!(prom_res.is_i32());
399            assert_eq!(prom_res.get_i32(), 15);
400        } else {
401            panic!("did not get a prom");
402        }
403    }
404}