diff --git a/compiler/src/emit.rs b/compiler/src/emit.rs deleted file mode 100644 index a5d0dd6..0000000 --- a/compiler/src/emit.rs +++ /dev/null @@ -1,1203 +0,0 @@ -//! Emits C: one fn for each walk that the program calls, one struct for each -//! cell (see `docs/model.md`, section 5) -//! -//! 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::{ - collections::{HashMap, HashSet}, - fmt::Write, -}; - -use crate::{ - infer::{ - InferResult, TypeCache, - cells::{Cell, Code, LENGTH_FIELD, read_value}, - types::Type, - }, - libqjs::parse::{Atom, AtomRef, ImmOperand, JsValue, opcodes::OpCode}, - native::{self, NATIVES, ProtoOf, atom_name, js_string_text}, - regify::{RegFn, RegInstr}, - transform::{FIRST_ARG, PROTO_FIELD, THIS_ARG}, -}; - -/// The number of a cell in C: the number in the header of its objects, and -/// the name of its struct -type CellNum = u32; - -/// A C representation -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -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 - Any, // any other sum: an `any_t` -} - -pub fn emit(fns: &[RegFn], cache: &TypeCache, atoms: &[Atom]) -> String { - let cells: Vec = cache.cells().cloned().collect(); - let nums = cells.iter().enumerate().map(|(i, cell)| (cell.clone(), i as CellNum)).collect(); - - let mut e = Emitter { - fns, - cache, - atoms, - cells, - nums, - queued: vec![false; cache.walks.len()], - queue: vec![], - helpers: HashSet::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, &[], fns); - let entry_args = vec!["(undefined_t){0}"; cache.walks[entry].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 \n#include \n\n// Natives\n", - ); - - for n in NATIVES { - let params: Vec = 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{structs}\n{statics}\n{fn_protos}\n{}\n{}{bodies}", - e.helper_protos, e.helper_defs - ); - let _ = writeln!(out, "int main(void) {{\n{init} {}({entry_args});\n return 0;\n}}", fn_name(cache, entry)); - out -} - -struct Emitter<'a> { - fns: &'a [RegFn], - cache: &'a TypeCache, - atoms: &'a [Atom], - cells: Vec, // every cell, by number - nums: HashMap, // the number of each cell - queued: Vec, // the walks that the code calls, by walk - queue: Vec, // the walks that the code calls, which are not written yet - helpers: HashSet, // the helpers made so far, by name - 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!(), - } - } - - // ─────────────────────────────────────────────── Types ─────────────────────────────────────────────── - - /// The representation of a value of type `ty` - 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 - fn sum_rep(&self, ty: &Type) -> Rep { - let others: Vec<&Type> = ty.members().iter().filter(|m| !m.is_nullish()).collect(); - - match others[..] { - [] => Rep::Any, - [Type::Str(_)] => Rep::OptStr, - [Type::Obj(cell)] => Rep::OptObj(self.num(cell)), - _ if others.iter().all(|m| matches!(m, Type::Obj(_))) => Rep::Objs, - _ => Rep::Any, - } - } - - /// 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 an - /// `any_t` - 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::Any, - rep => rep, - } - } - - /// The representation of an element of an array of cell `id`: its type, - /// or undefined for a hole - fn elem_rep(&self, id: CellNum) -> Rep { - self.rep(&self.cells[id as usize].read_elem()) - } - - 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::Any => "any_t".into(), - } - } - - 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::()) - } - - /// 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 - 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), - ); - } - } - - fn helper(&mut self, sig: String, body: String) { - let _ = writeln!(self.helper_protos, "{sig};"); - let _ = writeln!(self.helper_defs, "{sig} {{\n{body}}}\n"); - } - - // ──────────────────────────────────────────── Conversions ──────────────────────────────────────────── - - /// `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 - fn conv(&self, e: &str, from: Rep, to: Rep) -> String { - if from == to { - return e.into(); - } - - 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::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::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` - fn assign(&self, out: &mut String, dst: &str, expr: &str, from: Rep, to: Rep) { - let _ = writeln!(out, " {dst} = {};", self.conv(expr, from, to)); - } - - /// The absent value of a field of rep `rep` - fn absent(&self, rep: Rep) -> String { - match rep { - Rep::Any => "__any_absent()".into(), - _ => format!("(({})JSRT_ABSENT)", self.c_type(rep)), - } - } - - /// A C bool that is true if `e`, a field of rep `rep`, is not absent - fn present(&self, e: &str, rep: Rep) -> String { - match rep { - Rep::Any => format!("!__any_is_absent({e})"), - _ => format!("((void *){e} != JSRT_ABSENT)"), - } - } - - /// `e`, an `any_t` that holds an object of cell `id`, as a pointer to its - /// struct - 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 - 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::Any => format!("__any_is_nullish({e})"), - 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` - fn dispatch( - &mut self, - e: &str, - ty: &Type, - otherwise: &str, - out: &mut String, - mut body: impl FnMut(&mut Self, &Type, &str, Rep) -> Option, - ) { - 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) => {} - (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 rep { - Rep::Any => format!("__any_is_object({e})"), - _ => format!("!{}", self.nullish(e, ty)), - }; - - match (rep, &objects[..]) { - (_, []) => {} - (Rep::OptObj(_), [(_, stmt)]) => { - let _ = write!(out, "if ({is_object}) {{ {stmt} }} else "); - } - _ => { - let cell = match rep { - Rep::Any => format!("((jsrt_header *)__any_as_object({e}))->cell"), - _ => 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 - 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, Type::Obj(cell)) => self.any_pointer(e, self.num(cell)), - (Rep::Any, _) => self.conv(e, Rep::Any, 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 - 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, _) => format!("__any_is_{kind}({e})"), - (_, Type::Undefined) => format!("((void *){e} == JSRT_UNDEFINED)"), - (_, Type::Null) => format!("((void *){e} == NULL)"), - _ => format!("((void *){e} != NULL && (void *){e} != JSRT_UNDEFINED)"), - } - } - - // ───────────────────────────────────────────── Functions ───────────────────────────────────────────── - - /// Walk `w` is called, so it is written - fn use_walk(&mut self, w: usize) { - if !self.queued[w] { - self.queued[w] = true; - self.queue.push(w); - } - } - - fn signature(&self, w: usize, res: &InferResult) -> String { - let params: Vec = (self.cache.walks[w].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} {}({})", fn_name(self.cache, w), params.join(", ")) - } - - fn emit_fn(&mut self, w: usize, res: &InferResult, out: &mut String) { - let fn_id = self.cache.walks[w].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].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})") - } - - // ───────────────────────────────────────────── Instrs ──────────────────────────────────────────────── - - 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, 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); - } - - /// `obj.field = value`. A primitive has no fields to write, and undefined - /// and null throw - 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 - 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 - 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 - 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 - 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 - 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 - 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 - 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) - } - - /// A call. The callee's cell tells which code runs, and the callee is its - /// own env. A callee that is not a fn throws - fn call(&mut self, res: &InferResult, instr: &RegInstr, out: &mut String) { - let arg_types: Vec = 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()?, &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) - 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 ret_rep { - Rep::Any => "__any_is_object(r)".into(), - _ => 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 - fn call_code(&mut self, code: Code, 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, arg_types, self.fns); - self.use_walk(w); - - let walk = &self.cache.walks[w]; - let args: Vec = (walk.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!("{}({})", fn_name(self.cache, 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 = (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) - } - } - } - - // ───────────────────────────────────────────── Helpers ─────────────────────────────────────────────── - - /// A C bool that is JS's truthiness of `e`, a value of type `ty` - 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::Any => format!("__any_truthy({e})"), - } - } - - /// `e`, a value of type `ty`, as a string, like the spec's ToString - 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)), - } - } - - fn string_lit(&self, s: &str) -> String { - format!("jsrt_string_lit({})", c_str(s)) - } -} - -/// `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}") -} - -/// The C fn of walk `w` -fn fn_name(cache: &TypeCache, w: usize) -> String { - format!("f{}_{w}", cache.walks[w].fn_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 - ) -} diff --git a/compiler/src/emit/calls.rs b/compiler/src/emit/calls.rs new file mode 100644 index 0000000..d07093b --- /dev/null +++ b/compiler/src/emit/calls.rs @@ -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 = 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 = (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 = (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) + } + } + } +} diff --git a/compiler/src/emit/classes.rs b/compiler/src/emit/classes.rs new file mode 100644 index 0000000..4f267ed --- /dev/null +++ b/compiler/src/emit/classes.rs @@ -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, Vec, Vec) { + let cells: Vec = cache.cells().cloned().collect(); + let mut class: HashMap = cells.iter().map(|c| (c.clone(), 0)).collect(); + let mut sig = vec![]; + + loop { + let mut ids: HashMap, CellNum> = HashMap::new(); + let next: Vec = 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::>().len(); + cells.iter().zip(next).for_each(|(cell, n)| *class.get_mut(cell).unwrap() = n); + + if done { + break; + } + } + + let mut reps: Vec> = 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 = (0..walks.len()).map(|w| if walks[w].result.is_some() { 0 } else { w as u32 + 1 }).collect(); + let callees: Vec> = (0..walks.len()).map(|w| callees_of(fns, cache, w)).collect(); + let mut count = 0; + + loop { + let mut ids: HashMap, u32> = HashMap::new(); + let next: Vec = (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 = 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 { + 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 = 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, sig: &mut Vec) { + 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, sig: &mut Vec) { + 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> = 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) { + sig.extend(match atom { + AtomRef::TaggedInt(n) => [0, n], + AtomRef::Predefined(n) => [1, n], + AtomRef::FileLocal(n) => [2, n as u32], + }); +} diff --git a/compiler/src/emit/conv.rs b/compiler/src/emit/conv.rs new file mode 100644 index 0000000..e43bb6f --- /dev/null +++ b/compiler/src/emit/conv.rs @@ -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 { + 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 { + 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, + ) { + 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)) + } +} diff --git a/compiler/src/emit/instrs.rs b/compiler/src/emit/instrs.rs new file mode 100644 index 0000000..26e6155 --- /dev/null +++ b/compiler/src/emit/instrs.rs @@ -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); + } +} diff --git a/compiler/src/emit/mod.rs b/compiler/src/emit/mod.rs new file mode 100644 index 0000000..e75169b --- /dev/null +++ b/compiler/src/emit/mod.rs @@ -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 \n#include \n\n// Natives\n", + ); + + for n in NATIVES { + let params: Vec = 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, // a cell of each class, by number + nums: HashMap, // the class of each cell + walk_rep: Vec, // by walk: the walk whose fn it uses + queued: Vec, // the walks that the code calls, by walk + queue: Vec, // the walks that the code calls, which are not written yet + helpers: HashSet, // the helpers made so far, by name + sums: RefCell>, // 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 = (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 + ) +} diff --git a/compiler/src/emit/objects.rs b/compiler/src/emit/objects.rs new file mode 100644 index 0000000..163e6e7 --- /dev/null +++ b/compiler/src/emit/objects.rs @@ -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) + } +} diff --git a/compiler/src/emit/reps.rs b/compiler/src/emit/reps.rs new file mode 100644 index 0000000..aced9f3 --- /dev/null +++ b/compiler/src/emit/reps.rs @@ -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) -> 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 = 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 { + 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::()) + } + + /// 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"); + } +} diff --git a/compiler/src/infer/cells.rs b/compiler/src/infer/cells.rs index 05376ea..854449b 100644 --- a/compiler/src/infer/cells.rs +++ b/compiler/src/infer/cells.rs @@ -49,7 +49,6 @@ pub struct Field { pub struct CellInfo { pub kind: ObjKind, pub fields: Vec<(AtomRef, Field)>, // sorted by name - pub made: Option<(usize, usize)>, // the walk and the instr that made it. `None` for a built-in } /// The type of an object. Every copy of an `Obj` type holds the same cell, so @@ -59,8 +58,8 @@ pub struct CellInfo { pub struct Cell(Rc>); impl Cell { - pub fn new(kind: ObjKind, made: Option<(usize, usize)>) -> Self { - Cell(Rc::new(RefCell::new(CellInfo { kind, fields: vec![], made }))) + pub fn new(kind: ObjKind) -> Self { + Cell(Rc::new(RefCell::new(CellInfo { kind, fields: vec![] }))) } pub fn info(&self) -> Ref<'_, CellInfo> { diff --git a/compiler/src/infer/mod.rs b/compiler/src/infer/mod.rs index 16bd6d8..01086ed 100644 --- a/compiler/src/infer/mod.rs +++ b/compiler/src/infer/mod.rs @@ -1,9 +1,17 @@ //! 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 types; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use crate::{ libqjs::parse::{AtomRef, JsValue, opcodes::OpCode}, @@ -15,15 +23,17 @@ use crate::{ 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}; -/// The walk of one fn with one list of arg types -#[derive(Debug)] -pub struct Walk { +/// A call place: a fn and the index of its call instr +pub type Place = (usize, usize); + +/// 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 arg_types: Vec, // one for each param - cells: Vec<(usize, Cell)>, // the cells it made, by the instr that makes their objects - ret: Type, // what it returned in the rounds so far: a recursive call gives that - walking: bool, - pub result: Option, // the walk in the current round, once it is done + pub place: Place, + pub arg_types: Vec, // one for each param } #[derive(Debug)] @@ -34,30 +44,38 @@ pub struct InferResult { pub reachable: Vec, // 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, // in the current round, once it is done +} + #[derive(Debug)] pub struct TypeCache { pub walks: Vec, - keys: HashMap<(usize, Vec), usize>, // the walk of each fn and list of arg types + ids: HashMap, // 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, // the cells of the natives, then of the proto links pub protos: Protos, // the prototype of each `ProtoOf`, as the stdlib links it - version: usize, // goes up on every change of a cell, a walk's return or a proto, and on every new cell or walk + version: usize, // goes up on every change of a cell, a return or a proto, and on every new cell or walk } impl TypeCache { - /// The key of a call of `fn_id` with args of these types: the fn, and the - /// type of each of its params. An arg it does not pass is undefined, and - /// an arg it passes beyond them is never read. Only an arg's kind crosses - /// a call, not a known value, else every literal passed would make a new - /// walk. An object is its cell - fn key(fn_id: usize, arg_types: &[Type], fns: &[RegFn]) -> (usize, Vec) { + /// The request of a call of `fn_id` from `place` with args of these types. + /// An arg it does not pass is undefined, and an arg it passes beyond its + /// params is never read. Only an arg's kind crosses a call, not a known + /// value, else every literal passed would make a new walk + fn request(fn_id: usize, place: Place, arg_types: &[Type], fns: &[RegFn]) -> InferRequest { 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 - pub fn find(&self, fn_id: usize, arg_types: &[Type], fns: &[RegFn]) -> usize { - self.keys[&Self::key(fn_id, arg_types, fns)] + pub fn find(&self, fn_id: usize, place: Place, arg_types: &[Type], fns: &[RegFn]) -> usize { + self.ids[&Self::request(fn_id, place, arg_types, fns)] } /// The cell of a built-in fn @@ -74,44 +92,19 @@ impl TypeCache { &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 { - let made = self.walks.iter().flat_map(|w| w.cells.iter().map(|(_, cell)| cell)); - self.builtins.iter().chain(made) + self.builtins.iter().chain(self.objects.values()) } - fn new_cell(&mut self, kind: ObjKind, made: Option<(usize, usize)>) -> Cell { - self.version += 1; - Cell::new(kind, made) - } - - /// A cell that instr `idx` of walk `w`'s fn made, in the key of `w` or of - /// a walk that made a cell in it, and so on: nearest first. A new cell - /// would hold the instr twice in its name, and such names can nest with - /// no end, so the walk uses that cell again - fn made_before(&self, w: usize, idx: usize) -> Option { - 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); - } - } + /// The cell of the objects of `name`, made the first time + fn object(&mut self, name: (usize, usize, Place), kind: ObjKind) -> Cell { + if let Some(cell) = self.objects.get(&name) { + return cell.clone(); } - None + self.version += 1; + self.objects.entry(name).or_insert(Cell::new(kind)).clone() } /// Count a change, if there was one @@ -119,24 +112,17 @@ impl TypeCache { self.version += changed as usize; } - /// Infer a call of `fn_id`, walking it unless the walk of its key is done - /// in this round. Gives what it returns - fn call(&mut self, fn_id: usize, arg_types: &[Type], fns: &[RegFn]) -> Type { - let key = Self::key(fn_id, arg_types, fns); + /// Infer a call of `fn_id` from `place`, walking it unless its request is + /// done in this round. Gives what it returns + fn call(&mut self, fn_id: usize, place: Place, arg_types: &[Type], fns: &[RegFn]) -> Type { + 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, None => { self.version += 1; - self.walks.push(Walk { - fn_id, - arg_types: key.1.clone(), - cells: vec![], - ret: Type::Unknown, - walking: false, - result: None, - }); - self.keys.insert(key, self.walks.len() - 1); + self.ids.insert(req.clone(), self.walks.len()); + self.walks.push(Walk { req, ret: Type::Unknown, walking: false, result: None }); self.walks.len() - 1 } }; @@ -151,15 +137,13 @@ impl TypeCache { let res = walk_fn(w, fns, self); 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 joined = join(&walk.ret, &ret); + let grew = joined != walk.ret; walk.walking = false; walk.ret = joined; walk.result = Some(res); + self.changed(grew); ret } } @@ -185,13 +169,13 @@ fn agree2(a: &Type, b: &Type, f: impl Fn(&Type, &Type) -> Option Type { +/// A call of `code` from `place` with operands of these types (see the call +/// rule in `walk_fn()`). Gives what it returns +fn call_code(cache: &mut TypeCache, code: Code, arg_types: &[Type], fns: &[RegFn], place: Place) -> Type { let args = &arg_types[FIRST_ARG as usize..]; 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::ProtoLink(kind) => { 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 -/// nothing. A walk reads cells, protos and the returns of other walks, which -/// walks after it can still add to. All of them only grow, so the rounds end. -/// The results are those of the last round +/// nothing. The results are those of the last round pub fn infer_types(fns: &[RegFn]) -> TypeCache { let mut cache = TypeCache { walks: vec![], - keys: HashMap::new(), + ids: HashMap::new(), + objects: BTreeMap::new(), builtins: vec![], protos: std::array::from_fn(|_| Type::Unknown), version: 0, }; let codes = (0..NATIVES.len()).map(Code::Native).chain(ProtoOf::ALL.map(Code::ProtoLink)); - - for code in codes { - let cell = cache.new_cell(ObjKind::Fn(code), None); - cache.builtins.push(cell); - } + cache.builtins = codes.map(|code| Cell::new(ObjKind::Fn(code))).collect(); loop { 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 // called by the host with no args - cache.call(fns.len() - 1, &[], fns); + cache.call(fns.len() - 1, HOST, &[], fns); if cache.version == version { 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 { 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 mut val_types = vec![Type::Unknown; f.val_count]; let mut instr_types = vec![Type::Unknown; f.code.len()]; let mut ret_type = Type::Unknown; - // The cells that this walk made in the rounds before. A recursive call of - // the walk does not walk it, so it does not need them - let mut cells = std::mem::take(&mut cache.walks[w].cells); - // arg `i` is value id `var_count + i` (see regify) - 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(); } @@ -304,18 +279,9 @@ fn walk_fn(w: usize, reg_fns: &[RegFn], cache: &mut TypeCache) -> InferResult { let idx = block.start + offset; let arg = |i: usize| val_types[instr.args[i]].clone(); - // The cell of the objects that this instr makes, made the - // first time, unless the key already holds one (see - // `made_before()`) - let mut cell_here = |cache: &mut TypeCache, kind: ObjKind| { - let found = cells.iter().find(|(i, _)| *i == idx).map(|(_, cell)| cell.clone()); - - found.or_else(|| cache.made_before(w, idx)).unwrap_or_else(|| { - let cell = cache.new_cell(kind, Some((w, idx))); - cells.push((idx, cell.clone())); - cell - }) - }; + // The cell of the objects that this instr makes in this + // request: the one of its name + let cell_here = |cache: &mut TypeCache, kind: ObjKind| cache.object((fn_id, idx, place), kind); let ty = match instr.op { // ────────────────────────────────────────── Literals ─────────────────────────────────────────────────────── @@ -497,7 +463,7 @@ fn walk_fn(w: usize, reg_fns: &[RegFn], cache: &mut TypeCache) -> InferResult { arg_types.clear(); arg_types.extend(instr.args.iter().map(|&v| val_types[v].clone())); arg_types[0] = callee.clone(); - call_code(cache, code, &arg_types, reg_fns) + call_code(cache, code, &arg_types, reg_fns, (fn_id, idx)) }); // `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 - cache.walks[w].cells = cells; - InferResult { val_types, instr_types, diff --git a/compiler/src/main.rs b/compiler/src/main.rs index 5c66cc1..f8ec29a 100644 --- a/compiler/src/main.rs +++ b/compiler/src/main.rs @@ -92,9 +92,11 @@ fn main() { .cargo_metadata(false) .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 .to_command() - .args(["-std=c11", "-w"]) + .args(["-std=c11", "-w", "-ffunction-sections", "-fdata-sections", "-Wl,--gc-sections"]) .arg(dir.join("program.c")) .args(["-o", "a.out", "-lm"]) .status() diff --git a/compiler/src/transform/mod.rs b/compiler/src/transform/mod.rs index d299af2..4af907c 100644 --- a/compiler/src/transform/mod.rs +++ b/compiler/src/transform/mod.rs @@ -48,14 +48,18 @@ pub fn transform( fn transform_one(mut f: FlatFn, captures: &[Vec], atoms: &[Atom]) -> FlatFn { 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 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()); 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; } diff --git a/lib/stdlib/promise.js b/lib/stdlib/promise.js index da6f75a..045bb77 100644 --- a/lib/stdlib/promise.js +++ b/lib/stdlib/promise.js @@ -3,6 +3,17 @@ // `try`. `reject()` and a returned rejected promise do // - An unhandled rejection does nothing. Node prints it and stops // - `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 const __PENDING = 0; @@ -11,24 +22,49 @@ const __REJECTED = 2; class Promise { constructor(executor) { - this.state = __PENDING; - this.value = undefined; - this.resolved = false; // `resolve()` or `reject()` was called, maybe with a thenable - this.reactions = []; + const p = { __proto__: this.__proto__, state: __PENDING, value: undefined, reason: undefined, resolved: false, reactions: [] }; 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) { - const derived = new Promise(); - const reaction = { onFulfilled: onFulfilled, onRejected: onRejected, derived: derived }; + const derived = { __proto__: this.__proto__, state: __PENDING, value: undefined, reason: undefined, resolved: false, reactions: [] }; + 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) { this.reactions.push(reaction); + } else if (this.state === __FULFILLED) { + reaction.fulfil(this.value); } else { - __queue_reaction(reaction, this.state, this.value); + reaction.reject(this.reason); } return derived; @@ -53,19 +89,19 @@ class Promise { static resolve(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); return p; } static reject(reason) { - const p = new Promise(); + const p = { __proto__: this.prototype, state: __PENDING, value: undefined, reason: undefined, resolved: false, reactions: [] }; __reject(p, reason); return p; } static all(promises) { - const p = new Promise(); + const p = { __proto__: this.prototype, state: __PENDING, value: undefined, reason: undefined, resolved: false, reactions: [] }; const values = []; let left = promises.length; @@ -97,6 +133,13 @@ function __resolve(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 // is in a job of its own, as the spec says, so it takes a tick more function __fulfil_or_adopt(p, value) { @@ -104,48 +147,39 @@ function __fulfil_or_adopt(p, value) { const then = value.then; if (typeof then === "function") { - __queue_job(() => __adopt(p, value)); + __queue_adopt(p, value); return; } } - __settle(p, __FULFILLED, value); + __fulfil(p, value); } -function __reject(p, reason) { - if (p.resolved) return; - p.resolved = true; - __settle(p, __REJECTED, reason); +// A closure boxes the args that it captures, and the box is one for every +// call from one place. So only this fn captures `p` and `value`, and only +// thenables share its boxes +function __queue_adopt(p, thenable) { + __queue_job(() => __adopt(p, thenable)); } -function __settle(p, state, value) { - p.state = state; +// The reactions are cleared in place: a new array here would be one array +// for every promise +function __fulfil(p, value) { + p.state = __FULFILLED; p.value = value; const reactions = p.reactions; - p.reactions = []; - - for (let i = 0; i < reactions.length; i++) { - __queue_reaction(reactions[i], state, value); - } + for (let i = 0; i < reactions.length; i++) reactions[i].fulfil(value); + reactions.length = 0; } -function __queue_reaction(reaction, state, value) { - const handler = state === __FULFILLED ? reaction.onFulfilled : reaction.onRejected; - const derived = reaction.derived; - __queue_job(() => __react(handler, value, derived, state)); -} +function __settle_rejected(p, reason) { + p.state = __REJECTED; + p.reason = reason; -// Call `handler` with the value of a promise that is in `state`, and resolve -// `derived` with the result. With no handler, `derived` gets the same state -function __react(handler, value, derived, state) { - if (typeof handler === "function") { - __resolve(derived, handler(value)); - } else if (state === __FULFILLED) { - __resolve(derived, value); - } else { - __reject(derived, value); - } + const reactions = p.reactions; + for (let i = 0; i < reactions.length; i++) reactions[i].reject(reason); + reactions.length = 0; } // Make `p` follow the thenable `thenable` @@ -155,7 +189,7 @@ function __adopt(p, thenable) { if (p.state === __PENDING) __fulfil_or_adopt(p, value); }, (reason) => { - if (p.state === __PENDING) __settle(p, __REJECTED, reason); + if (p.state === __PENDING) __settle_rejected(p, reason); }, ); }