Modified infer
This commit is contained in:
@@ -2,8 +2,9 @@
|
||||
//!
|
||||
//! There are four kinds:
|
||||
//!
|
||||
//! - **A JS function**, as infer walked it. Infer walks a JS function once for each set of
|
||||
//! arg types that it is called with, and each walk is one C function (see `js`).
|
||||
//! - **A JS function**, as infer walked it. Infer walks a JS function once for each context,
|
||||
//! 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
|
||||
//! 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.
|
||||
@@ -19,7 +20,7 @@ use crate::{infer::Type, libqjs::parse::AtomRef, regify::Operand, transform::PRO
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
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
|
||||
Start,
|
||||
Complete,
|
||||
@@ -171,7 +172,7 @@ fn complete_call(program: &Program) -> Option<String> {
|
||||
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 {
|
||||
let args: Vec<String> = (args.iter().enumerate())
|
||||
.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},
|
||||
};
|
||||
use crate::{
|
||||
infer::{InstrRef, ObjAddr, Type},
|
||||
infer::{CallSite, InstrRef, ObjAddr, Type},
|
||||
libqjs::parse::{ImmOperand, JsValue, opcodes::OpCode},
|
||||
native::NATIVES,
|
||||
regify::{Operand, RegInstr},
|
||||
@@ -82,19 +82,21 @@ impl JsFunction<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Instr `idx` of this JS function
|
||||
fn here(&self, idx: usize) -> InstrRef {
|
||||
InstrRef {
|
||||
fn_idx: self.program.cache.walks[self.walk].req.fn_idx,
|
||||
/// The call site of the call at instr `idx`
|
||||
fn call_site(&self, idx: usize) -> CallSite {
|
||||
Some(InstrRef {
|
||||
fn_idx: self.context.fn_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 {
|
||||
let in_call = self.program.cache.walks[self.walk].req.in_call;
|
||||
let made_at = self.here(idx);
|
||||
self.program.types.class(ObjAddr { made_at, in_call })
|
||||
let obj = ObjAddr {
|
||||
context: self.context,
|
||||
instr: idx,
|
||||
};
|
||||
self.program.types.class(obj)
|
||||
}
|
||||
|
||||
/// `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();
|
||||
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![];
|
||||
|
||||
for i in 0..program.fns[fn_idx].arg_count {
|
||||
@@ -481,7 +483,7 @@ impl JsFunction<'_> {
|
||||
}
|
||||
|
||||
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
|
||||
if *ret == Type::Unknown {
|
||||
|
||||
+13
-12
@@ -1,8 +1,9 @@
|
||||
//! 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
|
||||
//! walk is one C function, `js<walk>`, in which every value has the type that infer found in
|
||||
//! that walk.
|
||||
//! Infer walks a JS function once for each context, which is the function in one call site,
|
||||
//! and each set of arg types. Each walk is one C function, `js<walk>`, in which every value
|
||||
//! 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
|
||||
//! 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,
|
||||
};
|
||||
use crate::{
|
||||
infer::Type,
|
||||
infer::{Context, Type},
|
||||
libqjs::parse::opcodes::OpCode,
|
||||
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 program: &'a Program<'a>,
|
||||
pub body: Body<'a>,
|
||||
pub walk: usize,
|
||||
pub context: Context, // the walk's function, and the call site that it is called from
|
||||
pub f: &'a RegFn,
|
||||
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
|
||||
/// goes into a cell named `a<k>`
|
||||
pub fn signature(program: &Program, walk: usize) -> String {
|
||||
let w = &program.cache.walks[walk];
|
||||
let f = &program.fns[w.req.fn_idx];
|
||||
let f = program.js_fn(walk);
|
||||
let shared = f.captured_slots();
|
||||
|
||||
let params: Vec<String> = (0..f.arg_count)
|
||||
@@ -58,19 +59,20 @@ pub fn signature(program: &Program, walk: usize) -> String {
|
||||
} else {
|
||||
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())
|
||||
}
|
||||
|
||||
/// The body of `js<walk>`: its locals, args and regs, then each block that can run
|
||||
pub fn body(program: &Program, walk: usize) -> String {
|
||||
let w = &program.cache.walks[walk];
|
||||
let f = &program.fns[w.req.fn_idx];
|
||||
let f = program.js_fn(walk);
|
||||
|
||||
let mut js = JsFunction {
|
||||
program,
|
||||
body: Body::new(program),
|
||||
walk,
|
||||
context: w.context,
|
||||
f,
|
||||
shared: f.captured_slots(),
|
||||
};
|
||||
@@ -134,10 +136,9 @@ impl JsFunction<'_> {
|
||||
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) {
|
||||
let walk = &self.program.cache.walks[self.walk];
|
||||
let to = self.program.types.of(&walk.ret);
|
||||
let to = self.program.types.of(self.program.cache.ret(self.walk));
|
||||
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
|
||||
fn add_functions(&mut self) {
|
||||
let walks = (0..self.cache.walks.len()).filter(|&walk| self.cache.is_live(walk));
|
||||
@@ -142,7 +142,7 @@ impl<'a> Program<'a> {
|
||||
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) {
|
||||
let text = match instr.op {
|
||||
@@ -183,9 +183,10 @@ impl<'a> Program<'a> {
|
||||
fn value_types(&self, function: &Function) -> Vec<Type> {
|
||||
match *function {
|
||||
Function::Js(walk) => {
|
||||
let f = &self.fns[self.cache.walks[walk].req.fn_idx];
|
||||
let slots = slots(f).into_iter().map(|slot| self.slot_type(walk, slot));
|
||||
slots.chain([self.cache.walks[walk].ret.clone()]).collect()
|
||||
let slots = slots(self.js_fn(walk))
|
||||
.into_iter()
|
||||
.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::Start | Function::Complete => vec![],
|
||||
@@ -196,7 +197,7 @@ impl<'a> Program<'a> {
|
||||
fn instrs(&self, walk: usize) -> impl Iterator<Item = &'a RegInstr> + use<'a> {
|
||||
let (cache, fns) = (self.cache, self.fns);
|
||||
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 = blocks.filter(|(_, reachable)| **reachable);
|
||||
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`
|
||||
}
|
||||
|
||||
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
|
||||
pub fn slot_type(&self, walk: usize, slot: Operand) -> Type {
|
||||
let addr = self.cache.addr(walk, slot, self.fns);
|
||||
|
||||
@@ -17,36 +17,39 @@ pub struct InstrRef {
|
||||
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>;
|
||||
|
||||
/// 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)]
|
||||
pub struct ObjAddr {
|
||||
pub made_at: InstrRef,
|
||||
pub in_call: CallSite,
|
||||
pub struct Context {
|
||||
pub fn_idx: usize,
|
||||
pub call_site: CallSite,
|
||||
}
|
||||
|
||||
/// A location in the program, which memory holds a type for. A location that an object
|
||||
/// can hold is named like an object, by where it is made and the call site of the request.
|
||||
/// One that only its walk can see is named by the walk
|
||||
/// The objects that one instr makes, in one context
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
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
|
||||
/// the program text (or the walks, which come from the keys), so there are finitely many
|
||||
/// addresses and keys, and infer ends
|
||||
/// Every location is named by its context and a place in its function, except the ones that
|
||||
/// only one walk can see, which are named by the walk. A type can hold an object's address,
|
||||
/// 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)]
|
||||
pub enum Addr {
|
||||
Object(ObjAddr), // an object
|
||||
Field(ObjAddr, AtomRef), // a field of an object, unless the field shares another location
|
||||
Captured {
|
||||
fn_idx: usize,
|
||||
slot: Operand,
|
||||
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
|
||||
Shared { context: Context, slot: Operand }, // a binding that closures share (see transform/closure.rs). The closures are objects, so it is named like them
|
||||
Private { walk: usize, slot: Operand }, // a binding that only its walk sees
|
||||
Return(usize), // what a walk returns
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// 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
|
||||
/// walks from that call site can share two locations into the field: walks of one fn
|
||||
/// with different closures as their env, for example. A field is one location, so those
|
||||
/// An address stands for all the objects that one instr makes in one context. Two walks
|
||||
/// of that context can share two locations into the field: walks with different
|
||||
/// 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
|
||||
/// joined. Their C types are then one type as well
|
||||
pub fn share_field(&mut self, obj: ObjAddr, name: AtomRef, addr: Addr) {
|
||||
|
||||
+81
-85
@@ -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
|
||||
//! has an address (see `Addr`), and memory holds one type for each address. An object is
|
||||
//! named by the instr that makes it and the call site of the request that runs the instr. A
|
||||
//! binding is named by its walk, or like an object if a closure shares it. A walk reads
|
||||
//! memory and the returns of other requests, which only grow, so infer walks the program in
|
||||
//! rounds until one changes nothing
|
||||
//! **The base model.** A context is a function in one call site. Infer walks each context:
|
||||
//! it goes over the function's instrs, and joins the type of each result into memory, which
|
||||
//! holds one type for each location (see `Addr`). An object is named by the instr that makes
|
||||
//! it and its context. A call walks the callee in its own context, and what the callee
|
||||
//! returns is a location too. A walk reads memory, and memory only grows, so infer walks the
|
||||
//! 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 types;
|
||||
@@ -18,53 +30,40 @@ use crate::{
|
||||
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};
|
||||
|
||||
/// A fn, the call it comes from, and the values of its args. Values compare by their
|
||||
/// 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
|
||||
/// A context, and the types of its args: what a walk walks. What the walk found is in memory
|
||||
#[derive(Debug)]
|
||||
pub struct Walk {
|
||||
pub req: InferRequest,
|
||||
pub ret: Type, // what it returns
|
||||
pub context: Context,
|
||||
pub arg_types: Vec<Type>, // one for each param
|
||||
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
|
||||
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)]
|
||||
pub struct TypeCache {
|
||||
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,
|
||||
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 {
|
||||
@@ -72,11 +71,11 @@ impl TypeCache {
|
||||
pub fn find(
|
||||
&self,
|
||||
fn_idx: usize,
|
||||
in_call: CallSite,
|
||||
call_site: CallSite,
|
||||
arg_types: &[Type],
|
||||
fns: &[RegFn],
|
||||
) -> 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
|
||||
@@ -84,15 +83,21 @@ impl TypeCache {
|
||||
self.walks[w].round == self.round
|
||||
}
|
||||
|
||||
/// The address of a local, arg or reg of walk `w`
|
||||
pub fn addr(&self, w: usize, slot: Operand, fns: &[RegFn]) -> Addr {
|
||||
let req = &self.walks[w].req;
|
||||
binding_addr(w, req, &fns[req.fn_idx].captured_slots(), slot)
|
||||
/// What walk `w` returns
|
||||
pub fn ret(&self, w: usize) -> &Type {
|
||||
self.memory.get(Addr::Return(w))
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
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
|
||||
@@ -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
|
||||
fn call(
|
||||
&mut self,
|
||||
fn_idx: usize,
|
||||
in_call: CallSite,
|
||||
call_site: CallSite,
|
||||
arg_types: &[Type],
|
||||
fns: &[RegFn],
|
||||
) -> 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,
|
||||
None => {
|
||||
self.ids.insert(req.clone(), self.walks.len());
|
||||
self.ids
|
||||
.insert((context, arg_types.clone()), self.walks.len());
|
||||
self.walks.push(Walk {
|
||||
req,
|
||||
ret: Type::Unknown,
|
||||
context,
|
||||
arg_types,
|
||||
reachable: vec![],
|
||||
round: 0,
|
||||
walking: false,
|
||||
});
|
||||
self.changes += 1;
|
||||
self.walks.len() - 1
|
||||
}
|
||||
};
|
||||
|
||||
if self.walks[w].walking || self.is_live(w) {
|
||||
return self.walks[w].ret.clone();
|
||||
}
|
||||
|
||||
if !self.walks[w].walking && !self.is_live(w) {
|
||||
self.walks[w].walking = true;
|
||||
let (ret, reachable) = walk::walk_fn(w, fns, self);
|
||||
let 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()
|
||||
}
|
||||
|
||||
self.ret(w).clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// The address of a local, arg or reg of walk `w` of `req`, given the slots of its fn that a
|
||||
/// closure shares. A binding that a closure shares is named like an object, any other by
|
||||
/// the walk
|
||||
fn binding_addr(w: usize, req: &InferRequest, captured: &[Operand], slot: Operand) -> Addr {
|
||||
match captured.contains(&slot) {
|
||||
true => Addr::Captured {
|
||||
fn_idx: req.fn_idx,
|
||||
slot,
|
||||
in_call: req.in_call,
|
||||
},
|
||||
false => Addr::Binding { walk: w, slot },
|
||||
/// The address of a local, arg or reg of walk `w` of `context`, given the slots of its
|
||||
/// function that closures share. A shared binding is named by its context, like the closures
|
||||
/// that share it. Any other binding is named by its walk
|
||||
fn binding_addr(w: usize, context: Context, shared: &[Operand], slot: Operand) -> Addr {
|
||||
match shared.contains(&slot) {
|
||||
true => Addr::Shared { context, slot },
|
||||
false => Addr::Private { walk: w, slot },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +182,6 @@ pub fn infer_types(fns: &[RegFn], bindings: &ModuleBindings) -> TypeCache {
|
||||
ids: HashMap::new(),
|
||||
memory: Memory::default(),
|
||||
round: 0,
|
||||
changes: 0,
|
||||
};
|
||||
|
||||
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
|
||||
/// 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 {
|
||||
Addr::Captured {
|
||||
let context = Context {
|
||||
fn_idx: entry,
|
||||
call_site: None,
|
||||
};
|
||||
|
||||
Addr::Shared {
|
||||
context,
|
||||
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,
|
||||
//! 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
|
||||
|
||||
use super::{
|
||||
InferRequest, TypeCache, binding_addr,
|
||||
memory::{Addr, InstrRef, ObjAddr},
|
||||
TypeCache, binding_addr,
|
||||
memory::{Addr, Context, InstrRef, ObjAddr},
|
||||
types::{
|
||||
BaseType, EMPTY_STRING, LENGTH_FIELD, Shape, Type, decide_pair, is_base, join, join_all,
|
||||
loose_eq, pairs, strict_eq, truthiness,
|
||||
@@ -20,19 +21,24 @@ use crate::{
|
||||
transform::{FIRST_ARG, THIS_ARG},
|
||||
};
|
||||
|
||||
/// Walk `w`. Gives what it returns, and the blocks that can run
|
||||
pub fn walk_fn(w: usize, fns: &[RegFn], cache: &mut TypeCache) -> (Type, Vec<bool>) {
|
||||
let req = cache.walks[w].req.clone();
|
||||
let f = &fns[req.fn_idx];
|
||||
/// Walk `w`. Gives the blocks that can run
|
||||
pub fn walk_fn(w: usize, fns: &[RegFn], cache: &mut TypeCache) -> Vec<bool> {
|
||||
let context = cache.walks[w].context;
|
||||
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 {
|
||||
w,
|
||||
context,
|
||||
f,
|
||||
fns,
|
||||
captured: f.captured_slots(),
|
||||
req,
|
||||
shared: f.captured_slots(),
|
||||
cache,
|
||||
ret: Type::Unknown,
|
||||
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> {
|
||||
w: usize,
|
||||
req: InferRequest,
|
||||
context: Context,
|
||||
f: &'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,
|
||||
ret: Type,
|
||||
reachable: Vec<bool>,
|
||||
}
|
||||
|
||||
impl Walker<'_> {
|
||||
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 {
|
||||
ObjAddr {
|
||||
made_at: InstrRef {
|
||||
fn_idx: self.req.fn_idx,
|
||||
instr_idx: idx,
|
||||
},
|
||||
in_call: self.req.in_call,
|
||||
context: self.context,
|
||||
instr: idx,
|
||||
}
|
||||
}
|
||||
|
||||
fn run(mut self) -> (Type, 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);
|
||||
}
|
||||
|
||||
fn run(mut self) -> Vec<bool> {
|
||||
self.reachable[0] = true;
|
||||
|
||||
// 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) {
|
||||
return (self.ret, self.reachable);
|
||||
return self.reachable;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,7 +143,7 @@ impl Walker<'_> {
|
||||
let ty = self.rule(instr, idx, &args);
|
||||
|
||||
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 {
|
||||
@@ -381,8 +378,8 @@ impl Walker<'_> {
|
||||
/// `new` gives what the constructor returns if that is an object, else the new object
|
||||
/// itself, `this`
|
||||
fn call(&mut self, instr: &RegInstr, idx: usize, args: &[Type]) -> Type {
|
||||
let in_call = Some(InstrRef {
|
||||
fn_idx: self.req.fn_idx,
|
||||
let call_site = Some(InstrRef {
|
||||
fn_idx: self.context.fn_idx,
|
||||
instr_idx: idx,
|
||||
});
|
||||
|
||||
@@ -402,7 +399,7 @@ impl Walker<'_> {
|
||||
|
||||
let mut call_args = args.to_vec();
|
||||
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();
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
//! 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:
|
||||
//! - 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
|
||||
//! after the `close_loc`
|
||||
//!
|
||||
|
||||
Reference in New Issue
Block a user