Files
jsrt/compiler/src/transform/mod.rs
T
2026-10-16 08:00:06 +00:00

155 lines
4.0 KiB
Rust

//! This module applies a set of transforms on the bytecode to lower them to a simpler
//! form more suitable for type inference and emission
//!
//! After transformation, the following instructions are eliminated:
//! - call{0, 1, 2, 3}
//! - tail_call, tail_call_method
//! - get_field2
//! - get_array_el2
//! - get_loc0_loc1
//! - inc_loc, dec_loc, add_loc
//! - set_proto
//! - define_method
//! - check_ctor
//! - special_object
//! - get_var
//! - push_this
//! - get_arg{0, 1, 2, 3}
//! - put_arg{0, 1, 2, 3}
//! - set_arg{0, 1, 2, 3}
//! - get_var_ref{0, 1, 2, 3}, get_var_ref_check
//! - put_var_ref{0, 1, 2, 3}, put_var_ref_check, put_var_ref_check_init
//! - set_var_ref{0, 1, 2, 3}, set_var_ref
//! - set_loc_uninitialized
//! - fclosure8
//! - define_class
//!
//!
//! The following instructions take on a different meaning:
//! - call_constructor (see new.rs)
//! - close_loc (see closure.rs)
//!
//! And the following instructions are added:
//! - define_field_ref (see closure.rs)
//! - close_field (see closure.rs)
//!
//! Async fns and generators become a constructor and a step closure first (see async.rs),
//! so no instr of theirs is left: initial_yield, yield, await, return_async in them, and
//! close_var_ref, which deasync makes and closure.rs lowers
mod args;
mod r#async;
mod class;
mod closure;
mod expand;
mod new;
use crate::{
flatten::FlatFn,
libqjs::parse::{Atom, AtomRef, ClosureVar},
};
use args::try_replace_arg_access;
use r#async::deasync;
use class::declass;
use closure::try_replace_closure;
use expand::{replace_get_var, try_replace_expansion};
use new::try_replace_new;
// Object Prototype Rules:
// - The `__proto__` field is the JS spec's `[[Prototype]]`, ie the link between an object and
// its parent prototype
//
// - The `prototype` field is the normal `.prototype` field used by classes, ie it holds the
// object that `new F()` will link
//
// So objects are created by:
// ```
// Object.create = function (proto) {
// return { __proto__: proto };
// };
// ```
/// `__proto__`
pub const PROTO_FIELD: AtomRef = AtomRef::Predefined(72);
/// `prototype`
pub const PROTOTYPE_FIELD: AtomRef = AtomRef::Predefined(63);
// Arg Rules:
// After transformation, every fn takes its capture env and `this` as the first two args
// and all the declared args after them
pub const ENV_ARG: u16 = 0;
pub const THIS_ARG: u16 = 1;
pub const FIRST_ARG: u16 = 2;
pub fn transform(
mut flat_fns: Vec<FlatFn>,
mut captures: Vec<Vec<ClosureVar>>,
atoms: &[Atom],
) -> Vec<FlatFn> {
// Async fns and generators become state machines first: this adds fns, and changes the
// captures that the other transforms read
deasync(&mut flat_fns, &mut captures, atoms);
flat_fns
.into_iter()
.enumerate()
.map(|(idx, f)| transform_one(f, &captures[idx], &captures, atoms))
.collect()
}
/// `own` is the captures of `f`
fn transform_one(
mut f: FlatFn,
own: &[ClosureVar],
captures: &[Vec<ClosureVar>],
atoms: &[Atom],
) -> FlatFn {
// Transform class declarations
let code = declass(&f.code);
// Transforms may need to allocate new local vars
let mut next_local = f.local_count;
let mut new_local = || {
let n = next_local;
next_local += 1;
n
};
// Apply transformations
let mut out = Vec::with_capacity(code.len());
for instr in &code {
if try_replace_new(instr, &mut new_local, &mut out) {
continue;
}
if try_replace_arg_access(instr, &mut out) {
continue;
}
if try_replace_expansion(instr, &mut out) {
continue;
}
if replace_get_var(instr, atoms, &mut f.cpool, &mut out) {
continue;
}
if try_replace_closure(instr, own, captures, &f.cpool, &mut out) {
continue;
}
out.push(instr.clone());
}
FlatFn {
name: f.name,
kind: f.kind,
arg_count: f.arg_count + FIRST_ARG as usize,
local_count: next_local,
cpool: f.cpool,
code: out,
}
}