Skip to content

Commit

Permalink
wip, working on quickjs-ng support
Browse files Browse the repository at this point in the history
  • Loading branch information
andrieshiemstra committed Feb 15, 2024
1 parent 3efd6f0 commit 8e203fb
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 55 deletions.
10 changes: 6 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ readme = "README.md"
categories = ["development-tools"]

[features]
default = ["console", "setimmediate", "setinterval", "settimeout", "typescript"]
default = ["console", "setimmediate", "setinterval", "settimeout", "typescript", "bellard"]
tokio_full = ["tokio/full"]
console = []
settimeout = []
setinterval = []
setimmediate = []
typescript = ["swc", "swc_atoms", "swc_cached", "swc_common", "swc_macros_common", "swc_eq_ignore_macros", "swc_visit", "swc_visit_macros", "swc_config", "swc_config_macro", "swc_ecma_codegen", "swc_ecma_ast", "swc_ecma_codegen_macros", "swc_ecma_ext_transforms", "swc_ecma_utils", "swc_ecma_visit", "swc_ecma_lints", "swc_ecma_loader", "swc_ecma_minifier", "swc_ecma_parser", "swc_error_reporters", "swc_fast_graph", "swc_ecma_usage_analyzer", "swc_timer", "swc_ecma_preset_env", "swc_ecma_transforms", "swc_ecma_transforms_base", "swc_ecma_transforms_compat", "swc_ecma_transforms_classes", "swc_ecma_transforms_module", "swc_ecma_transforms_optimization", "swc_ecma_transforms_proposal", "swc_ecma_transforms_macros", "swc_ecma_transforms_react", "swc_ecma_transforms_typescript", "swc_node_comments", "swc_trace_macro"]
bellard = ["libquickjs-sys/bellard"]
quickjs-ng = ["libquickjs-sys/quickjs-ng"]

[dependencies]
hirofa_utils = "0.7"
Expand All @@ -28,9 +30,9 @@ hirofa_utils = "0.7"
#hirofa_utils = {git="https://github.com/HiRoFa/utils"}
backtrace = "0.3.67"

#libquickjs-sys = {package="hirofa-quickjs-sys", git='https://github.com/HiRoFa/quickjs-sys', features=["bellard"]}
#libquickjs-sys = {package="hirofa-quickjs-sys", path='../quickjs-sys', features=["bellard"]}
libquickjs-sys = {package="hirofa-quickjs-sys", version="0.2.0", features=["bellard"]}
libquickjs-sys = {package="hirofa-quickjs-sys", git='https://github.com/HiRoFa/quickjs-sys'}
#libquickjs-sys = {package="hirofa-quickjs-sys", path='../quickjs-sys'}
#libquickjs-sys = {package="hirofa-quickjs-sys", version="0.2.0", features=["bellard"]}
lazy_static = "1.4.0"
log = "0.4"
num_cpus = "1"
Expand Down
31 changes: 19 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,27 @@ quickjs_runtime is a library for quickly getting started with embedding a javasc

