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 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 #[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 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 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}