//! 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, mut captures: Vec>, atoms: &[Atom], ) -> Vec { // 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], 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, } }