Link libqjs

This commit is contained in:
2026-09-09 19:51:20 +00:00
parent f99ebcc722
commit f8b1ef045e
3 changed files with 157 additions and 21 deletions
+13
View File
@@ -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");
}
+141
View File
@@ -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<Vec<u8>, 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<Vec<u8>, 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::<c_void>());
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);
}
+3 -21
View File
@@ -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::<Vec<String>>();
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:#?}");*/
}