Locked in type model
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,102 @@
|
|||||||
|
//! Calls of fns, natives and proto links
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl<'a> Emitter<'a> {
|
||||||
|
/// A call from `place`. The callee's cell tells which code runs, and the
|
||||||
|
/// callee is its own env. A callee that is not a fn throws
|
||||||
|
pub(super) fn call(&mut self, res: &InferResult, place: Place, instr: &RegInstr, out: &mut String) {
|
||||||
|
let arg_types: Vec<Type> = instr.args.iter().map(|&a| res.val_types[a].clone()).collect();
|
||||||
|
let operands: Vec<(String, Rep)> = (instr.args.iter().zip(&arg_types))
|
||||||
|
.map(|(a, ty)| (format!("v{a}"), self.rep(ty)))
|
||||||
|
.collect();
|
||||||
|
let dst = (instr.dsts[0], self.rep(&res.val_types[instr.dsts[0]]));
|
||||||
|
|
||||||
|
self.dispatch(&operands[0].0, &arg_types[0], "jsrt_type_error();", out, |e, member, callee, rep| {
|
||||||
|
let Type::Obj(cell) = member else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut arg_types = arg_types.clone();
|
||||||
|
let mut operands = operands.clone();
|
||||||
|
arg_types[0] = member.clone();
|
||||||
|
operands[0] = (callee.into(), rep);
|
||||||
|
|
||||||
|
let (call, ret) = e.call_code(cell.code()?, place, &arg_types, &operands);
|
||||||
|
Some(e.call_result(instr.op, &call, &ret, &operands[THIS_ARG as usize], dst))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The statement that runs `call`, which gives a value of type `ret`, and
|
||||||
|
/// puts the call's result in `dst` (its value, and its rep). `new` gives
|
||||||
|
/// what the constructor returns if that is an object, else the new object
|
||||||
|
/// itself, `this` (see infer)
|
||||||
|
pub(super) fn call_result(&self, op: OpCode, call: &str, ret: &Type, this: &(String, Rep), dst: (usize, Rep)) -> String {
|
||||||
|
let (dst, dst_rep) = (format!("v{}", dst.0), dst.1);
|
||||||
|
let ret_rep = self.rep(ret);
|
||||||
|
let members = ret.members();
|
||||||
|
let objects = members.iter().filter(|m| matches!(m, Type::Obj(_))).count();
|
||||||
|
let this = self.conv(&this.0, this.1, dst_rep);
|
||||||
|
|
||||||
|
match op {
|
||||||
|
_ if *ret == Type::Unknown => format!("{call};"),
|
||||||
|
OpCode::CallConstructor if objects == 0 => format!("{call}; {dst} = {this};"),
|
||||||
|
OpCode::CallConstructor if objects < members.len() => {
|
||||||
|
let is_object = match self.tagged("r", ret_rep) {
|
||||||
|
Some(a) => format!("__any_is_object({a})"),
|
||||||
|
None => format!("!{}", self.nullish("r", ret)),
|
||||||
|
};
|
||||||
|
let value = self.conv("r", ret_rep, dst_rep);
|
||||||
|
format!("{{ {} r = {call}; {dst} = {is_object} ? {value} : {this}; }}", self.c_type(ret_rep))
|
||||||
|
}
|
||||||
|
_ => format!("{dst} = {};", self.conv(call, ret_rep, dst_rep)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The C call of `code` with these operands (the callee, `this`, then the
|
||||||
|
/// args, each with its rep) whose types are `arg_types`, and the type it
|
||||||
|
/// returns
|
||||||
|
pub(super) fn call_code(&mut self, code: Code, place: Place, arg_types: &[Type], operands: &[(String, Rep)]) -> (String, Type) {
|
||||||
|
// An operand as a value of rep `to`. One that the call does not pass is
|
||||||
|
// undefined
|
||||||
|
let operand = |e: &Self, i: usize, to: Rep| match operands.get(i) {
|
||||||
|
Some((value, rep)) => e.conv(value, *rep, to),
|
||||||
|
None => e.conv("(undefined_t){0}", Rep::Undefined, to),
|
||||||
|
};
|
||||||
|
|
||||||
|
match code {
|
||||||
|
Code::Fn(fn_id) => {
|
||||||
|
let w = self.cache.find(fn_id, place, arg_types, self.fns);
|
||||||
|
self.use_walk(w);
|
||||||
|
|
||||||
|
let walk = &self.cache.walks[w];
|
||||||
|
let args: Vec<String> = (walk.req.arg_types.iter().enumerate())
|
||||||
|
.map(|(i, param)| operand(self, i, self.rep(param)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let ret = walk.result.as_ref().unwrap().ret_type.clone();
|
||||||
|
(format!("{}({})", self.fn_name(w), args.join(", ")), ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A native whose args cannot be of its params' kinds throws
|
||||||
|
Code::Native(id) if native::ret_type(id, &arg_types[FIRST_ARG as usize..]) == Type::Unknown => {
|
||||||
|
("jsrt_type_error()".into(), Type::Unknown)
|
||||||
|
}
|
||||||
|
Code::Native(id) => {
|
||||||
|
let native = &NATIVES[id];
|
||||||
|
|
||||||
|
let args: Vec<String> = (native.args.iter().enumerate())
|
||||||
|
.map(|(i, param)| operand(self, FIRST_ARG as usize + i, self.rep(param)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
(format!("{}({})", native.name, args.join(", ")), native.ret.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keeps the prototype where field reads on primitives find it
|
||||||
|
Code::ProtoLink(kind) => {
|
||||||
|
let value = operand(self, FIRST_ARG as usize, self.rep(&self.cache.protos[kind as usize]));
|
||||||
|
(format!("({} = {value}, (undefined_t){{0}})", proto_global(kind)), Type::Undefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
//! Which cells and walks C cannot tell apart
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Groups the cells and the walks that C cannot tell apart. Two cells are the
|
||||||
|
/// same in C if their kinds, code and fields are, with each cell in a field's
|
||||||
|
/// type replaced by its class. Two walks are the same if their fns, the types
|
||||||
|
/// of their values and instrs, their reachable blocks and the classes of the
|
||||||
|
/// walks they call are. Both start in one class each, which splits until no
|
||||||
|
/// class splits. Gives the class of each cell, a cell of each class, and for
|
||||||
|
/// each walk, the first walk of its class
|
||||||
|
pub(super) fn classes(fns: &[RegFn], cache: &TypeCache) -> (HashMap<Cell, CellNum>, Vec<Cell>, Vec<usize>) {
|
||||||
|
let cells: Vec<Cell> = cache.cells().cloned().collect();
|
||||||
|
let mut class: HashMap<Cell, CellNum> = cells.iter().map(|c| (c.clone(), 0)).collect();
|
||||||
|
let mut sig = vec![];
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut ids: HashMap<Vec<u32>, CellNum> = HashMap::new();
|
||||||
|
let next: Vec<CellNum> = cells
|
||||||
|
.iter()
|
||||||
|
.map(|cell| {
|
||||||
|
sig.clear();
|
||||||
|
sig.push(class[cell]);
|
||||||
|
cell_sig(cell, &class, &mut sig);
|
||||||
|
let n = ids.len() as CellNum;
|
||||||
|
*ids.entry(sig.clone()).or_insert(n)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let done = ids.len() == class.values().collect::<HashSet<_>>().len();
|
||||||
|
cells.iter().zip(next).for_each(|(cell, n)| *class.get_mut(cell).unwrap() = n);
|
||||||
|
|
||||||
|
if done {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut reps: Vec<Option<Cell>> = vec![None; class.values().max().map_or(0, |&m| m as usize + 1)];
|
||||||
|
cells.iter().for_each(|cell| _ = reps[class[cell] as usize].get_or_insert_with(|| cell.clone()));
|
||||||
|
|
||||||
|
// Walks. A walk that no round reached has no fn, and its own class
|
||||||
|
let walks = &cache.walks;
|
||||||
|
let mut wclass: Vec<u32> = (0..walks.len()).map(|w| if walks[w].result.is_some() { 0 } else { w as u32 + 1 }).collect();
|
||||||
|
let callees: Vec<Vec<usize>> = (0..walks.len()).map(|w| callees_of(fns, cache, w)).collect();
|
||||||
|
let mut count = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut ids: HashMap<Vec<u32>, u32> = HashMap::new();
|
||||||
|
let next: Vec<u32> = (0..walks.len())
|
||||||
|
.map(|w| {
|
||||||
|
sig.clear();
|
||||||
|
sig.push(wclass[w]);
|
||||||
|
|
||||||
|
if let Some(res) = &walks[w].result {
|
||||||
|
sig.push(walks[w].req.fn_id as u32);
|
||||||
|
walks[w].req.arg_types.iter().chain(&res.val_types).chain(&res.instr_types).for_each(|t| type_sig(t, &class, &mut sig));
|
||||||
|
type_sig(&res.ret_type, &class, &mut sig);
|
||||||
|
sig.extend(res.reachable.iter().map(|&r| r as u32));
|
||||||
|
sig.extend(callees[w].iter().map(|&c| wclass[c]));
|
||||||
|
}
|
||||||
|
|
||||||
|
let n = ids.len() as u32;
|
||||||
|
*ids.entry(sig.clone()).or_insert(n)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
wclass = next;
|
||||||
|
|
||||||
|
if ids.len() == count {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
count = ids.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut first: HashMap<u32, usize> = HashMap::new();
|
||||||
|
let walk_rep = (0..walks.len()).map(|w| *first.entry(wclass[w]).or_insert(w)).collect();
|
||||||
|
|
||||||
|
(class, reps.into_iter().map(Option::unwrap).collect(), walk_rep)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The walks that walk `w` calls, in the order that emit writes the calls
|
||||||
|
pub(super) fn callees_of(fns: &[RegFn], cache: &TypeCache, w: usize) -> Vec<usize> {
|
||||||
|
let Some(res) = &cache.walks[w].result else { return vec![] };
|
||||||
|
let fn_id = cache.walks[w].req.fn_id;
|
||||||
|
let f = &fns[fn_id];
|
||||||
|
let mut out = vec![];
|
||||||
|
|
||||||
|
let blocks = f.blocks.iter().enumerate().filter(|(b, _)| res.reachable[*b]);
|
||||||
|
let code = blocks.flat_map(|(_, block): (usize, &BasicBlock)| (block.start..block.end).map(|idx| (idx, &f.code[idx])));
|
||||||
|
|
||||||
|
for (idx, instr) in code {
|
||||||
|
if !matches!(instr.op, OpCode::Call | OpCode::CallMethod | OpCode::CallConstructor)
|
||||||
|
|| instr.args.iter().any(|&a| res.val_types[a] == Type::Unknown)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut args: Vec<Type> = instr.args.iter().map(|&a| res.val_types[a].clone()).collect();
|
||||||
|
|
||||||
|
for member in res.val_types[instr.args[0]].members() {
|
||||||
|
if let Type::Obj(cell) = member
|
||||||
|
&& let Some(Code::Fn(callee)) = cell.code()
|
||||||
|
{
|
||||||
|
args[0] = member.clone();
|
||||||
|
out.push(cache.find(callee, (fn_id, idx), &args, fns));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Append what C sees of `cell` to `sig`
|
||||||
|
pub(super) fn cell_sig(cell: &Cell, class: &HashMap<Cell, CellNum>, sig: &mut Vec<u32>) {
|
||||||
|
let info = cell.info();
|
||||||
|
|
||||||
|
match &info.kind {
|
||||||
|
ObjKind::Plain => sig.push(0),
|
||||||
|
ObjKind::Fn(code) => sig.extend(match *code {
|
||||||
|
Code::Fn(id) => [1, id as u32],
|
||||||
|
Code::Native(id) => [2, id as u32],
|
||||||
|
Code::ProtoLink(kind) => [3, kind as u32],
|
||||||
|
}),
|
||||||
|
ObjKind::Array { elems } => {
|
||||||
|
sig.push(4);
|
||||||
|
type_sig(elems, class, sig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sig.push(info.fields.len() as u32);
|
||||||
|
|
||||||
|
for (name, field) in &info.fields {
|
||||||
|
atom_sig(*name, sig);
|
||||||
|
sig.push(field.defined as u32);
|
||||||
|
type_sig(&field.ty, class, sig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Append `ty` to `sig`, with each cell as its class. No encoding is a prefix
|
||||||
|
/// of another, so a list of them reads back one way
|
||||||
|
pub(super) fn type_sig(ty: &Type, class: &HashMap<Cell, CellNum>, sig: &mut Vec<u32>) {
|
||||||
|
match ty {
|
||||||
|
Type::Unknown => sig.push(0),
|
||||||
|
Type::Undefined => sig.push(1),
|
||||||
|
Type::Null => sig.push(2),
|
||||||
|
Type::Bool(b) => sig.push(3 + b.map_or(0, |b| 1 + b as u32)),
|
||||||
|
Type::Int => sig.push(6),
|
||||||
|
Type::Float => sig.push(7),
|
||||||
|
Type::Str(None) => sig.extend([8, u32::MAX]),
|
||||||
|
Type::Str(Some(atom)) => {
|
||||||
|
sig.push(8);
|
||||||
|
atom_sig(*atom, sig);
|
||||||
|
}
|
||||||
|
Type::Obj(cell) => sig.extend([10, class[cell]]),
|
||||||
|
Type::Sum(members) => {
|
||||||
|
let mut parts: Vec<Vec<u32>> = members
|
||||||
|
.iter()
|
||||||
|
.map(|m| {
|
||||||
|
let mut part = vec![];
|
||||||
|
type_sig(m, class, &mut part);
|
||||||
|
part
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
parts.sort_unstable();
|
||||||
|
parts.dedup();
|
||||||
|
sig.extend([11, parts.len() as u32]);
|
||||||
|
parts.iter().for_each(|p| sig.extend(p));
|
||||||
|
}
|
||||||
|
Type::Any => sig.push(12),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn atom_sig(atom: AtomRef, sig: &mut Vec<u32>) {
|
||||||
|
sig.extend(match atom {
|
||||||
|
AtomRef::TaggedInt(n) => [0, n],
|
||||||
|
AtomRef::Predefined(n) => [1, n],
|
||||||
|
AtomRef::FileLocal(n) => [2, n as u32],
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
//! Conversions between representations, `dispatch` over the members of a
|
||||||
|
//! sum, and the tests on values that C needs
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl<'a> Emitter<'a> {
|
||||||
|
/// `e`, a value of rep `from`, as a value of rep `to`. `e` is evaluated
|
||||||
|
/// once, even where its value is not needed, because it can be a call
|
||||||
|
///
|
||||||
|
/// Types grow in a join, so most conversions go to a rep that holds more:
|
||||||
|
/// to a sum, or from `Int` to `Double`. A conversion to a rep that holds
|
||||||
|
/// less checks the kind, and a failed check is a `TypeError`: for example
|
||||||
|
/// a string that can be undefined, passed to a native's string param. A
|
||||||
|
/// value of a param that the caller does not pass is undefined
|
||||||
|
pub(super) fn conv(&self, e: &str, from: Rep, to: Rep) -> String {
|
||||||
|
if from == to {
|
||||||
|
return e.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
// A tagged union is made in place from a value of one kind that it
|
||||||
|
// has. Else it goes through `any_t`, which the C compiler removes
|
||||||
|
match (from, to) {
|
||||||
|
(Rep::Sum(_), _) => return self.conv(&self.tagged(e, from).unwrap(), Rep::Any, to),
|
||||||
|
(_, Rep::Sum(k)) if let Some(made) = self.make_sum(e, from, k) => return made,
|
||||||
|
(_, Rep::Sum(_)) => return format!("any_{}({})", self.c_type(to), self.conv(e, from, Rep::Any)),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let c_to = self.c_type(to);
|
||||||
|
let after_e = |value: String| format!("((void)({e}), {value})");
|
||||||
|
|
||||||
|
match (from, to) {
|
||||||
|
(_, Rep::Any) => match from {
|
||||||
|
Rep::Undefined => after_e("__any_undefined()".into()),
|
||||||
|
Rep::Null => after_e("__any_null()".into()),
|
||||||
|
Rep::Bool => format!("__any_from_bool({e})"),
|
||||||
|
Rep::Int => format!("__any_from_int({e})"),
|
||||||
|
Rep::Double => format!("__any_from_float({e})"),
|
||||||
|
Rep::Str | Rep::OptStr => format!("__any_from_string({e})"),
|
||||||
|
Rep::Obj(_) | Rep::OptObj(_) | Rep::Objs => format!("__any_from_object({e})"),
|
||||||
|
Rep::Sum(_) | Rep::Any => unreachable!(),
|
||||||
|
},
|
||||||
|
(Rep::Any, _) => match to {
|
||||||
|
Rep::Undefined => after_e("(undefined_t){0}".into()),
|
||||||
|
Rep::Null => after_e("(null_t){0}".into()),
|
||||||
|
Rep::Bool => format!("__any_as_bool({e})"),
|
||||||
|
Rep::Int => format!("__any_as_int({e})"),
|
||||||
|
Rep::Double => format!("__any_as_float({e})"),
|
||||||
|
Rep::Str => format!("(({c_to})jsrt_some(__any_as_string({e})))"),
|
||||||
|
Rep::Obj(_) => format!("(({c_to})jsrt_some(__any_as_object({e})))"),
|
||||||
|
Rep::OptStr => format!("__any_as_string({e})"),
|
||||||
|
Rep::OptObj(_) | Rep::Objs => format!("(({c_to})__any_as_object({e}))"),
|
||||||
|
Rep::Sum(_) | Rep::Any => unreachable!(),
|
||||||
|
},
|
||||||
|
(Rep::Int, Rep::Double) => format!("(double){e}"),
|
||||||
|
(Rep::Double, Rep::Int) => format!("(int32_t){e}"),
|
||||||
|
(Rep::Str, Rep::OptStr) | (Rep::Obj(_), Rep::OptObj(_)) => e.into(),
|
||||||
|
(Rep::OptStr, Rep::Str) | (Rep::OptObj(_), Rep::Obj(_)) => format!("(({c_to})jsrt_some({e}))"),
|
||||||
|
(Rep::Obj(_) | Rep::OptObj(_), Rep::Objs) => format!("((jsrt_header *){e})"),
|
||||||
|
(Rep::Undefined, Rep::OptStr | Rep::OptObj(_) | Rep::Objs) => after_e(format!("(({c_to})JSRT_UNDEFINED)")),
|
||||||
|
(Rep::Null, Rep::OptStr | Rep::OptObj(_) | Rep::Objs) => after_e(format!("(({c_to})NULL)")),
|
||||||
|
|
||||||
|
// Reps that correct types never make meet
|
||||||
|
_ => after_e(format!("({c_to}){{0}} /* mismatch */")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `dst = expr`, where `expr` is a value of rep `from`
|
||||||
|
pub(super) fn assign(&self, out: &mut String, dst: &str, expr: &str, from: Rep, to: Rep) {
|
||||||
|
let _ = writeln!(out, " {dst} = {};", self.conv(expr, from, to));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `e`, a value of rep `from`, as a tagged union of kinds `k`, if `from`
|
||||||
|
/// has one kind and `k` has it, with no sentinels to tell apart
|
||||||
|
pub(super) fn make_sum(&self, e: &str, from: Rep, k: Kinds) -> Option<String> {
|
||||||
|
let (kind, member) = match from {
|
||||||
|
Rep::Undefined => (1, None),
|
||||||
|
Rep::Null => (2, None),
|
||||||
|
Rep::Bool => (3, Some("b")),
|
||||||
|
Rep::Int => (4, Some("i")),
|
||||||
|
Rep::Double => (5, Some("f")),
|
||||||
|
Rep::Str => (6, Some("s")),
|
||||||
|
Rep::Obj(_) => (7, Some("o")),
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if k & 1 << kind == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let t = self.c_type(Rep::Sum(k));
|
||||||
|
let kind = KINDS[kind].0;
|
||||||
|
|
||||||
|
Some(match member {
|
||||||
|
Some(m) => format!("(({t}){{ .kind = JSRT_KIND_{kind}, .as.{m} = {e} }})"),
|
||||||
|
None => format!("((void)({e}), ({t}){{ .kind = JSRT_KIND_{kind} }})"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `e`, a value of rep `rep`, as an `any_t`, if `rep` is a tagged value
|
||||||
|
pub(super) fn tagged(&self, e: &str, rep: Rep) -> Option<String> {
|
||||||
|
match rep {
|
||||||
|
Rep::Any => Some(e.into()),
|
||||||
|
Rep::Sum(_) => Some(format!("{}_any({e})", self.c_type(rep))),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The absent value of a field of rep `rep`
|
||||||
|
pub(super) fn absent(&self, rep: Rep) -> String {
|
||||||
|
match rep {
|
||||||
|
Rep::Any | Rep::Sum(_) => self.conv("__any_absent()", Rep::Any, rep),
|
||||||
|
_ => format!("(({})JSRT_ABSENT)", self.c_type(rep)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A C bool that is true if `e`, a field of rep `rep`, is not absent
|
||||||
|
pub(super) fn present(&self, e: &str, rep: Rep) -> String {
|
||||||
|
match self.tagged(e, rep) {
|
||||||
|
Some(a) => format!("!__any_is_absent({a})"),
|
||||||
|
None => format!("((void *){e} != JSRT_ABSENT)"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `e`, an `any_t` that holds an object of cell `id`, as a pointer to its
|
||||||
|
/// struct
|
||||||
|
pub(super) fn any_pointer(&self, e: &str, id: CellNum) -> String {
|
||||||
|
format!("(({} *)__any_as_object({e}))", struct_name(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A C bool that is true if `e`, a value of type `ty`, is null or
|
||||||
|
/// undefined
|
||||||
|
pub(super) fn nullish(&self, e: &str, ty: &Type) -> String {
|
||||||
|
let can_be = ty.members().iter().any(Type::is_nullish);
|
||||||
|
|
||||||
|
match self.rep(ty) {
|
||||||
|
Rep::Undefined | Rep::Null => "true".into(),
|
||||||
|
rep @ (Rep::Any | Rep::Sum(_)) => format!("__any_is_nullish({})", self.tagged(e, rep).unwrap()),
|
||||||
|
Rep::OptStr | Rep::OptObj(_) | Rep::Objs if can_be => {
|
||||||
|
format!("((void *){e} == NULL || (void *){e} == JSRT_UNDEFINED)")
|
||||||
|
}
|
||||||
|
_ => "false".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes `body`'s statement for each member of `ty`, the type of `e`. A
|
||||||
|
/// statement gets the member, and `e` as a value of that member and its
|
||||||
|
/// rep. At runtime, a member is told apart by its kind, and an object by
|
||||||
|
/// the cell in its header. `body` gives `None` for a member on which the
|
||||||
|
/// op throws. A value that no statement takes runs `otherwise`
|
||||||
|
pub(super) fn dispatch(
|
||||||
|
&mut self,
|
||||||
|
e: &str,
|
||||||
|
ty: &Type,
|
||||||
|
otherwise: &str,
|
||||||
|
out: &mut String,
|
||||||
|
mut body: impl FnMut(&mut Self, &Type, &str, Rep) -> Option<String>,
|
||||||
|
) {
|
||||||
|
let rep = self.rep(ty);
|
||||||
|
|
||||||
|
if let [member] = ty.members() {
|
||||||
|
let stmt = body(self, member, e, rep);
|
||||||
|
let _ = writeln!(out, " {}", stmt.as_deref().unwrap_or(otherwise));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = write!(out, " ");
|
||||||
|
let mut objects = vec![];
|
||||||
|
|
||||||
|
for member in ty.members() {
|
||||||
|
let (value, member_rep) = self.member_value(e, rep, member);
|
||||||
|
|
||||||
|
match (member, body(self, member, &value, member_rep)) {
|
||||||
|
(_, None) => {}
|
||||||
|
// Cells of one class do the same
|
||||||
|
(Type::Obj(cell), Some(_)) if objects.iter().any(|(id, _)| *id == self.num(cell)) => {}
|
||||||
|
(Type::Obj(cell), Some(stmt)) => objects.push((self.num(cell), stmt)),
|
||||||
|
(_, Some(stmt)) => {
|
||||||
|
let _ = write!(out, "if ({}) {{ {stmt} }} else ", self.is_member(e, rep, member));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The objects, by cell. A pointer to the objects of one cell needs no
|
||||||
|
// switch
|
||||||
|
let is_object = match self.tagged(e, rep) {
|
||||||
|
Some(a) => format!("__any_is_object({a})"),
|
||||||
|
None => format!("!{}", self.nullish(e, ty)),
|
||||||
|
};
|
||||||
|
|
||||||
|
match (rep, &objects[..]) {
|
||||||
|
(_, []) => {}
|
||||||
|
(Rep::OptObj(_), [(_, stmt)]) => {
|
||||||
|
let _ = write!(out, "if ({is_object}) {{ {stmt} }} else ");
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let cell = match self.tagged(e, rep) {
|
||||||
|
Some(a) => format!("((jsrt_header *)__any_as_object({a}))->cell"),
|
||||||
|
None => format!("{e}->cell"),
|
||||||
|
};
|
||||||
|
let _ = writeln!(out, "if ({is_object}) switch ({cell}) {{");
|
||||||
|
|
||||||
|
for (id, stmt) in &objects {
|
||||||
|
let _ = writeln!(out, " case {id}: {{ {stmt} }} break;");
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = write!(out, " default: {otherwise}\n }} else ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = writeln!(out, "{otherwise}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `e`, a value of rep `rep`, as a value of `member`, a member of its type,
|
||||||
|
/// and that value's rep
|
||||||
|
pub(super) fn member_value(&self, e: &str, rep: Rep, member: &Type) -> (String, Rep) {
|
||||||
|
let member_rep = self.rep(member);
|
||||||
|
|
||||||
|
let value = match (rep, member) {
|
||||||
|
(Rep::Any | Rep::Sum(_), Type::Obj(cell)) => self.any_pointer(&self.tagged(e, rep).unwrap(), self.num(cell)),
|
||||||
|
(Rep::Any | Rep::Sum(_), _) => self.conv(e, rep, member_rep),
|
||||||
|
(Rep::Objs, Type::Obj(cell)) => format!("(({} *){e})", struct_name(self.num(cell))),
|
||||||
|
(_, Type::Undefined | Type::Null) => format!("({}){{0}}", self.c_type(member_rep)),
|
||||||
|
_ => e.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
(value, member_rep)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A C bool that is true if `e`, a value of rep `rep`, is of `member`, a
|
||||||
|
/// member of its type that is not an object
|
||||||
|
pub(super) fn is_member(&self, e: &str, rep: Rep, member: &Type) -> String {
|
||||||
|
let kind = match member {
|
||||||
|
Type::Undefined => "undefined",
|
||||||
|
Type::Null => "null",
|
||||||
|
Type::Bool(_) => "boolean",
|
||||||
|
Type::Int | Type::Float => "number",
|
||||||
|
_ => "string",
|
||||||
|
};
|
||||||
|
|
||||||
|
match (rep, member) {
|
||||||
|
(Rep::Any | Rep::Sum(_), _) => format!("__any_is_{kind}({})", self.tagged(e, rep).unwrap()),
|
||||||
|
(_, Type::Undefined) => format!("((void *){e} == JSRT_UNDEFINED)"),
|
||||||
|
(_, Type::Null) => format!("((void *){e} == NULL)"),
|
||||||
|
_ => format!("((void *){e} != NULL && (void *){e} != JSRT_UNDEFINED)"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A C bool that is JS's truthiness of `e`, a value of type `ty`
|
||||||
|
pub(super) fn truthy(&self, e: &str, ty: &Type) -> String {
|
||||||
|
match self.rep(ty) {
|
||||||
|
Rep::Bool => e.into(),
|
||||||
|
Rep::Int => format!("({e} != 0)"),
|
||||||
|
Rep::Double => format!("({e} != 0 && {e} == {e})"),
|
||||||
|
Rep::Str => format!("(jsrt_string_length({e}) != 0)"),
|
||||||
|
Rep::OptStr => format!("(!{} && jsrt_string_length({e}) != 0)", self.nullish(e, ty)),
|
||||||
|
Rep::Obj(_) => "true".into(),
|
||||||
|
Rep::OptObj(_) | Rep::Objs => format!("!{}", self.nullish(e, ty)),
|
||||||
|
Rep::Undefined | Rep::Null => "false".into(),
|
||||||
|
rep @ (Rep::Any | Rep::Sum(_)) => format!("__any_truthy({})", self.tagged(e, rep).unwrap()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `e`, a value of type `ty`, as a string, like the spec's ToString
|
||||||
|
pub(super) fn to_string(&self, e: &str, ty: &Type) -> String {
|
||||||
|
match self.rep(ty) {
|
||||||
|
Rep::Str => e.into(),
|
||||||
|
Rep::Int | Rep::Double => format!("__native_number_to_string((double){e})"),
|
||||||
|
Rep::Bool => {
|
||||||
|
let (t, f) = (self.string_lit("true"), self.string_lit("false"));
|
||||||
|
format!("({e} ? {t} : {f})")
|
||||||
|
}
|
||||||
|
Rep::Undefined => self.string_lit("undefined"),
|
||||||
|
Rep::Null => self.string_lit("null"),
|
||||||
|
rep => format!("__any_to_string({})", self.conv(e, rep, Rep::Any)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn string_lit(&self, s: &str) -> String {
|
||||||
|
format!("jsrt_string_lit({})", c_str(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
//! The C of each instr
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl<'a> Emitter<'a> {
|
||||||
|
pub(super) fn emit_instr(&mut self, fn_id: usize, res: &InferResult, idx: usize, instr: &RegInstr, out: &mut String) {
|
||||||
|
use OpCode::*;
|
||||||
|
|
||||||
|
if matches!(instr.op, Nop | Drop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No value reaches the instr, so it cannot run. An instr that values
|
||||||
|
// reach but that gives no value always throws, or is a call that
|
||||||
|
// never returns: its op's code below does that
|
||||||
|
if instr.args.iter().any(|&a| res.val_types[a] == Type::Unknown) {
|
||||||
|
let _ = writeln!(out, " abort(); /* not reached */");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ty = &res.instr_types[idx];
|
||||||
|
let v = |i: usize| format!("v{}", instr.args[i]);
|
||||||
|
let vt = |i: usize| &res.val_types[instr.args[i]];
|
||||||
|
let vr = |i: usize| self.rep(&res.val_types[instr.args[i]]);
|
||||||
|
let dst = instr.dsts.first().map(|d| format!("v{d}")).unwrap_or_default();
|
||||||
|
let dst_rep = instr.dsts.first().map_or(Rep::Undefined, |&d| self.rep(&res.val_types[d]));
|
||||||
|
let rep = self.rep(ty);
|
||||||
|
|
||||||
|
// A known value is its own literal. A call still runs, for what it
|
||||||
|
// does
|
||||||
|
let known = match ty {
|
||||||
|
Type::Bool(Some(b)) => Some(b.to_string()),
|
||||||
|
Type::Str(Some(atom)) => Some(self.string_lit(&atom_name(*atom, self.atoms))),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let is_ret = matches!(instr.op, Return | ReturnAsync | ReturnUndef);
|
||||||
|
let is_call = matches!(instr.op, Call | CallMethod | CallConstructor);
|
||||||
|
|
||||||
|
if let Some(lit) = known.filter(|_| !is_ret && !is_call) {
|
||||||
|
return self.assign(out, &dst, &lit, rep, dst_rep);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binary ops: the lhs was pushed first, so it is popped last
|
||||||
|
let (l, r) = (instr.args.len() == 2).then(|| (v(1), v(0))).unwrap_or_default();
|
||||||
|
let numbers = instr.args.len() == 2 && is_num(vr(0)) && is_num(vr(1));
|
||||||
|
|
||||||
|
let expr = match instr.op {
|
||||||
|
// ── Literals ──
|
||||||
|
PushMinus1 => "-1".into(),
|
||||||
|
Push0 => "0".into(),
|
||||||
|
Push1 => "1".into(),
|
||||||
|
Push2 => "2".into(),
|
||||||
|
Push3 => "3".into(),
|
||||||
|
Push4 => "4".into(),
|
||||||
|
Push5 => "5".into(),
|
||||||
|
Push6 => "6".into(),
|
||||||
|
Push7 => "7".into(),
|
||||||
|
PushI8 | PushI16 | PushI32 => match instr.imm {
|
||||||
|
ImmOperand::I8(n) => n.to_string(),
|
||||||
|
ImmOperand::I16(n) => n.to_string(),
|
||||||
|
ImmOperand::I32(n) => n.to_string(),
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
PushConst | PushConst8 => match &self.fns[fn_id].cpool[instr.imm.const_idx()] {
|
||||||
|
JsValue::Int(n) => n.to_string(),
|
||||||
|
JsValue::Float(x) if x.is_finite() => format!("{x:?}"),
|
||||||
|
JsValue::Float(x) if x.is_nan() => "NAN".into(),
|
||||||
|
JsValue::Float(x) => format!("{}INFINITY", if *x < 0.0 { "-" } else { "" }),
|
||||||
|
JsValue::String(s) => self.string_lit(&js_string_text(s)),
|
||||||
|
JsValue::NativeFn(id) => format!("&{}", builtin_name(self.num(self.cache.builtin(Code::Native(*id))))),
|
||||||
|
JsValue::ProtoLink(kind) => format!("&{}", builtin_name(self.num(self.cache.builtin(Code::ProtoLink(*kind))))),
|
||||||
|
_ => format!("({}){{0}}", self.c_type(rep)),
|
||||||
|
},
|
||||||
|
Undefined | Null => format!("({}){{0}}", self.c_type(rep)),
|
||||||
|
Object | Fclosure | Fclosure8 => self.alloc(self.num_of(ty)),
|
||||||
|
// The elements are the operands, last first
|
||||||
|
ArrayFrom => {
|
||||||
|
let id = self.num_of(ty);
|
||||||
|
let (name, elem) = (struct_name(id), self.elem_rep(id));
|
||||||
|
let _ = writeln!(out, " {dst} = {};\n set_length_{name}({dst}, {});", self.alloc(id), instr.args.len());
|
||||||
|
|
||||||
|
for (i, &v) in instr.args.iter().rev().enumerate() {
|
||||||
|
let value = self.conv(&format!("v{v}"), self.rep(&res.val_types[v]), elem);
|
||||||
|
let _ = writeln!(out, " {dst}->items[{i}] = {value};");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GetArrayEl | PutArrayEl => return self.element(res, instr, out),
|
||||||
|
|
||||||
|
// ── Arithmetic ──
|
||||||
|
Add if matches!(ty, Type::Str(_)) => {
|
||||||
|
let (ls, rs) = (self.to_string(&l, vt(1)), self.to_string(&r, vt(0)));
|
||||||
|
format!("__native_string_concat({ls}, {rs})")
|
||||||
|
}
|
||||||
|
Add | Sub | Mul if numbers => {
|
||||||
|
let op = match instr.op { Add => "+", Sub => "-", _ => "*" };
|
||||||
|
format!("{l} {op} {r}")
|
||||||
|
}
|
||||||
|
Mod if numbers && rep == Rep::Int => format!("{l} % {r}"),
|
||||||
|
Mod if numbers => format!("fmod({l}, {r})"),
|
||||||
|
Pow if numbers => format!("pow({l}, {r})"),
|
||||||
|
Div if numbers => format!("(double){l} / {r}"),
|
||||||
|
// An operand that is not a number: JS's operator, at runtime
|
||||||
|
Add | Sub | Mul | Div | Mod | Pow => {
|
||||||
|
let name = match instr.op {
|
||||||
|
Add => "add", Sub => "sub", Mul => "mul", Div => "div", Mod => "mod", _ => "pow",
|
||||||
|
};
|
||||||
|
let (la, ra) = (self.conv(&l, vr(1), Rep::Any), self.conv(&r, vr(0), Rep::Any));
|
||||||
|
return self.assign(out, &dst, &format!("__any_{name}({la}, {ra})"), Rep::Any, dst_rep);
|
||||||
|
}
|
||||||
|
|
||||||
|
Neg | Plus | Inc | Dec | PostInc | PostDec if is_num(vr(0)) => {
|
||||||
|
let new = match instr.op {
|
||||||
|
Neg => format!("-{}", v(0)),
|
||||||
|
Plus => v(0),
|
||||||
|
Inc | PostInc => format!("{} + 1", v(0)),
|
||||||
|
_ => format!("{} - 1", v(0)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pushes the old value, then the new one
|
||||||
|
if matches!(instr.op, PostInc | PostDec) {
|
||||||
|
let new_dst = format!("v{}", instr.dsts[1]);
|
||||||
|
self.assign(out, &new_dst, &new, rep, self.rep(&res.val_types[instr.dsts[1]]));
|
||||||
|
v(0)
|
||||||
|
} else {
|
||||||
|
new
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Neg | Plus | Inc | Dec | PostInc | PostDec => {
|
||||||
|
let name = match instr.op {
|
||||||
|
Neg => "neg", Plus => "plus", Inc | PostInc => "inc", _ => "dec",
|
||||||
|
};
|
||||||
|
let operand = self.conv(&v(0), vr(0), Rep::Any);
|
||||||
|
|
||||||
|
// Pushes the old value as a number, then the new one
|
||||||
|
if matches!(instr.op, PostInc | PostDec) {
|
||||||
|
let new_dst = format!("v{}", instr.dsts[1]);
|
||||||
|
let new = format!("__any_{name}({operand})");
|
||||||
|
self.assign(out, &new_dst, &new, Rep::Any, self.rep(&res.val_types[instr.dsts[1]]));
|
||||||
|
return self.assign(out, &dst, &format!("__any_plus({operand})"), Rep::Any, dst_rep);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.assign(out, &dst, &format!("__any_{name}({operand})"), Rep::Any, dst_rep);
|
||||||
|
}
|
||||||
|
Not => match vr(0) {
|
||||||
|
Rep::Int => format!("~{}", v(0)),
|
||||||
|
r => format!("~__any_to_int32({})", self.conv(&v(0), r, Rep::Any)),
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── Comparisons ──
|
||||||
|
Lt | Lte | Gt | Gte | Eq | Neq | StrictEq | StrictNeq => {
|
||||||
|
let op = match instr.op {
|
||||||
|
Lt => "<", Lte => "<=", Gt => ">", Gte => ">=",
|
||||||
|
Eq | StrictEq => "==", _ => "!=",
|
||||||
|
};
|
||||||
|
let eq = matches!(instr.op, Eq | Neq | StrictEq | StrictNeq);
|
||||||
|
|
||||||
|
match (vr(1), vr(0)) {
|
||||||
|
_ if numbers => format!("{l} {op} {r}"),
|
||||||
|
(Rep::Bool, Rep::Bool) => format!("{l} {op} {r}"),
|
||||||
|
(Rep::Str, Rep::Str) => match instr.op {
|
||||||
|
Eq | StrictEq => format!("jsrt_string_eq({l}, {r})"),
|
||||||
|
Neq | StrictNeq => format!("!jsrt_string_eq({l}, {r})"),
|
||||||
|
_ => format!("jsrt_string_cmp({l}, {r}) {op} 0"),
|
||||||
|
},
|
||||||
|
// Two objects are equal if they are one object
|
||||||
|
(Rep::Obj(_), Rep::Obj(_)) if eq => format!("(void *){l} {op} (void *){r}"),
|
||||||
|
(lr, rr) => {
|
||||||
|
let name = match instr.op {
|
||||||
|
Lt => "lt", Lte => "lte", Gt => "gt", Gte => "gte", Eq => "eq",
|
||||||
|
Neq => "neq", StrictEq => "strict_eq", _ => "strict_neq",
|
||||||
|
};
|
||||||
|
let (la, ra) = (self.conv(&l, lr, Rep::Any), self.conv(&r, rr, Rep::Any));
|
||||||
|
format!("__any_{name}({la}, {ra})")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Lnot => format!("!{}", self.truthy(&v(0), vt(0))),
|
||||||
|
|
||||||
|
// ── Types of values only known at runtime ──
|
||||||
|
Typeof => format!("__any_typeof({})", self.conv(&v(0), vr(0), Rep::Any)),
|
||||||
|
TypeofIsUndefined | TypeofIsFunction | IsUndefined | IsNull | IsUndefinedOrNull => {
|
||||||
|
let name = match instr.op {
|
||||||
|
TypeofIsFunction => "is_function",
|
||||||
|
IsNull => "is_null",
|
||||||
|
IsUndefinedOrNull => "is_nullish",
|
||||||
|
_ => "is_undefined",
|
||||||
|
};
|
||||||
|
format!("__any_{name}({})", self.conv(&v(0), vr(0), Rep::Any))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Movers ──
|
||||||
|
SetLoc | SetName | ToPropkey => v(0),
|
||||||
|
|
||||||
|
// ── Objects ──
|
||||||
|
DefineField | PutField => return self.write_field(res, instr, out),
|
||||||
|
GetField | GetLength => return self.read_field(res, instr, out),
|
||||||
|
|
||||||
|
// ── Calls ──
|
||||||
|
Call | CallMethod | CallConstructor => return self.call(res, (fn_id, idx), instr, out),
|
||||||
|
|
||||||
|
// ── Returns ──
|
||||||
|
Return | ReturnAsync => {
|
||||||
|
let value = self.conv(&v(0), vr(0), self.rep(&res.ret_type));
|
||||||
|
let _ = writeln!(out, " return {value};");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ReturnUndef => {
|
||||||
|
let value = self.conv("(undefined_t){0}", Rep::Undefined, self.rep(&res.ret_type));
|
||||||
|
let _ = writeln!(out, " return {value};");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => unimplemented!("emit {instr:?}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.assign(out, &dst, &expr, rep, dst_rep);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
//! Emits C: one fn for each walk that the program calls, one struct for each
|
||||||
|
//! cell (see `docs/model.md`, section 5). Cells and walks that C cannot tell
|
||||||
|
//! apart share one struct and one fn (see `classes()`)
|
||||||
|
//!
|
||||||
|
//! A value's C representation is a function of its type (`rep()`), one for
|
||||||
|
//! each kind of type. An object is a pointer to a struct of its cell. Each
|
||||||
|
//! object starts with a header that holds the number of its cell, and the
|
||||||
|
//! fields that can be absent start as absent
|
||||||
|
//!
|
||||||
|
//! The runtime (`lib/rt/`) gives the primitive types, the string ops, the
|
||||||
|
//! `any_t` ops and the allocator
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
cell::RefCell,
|
||||||
|
collections::{BTreeSet, HashMap, HashSet},
|
||||||
|
fmt::Write,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
infer::{
|
||||||
|
HOST, InferResult, Place, TypeCache,
|
||||||
|
cells::{Cell, Code, LENGTH_FIELD, ObjKind, read_value},
|
||||||
|
types::Type,
|
||||||
|
},
|
||||||
|
libqjs::parse::{Atom, AtomRef, ImmOperand, JsValue, opcodes::OpCode},
|
||||||
|
native::{self, NATIVES, ProtoOf, atom_name, js_string_text},
|
||||||
|
regify::{BasicBlock, RegFn, RegInstr},
|
||||||
|
transform::{FIRST_ARG, PROTO_FIELD, THIS_ARG},
|
||||||
|
};
|
||||||
|
|
||||||
|
mod calls;
|
||||||
|
mod classes;
|
||||||
|
mod conv;
|
||||||
|
mod instrs;
|
||||||
|
mod objects;
|
||||||
|
mod reps;
|
||||||
|
|
||||||
|
use classes::*;
|
||||||
|
use reps::*;
|
||||||
|
|
||||||
|
/// The number of a class of cells in C: the number in the header of their
|
||||||
|
/// objects, and the name of their struct
|
||||||
|
type CellNum = u32;
|
||||||
|
|
||||||
|
pub fn emit(fns: &[RegFn], cache: &TypeCache, atoms: &[Atom]) -> String {
|
||||||
|
let (nums, cells, walk_rep) = classes(fns, cache);
|
||||||
|
|
||||||
|
let mut e = Emitter {
|
||||||
|
fns,
|
||||||
|
cache,
|
||||||
|
atoms,
|
||||||
|
cells,
|
||||||
|
nums,
|
||||||
|
walk_rep,
|
||||||
|
queued: vec![false; cache.walks.len()],
|
||||||
|
queue: vec![],
|
||||||
|
helpers: HashSet::new(),
|
||||||
|
sums: RefCell::new(BTreeSet::new()),
|
||||||
|
helper_protos: String::new(),
|
||||||
|
helper_defs: String::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Every struct is declared first, so that a field can point to any of
|
||||||
|
// them
|
||||||
|
let mut structs = String::new();
|
||||||
|
|
||||||
|
for id in 0..e.cells.len() as CellNum {
|
||||||
|
let _ = writeln!(structs, "typedef struct {0} {0};", struct_name(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
for id in 0..e.cells.len() as CellNum {
|
||||||
|
e.struct_def(id, &mut structs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Which cells are fns, by number, for the runtime's `typeof`
|
||||||
|
let fn_cells: Vec<&str> = e.cells.iter().map(|cell| if cell.code().is_some() { "1" } else { "0" }).collect();
|
||||||
|
let mut statics = format!("const bool jsrt_fn_cells[] = {{ {} }};\n\n", fn_cells.join(", "));
|
||||||
|
|
||||||
|
// A built-in fn is one static object
|
||||||
|
let mut init = String::new();
|
||||||
|
|
||||||
|
for cell in cache.builtins() {
|
||||||
|
let id = e.num(cell);
|
||||||
|
let name = struct_name(id);
|
||||||
|
let _ = writeln!(statics, "static {name} {};", builtin_name(id));
|
||||||
|
let _ = writeln!(init, " init_{name}(&{}, {id});", builtin_name(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
for kind in ProtoOf::ALL {
|
||||||
|
let ty = &cache.protos[kind as usize];
|
||||||
|
|
||||||
|
if *ty != Type::Unknown {
|
||||||
|
let _ = writeln!(statics, "static {} {};", e.c_type(e.rep(ty)), proto_global(kind));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The walks that the code calls, from the entry. The host calls the entry
|
||||||
|
// with nothing: its env and `this` are undefined
|
||||||
|
let entry = cache.find(fns.len() - 1, HOST, &[], fns);
|
||||||
|
let entry_args = vec!["(undefined_t){0}"; cache.walks[entry].req.arg_types.len()].join(", ");
|
||||||
|
e.use_walk(entry);
|
||||||
|
|
||||||
|
let mut fn_protos = String::new();
|
||||||
|
let mut bodies = String::new();
|
||||||
|
|
||||||
|
while let Some(w) = e.queue.pop() {
|
||||||
|
let res = cache.walks[w].result.as_ref().unwrap();
|
||||||
|
let _ = writeln!(fn_protos, "{};", e.signature(w, res));
|
||||||
|
e.emit_fn(w, res, &mut bodies);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The program is one file, so it also holds the runtime. The runtime goes
|
||||||
|
// first (see `lib/rt/jsrt.h`)
|
||||||
|
let mut out = String::from(
|
||||||
|
"#define JSRT_IMPLEMENTATION\n#include \"jsrt.h\"\n#include <math.h>\n#include <stdlib.h>\n\n// Natives\n",
|
||||||
|
);
|
||||||
|
|
||||||
|
for n in NATIVES {
|
||||||
|
let params: Vec<String> = n.args.iter().map(|t| e.c_type(e.rep(t))).collect();
|
||||||
|
let _ = writeln!(out, "{} {}({});", e.c_type(e.rep(&n.ret)), n.name, params.join(", "));
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = write!(
|
||||||
|
out,
|
||||||
|
"\n{}\n{structs}\n{statics}\n{fn_protos}\n{}\n{}{bodies}",
|
||||||
|
sum_defs(&e.sums.borrow()),
|
||||||
|
e.helper_protos,
|
||||||
|
e.helper_defs
|
||||||
|
);
|
||||||
|
let _ = writeln!(out, "int main(void) {{\n{init} {}({entry_args});\n return 0;\n}}", e.fn_name(entry));
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Emitter<'a> {
|
||||||
|
fns: &'a [RegFn],
|
||||||
|
cache: &'a TypeCache,
|
||||||
|
atoms: &'a [Atom],
|
||||||
|
cells: Vec<Cell>, // a cell of each class, by number
|
||||||
|
nums: HashMap<Cell, CellNum>, // the class of each cell
|
||||||
|
walk_rep: Vec<usize>, // by walk: the walk whose fn it uses
|
||||||
|
queued: Vec<bool>, // the walks that the code calls, by walk
|
||||||
|
queue: Vec<usize>, // the walks that the code calls, which are not written yet
|
||||||
|
helpers: HashSet<String>, // the helpers made so far, by name
|
||||||
|
sums: RefCell<BTreeSet<Kinds>>, // the tagged unions that the C uses
|
||||||
|
helper_protos: String,
|
||||||
|
helper_defs: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Emitter<'a> {
|
||||||
|
fn num(&self, cell: &Cell) -> CellNum {
|
||||||
|
self.nums[cell]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The number of the cell of an `Obj` type
|
||||||
|
fn num_of(&self, ty: &Type) -> CellNum {
|
||||||
|
match ty {
|
||||||
|
Type::Obj(cell) => self.num(cell),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Walk `w` is called, so the walk whose fn it uses is written
|
||||||
|
fn use_walk(&mut self, w: usize) {
|
||||||
|
let w = self.walk_rep[w];
|
||||||
|
|
||||||
|
if !self.queued[w] {
|
||||||
|
self.queued[w] = true;
|
||||||
|
self.queue.push(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self, w: usize, res: &InferResult) -> String {
|
||||||
|
let params: Vec<String> = (self.cache.walks[w].req.arg_types.iter().enumerate())
|
||||||
|
.map(|(i, ty)| format!("{} p{i}", self.c_type(self.rep(ty))))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let ret = self.c_type(self.rep(&res.ret_type));
|
||||||
|
format!("static {ret} {}({})", self.fn_name(w), params.join(", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_fn(&mut self, w: usize, res: &InferResult, out: &mut String) {
|
||||||
|
let fn_id = self.cache.walks[w].req.fn_id;
|
||||||
|
let f = &self.fns[fn_id];
|
||||||
|
let mut body = String::new();
|
||||||
|
|
||||||
|
// Args are values `var_count + i` (see regify)
|
||||||
|
for (i, ty) in self.cache.walks[w].req.arg_types.iter().enumerate() {
|
||||||
|
let arg = f.var_count + i;
|
||||||
|
self.assign(&mut body, &format!("v{arg}"), &format!("p{i}"), self.rep(ty), self.rep(&res.val_types[arg]));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (b, block) in f.blocks.iter().enumerate() {
|
||||||
|
let _ = writeln!(body, "L{b}:;");
|
||||||
|
|
||||||
|
if !res.reachable[b] {
|
||||||
|
let _ = writeln!(body, " abort();");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let code = &f.code[block.start..block.end];
|
||||||
|
|
||||||
|
let (instrs, last) = match code.split_last() {
|
||||||
|
Some((last, rest)) if is_terminator(last.op) => (rest, Some(last)),
|
||||||
|
_ => (code, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (i, instr) in instrs.iter().enumerate() {
|
||||||
|
self.emit_instr(fn_id, res, block.start + i, instr, &mut body);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Postfix copies carry values into the next block's stack slots
|
||||||
|
for copy in &block.postfix {
|
||||||
|
let (src, dst) = (copy.args[0], copy.dsts[0]);
|
||||||
|
let (from, to) = (self.rep(&res.val_types[src]), self.rep(&res.val_types[dst]));
|
||||||
|
self.assign(&mut body, &format!("v{dst}"), &format!("v{src}"), from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
let goto = |i: usize| format!("goto L{};", block.succ[i]);
|
||||||
|
|
||||||
|
match last.map(|l| l.op) {
|
||||||
|
Some(OpCode::IfFalse | OpCode::IfFalse8 | OpCode::IfTrue | OpCode::IfTrue8) => {
|
||||||
|
let last = last.unwrap();
|
||||||
|
let cond = self.truthy(&format!("v{}", last.args[0]), &res.val_types[last.args[0]]);
|
||||||
|
let not = if matches!(last.op, OpCode::IfFalse | OpCode::IfFalse8) { "!" } else { "" };
|
||||||
|
let _ = writeln!(body, " if ({not}{cond}) {} else {}", goto(0), goto(1));
|
||||||
|
}
|
||||||
|
Some(OpCode::Goto | OpCode::Goto8 | OpCode::Goto16) => {
|
||||||
|
let _ = writeln!(body, " {}", goto(0));
|
||||||
|
}
|
||||||
|
Some(_) => self.emit_instr(fn_id, res, block.end - 1, last.unwrap(), &mut body),
|
||||||
|
None if !block.succ.is_empty() => {
|
||||||
|
let _ = writeln!(body, " {}", goto(0));
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = writeln!(out, "{} {{", self.signature(w, res));
|
||||||
|
|
||||||
|
for (v, ty) in res.val_types.iter().enumerate() {
|
||||||
|
let _ = writeln!(out, " {} v{v};", self.c_type(self.rep(ty)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = writeln!(out, "{body}}}\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A new object of cell `id`, on the heap
|
||||||
|
fn alloc(&self, id: CellNum) -> String {
|
||||||
|
let name = struct_name(id);
|
||||||
|
format!("init_{name}(jsrt_alloc(sizeof({name})), {id})")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The C fn of walk `w`: the fn of the walk of its class
|
||||||
|
fn fn_name(&self, w: usize) -> String {
|
||||||
|
let w = self.walk_rep[w];
|
||||||
|
format!("f{}_{w}", self.cache.walks[w].req.fn_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `s` as a C string literal. Every byte that is not plain printable ASCII is
|
||||||
|
/// an octal escape, so UTF-8 passes through unchanged
|
||||||
|
fn c_str(s: &str) -> String {
|
||||||
|
let mut lit = String::from("\"");
|
||||||
|
|
||||||
|
for b in s.bytes() {
|
||||||
|
match b {
|
||||||
|
b'"' | b'\\' => lit.extend(['\\', b as char]),
|
||||||
|
0x20..0x7f => lit.push(b as char),
|
||||||
|
_ => lit += &format!("\\{b:03o}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lit + "\""
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_num(rep: Rep) -> bool {
|
||||||
|
matches!(rep, Rep::Int | Rep::Double)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The struct of the objects of cell `id`
|
||||||
|
fn struct_name(id: CellNum) -> String {
|
||||||
|
format!("S{id}")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The static object of the built-in fn of cell `id`
|
||||||
|
fn builtin_name(id: CellNum) -> String {
|
||||||
|
format!("b{id}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proto_global(kind: ProtoOf) -> String {
|
||||||
|
format!("proto_{kind:?}").to_lowercase()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_terminator(op: OpCode) -> bool {
|
||||||
|
use OpCode::*;
|
||||||
|
matches!(
|
||||||
|
op,
|
||||||
|
Goto | Goto8 | Goto16 | IfFalse | IfFalse8 | IfTrue | IfTrue8 | Return | ReturnAsync | ReturnUndef
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
//! Field reads and writes, array elements, and prototype chains
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl<'a> Emitter<'a> {
|
||||||
|
/// `obj.field = value`. A primitive has no fields to write, and undefined
|
||||||
|
/// and null throw
|
||||||
|
pub(super) fn write_field(&mut self, res: &InferResult, instr: &RegInstr, out: &mut String) {
|
||||||
|
let (value, obj) = (instr.args[0], instr.args[1]);
|
||||||
|
let value_rep = self.rep(&res.val_types[value]);
|
||||||
|
let field = instr.imm.atom();
|
||||||
|
let value = format!("v{value}");
|
||||||
|
|
||||||
|
self.dispatch(&format!("v{obj}"), &res.val_types[obj], "jsrt_type_error();", out, |e, member, obj, _| {
|
||||||
|
match member {
|
||||||
|
Type::Obj(cell) if field == LENGTH_FIELD && cell.is_array() => {
|
||||||
|
let name = struct_name(e.num(cell));
|
||||||
|
Some(format!("set_length_{name}({obj}, {});", e.conv(&value, value_rep, Rep::Int)))
|
||||||
|
}
|
||||||
|
Type::Obj(cell) => {
|
||||||
|
let slot = e.slot_rep(&cell.field(field).unwrap().ty, cell.can_be_absent(field));
|
||||||
|
Some(format!("{obj}->{} = {};", e.field_name(field), e.conv(&value, value_rep, slot)))
|
||||||
|
}
|
||||||
|
Type::Undefined | Type::Null => None,
|
||||||
|
_ => Some(";".into()),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `dst = obj.field`: on an object, the read through its prototype chain;
|
||||||
|
/// on a primitive, the read on its kind's prototype, except that a string's
|
||||||
|
/// `length` is its own. Undefined and null throw
|
||||||
|
pub(super) fn read_field(&mut self, res: &InferResult, instr: &RegInstr, out: &mut String) {
|
||||||
|
let (obj, dst) = (instr.args[0], instr.dsts[0]);
|
||||||
|
let dst_rep = self.rep(&res.val_types[dst]);
|
||||||
|
|
||||||
|
let field = match instr.op {
|
||||||
|
OpCode::GetLength => LENGTH_FIELD,
|
||||||
|
_ => instr.imm.atom(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.dispatch(&format!("v{obj}"), &res.val_types[obj], "jsrt_type_error();", out, |e, member, obj, _| {
|
||||||
|
let (read, rep) = match member {
|
||||||
|
Type::Obj(cell) => e.read_on(cell, field, obj),
|
||||||
|
_ => e.read_primitive(member, obj, field)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(format!("v{dst} = {};", e.conv(&read, rep, dst_rep)))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `dst = a[i]` or `a[i] = value`, on an array, and `dst = s[i]` on a
|
||||||
|
/// string. A string cannot be written, and undefined and null throw
|
||||||
|
pub(super) fn element(&mut self, res: &InferResult, instr: &RegInstr, out: &mut String) {
|
||||||
|
let put = instr.op == OpCode::PutArrayEl;
|
||||||
|
let (index, obj) = (instr.args[put as usize], instr.args[put as usize + 1]);
|
||||||
|
let index = self.conv(&format!("v{index}"), self.rep(&res.val_types[index]), Rep::Int);
|
||||||
|
|
||||||
|
// A put has the value, a get the dst
|
||||||
|
let (v, v_rep) = match put {
|
||||||
|
true => (instr.args[0], self.rep(&res.val_types[instr.args[0]])),
|
||||||
|
false => (instr.dsts[0], self.rep(&res.val_types[instr.dsts[0]])),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.dispatch(&format!("v{obj}"), &res.val_types[obj], "jsrt_type_error();", out, |e, member, obj, _| {
|
||||||
|
let id = match member {
|
||||||
|
Type::Obj(cell) => e.num(cell),
|
||||||
|
Type::Str(_) if !put => {
|
||||||
|
let c = format!("jsrt_string_at({obj}, {index})");
|
||||||
|
return Some(format!("v{v} = {};", e.conv(&c, Rep::OptStr, v_rep)));
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (name, elem) = (struct_name(id), e.elem_rep(id));
|
||||||
|
|
||||||
|
Some(match put {
|
||||||
|
true => format!("set_elem_{name}({obj}, {index}, {});", e.conv(&format!("v{v}"), v_rep, elem)),
|
||||||
|
false => format!("v{v} = {};", e.conv(&format!("elem_{name}({obj}, {index})"), elem, v_rep)),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A read of `field` on the object of `cell` at `ptr`, and its rep. An own
|
||||||
|
/// field that is never absent is read in place. Any other read goes
|
||||||
|
/// through the prototype chain
|
||||||
|
pub(super) fn read_on(&mut self, cell: &Cell, field: AtomRef, ptr: &str) -> (String, Rep) {
|
||||||
|
if field == LENGTH_FIELD && cell.is_array() {
|
||||||
|
return (format!("{ptr}->length"), Rep::Int);
|
||||||
|
}
|
||||||
|
|
||||||
|
match cell.field(field) {
|
||||||
|
Some(f) if !cell.can_be_absent(field) => (format!("{ptr}->{}", self.field_name(field)), self.rep(&f.ty)),
|
||||||
|
_ => {
|
||||||
|
let (reader, rep) = self.field_reader(cell, field);
|
||||||
|
(format!("{reader}({ptr})"), rep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A read of `field` on `e`, a primitive of type `ty`, and its rep: a
|
||||||
|
/// string's length, or a read on its kind's prototype. `None` if it throws
|
||||||
|
pub(super) fn read_primitive(&mut self, ty: &Type, e: &str, field: AtomRef) -> Option<(String, Rep)> {
|
||||||
|
let kind = ProtoOf::of(ty)?;
|
||||||
|
|
||||||
|
if kind == ProtoOf::String && field == LENGTH_FIELD {
|
||||||
|
return Some((format!("jsrt_string_length({e})"), Rep::Int));
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(self.read_proto(kind, field))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A read of `field` on the prototype that the stdlib links for `kind`,
|
||||||
|
/// and its rep
|
||||||
|
pub(super) fn read_proto(&mut self, kind: ProtoOf, field: AtomRef) -> (String, Rep) {
|
||||||
|
match &self.cache.protos[kind as usize] {
|
||||||
|
Type::Unknown => ("(undefined_t){0}".into(), Rep::Undefined),
|
||||||
|
Type::Obj(cell) => self.read_on(&cell.clone(), field, &proto_global(kind)),
|
||||||
|
_ => self.proto_reader(kind, field),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The helper that reads `field` on the prototype that the stdlib links
|
||||||
|
/// for `kind`, where the prototype's type is a sum, and the rep it gives.
|
||||||
|
/// It reads on each member of the type
|
||||||
|
pub(super) fn proto_reader(&mut self, kind: ProtoOf, field: AtomRef) -> (String, Rep) {
|
||||||
|
let name = format!("get_{}_{}", proto_global(kind), self.field_name(field));
|
||||||
|
let proto = &self.cache.protos[kind as usize];
|
||||||
|
let ret = self.rep(&read_value(proto, field, &self.cache.protos));
|
||||||
|
|
||||||
|
if !self.helpers.insert(name.clone()) {
|
||||||
|
return (format!("{name}()"), ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut body = String::new();
|
||||||
|
|
||||||
|
self.dispatch(&proto_global(kind), proto, "jsrt_type_error();", &mut body, |e, member, proto, _| {
|
||||||
|
let Type::Obj(cell) = member else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let (read, rep) = e.read_on(cell, field, proto);
|
||||||
|
Some(format!("return {};", e.conv(&read, rep, ret)))
|
||||||
|
});
|
||||||
|
|
||||||
|
self.helper(format!("static {} {name}(void)", self.c_type(ret)), body);
|
||||||
|
(format!("{name}()"), ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The helper that reads `field` on an object of `cell`, and the rep it
|
||||||
|
/// gives. It reads the own field, and where that can be absent, goes on to
|
||||||
|
/// the prototype. At the end of the chain, the field is undefined
|
||||||
|
pub(super) fn field_reader(&mut self, cell: &Cell, field: AtomRef) -> (String, Rep) {
|
||||||
|
let name = format!("get_{}_{}", struct_name(self.num(cell)), self.field_name(field));
|
||||||
|
let read = cell.read(field, &self.cache.protos);
|
||||||
|
let ret = self.rep(&read);
|
||||||
|
|
||||||
|
if !self.helpers.insert(name.clone()) {
|
||||||
|
return (name, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
let own = cell.field(field);
|
||||||
|
let absent = cell.can_be_absent(field);
|
||||||
|
let mut body = String::new();
|
||||||
|
|
||||||
|
if let Some(own) = &own {
|
||||||
|
let slot = self.slot_rep(&own.ty, absent);
|
||||||
|
let member = format!("o->{}", self.field_name(field));
|
||||||
|
let value = self.conv(&member, slot, ret);
|
||||||
|
|
||||||
|
match absent {
|
||||||
|
false => {
|
||||||
|
let _ = writeln!(body, " return {value};");
|
||||||
|
}
|
||||||
|
true => {
|
||||||
|
let _ = writeln!(body, " if ({}) return {value};", self.present(&member, slot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An array's prototype is the one that the stdlib links for arrays
|
||||||
|
if absent && cell.is_array() {
|
||||||
|
let (read, rep) = self.read_proto(ProtoOf::Array, field);
|
||||||
|
let _ = writeln!(body, " return {};", self.conv(&read, rep, ret));
|
||||||
|
} else if absent {
|
||||||
|
let proto_ty = cell.field(PROTO_FIELD).map_or(Type::Unknown, |f| f.ty);
|
||||||
|
let proto_absent = cell.can_be_absent(PROTO_FIELD);
|
||||||
|
let slot = self.slot_rep(&proto_ty, proto_absent);
|
||||||
|
let member = format!("o->{}", self.field_name(PROTO_FIELD));
|
||||||
|
let mut chain = String::new();
|
||||||
|
|
||||||
|
// On a prototype that is not an object, the chain ends
|
||||||
|
self.dispatch(&member, &proto_ty, "{}", &mut chain, |e, member, proto, _| {
|
||||||
|
let Type::Obj(proto_cell) = member else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let (reader, rep) = e.field_reader(proto_cell, field);
|
||||||
|
Some(format!("return {};", e.conv(&format!("{reader}({proto})"), rep, ret)))
|
||||||
|
});
|
||||||
|
|
||||||
|
match (&proto_ty, proto_absent) {
|
||||||
|
(Type::Unknown, _) => {}
|
||||||
|
(_, true) => {
|
||||||
|
let _ = writeln!(body, " if ({}) {{\n{chain} }}", self.present(&member, slot));
|
||||||
|
}
|
||||||
|
(_, false) => body += &chain,
|
||||||
|
}
|
||||||
|
|
||||||
|
// The end of the chain. If the read cannot give undefined, the chain
|
||||||
|
// always finds the field before it
|
||||||
|
match read.members().contains(&Type::Undefined) {
|
||||||
|
true => {
|
||||||
|
let undefined = self.conv("(undefined_t){0}", Rep::Undefined, ret);
|
||||||
|
let _ = writeln!(body, " return {undefined};");
|
||||||
|
}
|
||||||
|
false => body += " abort();\n",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A field that is defined, but that no value reaches: the read cannot
|
||||||
|
// run
|
||||||
|
if own.is_none() && !absent {
|
||||||
|
let _ = writeln!(body, " abort();");
|
||||||
|
}
|
||||||
|
|
||||||
|
let sig = format!("static {} {name}({} *o)", self.c_type(ret), struct_name(self.num(cell)));
|
||||||
|
self.helper(sig, body);
|
||||||
|
(name, ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
//! How each type looks in C: its representation, the C type of that,
|
||||||
|
//! the struct of each class of cells, and the tagged union of each sum
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// A C representation
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub(super) enum Rep {
|
||||||
|
Undefined,
|
||||||
|
Null,
|
||||||
|
Bool,
|
||||||
|
Int,
|
||||||
|
Double,
|
||||||
|
Str, // a string, or in a field, the sentinel for absent
|
||||||
|
Obj(CellNum), // a pointer to an object of the cell, or in a field, the sentinel for absent
|
||||||
|
OptStr, // a sum of a string and null or undefined: `NULL` is null, and `JSRT_UNDEFINED` is undefined
|
||||||
|
OptObj(CellNum), // a sum of the objects of one cell and null or undefined: a pointer, with the same sentinels
|
||||||
|
Objs, // a sum of the objects of several cells, maybe with null or undefined: a pointer to the header
|
||||||
|
Sum(Kinds), // any other sum: a tagged union of its kinds (see `sum_defs()`)
|
||||||
|
Any, // any value: an `any_t`. The `Any` type, and the args of the runtime's ops on sums
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A set of kinds of value, one bit for each `jsrt_kind` (see `lib/rt/any.h`)
|
||||||
|
pub(super) type Kinds = u8;
|
||||||
|
|
||||||
|
pub(super) const ABSENT: Kinds = 1 << 0;
|
||||||
|
|
||||||
|
/// The `jsrt_kind` of each kind, and its member in a tagged union, if it has a
|
||||||
|
/// value besides its kind
|
||||||
|
pub(super) const KINDS: [(&str, Option<(&str, &str)>); 8] = [
|
||||||
|
("ABSENT", None),
|
||||||
|
("UNDEFINED", None),
|
||||||
|
("NULL", None),
|
||||||
|
("BOOL", Some(("bool", "b"))),
|
||||||
|
("INT", Some(("int32_t", "i"))),
|
||||||
|
("FLOAT", Some(("double", "f"))),
|
||||||
|
("STRING", Some(("string_t", "s"))),
|
||||||
|
("OBJECT", Some(("void *", "o"))),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// The kinds of the values of type `ty`
|
||||||
|
pub(super) fn kinds(ty: &Type) -> Kinds {
|
||||||
|
ty.members().iter().fold(0, |all, member| {
|
||||||
|
all | 1 << match member {
|
||||||
|
Type::Undefined => 1,
|
||||||
|
Type::Null => 2,
|
||||||
|
Type::Bool(_) => 3,
|
||||||
|
Type::Int => 4,
|
||||||
|
Type::Float => 5,
|
||||||
|
Type::Str(_) => 6,
|
||||||
|
_ => 7,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The C of the tagged union of each set of kinds in `sums`, and its
|
||||||
|
/// conversions to and from `any_t`. Every member of a union is at its start,
|
||||||
|
/// so a conversion copies the payload's bytes. A union has one kind of number
|
||||||
|
/// (see `join()`): the other converts. A kind that the union lacks is a
|
||||||
|
/// `TypeError`
|
||||||
|
pub(super) fn sum_defs(sums: &BTreeSet<Kinds>) -> String {
|
||||||
|
let mut out = String::new();
|
||||||
|
|
||||||
|
for &k in sums {
|
||||||
|
let has = |i: usize| k & 1 << i != 0;
|
||||||
|
let union: String = (0..8).filter(|&i| has(i)).filter_map(|i| KINDS[i].1).map(|(t, m)| format!(" {t} {m};")).collect();
|
||||||
|
let copy = |to: &str, from: &str| match union.is_empty() {
|
||||||
|
true => String::new(),
|
||||||
|
false => format!(" __builtin_memcpy(&{to}.as, &{from}.as, sizeof {to}.as);"),
|
||||||
|
};
|
||||||
|
let number = match (has(4), has(5)) {
|
||||||
|
(false, true) => " if (a.kind == JSRT_KIND_INT) { v.kind = JSRT_KIND_FLOAT; v.as.f = a.as.i; return v; }",
|
||||||
|
(true, false) => " if (a.kind == JSRT_KIND_FLOAT) { v.kind = JSRT_KIND_INT; v.as.i = (int32_t)a.as.f; return v; }",
|
||||||
|
_ => "",
|
||||||
|
};
|
||||||
|
let union = if union.is_empty() { String::new() } else { format!(" union {{{union} }} as;") };
|
||||||
|
|
||||||
|
let _ = writeln!(out, "typedef struct {{ jsrt_kind kind;{union} }} sum_{k};");
|
||||||
|
let _ = writeln!(out, "static inline any_t sum_{k}_any(sum_{k} v) {{ any_t a = {{ .kind = v.kind }};{} return a; }}", copy("a", "v"));
|
||||||
|
let _ = writeln!(
|
||||||
|
out,
|
||||||
|
"static inline sum_{k} any_sum_{k}(any_t a) {{ sum_{k} v = {{ .kind = a.kind }};{number} if (!({k}u >> a.kind & 1)) jsrt_type_error();{} return v; }}",
|
||||||
|
copy("v", "a")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Emitter<'a> {
|
||||||
|
/// The representation of a value of type `ty`
|
||||||
|
pub(super) fn rep(&self, ty: &Type) -> Rep {
|
||||||
|
match ty {
|
||||||
|
Type::Unknown | Type::Undefined => Rep::Undefined,
|
||||||
|
Type::Null => Rep::Null,
|
||||||
|
Type::Bool(_) => Rep::Bool,
|
||||||
|
Type::Int => Rep::Int,
|
||||||
|
Type::Float => Rep::Double,
|
||||||
|
Type::Str(_) => Rep::Str,
|
||||||
|
Type::Obj(cell) => Rep::Obj(self.num(cell)),
|
||||||
|
Type::Sum(_) => self.sum_rep(ty),
|
||||||
|
Type::Any => Rep::Any,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The representation of a sum. A string or the objects of one cell, with
|
||||||
|
/// null or undefined, is that pointer with sentinels. The objects of
|
||||||
|
/// several cells are a pointer to their header. Any other sum is an
|
||||||
|
/// `any_t`, which holds every kind
|
||||||
|
pub(super) fn sum_rep(&self, ty: &Type) -> Rep {
|
||||||
|
let others: Vec<&Type> = ty.members().iter().filter(|m| !m.is_nullish()).collect();
|
||||||
|
let mut classes: Vec<CellNum> = others.iter().filter_map(|m| self.cell_num(m)).collect();
|
||||||
|
classes.dedup();
|
||||||
|
|
||||||
|
match (&others[..], &classes[..]) {
|
||||||
|
([], _) => Rep::Sum(kinds(ty)),
|
||||||
|
([Type::Str(_)], _) => Rep::OptStr,
|
||||||
|
(_, [id]) if classes.len() == 1 && others.iter().all(|m| matches!(m, Type::Obj(_))) => Rep::OptObj(*id),
|
||||||
|
_ if others.iter().all(|m| matches!(m, Type::Obj(_))) => Rep::Objs,
|
||||||
|
_ => Rep::Sum(kinds(ty)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The class of an `Obj` type
|
||||||
|
pub(super) fn cell_num(&self, ty: &Type) -> Option<CellNum> {
|
||||||
|
match ty {
|
||||||
|
Type::Obj(cell) => Some(self.num(cell)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The representation of a field of type `ty`. A field that can be absent
|
||||||
|
/// needs a sentinel for absent: a pointer has one, and any other rep is a
|
||||||
|
/// tagged union that can also be absent
|
||||||
|
pub(super) fn slot_rep(&self, ty: &Type, absent: bool) -> Rep {
|
||||||
|
match self.rep(ty) {
|
||||||
|
rep @ (Rep::Str | Rep::Obj(_) | Rep::OptStr | Rep::OptObj(_) | Rep::Objs) => rep,
|
||||||
|
_ if absent => Rep::Sum(kinds(ty) | ABSENT),
|
||||||
|
rep => rep,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The representation of an element of an array of cell `id`: its type,
|
||||||
|
/// or undefined for a hole
|
||||||
|
pub(super) fn elem_rep(&self, id: CellNum) -> Rep {
|
||||||
|
self.rep(&self.cells[id as usize].read_elem())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn c_type(&self, rep: Rep) -> String {
|
||||||
|
match rep {
|
||||||
|
Rep::Undefined => "undefined_t".into(),
|
||||||
|
Rep::Null => "null_t".into(),
|
||||||
|
Rep::Bool => "bool".into(),
|
||||||
|
Rep::Int => "int32_t".into(),
|
||||||
|
Rep::Double => "double".into(),
|
||||||
|
Rep::Str | Rep::OptStr => "string_t".into(),
|
||||||
|
Rep::Obj(id) | Rep::OptObj(id) => format!("{} *", struct_name(id)),
|
||||||
|
Rep::Objs => "jsrt_header *".into(),
|
||||||
|
Rep::Sum(k) => {
|
||||||
|
self.sums.borrow_mut().insert(k);
|
||||||
|
format!("sum_{k}")
|
||||||
|
}
|
||||||
|
Rep::Any => "any_t".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn field_name(&self, name: AtomRef) -> String {
|
||||||
|
let text = atom_name(name, self.atoms);
|
||||||
|
let text = text.chars().map(|c| if c.is_ascii_alphanumeric() { c } else { '_' });
|
||||||
|
format!("f_{}", text.collect::<String>())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The struct of cell `id`, and its helpers: `init_S`, which sets the
|
||||||
|
/// header and makes each field that can be absent absent, and for an
|
||||||
|
/// array, the helpers of its elements
|
||||||
|
pub(super) fn struct_def(&mut self, id: CellNum, out: &mut String) {
|
||||||
|
let cell = self.cells[id as usize].clone();
|
||||||
|
let name = struct_name(id);
|
||||||
|
let mut members = String::from("jsrt_header h;");
|
||||||
|
let mut body = String::from(" o->h.cell = cell;\n");
|
||||||
|
|
||||||
|
// An array's elements come after its header. `items` has room for
|
||||||
|
// `cap` of them. An element holds undefined for a hole
|
||||||
|
let elem = cell.is_array().then(|| self.elem_rep(id));
|
||||||
|
|
||||||
|
if let Some(elem) = elem {
|
||||||
|
let _ = write!(members, " int32_t length; int32_t cap; {} *items;", self.c_type(elem));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (field, f) in &cell.info().fields {
|
||||||
|
let absent = cell.can_be_absent(*field);
|
||||||
|
let rep = self.slot_rep(&f.ty, absent);
|
||||||
|
let member = self.field_name(*field);
|
||||||
|
let _ = write!(members, " {} {member};", self.c_type(rep));
|
||||||
|
|
||||||
|
if absent {
|
||||||
|
let _ = writeln!(body, " o->{member} = {};", self.absent(rep));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = writeln!(out, "struct {name} {{ {members} }};");
|
||||||
|
self.helper(
|
||||||
|
format!("static {name} *init_{name}({name} *o, uint32_t cell)"),
|
||||||
|
format!("{body} return o;\n"),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(elem) = elem {
|
||||||
|
let (t, hole) = (self.c_type(elem), self.conv("(undefined_t){0}", Rep::Undefined, elem));
|
||||||
|
self.helper(
|
||||||
|
format!("static void set_length_{name}({name} *a, int32_t n)"),
|
||||||
|
format!(
|
||||||
|
" if (n < 0) jsrt_type_error();\n\
|
||||||
|
\x20 a->items = jsrt_grow(a->items, &a->cap, n, sizeof *a->items);\n\
|
||||||
|
\x20 for (int32_t i = a->length; i < n; i++) a->items[i] = {hole};\n\
|
||||||
|
\x20 a->length = n;\n"
|
||||||
|
),
|
||||||
|
);
|
||||||
|
self.helper(
|
||||||
|
format!("static {t} elem_{name}({name} *a, int32_t i)"),
|
||||||
|
format!(" return i >= 0 && i < a->length ? a->items[i] : {hole};\n"),
|
||||||
|
);
|
||||||
|
self.helper(
|
||||||
|
format!("static void set_elem_{name}({name} *a, int32_t i, {t} v)"),
|
||||||
|
" if (i < 0) jsrt_type_error();\n if (i >= a->length) set_length_{NAME}(a, i + 1);\n a->items[i] = v;\n"
|
||||||
|
.replace("{NAME}", &name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn helper(&mut self, sig: String, body: String) {
|
||||||
|
let _ = writeln!(self.helper_protos, "{sig};");
|
||||||
|
let _ = writeln!(self.helper_defs, "{sig} {{\n{body}}}\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,7 +49,6 @@ pub struct Field {
|
|||||||
pub struct CellInfo {
|
pub struct CellInfo {
|
||||||
pub kind: ObjKind,
|
pub kind: ObjKind,
|
||||||
pub fields: Vec<(AtomRef, Field)>, // sorted by name
|
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
|
/// The type of an object. Every copy of an `Obj` type holds the same cell, so
|
||||||
@@ -59,8 +58,8 @@ pub struct CellInfo {
|
|||||||
pub struct Cell(Rc<RefCell<CellInfo>>);
|
pub struct Cell(Rc<RefCell<CellInfo>>);
|
||||||
|
|
||||||
impl Cell {
|
impl Cell {
|
||||||
pub fn new(kind: ObjKind, made: Option<(usize, usize)>) -> Self {
|
pub fn new(kind: ObjKind) -> Self {
|
||||||
Cell(Rc::new(RefCell::new(CellInfo { kind, fields: vec![], made })))
|
Cell(Rc::new(RefCell::new(CellInfo { kind, fields: vec![] })))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn info(&self) -> Ref<'_, CellInfo> {
|
pub fn info(&self) -> Ref<'_, CellInfo> {
|
||||||
|
|||||||
+72
-108
@@ -1,9 +1,17 @@
|
|||||||
//! Infers a type for every value (see `docs/model.md`, section 4)
|
//! Infers a type for every value (see `docs/model.md`, section 4)
|
||||||
|
//!
|
||||||
|
//! A request is a fn, the call place that calls it, and the types of its args.
|
||||||
|
//! Its result is the type of each of its values. An object is named by the
|
||||||
|
//! instr that makes it and the call place of the request that runs the instr,
|
||||||
|
//! and each name has one cell for the whole program. Both come from the
|
||||||
|
//! program text, so there are finitely many requests and names, and infer
|
||||||
|
//! ends. A walk reads cells, protos and the returns of other requests, which
|
||||||
|
//! only grow, so infer walks the program in rounds until one changes nothing
|
||||||
|
|
||||||
pub mod cells;
|
pub mod cells;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
libqjs::parse::{AtomRef, JsValue, opcodes::OpCode},
|
libqjs::parse::{AtomRef, JsValue, opcodes::OpCode},
|
||||||
@@ -15,15 +23,17 @@ use crate::{
|
|||||||
use cells::{Cell, Code, LENGTH_FIELD, ObjKind, Protos, read_value};
|
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};
|
use types::{BaseType, EMPTY_STRING, Type, is_function, join, loose_eq, strict_eq, test, truthiness, typeof_name};
|
||||||
|
|
||||||
/// The walk of one fn with one list of arg types
|
/// A call place: a fn and the index of its call instr
|
||||||
#[derive(Debug)]
|
pub type Place = (usize, usize);
|
||||||
pub struct Walk {
|
|
||||||
|
/// The call place of the entry fn, which the host calls
|
||||||
|
pub const HOST: Place = (usize::MAX, 0);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||||
|
pub struct InferRequest {
|
||||||
pub fn_id: usize,
|
pub fn_id: usize,
|
||||||
pub arg_types: Vec<Type>, // one for each param
|
pub place: Place,
|
||||||
cells: Vec<(usize, Cell)>, // the cells it made, by the instr that makes their objects
|
pub arg_types: Vec<Type>, // one for each param
|
||||||
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)]
|
#[derive(Debug)]
|
||||||
@@ -34,30 +44,38 @@ pub struct InferResult {
|
|||||||
pub reachable: Vec<bool>, // the blocks that can run
|
pub reachable: Vec<bool>, // the blocks that can run
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The walks of the program, by their keys
|
/// A request and its result
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Walk {
|
||||||
|
pub req: InferRequest,
|
||||||
|
ret: Type, // what it returned in the rounds so far: a recursive call gives that
|
||||||
|
walking: bool,
|
||||||
|
pub result: Option<InferResult>, // in the current round, once it is done
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TypeCache {
|
pub struct TypeCache {
|
||||||
pub walks: Vec<Walk>,
|
pub walks: Vec<Walk>,
|
||||||
keys: HashMap<(usize, Vec<Type>), usize>, // the walk of each fn and list of arg types
|
ids: HashMap<InferRequest, usize>, // the walk of each request
|
||||||
|
objects: BTreeMap<(usize, usize, Place), Cell>, // the cell of each name: the fn and instr that make the objects, and the call place
|
||||||
builtins: Vec<Cell>, // the cells of the natives, then of the proto links
|
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
|
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
|
version: usize, // goes up on every change of a cell, a return or a proto, and on every new cell or walk
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypeCache {
|
impl TypeCache {
|
||||||
/// The key of a call of `fn_id` with args of these types: the fn, and the
|
/// The request of a call of `fn_id` from `place` with args of these types.
|
||||||
/// type of each of its params. An arg it does not pass is undefined, and
|
/// An arg it does not pass is undefined, and an arg it passes beyond its
|
||||||
/// an arg it passes beyond them is never read. Only an arg's kind crosses
|
/// params is never read. Only an arg's kind crosses a call, not a known
|
||||||
/// a call, not a known value, else every literal passed would make a new
|
/// value, else every literal passed would make a new walk
|
||||||
/// walk. An object is its cell
|
fn request(fn_id: usize, place: Place, arg_types: &[Type], fns: &[RegFn]) -> InferRequest {
|
||||||
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));
|
let params = (0..fns[fn_id].arg_count).map(|i| arg_types.get(i).map_or(Type::Undefined, Type::widen));
|
||||||
(fn_id, params.collect())
|
InferRequest { fn_id, place, arg_types: params.collect() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The walk of 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 {
|
pub fn find(&self, fn_id: usize, place: Place, arg_types: &[Type], fns: &[RegFn]) -> usize {
|
||||||
self.keys[&Self::key(fn_id, arg_types, fns)]
|
self.ids[&Self::request(fn_id, place, arg_types, fns)]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The cell of a built-in fn
|
/// The cell of a built-in fn
|
||||||
@@ -74,44 +92,19 @@ impl TypeCache {
|
|||||||
&self.builtins
|
&self.builtins
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Every cell: the built-in ones, then the ones that each walk made
|
/// Every cell: the built-in ones, then one for each name
|
||||||
pub fn cells(&self) -> impl Iterator<Item = &Cell> {
|
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(self.objects.values())
|
||||||
self.builtins.iter().chain(made)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_cell(&mut self, kind: ObjKind, made: Option<(usize, usize)>) -> Cell {
|
/// The cell of the objects of `name`, made the first time
|
||||||
self.version += 1;
|
fn object(&mut self, name: (usize, usize, Place), kind: ObjKind) -> Cell {
|
||||||
Cell::new(kind, made)
|
if let Some(cell) = self.objects.get(&name) {
|
||||||
}
|
return cell.clone();
|
||||||
|
|
||||||
/// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
self.version += 1;
|
||||||
|
self.objects.entry(name).or_insert(Cell::new(kind)).clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Count a change, if there was one
|
/// Count a change, if there was one
|
||||||
@@ -119,24 +112,17 @@ impl TypeCache {
|
|||||||
self.version += changed as usize;
|
self.version += changed as usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Infer a call of `fn_id`, walking it unless the walk of its key is done
|
/// Infer a call of `fn_id` from `place`, walking it unless its request is
|
||||||
/// in this round. Gives what it returns
|
/// done in this round. Gives what it returns
|
||||||
fn call(&mut self, fn_id: usize, arg_types: &[Type], fns: &[RegFn]) -> Type {
|
fn call(&mut self, fn_id: usize, place: Place, arg_types: &[Type], fns: &[RegFn]) -> Type {
|
||||||
let key = Self::key(fn_id, arg_types, fns);
|
let req = Self::request(fn_id, place, arg_types, fns);
|
||||||
|
|
||||||
let w = match self.keys.get(&key) {
|
let w = match self.ids.get(&req) {
|
||||||
Some(&w) => w,
|
Some(&w) => w,
|
||||||
None => {
|
None => {
|
||||||
self.version += 1;
|
self.version += 1;
|
||||||
self.walks.push(Walk {
|
self.ids.insert(req.clone(), self.walks.len());
|
||||||
fn_id,
|
self.walks.push(Walk { req, ret: Type::Unknown, walking: false, result: None });
|
||||||
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
|
self.walks.len() - 1
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -151,15 +137,13 @@ impl TypeCache {
|
|||||||
let res = walk_fn(w, fns, self);
|
let res = walk_fn(w, fns, self);
|
||||||
let ret = res.ret_type.clone();
|
let ret = res.ret_type.clone();
|
||||||
|
|
||||||
let last = &self.walks[w].ret;
|
|
||||||
let joined = join(last, &ret);
|
|
||||||
let grew = joined != *last;
|
|
||||||
self.changed(grew);
|
|
||||||
|
|
||||||
let walk = &mut self.walks[w];
|
let walk = &mut self.walks[w];
|
||||||
|
let joined = join(&walk.ret, &ret);
|
||||||
|
let grew = joined != walk.ret;
|
||||||
walk.walking = false;
|
walk.walking = false;
|
||||||
walk.ret = joined;
|
walk.ret = joined;
|
||||||
walk.result = Some(res);
|
walk.result = Some(res);
|
||||||
|
self.changed(grew);
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,13 +169,13 @@ fn agree2<T: PartialEq>(a: &Type, b: &Type, f: impl Fn(&Type, &Type) -> Option<T
|
|||||||
results.all(|t| t.as_ref() == Some(&first)).then_some(first)
|
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
|
/// A call of `code` from `place` with operands of these types (see the call
|
||||||
/// `walk_fn()`). Gives what it returns
|
/// rule in `walk_fn()`). Gives what it returns
|
||||||
fn call_code(cache: &mut TypeCache, code: Code, arg_types: &[Type], fns: &[RegFn]) -> Type {
|
fn call_code(cache: &mut TypeCache, code: Code, arg_types: &[Type], fns: &[RegFn], place: Place) -> Type {
|
||||||
let args = &arg_types[FIRST_ARG as usize..];
|
let args = &arg_types[FIRST_ARG as usize..];
|
||||||
|
|
||||||
match code {
|
match code {
|
||||||
Code::Fn(fn_id) => cache.call(fn_id, arg_types, fns),
|
Code::Fn(fn_id) => cache.call(fn_id, place, arg_types, fns),
|
||||||
Code::Native(id) => native::ret_type(id, args),
|
Code::Native(id) => native::ret_type(id, args),
|
||||||
Code::ProtoLink(kind) => {
|
Code::ProtoLink(kind) => {
|
||||||
let linked = &cache.protos[kind as usize];
|
let linked = &cache.protos[kind as usize];
|
||||||
@@ -214,24 +198,19 @@ fn check_index(index: &Type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Walk the program from the entry, in rounds, until a round changes
|
/// 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
|
/// nothing. The results are those of the last round
|
||||||
/// 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 {
|
pub fn infer_types(fns: &[RegFn]) -> TypeCache {
|
||||||
let mut cache = TypeCache {
|
let mut cache = TypeCache {
|
||||||
walks: vec![],
|
walks: vec![],
|
||||||
keys: HashMap::new(),
|
ids: HashMap::new(),
|
||||||
|
objects: BTreeMap::new(),
|
||||||
builtins: vec![],
|
builtins: vec![],
|
||||||
protos: std::array::from_fn(|_| Type::Unknown),
|
protos: std::array::from_fn(|_| Type::Unknown),
|
||||||
version: 0,
|
version: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let codes = (0..NATIVES.len()).map(Code::Native).chain(ProtoOf::ALL.map(Code::ProtoLink));
|
let codes = (0..NATIVES.len()).map(Code::Native).chain(ProtoOf::ALL.map(Code::ProtoLink));
|
||||||
|
cache.builtins = codes.map(|code| Cell::new(ObjKind::Fn(code))).collect();
|
||||||
for code in codes {
|
|
||||||
let cell = cache.new_cell(ObjKind::Fn(code), None);
|
|
||||||
cache.builtins.push(cell);
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let version = cache.version;
|
let version = cache.version;
|
||||||
@@ -239,7 +218,7 @@ pub fn infer_types(fns: &[RegFn]) -> TypeCache {
|
|||||||
|
|
||||||
// The synthetic program entry is appended last by `flatten`. It is
|
// The synthetic program entry is appended last by `flatten`. It is
|
||||||
// called by the host with no args
|
// called by the host with no args
|
||||||
cache.call(fns.len() - 1, &[], fns);
|
cache.call(fns.len() - 1, HOST, &[], fns);
|
||||||
|
|
||||||
if cache.version == version {
|
if cache.version == version {
|
||||||
return cache;
|
return cache;
|
||||||
@@ -250,18 +229,14 @@ pub fn infer_types(fns: &[RegFn]) -> TypeCache {
|
|||||||
fn walk_fn(w: usize, reg_fns: &[RegFn], cache: &mut TypeCache) -> InferResult {
|
fn walk_fn(w: usize, reg_fns: &[RegFn], cache: &mut TypeCache) -> InferResult {
|
||||||
use OpCode::*;
|
use OpCode::*;
|
||||||
|
|
||||||
let fn_id = cache.walks[w].fn_id;
|
let (fn_id, place) = (cache.walks[w].req.fn_id, cache.walks[w].req.place);
|
||||||
let f = ®_fns[fn_id];
|
let f = ®_fns[fn_id];
|
||||||
let mut val_types = vec![Type::Unknown; f.val_count];
|
let mut val_types = vec![Type::Unknown; f.val_count];
|
||||||
let mut instr_types = vec![Type::Unknown; f.code.len()];
|
let mut instr_types = vec![Type::Unknown; f.code.len()];
|
||||||
let mut ret_type = Type::Unknown;
|
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)
|
// arg `i` is value id `var_count + i` (see regify)
|
||||||
for (i, ty) in cache.walks[w].arg_types.iter().enumerate() {
|
for (i, ty) in cache.walks[w].req.arg_types.iter().enumerate() {
|
||||||
val_types[f.var_count + i] = ty.clone();
|
val_types[f.var_count + i] = ty.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,18 +279,9 @@ fn walk_fn(w: usize, reg_fns: &[RegFn], cache: &mut TypeCache) -> InferResult {
|
|||||||
let idx = block.start + offset;
|
let idx = block.start + offset;
|
||||||
let arg = |i: usize| val_types[instr.args[i]].clone();
|
let arg = |i: usize| val_types[instr.args[i]].clone();
|
||||||
|
|
||||||
// The cell of the objects that this instr makes, made the
|
// The cell of the objects that this instr makes in this
|
||||||
// first time, unless the key already holds one (see
|
// request: the one of its name
|
||||||
// `made_before()`)
|
let cell_here = |cache: &mut TypeCache, kind: ObjKind| cache.object((fn_id, idx, place), kind);
|
||||||
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 {
|
let ty = match instr.op {
|
||||||
// ────────────────────────────────────────── Literals ───────────────────────────────────────────────────────
|
// ────────────────────────────────────────── Literals ───────────────────────────────────────────────────────
|
||||||
@@ -497,7 +463,7 @@ fn walk_fn(w: usize, reg_fns: &[RegFn], cache: &mut TypeCache) -> InferResult {
|
|||||||
arg_types.clear();
|
arg_types.clear();
|
||||||
arg_types.extend(instr.args.iter().map(|&v| val_types[v].clone()));
|
arg_types.extend(instr.args.iter().map(|&v| val_types[v].clone()));
|
||||||
arg_types[0] = callee.clone();
|
arg_types[0] = callee.clone();
|
||||||
call_code(cache, code, &arg_types, reg_fns)
|
call_code(cache, code, &arg_types, reg_fns, (fn_id, idx))
|
||||||
});
|
});
|
||||||
|
|
||||||
// `new` gives what the constructor returns if that is an
|
// `new` gives what the constructor returns if that is an
|
||||||
@@ -564,8 +530,6 @@ fn walk_fn(w: usize, reg_fns: &[RegFn], cache: &mut TypeCache) -> InferResult {
|
|||||||
}
|
}
|
||||||
} // loop
|
} // loop
|
||||||
|
|
||||||
cache.walks[w].cells = cells;
|
|
||||||
|
|
||||||
InferResult {
|
InferResult {
|
||||||
val_types,
|
val_types,
|
||||||
instr_types,
|
instr_types,
|
||||||
|
|||||||
@@ -92,9 +92,11 @@ fn main() {
|
|||||||
.cargo_metadata(false)
|
.cargo_metadata(false)
|
||||||
.get_compiler();
|
.get_compiler();
|
||||||
|
|
||||||
|
// Each fn and global in a section of its own, so that the linker drops the
|
||||||
|
// runtime's that the program never uses
|
||||||
let status = cc
|
let status = cc
|
||||||
.to_command()
|
.to_command()
|
||||||
.args(["-std=c11", "-w"])
|
.args(["-std=c11", "-w", "-ffunction-sections", "-fdata-sections", "-Wl,--gc-sections"])
|
||||||
.arg(dir.join("program.c"))
|
.arg(dir.join("program.c"))
|
||||||
.args(["-o", "a.out", "-lm"])
|
.args(["-o", "a.out", "-lm"])
|
||||||
.status()
|
.status()
|
||||||
|
|||||||
@@ -48,14 +48,18 @@ pub fn transform(
|
|||||||
fn transform_one(mut f: FlatFn, captures: &[Vec<ClosureVar>], atoms: &[Atom]) -> FlatFn {
|
fn transform_one(mut f: FlatFn, captures: &[Vec<ClosureVar>], atoms: &[Atom]) -> FlatFn {
|
||||||
let code = declass(&f.code);
|
let code = declass(&f.code);
|
||||||
|
|
||||||
// Temp locals, after the fn's own, for the args of a `new` (see replace_new())
|
// Temp locals, after the fn's own, for the args of a `new` (see
|
||||||
|
// replace_new()). Each `new` has its own: a local has one type for its
|
||||||
|
// whole fn, so shared temps would join the args of every `new` in it
|
||||||
let temps = f.var_count;
|
let temps = f.var_count;
|
||||||
let n_temps = code.iter().filter_map(is_new).max().unwrap_or(0);
|
let n_temps: usize = code.iter().filter_map(is_new).sum();
|
||||||
|
let mut next_temp = temps;
|
||||||
|
|
||||||
let mut out = Vec::with_capacity(code.len());
|
let mut out = Vec::with_capacity(code.len());
|
||||||
|
|
||||||
for instr in &code {
|
for instr in &code {
|
||||||
if replace_new(instr, temps, &mut out) {
|
if replace_new(instr, next_temp, &mut out) {
|
||||||
|
next_temp += is_new(instr).unwrap();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+74
-40
@@ -3,6 +3,17 @@
|
|||||||
// `try`. `reject()` and a returned rejected promise do
|
// `try`. `reject()` and a returned rejected promise do
|
||||||
// - An unhandled rejection does nothing. Node prints it and stops
|
// - An unhandled rejection does nothing. Node prints it and stops
|
||||||
// - `Promise.all` takes an array, not any iterable
|
// - `Promise.all` takes an array, not any iterable
|
||||||
|
//
|
||||||
|
// Written for type inference, which names an object by the instr that makes
|
||||||
|
// it and the call place of the call it is made in (see compiler/src/infer):
|
||||||
|
// - A promise is an object literal, made in the fn that user code calls, not
|
||||||
|
// in a helper. So each place in user code gets its own promise objects, and
|
||||||
|
// their fields are always there
|
||||||
|
// - The closures that run a reaction are made in `then`, for the same reason
|
||||||
|
// - Fulfilment and rejection have their own fns and fields, so that a result
|
||||||
|
// and a reason never share a type
|
||||||
|
// - A fn that makes a closure keeps out of the common path: a captured arg is
|
||||||
|
// in a box, which every call from one place shares
|
||||||
|
|
||||||
// The states of a promise
|
// The states of a promise
|
||||||
const __PENDING = 0;
|
const __PENDING = 0;
|
||||||
@@ -11,24 +22,49 @@ const __REJECTED = 2;
|
|||||||
|
|
||||||
class Promise {
|
class Promise {
|
||||||
constructor(executor) {
|
constructor(executor) {
|
||||||
this.state = __PENDING;
|
const p = { __proto__: this.__proto__, state: __PENDING, value: undefined, reason: undefined, resolved: false, reactions: [] };
|
||||||
this.value = undefined;
|
|
||||||
this.resolved = false; // `resolve()` or `reject()` was called, maybe with a thenable
|
|
||||||
this.reactions = [];
|
|
||||||
|
|
||||||
if (executor !== undefined) {
|
if (executor !== undefined) {
|
||||||
executor((value) => __resolve(this, value), (reason) => __reject(this, reason));
|
executor((value) => __resolve(p, value), (reason) => __reject(p, reason));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
then(onFulfilled, onRejected) {
|
then(onFulfilled, onRejected) {
|
||||||
const derived = new Promise();
|
const derived = { __proto__: this.__proto__, state: __PENDING, value: undefined, reason: undefined, resolved: false, reactions: [] };
|
||||||
const reaction = { onFulfilled: onFulfilled, onRejected: onRejected, derived: derived };
|
let value;
|
||||||
|
let reason;
|
||||||
|
|
||||||
|
const fulfilled = () => {
|
||||||
|
if (typeof onFulfilled === "function") __resolve(derived, onFulfilled(value));
|
||||||
|
else __resolve(derived, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const rejected = () => {
|
||||||
|
if (typeof onRejected === "function") __resolve(derived, onRejected(reason));
|
||||||
|
else __reject(derived, reason);
|
||||||
|
};
|
||||||
|
|
||||||
|
// What `__fulfil()` and `__settle_rejected()` call: each runs its
|
||||||
|
// handler in a job of its own
|
||||||
|
const reaction = {
|
||||||
|
fulfil: (v) => {
|
||||||
|
value = v;
|
||||||
|
__queue_job(fulfilled);
|
||||||
|
},
|
||||||
|
reject: (r) => {
|
||||||
|
reason = r;
|
||||||
|
__queue_job(rejected);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
if (this.state === __PENDING) {
|
if (this.state === __PENDING) {
|
||||||
this.reactions.push(reaction);
|
this.reactions.push(reaction);
|
||||||
|
} else if (this.state === __FULFILLED) {
|
||||||
|
reaction.fulfil(this.value);
|
||||||
} else {
|
} else {
|
||||||
__queue_reaction(reaction, this.state, this.value);
|
reaction.reject(this.reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
return derived;
|
return derived;
|
||||||
@@ -53,19 +89,19 @@ class Promise {
|
|||||||
|
|
||||||
static resolve(value) {
|
static resolve(value) {
|
||||||
if (__is_promise(value)) return value;
|
if (__is_promise(value)) return value;
|
||||||
const p = new Promise();
|
const p = { __proto__: this.prototype, state: __PENDING, value: undefined, reason: undefined, resolved: false, reactions: [] };
|
||||||
__resolve(p, value);
|
__resolve(p, value);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
static reject(reason) {
|
static reject(reason) {
|
||||||
const p = new Promise();
|
const p = { __proto__: this.prototype, state: __PENDING, value: undefined, reason: undefined, resolved: false, reactions: [] };
|
||||||
__reject(p, reason);
|
__reject(p, reason);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
static all(promises) {
|
static all(promises) {
|
||||||
const p = new Promise();
|
const p = { __proto__: this.prototype, state: __PENDING, value: undefined, reason: undefined, resolved: false, reactions: [] };
|
||||||
const values = [];
|
const values = [];
|
||||||
let left = promises.length;
|
let left = promises.length;
|
||||||
|
|
||||||
@@ -97,6 +133,13 @@ function __resolve(p, value) {
|
|||||||
__fulfil_or_adopt(p, value);
|
__fulfil_or_adopt(p, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reject `p` with `reason`, unless it was resolved or rejected before
|
||||||
|
function __reject(p, reason) {
|
||||||
|
if (p.resolved) return;
|
||||||
|
p.resolved = true;
|
||||||
|
__settle_rejected(p, reason);
|
||||||
|
}
|
||||||
|
|
||||||
// Fulfil `p` with `value`, or if `value` is a thenable, adopt its state. That
|
// Fulfil `p` with `value`, or if `value` is a thenable, adopt its state. That
|
||||||
// is in a job of its own, as the spec says, so it takes a tick more
|
// is in a job of its own, as the spec says, so it takes a tick more
|
||||||
function __fulfil_or_adopt(p, value) {
|
function __fulfil_or_adopt(p, value) {
|
||||||
@@ -104,48 +147,39 @@ function __fulfil_or_adopt(p, value) {
|
|||||||
const then = value.then;
|
const then = value.then;
|
||||||
|
|
||||||
if (typeof then === "function") {
|
if (typeof then === "function") {
|
||||||
__queue_job(() => __adopt(p, value));
|
__queue_adopt(p, value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
__settle(p, __FULFILLED, value);
|
__fulfil(p, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function __reject(p, reason) {
|
// A closure boxes the args that it captures, and the box is one for every
|
||||||
if (p.resolved) return;
|
// call from one place. So only this fn captures `p` and `value`, and only
|
||||||
p.resolved = true;
|
// thenables share its boxes
|
||||||
__settle(p, __REJECTED, reason);
|
function __queue_adopt(p, thenable) {
|
||||||
|
__queue_job(() => __adopt(p, thenable));
|
||||||
}
|
}
|
||||||
|
|
||||||
function __settle(p, state, value) {
|
// The reactions are cleared in place: a new array here would be one array
|
||||||
p.state = state;
|
// for every promise
|
||||||
|
function __fulfil(p, value) {
|
||||||
|
p.state = __FULFILLED;
|
||||||
p.value = value;
|
p.value = value;
|
||||||
|
|
||||||
const reactions = p.reactions;
|
const reactions = p.reactions;
|
||||||
p.reactions = [];
|
for (let i = 0; i < reactions.length; i++) reactions[i].fulfil(value);
|
||||||
|
reactions.length = 0;
|
||||||
for (let i = 0; i < reactions.length; i++) {
|
|
||||||
__queue_reaction(reactions[i], state, value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function __queue_reaction(reaction, state, value) {
|
function __settle_rejected(p, reason) {
|
||||||
const handler = state === __FULFILLED ? reaction.onFulfilled : reaction.onRejected;
|
p.state = __REJECTED;
|
||||||
const derived = reaction.derived;
|
p.reason = reason;
|
||||||
__queue_job(() => __react(handler, value, derived, state));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call `handler` with the value of a promise that is in `state`, and resolve
|
const reactions = p.reactions;
|
||||||
// `derived` with the result. With no handler, `derived` gets the same state
|
for (let i = 0; i < reactions.length; i++) reactions[i].reject(reason);
|
||||||
function __react(handler, value, derived, state) {
|
reactions.length = 0;
|
||||||
if (typeof handler === "function") {
|
|
||||||
__resolve(derived, handler(value));
|
|
||||||
} else if (state === __FULFILLED) {
|
|
||||||
__resolve(derived, value);
|
|
||||||
} else {
|
|
||||||
__reject(derived, value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make `p` follow the thenable `thenable`
|
// Make `p` follow the thenable `thenable`
|
||||||
@@ -155,7 +189,7 @@ function __adopt(p, thenable) {
|
|||||||
if (p.state === __PENDING) __fulfil_or_adopt(p, value);
|
if (p.state === __PENDING) __fulfil_or_adopt(p, value);
|
||||||
},
|
},
|
||||||
(reason) => {
|
(reason) => {
|
||||||
if (p.state === __PENDING) __settle(p, __REJECTED, reason);
|
if (p.state === __PENDING) __settle_rejected(p, reason);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user