Mid rewrite
This commit is contained in:
@@ -1,39 +1,74 @@
|
||||
//! 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_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
|
||||
//! - close_loc
|
||||
//! - fclosure8
|
||||
//! - define_class
|
||||
//!
|
||||
//!
|
||||
//! And the following instructions take on a different meaning:
|
||||
//! - call_constructor (see new.rs)
|
||||
|
||||
mod args;
|
||||
mod bindings;
|
||||
mod declass;
|
||||
mod class;
|
||||
mod closure;
|
||||
mod expand;
|
||||
mod new;
|
||||
|
||||
use args::replace_arg_access;
|
||||
use bindings::replace_bindings;
|
||||
use declass::declass;
|
||||
use expand::{replace_expansion, replace_get_var};
|
||||
use new::{is_new, replace_new};
|
||||
|
||||
use crate::{
|
||||
flatten::FlatFn,
|
||||
libqjs::parse::{Atom, AtomRef, ClosureVar, ImmOperand, Instr, opcodes::OpCode},
|
||||
libqjs::parse::{Atom, AtomRef, ClosureVar},
|
||||
};
|
||||
|
||||
/// The field an object's prototype is kept in. In JS, `o.__proto__` is the
|
||||
/// prototype of an ordinary object, so user code that reads or writes it
|
||||
/// gets the right behaviour with no extra work.
|
||||
pub const PROTO_FIELD: AtomRef = AtomRef::Predefined(72); // __proto__
|
||||
use args::try_replace_arg_access;
|
||||
use class::declass;
|
||||
use expand::{replace_get_var, try_replace_expansion};
|
||||
use new::try_replace_new;
|
||||
|
||||
/// The field a constructor keeps the prototype of its instances in
|
||||
pub const PROTOTYPE_FIELD: AtomRef = AtomRef::Predefined(63); // prototype
|
||||
// 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);
|
||||
|
||||
/// Every fn takes its env and `this` as its first two params, and its declared
|
||||
/// params after them. So the params of a call do not depend on how many args
|
||||
/// the callee declares: a missing arg is not passed, and an extra one is not
|
||||
/// read.
|
||||
// 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;
|
||||
|
||||
/// The field a box keeps its binding's value in (see replace_bindings())
|
||||
pub const BOX_FIELD: AtomRef = AtomRef::Predefined(86); // <var>
|
||||
|
||||
pub fn transform(
|
||||
flat_fns: Vec<FlatFn>,
|
||||
captures: &[Vec<ClosureVar>],
|
||||
@@ -46,28 +81,31 @@ pub fn transform(
|
||||
}
|
||||
|
||||
fn transform_one(mut f: FlatFn, captures: &[Vec<ClosureVar>], atoms: &[Atom]) -> FlatFn {
|
||||
// Transform class declarations
|
||||
let code = declass(&f.code);
|
||||
|
||||
// Temp locals, after the fn's own, for the args of a `new` (see
|
||||
// replace_new()). Each `new` has its own: a local has one type for its
|
||||
// whole fn, so shared temps would join the args of every `new` in it
|
||||
let temps = f.var_count;
|
||||
let n_temps: usize = code.iter().filter_map(is_new).sum();
|
||||
let mut next_temp = temps;
|
||||
// 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 replace_new(instr, next_temp, &mut out) {
|
||||
next_temp += is_new(instr).unwrap();
|
||||
if try_replace_new(instr, &mut new_local, &mut out) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if replace_arg_access(instr, &mut out) {
|
||||
if try_replace_arg_access(instr, &mut out) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if replace_expansion(instr, &mut out) {
|
||||
if try_replace_expansion(instr, &mut out) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -75,22 +113,16 @@ fn transform_one(mut f: FlatFn, captures: &[Vec<ClosureVar>], atoms: &[Atom]) ->
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: replace closures/bindings
|
||||
|
||||
out.push(instr.clone());
|
||||
}
|
||||
|
||||
// Last, so that it also sees the var accesses that the expansions make
|
||||
let (code, var_count) = replace_bindings(&out, &f.cpool, captures, temps + n_temps);
|
||||
|
||||
FlatFn {
|
||||
name: f.name,
|
||||
arg_count: f.arg_count + FIRST_ARG as usize,
|
||||
var_count,
|
||||
local_count: f.local_count,
|
||||
cpool: f.cpool,
|
||||
code,
|
||||
}
|
||||
}
|
||||
|
||||
/// Pushes instrs at the pc of the instr they replace
|
||||
fn pusher(pc: usize, out: &mut Vec<Instr>) -> impl FnMut(OpCode, ImmOperand) + '_ {
|
||||
move |op, imm| out.push(Instr { pc, op, imm })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user