Modified infer
This commit is contained in:
@@ -2,8 +2,9 @@
|
|||||||
//!
|
//!
|
||||||
//! There are four kinds:
|
//! There are four kinds:
|
||||||
//!
|
//!
|
||||||
//! - **A JS function**, as infer walked it. Infer walks a JS function once for each set of
|
//! - **A JS function**, as infer walked it. Infer walks a JS function once for each context,
|
||||||
//! arg types that it is called with, and each walk is one C function (see `js`).
|
//! which is the function in one call site, and each set of arg types. Each walk is one C
|
||||||
|
//! function (see `js`).
|
||||||
//! - **A lookup**, which gets a field that some objects of a class do not have. It gives the
|
//! - **A lookup**, which gets a field that some objects of a class do not have. It gives the
|
||||||
//! object's own field if the object has it, else it looks on the object's prototype, and so
|
//! object's own field if the object has it, else it looks on the object's prototype, and so
|
||||||
//! on along the prototype chain. At the end of the chain, the field is undefined.
|
//! on along the prototype chain. At the end of the chain, the field is undefined.
|
||||||
@@ -19,7 +20,7 @@ use crate::{infer::Type, libqjs::parse::AtomRef, regify::Operand, transform::PRO
|
|||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub enum Function {
|
pub enum Function {
|
||||||
Js(usize), // a JS function, as one walk of infer: the walk's index
|
Js(usize), // a JS function, as one walk: the walk's index
|
||||||
Lookup(usize, AtomRef), // a lookup of a field on the objects of a class: the class, and the field
|
Lookup(usize, AtomRef), // a lookup of a field on the objects of a class: the class, and the field
|
||||||
Start,
|
Start,
|
||||||
Complete,
|
Complete,
|
||||||
@@ -171,7 +172,7 @@ fn complete_call(program: &Program) -> Option<String> {
|
|||||||
Some(host_call(program, walk, &args))
|
Some(host_call(program, walk, &args))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A call of a JS function's walk, with each arg converted to its param's C type
|
/// A call of a walk's C function, with each arg converted to its param's C type
|
||||||
fn host_call(program: &Program, walk: usize, args: &[(String, Member)]) -> String {
|
fn host_call(program: &Program, walk: usize, args: &[(String, Member)]) -> String {
|
||||||
let args: Vec<String> = (args.iter().enumerate())
|
let args: Vec<String> = (args.iter().enumerate())
|
||||||
.map(|(i, (value, member))| {
|
.map(|(i, (value, member))| {
|
||||||
|
|||||||
+14
-12
@@ -11,7 +11,7 @@ use super::{
|
|||||||
values::{base, int, strict_eq, to_int32, to_number, truthy, undefined, unit_literal},
|
values::{base, int, strict_eq, to_int32, to_number, truthy, undefined, unit_literal},
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
infer::{InstrRef, ObjAddr, Type},
|
infer::{CallSite, InstrRef, ObjAddr, Type},
|
||||||
libqjs::parse::{ImmOperand, JsValue, opcodes::OpCode},
|
libqjs::parse::{ImmOperand, JsValue, opcodes::OpCode},
|
||||||
native::NATIVES,
|
native::NATIVES,
|
||||||
regify::{Operand, RegInstr},
|
regify::{Operand, RegInstr},
|
||||||
@@ -82,19 +82,21 @@ impl JsFunction<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Instr `idx` of this JS function
|
/// The call site of the call at instr `idx`
|
||||||
fn here(&self, idx: usize) -> InstrRef {
|
fn call_site(&self, idx: usize) -> CallSite {
|
||||||
InstrRef {
|
Some(InstrRef {
|
||||||
fn_idx: self.program.cache.walks[self.walk].req.fn_idx,
|
fn_idx: self.context.fn_idx,
|
||||||
instr_idx: idx,
|
instr_idx: idx,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The class of the objects that instr `idx` makes, in this walk
|
/// The class of the objects that instr `idx` makes, in this walk's context
|
||||||
pub(super) fn class_here(&self, idx: usize) -> usize {
|
pub(super) fn class_here(&self, idx: usize) -> usize {
|
||||||
let in_call = self.program.cache.walks[self.walk].req.in_call;
|
let obj = ObjAddr {
|
||||||
let made_at = self.here(idx);
|
context: self.context,
|
||||||
self.program.types.class(ObjAddr { made_at, in_call })
|
instr: idx,
|
||||||
|
};
|
||||||
|
self.program.types.class(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `close_loc x`: a local that closures share gets a new cell with the same value.
|
/// `close_loc x`: a local that closures share gets a new cell with the same value.
|
||||||
@@ -466,7 +468,7 @@ impl JsFunction<'_> {
|
|||||||
|
|
||||||
let mut types: Vec<Type> = instr.args.iter().map(|&s| self.ty(s)).collect();
|
let mut types: Vec<Type> = instr.args.iter().map(|&s| self.ty(s)).collect();
|
||||||
types[0] = Type::Ref(program.types.classes[class].objs[0]);
|
types[0] = Type::Ref(program.types.classes[class].objs[0]);
|
||||||
let walk = (program.cache).find(fn_idx, Some(self.here(idx)), &types, program.fns);
|
let walk = (program.cache).find(fn_idx, self.call_site(idx), &types, program.fns);
|
||||||
let mut args = vec![];
|
let mut args = vec![];
|
||||||
|
|
||||||
for i in 0..program.fns[fn_idx].arg_count {
|
for i in 0..program.fns[fn_idx].arg_count {
|
||||||
@@ -481,7 +483,7 @@ impl JsFunction<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let call = format!("js{walk}({})", args.join(", "));
|
let call = format!("js{walk}({})", args.join(", "));
|
||||||
let ret = &program.cache.walks[walk].ret;
|
let ret = program.cache.ret(walk);
|
||||||
|
|
||||||
// A function that never returns gives no value
|
// A function that never returns gives no value
|
||||||
if *ret == Type::Unknown {
|
if *ret == Type::Unknown {
|
||||||
|
|||||||
+13
-12
@@ -1,8 +1,9 @@
|
|||||||
//! A JS function as a C function
|
//! A JS function as a C function
|
||||||
//!
|
//!
|
||||||
//! Infer walks each JS function once for each set of arg types that it is called with. Each
|
//! Infer walks a JS function once for each context, which is the function in one call site,
|
||||||
//! walk is one C function, `js<walk>`, in which every value has the type that infer found in
|
//! and each set of arg types. Each walk is one C function, `js<walk>`, in which every value
|
||||||
//! that walk.
|
//! has the type that infer found in that walk. What the walk returns is in memory too (see
|
||||||
|
//! `TypeCache::ret`).
|
||||||
//!
|
//!
|
||||||
//! Each local, arg and reg is a C local, of the C type of its type. A local or arg that a
|
//! Each local, arg and reg is a C local, of the C type of its type. A local or arg that a
|
||||||
//! closure shares is a pointer to a cell (see transform/closure.rs). The cell is made when the
|
//! closure shares is a pointer to a cell (see transform/closure.rs). The cell is made when the
|
||||||
@@ -20,16 +21,17 @@ use super::{
|
|||||||
values::truthy,
|
values::truthy,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
infer::Type,
|
infer::{Context, Type},
|
||||||
libqjs::parse::opcodes::OpCode,
|
libqjs::parse::opcodes::OpCode,
|
||||||
regify::{BasicBlock, Operand, RegFn},
|
regify::{BasicBlock, Operand, RegFn},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A JS function's walk, as its C function is written
|
/// A walk of a JS function, as its C function is written
|
||||||
pub struct JsFunction<'a> {
|
pub struct JsFunction<'a> {
|
||||||
pub program: &'a Program<'a>,
|
pub program: &'a Program<'a>,
|
||||||
pub body: Body<'a>,
|
pub body: Body<'a>,
|
||||||
pub walk: usize,
|
pub walk: usize,
|
||||||
|
pub context: Context, // the walk's function, and the call site that it is called from
|
||||||
pub f: &'a RegFn,
|
pub f: &'a RegFn,
|
||||||
shared: Vec<Operand>, // the locals and args that closures share: each is a pointer to a cell
|
shared: Vec<Operand>, // the locals and args that closures share: each is a pointer to a cell
|
||||||
}
|
}
|
||||||
@@ -37,8 +39,7 @@ pub struct JsFunction<'a> {
|
|||||||
/// `static <ret> js<walk>(<params>)`. A shared arg's param is named `p<k>`, because its value
|
/// `static <ret> js<walk>(<params>)`. A shared arg's param is named `p<k>`, because its value
|
||||||
/// goes into a cell named `a<k>`
|
/// goes into a cell named `a<k>`
|
||||||
pub fn signature(program: &Program, walk: usize) -> String {
|
pub fn signature(program: &Program, walk: usize) -> String {
|
||||||
let w = &program.cache.walks[walk];
|
let f = program.js_fn(walk);
|
||||||
let f = &program.fns[w.req.fn_idx];
|
|
||||||
let shared = f.captured_slots();
|
let shared = f.captured_slots();
|
||||||
|
|
||||||
let params: Vec<String> = (0..f.arg_count)
|
let params: Vec<String> = (0..f.arg_count)
|
||||||
@@ -58,19 +59,20 @@ pub fn signature(program: &Program, walk: usize) -> String {
|
|||||||
} else {
|
} else {
|
||||||
params.join(", ")
|
params.join(", ")
|
||||||
};
|
};
|
||||||
let ret = program.types.of(&w.ret);
|
let ret = program.types.of(program.cache.ret(walk));
|
||||||
format!("static {} js{walk}({params})", ret.c_name())
|
format!("static {} js{walk}({params})", ret.c_name())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The body of `js<walk>`: its locals, args and regs, then each block that can run
|
/// The body of `js<walk>`: its locals, args and regs, then each block that can run
|
||||||
pub fn body(program: &Program, walk: usize) -> String {
|
pub fn body(program: &Program, walk: usize) -> String {
|
||||||
let w = &program.cache.walks[walk];
|
let w = &program.cache.walks[walk];
|
||||||
let f = &program.fns[w.req.fn_idx];
|
let f = program.js_fn(walk);
|
||||||
|
|
||||||
let mut js = JsFunction {
|
let mut js = JsFunction {
|
||||||
program,
|
program,
|
||||||
body: Body::new(program),
|
body: Body::new(program),
|
||||||
walk,
|
walk,
|
||||||
|
context: w.context,
|
||||||
f,
|
f,
|
||||||
shared: f.captured_slots(),
|
shared: f.captured_slots(),
|
||||||
};
|
};
|
||||||
@@ -134,10 +136,9 @@ impl JsFunction<'_> {
|
|||||||
self.set(dst, value, CType::One(member));
|
self.set(dst, value, CType::One(member));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `value`, of C type `from`
|
/// Return `value`, of C type `from`, as the C type of what the walk returns
|
||||||
pub fn ret(&mut self, value: &str, from: CType) {
|
pub fn ret(&mut self, value: &str, from: CType) {
|
||||||
let walk = &self.program.cache.walks[self.walk];
|
let to = self.program.types.of(self.program.cache.ret(self.walk));
|
||||||
let to = self.program.types.of(&walk.ret);
|
|
||||||
self.body.put("return ", value, from, to);
|
self.body.put("return ", value, from, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ impl<'a> Program<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One C function for each walk of a JS function in infer's last round, one for each
|
/// One C function for each walk in infer's last round, one for each
|
||||||
/// lookup, and the host's two entry points
|
/// lookup, and the host's two entry points
|
||||||
fn add_functions(&mut self) {
|
fn add_functions(&mut self) {
|
||||||
let walks = (0..self.cache.walks.len()).filter(|&walk| self.cache.is_live(walk));
|
let walks = (0..self.cache.walks.len()).filter(|&walk| self.cache.is_live(walk));
|
||||||
@@ -142,7 +142,7 @@ impl<'a> Program<'a> {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let cpool = &self.fns[self.cache.walks[walk].req.fn_idx].cpool;
|
let cpool = &self.js_fn(walk).cpool;
|
||||||
|
|
||||||
for instr in self.instrs(walk) {
|
for instr in self.instrs(walk) {
|
||||||
let text = match instr.op {
|
let text = match instr.op {
|
||||||
@@ -183,9 +183,10 @@ impl<'a> Program<'a> {
|
|||||||
fn value_types(&self, function: &Function) -> Vec<Type> {
|
fn value_types(&self, function: &Function) -> Vec<Type> {
|
||||||
match *function {
|
match *function {
|
||||||
Function::Js(walk) => {
|
Function::Js(walk) => {
|
||||||
let f = &self.fns[self.cache.walks[walk].req.fn_idx];
|
let slots = slots(self.js_fn(walk))
|
||||||
let slots = slots(f).into_iter().map(|slot| self.slot_type(walk, slot));
|
.into_iter()
|
||||||
slots.chain([self.cache.walks[walk].ret.clone()]).collect()
|
.map(|slot| self.slot_type(walk, slot));
|
||||||
|
slots.chain([self.cache.ret(walk).clone()]).collect()
|
||||||
}
|
}
|
||||||
Function::Lookup(class, field) => vec![self.lookup_type(class, field)],
|
Function::Lookup(class, field) => vec![self.lookup_type(class, field)],
|
||||||
Function::Start | Function::Complete => vec![],
|
Function::Start | Function::Complete => vec![],
|
||||||
@@ -196,7 +197,7 @@ impl<'a> Program<'a> {
|
|||||||
fn instrs(&self, walk: usize) -> impl Iterator<Item = &'a RegInstr> + use<'a> {
|
fn instrs(&self, walk: usize) -> impl Iterator<Item = &'a RegInstr> + use<'a> {
|
||||||
let (cache, fns) = (self.cache, self.fns);
|
let (cache, fns) = (self.cache, self.fns);
|
||||||
let walk = &cache.walks[walk];
|
let walk = &cache.walks[walk];
|
||||||
let f = &fns[walk.req.fn_idx];
|
let f = &fns[walk.context.fn_idx];
|
||||||
let blocks = f.blocks.iter().zip(&walk.reachable);
|
let blocks = f.blocks.iter().zip(&walk.reachable);
|
||||||
let blocks = blocks.filter(|(_, reachable)| **reachable);
|
let blocks = blocks.filter(|(_, reachable)| **reachable);
|
||||||
blocks.flat_map(|(block, _)| &f.code[block.start..block.end])
|
blocks.flat_map(|(block, _)| &f.code[block.start..block.end])
|
||||||
@@ -214,7 +215,12 @@ pub struct Case {
|
|||||||
pub first: bool, // the first case of its operands: an `if`. The others are `else if`
|
pub first: bool, // the first case of its operands: an `if`. The others are `else if`
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Program<'_> {
|
impl<'a> Program<'a> {
|
||||||
|
/// The JS function of a walk
|
||||||
|
pub fn js_fn(&self, walk: usize) -> &'a RegFn {
|
||||||
|
&self.fns[self.cache.walks[walk].context.fn_idx]
|
||||||
|
}
|
||||||
|
|
||||||
/// The type of a local, arg or reg of a JS function, in one walk
|
/// The type of a local, arg or reg of a JS function, in one walk
|
||||||
pub fn slot_type(&self, walk: usize, slot: Operand) -> Type {
|
pub fn slot_type(&self, walk: usize, slot: Operand) -> Type {
|
||||||
let addr = self.cache.addr(walk, slot, self.fns);
|
let addr = self.cache.addr(walk, slot, self.fns);
|
||||||
|
|||||||
@@ -17,36 +17,39 @@ pub struct InstrRef {
|
|||||||
pub instr_idx: usize,
|
pub instr_idx: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The call instr that a request comes from. `None` for the entry fn, which the host calls
|
/// The call instr that a function is called from. `None` for a call from the host
|
||||||
pub type CallSite = Option<InstrRef>;
|
pub type CallSite = Option<InstrRef>;
|
||||||
|
|
||||||
/// The objects that one instr makes, in the requests from one call site
|
/// A function, in one call site. Without polymorphism, infer would walk each context once.
|
||||||
|
/// Objects, and the bindings that closures share, are named by their context, so that every
|
||||||
|
/// walk of a context sees the same ones (see `TypeCache`)
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct ObjAddr {
|
pub struct Context {
|
||||||
pub made_at: InstrRef,
|
pub fn_idx: usize,
|
||||||
pub in_call: CallSite,
|
pub call_site: CallSite,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A location in the program, which memory holds a type for. A location that an object
|
/// The objects that one instr makes, in one context
|
||||||
/// can hold is named like an object, by where it is made and the call site of the request.
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
/// One that only its walk can see is named by the walk
|
pub struct ObjAddr {
|
||||||
|
pub context: Context,
|
||||||
|
pub instr: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A location in the program, which memory holds a type for
|
||||||
///
|
///
|
||||||
/// A request key can hold an address, in a `Type::Ref`. Every part of an address comes from
|
/// Every location is named by its context and a place in its function, except the ones that
|
||||||
/// the program text (or the walks, which come from the keys), so there are finitely many
|
/// only one walk can see, which are named by the walk. A type can hold an object's address,
|
||||||
/// addresses and keys, and infer ends
|
/// and a walk's arg types can hold that type, so objects are never named by a walk: that
|
||||||
|
/// could make new walks without end. Every part of an address comes from the program text or
|
||||||
|
/// from a walk, so there are finitely many, and infer ends
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub enum Addr {
|
pub enum Addr {
|
||||||
Object(ObjAddr), // an object
|
Object(ObjAddr), // an object
|
||||||
Field(ObjAddr, AtomRef), // a field of an object, unless the field shares another location
|
Field(ObjAddr, AtomRef), // a field of an object, unless the field shares another location
|
||||||
Captured {
|
Shared { context: Context, slot: Operand }, // a binding that closures share (see transform/closure.rs). The closures are objects, so it is named like them
|
||||||
fn_idx: usize,
|
Private { walk: usize, slot: Operand }, // a binding that only its walk sees
|
||||||
slot: Operand,
|
Return(usize), // what a walk returns
|
||||||
in_call: CallSite,
|
|
||||||
}, // a binding that a closure shares (see transform/closure.rs)
|
|
||||||
Binding {
|
|
||||||
walk: usize,
|
|
||||||
slot: Operand,
|
|
||||||
}, // a binding that only its walk sees
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What each location holds, by its address. A location that nothing has reached holds
|
/// What each location holds, by its address. A location that nothing has reached holds
|
||||||
@@ -226,9 +229,9 @@ impl Memory {
|
|||||||
/// Make its field `name` the location `addr` (see `define_field_ref`). Such a field is
|
/// Make its field `name` the location `addr` (see `define_field_ref`). Such a field is
|
||||||
/// always made when the object is made, so it is defined
|
/// always made when the object is made, so it is defined
|
||||||
///
|
///
|
||||||
/// An address stands for all the objects that one instr makes in one call site. Two
|
/// An address stands for all the objects that one instr makes in one context. Two walks
|
||||||
/// walks from that call site can share two locations into the field: walks of one fn
|
/// of that context can share two locations into the field: walks with different
|
||||||
/// with different closures as their env, for example. A field is one location, so those
|
/// closures as their env, for example. A field is one location, so those
|
||||||
/// two are one location too: they are merged, as the types of all its objects' fields are
|
/// two are one location too: they are merged, as the types of all its objects' fields are
|
||||||
/// joined. Their C types are then one type as well
|
/// joined. Their C types are then one type as well
|
||||||
pub fn share_field(&mut self, obj: ObjAddr, name: AtomRef, addr: Addr) {
|
pub fn share_field(&mut self, obj: ObjAddr, name: AtomRef, addr: Addr) {
|
||||||
|
|||||||
+85
-89
@@ -1,11 +1,23 @@
|
|||||||
//! Infers a type for every location
|
//! Infers a type for every location in the program
|
||||||
//!
|
//!
|
||||||
//! A request is a fn, the call site that calls it, and the types of its args. Every location
|
//! **The base model.** A context is a function in one call site. Infer walks each context:
|
||||||
//! has an address (see `Addr`), and memory holds one type for each address. An object is
|
//! it goes over the function's instrs, and joins the type of each result into memory, which
|
||||||
//! named by the instr that makes it and the call site of the request that runs the instr. A
|
//! holds one type for each location (see `Addr`). An object is named by the instr that makes
|
||||||
//! binding is named by its walk, or like an object if a closure shares it. A walk reads
|
//! it and its context. A call walks the callee in its own context, and what the callee
|
||||||
//! memory and the returns of other requests, which only grow, so infer walks the program in
|
//! returns is a location too. A walk reads memory, and memory only grows, so infer walks the
|
||||||
//! rounds until one changes nothing
|
//! program in rounds, until a round changes nothing.
|
||||||
|
//!
|
||||||
|
//! **Polymorphism.** A function can be called with args of different types. Each set of arg
|
||||||
|
//! types gets a walk of its own, so a walk is a context and the types of its args. This adds
|
||||||
|
//! three things to the base model:
|
||||||
|
//!
|
||||||
|
//! 1. A walk's arg types are part of its key. An arg passes only its kind, not a known value,
|
||||||
|
//! so that there are finitely many keys (see `key`).
|
||||||
|
//! 2. A binding that no closure shares is named by its walk, so that each walk has its own
|
||||||
|
//! types (see `binding_addr`). Objects and shared bindings stay named by their context.
|
||||||
|
//! 3. A walk can go stale. When an arg's type grows, its call has a new key, so the call
|
||||||
|
//! makes a new walk. Only the walks of the last round are part of the program (see
|
||||||
|
//! `TypeCache::is_live`).
|
||||||
|
|
||||||
mod memory;
|
mod memory;
|
||||||
mod types;
|
mod types;
|
||||||
@@ -18,53 +30,40 @@ use crate::{
|
|||||||
regify::{Operand, RegFn},
|
regify::{Operand, RegFn},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use memory::{Addr, Builtins, CallSite, InstrRef, Memory, ObjAddr};
|
pub use memory::{Addr, Builtins, CallSite, Context, InstrRef, Memory, ObjAddr};
|
||||||
pub use types::{LENGTH_FIELD, Shape, Type, join};
|
pub use types::{LENGTH_FIELD, Shape, Type, join};
|
||||||
|
|
||||||
/// A fn, the call it comes from, and the values of its args. Values compare by their
|
/// A context, and the types of its args: what a walk walks. What the walk found is in memory
|
||||||
/// types, and an object by its address, so there are finitely many requests. The call site
|
|
||||||
/// is part of the key because it names the objects that the walk makes
|
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
|
||||||
pub struct InferRequest {
|
|
||||||
pub fn_idx: usize,
|
|
||||||
pub in_call: CallSite,
|
|
||||||
pub arg_types: Vec<Type>, // one for each param
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InferRequest {
|
|
||||||
/// The request of a call of `fn_idx` from `in_call` with args of these types. An arg it
|
|
||||||
/// does not pass is undefined, and an arg it passes beyond its params is never read.
|
|
||||||
/// Only an arg's kind crosses a call, not a known value, else every literal passed would
|
|
||||||
/// make a new walk
|
|
||||||
fn new(fn_idx: usize, in_call: CallSite, arg_types: &[Type], fns: &[RegFn]) -> Self {
|
|
||||||
let params = (0..fns[fn_idx].arg_count)
|
|
||||||
.map(|i| arg_types.get(i).map_or(Type::Undefined, Type::widen));
|
|
||||||
Self {
|
|
||||||
fn_idx,
|
|
||||||
in_call,
|
|
||||||
arg_types: params.collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A request and what its walks found. Its index in `TypeCache::walks` names its bindings,
|
|
||||||
/// whose types are in memory
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Walk {
|
pub struct Walk {
|
||||||
pub req: InferRequest,
|
pub context: Context,
|
||||||
pub ret: Type, // what it returns
|
pub arg_types: Vec<Type>, // one for each param
|
||||||
pub reachable: Vec<bool>, // the blocks that can run
|
pub reachable: Vec<bool>, // the blocks that can run
|
||||||
round: usize, // the last round that walked it. A walk that the last round did not reach is stale
|
round: usize, // the last round that walked it. A walk that the last round did not reach is stale
|
||||||
walking: bool, // it is on the call stack: a recursive call gives `ret` as it is
|
walking: bool, // it is on the call stack: a recursive call gives what it returned so far
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TypeCache {
|
pub struct TypeCache {
|
||||||
pub walks: Vec<Walk>,
|
pub walks: Vec<Walk>,
|
||||||
ids: HashMap<InferRequest, usize>, // the walk of each request
|
ids: HashMap<(Context, Vec<Type>), usize>, // the walk of each context and arg types
|
||||||
pub memory: Memory,
|
pub memory: Memory,
|
||||||
round: usize,
|
round: usize,
|
||||||
changes: usize, // new walks and growths of a return. With the changes of memory, these tell whether a round found anything new
|
}
|
||||||
|
|
||||||
|
/// The key of a call of `fn_idx` from `call_site` with args of these types. An arg that the
|
||||||
|
/// call does not pass is undefined, and an arg that it passes beyond the params is never
|
||||||
|
/// read. Only an arg's kind passes, not a known value, else every literal that a call passes
|
||||||
|
/// would make a new walk
|
||||||
|
fn key(
|
||||||
|
fn_idx: usize,
|
||||||
|
call_site: CallSite,
|
||||||
|
arg_types: &[Type],
|
||||||
|
fns: &[RegFn],
|
||||||
|
) -> (Context, Vec<Type>) {
|
||||||
|
let params =
|
||||||
|
(0..fns[fn_idx].arg_count).map(|i| arg_types.get(i).map_or(Type::Undefined, Type::widen));
|
||||||
|
(Context { fn_idx, call_site }, params.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypeCache {
|
impl TypeCache {
|
||||||
@@ -72,11 +71,11 @@ impl TypeCache {
|
|||||||
pub fn find(
|
pub fn find(
|
||||||
&self,
|
&self,
|
||||||
fn_idx: usize,
|
fn_idx: usize,
|
||||||
in_call: CallSite,
|
call_site: CallSite,
|
||||||
arg_types: &[Type],
|
arg_types: &[Type],
|
||||||
fns: &[RegFn],
|
fns: &[RegFn],
|
||||||
) -> usize {
|
) -> usize {
|
||||||
self.ids[&InferRequest::new(fn_idx, in_call, arg_types, fns)]
|
self.ids[&key(fn_idx, call_site, arg_types, fns)]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the last round walked `w`. Only such walks are part of the program
|
/// Whether the last round walked `w`. Only such walks are part of the program
|
||||||
@@ -84,15 +83,21 @@ impl TypeCache {
|
|||||||
self.walks[w].round == self.round
|
self.walks[w].round == self.round
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The address of a local, arg or reg of walk `w`
|
/// What walk `w` returns
|
||||||
pub fn addr(&self, w: usize, slot: Operand, fns: &[RegFn]) -> Addr {
|
pub fn ret(&self, w: usize) -> &Type {
|
||||||
let req = &self.walks[w].req;
|
self.memory.get(Addr::Return(w))
|
||||||
binding_addr(w, req, &fns[req.fn_idx].captured_slots(), slot)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Everything that can change, counted. Infer is done when a round leaves it the same
|
/// The address of a local, arg or reg of walk `w`
|
||||||
|
pub fn addr(&self, w: usize, slot: Operand, fns: &[RegFn]) -> Addr {
|
||||||
|
let context = self.walks[w].context;
|
||||||
|
binding_addr(w, context, &fns[context.fn_idx].captured_slots(), slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Everything that can change, counted: memory, and the walks. Infer is done when a round
|
||||||
|
/// leaves it the same
|
||||||
fn progress(&self) -> usize {
|
fn progress(&self) -> usize {
|
||||||
self.changes + self.memory.changes()
|
self.memory.changes() + self.walks.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The host's call of `__complete` with an op's handle: each closure that the binding can
|
/// The host's call of `__complete` with an op's handle: each closure that the binding can
|
||||||
@@ -112,66 +117,54 @@ impl TypeCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Infer a call of `fn_idx` from `in_call`, walking it unless this round walked it
|
/// Infer a call of `fn_idx` from `call_site`, walking it unless this round walked it
|
||||||
/// already. Gives what it returns
|
/// already. Gives what it returns
|
||||||
fn call(
|
fn call(
|
||||||
&mut self,
|
&mut self,
|
||||||
fn_idx: usize,
|
fn_idx: usize,
|
||||||
in_call: CallSite,
|
call_site: CallSite,
|
||||||
arg_types: &[Type],
|
arg_types: &[Type],
|
||||||
fns: &[RegFn],
|
fns: &[RegFn],
|
||||||
) -> Type {
|
) -> Type {
|
||||||
let req = InferRequest::new(fn_idx, in_call, arg_types, fns);
|
let (context, arg_types) = key(fn_idx, call_site, arg_types, fns);
|
||||||
|
|
||||||
let w = match self.ids.get(&req) {
|
let w = match self.ids.get(&(context, arg_types.clone())) {
|
||||||
Some(&w) => w,
|
Some(&w) => w,
|
||||||
None => {
|
None => {
|
||||||
self.ids.insert(req.clone(), self.walks.len());
|
self.ids
|
||||||
|
.insert((context, arg_types.clone()), self.walks.len());
|
||||||
self.walks.push(Walk {
|
self.walks.push(Walk {
|
||||||
req,
|
context,
|
||||||
ret: Type::Unknown,
|
arg_types,
|
||||||
reachable: vec![],
|
reachable: vec![],
|
||||||
round: 0,
|
round: 0,
|
||||||
walking: false,
|
walking: false,
|
||||||
});
|
});
|
||||||
self.changes += 1;
|
|
||||||
self.walks.len() - 1
|
self.walks.len() - 1
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.walks[w].walking || self.is_live(w) {
|
if !self.walks[w].walking && !self.is_live(w) {
|
||||||
return self.walks[w].ret.clone();
|
self.walks[w].walking = true;
|
||||||
|
let reachable = walk::walk_fn(w, fns, self);
|
||||||
|
|
||||||
|
let walk = &mut self.walks[w];
|
||||||
|
walk.walking = false;
|
||||||
|
walk.round = self.round;
|
||||||
|
walk.reachable = reachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.walks[w].walking = true;
|
self.ret(w).clone()
|
||||||
let (ret, reachable) = walk::walk_fn(w, fns, self);
|
|
||||||
|
|
||||||
let walk = &mut self.walks[w];
|
|
||||||
let joined = types::join(&walk.ret, &ret);
|
|
||||||
|
|
||||||
if joined != walk.ret {
|
|
||||||
walk.ret = joined;
|
|
||||||
self.changes += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
walk.walking = false;
|
|
||||||
walk.round = self.round;
|
|
||||||
walk.reachable = reachable;
|
|
||||||
walk.ret.clone()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The address of a local, arg or reg of walk `w` of `req`, given the slots of its fn that a
|
/// The address of a local, arg or reg of walk `w` of `context`, given the slots of its
|
||||||
/// closure shares. A binding that a closure shares is named like an object, any other by
|
/// function that closures share. A shared binding is named by its context, like the closures
|
||||||
/// the walk
|
/// that share it. Any other binding is named by its walk
|
||||||
fn binding_addr(w: usize, req: &InferRequest, captured: &[Operand], slot: Operand) -> Addr {
|
fn binding_addr(w: usize, context: Context, shared: &[Operand], slot: Operand) -> Addr {
|
||||||
match captured.contains(&slot) {
|
match shared.contains(&slot) {
|
||||||
true => Addr::Captured {
|
true => Addr::Shared { context, slot },
|
||||||
fn_idx: req.fn_idx,
|
false => Addr::Private { walk: w, slot },
|
||||||
slot,
|
|
||||||
in_call: req.in_call,
|
|
||||||
},
|
|
||||||
false => Addr::Binding { walk: w, slot },
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +182,6 @@ pub fn infer_types(fns: &[RegFn], bindings: &ModuleBindings) -> TypeCache {
|
|||||||
ids: HashMap::new(),
|
ids: HashMap::new(),
|
||||||
memory: Memory::default(),
|
memory: Memory::default(),
|
||||||
round: 0,
|
round: 0,
|
||||||
changes: 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
cache.memory.builtins = Builtins {
|
cache.memory.builtins = Builtins {
|
||||||
@@ -215,11 +207,15 @@ pub fn infer_types(fns: &[RegFn], bindings: &ModuleBindings) -> TypeCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The address of module binding `local` (see `flatten::ModuleBindings`): a local of the
|
/// The address of module binding `local` (see `flatten::ModuleBindings`): a local of the
|
||||||
/// entry, which the module fn captures
|
/// entry, which the module's function shares with its closures
|
||||||
pub fn module_binding(entry: usize, local: usize) -> Addr {
|
pub fn module_binding(entry: usize, local: usize) -> Addr {
|
||||||
Addr::Captured {
|
let context = Context {
|
||||||
fn_idx: entry,
|
fn_idx: entry,
|
||||||
|
call_site: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Addr::Shared {
|
||||||
|
context,
|
||||||
slot: Operand::Local(local),
|
slot: Operand::Local(local),
|
||||||
in_call: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-31
@@ -1,4 +1,5 @@
|
|||||||
//! One walk: the types that one request gives the locals, args and regs of its fn
|
//! One walk: the types that one context and its arg types give the locals, args and regs of
|
||||||
|
//! a function
|
||||||
//!
|
//!
|
||||||
//! The walk goes over the reachable blocks until nothing changes. Each instr has a rule,
|
//! The walk goes over the reachable blocks until nothing changes. Each instr has a rule,
|
||||||
//! which gives the type of its result from the types of its operands. The result joins into
|
//! which gives the type of its result from the types of its operands. The result joins into
|
||||||
@@ -6,8 +7,8 @@
|
|||||||
//! reached the instr yet
|
//! reached the instr yet
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
InferRequest, TypeCache, binding_addr,
|
TypeCache, binding_addr,
|
||||||
memory::{Addr, InstrRef, ObjAddr},
|
memory::{Addr, Context, InstrRef, ObjAddr},
|
||||||
types::{
|
types::{
|
||||||
BaseType, EMPTY_STRING, LENGTH_FIELD, Shape, Type, decide_pair, is_base, join, join_all,
|
BaseType, EMPTY_STRING, LENGTH_FIELD, Shape, Type, decide_pair, is_base, join, join_all,
|
||||||
loose_eq, pairs, strict_eq, truthiness,
|
loose_eq, pairs, strict_eq, truthiness,
|
||||||
@@ -20,19 +21,24 @@ use crate::{
|
|||||||
transform::{FIRST_ARG, THIS_ARG},
|
transform::{FIRST_ARG, THIS_ARG},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Walk `w`. Gives what it returns, and the blocks that can run
|
/// Walk `w`. Gives the blocks that can run
|
||||||
pub fn walk_fn(w: usize, fns: &[RegFn], cache: &mut TypeCache) -> (Type, Vec<bool>) {
|
pub fn walk_fn(w: usize, fns: &[RegFn], cache: &mut TypeCache) -> Vec<bool> {
|
||||||
let req = cache.walks[w].req.clone();
|
let context = cache.walks[w].context;
|
||||||
let f = &fns[req.fn_idx];
|
let f = &fns[context.fn_idx];
|
||||||
|
|
||||||
|
// The args get their types from the walk's key
|
||||||
|
for (i, ty) in cache.walks[w].arg_types.clone().iter().enumerate() {
|
||||||
|
let addr = binding_addr(w, context, &f.captured_slots(), Operand::Arg(i));
|
||||||
|
cache.memory.join(addr, ty);
|
||||||
|
}
|
||||||
|
|
||||||
let walker = Walker {
|
let walker = Walker {
|
||||||
w,
|
w,
|
||||||
|
context,
|
||||||
f,
|
f,
|
||||||
fns,
|
fns,
|
||||||
captured: f.captured_slots(),
|
shared: f.captured_slots(),
|
||||||
req,
|
|
||||||
cache,
|
cache,
|
||||||
ret: Type::Unknown,
|
|
||||||
reachable: vec![false; f.blocks.len()],
|
reachable: vec![false; f.blocks.len()],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -41,37 +47,28 @@ pub fn walk_fn(w: usize, fns: &[RegFn], cache: &mut TypeCache) -> (Type, Vec<boo
|
|||||||
|
|
||||||
struct Walker<'a> {
|
struct Walker<'a> {
|
||||||
w: usize,
|
w: usize,
|
||||||
req: InferRequest,
|
context: Context,
|
||||||
f: &'a RegFn,
|
f: &'a RegFn,
|
||||||
fns: &'a [RegFn],
|
fns: &'a [RegFn],
|
||||||
captured: Vec<Operand>, // the slots of `f` that a closure shares
|
shared: Vec<Operand>, // the slots of `f` that closures share
|
||||||
cache: &'a mut TypeCache,
|
cache: &'a mut TypeCache,
|
||||||
ret: Type,
|
|
||||||
reachable: Vec<bool>,
|
reachable: Vec<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Walker<'_> {
|
impl Walker<'_> {
|
||||||
fn addr(&self, slot: Operand) -> Addr {
|
fn addr(&self, slot: Operand) -> Addr {
|
||||||
binding_addr(self.w, &self.req, &self.captured, slot)
|
binding_addr(self.w, self.context, &self.shared, slot)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The address of the objects that the instr at `idx` makes in this walk
|
/// The address of the objects that the instr at `idx` makes, in this context
|
||||||
fn here(&self, idx: usize) -> ObjAddr {
|
fn here(&self, idx: usize) -> ObjAddr {
|
||||||
ObjAddr {
|
ObjAddr {
|
||||||
made_at: InstrRef {
|
context: self.context,
|
||||||
fn_idx: self.req.fn_idx,
|
instr: idx,
|
||||||
instr_idx: idx,
|
|
||||||
},
|
|
||||||
in_call: self.req.in_call,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(mut self) -> (Type, Vec<bool>) {
|
fn run(mut self) -> Vec<bool> {
|
||||||
// The args get their values from the request
|
|
||||||
for (i, ty) in self.req.arg_types.iter().enumerate() {
|
|
||||||
self.cache.memory.join(self.addr(Operand::Arg(i)), ty);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.reachable[0] = true;
|
self.reachable[0] = true;
|
||||||
|
|
||||||
// A return only changes once its block is reachable, and a read only gives more
|
// A return only changes once its block is reachable, and a read only gives more
|
||||||
@@ -87,7 +84,7 @@ impl Walker<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (self.cache.progress(), &self.reachable) == (before.0, &before.1) {
|
if (self.cache.progress(), &self.reachable) == (before.0, &before.1) {
|
||||||
return (self.ret, self.reachable);
|
return self.reachable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,7 +143,7 @@ impl Walker<'_> {
|
|||||||
let ty = self.rule(instr, idx, &args);
|
let ty = self.rule(instr, idx, &args);
|
||||||
|
|
||||||
if matches!(instr.op, Return | ReturnAsync | ReturnUndef) {
|
if matches!(instr.op, Return | ReturnAsync | ReturnUndef) {
|
||||||
self.ret = join(&self.ret, &ty);
|
self.cache.memory.join(Addr::Return(self.w), &ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
for &dst in &instr.dsts {
|
for &dst in &instr.dsts {
|
||||||
@@ -381,8 +378,8 @@ impl Walker<'_> {
|
|||||||
/// `new` gives what the constructor returns if that is an object, else the new object
|
/// `new` gives what the constructor returns if that is an object, else the new object
|
||||||
/// itself, `this`
|
/// itself, `this`
|
||||||
fn call(&mut self, instr: &RegInstr, idx: usize, args: &[Type]) -> Type {
|
fn call(&mut self, instr: &RegInstr, idx: usize, args: &[Type]) -> Type {
|
||||||
let in_call = Some(InstrRef {
|
let call_site = Some(InstrRef {
|
||||||
fn_idx: self.req.fn_idx,
|
fn_idx: self.context.fn_idx,
|
||||||
instr_idx: idx,
|
instr_idx: idx,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -402,7 +399,7 @@ impl Walker<'_> {
|
|||||||
|
|
||||||
let mut call_args = args.to_vec();
|
let mut call_args = args.to_vec();
|
||||||
call_args[0] = callee.clone();
|
call_args[0] = callee.clone();
|
||||||
self.cache.call(fn_idx, in_call, &call_args, self.fns)
|
self.cache.call(fn_idx, call_site, &call_args, self.fns)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
//! The owner's code does not change for this: `get_loc k` and `put_loc k` always use the
|
//! The owner's code does not change for this: `get_loc k` and `put_loc k` always use the
|
||||||
//! current location of `k`. This module keeps `close_loc` as it is. Then:
|
//! current location of `k`. This module keeps `close_loc` as it is. Then:
|
||||||
//! - infer can ignore it. The old and new locations of a binding have the same address
|
//! - infer can ignore it. The old and new locations of a binding have the same address
|
||||||
//! (`Addr::Captured`), so their types are joined
|
//! (`Addr::Shared`), so their types are joined
|
||||||
//! - `escape.rs` must give each iteration a new location if a closure can see the old one
|
//! - `escape.rs` must give each iteration a new location if a closure can see the old one
|
||||||
//! after the `close_loc`
|
//! after the `close_loc`
|
||||||
//!
|
//!
|
||||||
|
|||||||
Reference in New Issue
Block a user