From f8b1ef045e61ee65a897e4eee4ca4b3e81a89e82 Mon Sep 17 00:00:00 2001 From: uditdey Date: Wed, 9 Sep 2026 19:51:20 +0000 Subject: [PATCH] Link `libqjs` --- compiler/build.rs | 13 ++++ compiler/src/libqjs.rs | 141 +++++++++++++++++++++++++++++++++++++++++ compiler/src/main.rs | 24 +------ 3 files changed, 157 insertions(+), 21 deletions(-) create mode 100644 compiler/build.rs create mode 100644 compiler/src/libqjs.rs diff --git a/compiler/build.rs b/compiler/build.rs new file mode 100644 index 0000000..1f59433 --- /dev/null +++ b/compiler/build.rs @@ -0,0 +1,13 @@ +const QUICKJS: &str = "../third_party/quickjs/build"; + +fn main() { + println!("cargo:rustc-link-search=native={QUICKJS}"); + println!("cargo:rustc-link-lib=static=qjs"); + + println!("cargo:rustc-link-lib=dylib=m"); + println!("cargo:rustc-link-lib=dylib=dl"); + println!("cargo:rustc-link-lib=dylib=pthread"); + + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed={QUICKJS}/libqjs.a"); +} diff --git a/compiler/src/libqjs.rs b/compiler/src/libqjs.rs new file mode 100644 index 0000000..1ddb265 --- /dev/null +++ b/compiler/src/libqjs.rs @@ -0,0 +1,141 @@ +use std::ffi::{CStr, CString, c_char, c_int, c_void}; + +/// Compile `source` to quickjs bytecode, as `qjsc -b` does. +/// +/// `name` is the module name quickjs records and reports errors against; it +/// does not have to be a path that exists. +pub fn compile(source: &str, name: &str) -> Result, String> { + // A runtime and a context per call: compiling holds no state worth reusing, + // and this way nothing outlives the returned bytes. + unsafe { + let rt = JS_NewRuntime(); + let ctx = JS_NewContext(rt); + + let result = compile_in(ctx, source, name); + + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + + result + } +} + +/// The body of `compile`, so the runtime is freed on either outcome. +unsafe fn compile_in(ctx: *mut JSContext, source: &str, name: &str) -> Result, String> { + // JS_Eval requires `input[input_len] == 0`, which a &str does not promise: + // the byte past the end is whatever the allocator left there. Read as a + // token, that byte decides whether the source parses. + let source = CString::new(source).map_err(|_| "source has a nul byte".to_string())?; + let name = CString::new(name).map_err(|_| "module name has a nul byte".to_string())?; + + unsafe { + // COMPILE_ONLY stops at the bytecode, so nothing in the source runs. + let obj = JS_Eval( + ctx, + source.as_ptr(), + source.as_bytes().len(), + name.as_ptr(), + JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY, + ); + + if obj.tag == JS_TAG_EXCEPTION { + return Err(exception(ctx)); + } + + let mut len = 0; + let buf = JS_WriteObject(ctx, &mut len, obj, JS_WRITE_OBJ_BYTECODE); + JS_FreeValue(ctx, obj); + + if buf.is_null() { + return Err(exception(ctx)); + } + + let bytes = std::slice::from_raw_parts(buf, len).to_vec(); + js_free(ctx, buf.cast::()); + + Ok(bytes) + } +} + +/// The pending exception, as text. Taking it also clears it. +unsafe fn exception(ctx: *mut JSContext) -> String { + unsafe { + let err = JS_GetException(ctx); + let text = JS_ToCStringLen2(ctx, std::ptr::null_mut(), err, false); + + let message = if text.is_null() { + "quickjs raised an exception it could not describe".to_string() + } else { + CStr::from_ptr(text).to_string_lossy().into_owned() + }; + + if !text.is_null() { + JS_FreeCString(ctx, text); + } + + JS_FreeValue(ctx, err); + message + } +} + +// ── the C side ───────────────────────────────────────────────────────────── + +enum JSRuntime {} +enum JSContext {} + +/// 16 bytes passed and returned BY VALUE. quickjs only nan-boxes on 32-bit +/// targets (`JS_NAN_BOXING`, quickjs.h), where this layout would be wrong. +#[repr(C)] +#[derive(Clone, Copy)] +struct JSValue { + u: JSValueUnion, + tag: i64, +} + +#[repr(C)] +#[derive(Clone, Copy)] +union JSValueUnion { + int32: i32, + float64: f64, + ptr: *mut c_void, +} + +// `JS_IsException` and `JS_ToCString` are `static inline` in the header, so +// they are not symbols to link against. These are the constants they read. +const JS_TAG_EXCEPTION: i64 = 6; +const JS_EVAL_TYPE_MODULE: c_int = 1 << 0; +const JS_EVAL_FLAG_COMPILE_ONLY: c_int = 1 << 5; +const JS_WRITE_OBJ_BYTECODE: c_int = 1 << 0; + +unsafe extern "C" { + fn JS_NewRuntime() -> *mut JSRuntime; + fn JS_FreeRuntime(rt: *mut JSRuntime); + fn JS_NewContext(rt: *mut JSRuntime) -> *mut JSContext; + fn JS_FreeContext(ctx: *mut JSContext); + + fn JS_Eval( + ctx: *mut JSContext, + input: *const c_char, + input_len: usize, + filename: *const c_char, + eval_flags: c_int, + ) -> JSValue; + + fn JS_WriteObject( + ctx: *mut JSContext, + psize: *mut usize, + obj: JSValue, + flags: c_int, + ) -> *mut u8; + + fn JS_GetException(ctx: *mut JSContext) -> JSValue; + fn JS_ToCStringLen2( + ctx: *mut JSContext, + plen: *mut usize, + val: JSValue, + cesu8: bool, + ) -> *const c_char; + fn JS_FreeCString(ctx: *mut JSContext, ptr: *const c_char); + fn JS_FreeValue(ctx: *mut JSContext, v: JSValue); + fn js_free(ctx: *mut JSContext, ptr: *mut c_void); +} diff --git a/compiler/src/main.rs b/compiler/src/main.rs index 969eb63..2e75a1b 100644 --- a/compiler/src/main.rs +++ b/compiler/src/main.rs @@ -3,11 +3,10 @@ mod declosure; mod demutate; mod flatten; mod infer; +mod libqjs; mod parse; mod regify; -use std::process::Command; - use declass::declass; use declosure::declosure; use demutate::demutate; @@ -16,24 +15,15 @@ use infer::infer; use parse::QuickJSBinary; use regify::regify; -const QJSC: &str = "../third_party/quickjs/build/qjsc"; -const BIN_OUT: &str = "/tmp/qjsrt.bin"; - fn main() { let args = std::env::args().collect::>(); let filename = args.get(1).unwrap(); // Compile source file - let output = Command::new(QJSC) - .args(["-b", "-o", BIN_OUT]) - .arg(filename) - .output() - .unwrap(); - - assert!(output.status.success()); + let source = std::fs::read_to_string(filename).unwrap(); + let bin = libqjs::compile(&source, filename).unwrap(); // Parse, then flatten - let bin = std::fs::read(BIN_OUT).unwrap(); let (_, parsed) = QuickJSBinary::parse(&bin).unwrap(); let (flat_fns, atoms) = flatten(parsed); @@ -65,12 +55,4 @@ fn main() { } println!("{atoms:#?}"); - - /*println!("=== after deprototype ==="); - let deprototyped = deprototype(declassed); - println!("{deprototyped:#?}"); - - println!("=== after declosure ==="); - let declosured = declosure(deprototyped); - println!("{declosured:#?}");*/ }