Big Rewrite
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
use std::ffi::{c_int, c_void};
|
||||
|
||||
pub const JS_TAG_EXCEPTION: i64 = 6;
|
||||
pub const JS_EVAL_TYPE_MODULE: c_int = 1 << 0;
|
||||
pub const JS_EVAL_FLAG_COMPILE_ONLY: c_int = 1 << 5;
|
||||
pub const JS_WRITE_OBJ_BYTECODE: c_int = 1 << 0;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct JSValue {
|
||||
u: JSValueUnion,
|
||||
pub tag: i64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
union JSValueUnion {
|
||||
int32: i32,
|
||||
float64: f64,
|
||||
ptr: *mut c_void,
|
||||
}
|
||||
|
||||
pub type JSRuntime = c_void;
|
||||
pub type JSContext = c_void;
|
||||
|
||||
unsafe extern "C" {
|
||||
pub fn JS_NewRuntime() -> *mut JSRuntime;
|
||||
pub fn JS_FreeRuntime(rt: *mut JSRuntime);
|
||||
pub fn JS_NewContext(rt: *mut JSRuntime) -> *mut JSContext;
|
||||
pub fn JS_FreeContext(ctx: *mut JSContext);
|
||||
|
||||
pub fn JS_Eval(
|
||||
ctx: *mut JSContext,
|
||||
input: *const u8,
|
||||
input_len: usize,
|
||||
filename: *const u8,
|
||||
eval_flags: c_int,
|
||||
) -> JSValue;
|
||||
|
||||
pub fn JS_FreeValue(ctx: *mut JSContext, v: JSValue);
|
||||
|
||||
pub fn JS_WriteObject(
|
||||
ctx: *mut JSContext,
|
||||
psize: *mut usize,
|
||||
obj: JSValue,
|
||||
flags: c_int,
|
||||
) -> *mut u8;
|
||||
|
||||
pub fn JS_GetException(ctx: *mut JSContext) -> JSValue;
|
||||
|
||||
pub fn JS_ToCStringLen2(
|
||||
ctx: *mut JSContext,
|
||||
plen: *mut usize,
|
||||
val: JSValue,
|
||||
cesu8: bool,
|
||||
) -> *const i8;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
mod ffi;
|
||||
pub mod parse;
|
||||
|
||||
use std::ffi::{CStr, CString};
|
||||
|
||||
use parse::QuickJSBinary;
|
||||
|
||||
pub fn compile(src: &[u8], filename: &str) -> QuickJSBinary {
|
||||
unsafe {
|
||||
let filename = CString::new(filename).unwrap();
|
||||
|
||||
let rt = ffi::JS_NewRuntime();
|
||||
let ctx = ffi::JS_NewContext(rt);
|
||||
|
||||
let obj = ffi::JS_Eval(
|
||||
ctx,
|
||||
src.as_ptr(),
|
||||
src.len(),
|
||||
filename.as_ptr().cast(),
|
||||
ffi::JS_EVAL_FLAG_COMPILE_ONLY | ffi::JS_EVAL_TYPE_MODULE,
|
||||
);
|
||||
|
||||
assert!(obj.tag != ffi::JS_TAG_EXCEPTION, "{}", get_exception(ctx));
|
||||
|
||||
let mut len = 0;
|
||||
let buf = ffi::JS_WriteObject(ctx, &mut len, obj, ffi::JS_WRITE_OBJ_BYTECODE);
|
||||
ffi::JS_FreeValue(ctx, obj);
|
||||
|
||||
assert!(!buf.is_null());
|
||||
let bytes = std::slice::from_raw_parts(buf, len).to_vec();
|
||||
|
||||
// TODO: Are we sure other cleanup is not necessary?
|
||||
ffi::JS_FreeContext(ctx);
|
||||
ffi::JS_FreeRuntime(rt);
|
||||
|
||||
QuickJSBinary::parse(&bytes).unwrap().1
|
||||
}
|
||||
}
|
||||
|
||||
fn get_exception(ctx: *mut ffi::JSContext) -> String {
|
||||
unsafe {
|
||||
let err = ffi::JS_GetException(ctx);
|
||||
let text = ffi::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()
|
||||
};
|
||||
|
||||
message
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,895 @@
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::Write;
|
||||
|
||||
use nom::{
|
||||
IResult, Parser,
|
||||
bytes::complete::take,
|
||||
error::{Error, ErrorKind},
|
||||
multi::count,
|
||||
number::complete::{le_u16, le_u32, le_u64, u8},
|
||||
};
|
||||
|
||||
use num_enum::TryFromPrimitive;
|
||||
|
||||
pub mod atom_names {
|
||||
include!("../../../c_parse/atom_names.rs");
|
||||
}
|
||||
|
||||
pub mod opcodes {
|
||||
include!("../../../c_parse/opcodes.rs");
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct QuickJSBinary {
|
||||
pub version: u8,
|
||||
pub checksum: u32,
|
||||
pub atoms: Vec<Atom>,
|
||||
pub root_mod: Module,
|
||||
}
|
||||
|
||||
impl QuickJSBinary {
|
||||
pub fn parse(bin: &[u8]) -> IResult<&[u8], Self> {
|
||||
let (bin, version) = u8(bin)?;
|
||||
let (bin, checksum) = le_u32(bin)?;
|
||||
let (bin, num_atoms) = leb128(bin)?;
|
||||
let (bin, atoms) = count(Atom::parse, num_atoms).parse(bin)?;
|
||||
let (bin, value) = JsValue::parse(bin)?;
|
||||
|
||||
let JsValue::Module(root_mod) = value else {
|
||||
panic!()
|
||||
};
|
||||
|
||||
Ok((
|
||||
bin,
|
||||
QuickJSBinary {
|
||||
version,
|
||||
checksum,
|
||||
atoms,
|
||||
root_mod,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Atom {
|
||||
Predefined(u32),
|
||||
String(JsString),
|
||||
GlobalSymbol(JsString),
|
||||
Symbol(JsString),
|
||||
}
|
||||
|
||||
impl Atom {
|
||||
fn parse(bin: &[u8]) -> IResult<&[u8], Self> {
|
||||
let (bin, kind) = u8(bin)?;
|
||||
|
||||
let ret = match kind {
|
||||
0 => {
|
||||
let (bin, raw) = le_u32(bin)?;
|
||||
(bin, Self::Predefined(raw))
|
||||
}
|
||||
1 => {
|
||||
let (bin, header) = leb128(bin)?;
|
||||
let wide_char = header & 1 == 1;
|
||||
let len = header >> 1;
|
||||
let (bin, data) = take(len)(bin)?;
|
||||
let data = data.to_vec();
|
||||
|
||||
let string = if wide_char {
|
||||
JsString::UTF16(data)
|
||||
} else {
|
||||
JsString::Latin1(data)
|
||||
};
|
||||
|
||||
(bin, Self::String(string))
|
||||
}
|
||||
2 => todo!(),
|
||||
3 => todo!(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum AtomRef {
|
||||
TaggedInt(u32),
|
||||
Predefined(u32),
|
||||
FileLocal(usize),
|
||||
}
|
||||
|
||||
impl AtomRef {
|
||||
fn parse(bin: &[u8]) -> IResult<&[u8], Self> {
|
||||
let (bin, v) = leb128(bin)?;
|
||||
if v & 1 == 1 {
|
||||
return Ok((bin, Self::TaggedInt((v >> 1) as u32)));
|
||||
}
|
||||
let i = v >> 1;
|
||||
let end = atom_names::JS_ATOM_END as usize;
|
||||
if i < end {
|
||||
Ok((bin, Self::Predefined(i as u32)))
|
||||
} else {
|
||||
Ok((bin, Self::FileLocal(i - end)))
|
||||
}
|
||||
}
|
||||
|
||||
// LLM GENERATED
|
||||
//
|
||||
// Atom operands *inside bytecode* use a different encoding from the
|
||||
// `AtomRef::parse` above: a bare u32 with no shift, where a tagged
|
||||
// integer is marked by bit 31 instead of bit 0. Same index space, two
|
||||
// encodings
|
||||
fn from_code_u32(v: u32) -> Self {
|
||||
if v & (1 << 31) != 0 {
|
||||
return Self::TaggedInt(v & !(1 << 31));
|
||||
}
|
||||
let end = atom_names::JS_ATOM_END;
|
||||
if v < end {
|
||||
Self::Predefined(v)
|
||||
} else {
|
||||
Self::FileLocal((v - end) as usize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for AtomRef {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::TaggedInt(n) => write!(f, "int({n})"),
|
||||
Self::Predefined(i) => {
|
||||
let name = atom_names::PREDEFINED_ATOMS[*i as usize];
|
||||
write!(f, "atom({name:?})")
|
||||
}
|
||||
Self::FileLocal(i) => write!(f, "file_atom[{i}]"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum JsValue {
|
||||
Null,
|
||||
Undefined,
|
||||
BoolFalse,
|
||||
BoolTrue,
|
||||
Int(i32),
|
||||
Float(f64),
|
||||
String(JsString),
|
||||
Object,
|
||||
Array(Vec<JsValue>),
|
||||
/// Two's-complement little-endian bytes. Empty means zero.
|
||||
BigInt(Vec<u8>),
|
||||
/// A tagged-template's frozen `strings` array. `cooked` is the
|
||||
/// array body; `raw` is the extra trailing value stored as `.raw`
|
||||
/// (`Undefined` when the template has no raw form).
|
||||
TemplateObject {
|
||||
cooked: Vec<JsValue>,
|
||||
raw: Box<JsValue>,
|
||||
},
|
||||
Function(Function),
|
||||
FnById(usize),
|
||||
Module(Module),
|
||||
TypedArray,
|
||||
ArrayBuffer,
|
||||
SharedArrayBuffer,
|
||||
RegExp,
|
||||
Date,
|
||||
ObjectValue,
|
||||
ObjectReference,
|
||||
Map,
|
||||
Set,
|
||||
Symbol,
|
||||
}
|
||||
|
||||
impl JsValue {
|
||||
fn parse(bin: &[u8]) -> IResult<&[u8], Self> {
|
||||
let (bin, tag) = u8(bin)?;
|
||||
|
||||
let ret = match tag {
|
||||
1 => (bin, Self::Null),
|
||||
2 => (bin, Self::Undefined),
|
||||
3 => (bin, Self::BoolFalse),
|
||||
4 => (bin, Self::BoolTrue),
|
||||
5 => {
|
||||
let (bin, v) = sleb128(bin)?;
|
||||
(bin, Self::Int(v))
|
||||
}
|
||||
6 => {
|
||||
let (bin, bits) = le_u64(bin)?;
|
||||
(bin, Self::Float(f64::from_bits(bits)))
|
||||
}
|
||||
7 => {
|
||||
// String — same JSString encoding as atoms. Tentative.
|
||||
let (bin, header) = leb128(bin)?;
|
||||
let wide = header & 1 == 1;
|
||||
let len = header >> 1;
|
||||
let byte_len = if wide { len * 2 } else { len };
|
||||
let (bin, data) = take(byte_len)(bin)?;
|
||||
let s = if wide {
|
||||
JsString::UTF16(data.to_vec())
|
||||
} else {
|
||||
JsString::Latin1(data.to_vec())
|
||||
};
|
||||
(bin, Self::String(s))
|
||||
}
|
||||
9 => {
|
||||
// Tags 9 and 11 are one reader in quickjs (`JS_ReadArray`): a
|
||||
// leb128 count then that many values. A template object adds one
|
||||
// trailing value, defined as `.raw` when it is not undefined.
|
||||
let (bin, len) = leb128(bin)?;
|
||||
let (bin, items) = count(JsValue::parse, len as usize).parse(bin)?;
|
||||
(bin, Self::Array(items))
|
||||
}
|
||||
10 => {
|
||||
// Length is a byte count, not a limb count. quickjs reads it
|
||||
// back as u32 limbs plus a tail of loose bytes, which is the
|
||||
// same byte sequence either way.
|
||||
let (bin, len) = leb128(bin)?;
|
||||
let (bin, data) = take(len)(bin)?;
|
||||
(bin, Self::BigInt(data.to_vec()))
|
||||
}
|
||||
11 => {
|
||||
let (bin, len) = leb128(bin)?;
|
||||
let (bin, cooked) = count(JsValue::parse, len as usize).parse(bin)?;
|
||||
let (bin, raw) = JsValue::parse(bin)?;
|
||||
(
|
||||
bin,
|
||||
Self::TemplateObject {
|
||||
cooked,
|
||||
raw: Box::new(raw),
|
||||
},
|
||||
)
|
||||
}
|
||||
12 => {
|
||||
let (bin, function) = Function::parse(bin)?;
|
||||
(bin, Self::Function(function))
|
||||
}
|
||||
13 => {
|
||||
let (bin, module) = Module::parse(bin)?;
|
||||
(bin, Self::Module(module))
|
||||
}
|
||||
_ => todo!("JsValue tag {tag}"),
|
||||
};
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, TryFromPrimitive)]
|
||||
#[repr(u16)]
|
||||
pub enum FuncKind {
|
||||
Normal = 0,
|
||||
Generator = 1,
|
||||
Async = 2,
|
||||
AsyncGenerator = 3,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FunctionFlags {
|
||||
pub has_prototype: bool,
|
||||
pub has_simple_param_list: bool,
|
||||
pub is_derived_class_constructor: bool,
|
||||
pub need_home_object: bool,
|
||||
pub func_kind: FuncKind,
|
||||
pub new_target_allowed: bool,
|
||||
pub super_call_allowed: bool,
|
||||
pub super_allowed: bool,
|
||||
pub arguments_allowed: bool,
|
||||
pub backtrace_barrier: bool,
|
||||
pub has_debug: bool,
|
||||
}
|
||||
|
||||
impl FunctionFlags {
|
||||
fn parse(bin: &[u8]) -> IResult<&[u8], Self> {
|
||||
let (bin, raw) = le_u16(bin)?;
|
||||
|
||||
Ok((
|
||||
bin,
|
||||
Self {
|
||||
has_prototype: raw & (1 << 0) != 0,
|
||||
has_simple_param_list: raw & (1 << 1) != 0,
|
||||
is_derived_class_constructor: raw & (1 << 2) != 0,
|
||||
need_home_object: raw & (1 << 3) != 0,
|
||||
func_kind: FuncKind::try_from_primitive((raw >> 4) & 0b11).unwrap(),
|
||||
new_target_allowed: raw & (1 << 6) != 0,
|
||||
super_call_allowed: raw & (1 << 7) != 0,
|
||||
super_allowed: raw & (1 << 8) != 0,
|
||||
arguments_allowed: raw & (1 << 9) != 0,
|
||||
backtrace_barrier: raw & (1 << 10) != 0,
|
||||
has_debug: raw & (1 << 11) != 0,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, TryFromPrimitive)]
|
||||
#[repr(u8)]
|
||||
pub enum VarKind {
|
||||
Normal = 0,
|
||||
FunctionDecl = 1,
|
||||
NewFunctionDecl = 2,
|
||||
Catch = 3,
|
||||
FunctionName = 4,
|
||||
PrivateField = 5,
|
||||
PrivateMethod = 6,
|
||||
PrivateGetter = 7,
|
||||
PrivateSetter = 8,
|
||||
PrivateGetterSetter = 9,
|
||||
Using = 10,
|
||||
UsingMethod = 11,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VarDefFlags {
|
||||
pub kind: VarKind,
|
||||
pub is_const: bool,
|
||||
pub is_lexical: bool,
|
||||
pub is_captured: bool,
|
||||
}
|
||||
|
||||
impl VarDefFlags {
|
||||
fn parse(bin: &[u8]) -> IResult<&[u8], Self> {
|
||||
let (bin, raw) = u8(bin)?;
|
||||
|
||||
let kind = VarKind::try_from_primitive(raw & 0b1111).unwrap();
|
||||
let is_const = raw & (1 << 4) != 0;
|
||||
let is_lexical = raw & (1 << 5) != 0;
|
||||
let is_captured = raw & (1 << 6) != 0;
|
||||
|
||||
Ok((
|
||||
bin,
|
||||
Self {
|
||||
kind,
|
||||
is_const,
|
||||
is_lexical,
|
||||
is_captured,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VarDef {
|
||||
pub name: AtomRef,
|
||||
pub scope_level: usize,
|
||||
pub scope_next_plus_1: usize,
|
||||
pub flags: VarDefFlags,
|
||||
pub var_ref_idx: Option<usize>,
|
||||
}
|
||||
|
||||
impl VarDef {
|
||||
fn parse(bin: &[u8]) -> IResult<&[u8], Self> {
|
||||
let (bin, name) = AtomRef::parse(bin)?;
|
||||
let (bin, scope_level) = leb128(bin)?;
|
||||
let (bin, scope_next_plus_1) = leb128(bin)?;
|
||||
let (bin, flags) = VarDefFlags::parse(bin)?;
|
||||
|
||||
let (bin, var_ref_idx) = if flags.is_captured {
|
||||
let (bin, idx) = leb128(bin)?;
|
||||
(bin, Some(idx))
|
||||
} else {
|
||||
(bin, None)
|
||||
};
|
||||
|
||||
Ok((
|
||||
bin,
|
||||
Self {
|
||||
name,
|
||||
scope_level,
|
||||
scope_next_plus_1,
|
||||
flags,
|
||||
var_ref_idx,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, TryFromPrimitive)]
|
||||
#[repr(u8)]
|
||||
pub enum ClosureKind {
|
||||
Local = 0,
|
||||
Arg = 1,
|
||||
Ref = 2,
|
||||
GlobalRef = 3,
|
||||
GlobalDecl = 4,
|
||||
Global = 5,
|
||||
ModuleDecl = 6,
|
||||
ModuleImport = 7,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ClosureVarFlags {
|
||||
pub closure_kind: ClosureKind,
|
||||
pub is_const: bool,
|
||||
pub is_lexical: bool,
|
||||
pub var_kind: VarKind,
|
||||
}
|
||||
|
||||
impl ClosureVarFlags {
|
||||
fn parse(bin: &[u8]) -> IResult<&[u8], Self> {
|
||||
let (bin, raw) = leb128(bin)?;
|
||||
|
||||
// bits 0..2 = closure_type, bit 3 = is_const, bit 4 = is_lexical, bits 5..8 = var_kind
|
||||
let closure_kind = ClosureKind::try_from_primitive((raw & 0b111) as u8).unwrap();
|
||||
let is_const = raw & (1 << 3) != 0;
|
||||
let is_lexical = raw & (1 << 4) != 0;
|
||||
let var_kind = VarKind::try_from_primitive(((raw >> 5) & 0xF) as u8).unwrap();
|
||||
|
||||
Ok((
|
||||
bin,
|
||||
Self {
|
||||
closure_kind,
|
||||
is_const,
|
||||
is_lexical,
|
||||
var_kind,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ClosureVar {
|
||||
pub name: AtomRef,
|
||||
pub var_idx: usize,
|
||||
pub flags: ClosureVarFlags,
|
||||
}
|
||||
|
||||
impl ClosureVar {
|
||||
fn parse(bin: &[u8]) -> IResult<&[u8], Self> {
|
||||
let (bin, name) = AtomRef::parse(bin)?;
|
||||
let (bin, var_idx) = leb128(bin)?;
|
||||
let (bin, flags) = ClosureVarFlags::parse(bin)?;
|
||||
|
||||
Ok((
|
||||
bin,
|
||||
Self {
|
||||
name,
|
||||
var_idx,
|
||||
flags,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// LLM GENERATED, TENTATIVE
|
||||
//
|
||||
// Decoded operands, one variant per `OpFmt` family. Widths come from
|
||||
// `OPCODE_INFO[op].size`, which counts the opcode byte, so an
|
||||
// instruction carries `size - 1` operand bytes.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ImmOperand {
|
||||
/// No operand bytes: `none`, `npopx`, and the `none_*` families
|
||||
/// where the value is folded into the opcode number itself.
|
||||
None,
|
||||
U8(u8),
|
||||
I8(i8),
|
||||
U16(u16),
|
||||
I16(i16),
|
||||
U32(u32),
|
||||
I32(i32),
|
||||
U32x2(u32, u32),
|
||||
/// Local slot index: `loc`, `loc8`.
|
||||
Loc(u16),
|
||||
/// Argument slot index: `arg`.
|
||||
Arg(u16),
|
||||
/// Closure variable index: `var_ref`.
|
||||
VarRef(u16),
|
||||
/// Constant pool index: `const`, `const8`.
|
||||
Const(u32),
|
||||
/// Argument count of a call: `npop`.
|
||||
Npop(u16),
|
||||
/// `npop_u16`, used by `eval`: the u16 is a local index biased +1
|
||||
/// (the scope chain entry point, FORMAT.md §3.3).
|
||||
NpopU16 {
|
||||
npop: u16,
|
||||
scope: u16,
|
||||
},
|
||||
/// Jump. The wire value is a signed offset from the operand
|
||||
/// field's own position; `target` is already resolved.
|
||||
Label {
|
||||
target: usize,
|
||||
},
|
||||
Atom(AtomRef),
|
||||
AtomU8 {
|
||||
atom: AtomRef,
|
||||
arg: u8,
|
||||
},
|
||||
AtomU16 {
|
||||
atom: AtomRef,
|
||||
arg: u16,
|
||||
},
|
||||
AtomLabelU8 {
|
||||
atom: AtomRef,
|
||||
target: usize,
|
||||
arg: u8,
|
||||
},
|
||||
AtomLabelU16 {
|
||||
atom: AtomRef,
|
||||
target: usize,
|
||||
arg: u16,
|
||||
},
|
||||
LabelU16 {
|
||||
target: usize,
|
||||
arg: u16,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct Instr {
|
||||
pub pc: usize,
|
||||
pub op: opcodes::OpCode,
|
||||
pub imm_operands: ImmOperand,
|
||||
}
|
||||
|
||||
impl Debug for Instr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:04} {}", self.pc, self.op.name())?;
|
||||
match &self.imm_operands {
|
||||
ImmOperand::None => Ok(()),
|
||||
other => write!(f, " {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LLM GENERATED, TENTATIVE
|
||||
//
|
||||
// Walks `code` instruction by instruction. Two things make this more
|
||||
// than a byte copy (FORMAT.md §2.3 and notes 2 and 5):
|
||||
//
|
||||
// - The opcode byte indexes `short_opcode_info()`, giving the opcode,
|
||||
// its width, and its operand format. The generated table already
|
||||
// omits the compiler's temporary opcodes, so no short-opcode shift
|
||||
// applies.
|
||||
// - Jump operands are offsets from the operand field's own position,
|
||||
// not from the start or end of the instruction. For the
|
||||
// `atom_label_*` formats that base sits 4 bytes into the operands.
|
||||
fn parse_bytecode(code: &[u8]) -> IResult<&[u8], Vec<Instr>> {
|
||||
let jump = |base: usize, offset: i64| (base as i64 + offset) as usize;
|
||||
|
||||
let mut out = Vec::new();
|
||||
let mut pc = 0usize;
|
||||
|
||||
while pc < code.len() {
|
||||
let op = code[pc];
|
||||
|
||||
let Some(info) = opcodes::short_opcode_info(op) else {
|
||||
return Err(nom::Err::Failure(Error::new(code, ErrorKind::Tag)));
|
||||
};
|
||||
|
||||
let size = info.size as usize;
|
||||
if pc + size > code.len() {
|
||||
return Err(nom::Err::Incomplete(nom::Needed::new(
|
||||
pc + size - code.len(),
|
||||
)));
|
||||
}
|
||||
|
||||
let ops = &code[pc + 1..pc + size];
|
||||
let u8_at = |i: usize| ops[i];
|
||||
let u16_at = |i: usize| u16::from_le_bytes([ops[i], ops[i + 1]]);
|
||||
let u32_at = |i: usize| u32::from_le_bytes([ops[i], ops[i + 1], ops[i + 2], ops[i + 3]]);
|
||||
|
||||
// The label field of an `atom_label_*` operand starts after the atom.
|
||||
let atom_label_base = pc + 1 + 4;
|
||||
|
||||
let operands = match info.fmt {
|
||||
opcodes::OpFmt::None
|
||||
| opcodes::OpFmt::NoneInt
|
||||
| opcodes::OpFmt::NoneLoc
|
||||
| opcodes::OpFmt::NoneArg
|
||||
| opcodes::OpFmt::NoneVarRef
|
||||
| opcodes::OpFmt::Npopx => ImmOperand::None,
|
||||
|
||||
opcodes::OpFmt::U8 => ImmOperand::U8(u8_at(0)),
|
||||
opcodes::OpFmt::I8 => ImmOperand::I8(u8_at(0) as i8),
|
||||
opcodes::OpFmt::Loc8 => ImmOperand::Loc(u8_at(0) as u16),
|
||||
opcodes::OpFmt::Const8 => ImmOperand::Const(u8_at(0) as u32),
|
||||
opcodes::OpFmt::Label8 => ImmOperand::Label {
|
||||
target: jump(pc + 1, u8_at(0) as i8 as i64),
|
||||
},
|
||||
|
||||
opcodes::OpFmt::U16 => ImmOperand::U16(u16_at(0)),
|
||||
opcodes::OpFmt::I16 => ImmOperand::I16(u16_at(0) as i16),
|
||||
opcodes::OpFmt::Label16 => ImmOperand::Label {
|
||||
target: jump(pc + 1, u16_at(0) as i16 as i64),
|
||||
},
|
||||
opcodes::OpFmt::Npop => ImmOperand::Npop(u16_at(0)),
|
||||
opcodes::OpFmt::NpopU16 => ImmOperand::NpopU16 {
|
||||
npop: u16_at(0),
|
||||
scope: u16_at(2),
|
||||
},
|
||||
opcodes::OpFmt::Loc => ImmOperand::Loc(u16_at(0)),
|
||||
opcodes::OpFmt::Arg => ImmOperand::Arg(u16_at(0)),
|
||||
opcodes::OpFmt::VarRef => ImmOperand::VarRef(u16_at(0)),
|
||||
|
||||
opcodes::OpFmt::U32 => ImmOperand::U32(u32_at(0)),
|
||||
opcodes::OpFmt::U32x2 => ImmOperand::U32x2(u32_at(0), u32_at(4)),
|
||||
opcodes::OpFmt::I32 => ImmOperand::I32(u32_at(0) as i32),
|
||||
opcodes::OpFmt::Const => ImmOperand::Const(u32_at(0)),
|
||||
opcodes::OpFmt::Label => ImmOperand::Label {
|
||||
target: jump(pc + 1, u32_at(0) as i32 as i64),
|
||||
},
|
||||
|
||||
opcodes::OpFmt::Atom => ImmOperand::Atom(AtomRef::from_code_u32(u32_at(0))),
|
||||
opcodes::OpFmt::AtomU8 => ImmOperand::AtomU8 {
|
||||
atom: AtomRef::from_code_u32(u32_at(0)),
|
||||
arg: u8_at(4),
|
||||
},
|
||||
opcodes::OpFmt::AtomU16 => ImmOperand::AtomU16 {
|
||||
atom: AtomRef::from_code_u32(u32_at(0)),
|
||||
arg: u16_at(4),
|
||||
},
|
||||
opcodes::OpFmt::AtomLabelU8 => ImmOperand::AtomLabelU8 {
|
||||
atom: AtomRef::from_code_u32(u32_at(0)),
|
||||
target: jump(atom_label_base, u32_at(4) as i32 as i64),
|
||||
arg: u8_at(8),
|
||||
},
|
||||
opcodes::OpFmt::AtomLabelU16 => ImmOperand::AtomLabelU16 {
|
||||
atom: AtomRef::from_code_u32(u32_at(0)),
|
||||
target: jump(atom_label_base, u32_at(4) as i32 as i64),
|
||||
arg: u16_at(8),
|
||||
},
|
||||
opcodes::OpFmt::LabelU16 => ImmOperand::LabelU16 {
|
||||
target: jump(pc + 1, u32_at(0) as i32 as i64),
|
||||
arg: u16_at(4),
|
||||
},
|
||||
};
|
||||
|
||||
out.push(Instr {
|
||||
pc,
|
||||
op: info.op,
|
||||
imm_operands: operands,
|
||||
});
|
||||
|
||||
pc += size;
|
||||
}
|
||||
|
||||
Ok((&code[pc..], out))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Function {
|
||||
pub flags: FunctionFlags,
|
||||
pub is_strict_mode: bool,
|
||||
pub name: AtomRef,
|
||||
pub arg_count: usize,
|
||||
/// Num locals
|
||||
pub var_count: usize,
|
||||
/// Parameters before the first default or rest, this is Function.prototype.length
|
||||
pub defined_arg_count: usize,
|
||||
pub stack_size: usize,
|
||||
pub var_ref_count: usize,
|
||||
/// arguments first, then locals
|
||||
pub var_defs: Vec<VarDef>,
|
||||
pub closure_vars: Vec<ClosureVar>,
|
||||
pub cpool: Vec<JsValue>,
|
||||
/// LLM GENERATED, TENTATIVE
|
||||
pub byte_code: Vec<Instr>,
|
||||
}
|
||||
|
||||
impl Function {
|
||||
fn parse(bin: &[u8]) -> IResult<&[u8], Self> {
|
||||
let (bin, flags) = FunctionFlags::parse(bin)?;
|
||||
let (bin, strict_mode) = u8(bin)?;
|
||||
let (bin, name) = AtomRef::parse(bin)?;
|
||||
let (bin, arg_count) = leb128(bin)?;
|
||||
let (bin, var_count) = leb128(bin)?;
|
||||
let (bin, defined_arg_count) = leb128(bin)?;
|
||||
let (bin, stack_size) = leb128(bin)?;
|
||||
let (bin, var_ref_count) = leb128(bin)?;
|
||||
let (bin, closure_var_count) = leb128(bin)?;
|
||||
let (bin, cpool_count) = leb128(bin)?;
|
||||
let (bin, bytecode_len) = leb128(bin)?;
|
||||
let (bin, var_def_count) = leb128(bin)?;
|
||||
|
||||
let (bin, var_defs) = count(VarDef::parse, var_def_count).parse(bin)?;
|
||||
let (bin, closure_vars) = count(ClosureVar::parse, closure_var_count).parse(bin)?;
|
||||
let (bin, cpool) = count(JsValue::parse, cpool_count).parse(bin)?;
|
||||
|
||||
let (bin, code) = take(bytecode_len)(bin)?;
|
||||
let (_, byte_code) = parse_bytecode(code)?;
|
||||
|
||||
// skip debug block
|
||||
let bin = if flags.has_debug {
|
||||
let (bin, _filename) = AtomRef::parse(bin)?;
|
||||
let (bin, _line_num) = leb128(bin)?;
|
||||
let (bin, _col_num) = leb128(bin)?;
|
||||
let (bin, pc2line_len) = leb128(bin)?;
|
||||
let (bin, _pc2line) = take(pc2line_len)(bin)?;
|
||||
let (bin, source_len) = leb128(bin)?;
|
||||
let (bin, _source) = take(source_len)(bin)?;
|
||||
bin
|
||||
} else {
|
||||
bin
|
||||
};
|
||||
|
||||
Ok((
|
||||
bin,
|
||||
Self {
|
||||
flags,
|
||||
is_strict_mode: strict_mode == 1,
|
||||
name,
|
||||
arg_count,
|
||||
var_count,
|
||||
defined_arg_count,
|
||||
stack_size,
|
||||
var_ref_count,
|
||||
var_defs,
|
||||
closure_vars,
|
||||
cpool,
|
||||
byte_code,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Module {
|
||||
pub name: AtomRef,
|
||||
pub import_modules: Vec<AtomRef>,
|
||||
pub exports: Vec<ExportInfo>,
|
||||
pub star_exports: Vec<usize>,
|
||||
pub imports: Vec<ImportInfo>,
|
||||
pub has_top_level_await: bool,
|
||||
pub root_fn: Function,
|
||||
}
|
||||
|
||||
impl Module {
|
||||
pub fn parse(bin: &[u8]) -> IResult<&[u8], Self> {
|
||||
let (bin, name) = AtomRef::parse(bin)?;
|
||||
|
||||
let (bin, num_import_modules) = leb128(bin)?;
|
||||
let (bin, import_modules) =
|
||||
count(AtomRef::parse, num_import_modules as usize).parse(bin)?;
|
||||
|
||||
let (bin, num_exports) = leb128(bin)?;
|
||||
let (bin, exports) = count(ExportInfo::parse, num_exports).parse(bin)?;
|
||||
let (bin, num_star_exports) = leb128(bin)?;
|
||||
let (bin, star_exports) = count(leb128, num_star_exports).parse(bin)?;
|
||||
let (bin, num_imports) = leb128(bin)?;
|
||||
let (bin, imports) = count(ImportInfo::parse, num_imports).parse(bin)?;
|
||||
let (bin, has_tla) = u8(bin)?;
|
||||
let (bin, value) = JsValue::parse(bin)?;
|
||||
|
||||
let JsValue::Function(root_fn) = value else {
|
||||
panic!()
|
||||
};
|
||||
|
||||
Ok((
|
||||
bin,
|
||||
Self {
|
||||
name,
|
||||
import_modules,
|
||||
exports,
|
||||
star_exports,
|
||||
imports,
|
||||
has_top_level_await: has_tla == 1,
|
||||
root_fn,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ExportInfo {
|
||||
/// All exports except aliases and star exports
|
||||
Local { var_idx: usize, name: AtomRef },
|
||||
/// Alias exports, has `from`, eg: export { a } from "./dep.js";
|
||||
Alias,
|
||||
}
|
||||
|
||||
impl ExportInfo {
|
||||
fn parse(bin: &[u8]) -> IResult<&[u8], Self> {
|
||||
let (bin, kind) = u8(bin)?;
|
||||
|
||||
let ret = match kind {
|
||||
0 => {
|
||||
let (bin, var_idx) = leb128(bin)?;
|
||||
let (bin, name) = AtomRef::parse(bin)?;
|
||||
(bin, Self::Local { var_idx, name })
|
||||
}
|
||||
1 => (bin, Self::Alias),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ImportInfo {
|
||||
pub var_idx: usize,
|
||||
pub name: AtomRef,
|
||||
pub req_module_idx: usize,
|
||||
}
|
||||
|
||||
impl ImportInfo {
|
||||
fn parse(bin: &[u8]) -> IResult<&[u8], Self> {
|
||||
let (bin, var_idx) = leb128(bin)?;
|
||||
let (bin, name) = AtomRef::parse(bin)?;
|
||||
let (bin, req_module_idx) = leb128(bin)?;
|
||||
|
||||
Ok((
|
||||
bin,
|
||||
Self {
|
||||
var_idx,
|
||||
name,
|
||||
req_module_idx,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Signed LEB128 via zigzag over unsigned LEB128. Tentative.
|
||||
fn sleb128(input: &[u8]) -> IResult<&[u8], i32> {
|
||||
let (rest, u) = leb128(input)?;
|
||||
let v = (u >> 1) as i32 ^ -((u & 1) as i32);
|
||||
Ok((rest, v))
|
||||
}
|
||||
|
||||
/// Parses leb128 into u64 (assumes usize == u64)
|
||||
fn leb128(input: &[u8]) -> IResult<&[u8], usize> {
|
||||
let mut result: u64 = 0;
|
||||
let mut shift: u32 = 0;
|
||||
let mut pos = 0;
|
||||
|
||||
loop {
|
||||
if pos >= input.len() {
|
||||
return Err(nom::Err::Incomplete(nom::Needed::new(1)));
|
||||
}
|
||||
|
||||
let byte = input[pos];
|
||||
pos += 1;
|
||||
|
||||
// Guard against overflow (u64 holds 64 bits, so max shift is 63)
|
||||
if shift >= 64 {
|
||||
return Err(nom::Err::Failure(Error::new(input, ErrorKind::TooLarge)));
|
||||
}
|
||||
|
||||
result |= ((byte & 0x7F) as u64) << shift;
|
||||
shift += 7;
|
||||
|
||||
// High bit clear means this is the last byte
|
||||
if byte & 0x80 == 0 {
|
||||
return Ok((&input[pos..], result as usize));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum JsString {
|
||||
Latin1(Vec<u8>),
|
||||
UTF16(Vec<u8>),
|
||||
}
|
||||
|
||||
impl Debug for JsString {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_char('"')?;
|
||||
match self {
|
||||
JsString::Latin1(bytes) => {
|
||||
for &b in bytes {
|
||||
let ch = b as char;
|
||||
if ch == '"' || ch == '\\' {
|
||||
f.write_char('\\')?;
|
||||
}
|
||||
f.write_char(ch)?;
|
||||
}
|
||||
}
|
||||
JsString::UTF16(bytes) => {
|
||||
let u16s: Vec<u16> = bytes
|
||||
.chunks_exact(2)
|
||||
.map(|c| u16::from_le_bytes([c[0], c[1]]))
|
||||
.collect();
|
||||
for result in char::decode_utf16(u16s.into_iter()) {
|
||||
match result {
|
||||
Ok(ch) => {
|
||||
if ch == '"' || ch == '\\' {
|
||||
f.write_char('\\')?;
|
||||
}
|
||||
f.write_char(ch)?;
|
||||
}
|
||||
Err(e) => write!(f, "\\u{{{:04X}}}", e.unpaired_surrogate())?,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
f.write_char('"')
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user