quickjs_runtime/quickjs_utils/
compile.rs1use 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
11pub 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 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
77pub 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
102pub 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 let ret = slice.to_vec();
148 q::js_free(context, slice_u8 as *mut c_void);
149 ret
150}
151
152pub 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 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 #[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 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 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}