Skip to main content

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 lazy_static::lazy_static;
198    use std::panic;
199    use std::sync::Arc;
200
201    #[test]
202    fn test_compile() {
203        let rt = init_test_rt();
204
205        rt.exe_rt_task_in_event_loop(|q_js_rt| {
206            let q_ctx = q_js_rt.get_main_realm();
207            let func_res = unsafe {
208                compile(
209                    q_ctx.context,
210                    Script::new(
211                        "test_func.es",
212                        "let a_tb3 = 7; let b_tb3 = 5; a_tb3 * b_tb3;",
213                    ),
214                )
215            };
216            let func = func_res.expect("func compile failed");
217            let bytecode: Vec<u8> = unsafe { to_bytecode(q_ctx.context, &func) };
218            drop(func);
219            assert!(!bytecode.is_empty());
220            let func2_res = unsafe { from_bytecode(q_ctx.context, &bytecode) };
221            let func2 = func2_res.expect("could not read bytecode");
222            let run_res = unsafe { run_compiled_function(q_ctx.context, &func2) };
223            match run_res {
224                Ok(res) => {
225                    let i_res = primitives::to_i32(&res);
226                    let i = i_res.expect("could not convert to i32");
227                    assert_eq!(i, 7 * 5);
228                }
229                Err(e) => {
230                    panic!("run failed1: {}", e);
231                }
232            }
233        });
234    }
235
236    #[test]
237    fn test_bytecode() {
238        let rt = init_test_rt();
239        rt.exe_rt_task_in_event_loop(|q_js_rt| unsafe {
240            let q_ctx = q_js_rt.get_main_realm();
241            let func_res = compile(
242                q_ctx.context,
243                Script::new(
244                    "test_func.es",
245                    "let a_tb4 = 7; let b_tb4 = 5; a_tb4 * b_tb4;",
246                ),
247            );
248            let func = func_res.expect("func compile failed");
249            let bytecode: Vec<u8> = to_bytecode(q_ctx.context, &func);
250            drop(func);
251            assert!(!bytecode.is_empty());
252            let func2_res = from_bytecode(q_ctx.context, &bytecode);
253            let func2 = func2_res.expect("could not read bytecode");
254            let run_res = run_compiled_function(q_ctx.context, &func2);
255
256            match run_res {
257                Ok(res) => {
258                    let i_res = primitives::to_i32(&res);
259                    let i = i_res.expect("could not convert to i32");
260                    assert_eq!(i, 7 * 5);
261                }
262                Err(e) => {
263                    panic!("run failed: {}", e);
264                }
265            }
266        });
267    }
268
269    #[test]
270    fn test_bytecode_bad_compile() {
271        let rt = QuickJsRuntimeBuilder::new().build();
272        rt.exe_rt_task_in_event_loop(|q_js_rt| {
273            let q_ctx = q_js_rt.get_main_realm();
274
275            let func_res = unsafe {
276                compile(
277                    q_ctx.context,
278                    Script::new(
279                        "test_func_fail.es",
280                        "{the changes of me compil1ng a're slim to 0-0}",
281                    ),
282                )
283            };
284            func_res.expect_err("func compiled unexpectedly");
285        })
286    }
287
288    #[test]
289    fn test_bytecode_bad_run() {
290        let rt = QuickJsRuntimeBuilder::new().build();
291        rt.exe_rt_task_in_event_loop(|q_js_rt| unsafe {
292            let q_ctx = q_js_rt.get_main_realm();
293
294            let func_res = compile(
295                q_ctx.context,
296                Script::new("test_func_runfail.es", "let abcdef = 1;"),
297            );
298            let func = func_res.expect("func compile failed");
299            #[cfg(feature = "bellard")]
300            assert_eq!(1, func.get_ref_count());
301
302            let bytecode: Vec<u8> = to_bytecode(q_ctx.context, &func);
303
304            #[cfg(feature = "bellard")]
305            assert_eq!(1, func.get_ref_count());
306
307            drop(func);
308
309            #[cfg(feature = "bellard")]
310            assert!(!bytecode.is_empty());
311
312            let func2_res = from_bytecode(q_ctx.context, &bytecode);
313            let func2 = func2_res.expect("could not read bytecode");
314            //should fail the second time you run this because abcdef is already defined
315
316            #[cfg(feature = "bellard")]
317            assert_eq!(1, func2.get_ref_count());
318
319            let run_res1 =
320                run_compiled_function(q_ctx.context, &func2).expect("run 1 failed unexpectedly");
321            drop(run_res1);
322
323            #[cfg(feature = "bellard")]
324            assert_eq!(1, func2.get_ref_count());
325
326            let _run_res2 = run_compiled_function(q_ctx.context, &func2)
327                .expect_err("run 2 succeeded unexpectedly");
328
329            #[cfg(feature = "bellard")]
330            assert_eq!(1, func2.get_ref_count());
331        });
332    }
333
334    lazy_static! {
335        static ref COMPILED_BYTES: Arc<Vec<u8>> = init_bytes();
336    }
337
338    fn init_bytes() -> Arc<Vec<u8>> {
339        // in order to init our bytes fgor our module we lazy init a rt
340        let rt = QuickJsRuntimeBuilder::new().build();
341        rt.loop_realm_sync(None, |_rt, realm| unsafe {
342            let script = Script::new(
343                "test_module.js",
344                "export function someFunction(a, b){return a*b;};",
345            );
346
347            let module = compile_module(realm.context, script).expect("compile failed");
348
349            Arc::new(to_bytecode(realm.context, &module))
350        })
351    }
352
353    struct Cml {}
354    impl CompiledModuleLoader for Cml {
355        fn normalize_path(
356            &self,
357            _q_ctx: &QuickJsRealmAdapter,
358            _ref_path: &str,
359            path: &str,
360        ) -> Option<String> {
361            Some(path.to_string())
362        }
363
364        fn load_module(&self, _q_ctx: &QuickJsRealmAdapter, _absolute_path: &str) -> Arc<Vec<u8>> {
365            COMPILED_BYTES.clone()
366        }
367    }
368
369    #[test]
370    fn test_bytecode_module() {
371        /*panic::set_hook(Box::new(|panic_info| {
372            let backtrace = Backtrace::new();
373            println!("thread panic occurred: {panic_info}\nbacktrace: {backtrace:?}");
374            log::error!(
375                "thread panic occurred: {}\nbacktrace: {:?}",
376                panic_info,
377                backtrace
378            );
379        }));*/
380
381        //simple_logging::log_to_file("quickjs_runtime.log", LevelFilter::max())
382        //            .expect("could not init logger");
383
384        let rt = QuickJsRuntimeBuilder::new()
385            .compiled_module_loader(Cml {})
386            .build();
387
388        let test_script = Script::new(
389            "test_bytecode_module.js",
390            "import('testcompiledmodule').then((mod) => {return mod.someFunction(3, 5);})",
391        );
392        let res_fut = rt.eval(None, test_script);
393        let res_prom = block_on(res_fut).expect("script failed");
394        if let JsValueFacade::JsPromise { cached_promise } = res_prom {
395            let prom_res_fut = cached_promise.get_promise_result();
396            let prom_res = block_on(prom_res_fut)
397                .expect("prom failed")
398                .expect("prom was rejected");
399            assert!(prom_res.is_i32());
400            assert_eq!(prom_res.get_i32(), 15);
401        } else {
402            panic!("did not get a prom");
403        }
404    }
405}