**as of 2024 this lib no longer relies on [libquickjs-sys](https://github.com/theduke/quickjs-rs/tree/master/libquickjs-sys) but on out own [hirofa-quickjs-sys](https://github.com/HiRoFa/quickjs-sys) adding flexibility in used quickjs version**

quickjs_runtime runs all javascript action in a single thread using an EventLoop. This means you can call javascript safely from several threads by adding tasks to the EventLoop.

# quickjs or quickjs-ng

I'm working on supporting both the original quickjs and the quickjs-ng project.

You can try out quickjs-ng by adding the dep to quickjs_runtime like this:
```toml
quickjs_runtime = {git="https://github.com/HiRoFa/quickjs_es_runtime", features=["console", "setimmediate", "setinterval", "settimeout", "typescript", "quickjs-ng"], default-features=false}
```

Use at your own risk as I have not extensively tested it yet

# Usage and Features

An example on how to embed a script engine in rust using this lib can be found here: [github.com/andrieshiemstra/ScriptExtensionLayerExample](https://github.com/andrieshiemstra/ScriptExtensionLayerExample). It was published in TWIR as a walkthrough.

quickjs_runtime focuses on making [quickjs](https://bellard.org/quickjs/) easy to use and does not add any additional features, that's where these projects come in:
* A more feature-rich (e.g. fetch api support, http based module loader and much more) runtime: [GreenCopperRuntime](https://github.com/HiRoFa/GreenCopperRuntime).
* The commandline client: [GreenCopperCmd](https://github.com/HiRoFa/GreenCopperCmd).


The big difference to quickjs-rs is that quickjs_runtime executes all quickjs related code in a dedicated single-threaded EventLoop.

Please see the [DOCS](https://hirofa.github.io/quickjs_es_runtime/quickjs_runtime/index.html) for all inner workings

# This lib serves two main goals:
Expand All @@ -24,8 +36,8 @@ Please see the [DOCS](https://hirofa.github.io/quickjs_es_runtime/quickjs_runtim
* Pass a module loader

## 2. Wrap quickjs for use as a ready to go JavaScript Runtime
* This is the EsRuntime struct, it provides an EventQueue which has a thread_local QuickJsRuntime
* All values are copied or abstracted in an EsValueFacade
* Start at the QuickjsRuntimeFacade, it provides an EventQueue which has a thread_local QuickJsRuntimeAdapter
* All values are copied or abstracted in a JsValueFacades
* So no need to worry about Garbage collection
* evaluate script and invoke functions while waiting for results blocking or with async/await
* Get Promise result blocking or with async/await
Expand All @@ -40,7 +52,7 @@ Please see the [DOCS](https://hirofa.github.io/quickjs_es_runtime/quickjs_runtim
* Create promises in JavaScript which execute async
* Eval modules ([docs](https://hirofa.github.io/quickjs_es_runtime/quickjs_runtime/facades/struct.QuickJsRuntimeFacade.html#method.eval_module))
* Load modules (dynamic and static) ([docs](https://hirofa.github.io/quickjs_es_runtime/quickjs_runtime/builder/struct.QuickJsRuntimeBuilder.html#method.script_module_loader))
* fetch api (moved to [GreenCopperRuntime](https://github.com/HiRoFa/GreenCopperRuntime))
* ~~fetch api (moved to [GreenCopperRuntime](https://github.com/HiRoFa/GreenCopperRuntime))~~
* setImmediate
* setTimeout/Interval (and clear)
* script preprocessing (impls for ifdef/macro's/typescript can be found in [GreenCopperRuntime](https://github.com/HiRoFa/GreenCopperRuntime))
Expand All @@ -55,11 +67,6 @@ Please see the [DOCS](https://hirofa.github.io/quickjs_es_runtime/quickjs_runtim
* async/await support on eval/call_function/promise resolution ([docs](https://hirofa.github.io/quickjs_es_runtime/quickjs_runtime/values/struct.CachedJsPromiseRef.html#method.get_promise_result))
* import native Modules (e.g. dynamic loading of rust functions or Proxy classes) ([docs](https://hirofa.github.io/quickjs_es_runtime/quickjs_runtime/builder/struct.QuickJsRuntimeBuilder.html#method.native_module_loader))

## Future / Todo

* Worker support
* WebAssembly support

# Goals

Embedding a script engine in a rust project seems a very tedious job which involves learning a lot about the inner workings of that engine.
Expand All @@ -82,7 +89,7 @@ The fun stuff about QuickJS:
Cargo.toml
```toml
[dependencies]
quickjs_runtime = {version = "0.11", features=["default", "typescript"]}
quickjs_runtime = "0.12"
```

Here are some quickstarts:
Expand Down
19 changes: 12 additions & 7 deletions src/quickjs_utils/bigints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,12 @@ pub mod tests {
use crate::quickjs_utils::bigints;
use crate::quickjs_utils::bigints::new_bigint_str_q;

#[test]
fn test_bigint() {
//#[test]
fn _test_bigint() {
let rt = init_test_rt();
rt.exe_rt_task_in_event_loop(|q_js_rt| {
let q_ctx = q_js_rt.get_main_realm();
let bi_ref =
new_bigint_str_q(q_ctx, "345346345645234564536345345345345456534783448567")
.expect("could not create bigint from str");
let to_str = bigints::to_string_q(q_ctx, &bi_ref).expect("could not tostring bigint");
assert_eq!(to_str, "345346345645234564536345345345345456534783448567");

let bi_ref = bigints::new_bigint_i64_q(q_ctx, 659863456456)
.expect("could not create bigint from u64");
let to_str = bigints::to_string_q(q_ctx, &bi_ref).expect("could not tostring bigint");
Expand All @@ -104,6 +100,15 @@ pub mod tests {
.expect("could not create bigint from u64");
let to_str = bigints::to_string_q(q_ctx, &bi_ref).expect("could not tostring bigint");
assert_eq!(to_str, "659863456457");

let bi_ref =
new_bigint_str_q(q_ctx, "345346345645234564536345345345345456534783448567")
.expect("could not create bigint from str");

log::debug!("bi_ref.get_js_type is {}", bi_ref.get_js_type());

let to_str = bigints::to_string_q(q_ctx, &bi_ref).expect("could not tostring bigint");
assert_eq!(to_str, "345346345645234564536345345345345456534783448567");
});
}
}
53 changes: 30 additions & 23 deletions src/quickjs_utils/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,33 +276,40 @@ pub mod tests {
}
});

let mjsvf = rt
.eval_module_sync(
None,
Script::new(
"test_ex2.es",
r#"
#[cfg(feature = "bellard")]
{
let mjsvf = rt
.eval_module_sync(
None,
Script::new(
"test_ex2.es",
r#"
throw Error('poof');
"#,
),
)
.expect("script compilation failed");
match mjsvf {
JsValueFacade::JsPromise { cached_promise } => {
let pres = cached_promise
.get_promise_result_sync()
.expect("promise timed out");
match pres {
Ok(m) => {
log::info!("prom resolved to {}", m.stringify())
}
Err(e) => {
log::info!("prom rejected to {}", e.stringify())
),
)
.map_err(|e| {
log::error!("script compilation failed: {e}");
e
})
.expect("script compilation failed");
match mjsvf {
JsValueFacade::JsPromise { cached_promise } => {
let pres = cached_promise
.get_promise_result_sync()
.expect("promise timed out");
match pres {
Ok(m) => {
log::info!("prom resolved to {}", m.stringify())
}
Err(e) => {
log::info!("prom rejected to {}", e.stringify())
}
}
}
}
_ => {
panic!("not a prom")
_ => {
panic!("not a prom")
}
}
}

Expand Down
9 changes: 7 additions & 2 deletions src/quickjs_utils/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -513,8 +513,13 @@ thread_local! {
};

static CALLBACK_CLASS_ID: RefCell<u32> = {
let mut c_id: u32 = 0;
let class_id: u32 = unsafe { q::JS_NewClassID(&mut c_id) };

let class_id: u32 =
QuickJsRuntimeAdapter::do_with(|q_js_rt| {
q_js_rt.new_class_id()
});


log::trace!("got class id {}", class_id);

CALLBACK_CLASS_DEF.with(|cd_rc| {
Expand Down
6 changes: 3 additions & 3 deletions src/quickjs_utils/interrupthandler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub mod tests {
use crate::builder::QuickJsRuntimeBuilder;
use crate::jsutils::Script;
use crate::quickjs_utils::get_script_or_module_name_q;
use backtrace::Backtrace;

use std::cell::RefCell;
use std::panic;
use std::sync::{Arc, Mutex};
Expand All @@ -40,15 +40,15 @@ pub mod tests {
let called = Arc::new(Mutex::new(RefCell::new(false)));
let called2 = called.clone();

panic::set_hook(Box::new(|panic_info| {
/*panic::set_hook(Box::new(|panic_info| {
let backtrace = Backtrace::new();
println!("thread panic occurred: {panic_info}\nbacktrace: {backtrace:?}");
log::error!(
"thread panic occurred: {}\nbacktrace: {:?}",
panic_info,
backtrace
);
}));
}));*/

//simple_logging::log_to_file("esruntime.log", LevelFilter::max())
// .expect("could not init logger");
Expand Down
1 change: 1 addition & 0 deletions src/quickjs_utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub mod objects;
pub mod primitives;
pub mod promises;
pub mod properties;
pub mod runtime;
pub mod sets;
pub mod typedarrays;

Expand Down
18 changes: 18 additions & 0 deletions src/quickjs_utils/runtime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use libquickjs_sys as q;

/// create new class id
/// # Safety
/// make sure the runtime param is from a live JsRuntimeAdapter instance
pub unsafe fn new_class_id(_runtime: *mut q::JSRuntime) -> u32 {
let mut c_id: u32 = 0;

#[cfg(feature = "bellard")]
let class_id: u32 = q::JS_NewClassID(&mut c_id);

#[cfg(feature = "quickjs-ng")]
let class_id: u32 = q::JS_NewClassID(_runtime, &mut c_id);

log::trace!("got class id {}", class_id);

class_id
}
19 changes: 19 additions & 0 deletions src/quickjsruntimeadapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::quickjs_utils::modules::{
add_module_export, compile_module, get_module_def, get_module_name, new_module,
set_module_export,
};
use crate::quickjs_utils::runtime::new_class_id;
use crate::quickjs_utils::{gc, interrupthandler, modules, promises};
use crate::quickjsrealmadapter::QuickJsRealmAdapter;
use libquickjs_sys as q;
Expand Down Expand Up @@ -346,6 +347,10 @@ impl QuickJsRuntimeAdapter {
})
}

pub fn new_class_id(&self) -> u32 {
unsafe { new_class_id(self.runtime) }
}

pub fn print_stats(&self) {
for ctx in &self.contexts {
println!("> ----- ctx: {}", ctx.0);
Expand Down Expand Up @@ -810,6 +815,7 @@ pub mod tests {
use crate::quickjsrealmadapter::QuickJsRealmAdapter;
use crate::quickjsruntimeadapter::QuickJsRuntimeAdapter;

use crate::facades::tests::init_test_rt;
use std::panic;

use crate::jsutils::modules::ScriptModuleLoader;
Expand Down Expand Up @@ -860,6 +866,19 @@ pub mod tests {
});
}

#[test]
fn test_eval() {
let rt = init_test_rt();
rt.eval_sync(
None,
Script::new(
"eval.js",
"console.log('bigint: ' + BigInt('1234') + '/' + BigInt(8765));",
),
)
.expect("script failed to compile");
}

#[test]
fn test_realm_init() {
/*panic::set_hook(Box::new(|panic_info| {
Expand Down
15 changes: 11 additions & 4 deletions src/reflection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,12 @@ thread_local! {
})
};
pub static PROXY_STATIC_CLASS_ID: RefCell<u32> = {
let mut c_id: u32 = 0;
let class_id: u32 = unsafe { q::JS_NewClassID(&mut c_id) };

let class_id: u32 =
QuickJsRuntimeAdapter::do_with(|q_js_rt| {
q_js_rt.new_class_id()
});

log::trace!("got static class id {}", class_id);

PROXY_STATIC_CLASS_DEF.with(|cd_rc| {
Expand All @@ -149,8 +153,11 @@ thread_local! {
RefCell::new(class_id)
};
pub static PROXY_INSTANCE_CLASS_ID: RefCell<u32> = {
let mut c_id: u32 = 0;
let class_id: u32 = unsafe { q::JS_NewClassID(&mut c_id) };

let class_id: u32 =
QuickJsRuntimeAdapter::do_with(|q_js_rt| {
q_js_rt.new_class_id()
});
log::trace!("got class id {}", class_id);

PROXY_INSTANCE_CLASS_DEF.with(|cd_rc| {
Expand Down

0 comments on commit 8e203fb

Please sign in to comment.