New partly working cell model
This commit is contained in:
@@ -1,63 +0,0 @@
|
||||
//! Escape (see `docs/model.md`, section 6)
|
||||
|
||||
use super::{Flow, instrs};
|
||||
use crate::{libqjs::parse::opcodes::OpCode::*, regify::RegFn};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Escape {
|
||||
/// No holder outlives the frame, so the cell can be on the stack
|
||||
Local,
|
||||
/// The cell must be on the heap. `docs/model.md` also has `returned`, which
|
||||
/// this analysis does not prove yet, so such a cell escapes
|
||||
Escapes,
|
||||
}
|
||||
|
||||
/// The escape of each value that `object`, `fclosure` or `array_from` gives. Every other
|
||||
/// value gives `Escapes`, which nothing reads
|
||||
pub fn analyze(f: &RegFn, flow: &mut Flow) -> Vec<Escape> {
|
||||
let mut escape = vec![Escape::Escapes; f.val_count];
|
||||
|
||||
for (block, instr) in instrs(f) {
|
||||
if matches!(instr.op, Object | Fclosure | ArrayFrom) {
|
||||
let cell = instr.dsts[0];
|
||||
flow.new_walk();
|
||||
|
||||
if !escapes(flow, cell, flow.in_loop[block]) {
|
||||
escape[cell] = Escape::Local;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
escape
|
||||
}
|
||||
|
||||
/// Whether the cell that `v` holds can be reached after the frame returns, or
|
||||
/// when its creating op runs again (`looped`: the op is in a loop). A var or a
|
||||
/// join slot can keep the cell until the next run, so in a loop any copy
|
||||
/// escapes. A field, an element, a callee and a return can keep it beyond the
|
||||
/// frame
|
||||
fn escapes(flow: &mut Flow, v: usize, looped: bool) -> bool {
|
||||
flow.visit(v);
|
||||
|
||||
for i in flow.use_range(v) {
|
||||
let u = flow.uses[i];
|
||||
|
||||
let out = match u.op {
|
||||
Call | CallMethod | CallConstructor | Return | ReturnAsync => true,
|
||||
DefineField | PutField | PutArrayEl if u.operand == 0 => true,
|
||||
ArrayFrom => true,
|
||||
SetLoc | SetName if looped => true,
|
||||
SetLoc | SetName => {
|
||||
let dst = u.dst.unwrap() as usize;
|
||||
flow.visit(dst) && escapes(flow, dst, looped)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if out {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
//! The analyses that narrow the representation (see `docs/model.md`, section 6).
|
||||
//! They run after infer, on each fn's code
|
||||
|
||||
mod escape;
|
||||
|
||||
use crate::{
|
||||
libqjs::parse::opcodes::OpCode,
|
||||
regify::{RegFn, RegInstr},
|
||||
};
|
||||
|
||||
pub use escape::Escape;
|
||||
|
||||
pub struct Analysis {
|
||||
/// Where the cell can be reached, for each value that a creating op gives
|
||||
pub escape: Vec<Escape>,
|
||||
}
|
||||
|
||||
impl Analysis {
|
||||
fn of(f: &RegFn) -> Self {
|
||||
let mut flow = Flow::of(f);
|
||||
|
||||
Self {
|
||||
escape: escape::analyze(f, &mut flow),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn analyze(fns: &[RegFn]) -> Vec<Analysis> {
|
||||
fns.iter().map(Analysis::of).collect()
|
||||
}
|
||||
|
||||
/// One use of a value: an operand of an instr
|
||||
#[derive(Clone, Copy)]
|
||||
struct Use {
|
||||
op: OpCode,
|
||||
/// Which operand the value is
|
||||
operand: u32,
|
||||
/// The instr's first dst, if it has one
|
||||
dst: Option<u32>,
|
||||
}
|
||||
|
||||
/// What the analyses need about a fn: the uses of each value, in one array
|
||||
/// sorted by value (see `ranges()`), and which blocks are in a loop
|
||||
struct Flow {
|
||||
uses: Vec<Use>,
|
||||
use_starts: Vec<u32>,
|
||||
/// Whether a path leads from each block back to it
|
||||
in_loop: Vec<bool>,
|
||||
/// Scratch for walks over values and over blocks. A mark is current if it
|
||||
/// equals its walk's stamp, so the arrays are never cleared
|
||||
val_stamp: u32,
|
||||
seen_vals: Vec<u32>,
|
||||
block_stamp: u32,
|
||||
seen_blocks: Vec<u32>,
|
||||
stack: Vec<u32>,
|
||||
}
|
||||
|
||||
impl Flow {
|
||||
fn of(f: &RegFn) -> Self {
|
||||
let uses = instrs(f).flat_map(|(_, instr)| {
|
||||
(instr.args.iter().enumerate()).map(move |(operand, &v)| {
|
||||
let dst = instr.dsts.first().map(|&d| d as u32);
|
||||
(v, Use { op: instr.op, operand: operand as u32, dst })
|
||||
})
|
||||
});
|
||||
|
||||
let (uses, use_starts) = ranges(f.val_count, uses);
|
||||
|
||||
let mut flow = Self {
|
||||
uses,
|
||||
use_starts,
|
||||
in_loop: vec![],
|
||||
val_stamp: 0,
|
||||
seen_vals: vec![0; f.val_count],
|
||||
block_stamp: 0,
|
||||
seen_blocks: vec![0; f.blocks.len()],
|
||||
stack: vec![],
|
||||
};
|
||||
|
||||
flow.in_loop = (0..f.blocks.len()).map(|b| flow.returns_to(f, b)).collect();
|
||||
flow
|
||||
}
|
||||
|
||||
/// The uses of value `v`, by index into `uses`
|
||||
fn use_range(&self, v: usize) -> std::ops::Range<usize> {
|
||||
self.use_starts[v] as usize..self.use_starts[v + 1] as usize
|
||||
}
|
||||
|
||||
/// Start a new walk over values: every mark of the walks before is now
|
||||
/// stale
|
||||
fn new_walk(&mut self) {
|
||||
self.val_stamp += 1;
|
||||
}
|
||||
|
||||
/// Mark value `v` seen in this walk. Whether it was not seen before
|
||||
fn visit(&mut self, v: usize) -> bool {
|
||||
let fresh = self.seen_vals[v] != self.val_stamp;
|
||||
self.seen_vals[v] = self.val_stamp;
|
||||
fresh
|
||||
}
|
||||
|
||||
/// Whether a path leads from `block` back to it
|
||||
fn returns_to(&mut self, f: &RegFn, block: usize) -> bool {
|
||||
self.block_stamp += 1;
|
||||
let stamp = self.block_stamp;
|
||||
|
||||
self.stack.clear();
|
||||
self.stack.extend(f.blocks[block].succ.iter().map(|&s| s as u32));
|
||||
|
||||
while let Some(b) = self.stack.pop() {
|
||||
let b = b as usize;
|
||||
|
||||
if self.seen_blocks[b] == stamp {
|
||||
continue;
|
||||
}
|
||||
|
||||
if b == block {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.seen_blocks[b] = stamp;
|
||||
self.stack.extend(f.blocks[b].succ.iter().map(|&s| s as u32));
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// The `(value, item)` pairs, as one array grouped by value: the items of
|
||||
/// value `v` are `items[starts[v]..starts[v + 1]]`
|
||||
fn ranges<T>(n: usize, pairs: impl Iterator<Item = (usize, T)>) -> (Vec<T>, Vec<u32>) {
|
||||
let mut pairs: Vec<(usize, T)> = pairs.collect();
|
||||
pairs.sort_by_key(|(v, _)| *v);
|
||||
|
||||
let mut starts = vec![0u32; n + 1];
|
||||
|
||||
for (v, _) in &pairs {
|
||||
starts[v + 1] += 1;
|
||||
}
|
||||
|
||||
for v in 0..n {
|
||||
starts[v + 1] += starts[v];
|
||||
}
|
||||
|
||||
(pairs.into_iter().map(|(_, item)| item).collect(), starts)
|
||||
}
|
||||
|
||||
/// Every instr of `f`, with its block: a block's own instrs, then its postfix
|
||||
/// copies
|
||||
fn instrs(f: &RegFn) -> impl Iterator<Item = (usize, &RegInstr)> {
|
||||
f.blocks.iter().enumerate().flat_map(move |(b, block)| {
|
||||
let code = f.code[block.start..block.end].iter();
|
||||
code.chain(&block.postfix).map(move |instr| (b, instr))
|
||||
})
|
||||
}
|
||||
+258
-538
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,256 @@
|
||||
//! Cells: the types of objects, which every holder of an object shares (see
|
||||
//! `docs/model.md`, section 3)
|
||||
|
||||
use std::{
|
||||
cell::{Ref, RefCell},
|
||||
fmt,
|
||||
hash::{Hash, Hasher},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
libqjs::parse::AtomRef,
|
||||
native::ProtoOf,
|
||||
transform::{BOX_FIELD, PROTO_FIELD},
|
||||
};
|
||||
|
||||
use super::types::{Type, join};
|
||||
|
||||
pub const LENGTH_FIELD: AtomRef = AtomRef::Predefined(51); // length
|
||||
|
||||
/// The prototype of each `ProtoOf`, as the stdlib links it
|
||||
pub type Protos = [Type; ProtoOf::ALL.len()];
|
||||
|
||||
/// The code a fn object runs
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Code {
|
||||
Fn(usize),
|
||||
Native(usize),
|
||||
ProtoLink(ProtoOf),
|
||||
}
|
||||
|
||||
/// What the objects of a cell are, besides objects with named fields
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ObjKind {
|
||||
Plain,
|
||||
Fn(Code), // a fn, which runs the code
|
||||
Array { elems: Type }, // an array, whose elements are the values written to them, or holes
|
||||
}
|
||||
|
||||
/// A field of the objects of a cell
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Field {
|
||||
pub ty: Type,
|
||||
pub defined: bool, // every object of the cell has it from the start (see `write()`)
|
||||
}
|
||||
|
||||
/// What a cell knows about its objects
|
||||
#[derive(Debug)]
|
||||
pub struct CellInfo {
|
||||
pub kind: ObjKind,
|
||||
pub fields: Vec<(AtomRef, Field)>, // sorted by name
|
||||
pub made: Option<(usize, usize)>, // the walk and the instr that made it. `None` for a built-in
|
||||
}
|
||||
|
||||
/// The type of an object. Every copy of an `Obj` type holds the same cell, so
|
||||
/// a write through one holder is seen by all. A cell is its `Rc`: two cells
|
||||
/// are equal only if they are one cell, and they are ordered by address
|
||||
#[derive(Clone)]
|
||||
pub struct Cell(Rc<RefCell<CellInfo>>);
|
||||
|
||||
impl Cell {
|
||||
pub fn new(kind: ObjKind, made: Option<(usize, usize)>) -> Self {
|
||||
Cell(Rc::new(RefCell::new(CellInfo { kind, fields: vec![], made })))
|
||||
}
|
||||
|
||||
pub fn info(&self) -> Ref<'_, CellInfo> {
|
||||
self.0.borrow()
|
||||
}
|
||||
|
||||
/// The address of the cell, which is its identity
|
||||
fn addr(&self) -> usize {
|
||||
Rc::as_ptr(&self.0) as usize
|
||||
}
|
||||
|
||||
/// The code that its objects run, if they are fns
|
||||
pub fn code(&self) -> Option<Code> {
|
||||
match self.info().kind {
|
||||
ObjKind::Fn(code) => Some(code),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_array(&self) -> bool {
|
||||
matches!(self.info().kind, ObjKind::Array { .. })
|
||||
}
|
||||
|
||||
/// The type of its elements, for an array
|
||||
pub fn elems(&self) -> Type {
|
||||
match &self.info().kind {
|
||||
ObjKind::Array { elems } => elems.clone(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn field(&self, name: AtomRef) -> Option<Field> {
|
||||
let info = self.info();
|
||||
let i = info.fields.binary_search_by_key(&name, |(n, _)| *n).ok()?;
|
||||
Some(info.fields[i].1.clone())
|
||||
}
|
||||
|
||||
/// Whether one of its objects can lack its own `field`. A box's value
|
||||
/// never can: a read of a binding before its first write throws (its
|
||||
/// TDZ), so a read that gives a value comes after a write (see transform)
|
||||
pub fn can_be_absent(&self, field: AtomRef) -> bool {
|
||||
field != BOX_FIELD && !self.field(field).is_some_and(|f| f.defined)
|
||||
}
|
||||
|
||||
/// Join `ty` into its `field`. A field is defined if a `define_field`
|
||||
/// makes it: that is a write into an object before it goes anywhere (see
|
||||
/// regify). A field that another write makes can be absent. An array's
|
||||
/// `length` is not a field: a write of it sets the array's length.
|
||||
/// Whether the cell changed
|
||||
pub fn write(&self, field: AtomRef, ty: &Type, defined: bool) -> bool {
|
||||
if field == LENGTH_FIELD && self.is_array() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The join is done before the borrow: `ty` can hold this cell
|
||||
let old = self.field(field);
|
||||
let new = join(old.as_ref().map_or(&Type::Unknown, |f| &f.ty), ty);
|
||||
let fields = &mut self.0.borrow_mut().fields;
|
||||
|
||||
match fields.binary_search_by_key(&field, |(n, _)| *n) {
|
||||
Ok(i) if fields[i].1.ty == new => false,
|
||||
Ok(i) => {
|
||||
fields[i].1.ty = new;
|
||||
true
|
||||
}
|
||||
Err(i) => {
|
||||
fields.insert(i, (field, Field { ty: new, defined }));
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Join `ty` into the type of its elements. Whether the cell changed
|
||||
pub fn write_elem(&self, ty: &Type) -> bool {
|
||||
let new = join(&self.elems(), ty);
|
||||
|
||||
match &mut self.0.borrow_mut().kind {
|
||||
ObjKind::Array { elems } if *elems != new => {
|
||||
*elems = new;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// A read of an element of one of its arrays. An index can be a hole, or
|
||||
/// past the end, which gives undefined
|
||||
pub fn read_elem(&self) -> Type {
|
||||
join(&self.elems(), &Type::Undefined)
|
||||
}
|
||||
|
||||
/// A read of `field` on one of its objects: its own field, then, where
|
||||
/// that can be absent, the read on its prototype. At the end of the
|
||||
/// chain, an absent field is undefined. An array's prototype is the one
|
||||
/// that the stdlib links for arrays, in `protos`, and its `length` is not a
|
||||
/// field
|
||||
pub fn read(&self, field: AtomRef, protos: &Protos) -> Type {
|
||||
self.read_chain(field, protos, &mut vec![])
|
||||
}
|
||||
|
||||
/// `read()`, on a chain that came through the cells `chain`. Objects of
|
||||
/// one cell can be each other's prototypes, so a chain of cells can come
|
||||
/// back to a cell, which a chain of objects cannot. The read on that cell
|
||||
/// is already in the chain, so it gives nothing more
|
||||
fn read_chain(&self, field: AtomRef, protos: &Protos, chain: &mut Vec<Cell>) -> Type {
|
||||
let array = self.is_array();
|
||||
|
||||
if array && field == LENGTH_FIELD {
|
||||
return Type::Int;
|
||||
}
|
||||
|
||||
let own = self.field(field).map_or(Type::Unknown, |f| f.ty);
|
||||
|
||||
if !self.can_be_absent(field) || chain.contains(self) {
|
||||
return own;
|
||||
}
|
||||
|
||||
// The chain ends at a prototype that is absent or not an object
|
||||
let (proto, proto_absent) = match array {
|
||||
true => (protos[ProtoOf::Array as usize].clone(), false),
|
||||
false => (self.field(PROTO_FIELD).map_or(Type::Unknown, |f| f.ty), self.can_be_absent(PROTO_FIELD)),
|
||||
};
|
||||
let mut ty = own;
|
||||
|
||||
if proto_absent || proto == Type::Unknown {
|
||||
ty = join(&ty, &Type::Undefined);
|
||||
}
|
||||
|
||||
chain.push(self.clone());
|
||||
|
||||
for member in proto.members() {
|
||||
let read = match member {
|
||||
Type::Obj(proto) => proto.read_chain(field, protos, chain),
|
||||
_ => Type::Undefined,
|
||||
};
|
||||
|
||||
ty = join(&ty, &read);
|
||||
}
|
||||
|
||||
chain.pop();
|
||||
ty
|
||||
}
|
||||
}
|
||||
|
||||
/// A read of `field` on a value of type `ty`. A primitive reads its kind's
|
||||
/// prototype in `protos`. Undefined and null have no fields: the read throws,
|
||||
/// so it gives nothing. A sum reads on each of its members
|
||||
pub fn read_value(ty: &Type, field: AtomRef, protos: &Protos) -> Type {
|
||||
match ty {
|
||||
Type::Obj(cell) => cell.read(field, protos),
|
||||
Type::Sum(members) => {
|
||||
let reads = members.iter().map(|member| read_value(member, field, protos));
|
||||
reads.fold(Type::Unknown, |all, read| join(&all, &read))
|
||||
}
|
||||
ty => match ProtoOf::of(ty).map(|kind| &protos[kind as usize]) {
|
||||
Some(Type::Unknown) | None => Type::Unknown,
|
||||
Some(proto) => read_value(proto, field, protos),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Cell {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.addr() == other.addr()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Cell {}
|
||||
|
||||
impl PartialOrd for Cell {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Cell {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.addr().cmp(&other.addr())
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Cell {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.addr().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// A cell prints as its address only: its fields can hold the cell itself
|
||||
impl fmt::Debug for Cell {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "cell {:#x}", self.addr())
|
||||
}
|
||||
}
|
||||
+279
-299
@@ -1,165 +1,188 @@
|
||||
//! Infers a type for every value (see `docs/model.md`, section 3)
|
||||
//! Infers a type for every value (see `docs/model.md`, section 4)
|
||||
|
||||
pub mod sites;
|
||||
pub mod cells;
|
||||
pub mod types;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
libqjs::parse::{AtomRef, JsValue, opcodes::OpCode},
|
||||
native::{self, ProtoOf},
|
||||
native::{self, NATIVES, ProtoOf},
|
||||
regify::RegFn,
|
||||
transform::{FIRST_ARG, THIS_ARG},
|
||||
};
|
||||
|
||||
use sites::{Code, CtxId, LENGTH_FIELD, Place, Protos, SiteId, SiteTable};
|
||||
use types::{BaseType, EMPTY_STRING, Type, is_function, loose_eq, strict_eq, test, truthiness, typeof_name};
|
||||
use cells::{Cell, Code, LENGTH_FIELD, ObjKind, Protos, read_value};
|
||||
use types::{BaseType, EMPTY_STRING, Type, is_function, join, loose_eq, strict_eq, test, truthiness, typeof_name};
|
||||
|
||||
|
||||
/// One walk of a fn with one tuple of arg types (see `docs/model.md`,
|
||||
/// section 3.5)
|
||||
/// The walk of one fn with one list of arg types
|
||||
#[derive(Debug)]
|
||||
pub struct Variant {
|
||||
pub struct Walk {
|
||||
pub fn_id: usize,
|
||||
/// One for each of the fn's params (see `TypeCache::variant()`)
|
||||
pub arg_types: Vec<Type>,
|
||||
/// The context of the sites that it makes (see `SiteTable::context()`)
|
||||
pub ctx: CtxId,
|
||||
/// The walk in the current round, once it is done
|
||||
pub result: Option<InferResult>,
|
||||
/// What it returned in the rounds so far, which is what a call of it gives
|
||||
/// while it is still being walked (a recursive call)
|
||||
ret: Type,
|
||||
pub arg_types: Vec<Type>, // one for each param
|
||||
cells: Vec<(usize, Cell)>, // the cells it made, by the instr that makes their objects
|
||||
ret: Type, // what it returned in the rounds so far: a recursive call gives that
|
||||
walking: bool,
|
||||
pub result: Option<InferResult>, // the walk in the current round, once it is done
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InferResult {
|
||||
pub val_types: Vec<Type>,
|
||||
/// The type each instr gives, before it is joined into its dst
|
||||
pub instr_types: Vec<Type>,
|
||||
pub instr_types: Vec<Type>, // the type each instr gives, before it is joined into its dst
|
||||
pub ret_type: Type,
|
||||
/// The blocks that can run
|
||||
pub reachable: Vec<bool>,
|
||||
pub reachable: Vec<bool>, // the blocks that can run
|
||||
}
|
||||
|
||||
/// The walks of the program, by their keys
|
||||
#[derive(Debug)]
|
||||
pub struct TypeCache {
|
||||
pub variants: Vec<Variant>,
|
||||
/// Each fn's variants, by their arg types
|
||||
by_args: Vec<HashMap<Vec<Type>, usize>>,
|
||||
/// Scratch for a call's arg types
|
||||
key: Vec<Type>,
|
||||
pub site_table: SiteTable,
|
||||
/// The prototype of each primitive kind, as the stdlib links it, by kind
|
||||
pub protos: Protos,
|
||||
/// Goes up when a variant's return or a proto changes (see `infer_types()`)
|
||||
changes: usize,
|
||||
pub walks: Vec<Walk>,
|
||||
keys: HashMap<(usize, Vec<Type>), usize>, // the walk of each fn and list of arg types
|
||||
builtins: Vec<Cell>, // the cells of the natives, then of the proto links
|
||||
pub protos: Protos, // the prototype of each `ProtoOf`, as the stdlib links it
|
||||
version: usize, // goes up on every change of a cell, a walk's return or a proto, and on every new cell or walk
|
||||
}
|
||||
|
||||
impl TypeCache {
|
||||
/// The variant for a call of `fn_id` with args of these types, made if it
|
||||
/// is new. It is keyed by the fn's params: an arg it does not pass is
|
||||
/// undefined, and an arg it passes beyond them is never read. Only an
|
||||
/// arg's kind crosses a call, not a known value, else every literal passed
|
||||
/// would make a new variant
|
||||
pub fn variant(&mut self, fn_id: usize, arg_types: &[Type], fns: &[RegFn]) -> usize {
|
||||
let site_table = &mut self.site_table;
|
||||
self.key.clear();
|
||||
self.key.extend(params(fn_id, arg_types, fns).map(|ty| site_table.widen(&ty)));
|
||||
|
||||
if let Some(&v) = self.by_args[fn_id].get(&self.key[..]) {
|
||||
return v;
|
||||
}
|
||||
|
||||
let v = self.variants.len();
|
||||
self.by_args[fn_id].insert(self.key.clone(), v);
|
||||
self.variants.push(Variant {
|
||||
fn_id,
|
||||
arg_types: self.key.clone(),
|
||||
ctx: self.site_table.context(&self.key),
|
||||
result: None,
|
||||
ret: Type::Unknown,
|
||||
walking: false,
|
||||
});
|
||||
|
||||
v
|
||||
/// The key of a call of `fn_id` with args of these types: the fn, and the
|
||||
/// type of each of its params. An arg it does not pass is undefined, and
|
||||
/// an arg it passes beyond them is never read. Only an arg's kind crosses
|
||||
/// a call, not a known value, else every literal passed would make a new
|
||||
/// walk. An object is its cell
|
||||
fn key(fn_id: usize, arg_types: &[Type], fns: &[RegFn]) -> (usize, Vec<Type>) {
|
||||
let params = (0..fns[fn_id].arg_count).map(|i| arg_types.get(i).map_or(Type::Undefined, Type::widen));
|
||||
(fn_id, params.collect())
|
||||
}
|
||||
|
||||
/// The variant for a call that infer walked
|
||||
/// The walk of a call that infer walked
|
||||
pub fn find(&self, fn_id: usize, arg_types: &[Type], fns: &[RegFn]) -> usize {
|
||||
let site_table = &self.site_table;
|
||||
let key: Vec<Type> = params(fn_id, arg_types, fns).map(|ty| site_table.widened(&ty)).collect();
|
||||
self.by_args[fn_id][&key]
|
||||
self.keys[&Self::key(fn_id, arg_types, fns)]
|
||||
}
|
||||
|
||||
/// The join of `f` over the members of `ty` (see `SiteTable::members()`).
|
||||
/// The lists of sums only grow, so a member stays where it is while `f`
|
||||
/// adds sums
|
||||
fn map(&mut self, ty: Type, mut f: impl FnMut(&mut Self, Type) -> Type) -> Type {
|
||||
let mut out = Type::Unknown;
|
||||
/// The cell of a built-in fn
|
||||
pub fn builtin(&self, code: Code) -> &Cell {
|
||||
match code {
|
||||
Code::Native(id) => &self.builtins[id],
|
||||
Code::ProtoLink(kind) => &self.builtins[NATIVES.len() + kind as usize],
|
||||
Code::Fn(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..self.site_table.members(&ty).len() {
|
||||
let member = self.site_table.members(&ty)[i];
|
||||
let ty = f(self, member);
|
||||
out = self.site_table.join(&out, &ty);
|
||||
/// The cells of the built-in fns
|
||||
pub fn builtins(&self) -> &[Cell] {
|
||||
&self.builtins
|
||||
}
|
||||
|
||||
/// Every cell: the built-in ones, then the ones that each walk made
|
||||
pub fn cells(&self) -> impl Iterator<Item = &Cell> {
|
||||
let made = self.walks.iter().flat_map(|w| w.cells.iter().map(|(_, cell)| cell));
|
||||
self.builtins.iter().chain(made)
|
||||
}
|
||||
|
||||
fn new_cell(&mut self, kind: ObjKind, made: Option<(usize, usize)>) -> Cell {
|
||||
self.version += 1;
|
||||
Cell::new(kind, made)
|
||||
}
|
||||
|
||||
/// A cell that instr `idx` of walk `w`'s fn made, in the key of `w` or of
|
||||
/// a walk that made a cell in it, and so on: nearest first. A new cell
|
||||
/// would hold the instr twice in its name, and such names can nest with
|
||||
/// no end, so the walk uses that cell again
|
||||
fn made_before(&self, w: usize, idx: usize) -> Option<Cell> {
|
||||
let fn_id = self.walks[w].fn_id;
|
||||
let mut queue = vec![w];
|
||||
let mut i = 0;
|
||||
|
||||
while let Some(&walk) = queue.get(i) {
|
||||
i += 1;
|
||||
|
||||
for member in self.walks[walk].arg_types.iter().flat_map(Type::members) {
|
||||
let Type::Obj(cell) = member else { continue };
|
||||
let Some((maker, at)) = cell.info().made else { continue };
|
||||
|
||||
if self.walks[maker].fn_id == fn_id && at == idx {
|
||||
return Some(cell.clone());
|
||||
}
|
||||
|
||||
if !queue.contains(&maker) {
|
||||
queue.push(maker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
None
|
||||
}
|
||||
|
||||
/// The join of `f` over each pair of members of `a` and `b`
|
||||
fn map2(&mut self, a: Type, b: Type, mut f: impl FnMut(&mut Self, Type, Type) -> Type) -> Type {
|
||||
self.map(a, |cache, a| cache.map(b, |cache, b| f(cache, a, b)))
|
||||
/// Count a change, if there was one
|
||||
fn changed(&mut self, changed: bool) {
|
||||
self.version += changed as usize;
|
||||
}
|
||||
|
||||
/// What `f` gives for every member of `ty`, if all members give the same
|
||||
fn agree<T: PartialEq>(&self, ty: &Type, f: impl Fn(&Type) -> Option<T>) -> Option<T> {
|
||||
let mut members = self.site_table.members(ty).iter().map(f);
|
||||
let first = members.next()??;
|
||||
members.all(|t| t.as_ref() == Some(&first)).then_some(first)
|
||||
}
|
||||
|
||||
/// What `f` gives for every pair of members of `a` and `b`, if all pairs
|
||||
/// give the same
|
||||
fn agree2<T: PartialEq>(&self, a: &Type, b: &Type, f: impl Fn(&Type, &Type) -> Option<T>) -> Option<T> {
|
||||
let members = |t| self.site_table.members(t).iter();
|
||||
let mut pairs = members(a).flat_map(|x| members(b).map(move |y| (x, y))).map(|(x, y)| f(x, y));
|
||||
let first = pairs.next()??;
|
||||
pairs.all(|t| t.as_ref() == Some(&first)).then_some(first)
|
||||
}
|
||||
|
||||
/// Infer a call of `fn_id`, walking it unless an identical call was
|
||||
/// already walked in this round. Gives what it returns
|
||||
/// Infer a call of `fn_id`, walking it unless the walk of its key is done
|
||||
/// in this round. Gives what it returns
|
||||
fn call(&mut self, fn_id: usize, arg_types: &[Type], fns: &[RegFn]) -> Type {
|
||||
let v = self.variant(fn_id, arg_types, fns);
|
||||
let key = Self::key(fn_id, arg_types, fns);
|
||||
|
||||
match &self.variants[v] {
|
||||
Variant { result: Some(res), .. } => return res.ret_type,
|
||||
Variant { walking: true, ret, .. } => return *ret,
|
||||
let w = match self.keys.get(&key) {
|
||||
Some(&w) => w,
|
||||
None => {
|
||||
self.version += 1;
|
||||
self.walks.push(Walk {
|
||||
fn_id,
|
||||
arg_types: key.1.clone(),
|
||||
cells: vec![],
|
||||
ret: Type::Unknown,
|
||||
walking: false,
|
||||
result: None,
|
||||
});
|
||||
self.keys.insert(key, self.walks.len() - 1);
|
||||
self.walks.len() - 1
|
||||
}
|
||||
};
|
||||
|
||||
match &self.walks[w] {
|
||||
Walk { walking: true, ret, .. } => return ret.clone(),
|
||||
Walk { result: Some(res), .. } => return res.ret_type.clone(),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.variants[v].walking = true;
|
||||
let res = walk_fn(v, fns, self);
|
||||
let ret = res.ret_type;
|
||||
self.walks[w].walking = true;
|
||||
let res = walk_fn(w, fns, self);
|
||||
let ret = res.ret_type.clone();
|
||||
|
||||
let last = self.variants[v].ret;
|
||||
let joined = self.site_table.join(&last, &ret);
|
||||
self.changes += (joined != last) as usize;
|
||||
let last = &self.walks[w].ret;
|
||||
let joined = join(last, &ret);
|
||||
let grew = joined != *last;
|
||||
self.changed(grew);
|
||||
|
||||
let variant = &mut self.variants[v];
|
||||
variant.walking = false;
|
||||
variant.ret = joined;
|
||||
variant.result = Some(res);
|
||||
let walk = &mut self.walks[w];
|
||||
walk.walking = false;
|
||||
walk.ret = joined;
|
||||
walk.result = Some(res);
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of each of fn `fn_id`'s params, for a call with args of these
|
||||
/// types, before known values are dropped (see `TypeCache::variant()`)
|
||||
fn params<'a>(fn_id: usize, arg_types: &'a [Type], fns: &[RegFn]) -> impl Iterator<Item = Type> + 'a {
|
||||
(0..fns[fn_id].arg_count).map(|i| arg_types.get(i).copied().unwrap_or(Type::Undefined))
|
||||
/// The join of `f` over the members of `ty`
|
||||
fn map(ty: &Type, mut f: impl FnMut(&Type) -> Type) -> Type {
|
||||
ty.members().iter().fold(Type::Unknown, |all, member| join(&all, &f(member)))
|
||||
}
|
||||
|
||||
/// What `f` gives for every member of `ty`, if all members give the same
|
||||
fn agree<T: PartialEq>(ty: &Type, f: impl Fn(&Type) -> Option<T>) -> Option<T> {
|
||||
let mut members = ty.members().iter().map(f);
|
||||
let first = members.next()??;
|
||||
members.all(|t| t.as_ref() == Some(&first)).then_some(first)
|
||||
}
|
||||
|
||||
/// What `f` gives for every pair of members of `a` and `b`, if all pairs give
|
||||
/// the same
|
||||
fn agree2<T: PartialEq>(a: &Type, b: &Type, f: impl Fn(&Type, &Type) -> Option<T>) -> Option<T> {
|
||||
let pairs = a.members().iter().flat_map(|x| b.members().iter().map(move |y| (x, y)));
|
||||
let mut results = pairs.map(|(x, y)| f(x, y));
|
||||
let first = results.next()??;
|
||||
results.all(|t| t.as_ref() == Some(&first)).then_some(first)
|
||||
}
|
||||
|
||||
/// A call of `code` with operands of these types (see the call rule in
|
||||
@@ -169,104 +192,77 @@ fn call_code(cache: &mut TypeCache, code: Code, arg_types: &[Type], fns: &[RegFn
|
||||
|
||||
match code {
|
||||
Code::Fn(fn_id) => cache.call(fn_id, arg_types, fns),
|
||||
Code::Native(id) => native::ret_type(id, args, &cache.site_table),
|
||||
Code::Native(id) => native::ret_type(id, args),
|
||||
Code::ProtoLink(kind) => {
|
||||
let linked = cache.protos[kind as usize];
|
||||
let joined = cache.site_table.join(&linked, args.first().unwrap_or(&Type::Undefined));
|
||||
cache.changes += (joined != linked) as usize;
|
||||
let linked = &cache.protos[kind as usize];
|
||||
let joined = join(linked, args.first().unwrap_or(&Type::Undefined));
|
||||
cache.changed(joined != *linked);
|
||||
cache.protos[kind as usize] = joined;
|
||||
Type::Undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A call of a callee of type `Any`, with operands of these types: a call of
|
||||
/// any fn object in an `Any`, with every operand an `Any`, except that the
|
||||
/// callee is its own env. That is the variant that emit's dispatch on `Any`
|
||||
/// calls (see `call_any_variant()`). Gives an `Any`
|
||||
fn call_any(cache: &mut TypeCache, arg_types: &mut [Type], fns: &[RegFn]) -> Type {
|
||||
for ty in arg_types.iter_mut() {
|
||||
*ty = cache.site_table.to_any(ty);
|
||||
}
|
||||
|
||||
for site in 0..cache.site_table.sites.len() as SiteId {
|
||||
let Some(code) = cache.site_table.site(site).code().filter(|_| cache.site_table.site(site).in_any) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
arg_types[0] = Type::Obj(site);
|
||||
let ret = call_code(cache, code, arg_types, fns);
|
||||
cache.site_table.to_any(&ret);
|
||||
}
|
||||
|
||||
Type::Any
|
||||
}
|
||||
|
||||
/// The site that `place` makes in context `ctx`, made if it is new
|
||||
fn site_at(cache: &mut TypeCache, place: Place, ctx: CtxId) -> SiteId {
|
||||
let place = cache.site_table.place_id(place);
|
||||
cache.site_table.site_at(place, ctx)
|
||||
}
|
||||
|
||||
/// An array index must be a number. Any other key is a computed field name,
|
||||
/// which is not supported
|
||||
fn check_index(cache: &TypeCache, index: &Type) {
|
||||
for member in cache.site_table.members(index) {
|
||||
fn check_index(index: &Type) {
|
||||
for member in index.members() {
|
||||
if !matches!(member, Type::Int | Type::Float) {
|
||||
unimplemented!("`a[k]` with a key of type {member:?}: computed field names");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The operand types of the variant that a call on `Any`, with `n_args`
|
||||
/// args, runs for a fn object of `site`
|
||||
pub fn call_any_variant(site: SiteId, n_args: usize) -> Vec<Type> {
|
||||
let mut arg_types = vec![Type::Any; FIRST_ARG as usize + n_args];
|
||||
arg_types[0] = Type::Obj(site);
|
||||
arg_types
|
||||
}
|
||||
|
||||
/// Walk every variant from the entry, in rounds, until a round changes
|
||||
/// nothing. A variant reads the shapes, the protos and the returns of other
|
||||
/// variants, which variants walked after it can still add to. All of them only
|
||||
/// grow, so the rounds end. The results are those of the last round
|
||||
/// Walk the program from the entry, in rounds, until a round changes
|
||||
/// nothing. A walk reads cells, protos and the returns of other walks, which
|
||||
/// walks after it can still add to. All of them only grow, so the rounds end.
|
||||
/// The results are those of the last round
|
||||
pub fn infer_types(fns: &[RegFn]) -> TypeCache {
|
||||
let mut cache = TypeCache {
|
||||
variants: vec![],
|
||||
by_args: fns.iter().map(|_| HashMap::new()).collect(),
|
||||
key: vec![],
|
||||
site_table: SiteTable::new(fns),
|
||||
protos: [Type::Unknown; ProtoOf::ALL.len()],
|
||||
changes: 0,
|
||||
walks: vec![],
|
||||
keys: HashMap::new(),
|
||||
builtins: vec![],
|
||||
protos: std::array::from_fn(|_| Type::Unknown),
|
||||
version: 0,
|
||||
};
|
||||
|
||||
let codes = (0..NATIVES.len()).map(Code::Native).chain(ProtoOf::ALL.map(Code::ProtoLink));
|
||||
|
||||
for code in codes {
|
||||
let cell = cache.new_cell(ObjKind::Fn(code), None);
|
||||
cache.builtins.push(cell);
|
||||
}
|
||||
|
||||
loop {
|
||||
let before = (cache.site_table.version, cache.changes);
|
||||
cache.variants.iter_mut().for_each(|v| v.result = None);
|
||||
let version = cache.version;
|
||||
cache.walks.iter_mut().for_each(|w| w.result = None);
|
||||
|
||||
// The synthetic program entry is appended last by `flatten`. It is
|
||||
// called by the host with no args
|
||||
cache.call(fns.len() - 1, &[], fns);
|
||||
|
||||
if before == (cache.site_table.version, cache.changes) {
|
||||
if cache.version == version {
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn walk_fn(variant: usize, reg_fns: &[RegFn], cache: &mut TypeCache) -> InferResult {
|
||||
fn walk_fn(w: usize, reg_fns: &[RegFn], cache: &mut TypeCache) -> InferResult {
|
||||
use OpCode::*;
|
||||
|
||||
let (fn_id, ctx) = (cache.variants[variant].fn_id, cache.variants[variant].ctx);
|
||||
let fn_id = cache.walks[w].fn_id;
|
||||
let f = ®_fns[fn_id];
|
||||
let mut val_types = vec![Type::Unknown; f.val_count];
|
||||
let mut instr_types = vec![Type::Unknown; f.code.len()];
|
||||
let mut ret_type = Type::Unknown;
|
||||
|
||||
// The cells that this walk made in the rounds before. A recursive call of
|
||||
// the walk does not walk it, so it does not need them
|
||||
let mut cells = std::mem::take(&mut cache.walks[w].cells);
|
||||
|
||||
// arg `i` is value id `var_count + i` (see regify)
|
||||
// Init them with the arg types passed in
|
||||
for (i, ty) in cache.variants[variant].arg_types.iter().enumerate() {
|
||||
val_types[f.var_count + i] = *ty;
|
||||
for (i, ty) in cache.walks[w].arg_types.iter().enumerate() {
|
||||
val_types[f.var_count + i] = ty.clone();
|
||||
}
|
||||
|
||||
// The blocks that can run, given what the types say about each condition
|
||||
@@ -277,11 +273,10 @@ fn walk_fn(variant: usize, reg_fns: &[RegFn], cache: &mut TypeCache) -> InferRes
|
||||
let mut arg_types: Vec<Type> = vec![];
|
||||
|
||||
// Walk instrs till nothing changes. A return only changes `ret_type` once
|
||||
// its block is reachable, and a field read only gives more once the site
|
||||
// table changes, so the values, the blocks and the site table are enough
|
||||
// to watch
|
||||
// its block is reachable, and a field read only gives more once a cell
|
||||
// changes, so the values, the blocks and the version are enough to watch
|
||||
loop {
|
||||
let version = cache.site_table.version;
|
||||
let version = cache.version;
|
||||
let mut changed = false;
|
||||
|
||||
for (block_idx, block) in f.blocks.iter().enumerate() {
|
||||
@@ -293,19 +288,34 @@ fn walk_fn(variant: usize, reg_fns: &[RegFn], cache: &mut TypeCache) -> InferRes
|
||||
let code = code.iter().chain(&block.postfix);
|
||||
|
||||
for (offset, instr) in code.enumerate() {
|
||||
let is_ret = matches!(instr.op, Return | ReturnAsync | ReturnUndef);
|
||||
// Jumps are the block's to walk (see below). The others give
|
||||
// nothing
|
||||
if matches!(instr.op, Nop | Drop | Goto | Goto8 | Goto16 | IfFalse | IfFalse8 | IfTrue | IfTrue8) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// A rule with an `Unknown` operand gives `Unknown`: no value has
|
||||
// reached the instr yet
|
||||
let waits = instr.args.iter().any(|&v| val_types[v] == Type::Unknown);
|
||||
|
||||
if (instr.dsts.is_empty() && !is_ret) || waits {
|
||||
if instr.args.iter().any(|&v| val_types[v] == Type::Unknown) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The instr's index in the code (postfix copies have none)
|
||||
let idx = block.start + offset;
|
||||
let arg = |i: usize| val_types[instr.args[i]];
|
||||
let arg = |i: usize| val_types[instr.args[i]].clone();
|
||||
|
||||
// The cell of the objects that this instr makes, made the
|
||||
// first time, unless the key already holds one (see
|
||||
// `made_before()`)
|
||||
let mut cell_here = |cache: &mut TypeCache, kind: ObjKind| {
|
||||
let found = cells.iter().find(|(i, _)| *i == idx).map(|(_, cell)| cell.clone());
|
||||
|
||||
found.or_else(|| cache.made_before(w, idx)).unwrap_or_else(|| {
|
||||
let cell = cache.new_cell(kind, Some((w, idx)));
|
||||
cells.push((idx, cell.clone()));
|
||||
cell
|
||||
})
|
||||
};
|
||||
|
||||
let ty = match instr.op {
|
||||
// ────────────────────────────────────────── Literals ───────────────────────────────────────────────────────
|
||||
@@ -324,8 +334,8 @@ fn walk_fn(variant: usize, reg_fns: &[RegFn], cache: &mut TypeCache) -> InferRes
|
||||
JsValue::BoolTrue => Type::Bool(Some(true)),
|
||||
JsValue::BoolFalse => Type::Bool(Some(false)),
|
||||
JsValue::Undefined => Type::Undefined,
|
||||
JsValue::NativeFn(id) => cache.site_table.create(cache.site_table.builtin(Place::Native(id))),
|
||||
JsValue::ProtoLink(kind) => cache.site_table.create(cache.site_table.builtin(Place::ProtoLink(kind))),
|
||||
JsValue::NativeFn(id) => Type::Obj(cache.builtin(Code::Native(id)).clone()),
|
||||
JsValue::ProtoLink(kind) => Type::Obj(cache.builtin(Code::ProtoLink(kind)).clone()),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
|
||||
@@ -335,18 +345,25 @@ fn walk_fn(variant: usize, reg_fns: &[RegFn], cache: &mut TypeCache) -> InferRes
|
||||
// ──────────────────────────────────────── Arithmetic ───────────────────────────────────────────────────────
|
||||
// `+` concatenates if either side becomes a string, which
|
||||
// a string or an object does. Else it adds numbers
|
||||
Add => cache.map2(arg(1), arg(0), |_, l, r| match (l, r) {
|
||||
(Type::Any, _) | (_, Type::Any) => Type::Any,
|
||||
(l, r) if [l, r].iter().any(|t| matches!(t, Type::Str(_) | Type::Obj(_))) => Type::Str(None),
|
||||
(Type::Int, Type::Int) => Type::Int,
|
||||
_ => Type::Float,
|
||||
}),
|
||||
Add => {
|
||||
let r = arg(0);
|
||||
|
||||
map(&arg(1), |l| map(&r, |r| match (l, r) {
|
||||
(l, r) if [l, r].iter().any(|t| matches!(t, Type::Str(_) | Type::Obj(_))) => Type::Str(None),
|
||||
(Type::Int, Type::Int) => Type::Int,
|
||||
_ => Type::Float,
|
||||
}))
|
||||
}
|
||||
// Any other operator converts its operands to numbers
|
||||
Sub | Mul | Mod | Pow => cache.map2(arg(1), arg(0), |_, l, r| match (l, r) {
|
||||
(Type::Int, Type::Int) => Type::Int,
|
||||
_ => Type::Float,
|
||||
}),
|
||||
Neg | Plus | Inc | Dec | PostInc | PostDec => cache.map(arg(0), |_, t| match t {
|
||||
Sub | Mul | Mod | Pow => {
|
||||
let r = arg(0);
|
||||
|
||||
map(&arg(1), |l| map(&r, |r| match (l, r) {
|
||||
(Type::Int, Type::Int) => Type::Int,
|
||||
_ => Type::Float,
|
||||
}))
|
||||
}
|
||||
Neg | Plus | Inc | Dec | PostInc | PostDec => map(&arg(0), |t| match t {
|
||||
Type::Int => Type::Int,
|
||||
_ => Type::Float,
|
||||
}),
|
||||
@@ -360,56 +377,45 @@ fn walk_fn(variant: usize, reg_fns: &[RegFn], cache: &mut TypeCache) -> InferRes
|
||||
// A known value lets infer skip the branch that cannot run
|
||||
// (see `reachable` below)
|
||||
StrictEq | StrictNeq | Eq | Neq => {
|
||||
let (a, b) = (arg(0), arg(1));
|
||||
|
||||
// An identity test of an object with what can be an
|
||||
// object (see `docs/model.md`, section 5)
|
||||
let members = |t: &Type| cache.site_table.members(t).to_vec();
|
||||
let (a_members, b_members) = (members(&a), members(&b));
|
||||
let can_be_object = |ms: &[Type]| ms.iter().any(|t| matches!(t, Type::Obj(_) | Type::Any));
|
||||
|
||||
if can_be_object(&a_members) && can_be_object(&b_members) {
|
||||
for t in a_members.iter().chain(&b_members) {
|
||||
if let Type::Obj(site) = t {
|
||||
cache.site_table.mark(*site, |shape| &mut shape.compared);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let eq = match instr.op {
|
||||
StrictEq | StrictNeq => cache.agree2(&a, &b, strict_eq),
|
||||
_ => cache.agree2(&a, &b, loose_eq),
|
||||
StrictEq | StrictNeq => agree2(&arg(0), &arg(1), strict_eq),
|
||||
_ => agree2(&arg(0), &arg(1), loose_eq),
|
||||
};
|
||||
|
||||
Type::Bool(eq.map(|eq| eq == matches!(instr.op, StrictEq | Eq)))
|
||||
}
|
||||
Lnot => Type::Bool(cache.agree(&arg(0), truthiness).map(|t| !t)),
|
||||
Typeof => match cache.agree(&arg(0), |t| typeof_name(t, &cache.site_table)) {
|
||||
Lnot => Type::Bool(agree(&arg(0), truthiness).map(|t| !t)),
|
||||
Typeof => match agree(&arg(0), typeof_name) {
|
||||
Some(name) => Type::Str(Some(AtomRef::Predefined(name))),
|
||||
None => Type::Str(None),
|
||||
},
|
||||
IsUndefined | TypeofIsUndefined => Type::Bool(cache.agree(&arg(0), |t| test(t, &[BaseType::Undefined]))),
|
||||
IsNull => Type::Bool(cache.agree(&arg(0), |t| test(t, &[BaseType::Null]))),
|
||||
IsUndefinedOrNull => Type::Bool(cache.agree(&arg(0), |t| test(t, &[BaseType::Undefined, BaseType::Null]))),
|
||||
TypeofIsFunction => Type::Bool(cache.agree(&arg(0), |t| is_function(t, &cache.site_table))),
|
||||
IsUndefined | TypeofIsUndefined => Type::Bool(agree(&arg(0), |t| test(t, &[BaseType::Undefined]))),
|
||||
IsNull => Type::Bool(agree(&arg(0), |t| test(t, &[BaseType::Null]))),
|
||||
IsUndefinedOrNull => Type::Bool(agree(&arg(0), |t| test(t, &[BaseType::Undefined, BaseType::Null]))),
|
||||
TypeofIsFunction => Type::Bool(agree(&arg(0), is_function)),
|
||||
|
||||
// ─────────────────────────────────────── Value Movers ──────────────────────────────────────────────────────
|
||||
SetLoc | SetName => arg(0),
|
||||
|
||||
// ───────────────────────────────────────── Objects ─────────────────────────────────────────────────────────
|
||||
Object | Fclosure | Fclosure8 => {
|
||||
let site = site_at(cache, Place::Op { fn_id, instr: idx }, ctx);
|
||||
cache.site_table.create(site)
|
||||
Object => Type::Obj(cell_here(cache, ObjKind::Plain)),
|
||||
Fclosure | Fclosure8 => {
|
||||
let JsValue::FnById(child) = f.cpool[instr.imm.const_idx()] else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
Type::Obj(cell_here(cache, ObjKind::Fn(Code::Fn(child))))
|
||||
}
|
||||
// The elements are its operands, last first
|
||||
// The elements are its operands
|
||||
ArrayFrom => {
|
||||
let site = site_at(cache, Place::Op { fn_id, instr: idx }, ctx);
|
||||
let cell = cell_here(cache, ObjKind::Array { elems: Type::Unknown });
|
||||
|
||||
for &v in &instr.args {
|
||||
cache.site_table.write_elem(site, &val_types[v]);
|
||||
let grew = cell.write_elem(&val_types[v]);
|
||||
cache.changed(grew);
|
||||
}
|
||||
|
||||
cache.site_table.create(site)
|
||||
Type::Obj(cell)
|
||||
}
|
||||
// `a[i]` and `a[i] = x`, on arrays, and `s[i]` on strings:
|
||||
// an index is a number. Other objects need computed field
|
||||
@@ -419,14 +425,14 @@ fn walk_fn(variant: usize, reg_fns: &[RegFn], cache: &mut TypeCache) -> InferRes
|
||||
GetArrayEl => {
|
||||
let index = arg(0);
|
||||
|
||||
cache.map(arg(1), |cache, obj| match obj {
|
||||
Type::Obj(site) if cache.site_table.site(site).is_array() => {
|
||||
check_index(cache, &index);
|
||||
cache.site_table.read_elem(site)
|
||||
map(&arg(1), |obj| match obj {
|
||||
Type::Obj(cell) if cell.is_array() => {
|
||||
check_index(&index);
|
||||
cell.read_elem()
|
||||
}
|
||||
Type::Str(_) => {
|
||||
check_index(cache, &index);
|
||||
cache.site_table.join(&Type::Str(None), &Type::Undefined)
|
||||
check_index(&index);
|
||||
join(&Type::Str(None), &Type::Undefined)
|
||||
}
|
||||
Type::Undefined | Type::Null => Type::Unknown,
|
||||
obj => unimplemented!("`o[k]` on {obj:?}: computed field names"),
|
||||
@@ -435,98 +441,71 @@ fn walk_fn(variant: usize, reg_fns: &[RegFn], cache: &mut TypeCache) -> InferRes
|
||||
PutArrayEl => {
|
||||
let (value, index) = (arg(0), arg(1));
|
||||
|
||||
cache.map(arg(2), |cache, obj| {
|
||||
for obj in arg(2).members() {
|
||||
match obj {
|
||||
Type::Obj(site) if cache.site_table.site(site).is_array() => {
|
||||
check_index(cache, &index);
|
||||
cache.site_table.write_elem(site, &value);
|
||||
cache.site_table.mark(site, |s| &mut s.written);
|
||||
Type::Obj(cell) if cell.is_array() => {
|
||||
check_index(&index);
|
||||
let grew = cell.write_elem(&value);
|
||||
cache.changed(grew);
|
||||
}
|
||||
Type::Undefined | Type::Null | Type::Str(_) => {}
|
||||
obj => unimplemented!("`o[k] = v` on {obj:?}: computed field names"),
|
||||
}
|
||||
}
|
||||
|
||||
Type::Unknown
|
||||
});
|
||||
|
||||
arg(2)
|
||||
Type::Unknown
|
||||
}
|
||||
// A key stays as it is: a number is an index
|
||||
ToPropkey => arg(0),
|
||||
// The write goes into the fields of the object's site, which
|
||||
// every value that holds an object of the site sees. A write
|
||||
// on `Any` can reach any object in an `Any`. A primitive has
|
||||
// no fields to write, and undefined and null throw
|
||||
// The write goes into the object's cell, which every holder
|
||||
// of the object sees. A primitive has no fields to write,
|
||||
// and undefined and null throw
|
||||
DefineField | PutField => {
|
||||
let (field, value) = (instr.imm.atom(), arg(0));
|
||||
// A define_field builds the object before it goes
|
||||
// anywhere (see regify)
|
||||
let creation = instr.op == DefineField;
|
||||
|
||||
cache.map(arg(1), |cache, obj| {
|
||||
match obj {
|
||||
Type::Obj(site) => {
|
||||
cache.site_table.write(site, field, &value);
|
||||
|
||||
if !creation {
|
||||
cache.site_table.mark(site, |shape| &mut shape.written);
|
||||
}
|
||||
}
|
||||
Type::Any => {
|
||||
for site in 0..cache.site_table.sites.len() as SiteId {
|
||||
if cache.site_table.site(site).in_any {
|
||||
cache.site_table.write(site, field, &value);
|
||||
cache.site_table.mark(site, |shape| &mut shape.written);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
for obj in arg(1).members() {
|
||||
if let Type::Obj(cell) = obj {
|
||||
let grew = cell.write(field, &value, instr.op == DefineField);
|
||||
cache.changed(grew);
|
||||
}
|
||||
}
|
||||
|
||||
Type::Unknown
|
||||
});
|
||||
|
||||
arg(1)
|
||||
Type::Unknown
|
||||
}
|
||||
GetField => cache.site_table.read_value(&arg(0), instr.imm.atom(), &cache.protos),
|
||||
GetField => read_value(&arg(0), instr.imm.atom(), &cache.protos),
|
||||
// A string's length is its own. Anything else reads a
|
||||
// `length` field
|
||||
GetLength => cache.map(arg(0), |cache, obj| match obj {
|
||||
GetLength => map(&arg(0), |obj| match obj {
|
||||
Type::Str(_) => Type::Int,
|
||||
obj => cache.site_table.read_value(&obj, LENGTH_FIELD, &cache.protos),
|
||||
obj => read_value(obj, LENGTH_FIELD, &cache.protos),
|
||||
}),
|
||||
|
||||
// ───────────────────────────────────── Function Calls ──────────────────────────────────────────────────────
|
||||
// Operands are the callee's params: the callee (its env),
|
||||
// `this`, then the args (see regify). A callee that is not
|
||||
// a fn throws, so it gives nothing
|
||||
// The callee's env is the fn object itself, so a call of
|
||||
// each member of a sum passes that member as the env
|
||||
// a fn throws, so it gives nothing. A call of each member
|
||||
// of a sum passes that member as the env
|
||||
Call | CallMethod | CallConstructor => {
|
||||
let this = arg(THIS_ARG as usize);
|
||||
let ret = map(&arg(0), |callee| {
|
||||
let Type::Obj(cell) = callee else {
|
||||
return Type::Unknown;
|
||||
};
|
||||
let Some(code) = cell.code() else {
|
||||
return Type::Unknown;
|
||||
};
|
||||
|
||||
let ret = cache.map(arg(0), |cache, callee| {
|
||||
arg_types.clear();
|
||||
arg_types.extend(instr.args.iter().map(|&v| val_types[v]));
|
||||
arg_types[0] = callee;
|
||||
|
||||
match callee {
|
||||
Type::Obj(site) => match cache.site_table.site(site).code() {
|
||||
Some(code) => call_code(cache, code, &arg_types, reg_fns),
|
||||
None => Type::Unknown,
|
||||
},
|
||||
Type::Any => call_any(cache, &mut arg_types, reg_fns),
|
||||
_ => Type::Unknown,
|
||||
}
|
||||
arg_types.extend(instr.args.iter().map(|&v| val_types[v].clone()));
|
||||
arg_types[0] = callee.clone();
|
||||
call_code(cache, code, &arg_types, reg_fns)
|
||||
});
|
||||
|
||||
// `new` gives what the constructor returns if that is an
|
||||
// object, else the new object itself, `this`
|
||||
match instr.op {
|
||||
CallConstructor => cache.map(ret, |cache, ret| match ret {
|
||||
Type::Obj(_) => ret,
|
||||
Type::Any => cache.site_table.join(&ret, &this),
|
||||
_ => this,
|
||||
CallConstructor => map(&ret, |ret| match ret {
|
||||
Type::Obj(_) => ret.clone(),
|
||||
_ => arg(THIS_ARG as usize),
|
||||
}),
|
||||
_ => ret,
|
||||
}
|
||||
@@ -542,16 +521,16 @@ fn walk_fn(variant: usize, reg_fns: &[RegFn], cache: &mut TypeCache) -> InferRes
|
||||
// Postfix copies come after the block's own instrs, and have no
|
||||
// entry: a copy gives the type of what it copies
|
||||
if idx < block.end {
|
||||
instr_types[idx] = cache.site_table.join(&instr_types[idx], &ty);
|
||||
instr_types[idx] = join(&instr_types[idx], &ty);
|
||||
}
|
||||
|
||||
if is_ret {
|
||||
ret_type = cache.site_table.join(&ret_type, &ty);
|
||||
if matches!(instr.op, Return | ReturnAsync | ReturnUndef) {
|
||||
ret_type = join(&ret_type, &ty);
|
||||
continue;
|
||||
}
|
||||
|
||||
for &dst in &instr.dsts {
|
||||
let joined = cache.site_table.join(&val_types[dst], &ty);
|
||||
let joined = join(&val_types[dst], &ty);
|
||||
changed |= joined != val_types[dst];
|
||||
val_types[dst] = joined;
|
||||
}
|
||||
@@ -563,9 +542,9 @@ fn walk_fn(variant: usize, reg_fns: &[RegFn], cache: &mut TypeCache) -> InferRes
|
||||
// (A block that only carries copies on an edge has no instrs)
|
||||
let jumps = match f.code[block.start..block.end].last() {
|
||||
Some(last) if matches!(last.op, IfFalse | IfFalse8 | IfTrue | IfTrue8) => {
|
||||
match val_types[last.args[0]] {
|
||||
match &val_types[last.args[0]] {
|
||||
Type::Unknown => continue,
|
||||
cond => cache.agree(&cond, truthiness).map(|t| t == matches!(last.op, IfTrue | IfTrue8)),
|
||||
cond => agree(cond, truthiness).map(|t| t == matches!(last.op, IfTrue | IfTrue8)),
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
@@ -580,11 +559,13 @@ fn walk_fn(variant: usize, reg_fns: &[RegFn], cache: &mut TypeCache) -> InferRes
|
||||
}
|
||||
} // block
|
||||
|
||||
if !changed && cache.site_table.version == version {
|
||||
if !changed && cache.version == version {
|
||||
break;
|
||||
}
|
||||
} // loop
|
||||
|
||||
cache.walks[w].cells = cells;
|
||||
|
||||
InferResult {
|
||||
val_types,
|
||||
instr_types,
|
||||
@@ -592,4 +573,3 @@ fn walk_fn(variant: usize, reg_fns: &[RegFn], cache: &mut TypeCache) -> InferRes
|
||||
reachable,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,498 +0,0 @@
|
||||
//! What infer knows about the objects of each site: the type of each field,
|
||||
//! and flags (see `docs/model.md`, section 3.3)
|
||||
|
||||
use crate::{
|
||||
libqjs::parse::{AtomRef, JsValue, opcodes::OpCode},
|
||||
native::{NATIVES, ProtoOf},
|
||||
regify::RegFn,
|
||||
transform::{BOX_FIELD, PROTO_FIELD},
|
||||
};
|
||||
|
||||
use super::types::Type;
|
||||
|
||||
pub const LENGTH_FIELD: AtomRef = AtomRef::Predefined(51); // length
|
||||
|
||||
/// The prototype of each `ProtoOf`, as the stdlib links it
|
||||
pub type Protos = [Type; ProtoOf::ALL.len()];
|
||||
|
||||
/// Where an object is made: the op that creates it, or a built-in fn. Places
|
||||
/// are numbered in this order (see `SiteTable::new()`)
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Place {
|
||||
Op { fn_id: usize, instr: usize },
|
||||
Native(usize),
|
||||
ProtoLink(ProtoOf),
|
||||
}
|
||||
|
||||
/// The number of a place: its index in `SiteTable::places`
|
||||
pub type PlaceId = u32;
|
||||
|
||||
/// The number of a context: its index in `SiteTable::contexts`
|
||||
pub type CtxId = u32;
|
||||
|
||||
/// The number of a site: its index in `SiteTable::sites`
|
||||
pub type SiteId = u32;
|
||||
|
||||
/// The code a fn object runs
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Code {
|
||||
Fn(usize),
|
||||
Native(usize),
|
||||
ProtoLink(ProtoOf),
|
||||
}
|
||||
|
||||
/// A field of the objects of a site
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Field {
|
||||
pub ty: Type,
|
||||
pub defined: bool, // every object of the site gets it when it is made, so it is never absent
|
||||
}
|
||||
|
||||
/// What the objects of a site are, besides objects with named fields
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ObjKind {
|
||||
Plain,
|
||||
Fn(Code), // a fn, which runs the code
|
||||
Array { elems: Type }, // an array, whose elements are the values written to them, or holes
|
||||
}
|
||||
|
||||
/// A place, and what every site of it starts with
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PlaceInfo {
|
||||
pub place: Place,
|
||||
kind: ObjKind, // the kind of its sites, before any element is written
|
||||
defined: Vec<AtomRef>, // the fields that its creation writes write, sorted
|
||||
sites: Vec<(CtxId, SiteId)>, // its sites so far, by context
|
||||
}
|
||||
|
||||
/// A part of a context (see `SiteTable::context()`)
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum CtxPart {
|
||||
Member(Type), // a member of a param's type that is not an object
|
||||
Place(PlaceId), // an object member, by the place that makes it
|
||||
End, // the end of a param's type
|
||||
}
|
||||
|
||||
/// What infer knows about the objects of one site
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SiteInfo {
|
||||
pub place: PlaceId,
|
||||
pub ctx: CtxId,
|
||||
pub kind: ObjKind,
|
||||
pub fields: Vec<(AtomRef, Field)>, // sorted by name
|
||||
pub written: bool, // a field of one of its objects is written after the object is made
|
||||
pub compared: bool, // one of its objects is in an identity test with an object
|
||||
pub in_any: bool, // one of its objects went into an `Any` (see `SiteTable::to_any()`)
|
||||
pub live: bool, // an op that makes one of its objects has run (see infer)
|
||||
}
|
||||
|
||||
impl SiteInfo {
|
||||
/// The code that its objects run, if they are fns
|
||||
pub fn code(&self) -> Option<Code> {
|
||||
match self.kind {
|
||||
ObjKind::Fn(code) => Some(code),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_array(&self) -> bool {
|
||||
matches!(self.kind, ObjKind::Array { .. })
|
||||
}
|
||||
|
||||
pub fn field(&self, name: AtomRef) -> Option<&Field> {
|
||||
let i = self.fields.binary_search_by_key(&name, |(n, _)| *n).ok()?;
|
||||
Some(&self.fields[i].1)
|
||||
}
|
||||
|
||||
/// Whether a copy of one of its objects cannot be told from the object:
|
||||
/// no write after creation can show the copy, and no identity test can
|
||||
/// (see `docs/model.md`, section 5). An object in an `Any` value can be
|
||||
/// written and compared by code that does not know its site
|
||||
pub fn is_value(&self) -> bool {
|
||||
!self.written && !self.compared && !self.in_any
|
||||
}
|
||||
}
|
||||
|
||||
/// Every place, every site, and every sum
|
||||
///
|
||||
/// A site is a place and a context: the objects that one place makes in the
|
||||
/// variants that have one context. So the objects follow the monomorphization
|
||||
/// of the fns that make them (see `docs/model.md`, section 3.3)
|
||||
///
|
||||
/// The sums are stored flat: the members of sum `i` are
|
||||
/// `sum_members[sum_starts[i]..sum_starts[i + 1]]`. So a search over all sums
|
||||
/// reads one array in order. The contexts are stored the same way
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SiteTable {
|
||||
pub places: Vec<PlaceInfo>, // place id -> where it is, sorted by `place`
|
||||
pub sites: Vec<SiteInfo>, // site id -> what infer knows about its objects
|
||||
ctx_parts: Vec<CtxPart>, // context id -> its parts, see above
|
||||
ctx_starts: Vec<u32>,
|
||||
pub(super) sum_members: Vec<Type>, // sum id -> its members, see above
|
||||
pub(super) sum_starts: Vec<u32>,
|
||||
pub version: usize, // goes up on every change, so a walk can tell that a read may give more
|
||||
}
|
||||
|
||||
impl SiteTable {
|
||||
/// One place for each creating op, in the order of fns and instrs, then
|
||||
/// the natives, then the proto links. A place starts its sites with the
|
||||
/// fields that its creation writes write: every `define_field` writes into
|
||||
/// the object that a creating op gives (see regify). The built-in fns have
|
||||
/// one site each, in the empty context (context 0)
|
||||
pub fn new(fns: &[RegFn]) -> Self {
|
||||
let mut places: Vec<PlaceInfo> = vec![];
|
||||
let place = |place, kind| PlaceInfo { place, kind, defined: vec![], sites: vec![] };
|
||||
|
||||
// The place that made each value of the fn, if a creating op gave it
|
||||
let mut created: Vec<Option<usize>> = vec![];
|
||||
|
||||
for (fn_id, f) in fns.iter().enumerate() {
|
||||
created.clear();
|
||||
created.resize(f.val_count, None);
|
||||
|
||||
for (instr, op) in f.code.iter().enumerate() {
|
||||
match op.op {
|
||||
OpCode::Object | OpCode::Fclosure | OpCode::Fclosure8 | OpCode::ArrayFrom => {
|
||||
let kind = match op.op {
|
||||
OpCode::Object => ObjKind::Plain,
|
||||
OpCode::ArrayFrom => ObjKind::Array { elems: Type::Unknown },
|
||||
_ => match f.cpool[op.imm.const_idx()] {
|
||||
JsValue::FnById(id) => ObjKind::Fn(Code::Fn(id)),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
};
|
||||
|
||||
created[op.dsts[0]] = Some(places.len());
|
||||
places.push(place(Place::Op { fn_id, instr }, kind));
|
||||
}
|
||||
OpCode::DefineField => {
|
||||
let defined = &mut places[created[op.args[1]].unwrap()].defined;
|
||||
|
||||
if let Err(i) = defined.binary_search(&op.imm.atom()) {
|
||||
defined.insert(i, op.imm.atom());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
places.extend((0..NATIVES.len()).map(|id| place(Place::Native(id), ObjKind::Fn(Code::Native(id)))));
|
||||
places.extend(ProtoOf::ALL.map(|kind| place(Place::ProtoLink(kind), ObjKind::Fn(Code::ProtoLink(kind)))));
|
||||
|
||||
let mut table = Self {
|
||||
places,
|
||||
sites: vec![],
|
||||
ctx_parts: vec![],
|
||||
ctx_starts: vec![0],
|
||||
sum_members: vec![],
|
||||
sum_starts: vec![0],
|
||||
version: 0,
|
||||
};
|
||||
|
||||
let empty = table.context(&[]);
|
||||
|
||||
for at in table.places.len() - NATIVES.len() - ProtoOf::ALL.len()..table.places.len() {
|
||||
table.site_at(at as PlaceId, empty);
|
||||
}
|
||||
|
||||
table
|
||||
}
|
||||
|
||||
/// The place `place`
|
||||
pub fn place_id(&self, place: Place) -> PlaceId {
|
||||
self.places.binary_search_by_key(&place, |p| p.place).unwrap() as PlaceId
|
||||
}
|
||||
|
||||
/// The site of a built-in fn
|
||||
pub fn builtin(&self, place: Place) -> SiteId {
|
||||
self.places[self.place_id(place) as usize].sites[0].1
|
||||
}
|
||||
|
||||
/// The context of a variant whose params have these types: the types,
|
||||
/// where each object member is named only by its place. So there are
|
||||
/// finitely many contexts, although a site's objects can be in a param of
|
||||
/// a variant whose own sites are new (recursion)
|
||||
pub fn context(&mut self, params: &[Type]) -> CtxId {
|
||||
let start = self.ctx_parts.len();
|
||||
|
||||
for ty in params {
|
||||
let members_start = self.ctx_parts.len();
|
||||
|
||||
for i in 0..self.members(ty).len() {
|
||||
let part = match self.members(ty)[i] {
|
||||
Type::Obj(site) => CtxPart::Place(self.sites[site as usize].place),
|
||||
member => CtxPart::Member(member),
|
||||
};
|
||||
|
||||
if !self.ctx_parts[members_start..].contains(&part) {
|
||||
self.ctx_parts.push(part);
|
||||
}
|
||||
}
|
||||
|
||||
self.ctx_parts.push(CtxPart::End);
|
||||
}
|
||||
|
||||
let new = &self.ctx_parts[start..];
|
||||
let starts = self.ctx_starts.windows(2);
|
||||
|
||||
match starts.map(|w| &self.ctx_parts[w[0] as usize..w[1] as usize]).position(|ctx| ctx == new) {
|
||||
Some(id) => {
|
||||
self.ctx_parts.truncate(start);
|
||||
id as CtxId
|
||||
}
|
||||
None => {
|
||||
self.ctx_starts.push(self.ctx_parts.len() as u32);
|
||||
(self.ctx_starts.len() - 2) as CtxId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The site of `place` in context `ctx`, made if it is new
|
||||
pub fn site_at(&mut self, place: PlaceId, ctx: CtxId) -> SiteId {
|
||||
if let Some(site) = self.find_site(place, ctx) {
|
||||
return site;
|
||||
}
|
||||
|
||||
let id = self.sites.len() as SiteId;
|
||||
let info = &mut self.places[place as usize];
|
||||
let fields = info.defined.iter().map(|&n| (n, Field { ty: Type::Unknown, defined: true })).collect();
|
||||
|
||||
info.sites.push((ctx, id));
|
||||
self.sites.push(SiteInfo {
|
||||
place,
|
||||
ctx,
|
||||
kind: info.kind,
|
||||
fields,
|
||||
written: false,
|
||||
compared: false,
|
||||
in_any: false,
|
||||
live: false,
|
||||
});
|
||||
|
||||
self.version += 1;
|
||||
id
|
||||
}
|
||||
|
||||
/// The site of `place` in context `ctx`, if infer made it
|
||||
pub fn find_site(&self, place: PlaceId, ctx: CtxId) -> Option<SiteId> {
|
||||
let sites = &self.places[place as usize].sites;
|
||||
sites.iter().find(|(c, _)| *c == ctx).map(|(_, site)| *site)
|
||||
}
|
||||
|
||||
pub fn site(&self, site: SiteId) -> &SiteInfo {
|
||||
&self.sites[site as usize]
|
||||
}
|
||||
|
||||
/// An object of the site, which a creating op gives
|
||||
pub fn create(&mut self, site: SiteId) -> Type {
|
||||
self.mark(site, |s| &mut s.live);
|
||||
Type::Obj(site)
|
||||
}
|
||||
|
||||
/// Set a flag of `site`. Whether it was not set before
|
||||
pub fn mark(&mut self, site: SiteId, flag: fn(&mut SiteInfo) -> &mut bool) -> bool {
|
||||
let flag = flag(&mut self.sites[site as usize]);
|
||||
|
||||
if *flag {
|
||||
return false;
|
||||
}
|
||||
|
||||
*flag = true;
|
||||
self.version += 1;
|
||||
true
|
||||
}
|
||||
|
||||
/// Whether an object of `site` can lack its own `field`. A box's value
|
||||
/// never can: JS gives a binding a value before any read of it that does
|
||||
/// not throw (see transform)
|
||||
pub fn can_be_absent(&self, site: SiteId, field: AtomRef) -> bool {
|
||||
field != BOX_FIELD && !self.site(site).field(field).is_some_and(|f| f.defined)
|
||||
}
|
||||
|
||||
/// A value of type `ty` goes into an `Any` value. Code that holds the
|
||||
/// `Any` does not know the site of an object in it, so the object's site
|
||||
/// is marked, and so are the sites of the objects in its fields, which
|
||||
/// that code can read
|
||||
pub fn to_any(&mut self, ty: &Type) -> Type {
|
||||
let mut stack = vec![];
|
||||
self.push_sites(ty, &mut stack);
|
||||
|
||||
while let Some(site) = stack.pop() {
|
||||
if self.mark(site, |s| &mut s.in_any) {
|
||||
for i in 0..self.site(site).fields.len() {
|
||||
let field = self.site(site).fields[i].1.ty;
|
||||
self.push_sites(&field, &mut stack);
|
||||
}
|
||||
|
||||
if let ObjKind::Array { elems } = self.site(site).kind {
|
||||
self.push_sites(&elems, &mut stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Type::Any
|
||||
}
|
||||
|
||||
/// Push the site of each object member of `ty`
|
||||
fn push_sites(&self, ty: &Type, stack: &mut Vec<SiteId>) {
|
||||
stack.extend(self.members(ty).iter().filter_map(|m| match m {
|
||||
Type::Obj(site) => Some(*site),
|
||||
_ => None,
|
||||
}));
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────── Fields ──────────────────────────────────────────────────
|
||||
|
||||
/// Join `ty` into `field` of the objects of `site`. A field that the site
|
||||
/// did not have can be absent. An array's `length` is not a field: a write
|
||||
/// of it sets the array's length (see `docs/model.md`, section 3.3)
|
||||
pub fn write(&mut self, site: SiteId, field: AtomRef, ty: &Type) {
|
||||
if field == LENGTH_FIELD && self.site(site).is_array() {
|
||||
return;
|
||||
}
|
||||
|
||||
let old = self.site(site).field(field).map_or(Type::Unknown, |f| f.ty);
|
||||
let new = self.join(&old, ty);
|
||||
|
||||
// Code that holds one of its objects in an `Any` can read the field
|
||||
if self.site(site).in_any {
|
||||
self.to_any(&new);
|
||||
}
|
||||
|
||||
let fields = &mut self.sites[site as usize].fields;
|
||||
|
||||
match fields.binary_search_by_key(&field, |(n, _)| *n) {
|
||||
Ok(i) if fields[i].1.ty == new => return,
|
||||
Ok(i) => fields[i].1.ty = new,
|
||||
Err(i) => fields.insert(i, (field, Field { ty: new, defined: false })),
|
||||
}
|
||||
|
||||
self.version += 1;
|
||||
}
|
||||
|
||||
/// Join `ty` into the element type of the arrays of `site`
|
||||
pub fn write_elem(&mut self, site: SiteId, ty: &Type) {
|
||||
let ObjKind::Array { elems } = self.site(site).kind else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let new = self.join(&elems, ty);
|
||||
|
||||
// Code that holds one of its arrays in an `Any` can read the elements
|
||||
if self.site(site).in_any {
|
||||
self.to_any(&new);
|
||||
}
|
||||
|
||||
if new != elems {
|
||||
self.sites[site as usize].kind = ObjKind::Array { elems: new };
|
||||
self.version += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// A read of an element of an array of `site`. An index can be a hole, or
|
||||
/// past the end, which gives undefined
|
||||
pub fn read_elem(&mut self, site: SiteId) -> Type {
|
||||
let ObjKind::Array { elems } = self.site(site).kind else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
self.join(&elems, &Type::Undefined)
|
||||
}
|
||||
|
||||
/// A read of `field` on an object of `site`: its own field, then, where
|
||||
/// that can be absent, the read on its prototype. At the end of the chain,
|
||||
/// an absent field is undefined. An array's prototype is the one that the
|
||||
/// stdlib links for arrays, in `protos`, and its `length` is not a field
|
||||
pub fn read(&mut self, site: SiteId, field: AtomRef, protos: &Protos) -> Type {
|
||||
self.read_chain(site, field, protos, self.sites.len())
|
||||
}
|
||||
|
||||
/// `read()`, with at most `depth` more prototypes. Objects of one site can
|
||||
/// be each other's prototypes, so the chain of sites can loop, which a
|
||||
/// chain of objects cannot. A chain longer than the number of sites has
|
||||
/// looped, and stops
|
||||
fn read_chain(&mut self, site: SiteId, field: AtomRef, protos: &Protos, depth: usize) -> Type {
|
||||
let array = self.site(site).is_array();
|
||||
|
||||
if array && field == LENGTH_FIELD {
|
||||
return Type::Int;
|
||||
}
|
||||
|
||||
let own = self.site(site).field(field).map_or(Type::Unknown, |f| f.ty);
|
||||
|
||||
if !self.can_be_absent(site, field) || depth == 0 {
|
||||
return own;
|
||||
}
|
||||
|
||||
// The chain ends at a prototype that is absent or not an object
|
||||
let (proto, proto_absent) = match array {
|
||||
true => (protos[ProtoOf::Array as usize], false),
|
||||
false => {
|
||||
let proto = self.site(site).field(PROTO_FIELD).map_or(Type::Unknown, |f| f.ty);
|
||||
(proto, self.can_be_absent(site, PROTO_FIELD))
|
||||
}
|
||||
};
|
||||
let mut ty = own;
|
||||
|
||||
if proto_absent || proto == Type::Unknown {
|
||||
ty = self.join(&ty, &Type::Undefined);
|
||||
}
|
||||
|
||||
for i in 0..self.members(&proto).len() {
|
||||
let read = match self.members(&proto)[i] {
|
||||
Type::Obj(proto) => self.read_chain(proto, field, protos, depth - 1),
|
||||
Type::Any => Type::Any,
|
||||
_ => Type::Undefined,
|
||||
};
|
||||
|
||||
ty = self.join(&ty, &read);
|
||||
}
|
||||
|
||||
ty
|
||||
}
|
||||
|
||||
/// A read of `field` on a value of type `ty`. A primitive reads its kind's
|
||||
/// prototype in `protos`. Undefined and null have no fields: the read
|
||||
/// throws, so it gives nothing. A sum reads on each of its members
|
||||
///
|
||||
/// A read on `Any` can read any object in an `Any`, or any primitive. What
|
||||
/// it reads goes into the `Any` that it gives
|
||||
pub fn read_value(&mut self, ty: &Type, field: AtomRef, protos: &Protos) -> Type {
|
||||
match *ty {
|
||||
Type::Obj(site) => self.read(site, field, protos),
|
||||
Type::Sum(_) => {
|
||||
let mut read = Type::Unknown;
|
||||
|
||||
for i in 0..self.members(ty).len() {
|
||||
let member = self.members(ty)[i];
|
||||
let ty = self.read_value(&member, field, protos);
|
||||
read = self.join(&read, &ty);
|
||||
}
|
||||
|
||||
read
|
||||
}
|
||||
Type::Any => {
|
||||
for site in 0..self.sites.len() as SiteId {
|
||||
if self.site(site).in_any {
|
||||
let read = self.read(site, field, protos);
|
||||
self.to_any(&read);
|
||||
}
|
||||
}
|
||||
|
||||
for kind in ProtoOf::ALL {
|
||||
if let Type::Obj(proto) = protos[kind as usize] {
|
||||
let read = self.read(proto, field, protos);
|
||||
self.to_any(&read);
|
||||
}
|
||||
}
|
||||
|
||||
Type::Any
|
||||
}
|
||||
ty => match ProtoOf::of(&ty).map(|kind| protos[kind as usize]) {
|
||||
Some(Type::Unknown) | None => Type::Unknown,
|
||||
Some(proto) => self.read_value(&proto, field, protos),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
+79
-145
@@ -1,15 +1,14 @@
|
||||
//! Types: the values that a place can hold, their joins, and what a type
|
||||
//! decides about the values in it (see `docs/model.md`, section 3.1)
|
||||
//! decides about the values in it (see `docs/model.md`, section 2)
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::libqjs::parse::AtomRef;
|
||||
|
||||
use super::sites::{SiteId, SiteTable};
|
||||
use super::cells::Cell;
|
||||
|
||||
pub(super) const EMPTY_STRING: AtomRef = AtomRef::Predefined(48); // ""
|
||||
|
||||
/// The number of a sum (see `SiteTable::members()`)
|
||||
pub type SumId = u32;
|
||||
|
||||
/// A type is one of these. Join rules:
|
||||
/// Unknown * T = T
|
||||
/// Any * T = Any
|
||||
@@ -21,8 +20,8 @@ pub type SumId = u32;
|
||||
/// A sum is a value of one of a list of types, its members. It has two or more
|
||||
/// members, none of them `Unknown`, `Sum` or `Any`, sorted in the order of the
|
||||
/// variants below. It has at most one member of each base type, except
|
||||
/// objects: one `Obj` for each site
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// objects: one `Obj` for each cell
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Type {
|
||||
Unknown, // no value has reached it yet
|
||||
Undefined,
|
||||
@@ -31,12 +30,12 @@ pub enum Type {
|
||||
Int,
|
||||
Float,
|
||||
Str(Option<AtomRef>), // the value, if it is known. Atoms are interned: equal atoms are equal strings
|
||||
Obj(SiteId), // an object that the site makes. A fn is an object whose site has code
|
||||
Sum(SumId), // see above
|
||||
Obj(Cell), // an object, which the cell describes. A fn is an object whose cell has code
|
||||
Sum(Rc<[Type]>), // see above
|
||||
Any, // any value. No rule gives it now: a join gives a sum instead
|
||||
}
|
||||
|
||||
/// A `Type` without its detail (a known value, a site): the kinds of value
|
||||
/// A `Type` without its detail (a known value, a cell): the kinds of value
|
||||
/// that JS tells apart
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BaseType {
|
||||
@@ -49,15 +48,6 @@ pub enum BaseType {
|
||||
}
|
||||
|
||||
impl Type {
|
||||
/// The type with the known value dropped
|
||||
pub fn widen(&self) -> Type {
|
||||
match self {
|
||||
Type::Bool(_) => Type::Bool(None),
|
||||
Type::Str(_) => Type::Str(None),
|
||||
ty => *ty,
|
||||
}
|
||||
}
|
||||
|
||||
/// The base type of every value of the type. `None` for `Unknown`, which
|
||||
/// has no values, and for `Sum` and `Any`, which can have more than one
|
||||
pub fn base(&self) -> Option<BaseType> {
|
||||
@@ -75,139 +65,87 @@ impl Type {
|
||||
pub fn is_nullish(&self) -> bool {
|
||||
matches!(self, Type::Undefined | Type::Null)
|
||||
}
|
||||
}
|
||||
|
||||
/// The join of two types of one base type, neither of them `Obj`
|
||||
fn join_kind(a: Type, b: Type) -> Type {
|
||||
match (a, b) {
|
||||
(Type::Bool(x), Type::Bool(y)) => Type::Bool(if x == y { x } else { None }),
|
||||
(Type::Str(x), Type::Str(y)) => Type::Str(if x == y { x } else { None }),
|
||||
(Type::Int, Type::Int) => Type::Int,
|
||||
(Type::Int | Type::Float, Type::Int | Type::Float) => Type::Float,
|
||||
(a, _) => a,
|
||||
}
|
||||
}
|
||||
|
||||
/// The sums, and the join, which makes them. The sums are stored in the site
|
||||
/// table, because a join also marks sites (see `SiteTable::to_any()`)
|
||||
impl SiteTable {
|
||||
/// The types that a value of type `ty` can have: the members of a sum,
|
||||
/// else `ty` itself. `Unknown` has none
|
||||
pub fn members<'a>(&'a self, ty: &'a Type) -> &'a [Type] {
|
||||
match ty {
|
||||
/// The types that a value of the type can have: the members of a sum,
|
||||
/// else the type itself. `Unknown` has none
|
||||
pub fn members(&self) -> &[Type] {
|
||||
match self {
|
||||
Type::Unknown => &[],
|
||||
Type::Sum(id) => {
|
||||
let id = *id as usize;
|
||||
&self.sum_members[self.sum_starts[id] as usize..self.sum_starts[id + 1] as usize]
|
||||
}
|
||||
Type::Sum(members) => members,
|
||||
ty => std::slice::from_ref(ty),
|
||||
}
|
||||
}
|
||||
|
||||
/// The members of every sum
|
||||
pub fn sums(&self) -> impl Iterator<Item = &[Type]> {
|
||||
self.sum_starts.windows(2).map(|w| &self.sum_members[w[0] as usize..w[1] as usize])
|
||||
}
|
||||
|
||||
/// The type whose members are the members of `a` and of `b`
|
||||
fn union(&mut self, a: &Type, b: &Type) -> Type {
|
||||
let start = self.sum_members.len();
|
||||
|
||||
for ty in [a, b] {
|
||||
for i in 0..self.members(ty).len() {
|
||||
let member = self.members(ty)[i];
|
||||
self.sum_members.push(member);
|
||||
}
|
||||
}
|
||||
|
||||
self.sum_of_new(start)
|
||||
}
|
||||
|
||||
/// `ty` with every known value dropped
|
||||
pub fn widen(&mut self, ty: &Type) -> Type {
|
||||
let start = self.sum_members.len();
|
||||
|
||||
for i in 0..self.members(ty).len() {
|
||||
let member = self.members(ty)[i].widen();
|
||||
self.sum_members.push(member);
|
||||
}
|
||||
|
||||
self.sum_of_new(start)
|
||||
}
|
||||
|
||||
/// `widen()` for a type that `widen()` was already given
|
||||
pub fn widened(&self, ty: &Type) -> Type {
|
||||
let list: Vec<Type> = self.members(ty).iter().map(Type::widen).collect();
|
||||
|
||||
match list[..] {
|
||||
[one] => one,
|
||||
_ => Type::Sum(self.sums().position(|sum| sum == list).unwrap() as SumId),
|
||||
/// The type with every known value dropped
|
||||
pub fn widen(&self) -> Type {
|
||||
match self {
|
||||
Type::Bool(_) => Type::Bool(None),
|
||||
Type::Str(_) => Type::Str(None),
|
||||
Type::Sum(members) => sum_of(members.iter().map(Type::widen).collect()),
|
||||
ty => ty.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The type whose members are the types at the end of `sum_members`, from
|
||||
/// `start` on. They are sorted, and two of one kind join. If they are one
|
||||
/// type, or a sum that exists, they are removed again
|
||||
fn sum_of_new(&mut self, start: usize) -> Type {
|
||||
let list = &mut self.sum_members[start..];
|
||||
list.sort_unstable();
|
||||
/// The join of two types (see `Type`)
|
||||
pub fn join(a: &Type, b: &Type) -> Type {
|
||||
match (a, b) {
|
||||
(Type::Unknown, t) | (t, Type::Unknown) => t.clone(),
|
||||
(a, b) if a == b => a.clone(),
|
||||
(Type::Any, _) | (_, Type::Any) => Type::Any,
|
||||
_ => {
|
||||
let joined = sum_of(a.members().iter().chain(b.members()).cloned().collect());
|
||||
|
||||
let mut n = 0;
|
||||
|
||||
for i in 0..list.len() {
|
||||
let ty = list[i];
|
||||
let joins = n > 0 && (list[n - 1] == ty || (ty.base() == list[n - 1].base() && ty.base() != Some(BaseType::Object)));
|
||||
|
||||
if joins {
|
||||
list[n - 1] = join_kind(list[n - 1], ty);
|
||||
} else {
|
||||
list[n] = ty;
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
|
||||
self.sum_members.truncate(start + n);
|
||||
|
||||
let new = &self.sum_members[start..];
|
||||
let found = match new {
|
||||
[one] => Some(*one),
|
||||
_ => self.sums().position(|sum| sum == new).map(|id| Type::Sum(id as SumId)),
|
||||
};
|
||||
|
||||
match found {
|
||||
Some(ty) => {
|
||||
self.sum_members.truncate(start);
|
||||
ty
|
||||
}
|
||||
None => {
|
||||
self.sum_starts.push(self.sum_members.len() as u32);
|
||||
Type::Sum((self.sum_starts.len() - 2) as SumId)
|
||||
// Keep the sum that exists, so that its members are not copied
|
||||
match () {
|
||||
_ if joined == *a => a.clone(),
|
||||
_ if joined == *b => b.clone(),
|
||||
_ => joined,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The join of two types (see `Type`)
|
||||
pub fn join(&mut self, a: &Type, b: &Type) -> Type {
|
||||
use Type::*;
|
||||
/// The type whose members are `list`: sorted, and two of one base type
|
||||
/// joined
|
||||
fn sum_of(mut list: Vec<Type>) -> Type {
|
||||
list.sort_unstable();
|
||||
list.dedup_by(|b, a| {
|
||||
let joins = a.base() == b.base() && a.base() != Some(BaseType::Object);
|
||||
|
||||
match (*a, *b) {
|
||||
(Unknown, t) | (t, Unknown) => t,
|
||||
(a, b) if a == b => a,
|
||||
(Any, t) | (t, Any) => self.to_any(&t),
|
||||
(a, b) => self.union(&a, &b),
|
||||
if joins {
|
||||
*a = join_base(a, b);
|
||||
}
|
||||
|
||||
joins || a == b
|
||||
});
|
||||
|
||||
match list.len() {
|
||||
1 => list.pop().unwrap(),
|
||||
_ => Type::Sum(list.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// The join of two types of one base type, neither of them `Obj`
|
||||
fn join_base(a: &Type, b: &Type) -> Type {
|
||||
match (a, b) {
|
||||
(Type::Bool(x), Type::Bool(y)) => Type::Bool(if x == y { *x } else { None }),
|
||||
(Type::Str(x), Type::Str(y)) => Type::Str(if x == y { *x } else { None }),
|
||||
(Type::Int, Type::Int) => Type::Int,
|
||||
(Type::Int | Type::Float, Type::Int | Type::Float) => Type::Float,
|
||||
(a, _) => a.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The type, if every value of it is one value that the type tells
|
||||
pub(super) fn single(ty: &Type) -> Option<Type> {
|
||||
match ty {
|
||||
Type::Undefined | Type::Null | Type::Bool(Some(_)) | Type::Str(Some(_)) => Some(*ty),
|
||||
Type::Undefined | Type::Null | Type::Bool(Some(_)) | Type::Str(Some(_)) => Some(ty.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// The tests below take one member of a type each (see `TypeCache::agree()`)
|
||||
// The tests below take one member of a type each (see `agree()` in infer)
|
||||
|
||||
/// Whether `a === b`, if the types decide it
|
||||
pub(super) fn strict_eq(a: &Type, b: &Type) -> Option<bool> {
|
||||
@@ -224,26 +162,23 @@ pub(super) fn strict_eq(a: &Type, b: &Type) -> Option<bool> {
|
||||
/// each other and to nothing else. Two values of one kind compare as `===`
|
||||
/// does
|
||||
pub(super) fn loose_eq(a: &Type, b: &Type) -> Option<bool> {
|
||||
let other = |t: &Type| !t.is_nullish() && *t != Type::Any;
|
||||
|
||||
match (a, b) {
|
||||
_ if a.is_nullish() && b.is_nullish() => Some(true),
|
||||
_ if (a.is_nullish() && other(b)) || (other(a) && b.is_nullish()) => Some(false),
|
||||
_ if a.base().is_some() && a.base() == b.base() => strict_eq(a, b),
|
||||
_ if a.is_nullish() || b.is_nullish() => Some(false),
|
||||
_ if a.base() == b.base() => strict_eq(a, b),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a value of type `ty` has one of the base types `bases`, if the type decides it
|
||||
/// Whether a value of type `ty` has one of the base types `bases`
|
||||
pub(super) fn test(ty: &Type, bases: &[BaseType]) -> Option<bool> {
|
||||
ty.base().map(|base| bases.contains(&base))
|
||||
}
|
||||
|
||||
/// Whether a value of type `ty` is a fn, if the type decides it
|
||||
pub(super) fn is_function(ty: &Type, site_table: &SiteTable) -> Option<bool> {
|
||||
/// Whether a value of type `ty` is a fn
|
||||
pub(super) fn is_function(ty: &Type) -> Option<bool> {
|
||||
match ty {
|
||||
Type::Obj(site) => Some(site_table.site(*site).code().is_some()),
|
||||
Type::Any => None,
|
||||
Type::Obj(cell) => Some(cell.code().is_some()),
|
||||
_ => Some(false),
|
||||
}
|
||||
}
|
||||
@@ -259,17 +194,16 @@ pub(super) fn truthiness(ty: &Type) -> Option<bool> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The atom of what `typeof` gives for a value of type `ty`, if the type
|
||||
/// decides it
|
||||
pub(super) fn typeof_name(ty: &Type, site_table: &SiteTable) -> Option<u32> {
|
||||
/// The atom of what `typeof` gives for a value of type `ty`
|
||||
pub(super) fn typeof_name(ty: &Type) -> Option<u32> {
|
||||
Some(match ty {
|
||||
Type::Undefined => 73, // "undefined"
|
||||
Type::Null => 77, // "object"
|
||||
Type::Bool(_) => 75, // "boolean"
|
||||
Type::Int | Type::Float => 74, // "number"
|
||||
Type::Str(_) => 76, // "string"
|
||||
Type::Obj(site) if site_table.site(*site).code().is_some() => 27, // "function"
|
||||
Type::Obj(_) => 77, // "object"
|
||||
Type::Undefined => 73, // "undefined"
|
||||
Type::Null => 77, // "object"
|
||||
Type::Bool(_) => 75, // "boolean"
|
||||
Type::Int | Type::Float => 74, // "number"
|
||||
Type::Str(_) => 76, // "string"
|
||||
Type::Obj(cell) if cell.code().is_some() => 27, // "function"
|
||||
Type::Obj(_) => 77, // "object"
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
mod analyze;
|
||||
mod emit;
|
||||
mod flatten;
|
||||
mod infer;
|
||||
@@ -10,7 +9,6 @@ mod transform;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
use analyze::analyze;
|
||||
use flatten::flatten;
|
||||
use infer::infer_types;
|
||||
use libqjs::compile;
|
||||
@@ -74,9 +72,8 @@ fn main() {
|
||||
let flat_fns = transform(flat_fns, &captures, &atoms);
|
||||
let reg_fns = regify(flat_fns);
|
||||
let ty_cache = infer_types(®_fns);
|
||||
let analysis = analyze(®_fns);
|
||||
|
||||
let c = emit::emit(®_fns, &ty_cache, &analysis, &atoms);
|
||||
let c = emit::emit(®_fns, &ty_cache, &atoms);
|
||||
|
||||
// Compile the C, with the runtime, to `a.out`
|
||||
let dir = std::env::temp_dir().join(format!("jsrt-{}", std::process::id()));
|
||||
|
||||
+6
-12
@@ -10,10 +10,7 @@
|
||||
//! kind. A field read on a primitive borrows its kind's prototype
|
||||
|
||||
use crate::{
|
||||
infer::{
|
||||
sites::SiteTable,
|
||||
types::{BaseType, Type},
|
||||
},
|
||||
infer::types::{BaseType, Type},
|
||||
libqjs::parse::{Atom, AtomRef, JsString, JsValue, atom_names::PREDEFINED_ATOMS},
|
||||
};
|
||||
|
||||
@@ -47,7 +44,7 @@ macro_rules! ty {
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub static NATIVES: &[NativeFn] = &[
|
||||
pub const NATIVES: &[NativeFn] = &[
|
||||
// IO
|
||||
native!("__native_print_string"(String) -> Undefined),
|
||||
|
||||
@@ -92,7 +89,6 @@ pub enum ProtoOf {
|
||||
|
||||
impl ProtoOf {
|
||||
pub const ALL: [ProtoOf; 4] = [ProtoOf::String, ProtoOf::Number, ProtoOf::Boolean, ProtoOf::Array];
|
||||
pub const PRIMITIVES: [ProtoOf; 3] = [ProtoOf::String, ProtoOf::Number, ProtoOf::Boolean];
|
||||
|
||||
/// The primitive kind of every value of type `ty`, if it has a prototype
|
||||
pub fn of(ty: &Type) -> Option<ProtoOf> {
|
||||
@@ -136,16 +132,14 @@ pub fn global(name: AtomRef, atoms: &[Atom]) -> JsValue {
|
||||
|
||||
/// What a call of native `idx` returns, with args of these types, in source
|
||||
/// order. An arg that cannot be of its param's kind makes the call throw, so
|
||||
/// the call gives nothing. An arg of a sum or `Any` is checked at runtime
|
||||
pub fn ret_type(idx: usize, args: &[Type], site_table: &SiteTable) -> Type {
|
||||
/// the call gives nothing. An arg of a sum is checked at runtime
|
||||
pub fn ret_type(idx: usize, args: &[Type]) -> Type {
|
||||
let native = &NATIVES[idx];
|
||||
let missing = std::iter::repeat(&Type::Undefined);
|
||||
let can_pass = |(param, arg): (&Type, &Type)| {
|
||||
*arg == Type::Any || site_table.members(arg).iter().any(|a| a.base() == param.base())
|
||||
};
|
||||
let can_pass = |(param, arg): (&Type, &Type)| arg.members().iter().any(|a| a.base() == param.base());
|
||||
|
||||
match native.args.iter().zip(args.iter().chain(missing)).all(can_pass) {
|
||||
true => native.ret,
|
||||
true => native.ret.clone(),
|
||||
false => Type::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,10 +118,11 @@ impl RegFn {
|
||||
}
|
||||
}
|
||||
|
||||
// A define_field into an object that no creating op of this fn gave
|
||||
// (a class field on `this`, a static field on the class) is a write
|
||||
// like any other: a put_field. So a define_field is always a write
|
||||
// that builds an object before it goes anywhere (see infer)
|
||||
// A define_field into an object that no op of this fn made (a class
|
||||
// field on `this`, a static field on the class) is a write like any
|
||||
// other: a put_field. So a define_field is always a write into an
|
||||
// object before it goes anywhere, and its field is never absent (see
|
||||
// `Cell::write()` in infer)
|
||||
let mut created = vec![false; next_val];
|
||||
|
||||
for instr in &out {
|
||||
@@ -265,17 +266,10 @@ fn walk_block(
|
||||
continue;
|
||||
}
|
||||
|
||||
// A field or element write lands back in the object's own value: the
|
||||
// object with the field is the same object. define_field also leaves
|
||||
// it on the stack, put_field and put_array_el do not
|
||||
if matches!(instr.op, OpCode::PutField | OpCode::DefineField | OpCode::PutArrayEl) {
|
||||
let obj = *out_instr.args.last().unwrap();
|
||||
|
||||
if n_push == 1 {
|
||||
stack.push(obj);
|
||||
}
|
||||
|
||||
out_instr.dsts.push(obj);
|
||||
// define_field leaves the object it wrote on the stack: the same value,
|
||||
// not a new one
|
||||
if instr.op == OpCode::DefineField {
|
||||
stack.push(out_instr.args[1]);
|
||||
out[instr_idx] = out_instr;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,9 @@ pub fn replace_expansion(instr: &Instr, out: &mut Vec<Instr>) -> bool {
|
||||
// [[HomeObject]], which only `super` reads. `super` is not supported
|
||||
// (see set_home_object in regify)
|
||||
(SpecialObject, ImmOperand::U8(4)) => &[(Undefined, none)],
|
||||
// A class constructor called without `new` throws. That is not
|
||||
// checked
|
||||
(CheckCtor, _) => &[(Nop, none)],
|
||||
// arguments, new.target, import.meta
|
||||
(SpecialObject, _) => unimplemented!("special_object {imm:?}"),
|
||||
_ => return false,
|
||||
|
||||
Reference in New Issue
Block a user