diff --git a/compiler/src/emit/body.rs b/compiler/src/emit/body.rs new file mode 100644 index 0000000..799a3a8 --- /dev/null +++ b/compiler/src/emit/body.rs @@ -0,0 +1,119 @@ +//! The body of a C function, as it is written: its lines, with their indent, and its temps + +use std::fmt::Write as _; + +use super::program::{Case, Program}; +use super::types::CType; + +pub struct Body<'a> { + pub program: &'a Program<'a>, + text: String, + depth: usize, // the indent of the next line + temps: usize, // the number of temps so far, which names the next one +} + +impl<'a> Body<'a> { + pub fn new(program: &'a Program<'a>) -> Self { + Body { + program, + text: String::new(), + depth: 1, + temps: 0, + } + } + + /// The lines of the body + pub fn finish(self) -> String { + self.text + } + + pub fn line(&mut self, text: impl AsRef) { + let indent = self.depth * 4; + let _ = writeln!(self.text, "{:indent$}{}", "", text.as_ref()); + } + + pub fn indent(&mut self, by: isize) { + self.depth = self.depth.saturating_add_signed(by); + } + + /// Throw a TypeError that says `what` (see `__rt_type_error`) + pub fn throw(&mut self, what: &str) { + self.line(format!("__rt_type_error(\"{what}\");")); + } + + /// A new C local of C type `ctype`, which is not set. Gives its name + pub fn temp(&mut self, ctype: CType) -> String { + let name = format!("t{}", self.temps); + self.temps += 1; + self.line(format!("{} {name};", ctype.c_name())); + name + } + + /// Start the C code of one case (see `Program::cases`): an `if` for the first case, and + /// an `else if` for the others. A case with no test is the only case, and needs neither + pub fn open(&mut self, case: &Case) { + if case.tests.is_empty() { + return; + } + + let test = case.tests.join(" && "); + + if case.first { + self.line(format!("if ({test}) {{")); + } else { + self.indent(-1); + self.line(format!("}} else if ({test}) {{")); + } + + self.indent(1); + } + + /// End the C code of the cases that `open` started + pub fn close(&mut self, cases: &[Case]) { + if !cases[0].tests.is_empty() { + self.indent(-1); + self.line("}"); + } + } + + /// The line `;`, where `value`, of C type `from`, is converted to C type + /// `to`. `target` is, for example, `x = ` or `return `. A union is converted member by + /// member, from a copy, so that `value` is evaluated once + pub fn put(&mut self, target: &str, value: &str, from: CType, to: CType) { + if from == to { + return self.line(format!("{target}{value};")); + } + + if let CType::One(member) = from { + let value = self.program.convert(value, member, to); + return self.line(format!("{target}{value};")); + } + + let copy = self.temp(from); + self.line(format!("{copy} = {value};")); + let cases = self.program.cases(&[(copy, from)]); + + for case in &cases { + self.open(case); + let (value, member) = &case.operands[0]; + let value = self.program.convert(value, *member, to); + self.line(format!("{target}{value};")); + } + + self.close(&cases); + } + + /// `value`, of C type `from`, converted to C type `to`, as C. A union is converted into a + /// new temp, whose name is given + pub fn convert(&mut self, value: &str, from: CType, to: CType) -> String { + match from { + _ if from == to => value.to_string(), + CType::One(member) => self.program.convert(value, member, to), + CType::Union(_) => { + let temp = self.temp(to); + self.put(&format!("{temp} = "), value, from, to); + temp + } + } + } +} diff --git a/compiler/src/emit/func.rs b/compiler/src/emit/func.rs deleted file mode 100644 index 616dc3e..0000000 --- a/compiler/src/emit/func.rs +++ /dev/null @@ -1,371 +0,0 @@ -//! One walk as one C fn -//! -//! Each local, arg and reg is a C local, of the C type of its type in memory. A binding that -//! a closure shares is a pointer to a cell (see transform/closure.rs): its cell is made at -//! entry, and again at each `close_loc`. A captured arg is copied into its cell at entry -//! -//! Each reachable block is a label. Its instrs come in order, then its postfix copies, then -//! its jump. A jump whose condition infer decided goes to the one block that can run - -use std::collections::HashMap; -use std::fmt::Write as _; - -use super::{Emitter, layout::Member, layout::Rep}; -use crate::{ - infer::Type, - libqjs::parse::opcodes::OpCode, - regify::{BasicBlock, Operand, RegFn}, -}; - -// ─────────────────────────────────────── Writers ──────────────────────────────────────── - -/// Lines of C, indented -#[derive(Default)] -pub struct Code { - pub out: String, - depth: usize, - temps: usize, -} - -/// What prints C: a walk's fn, or a read fn. Gives the lines, and splitting on sums -pub trait Writer<'a> { - fn code(&mut self) -> &mut Code; - fn emitter(&mut self) -> &mut Emitter<'a>; - - fn line(&mut self, text: impl AsRef) { - let code = self.code(); - let indent = code.depth * 4; - let _ = writeln!(code.out, "{:indent$}{}", "", text.as_ref()); - } - - fn indent(&mut self, by: isize) { - let code = self.code(); - code.depth = code.depth.saturating_add_signed(by); - } - - /// Throw a TypeError that says `what` (see `__rt_type_error`) - fn throw(&mut self, what: &str) { - self.line(format!("__rt_type_error(\"{what}\");")); - } - - /// A new C local of C type `rep`. Gives its name - fn temp(&mut self, rep: Rep) -> String { - let ctype = self.emitter().ctype(rep); - let code = self.code(); - let name = format!("t{}", code.temps); - code.temps += 1; - self.line(format!("{ctype} {name};")); - name - } - - /// Run `body` once for each combination of the members of `ops`, with each operand as - /// a value of that member. An operand that is a sum is split by a `switch` on its tag - fn split( - &mut self, - ops: &[(String, Rep)], - body: &mut dyn FnMut(&mut Self, &[(String, Member)]), - ) { - self.split_from(ops, &mut vec![], body); - } - - fn split_from( - &mut self, - ops: &[(String, Rep)], - done: &mut Vec<(String, Member)>, - body: &mut dyn FnMut(&mut Self, &[(String, Member)]), - ) { - let Some(((expr, rep), rest)) = ops.split_first() else { - return body(self, done); - }; - - match *rep { - Rep::One(member) => { - done.push((expr.clone(), member)); - self.split_from(rest, done, body); - done.pop(); - } - Rep::Sum(id) => { - let members = self.emitter().layout.sums[id].clone(); - self.line(format!("switch ({expr}.tag) {{")); - - for (tag, member) in members.into_iter().enumerate() { - self.line(format!("case {tag}: {{")); - self.indent(1); - done.push((format!("{expr}.as.m{tag}"), member)); - self.split_from(rest, done, body); - done.pop(); - self.line("break;"); - self.indent(-1); - self.line("}"); - } - - self.line("}"); - } - } - } -} - -// ───────────────────────────────────── A walk's fn ────────────────────────────────────── - -/// The C type of a local, arg or reg of walk `w`. A captured one is a pointer to a cell of -/// this type -pub fn slot_rep(e: &mut Emitter, w: usize, slot: Operand) -> Rep { - let ty = e.cache.memory.get(e.cache.addr(w, slot, e.fns)).clone(); - e.layout.rep(&ty) -} - -/// Every local, arg and reg of a fn -fn slots(f: &RegFn) -> Vec { - let locals = (0..f.local_count).map(Operand::Local); - let args = (0..f.arg_count).map(Operand::Arg); - let regs = (0..f.reg_count).map(Operand::Reg); - locals.chain(args).chain(regs).collect() -} - -pub struct FnEmitter<'e, 'a> { - pub e: &'e mut Emitter<'a>, - pub w: usize, - pub f: &'a RegFn, - pub captured: Vec, - pub reps: HashMap, // the C type of each local, arg and reg - pub types: HashMap, // the type of each, in memory - pub ret: Rep, - pub code: Code, -} - -impl<'a> Writer<'a> for FnEmitter<'_, 'a> { - fn code(&mut self) -> &mut Code { - &mut self.code - } - - fn emitter(&mut self) -> &mut Emitter<'a> { - self.e - } -} - -/// Print walk `w` as the C fn `w` -pub fn emit_walk(e: &mut Emitter, w: usize) { - let (fns, cache) = (e.fns, e.cache); - let walk = &cache.walks[w]; - let f = &fns[walk.req.fn_idx]; - let mut reps = HashMap::new(); - let mut types = HashMap::new(); - - for slot in slots(f) { - reps.insert(slot, slot_rep(e, w, slot)); - types.insert(slot, cache.memory.get(cache.addr(w, slot, fns)).clone()); - } - - let ret = e.layout.rep(&walk.ret); - let code = Code { - depth: 1, - ..Code::default() - }; - let captured = f.captured_slots(); - let mut fe = FnEmitter { - e, - w, - f, - captured, - reps, - types, - ret, - code, - }; - - // 1. The signature, with one param for each arg - let signature = fe.signature(); - let _ = writeln!(fe.e.protos, "{signature};"); - - // 2. The locals, args and regs, and their cells - fe.declare_slots(); - - // 3. The blocks that can run - let reachable = &cache.walks[w].reachable; - - for (i, block) in f.blocks.iter().enumerate() { - if reachable[i] { - fe.indent(-1); - fe.line(format!("b{i}:;")); - fe.indent(1); - fe.block(block, reachable); - } - } - - let body = std::mem::take(&mut fe.code.out); - let _ = write!(fe.e.walk_defs, "{signature} {{\n{body}}}\n\n"); -} - -impl<'a> FnEmitter<'_, 'a> { - /// The C name of a local, arg or reg. A captured one names the pointer to its cell - pub fn name(&self, slot: Operand) -> String { - match slot { - Operand::Local(k) => format!("l{k}"), - Operand::Arg(k) => format!("a{k}"), - Operand::Reg(k) => format!("r{k}"), - } - } - - pub fn is_captured(&self, slot: Operand) -> bool { - self.captured.contains(&slot) - } - - /// The value of a local, arg or reg, and its C type - pub fn read(&self, slot: Operand) -> (String, Rep) { - let name = self.name(slot); - - match self.is_captured(slot) { - true => (format!("(*{name})"), self.reps[&slot]), - false => (name, self.reps[&slot]), - } - } - - /// Write `expr`, a value of C type `from`, into a local, arg or reg - pub fn assign(&mut self, dst: Operand, expr: &str, from: Rep) { - let value = self.e.convert(expr, from, self.reps[&dst]); - let star = if self.is_captured(dst) { "*" } else { "" }; - self.line(format!("{star}{} = {value};", self.name(dst))); - } - - /// Write a value of one member into a local, arg or reg - pub fn set(&mut self, dst: Operand, expr: &str, member: Member) { - self.assign(dst, expr, Rep::One(member)); - } - - /// `static w()`. A captured arg's param is its value, which goes into its - /// cell - fn signature(&self) -> String { - let mut params = vec![]; - - for k in 0..self.f.arg_count { - let slot = Operand::Arg(k); - let ctype = self.e.ctype(self.reps[&slot]); - let name = if self.is_captured(slot) { - format!("p{k}") - } else { - self.name(slot) - }; - params.push(format!("{ctype} {name}")); - } - - let params = if params.is_empty() { - "void".into() - } else { - params.join(", ") - }; - format!("static {} w{}({params})", self.e.ctype(self.ret), self.w) - } - - /// A C local for each local and reg, and a cell for each captured local and arg. A module - /// binding's cell is also a C global (see `Emitter::binding`) - fn declare_slots(&mut self) { - for slot in slots(self.f) { - let ctype = self.e.ctype(self.reps[&slot]); - let name = self.name(slot); - - let cell = match slot { - Operand::Local(k) if self.e.is_global(self.w, slot) => { - format!("g{k} = __rt_alloc(sizeof({ctype}))") - } - _ => format!("__rt_alloc(sizeof({ctype}))"), - }; - - match (slot, self.is_captured(slot)) { - (Operand::Arg(k), true) => { - self.line(format!("{ctype} *{name} = {cell};")); - self.line(format!("*{name} = p{k};")); - } - (Operand::Arg(_), false) => {} - (_, true) => self.line(format!("{ctype} *{name} = {cell};")), - (_, false) => self.line(format!("{ctype} {name} = {{0}};")), - } - } - } - - /// A block: its instrs, its postfix copies, then its jump - fn block(&mut self, block: &BasicBlock, reachable: &[bool]) { - use OpCode::*; - - let last = &self.f.code[block.end - 1]; - let jumps = matches!( - last.op, - Goto | Goto8 | Goto16 | IfFalse | IfFalse8 | IfTrue | IfTrue8 - ); - let end = if jumps { block.end - 1 } else { block.end }; - - for idx in block.start..end { - if !self.instr(idx) { - return; - } - } - - for copy in &block.postfix { - let (value, rep) = self.read(copy.args[0]); - self.assign(copy.dsts[0], &value, rep); - } - - // succ[0] is the jump target, succ[1] the fall-through - let can_go = |i: usize| block.succ.get(i).filter(|&&s| reachable[s]).copied(); - - match last.op { - IfFalse | IfFalse8 | IfTrue | IfTrue8 => match (can_go(0), can_go(1)) { - (Some(target), Some(next)) => { - let cond = self.truthy(last.args[0]); - let test = if matches!(last.op, IfTrue | IfTrue8) { - cond - } else { - format!("!{cond}") - }; - self.line(format!("if ({test}) goto b{target};")); - self.line(format!("goto b{next};")); - } - (Some(only), None) | (None, Some(only)) => self.line(format!("goto b{only};")), - (None, None) => self.line("abort(); // not reached"), - }, - Return | ReturnAsync | ReturnUndef => {} - _ => { - if let Some(next) = can_go(0) { - self.line(format!("goto b{next};")); - } - } - } - } - - /// Whether a local, arg or reg is truthy, in a new C local - fn truthy(&mut self, slot: Operand) -> String { - let cond = self.temp(Rep::One(Member::Bool)); - let (value, rep) = self.read(slot); - - self.split(&[(value, rep)], &mut |fe, ms| { - let truthy = fe.e.truthy(&ms[0].0, ms[0].1); - fe.line(format!("{cond} = {truthy};")); - }); - - cond - } - - /// Print instr `idx`. Gives false if no value reaches it: then it cannot run, and nor can - /// the rest of its block - fn instr(&mut self, idx: usize) -> bool { - let instr = &self.f.code[idx]; - - if matches!(instr.op, OpCode::Nop) { - return true; - } - - // The first operand of `define_field_ref` is a location, not a value - let values = if instr.op == OpCode::DefineFieldRef { - &instr.args[1..] - } else { - &instr.args[..] - }; - - if values.iter().any(|slot| self.types[slot] == Type::Unknown) { - self.line("abort(); // not reached"); - return false; - } - - self.emit_instr(idx, instr); - true - } -} diff --git a/compiler/src/emit/functions.rs b/compiler/src/emit/functions.rs new file mode 100644 index 0000000..be8714d --- /dev/null +++ b/compiler/src/emit/functions.rs @@ -0,0 +1,184 @@ +//! The C functions of the program +//! +//! There are four kinds: +//! +//! - **A JS function**, as infer walked it. Infer walks a JS function once for each set of +//! arg types that it is called with, and each walk is one C function (see `js`). +//! - **A lookup**, which gets a field that some objects of a class do not have. It gives the +//! object's own field if the object has it, else it looks on the object's prototype, and so +//! on along the prototype chain. At the end of the chain, the field is undefined. +//! - **`jsrt_start`**, which the host calls to run the program (see lib/rt/jsrt.h). +//! - **`jsrt_complete`**, which the host calls when an IO op completes. + +use super::body::Body; +use super::js; +use super::program::Program; +use super::types::{CType, ClassKind, Member}; +use super::values::{undefined, unit_literal}; +use crate::{infer::Type, libqjs::parse::AtomRef, regify::Operand, transform::PROTO_FIELD}; + +#[derive(Clone, Copy)] +pub enum Function { + Js(usize), // a JS function, as one walk of infer: the walk's index + Lookup(usize, AtomRef), // a lookup of a field on the objects of a class: the class, and the field + Start, + Complete, +} + +impl Function { + pub fn name(&self, program: &Program) -> String { + match self { + Function::Js(walk) => format!("js{walk}"), + Function::Lookup(class, field) => { + format!("lookup_c{class}_{}", program.field_name(*field)) + } + Function::Start => "jsrt_start".into(), + Function::Complete => "jsrt_complete".into(), + } + } + + pub fn signature(&self, program: &Program) -> String { + let name = self.name(program); + + match self { + Function::Js(walk) => js::signature(program, *walk), + Function::Lookup(class, field) => { + let ctype = program.types.of(&program.lookup_type(*class, *field)); + format!("static {} {name}(struct c{class} *o)", ctype.c_name()) + } + Function::Start => format!("void {name}(void)"), + Function::Complete => format!("void {name}(int32_t h)"), + } + } + + pub fn definition(&self, program: &Program) -> String { + let body = match self { + Function::Js(walk) => js::body(program, *walk), + Function::Lookup(class, field) => lookup_body(program, *class, *field), + Function::Start => start_body(program), + Function::Complete => complete_body(program), + }; + + format!("{} {{\n{body}}}\n\n", self.signature(program)) + } +} + +/// The lookup of `field` on `o`, an object of `class`: its own field, then its prototype +fn lookup_body(program: &Program, class: usize, field: AtomRef) -> String { + let mut body = Body::new(program); + let ctype = program.types.of(&program.lookup_type(class, field)); + let (undefined, undefined_ctype) = undefined(); + let own_field = program.types.field(class, field); + let proto_field = program.types.field(class, PROTO_FIELD); + + // 1. Its own field, if it has it + if let Some(own) = own_field { + let value = program.field_access("o", own); + + if own.defined { + body.put("return ", &value, own.ctype, ctype); + } else { + body.line(format!("if (o->has_{}) {{", program.field_name(field))); + body.indent(1); + body.put("return ", &value, own.ctype, ctype); + body.indent(-1); + body.line("}"); + } + } + + // 2. Its prototype. An array's is the stdlib's. Another object's is its `__proto__` + // field, which it can lack + let proto = match program.types.classes[class].kind { + ClassKind::Array => (program.builtin_proto(Member::Obj(class))) + .map(|(value, class)| (value, CType::One(Member::Obj(class)))), + _ => proto_field.map(|own| (program.field_access("o", own), own.ctype)), + }; + + let Some(proto) = proto else { + body.put("return ", &undefined, undefined_ctype, ctype); + return body.finish(); + }; + + if proto_field.is_some_and(|own| !own.defined) { + body.line(format!( + "if (!o->has_{}) {{", + program.field_name(PROTO_FIELD) + )); + body.indent(1); + body.put("return ", &undefined, undefined_ctype, ctype); + body.indent(-1); + body.line("}"); + } + + // 3. The field on the prototype, for each class that the prototype can be. Anything + // else, such as null, ends the chain + let cases = program.cases(&[proto]); + + for case in &cases { + body.open(case); + + let (value, from) = match case.operands[0] { + (ref proto, Member::Obj(class)) => program.field_value(class, field, proto), + _ => (undefined.clone(), undefined_ctype), + }; + + body.put("return ", &value, from, ctype); + } + + body.close(&cases); + body.line("abort();"); + body.finish() +} + +/// Call the entry, with undefined args +fn start_body(program: &Program) -> String { + let arg_count = program.fns[program.fns.len() - 1].arg_count; + let undefined = ( + unit_literal(Member::Undefined).to_string(), + Member::Undefined, + ); + let call = host_call(program, program.entry, &vec![undefined; arg_count]); + format!(" {call};\n") +} + +/// Call the stdlib's `__complete` with `h`, the handle of the op, if the program has it +fn complete_body(program: &Program) -> String { + match complete_call(program) { + Some(call) => format!(" (void)h;\n {call};\n"), + None => " (void)h;\n".into(), + } +} + +/// The call of `__complete`. Infer made the same call (see `TypeCache::call_host_complete`) +fn complete_call(program: &Program) -> Option { + let (callee, ctype) = program.binding("__complete")?; + let class = ctype.class()?; + let ClassKind::Closure(fn_idx) = program.types.classes[class].kind else { + return None; + }; + + let obj = program.types.classes[class].objs[0]; + let types = [Type::Ref(obj), Type::Undefined, Type::Int]; + let walk = program.cache.find(fn_idx, None, &types, program.fns); + let args = [ + (callee, Member::Obj(class)), + ( + unit_literal(Member::Undefined).to_string(), + Member::Undefined, + ), + ("h".to_string(), Member::Int), + ]; + Some(host_call(program, walk, &args)) +} + +/// A call of a JS function's walk, with each arg converted to its param's C type +fn host_call(program: &Program, walk: usize, args: &[(String, Member)]) -> String { + let args: Vec = (args.iter().enumerate()) + .map(|(i, (value, member))| { + let param = program.slot_ctype(walk, Operand::Arg(i)); + program.convert(value, *member, param) + }) + .collect(); + + format!("js{walk}({})", args.join(", ")) +} diff --git a/compiler/src/emit/instr.rs b/compiler/src/emit/instr.rs index ba562a6..41ff5af 100644 --- a/compiler/src/emit/instr.rs +++ b/compiler/src/emit/instr.rs @@ -1,14 +1,14 @@ -//! The C of each instr: literals, operators, tests, moves, calls and returns. Objects are in -//! `objects` +//! The C of each instr: literals, operators, tests, moves, calls and returns. The instrs on +//! objects are in `objects` //! -//! A rule takes its operands' members and gives the C of its result, and the member of that -//! result. Operands that an instr pops come top first (see regify), so a binary operator -//! has its right side in `args[0]` +//! An instr's C depends on the members of its operands. Each combination of members is one +//! case, with C of its own (see `Program::cases`). The operands that an instr pops come top +//! first (see regify), so a binary operator has its right side in `args[0]` use super::{ - func::{FnEmitter, Writer}, - layout::{ClassKind, Member, Rep}, - undefined, unit_literal, + js::JsFunction, + types::{CType, ClassKind, Member}, + values::{base, int, strict_eq, to_int32, to_number, truthy, undefined, unit_literal}, }; use crate::{ infer::{InstrRef, ObjAddr, Type}, @@ -18,23 +18,11 @@ use crate::{ transform::{FIRST_ARG, THIS_ARG}, }; -/// What a conversion of an object to a string or a number throws: it would call the object's -/// methods, which is not supported -const NO_CONVERSION: &str = "converting an object to a string or a number is not supported"; +/// What converting an object to a string or a number throws. The conversion would call the +/// object's methods, which is not supported +pub const NO_CONVERSION: &str = "converting an object to a string or a number is not supported"; -/// A JS base type: the kinds of value that `===` tells apart -fn base(member: Member) -> u8 { - match member { - Member::Undefined => 0, - Member::Null => 1, - Member::Bool => 2, - Member::Int | Member::Float => 3, - Member::Str => 4, - Member::Obj(_) | Member::Native(_) => 5, - } -} - -impl FnEmitter<'_, '_> { +impl JsFunction<'_> { pub(super) fn emit_instr(&mut self, idx: usize, instr: &RegInstr) { use OpCode::*; @@ -43,7 +31,7 @@ impl FnEmitter<'_, '_> { | PushI16 | PushI32 | PushTrue | PushFalse | PushAtomValue | PushEmptyString | PushConst | PushConst8 | Undefined | Null => { let (expr, member) = self.literal(instr); - self.set(instr.dsts[0], &expr, member); + self.set_member(instr.dsts[0], &expr, member); } // Operators @@ -58,17 +46,17 @@ impl FnEmitter<'_, '_> { Lnot | Typeof | IsUndefined | TypeofIsUndefined | IsNull | IsUndefinedOrNull | TypeofIsFunction => self.test(instr), - // Moves. A key stays as it is: a number is an index + // Copies. A key stays as it is: a number is an index SetLoc | SetName | ToPropkey => { - let (value, rep) = self.read(instr.args[0]); - self.assign(instr.dsts[0], &value, rep); + let (value, ctype) = self.get(instr.args[0]); + self.set(instr.dsts[0], &value, ctype); } // Objects (see `objects`) Object | Fclosure => { let class = self.class_here(idx); let alloc = format!("__rt_alloc(sizeof(struct c{class}))"); - self.set(instr.dsts[0], &alloc, Member::Obj(class)); + self.set_member(instr.dsts[0], &alloc, Member::Obj(class)); } ArrayFrom => self.array_from(idx, instr), GetField | GetLength => self.get_field(instr), @@ -82,50 +70,50 @@ impl FnEmitter<'_, '_> { // Calls and returns Call | CallMethod | CallConstructor => self.call(idx, instr), Return | ReturnAsync => { - let value = self.read(instr.args[0]); - self.ret(value); + let (value, ctype) = self.get(instr.args[0]); + self.ret(&value, ctype); + } + ReturnUndef => { + let (value, ctype) = undefined(); + self.ret(&value, ctype); } - ReturnUndef => self.ret(undefined()), _ => unimplemented!("emit {instr:?}"), } } - /// The class of the objects that instr `idx` makes in this walk - pub(super) fn class_here(&self, idx: usize) -> usize { - let req = &self.e.cache.walks[self.w].req; - let made_at = InstrRef { - fn_idx: req.fn_idx, + /// Instr `idx` of this JS function + fn here(&self, idx: usize) -> InstrRef { + InstrRef { + fn_idx: self.program.cache.walks[self.walk].req.fn_idx, instr_idx: idx, - }; - self.e.layout.class(ObjAddr { - made_at, - in_call: req.in_call, - }) + } } - /// A new location for a binding that a closure shares (see transform/closure.rs). Any - /// other binding has one location + /// The class of the objects that instr `idx` makes, in this walk + pub(super) fn class_here(&self, idx: usize) -> usize { + let in_call = self.program.cache.walks[self.walk].req.in_call; + let made_at = self.here(idx); + self.program.types.class(ObjAddr { made_at, in_call }) + } + + /// `close_loc x`: a local that closures share gets a new cell with the same value. + /// Closures that point to the old cell keep it (see transform/closure.rs). A local that + /// is not shared has no cell, so nothing changes fn close_loc(&mut self, instr: &RegInstr) { let ImmOperand::Loc(k) = instr.imm else { unreachable!() }; let slot = Operand::Local(k as usize); - if self.is_captured(slot) { - let (ctype, name) = (self.e.ctype(self.reps[&slot]), self.name(slot)); - self.line(format!( + if self.is_shared(slot) { + let (ctype, name) = (self.ctype(slot).c_name(), self.name(slot)); + self.body.line(format!( "{{ {ctype} *cell = __rt_alloc(sizeof({ctype})); *cell = *{name}; {name} = cell; }}" )); } } - /// Return a value, as the fn's return type - fn ret(&mut self, (value, rep): (String, Rep)) { - let value = self.e.convert(&value, rep, self.ret); - self.line(format!("return {value};")); - } - // ───────────────────────────────────── Literals ───────────────────────────────────── /// A literal, and its member @@ -142,18 +130,18 @@ impl FnEmitter<'_, '_> { (_, ImmOperand::I32(n)) => (int(n as i64), Member::Int), (PushTrue, _) => ("true".into(), Member::Bool), (PushFalse, _) => ("false".into(), Member::Bool), - (PushAtomValue, _) => ( - self.e.string(&self.e.atom_text(instr.imm.atom())), - Member::Str, - ), - (PushEmptyString, _) => (self.e.string(""), Member::Str), + (PushAtomValue, _) => { + let text = instr.imm.atom().text(self.program.atoms); + (self.program.string(&text), Member::Str) + } + (PushEmptyString, _) => (self.program.string(""), Member::Str), (Undefined, _) => (unit_literal(Member::Undefined).into(), Member::Undefined), (Null, _) => (unit_literal(Member::Null).into(), Member::Null), _ => self.constant(instr), } } - /// A constant from the fn's constant pool, and its member + /// A constant from the JS function's constant pool, and its member fn constant(&mut self, instr: &RegInstr) -> (String, Member) { match &self.f.cpool[instr.imm.const_idx()] { JsValue::Int(n) => (int(*n as i64), Member::Int), @@ -163,7 +151,7 @@ impl FnEmitter<'_, '_> { Member::Float, ), JsValue::Float(x) => (format!("{x:?}"), Member::Float), - JsValue::String(s) => (self.e.string(&s.text()), Member::Str), + JsValue::String(s) => (self.program.string(&s.text()), Member::Str), JsValue::BoolTrue => ("true".into(), Member::Bool), JsValue::BoolFalse => ("false".into(), Member::Bool), JsValue::Undefined => (unit_literal(Member::Undefined).into(), Member::Undefined), @@ -183,35 +171,48 @@ impl FnEmitter<'_, '_> { use OpCode::*; let (dst, op) = (instr.dsts[0], instr.op); - let ops = [self.read(instr.args[1]), self.read(instr.args[0])]; + let cases = self + .program + .cases(&[self.get(instr.args[1]), self.get(instr.args[0])]); + let text = |m: Member| matches!(m, Member::Str | Member::Obj(_) | Member::Native(_)); - self.split(&ops, &mut |fe, ms| { - let ((l, lm), (r, rm)) = (&ms[0], &ms[1]); - let text = |m: Member| matches!(m, Member::Str | Member::Obj(_) | Member::Native(_)); + for case in &cases { + self.body.open(case); + let ((l, lm), (r, rm)) = (&case.operands[0], &case.operands[1]); if op == Add && (text(*lm) || text(*rm)) { - let (Some(ls), Some(rs)) = (fe.e.to_string(l, *lm), fe.e.to_string(r, *rm)) else { - return fe.throw(NO_CONVERSION); + let (Some(ls), Some(rs)) = ( + self.program.to_string(l, *lm), + self.program.to_string(r, *rm), + ) else { + self.body.throw(NO_CONVERSION); + continue; }; - return fe.set(dst, &format!("__rt_string_concat({ls}, {rs})"), Member::Str); + self.set_member(dst, &format!("__rt_string_concat({ls}, {rs})"), Member::Str); + continue; } - let (Some(ln), Some(rn)) = (fe.e.to_number(l, *lm), fe.e.to_number(r, *rm)) else { - return fe.throw(NO_CONVERSION); + let (Some(ln), Some(rn)) = (to_number(l, *lm), to_number(r, *rm)) else { + self.body.throw(NO_CONVERSION); + continue; }; // Numbers are doubles: C's operators on doubles are JS's, `%` is C's `fmod`, and // `**` differs only for NaN and infinite exponents (see `__rt_pow`) let expr = match op { - Add | Sub | Mul => format!("({ln} {} {rn})", c_op(op)), + Add => format!("({ln} + {rn})"), + Sub => format!("({ln} - {rn})"), + Mul => format!("({ln} * {rn})"), Mod => format!("fmod({ln}, {rn})"), Pow => format!("__rt_pow({ln}, {rn})"), _ => format!("({ln} / {rn})"), }; - fe.set(dst, &expr, Member::Float); - }); + self.set_member(dst, &expr, Member::Float); + } + + self.body.close(&cases); } /// `&`, `|`, `^`, `<<`, `>>` and `>>>`. Each operand becomes an int32, as JS's ToInt32 @@ -220,13 +221,17 @@ impl FnEmitter<'_, '_> { use OpCode::*; let (dst, op) = (instr.dsts[0], instr.op); - let ops = [self.read(instr.args[1]), self.read(instr.args[0])]; + let cases = self + .program + .cases(&[self.get(instr.args[1]), self.get(instr.args[0])]); - self.split(&ops, &mut |fe, ms| { - let ((l, lm), (r, rm)) = (&ms[0], &ms[1]); + for case in &cases { + self.body.open(case); + let ((l, lm), (r, rm)) = (&case.operands[0], &case.operands[1]); - let (Some(a), Some(b)) = (fe.e.to_int32(l, *lm), fe.e.to_int32(r, *rm)) else { - return fe.throw(NO_CONVERSION); + let (Some(a), Some(b)) = (to_int32(l, *lm), to_int32(r, *rm)) else { + self.body.throw(NO_CONVERSION); + continue; }; let (expr, member) = match op { @@ -244,8 +249,10 @@ impl FnEmitter<'_, '_> { ), }; - fe.set(dst, &expr, member); - }); + self.set_member(dst, &expr, member); + } + + self.body.close(&cases); } /// `-v`, `+v`, `v + 1`, `v - 1` and `~v`, on the value as a number. Only `+v` keeps an int @@ -255,16 +262,19 @@ impl FnEmitter<'_, '_> { use OpCode::*; let (dst, op) = (instr.dsts[0], instr.op); - let ops = [self.read(instr.args[0])]; + let cases = self.program.cases(&[self.get(instr.args[0])]); - self.split(&ops, &mut |fe, ms| { - let (v, m) = &ms[0]; - let Some(n) = fe.e.to_number(v, *m) else { - return fe.throw(NO_CONVERSION); + for case in &cases { + self.body.open(case); + let (v, m) = &case.operands[0]; + + let (Some(n), Some(int)) = (to_number(v, *m), to_int32(v, *m)) else { + self.body.throw(NO_CONVERSION); + continue; }; let (expr, member) = match (op, *m) { - (Not, _) => (format!("(~{})", fe.e.to_int32(v, *m).unwrap()), Member::Int), + (Not, _) => (format!("(~{int})"), Member::Int), (Plus, Member::Int) => (v.clone(), Member::Int), (Plus, _) => (n, Member::Float), (Neg, _) => (format!("(-{n})"), Member::Float), @@ -272,8 +282,10 @@ impl FnEmitter<'_, '_> { _ => (format!("({n} - 1)"), Member::Float), }; - fe.set(dst, &expr, member); - }); + self.set_member(dst, &expr, member); + } + + self.body.close(&cases); } /// `v++` and `v--`: the old value, as a number, then the new one @@ -284,42 +296,56 @@ impl FnEmitter<'_, '_> { } else { "-" }; - let ops = [self.read(instr.args[0])]; + let cases = self.program.cases(&[self.get(instr.args[0])]); - self.split(&ops, &mut |fe, ms| { - let (v, m) = &ms[0]; + for case in &cases { + self.body.open(case); + let (v, m) = &case.operands[0]; - let Some(n) = fe.e.to_number(v, *m) else { - return fe.throw(NO_CONVERSION); + let Some(n) = to_number(v, *m) else { + self.body.throw(NO_CONVERSION); + continue; }; - fe.set(old, &n, Member::Float); - fe.set(new, &format!("({n} {step} 1)"), Member::Float); - }); + self.set_member(old, &n, Member::Float); + self.set_member(new, &format!("({n} {step} 1)"), Member::Float); + } + + self.body.close(&cases); } /// `<`, `<=`, `>` and `>=`. Two strings compare as strings, anything else as numbers fn compare(&mut self, instr: &RegInstr) { - let (dst, op) = (instr.dsts[0], c_op(instr.op)); - let ops = [self.read(instr.args[1]), self.read(instr.args[0])]; + let dst = instr.dsts[0]; + let op = match instr.op { + OpCode::Lt => "<", + OpCode::Lte => "<=", + OpCode::Gt => ">", + _ => ">=", + }; + let cases = self + .program + .cases(&[self.get(instr.args[1]), self.get(instr.args[0])]); - self.split(&ops, &mut |fe, ms| { - let ((l, lm), (r, rm)) = (&ms[0], &ms[1]); + for case in &cases { + self.body.open(case); + let ((l, lm), (r, rm)) = (&case.operands[0], &case.operands[1]); - if (lm, rm) == (&Member::Str, &Member::Str) { - return fe.set( - dst, - &format!("(__rt_string_cmp({l}, {r}) {op} 0)"), - Member::Bool, - ); + if (*lm, *rm) == (Member::Str, Member::Str) { + let expr = format!("(__rt_string_cmp({l}, {r}) {op} 0)"); + self.set_member(dst, &expr, Member::Bool); + continue; } - let (Some(ln), Some(rn)) = (fe.e.to_number(l, *lm), fe.e.to_number(r, *rm)) else { - return fe.throw(NO_CONVERSION); + let (Some(ln), Some(rn)) = (to_number(l, *lm), to_number(r, *rm)) else { + self.body.throw(NO_CONVERSION); + continue; }; - fe.set(dst, &format!("({ln} {op} {rn})"), Member::Bool); - }); + self.set_member(dst, &format!("({ln} {op} {rn})"), Member::Bool); + } + + self.body.close(&cases); } /// `===`, `!==`, `==` and `!=` @@ -327,202 +353,195 @@ impl FnEmitter<'_, '_> { use OpCode::*; let (dst, op) = (instr.dsts[0], instr.op); - let ops = [self.read(instr.args[1]), self.read(instr.args[0])]; + let not = if matches!(op, Neq | StrictNeq) { + "!" + } else { + "" + }; + let cases = self + .program + .cases(&[self.get(instr.args[1]), self.get(instr.args[0])]); + let nullish = |m: Member| matches!(m, Member::Undefined | Member::Null); - self.split(&ops, &mut |fe, ms| { - let ((l, lm), (r, rm)) = (&ms[0], &ms[1]); - let nullish = |m: Member| matches!(m, Member::Undefined | Member::Null); + for case in &cases { + self.body.open(case); + let ((l, lm), (r, rm)) = (&case.operands[0], &case.operands[1]); + let numbers = (to_number(l, *lm), to_number(r, *rm)); - let eq = match op { - // Undefined and null are equal to each other and to nothing else. Two values of - // one kind compare as `===` does. A string or a bool against a number compares - // as numbers - Eq | Neq => match (*lm, *rm) { - _ if nullish(*lm) && nullish(*rm) => "true".into(), - _ if nullish(*lm) || nullish(*rm) => "false".into(), - _ if base(*lm) == base(*rm) => strict_eq(l, *lm, r, *rm), - _ => match (fe.e.to_number(l, *lm), fe.e.to_number(r, *rm)) { - (Some(ln), Some(rn)) => format!("({ln} == {rn})"), - _ => return fe.throw(NO_CONVERSION), - }, - }, - _ => strict_eq(l, *lm, r, *rm), + // Undefined and null are equal to each other and to nothing else. Two values of + // one kind compare as `===` does. A string or a bool against a number compares as + // numbers + let eq = match (op, numbers) { + (StrictEq | StrictNeq, _) => strict_eq(l, *lm, r, *rm), + _ if nullish(*lm) || nullish(*rm) => (nullish(*lm) && nullish(*rm)).to_string(), + _ if base(*lm) == base(*rm) => strict_eq(l, *lm, r, *rm), + (_, (Some(ln), Some(rn))) => format!("({ln} == {rn})"), + _ => { + self.body.throw(NO_CONVERSION); + continue; + } }; - let expr = if matches!(op, Neq | StrictNeq) { - format!("(!{eq})") - } else { - eq - }; - fe.set(dst, &expr, Member::Bool); - }); + self.set_member(dst, &format!("({not}{eq})"), Member::Bool); + } + + self.body.close(&cases); } - /// The tests of one value: `!`, `typeof`, and whether it is undefined, null or a fn + /// The tests of one value: `!`, `typeof`, and whether it is undefined, null or a function fn test(&mut self, instr: &RegInstr) { use OpCode::*; let (dst, op) = (instr.dsts[0], instr.op); - let ops = [self.read(instr.args[0])]; + let cases = self.program.cases(&[self.get(instr.args[0])]); - self.split(&ops, &mut |fe, ms| { - let (v, m) = (&ms[0].0, ms[0].1); - let known = |b: bool| (b.to_string(), Member::Bool); + for case in &cases { + self.body.open(case); + let (v, m) = (&case.operands[0].0, case.operands[0].1); - let (expr, member) = match op { - Lnot => (format!("(!{})", fe.e.truthy(v, m)), Member::Bool), - Typeof => { + let known = match op { + IsUndefined | TypeofIsUndefined => m == Member::Undefined, + IsNull => m == Member::Null, + IsUndefinedOrNull => matches!(m, Member::Undefined | Member::Null), + TypeofIsFunction => self.program.is_function(m), + Lnot => { + self.set_member(dst, &format!("(!{})", truthy(v, m)), Member::Bool); + continue; + } + _ => { let name = match m { Member::Undefined => "undefined", Member::Bool => "boolean", Member::Int | Member::Float => "number", Member::Str => "string", - _ if fe.e.is_function(m) => "function", + _ if self.program.is_function(m) => "function", _ => "object", }; - (fe.e.string(name), Member::Str) + let name = self.program.string(name); + self.set_member(dst, &name, Member::Str); + continue; } - IsUndefined | TypeofIsUndefined => known(m == Member::Undefined), - IsNull => known(m == Member::Null), - IsUndefinedOrNull => known(matches!(m, Member::Undefined | Member::Null)), - _ => known(fe.e.is_function(m)), }; - fe.set(dst, &expr, member); - }); + self.set_member(dst, &known.to_string(), Member::Bool); + } + + self.body.close(&cases); } // ────────────────────────────────────── Calls ─────────────────────────────────────── - /// A call. Its operands are the callee's params: the callee (its env), `this`, then the - /// args (see regify). Each closure that the callee can be has its own walk (see - /// `TypeCache::find`), so a callee that is a sum is split. A callee that is not a fn - /// throws - /// - /// `new` gives what the constructor returns if that is an object, else the new object - /// itself, `this` + /// A call. Its operands are the callee's params: the callee itself, `this`, then the args + /// (see regify). Each closure that the callee can be is called by its own case. A callee + /// that is not a function throws fn call(&mut self, idx: usize, instr: &RegInstr) { - let (cache, fns) = (self.e.cache, self.e.fns); - let in_call = Some(InstrRef { - fn_idx: cache.walks[self.w].req.fn_idx, - instr_idx: idx, - }); - let arg_types: Vec = instr - .args - .iter() - .map(|slot| self.types[slot].clone()) - .collect(); - let dst = instr.dsts[0]; - let ops = [self.read(instr.args[0])]; + let cases = self.program.cases(&[self.get(instr.args[0])]); - self.split(&ops, &mut |fe, ms| { - let (callee, member) = (&ms[0].0, ms[0].1); + for case in &cases { + self.body.open(case); + let (callee, member) = &case.operands[0]; - if let Member::Native(idx) = member { - fe.native_call(idx, instr); - return; + match (*member, self.program.class_kind(*member)) { + (Member::Native(native), _) => self.native_call(native, instr), + (Member::Obj(class), ClassKind::Closure(_)) => { + self.closure_call(idx, instr, callee, class) + } + _ => self.body.throw("not a function"), } + } - let (Member::Obj(class), ClassKind::Closure(fn_idx)) = (member, fe.class_kind(member)) - else { - fe.throw("not a function"); - return; - }; - - let mut types = arg_types.clone(); - types[0] = Type::Ref(fe.e.layout.classes[class].objs[0]); - let w = cache.find(fn_idx, in_call, &types, fns); - fe.e.want(w); - - // An arg it does not pass is undefined - let args: Vec = (0..fns[fn_idx].arg_count) - .map(|i| { - let param = super::func::slot_rep(fe.e, w, Operand::Arg(i)); - - let (value, rep) = match i { - 0 => (callee.clone(), Rep::One(member)), - _ if i < instr.args.len() => fe.read(instr.args[i]), - _ => undefined(), - }; - - fe.e.convert(&value, rep, param) - }) - .collect(); - - let call = format!("w{w}({})", args.join(", ")); - - // A callee that never returns gives no value - if cache.walks[w].ret == Type::Unknown { - fe.line(format!("{call};")); - fe.line("abort(); // does not return"); - return; - } - - let ret = fe.e.layout.rep(&cache.walks[w].ret); - - if instr.op != OpCode::CallConstructor { - fe.assign(dst, &call, ret); - return; - } - - let result = fe.temp(ret); - fe.line(format!("{result} = {call};")); - let this = fe.read(instr.args[THIS_ARG as usize]); - - fe.split(&[(result, ret)], &mut |fe, ms| match ms[0].1 { - Member::Obj(_) => fe.set(dst, &ms[0].0, ms[0].1), - _ => fe.assign(dst, &this.0, this.1), - }); - }); + self.body.close(&cases); } - /// A call of a native: a C call, with the args only. An int and a float both pass for a - /// number. Any other arg of the wrong kind throws (see `native::ret_type`) + /// A call of `callee`, a closure of `class`. Infer walked the closure's JS function for + /// the types of this call's args, and that walk is the C function to call. An arg that + /// the call does not pass is undefined. `new` gives what the constructor returns if that + /// is an object, else the new object itself, which is `this` + fn closure_call(&mut self, idx: usize, instr: &RegInstr, callee: &str, class: usize) { + let program = self.program; + let dst = instr.dsts[0]; + let ClassKind::Closure(fn_idx) = program.types.classes[class].kind else { + unreachable!() + }; + + let mut types: Vec = instr.args.iter().map(|&s| self.ty(s)).collect(); + types[0] = Type::Ref(program.types.classes[class].objs[0]); + let walk = (program.cache).find(fn_idx, Some(self.here(idx)), &types, program.fns); + let mut args = vec![]; + + for i in 0..program.fns[fn_idx].arg_count { + let (value, ctype) = match i { + 0 => (callee.to_string(), CType::One(Member::Obj(class))), + _ if i < instr.args.len() => self.get(instr.args[i]), + _ => undefined(), + }; + + let param = program.slot_ctype(walk, Operand::Arg(i)); + args.push(self.body.convert(&value, ctype, param)); + } + + let call = format!("js{walk}({})", args.join(", ")); + let ret = &program.cache.walks[walk].ret; + + // A function that never returns gives no value + if *ret == Type::Unknown { + self.body.line(format!("{call};")); + self.body.line("abort(); // does not return"); + return; + } + + let ret = program.types.of(ret); + + if instr.op != OpCode::CallConstructor { + return self.set(dst, &call, ret); + } + + let result = self.body.temp(ret); + self.body.line(format!("{result} = {call};")); + let (this, this_ctype) = self.get(instr.args[THIS_ARG as usize]); + let cases = program.cases(&[(result, ret)]); + + for case in &cases { + self.body.open(case); + + match case.operands[0] { + (ref obj, Member::Obj(class)) => self.set_member(dst, obj, Member::Obj(class)), + _ => self.set(dst, &this, this_ctype), + } + } + + self.body.close(&cases); + } + + /// A call of a native: a C call, with only the args. An int and a float both pass for a + /// number. An arg of any other wrong kind throws (see `native::ret_type`). An arg that the + /// call does not pass is undefined fn native_call(&mut self, idx: usize, instr: &RegInstr) { let native = &NATIVES[idx]; let dst = instr.dsts[0]; - let mut ops = vec![]; + let slots = (0..native.arg_types.len()).map(|i| instr.args.get(FIRST_ARG as usize + i)); + let operands: Vec<(String, CType)> = slots + .map(|slot| slot.map_or_else(undefined, |&slot| self.get(slot))) + .collect(); + let cases = self.program.cases(&operands); - // An arg that the call does not pass is undefined - for i in 0..native.arg_types.len() { - let slot = FIRST_ARG as usize + i; + for case in &cases { + self.body.open(case); + let args = case.operands.iter().zip(native.arg_types); + let args: Option> = args + .map(|((v, m), param)| native_arg(v, *m, param)) + .collect(); - match instr.args.get(slot) { - Some(&arg) => ops.push(self.read(arg)), - None => ops.push(undefined()), - } - } - - self.split(&ops, &mut |fe, ms| { - let mut args = vec![]; - - for ((value, member), param) in ms.iter().zip(native.arg_types) { - match native_arg(value, *member, param) { - Some(arg) => args.push(arg), - None => return fe.throw("a native got an arg of the wrong kind"), + match args { + Some(args) => { + let call = format!("{}({})", native.name, args.join(", ")); + self.set_member(dst, &call, native_member(&native.ret_type)); } + None => self.body.throw("a native got an arg of the wrong kind"), } - - let call = format!("{}({})", native.name, args.join(", ")); - fe.set(dst, &call, native_member(&native.ret_type)); - }); - } - - /// The kind of class of an object member - pub(super) fn class_kind(&self, member: Member) -> ClassKind { - match member { - Member::Obj(class) => self.e.layout.classes[class].kind, - _ => ClassKind::Obj, } - } -} -/// The C literal of an int -fn int(n: i64) -> String { - if n == i32::MIN as i64 { - "INT32_MIN".into() - } else { - n.to_string() + self.body.close(&cases); } } @@ -547,33 +566,3 @@ fn native_member(ty: &Type) -> Member { ty => unreachable!("a native returns {ty:?}"), } } - -/// `a === b`, for one member of each. Different kinds are never equal. Numbers compare as -/// numbers, strings by their text, and objects by identity -fn strict_eq(l: &str, lm: Member, r: &str, rm: Member) -> String { - match (lm, rm) { - _ if base(lm) != base(rm) => "false".into(), - (Member::Undefined | Member::Null, _) => "true".into(), - (Member::Str, _) => format!("__rt_string_eq({l}, {r})"), - (Member::Obj(_), Member::Obj(_)) => format!("((void *){l} == (void *){r})"), - (Member::Native(a), Member::Native(b)) => (a == b).to_string(), - (Member::Obj(_) | Member::Native(_), _) => "false".into(), - _ => format!("({l} == {r})"), - } -} - -/// The C operator of a JS operator -fn c_op(op: OpCode) -> &'static str { - use OpCode::*; - - match op { - Add => "+", - Sub => "-", - Mul => "*", - Lt => "<", - Lte => "<=", - Gt => ">", - Gte => ">=", - _ => unreachable!(), - } -} diff --git a/compiler/src/emit/js.rs b/compiler/src/emit/js.rs new file mode 100644 index 0000000..872f0bd --- /dev/null +++ b/compiler/src/emit/js.rs @@ -0,0 +1,256 @@ +//! A JS function as a C function +//! +//! Infer walks each JS function once for each set of arg types that it is called with. Each +//! walk is one C function, `js`, in which every value has the type that infer found in +//! that walk. +//! +//! Each local, arg and reg is a C local, of the C type of its type. A local or arg that a +//! closure shares is a pointer to a cell (see transform/closure.rs). The cell is made when the +//! function starts, and again at each `close_loc`. A shared arg is copied into its cell when +//! the function starts. +//! +//! Each block that can run is a C label. Its instrs come in order, then its postfix copies, +//! then its jump. If infer found that a condition is always true or always false, the jump +//! goes directly to the one block that can run. + +use super::{ + body::Body, + program::{Program, slots}, + types::{CType, Member}, + values::truthy, +}; +use crate::{ + infer::Type, + libqjs::parse::opcodes::OpCode, + regify::{BasicBlock, Operand, RegFn}, +}; + +/// A JS function's walk, as its C function is written +pub struct JsFunction<'a> { + pub program: &'a Program<'a>, + pub body: Body<'a>, + pub walk: usize, + pub f: &'a RegFn, + shared: Vec, // the locals and args that closures share: each is a pointer to a cell +} + +/// `static js()`. A shared arg's param is named `p`, because its value +/// goes into a cell named `a` +pub fn signature(program: &Program, walk: usize) -> String { + let w = &program.cache.walks[walk]; + let f = &program.fns[w.req.fn_idx]; + let shared = f.captured_slots(); + + let params: Vec = (0..f.arg_count) + .map(|k| { + let ctype = program.slot_ctype(walk, Operand::Arg(k)); + let prefix = if shared.contains(&Operand::Arg(k)) { + "p" + } else { + "a" + }; + format!("{} {prefix}{k}", ctype.c_name()) + }) + .collect(); + + let params = if params.is_empty() { + "void".into() + } else { + params.join(", ") + }; + let ret = program.types.of(&w.ret); + format!("static {} js{walk}({params})", ret.c_name()) +} + +/// The body of `js`: its locals, args and regs, then each block that can run +pub fn body(program: &Program, walk: usize) -> String { + let w = &program.cache.walks[walk]; + let f = &program.fns[w.req.fn_idx]; + + let mut js = JsFunction { + program, + body: Body::new(program), + walk, + f, + shared: f.captured_slots(), + }; + + js.declare_slots(); + + for (i, block) in f.blocks.iter().enumerate() { + if w.reachable[i] { + js.body.indent(-1); + js.body.line(format!("b{i}:;")); + js.body.indent(1); + js.block(block, &w.reachable); + } + } + + js.body.finish() +} + +impl JsFunction<'_> { + /// The C name of a local, arg or reg. A shared one names the pointer to its cell + pub fn name(&self, slot: Operand) -> String { + match slot { + Operand::Local(k) => format!("l{k}"), + Operand::Arg(k) => format!("a{k}"), + Operand::Reg(k) => format!("r{k}"), + } + } + + pub fn is_shared(&self, slot: Operand) -> bool { + self.shared.contains(&slot) + } + + /// The type of a local, arg or reg + pub fn ty(&self, slot: Operand) -> Type { + self.program.slot_type(self.walk, slot) + } + + /// The C type of a local, arg or reg + pub fn ctype(&self, slot: Operand) -> CType { + self.program.slot_ctype(self.walk, slot) + } + + /// The value of a local, arg or reg, as C, and its C type + pub fn get(&self, slot: Operand) -> (String, CType) { + match self.is_shared(slot) { + true => (format!("(*{})", self.name(slot)), self.ctype(slot)), + false => (self.name(slot), self.ctype(slot)), + } + } + + /// Set a local, arg or reg to `value`, of C type `from` + pub fn set(&mut self, dst: Operand, value: &str, from: CType) { + let star = if self.is_shared(dst) { "*" } else { "" }; + let target = format!("{star}{} = ", self.name(dst)); + let to = self.ctype(dst); + self.body.put(&target, value, from, to); + } + + /// Set a local, arg or reg to `value`, of one member + pub fn set_member(&mut self, dst: Operand, value: &str, member: Member) { + self.set(dst, value, CType::One(member)); + } + + /// Return `value`, of C type `from` + pub fn ret(&mut self, value: &str, from: CType) { + let walk = &self.program.cache.walks[self.walk]; + let to = self.program.types.of(&walk.ret); + self.body.put("return ", value, from, to); + } + + /// A C local for each local and reg, and a cell for each shared local and arg. A module + /// binding, which is a local of the entry, also puts its cell in its C global + fn declare_slots(&mut self) { + let entry = self.walk == self.program.entry; + + for slot in slots(self.f) { + let ctype = self.ctype(slot).c_name(); + let name = self.name(slot); + let cell = format!("__rt_alloc(sizeof({ctype}))"); + + let line = match (slot, self.is_shared(slot)) { + (Operand::Arg(_), false) => continue, + (Operand::Arg(k), true) => format!("{ctype} *{name} = {cell}; *{name} = p{k};"), + (Operand::Local(k), true) if entry && self.program.globals.contains_key(&k) => { + format!("{ctype} *{name} = g{k} = {cell};") + } + (_, true) => format!("{ctype} *{name} = {cell};"), + (_, false) => format!("{ctype} {name} = {{0}};"), + }; + + self.body.line(line); + } + } + + /// A block: its instrs, its postfix copies, then its jump + fn block(&mut self, block: &BasicBlock, reachable: &[bool]) { + use OpCode::*; + + let last = &self.f.code[block.end - 1]; + let jumps = matches!( + last.op, + Goto | Goto8 | Goto16 | IfFalse | IfFalse8 | IfTrue | IfTrue8 + ); + let end = if jumps { block.end - 1 } else { block.end }; + + for idx in block.start..end { + if !self.instr(idx) { + return; + } + } + + for copy in &block.postfix { + let (value, ctype) = self.get(copy.args[0]); + self.set(copy.dsts[0], &value, ctype); + } + + // succ[0] is the jump's target, and succ[1] is the next block + let can_go = |i: usize| block.succ.get(i).filter(|&&s| reachable[s]).copied(); + + match last.op { + IfFalse | IfFalse8 | IfTrue | IfTrue8 => match (can_go(0), can_go(1)) { + (Some(target), Some(next)) => { + let cond = self.truthy(last.args[0]); + let not = if matches!(last.op, IfTrue | IfTrue8) { + "" + } else { + "!" + }; + self.body.line(format!("if ({not}{cond}) goto b{target};")); + self.body.line(format!("goto b{next};")); + } + (Some(only), None) | (None, Some(only)) => self.body.line(format!("goto b{only};")), + (None, None) => self.body.line("abort(); // not reached"), + }, + Return | ReturnAsync | ReturnUndef => {} + _ => { + if let Some(next) = can_go(0) { + self.body.line(format!("goto b{next};")); + } + } + } + } + + /// Whether a local, arg or reg is truthy, in a new temp. Gives the temp's name + fn truthy(&mut self, slot: Operand) -> String { + let cond = self.body.temp(CType::One(Member::Bool)); + let cases = self.program.cases(&[self.get(slot)]); + + for case in &cases { + self.body.open(case); + let (value, member) = &case.operands[0]; + self.body + .line(format!("{cond} = {};", truthy(value, *member))); + } + + self.body.close(&cases); + cond + } + + /// Write the C of instr `idx`. Gives false if an operand has no value: then the instr + /// cannot run, and nor can the rest of its block + fn instr(&mut self, idx: usize) -> bool { + let instr = &self.f.code[idx]; + + if instr.op == OpCode::Nop { + return true; + } + + // The first operand of `define_field_ref` is a location, not a value + let values = match instr.op { + OpCode::DefineFieldRef => &instr.args[1..], + _ => &instr.args[..], + }; + + if values.iter().any(|slot| self.ty(*slot) == Type::Unknown) { + self.body.line("abort(); // not reached"); + return false; + } + + self.emit_instr(idx, instr); + true + } +} diff --git a/compiler/src/emit/mod.rs b/compiler/src/emit/mod.rs index 3c18b54..1dd0dbf 100644 --- a/compiler/src/emit/mod.rs +++ b/compiler/src/emit/mod.rs @@ -1,515 +1,127 @@ //! Emits the program as one C file //! -//! Emit plans first, then prints. The plan is the layout: the C type of every type, and a -//! struct for each class of objects (see `layout`). Then each walk that the program can -//! call becomes one C function, which `func` prints from the plan +//! The C file holds only three things: types, statics and functions. So emit has two steps: //! -//! Every instr is one rule, on the members of its operands' types: an operand of one member -//! is used as it is, and an operand that is a sum is split by a `switch` on its tag, with the -//! rule's C in each case. The result is written into the destination, which converts it: -//! into a sum, that is a write of the tag and of the union's member +//! 1. `Program::new` finds all of them, from the JS functions and what infer found: +//! - **Types:** a struct for each class of objects, and a tagged union for each type with +//! several members (see `types`). +//! - **Statics:** each string literal, and a C global for each module binding. +//! - **Functions:** one for each walk of a JS function, one for each lookup, and the +//! host's two entry points (see `functions`). +//! 2. `emit` writes each of them, in an order that C can compile. //! -//! A value that no write reached (a straggler `Unknown`) is undefined +//! A value that no write reached is undefined. -mod func; +mod body; +mod functions; mod instr; -mod layout; +mod js; mod objects; +mod program; +mod types; +mod values; -use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; -use std::fmt::Write; +use crate::{flatten::ModuleBindings, infer::TypeCache, libqjs::parse::Atom, regify::RegFn}; -use crate::{ - flatten::ModuleBindings, - infer::{Type, TypeCache, module_binding}, - libqjs::parse::{Atom, AtomRef}, - regify::{Operand, RegFn}, - transform::PROTOTYPE_FIELD, -}; - -use layout::{ClassField, ClassKind, Layout, Member, Rep}; - -/// The C of the program, which includes the runtime's `jsrt.h` -pub fn emit(fns: &[RegFn], cache: &TypeCache, atoms: &[Atom], bindings: &ModuleBindings) -> String { - let mut e = Emitter::new(fns, cache, atoms, bindings); - - // 1. The host's two calls (see lib/rt/jsrt.h): the entry, when the program starts, and the - // stdlib's `__complete`, when an IO op completes - let start = e.host_start(); - let complete = e.host_complete(); - - // 2. Every fn that a call can reach, and the read fns that they use. Printing a fn can - // want more of both - loop { - if let Some(w) = e.walk_queue.pop_front() { - func::emit_walk(&mut e, w); - } else if let Some((class, name)) = e.read_queue.pop_front() { - objects::emit_read(&mut e, class, name); - } else { - break; - } - } - - // 3. The file - e.finish(&start, &complete) -} - -pub struct Emitter<'a> { - fns: &'a [RegFn], - cache: &'a TypeCache, - atoms: &'a [Atom], - bindings: &'a ModuleBindings, - layout: Layout, - entry: usize, // the entry's walk - globals: BTreeMap, // the module bindings, which are C globals: each local of the entry, and its C type - - // What printing wants, and has not printed yet - walk_queue: VecDeque, - walks_wanted: HashSet, - read_queue: VecDeque<(usize, AtomRef)>, - reads: HashMap<(usize, AtomRef), (usize, Rep)>, // the read fn of a field on a class, and what it gives - strings: Vec, // the text of each string literal - string_ids: HashMap, - convs: HashMap<(Rep, Rep), usize>, // the conversion fn from a sum, by its reps - - // What is printed, in the order that the file needs it - protos: String, - conv_defs: String, - read_defs: String, - walk_defs: String, -} - -impl<'a> Emitter<'a> { - fn new( - fns: &'a [RegFn], - cache: &'a TypeCache, - atoms: &'a [Atom], - bindings: &'a ModuleBindings, - ) -> Self { - let entry_fn = fns.len() - 1; - let mut layout = Layout::new(&cache.memory); - - // The module bindings are C globals, so that any fn can read the stdlib's - let mut globals = BTreeMap::new(); - - for &local in bindings.values() { - let ty = cache.memory.get(module_binding(entry_fn, local)); - globals.insert(local, layout.rep(ty)); - } - - Emitter { - fns, - cache, - atoms, - bindings, - layout, - entry: cache.find(entry_fn, None, &[], fns), - globals, - walk_queue: VecDeque::new(), - walks_wanted: HashSet::new(), - read_queue: VecDeque::new(), - reads: HashMap::new(), - strings: vec![], - string_ids: HashMap::new(), - convs: HashMap::new(), - protos: String::new(), - conv_defs: String::new(), - read_defs: String::new(), - walk_defs: String::new(), - } - } - - /// Print walk `w` as a C fn, unless it is printed or wanted already - fn want(&mut self, w: usize) { - if self.walks_wanted.insert(w) { - self.walk_queue.push_back(w); - } - } -} - -// ────────────────────────────────────── Host calls ────────────────────────────────────── - -impl Emitter<'_> { - /// The call of the entry, with no args: `env` and `this` are undefined - fn host_start(&mut self) -> String { - self.want(self.entry); - let arg_count = self.fns[self.fns.len() - 1].arg_count; - let entry = self.entry; - let args = self.args(entry, vec![undefined(); arg_count]); - format!("w{entry}({})", args.join(", ")) - } - - /// The call of the stdlib's `__complete` with an op's handle `h`, if the program has it - fn host_complete(&mut self) -> String { - let Some((complete, Rep::One(Member::Obj(class)))) = self.binding("__complete") else { - return String::new(); - }; - let ClassKind::Closure(fn_idx) = self.layout.classes[class].kind else { - return String::new(); - }; - - let obj = self.layout.classes[class].objs[0]; - let w = self.cache.find( - fn_idx, - None, - &[Type::Ref(obj), Type::Undefined, Type::Int], - self.fns, - ); - self.want(w); - - let args = vec![ - (complete, Rep::One(Member::Obj(class))), - undefined(), - ("h".into(), Rep::One(Member::Int)), - ]; - format!("w{w}({});", self.args(w, args).join(", ")) - } - - /// The args of a call of walk `w`: each value converted to its param's C type - fn args(&mut self, w: usize, values: Vec<(String, Rep)>) -> Vec { - let mut args = vec![]; - - for (i, (value, rep)) in values.into_iter().enumerate() { - let param = func::slot_rep(self, w, Operand::Arg(i)); - args.push(self.convert(&value, rep, param)); - } - - args - } - - /// The value of the stdlib's module binding `name`, as C, and its C type - fn binding(&self, name: &str) -> Option<(String, Rep)> { - let local = *self.bindings.get(name)?; - Some((format!("(*g{local})"), self.globals[&local])) - } - - /// Whether a local of walk `w` is a module binding, which is a C global - fn is_global(&self, w: usize, slot: Operand) -> bool { - matches!(slot, Operand::Local(local) if w == self.entry && self.globals.contains_key(&local)) - } - - /// The prototype of a primitive or an array, as C, and its C type: the `prototype` of the - /// stdlib's class (see `Memory::builtin_proto`) - fn builtin_proto(&self, member: Member) -> Option<(String, Rep)> { - let class_name = match member { - Member::Str => "String", - Member::Int | Member::Float => "Number", - Member::Bool => "Boolean", - Member::Obj(class) if self.layout.classes[class].kind == ClassKind::Array => "Array", - _ => return None, - }; - - let (class_value, Rep::One(Member::Obj(class))) = self.binding(class_name)? else { - return None; - }; - - let field = self.layout.field(class, PROTOTYPE_FIELD)?; - Some((self.field_access(&class_value, field), field.rep)) - } -} - -// ──────────────────────────────────────── Values ──────────────────────────────────────── - -impl Emitter<'_> { - /// A string literal: a pointer to a static string - fn string(&mut self, text: &str) -> String { - let next = self.strings.len(); - let id = *self.string_ids.entry(text.to_string()).or_insert(next); - - if id == next { - self.strings.push(text.to_string()); - } - - format!("(&str{id})") - } - - fn atom_text(&self, atom: AtomRef) -> String { - atom.text(self.atoms) - } - - fn ctype(&self, rep: Rep) -> String { - match rep { - Rep::One(member) => member_ctype(member), - Rep::Sum(id) => format!("s{id}"), - } - } - - /// The C name of a field - fn field_name(&self, atom: AtomRef) -> String { - let text: String = (self.atom_text(atom).chars().take(24)) - .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' }) - .collect(); - - match atom { - AtomRef::TaggedInt(n) => format!("f_{text}_i{n}"), - AtomRef::Predefined(n) => format!("f_{text}_p{n}"), - AtomRef::FileLocal(n) => format!("f_{text}_a{n}"), - } - } - - /// A field of `obj`, to read or write. A field that shares a location holds a pointer to it - fn field_access(&self, obj: &str, field: &ClassField) -> String { - let name = self.field_name(field.name); - - match field.shared { - true => format!("(*{obj}->{name})"), - false => format!("{obj}->{name}"), - } - } - - /// `expr`, a value of `member`, as a number (a `double`), as JS's ToNumber does. `None` - /// for an object: its conversion calls its methods, which is not supported - fn to_number(&self, expr: &str, member: Member) -> Option { - Some(match member { - Member::Undefined => "NAN".into(), - Member::Null => "0.0".into(), - Member::Bool | Member::Int => format!("(double){expr}"), - Member::Float => expr.into(), - Member::Str => format!("__rt_string_to_number({expr})"), - Member::Obj(_) | Member::Native(_) => return None, - }) - } - - /// `expr`, a value of `member`, as an int32, as JS's ToInt32 does. `None` for an object - fn to_int32(&self, expr: &str, member: Member) -> Option { - match member { - Member::Int => Some(expr.into()), - _ => Some(format!("__rt_to_int32({})", self.to_number(expr, member)?)), - } - } - - /// `expr`, a value of `member`, as a string, as JS's ToString does. `None` for an object: - /// its conversion calls its methods, which is not supported - fn to_string(&mut self, expr: &str, member: Member) -> Option { - Some(match member { - Member::Undefined => self.string("undefined"), - Member::Null => self.string("null"), - Member::Bool => format!( - "({expr} ? {} : {})", - self.string("true"), - self.string("false") - ), - Member::Int => format!("__rt_number_to_string((double){expr})"), - Member::Float => format!("__rt_number_to_string({expr})"), - Member::Str => expr.into(), - Member::Obj(_) | Member::Native(_) => return None, - }) - } - - /// Whether `expr`, a value of `member`, is truthy - fn truthy(&self, expr: &str, member: Member) -> String { - match member { - Member::Undefined | Member::Null => "false".into(), - Member::Bool => expr.into(), - Member::Int => format!("({expr} != 0)"), - Member::Float => format!("({expr} == {expr} && {expr} != 0)"), - Member::Str => format!("(__rt_string_length({expr}) != 0)"), - Member::Obj(_) | Member::Native(_) => "true".into(), - } - } - - /// Whether a value of `member` is a fn - fn is_function(&self, member: Member) -> bool { - match member { - Member::Obj(class) => matches!(self.layout.classes[class].kind, ClassKind::Closure(_)), - Member::Native(_) => true, - _ => false, - } - } -} - -// ───────────────────────────────────── Conversions ────────────────────────────────────── - -impl Emitter<'_> { - /// `expr`, a value of C type `from`, as a value of C type `to`. Types only grow, so - /// `to` holds every member of `from` - fn convert(&mut self, expr: &str, from: Rep, to: Rep) -> String { - if from == to { - return expr.to_string(); - } - - match (from, to) { - (Rep::One(a), Rep::One(b)) => convert_member(expr, a, b), - (Rep::One(a), Rep::Sum(id)) => { - let (tag, b) = member_in(a, &self.layout.sums[id]); - format!( - "(s{id}){{.tag = {tag}, .as.m{tag} = {}}}", - convert_member(expr, a, b) - ) - } - (Rep::Sum(_), _) => { - let next = self.convs.len(); - let id = *self.convs.entry((from, to)).or_insert(next); - - if id == next { - self.print_conv(id, from, to); - } - - format!("cv{id}({expr})") - } - } - } - - /// The fn that converts a sum `from` into `to` - fn print_conv(&mut self, id: usize, from: Rep, to: Rep) { - let (from_c, to_c) = (self.ctype(from), self.ctype(to)); - let mut body = format!("static {to_c} cv{id}({from_c} v) {{\n switch (v.tag) {{\n"); - - for (tag, member) in self.layout.rep_members(from).into_iter().enumerate() { - let value = self.convert(&format!("v.as.m{tag}"), Rep::One(member), to); - let _ = writeln!(body, " case {tag}: return {value};"); - } - - body.push_str(" }\n abort();\n}\n\n"); - let _ = writeln!(self.protos, "static {to_c} cv{id}({from_c} v);"); - self.conv_defs.push_str(&body); - } -} - -/// The undefined value, and its C type -fn undefined() -> (String, Rep) { - ( - unit_literal(Member::Undefined).into(), - Rep::One(Member::Undefined), - ) -} - -fn member_ctype(member: Member) -> String { - match member { - Member::Undefined | Member::Native(_) => "undefined_t".into(), - Member::Null => "null_t".into(), - Member::Bool => "bool".into(), - Member::Int => "int32_t".into(), - Member::Float => "double".into(), - Member::Str => "string_t".into(), - Member::Obj(class) => format!("struct c{class} *"), - } -} - -/// The literal of a member that has one value -fn unit_literal(member: Member) -> &'static str { - match member { - Member::Undefined | Member::Native(_) => "(undefined_t){0}", - Member::Null => "(null_t){0}", - _ => unreachable!(), - } -} - -/// `expr` of member `a` as member `b`: the same, or an int as a float -fn convert_member(expr: &str, a: Member, b: Member) -> String { - match (a, b) { - _ if a == b => expr.into(), - (Member::Int, Member::Float) => format!("(double){expr}"), - _ => panic!("emit: a value of {a:?} where {b:?} was inferred"), - } -} - -/// The tag and member of a sum that holds a value of member `a`: its own, or a float for -/// an int -fn member_in(a: Member, members: &[Member]) -> (usize, Member) { - let find = |m: Member| members.iter().position(|&x| x == m).map(|tag| (tag, m)); - - find(a) - .or_else(|| (a == Member::Int).then(|| find(Member::Float)).flatten()) - .unwrap_or_else(|| panic!("emit: a value of {a:?} where {members:?} was inferred")) -} - -// ─────────────────────────────────────── The file ─────────────────────────────────────── +use program::Program; +use types::{CType, ClassKind}; const HEADER: &str = "// Generated by jsrt\n#define JSRT_IMPLEMENTATION\n#include \"jsrt.h\"\n\n#include \n#include \n\n"; -impl Emitter<'_> { - /// The C file: the types, the statics, the fns, then the host's entry points - fn finish(self, start: &str, complete: &str) -> String { - let mut c = String::from(HEADER); +/// The C of the program, which includes the runtime's `jsrt.h` +pub fn emit(fns: &[RegFn], cache: &TypeCache, atoms: &[Atom], bindings: &ModuleBindings) -> String { + let program = Program::new(fns, cache, atoms, bindings); + let classes = 0..program.types.classes.len(); + let unions = 0..program.types.unions.len(); + let mut c = String::from(HEADER); - self.print_types(&mut c); - self.print_statics(&mut c); - - c.push('\n'); - c.push_str(&self.protos); - c.push('\n'); - c.push_str(&self.conv_defs); - c.push_str(&self.read_defs); - c.push_str(&self.walk_defs); - - // The host's entry points (see lib/rt/jsrt.h) - let _ = writeln!(c, "void jsrt_start(void) {{\n {start};\n}}\n"); - let _ = write!( - c, - "void jsrt_complete(int32_t h) {{\n (void)h;\n {complete}\n}}\n" - ); - c + // Types. Each struct is declared first, because a union can hold a pointer to one + for class in classes.clone() { + c += &format!("struct c{class};\n"); } - /// The structs, declared first because a sum can point to one, then the sums, then the - /// structs' fields - fn print_types(&self, c: &mut String) { - for id in 0..self.layout.classes.len() { - let _ = writeln!(c, "struct c{id};"); - } + c += "\n"; - c.push('\n'); - - for (id, members) in self.layout.sums.iter().enumerate() { - let mut fields = String::new(); - - for (tag, &member) in members.iter().enumerate() { - let _ = write!(fields, " {} m{tag};", member_ctype(member)); - } - - let _ = writeln!( - c, - "typedef struct {{ uint8_t tag; union {{{fields} }} as; }} s{id};" - ); - } - - c.push('\n'); - - for (id, class) in self.layout.classes.iter().enumerate() { - let _ = writeln!(c, "struct c{id} {{"); - - if class.kind == ClassKind::Array { - let _ = writeln!( - c, - " int32_t length, cap;\n {} *items;", - self.ctype(class.elems) - ); - } - - for field in &class.fields { - let (ctype, name) = (self.ctype(field.rep), self.field_name(field.name)); - let pointer = if field.shared { "*" } else { "" }; - let _ = writeln!(c, " {ctype} {pointer}{name};"); - - if !field.defined { - let _ = writeln!(c, " bool has_{name};"); - } - } - - // C has no empty structs - if class.kind != ClassKind::Array && class.fields.is_empty() { - c.push_str(" char _;\n"); - } - - c.push_str("};\n\n"); - } + for union in unions { + c += &union_definition(&program, union); } - /// The string literals, whose UTF-16 units are in the C, and the module bindings - fn print_statics(&self, c: &mut String) { - for (id, text) in self.strings.iter().enumerate() { - let units: Vec = text.encode_utf16().map(|u| u.to_string()).collect(); - let _ = writeln!( - c, - "static struct jsrt_string str{id} = {{{}, {{{}}}}};", - units.len(), - units.join(", ") - ); - } + c += "\n"; - for (local, rep) in &self.globals { - let _ = writeln!(c, "static {} *g{local};", self.ctype(*rep)); - } + for class in classes { + c += &struct_definition(&program, class); } + + // Statics + for (id, text) in program.strings.iter().enumerate() { + c += &string_definition(id, text); + } + + for (&local, &ctype) in &program.globals { + c += &format!("static {} *g{local};\n", ctype.c_name()); + } + + // Functions. Each is declared first, because they call each other + c += "\n"; + + for function in &program.functions { + c += &function.signature(&program); + c += ";\n"; + } + + c += "\n"; + + for function in &program.functions { + c += &function.definition(&program); + } + + c +} + +/// A tagged union: a tag, and a C union of the members. The tag is the member's index +fn union_definition(program: &Program, id: usize) -> String { + let members = program.types.unions[id].iter().enumerate(); + let members: String = members + .map(|(tag, &member)| format!(" {} m{tag};", CType::One(member).c_name())) + .collect(); + + format!("typedef struct {{ uint8_t tag; union {{{members} }} as; }} u{id};\n") +} + +/// The struct of a class: an array's length and elements, then the fields. A field that +/// shares a location is a pointer to it. A field that some objects lack has a `has_` flag +fn struct_definition(program: &Program, id: usize) -> String { + let class = &program.types.classes[id]; + let mut c = format!("struct c{id} {{\n"); + + if class.kind == ClassKind::Array { + c += &format!( + " int32_t length, cap;\n {} *items;\n", + class.elems.c_name() + ); + } + + for field in &class.fields { + let name = program.field_name(field.name); + let pointer = if field.shared { "*" } else { "" }; + c += &format!(" {} {pointer}{name};\n", field.ctype.c_name()); + + if !field.defined { + c += &format!(" bool has_{name};\n"); + } + } + + // C has no empty structs + if class.kind != ClassKind::Array && class.fields.is_empty() { + c += " char _;\n"; + } + + c + "};\n\n" +} + +/// A string literal, with its UTF-16 units +fn string_definition(id: usize, text: &str) -> String { + let units: Vec = text.encode_utf16().map(|u| u.to_string()).collect(); + let (length, units) = (units.len(), units.join(", ")); + format!("static struct jsrt_string str{id} = {{{length}, {{{units}}}}};\n") } diff --git a/compiler/src/emit/objects.rs b/compiler/src/emit/objects.rs index db28319..f905301 100644 --- a/compiler/src/emit/objects.rs +++ b/compiler/src/emit/objects.rs @@ -1,397 +1,290 @@ -//! The C of objects: fields, elements, and the read fns that follow a prototype chain +//! The C of the instrs on objects: fields and elements //! -//! A field is a member of its class's struct. A field that every object has from the start -//! is read directly. Any other field can be absent: it has a `has_` flag, and a read of it -//! is a read fn, which reads its own field if it has one, else reads on its prototype. A -//! field that shares a location (see `define_field_ref`) holds a pointer to it - -use std::fmt::Write as _; +//! A field is a member of its class's struct. If every object of the class has the field from +//! the start, the field is used directly. Else the struct has a `has_` flag for the field, and +//! getting the field calls its lookup (see `functions`). A field that shares a location (see +//! `define_field_ref`) holds a pointer to it. use super::{ - Emitter, - func::{Code, FnEmitter, Writer}, - layout::{ClassKind, Member, Rep}, - undefined, + js::JsFunction, + types::{CType, ClassKind, Member}, + values::{as_index, in_range, undefined}, }; use crate::{ - infer::{LENGTH_FIELD, Type, join}, - libqjs::parse::{AtomRef, opcodes::OpCode}, + infer::LENGTH_FIELD, + libqjs::parse::opcodes::OpCode, regify::{Operand, RegInstr}, - transform::PROTO_FIELD, }; // ──────────────────────────────────────── Fields ──────────────────────────────────────── -impl FnEmitter<'_, '_> { - /// `[a, b, ...]`: the elements are the operands, last first +impl JsFunction<'_> { + /// `[a, b, ...]`. The operands are the elements, last first pub(super) fn array_from(&mut self, idx: usize, instr: &RegInstr) { let class = self.class_here(idx); - let elems = self.e.layout.classes[class].elems; - let array = self.temp(Rep::One(Member::Obj(class))); + let elems = self.program.types.classes[class].elems; + let array = self.body.temp(CType::One(Member::Obj(class))); let n = instr.args.len(); - self.line(format!("{array} = __rt_alloc(sizeof(struct c{class}));")); - self.line(format!( + self.body + .line(format!("{array} = __rt_alloc(sizeof(struct c{class}));")); + self.body.line(format!( "{array}->items = __rt_grow(NULL, &{array}->cap, {n}, sizeof *{array}->items);" )); - self.line(format!("{array}->length = {n};")); + self.body.line(format!("{array}->length = {n};")); for (i, &slot) in instr.args.iter().rev().enumerate() { - let (value, rep) = self.read(slot); - let value = self.e.convert(&value, rep, elems); - self.line(format!("{array}->items[{i}] = {value};")); + let (value, ctype) = self.get(slot); + self.body + .put(&format!("{array}->items[{i}] = "), &value, ctype, elems); } - self.set(instr.dsts[0], &array, Member::Obj(class)); + self.set_member(instr.dsts[0], &array, Member::Obj(class)); } - /// `o.f`, and `o.length`. A string's length is its own, and so is an array's. Undefined - /// and null throw. A primitive reads its kind's prototype (see `Emitter::builtin_proto`) + /// `o.f` and `o.length`. A string has its own length, and so does an array. A primitive + /// gets the field from the stdlib's prototype of its kind (see `Program::field_class`). + /// Undefined and null throw pub(super) fn get_field(&mut self, instr: &RegInstr) { - let name = match instr.op { + let field = match instr.op { OpCode::GetLength => LENGTH_FIELD, _ => instr.imm.atom(), }; let dst = instr.dsts[0]; - let ops = [self.read(instr.args[0])]; + let cases = self.program.cases(&[self.get(instr.args[0])]); - self.split(&ops, &mut |fe, ms| { - let (obj, member) = (&ms[0].0, ms[0].1); + for case in &cases { + self.body.open(case); + let (obj, member) = (&case.operands[0].0, case.operands[0].1); - match (member, fe.class_kind(member)) { - (Member::Str, _) if name == LENGTH_FIELD => { - fe.set(dst, &format!("__rt_string_length({obj})"), Member::Int) + match (member, self.program.field_class(member, field)) { + (Member::Obj(_), Some(class)) => { + let (value, ctype) = self.program.field_value(class, field, obj); + self.set(dst, &value, ctype); } - (Member::Obj(_), ClassKind::Array) if name == LENGTH_FIELD => { - fe.set(dst, &format!("{obj}->length"), Member::Int) + (_, Some(class)) => { + let (proto, _) = self.program.builtin_proto(member).unwrap(); + let (value, ctype) = self.program.field_value(class, field, &proto); + self.set(dst, &value, ctype); } - (Member::Obj(class), _) => { - let (value, rep) = read_field(fe.e, class, name, obj); - fe.assign(dst, &value, rep); + (Member::Str, None) if field == LENGTH_FIELD => { + let length = format!("__rt_string_length({obj})"); + self.set_member(dst, &length, Member::Int); + } + (Member::Obj(_), None) => { + self.set_member(dst, &format!("{obj}->length"), Member::Int) } (Member::Undefined | Member::Null, _) => { - fe.throw("cannot read a field of undefined or null") + self.body.throw("cannot read a field of undefined or null") } - (Member::Native(_), _) => fe.throw("a native fn has no fields"), - _ => match fe.e.builtin_proto(member) { - Some((proto, Rep::One(Member::Obj(proto_class)))) => { - let (value, rep) = read_field(fe.e, proto_class, name, &proto); - fe.assign(dst, &value, rep); - } - _ => fe.throw("fields of primitives need the stdlib"), - }, + (Member::Native(_), _) => self.body.throw("a native function has no fields"), + _ => self.body.throw("fields of primitives need the stdlib"), } - }); + } + + self.body.close(&cases); } - /// `o.f = v`, and a field made in an object literal. A primitive has no fields to write, - /// and undefined and null throw. A write of an array's `length` sets its length + /// `o.f = v`, and a field in an object literal. Setting an array's `length` sets its + /// length. A primitive has no fields to set, and undefined and null throw pub(super) fn put_field(&mut self, instr: &RegInstr) { - let name = instr.imm.atom(); - let value = self.read(instr.args[0]); - let ops = [self.read(instr.args[1])]; + let field = instr.imm.atom(); + let (value, from) = self.get(instr.args[0]); + let cases = self.program.cases(&[self.get(instr.args[1])]); - self.split(&ops, &mut |fe, ms| { - let (obj, member) = (&ms[0].0, ms[0].1); + for case in &cases { + self.body.open(case); + let (obj, member) = (&case.operands[0].0, case.operands[0].1); - match (member, fe.class_kind(member)) { - (Member::Obj(_), ClassKind::Array) if name == LENGTH_FIELD => { - fe.set_length(obj, &value); + match (member, self.program.class_kind(member)) { + (Member::Obj(_), ClassKind::Array) if field == LENGTH_FIELD => { + self.set_length(obj, &value, from) } (Member::Obj(class), _) => { - let field = fe.e.layout.field(class, name).unwrap().clone(); - let target = fe.e.field_access(obj, &field); - let v = fe.e.convert(&value.0, value.1, field.rep); - fe.line(format!("{target} = {v};")); + let f = self.program.types.field(class, field).unwrap(); + let target = self.program.field_access(obj, f); + self.body + .put(&format!("{target} = "), &value, from, f.ctype); - if !field.defined { - fe.line(format!("{obj}->has_{} = true;", fe.e.field_name(name))); + if !f.defined { + let name = self.program.field_name(field); + self.body.line(format!("{obj}->has_{name} = true;")); } } - _ => fe.throw("cannot set a field of a primitive, undefined or null"), + _ => self + .body + .throw("cannot set a field of a primitive, undefined or null"), } - }); + } + + self.body.close(&cases); } - /// `a.length = n`: the array gets `n` elements, the new ones holes. `n` must be an array - /// index: a whole number from 0, else it throws, as JS's RangeError - fn set_length(&mut self, obj: &str, value: &(String, Rep)) { - self.split(std::slice::from_ref(value), &mut |fe, ms| { - let (n, member) = (&ms[0].0, ms[0].1); + /// `a.length = n`: the array gets `n` elements, and the new ones are holes. `n` must be a + /// whole number from 0, else it throws, as JS's RangeError does + fn set_length(&mut self, obj: &str, value: &str, from: CType) { + let cases = self.program.cases(&[(value.to_string(), from)]); + + for case in &cases { + self.body.open(case); + let (n, member) = (&case.operands[0].0, case.operands[0].1); let length = match member { Member::Int => n.clone(), Member::Float => format!("(int32_t){n}"), - _ => return fe.throw("an array's length must be a number"), + _ => { + self.body.throw("an array's length must be a number"); + continue; + } }; let check = in_range(n, member, "INT32_MAX"); - fe.line(format!("if (!({check})) __rt_type_error(\"an array's length must be a whole number from 0\");")); - fe.line(format!("{obj}->items = __rt_grow({obj}->items, &{obj}->cap, {length}, sizeof *{obj}->items);")); - fe.line(format!("{obj}->length = {length};")); - }); + self.body.line(format!("if (!({check})) __rt_type_error(\"an array's length must be a whole number from 0\");")); + self.body.line(format!("{obj}->items = __rt_grow({obj}->items, &{obj}->cap, {length}, sizeof *{obj}->items);")); + self.body.line(format!("{obj}->length = {length};")); + } + + self.body.close(&cases); } - /// `define_field_ref`: the field becomes the location that regify gives as the first - /// operand. That is a captured binding, whose C local points to its cell, or the reg of - /// an object whose field of the same name points to one (see regify/walk.rs) + /// `define_field_ref`: the field becomes a pointer to the cell of the location in the + /// first operand. That location is a shared local or arg, whose C local points to its + /// cell, or a reg that holds an object whose field of the same name points to the cell + /// (see regify/walk.rs) pub(super) fn share_field(&mut self, instr: &RegInstr) { - let name = instr.imm.atom(); - let c_name = self.e.field_name(name); + let name = self.program.field_name(instr.imm.atom()); let location = instr.args[0]; - let ops = [self.read(instr.args[1])]; - self.split(&ops, &mut |fe, ms| { - let obj = &ms[0].0; + let cell = match location { + Operand::Reg(_) => match self.get(location) { + (holder, CType::One(Member::Obj(_))) => format!("{holder}->{name}"), + _ => unreachable!("a shared location is held by an object"), + }, + slot => self.name(slot), + }; - match location { - Operand::Reg(_) => { - let holder = fe.read(location); + let cases = self.program.cases(&[self.get(instr.args[1])]); - fe.split(&[holder], &mut |fe, ms| match ms[0].1 { - Member::Obj(_) => { - fe.line(format!("{obj}->{c_name} = {}->{c_name};", ms[0].0)) - } - _ => unreachable!(), - }); - } - slot => { - let cell = fe.name(slot); - fe.line(format!("{obj}->{c_name} = {cell};")); - } - } - }); + for case in &cases { + self.body.open(case); + self.body + .line(format!("{}->{name} = {cell};", case.operands[0].0)); + } + + self.body.close(&cases); } - /// `close_field x`: the field, which shares a location, gets a new cell with its value. - /// Closures that shared the old cell keep it (see transform/closure.rs) + /// `close_field x`: the field, which points to a cell, gets a new cell with the same + /// value. Closures that point to the old cell keep it (see transform/closure.rs) pub(super) fn close_field(&mut self, instr: &RegInstr) { - let name = instr.imm.atom(); - let ops = [self.read(instr.args[0])]; + let field = instr.imm.atom(); + let cases = self.program.cases(&[self.get(instr.args[0])]); - self.split(&ops, &mut |fe, ms| { - let (obj, member) = (&ms[0].0, ms[0].1); - let Member::Obj(class) = member else { unreachable!("a capture is held by an object") }; - let field = fe.e.layout.field(class, name).unwrap().clone(); - assert!(field.shared, "close_field of a field that shares no location"); + for case in &cases { + self.body.open(case); + let (obj, member) = (&case.operands[0].0, case.operands[0].1); + let Member::Obj(class) = member else { + unreachable!("a shared location is held by an object") + }; - let (ctype, c_name) = (fe.e.ctype(field.rep), fe.e.field_name(name)); - fe.line(format!("{{ {ctype} *cell = __rt_alloc(sizeof({ctype})); *cell = *{obj}->{c_name}; {obj}->{c_name} = cell; }}")); - }); + let f = self.program.types.field(class, field).unwrap(); + assert!(f.shared, "close_field of a field that is not a cell"); + + let (ctype, name) = (f.ctype.c_name(), self.program.field_name(field)); + self.body.line(format!("{{ {ctype} *cell = __rt_alloc(sizeof({ctype})); *cell = *{obj}->{name}; {obj}->{name} = cell; }}")); + } + + self.body.close(&cases); } } // ─────────────────────────────────────── Elements ─────────────────────────────────────── -impl FnEmitter<'_, '_> { - /// `a[i]` on an array, and `s[i]` on a string. An index that is not in it gives - /// undefined. Undefined and null throw +impl JsFunction<'_> { + /// `a[i]` on an array, and `s[i]` on a string. An index out of range gives undefined. + /// Undefined and null throw pub(super) fn get_elem(&mut self, instr: &RegInstr) { let dst = instr.dsts[0]; - let ops = [self.read(instr.args[1]), self.read(instr.args[0])]; + let operands = [self.get(instr.args[1]), self.get(instr.args[0])]; + let cases = self.program.cases(&operands); - self.split(&ops, &mut |fe, ms| { - let ((obj, om), (index, im)) = (&ms[0], &ms[1]); + for case in &cases { + self.body.open(case); + let ((obj, obj_member), (index, index_member)) = (&case.operands[0], &case.operands[1]); + let at = as_index(index, *index_member); - let (length, item, rep) = match (*om, fe.class_kind(*om)) { - (Member::Obj(class), ClassKind::Array) => { - let elems = fe.e.layout.classes[class].elems; - ( - format!("{obj}->length"), - format!("{obj}->items[{}]", as_index(index, *im)), - elems, - ) - } + let (length, item, ctype) = match (*obj_member, self.program.class_kind(*obj_member)) { + (Member::Obj(class), ClassKind::Array) => ( + format!("{obj}->length"), + format!("{obj}->items[{at}]"), + self.program.types.classes[class].elems, + ), (Member::Str, _) => ( format!("__rt_string_length({obj})"), - format!("__rt_string_at({obj}, {})", as_index(index, *im)), - Rep::One(Member::Str), + format!("__rt_string_at({obj}, {at})"), + CType::One(Member::Str), ), (Member::Undefined | Member::Null, _) => { - fe.throw("cannot read an element of undefined or null"); - return; + self.body + .throw("cannot read an element of undefined or null"); + continue; } - _ => unreachable!("infer reads elements only of arrays and strings"), + _ => unreachable!("infer gets elements only of arrays and strings"), }; - fe.line(format!("if ({}) {{", in_range(index, *im, &length))); - fe.indent(1); - fe.assign(dst, &item, rep); - fe.indent(-1); - fe.line("} else {"); - fe.indent(1); - let (value, rep) = undefined(); - fe.assign(dst, &value, rep); - fe.indent(-1); - fe.line("}"); - }); + let (none, none_ctype) = undefined(); + let check = in_range(index, *index_member, &length); + self.body.line(format!("if ({check}) {{")); + self.body.indent(1); + self.set(dst, &item, ctype); + self.body.indent(-1); + self.body.line("} else {"); + self.body.indent(1); + self.set(dst, &none, none_ctype); + self.body.indent(-1); + self.body.line("}"); + } + + self.body.close(&cases); } - /// `a[i] = v` on an array, which grows to hold `i`. A write to a string, or to undefined - /// or null, throws, and so does an index that is not an array index, which would be a - /// named field + /// `a[i] = v` on an array, which grows to hold `i`. Setting an element of a string, of + /// undefined or of null throws. So does an index that is not a whole number from 0, + /// because it would be a named field pub(super) fn put_elem(&mut self, instr: &RegInstr) { - let value = self.read(instr.args[0]); - let ops = [self.read(instr.args[2]), self.read(instr.args[1])]; + let (value, from) = self.get(instr.args[0]); + let operands = [self.get(instr.args[2]), self.get(instr.args[1])]; + let cases = self.program.cases(&operands); - self.split(&ops, &mut |fe, ms| { - let ((obj, om), (index, im)) = (&ms[0], &ms[1]); + for case in &cases { + self.body.open(case); + let ((obj, obj_member), (index, index_member)) = (&case.operands[0], &case.operands[1]); - let (Member::Obj(class), ClassKind::Array) = (*om, fe.class_kind(*om)) else { - fe.throw("cannot set an element of a value that is not an array"); - return; + let (Member::Obj(class), ClassKind::Array) = + (*obj_member, self.program.class_kind(*obj_member)) + else { + self.body + .throw("cannot set an element of a value that is not an array"); + continue; }; - let elems = fe.e.layout.classes[class].elems; - let i = as_index(index, *im); - let v = fe.e.convert(&value.0, value.1, elems); + let elems = self.program.types.classes[class].elems; + let at = as_index(index, *index_member); + let check = in_range(index, *index_member, "INT32_MAX"); - fe.line(format!("if (!({})) __rt_type_error(\"an array index must be a whole number from 0\");", in_range(index, *im, "INT32_MAX"))); - fe.line(format!("if ({i} >= {obj}->length) {{")); - fe.indent(1); - fe.line(format!("{obj}->items = __rt_grow({obj}->items, &{obj}->cap, {i} + 1, sizeof *{obj}->items);")); - fe.line(format!("{obj}->length = {i} + 1;")); - fe.indent(-1); - fe.line("}"); - fe.line(format!("{obj}->items[{i}] = {v};")); - }); - } -} - -/// An index of member `member` as an `int32_t` -fn as_index(index: &str, member: Member) -> String { - match member { - Member::Int => index.into(), - Member::Float => format!("(int32_t){index}"), - _ => unreachable!("an index is a number (see infer)"), - } -} - -/// Whether an index is an array index below `length`. A float must be a whole number -fn in_range(index: &str, member: Member, length: &str) -> String { - match member { - Member::Int => format!("{index} >= 0 && {index} < {length}"), - _ => format!("{index} >= 0 && {index} < {length} && {index} == (double)(int32_t){index}"), - } -} - -// ─────────────────────────────────────── Read fns ─────────────────────────────────────── - -/// The read of field `name` on `obj`, an object of `class`: the field itself if every object -/// has it, else a call of the read fn. Gives the value and its C type -fn read_field(e: &mut Emitter, class: usize, name: AtomRef, obj: &str) -> (String, Rep) { - if let Some(field) = e.layout.field(class, name).filter(|f| f.defined) { - return (e.field_access(obj, field), field.rep); - } - - let (id, rep) = read_fn(e, class, name); - (format!("rd{id}({obj})"), rep) -} - -/// The read fn of field `name` on `class`, wanted the first time. Gives its number and what -/// it gives -fn read_fn(e: &mut Emitter, class: usize, name: AtomRef) -> (usize, Rep) { - if let Some(&found) = e.reads.get(&(class, name)) { - return found; - } - - // What it gives is the join of the reads on the class's objects (see `Memory::read_field`) - let objs = e.layout.classes[class].objs.clone(); - let ty = objs.iter().fold(Type::Unknown, |all, &obj| { - join(&all, &e.cache.memory.read_field(&Type::Ref(obj), name)) - }); - let rep = e.layout.rep(&ty); - - let id = e.reads.len(); - e.reads.insert((class, name), (id, rep)); - e.read_queue.push_back((class, name)); - (id, rep) -} - -/// Prints read fns -struct ReadEmitter<'e, 'a> { - e: &'e mut Emitter<'a>, - code: Code, -} - -impl<'a> Writer<'a> for ReadEmitter<'_, 'a> { - fn code(&mut self) -> &mut Code { - &mut self.code - } - - fn emitter(&mut self) -> &mut Emitter<'a> { - self.e - } -} - -impl ReadEmitter<'_, '_> { - /// Return a value, as the read's type - fn ret(&mut self, (value, from): (String, Rep), rep: Rep) { - let value = self.e.convert(&value, from, rep); - self.line(format!("return {value};")); - } -} - -/// Print the read fn of field `name` on `class`: its own field if it has it, else the read on -/// its prototype. At the end of the chain, the field is undefined. An array's prototype is -/// the stdlib's (see `Emitter::builtin_proto`) -pub fn emit_read(e: &mut Emitter, class: usize, name: AtomRef) { - let (id, rep) = e.reads[&(class, name)]; - let signature = format!("static {} rd{id}(struct c{class} *o)", e.ctype(rep)); - let _ = writeln!(e.protos, "{signature};"); - - let code = Code::default(); - let mut re = ReadEmitter { e, code }; - re.indent(1); - - if let Some(field) = re.e.layout.field(class, name).cloned() { - let own = re.e.field_access("o", &field); - let value = re.e.convert(&own, field.rep, rep); - - match field.defined { - true => re.line(format!("return {value};")), - false => re.line(format!( - "if (o->has_{}) return {value};", - re.e.field_name(name) - )), + self.body.line(format!( + "if (!({check})) __rt_type_error(\"an array index must be a whole number from 0\");" + )); + self.body.line(format!("if ({at} >= {obj}->length) {{")); + self.body.indent(1); + self.body.line(format!("{obj}->items = __rt_grow({obj}->items, &{obj}->cap, {at} + 1, sizeof *{obj}->items);")); + self.body.line(format!("{obj}->length = {at} + 1;")); + self.body.indent(-1); + self.body.line("}"); + self.body + .put(&format!("{obj}->items[{at}] = "), &value, from, elems); } + + self.body.close(&cases); } - - let proto = re.e.layout.field(class, PROTO_FIELD).cloned(); - - match (re.e.layout.classes[class].kind, proto) { - // An array's prototype is the stdlib's (see `Emitter::builtin_proto`) - (ClassKind::Array, _) => match re.e.builtin_proto(Member::Obj(class)) { - Some((proto, Rep::One(Member::Obj(proto_class)))) => { - let read = read_field(re.e, proto_class, name, &proto); - re.ret(read, rep); - } - _ => re.ret(undefined(), rep), - }, - (_, None) => re.ret(undefined(), rep), - (_, Some(proto)) => { - if !proto.defined { - re.line(format!("if (!o->has_{}) {{", re.e.field_name(PROTO_FIELD))); - re.indent(1); - re.ret(undefined(), rep); - re.indent(-1); - re.line("}"); - } - - let value = re.e.field_access("o", &proto); - - re.split(&[(value, proto.rep)], &mut |re, ms| match ms[0].1 { - Member::Obj(proto_class) => { - let read = read_field(re.e, proto_class, name, &ms[0].0); - re.ret(read, rep); - } - _ => re.ret(undefined(), rep), - }); - - re.line("abort();"); - } - } - - let body = std::mem::take(&mut re.code.out); - let _ = write!(re.e.read_defs, "{signature} {{\n{body}}}\n\n"); } diff --git a/compiler/src/emit/program.rs b/compiler/src/emit/program.rs new file mode 100644 index 0000000..66d9819 --- /dev/null +++ b/compiler/src/emit/program.rs @@ -0,0 +1,439 @@ +//! The program, as C: its types, its statics and its functions +//! +//! `Program::new` finds all of them before any C is written. Writing a function only reads +//! the program, so the functions can be written in any order. + +use std::collections::{BTreeMap, BTreeSet}; + +use super::functions::Function; +use super::types::{CType, ClassField, ClassKind, Member, Types}; +use crate::{ + flatten::ModuleBindings, + infer::{LENGTH_FIELD, Type, TypeCache, join, module_binding}, + libqjs::parse::{Atom, AtomRef, JsValue, opcodes::OpCode}, + regify::{Operand, RegFn, RegInstr}, + transform::{PROTO_FIELD, PROTOTYPE_FIELD}, +}; + +/// Strings that the C code can need for any program: the names that `typeof` gives, and the +/// results of converting undefined, null and the bools to strings +const FIXED_STRINGS: &[&str] = &[ + "undefined", + "null", + "true", + "false", + "boolean", + "number", + "string", + "function", + "object", +]; + +pub struct Program<'a> { + // The input: the JS functions, and what infer found + pub fns: &'a [RegFn], + pub cache: &'a TypeCache, + pub atoms: &'a [Atom], + bindings: &'a ModuleBindings, + pub entry: usize, // the walk of the entry: the JS function that runs the program + + // The output + pub types: Types, + pub strings: Vec, // statics: the text of each string literal + pub globals: BTreeMap, // statics: the module bindings, which are locals of the entry, and their C types + pub functions: Vec, +} + +impl<'a> Program<'a> { + pub fn new( + fns: &'a [RegFn], + cache: &'a TypeCache, + atoms: &'a [Atom], + bindings: &'a ModuleBindings, + ) -> Self { + let mut program = Program { + fns, + cache, + atoms, + bindings, + entry: cache.find(fns.len() - 1, None, &[], fns), + types: Types::new(&cache.memory), + strings: vec![], + globals: BTreeMap::new(), + functions: vec![], + }; + + program.add_globals(); + program.add_functions(); + program.add_strings(); + program.add_unions(); + program + } + + /// Each module binding is a C global, so that any function can use the stdlib's + fn add_globals(&mut self) { + let entry = self.fns.len() - 1; + + for &local in self.bindings.values() { + let ty = self.cache.memory.get(module_binding(entry, local)); + let ctype = self.types.add(ty); + self.globals.insert(local, ctype); + } + } + + /// One C function for each walk of a JS function in infer's last round, one for each + /// lookup, and the host's two entry points + fn add_functions(&mut self) { + let walks = (0..self.cache.walks.len()).filter(|&walk| self.cache.is_live(walk)); + let mut lookups = BTreeSet::new(); + + for walk in walks.clone() { + self.add_lookups(walk, &mut lookups); + } + + let lookups = lookups + .into_iter() + .map(|(class, field)| Function::Lookup(class, field)); + self.functions.extend(walks.map(Function::Js)); + self.functions.extend(lookups); + self.functions.extend([Function::Start, Function::Complete]); + } + + /// The lookups that a JS function's walk needs: one for each field that it gets, on each + /// class that can lack the field + fn add_lookups(&self, walk: usize, lookups: &mut BTreeSet<(usize, AtomRef)>) { + for instr in self.instrs(walk) { + let field = match instr.op { + OpCode::GetField => instr.imm.atom(), + OpCode::GetLength => LENGTH_FIELD, + _ => continue, + }; + + let ty = self.slot_type(walk, instr.args[0]); + + for member in self.types.type_members(&ty) { + if let Some(class) = self.field_class(member, field) { + self.add_lookup(class, field, lookups); + } + } + } + } + + /// The lookup of `field` on `class`, if its objects can lack the field. If they can, the + /// lookup goes on to their prototypes, so those need lookups too + fn add_lookup(&self, class: usize, field: AtomRef, lookups: &mut BTreeSet<(usize, AtomRef)>) { + let defined = self.types.field(class, field).is_some_and(|f| f.defined); + + if defined || !lookups.insert((class, field)) { + return; + } + + for proto_class in self.proto_classes(class) { + self.add_lookup(proto_class, field, lookups); + } + } + + /// The text of every string literal: the fixed ones, and the ones in the JS functions + fn add_strings(&mut self) { + let mut strings: Vec = FIXED_STRINGS.iter().map(|s| s.to_string()).collect(); + + for &function in &self.functions { + let Function::Js(walk) = function else { + continue; + }; + + let cpool = &self.fns[self.cache.walks[walk].req.fn_idx].cpool; + + for instr in self.instrs(walk) { + let text = match instr.op { + OpCode::PushAtomValue => instr.imm.atom().text(self.atoms), + OpCode::PushEmptyString => String::new(), + OpCode::PushConst | OpCode::PushConst8 => match &cpool[instr.imm.const_idx()] { + JsValue::String(s) => s.text(), + _ => continue, + }, + _ => continue, + }; + + strings.push(text); + } + } + + for text in strings { + if !self.strings.contains(&text) { + self.strings.push(text); + } + } + } + + /// The union of every C type that the functions use, if it has several members. The + /// classes' fields have theirs already (see `Types::new`) + fn add_unions(&mut self) { + let types: Vec = (self.functions.iter()) + .flat_map(|function| self.value_types(function)) + .collect(); + + for ty in &types { + self.types.add(ty); + } + } + + /// The types of the values in a function: the locals, args and regs of a JS function, + /// and what it returns, or what a lookup gives + fn value_types(&self, function: &Function) -> Vec { + match *function { + Function::Js(walk) => { + let f = &self.fns[self.cache.walks[walk].req.fn_idx]; + let slots = slots(f).into_iter().map(|slot| self.slot_type(walk, slot)); + slots.chain([self.cache.walks[walk].ret.clone()]).collect() + } + Function::Lookup(class, field) => vec![self.lookup_type(class, field)], + Function::Start | Function::Complete => vec![], + } + } + + /// The instrs of a JS function that can run in a walk + fn instrs(&self, walk: usize) -> impl Iterator + use<'a> { + let (cache, fns) = (self.cache, self.fns); + let walk = &cache.walks[walk]; + let f = &fns[walk.req.fn_idx]; + let blocks = f.blocks.iter().zip(&walk.reachable); + let blocks = blocks.filter(|(_, reachable)| **reachable); + blocks.flat_map(|(block, _)| &f.code[block.start..block.end]) + } +} + +// ────────────────────────────────────── C values ─────────────────────────────────────── + +/// One combination of members of some operands. When an operand is a union, the C code tests +/// its tag, and then uses the member that the tag says +#[derive(Clone)] +pub struct Case { + pub operands: Vec<(String, Member)>, // each operand, as C, as a value of one member + pub tests: Vec, // the C tests that the operands are these members: one for each union + pub first: bool, // the first case of its operands: an `if`. The others are `else if` +} + +impl Program<'_> { + /// The type of a local, arg or reg of a JS function, in one walk + pub fn slot_type(&self, walk: usize, slot: Operand) -> Type { + let addr = self.cache.addr(walk, slot, self.fns); + self.cache.memory.get(addr).clone() + } + + /// The C type of a local, arg or reg of a JS function, in one walk + pub fn slot_ctype(&self, walk: usize, slot: Operand) -> CType { + self.types.of(&self.slot_type(walk, slot)) + } + + pub fn class_kind(&self, member: Member) -> ClassKind { + match member { + Member::Obj(class) => self.types.classes[class].kind, + _ => ClassKind::Obj, + } + } + + /// Whether a value of `member` is a function + pub fn is_function(&self, member: Member) -> bool { + let closure = matches!(self.class_kind(member), ClassKind::Closure(_)); + closure || matches!(member, Member::Native(_)) + } + + /// Every combination of the members of `operands`. Each operand is C, and its C type + pub fn cases(&self, operands: &[(String, CType)]) -> Vec { + let mut cases = vec![Case { + operands: vec![], + tests: vec![], + first: true, + }]; + + for (value, ctype) in operands { + let mut next = vec![]; + + for case in &cases { + for (tag, member) in self.types.members_of(*ctype).into_iter().enumerate() { + let mut case = case.clone(); + + match ctype { + CType::One(_) => case.operands.push((value.clone(), member)), + CType::Union(_) => { + case.operands.push((format!("{value}.as.m{tag}"), member)); + case.tests.push(format!("{value}.tag == {tag}")); + } + } + + case.first = next.is_empty(); + next.push(case); + } + } + + cases = next; + } + + cases + } + + /// `value`, of `member`, as a value of C type `to`. Types only grow, so `to` holds + /// `member`, or holds a float if `member` is an int + pub fn convert(&self, value: &str, member: Member, to: CType) -> String { + let members = self.types.members_of(to); + let same = members.iter().position(|&m| m == member); + let float = members.iter().position(|&m| m == Member::Float); + let float = float.filter(|_| member == Member::Int); + + let Some(tag) = same.or(float) else { + panic!("emit: a value of {member:?} where {members:?} was inferred"); + }; + + let value = match members[tag] == member { + true => value.to_string(), + false => format!("(double){value}"), + }; + + match to { + CType::One(_) => value, + CType::Union(id) => format!("(u{id}){{.tag = {tag}, .as.m{tag} = {value}}}"), + } + } + + /// A string literal, as C: a pointer to its static + pub fn string(&self, text: &str) -> String { + match self.strings.iter().position(|s| s == text) { + Some(id) => format!("(&str{id})"), + None => panic!("emit: no static for the string {text:?}"), + } + } + + /// `value`, of `member`, as a string, as JS's ToString does. `None` for an object: its + /// conversion calls its methods, which is not supported + pub fn to_string(&self, value: &str, member: Member) -> Option { + Some(match member { + Member::Undefined => self.string("undefined"), + Member::Null => self.string("null"), + Member::Bool => { + let (t, f) = (self.string("true"), self.string("false")); + format!("({value} ? {t} : {f})") + } + Member::Int => format!("__rt_number_to_string((double){value})"), + Member::Float => format!("__rt_number_to_string({value})"), + Member::Str => value.into(), + Member::Obj(_) | Member::Native(_) => return None, + }) + } + + /// The value of the stdlib's module binding `name`, as C, and its C type + pub fn binding(&self, name: &str) -> Option<(String, CType)> { + let local = *self.bindings.get(name)?; + Some((format!("(*g{local})"), self.globals[&local])) + } +} + +// ─────────────────────────────────────── Fields ──────────────────────────────────────── + +impl Program<'_> { + /// The C name of a field + pub fn field_name(&self, field: AtomRef) -> String { + let text: String = (field.text(self.atoms).chars().take(24)) + .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' }) + .collect(); + + match field { + AtomRef::TaggedInt(n) => format!("f_{text}_i{n}"), + AtomRef::Predefined(n) => format!("f_{text}_p{n}"), + AtomRef::FileLocal(n) => format!("f_{text}_a{n}"), + } + } + + /// A field of `obj`, as C to get or set it. A field that shares a location holds a + /// pointer to it + pub fn field_access(&self, obj: &str, field: &ClassField) -> String { + let name = self.field_name(field.name); + + match field.shared { + true => format!("(*{obj}->{name})"), + false => format!("{obj}->{name}"), + } + } + + /// The value of `field` on `obj`, an object of `class`, and its C type. If every object + /// of the class has the field, that is the field itself. Else it is a call of the field's + /// lookup + pub fn field_value(&self, class: usize, field: AtomRef, obj: &str) -> (String, CType) { + match self.types.field(class, field) { + Some(f) if f.defined => (self.field_access(obj, f), f.ctype), + _ => { + let lookup = Function::Lookup(class, field).name(self); + let ctype = self.types.of(&self.lookup_type(class, field)); + (format!("{lookup}({obj})"), ctype) + } + } + } + + /// The class of the object that has `field`, for a value of `member`. That is the value + /// itself if it is an object, or the stdlib's prototype of its kind if it is a primitive + /// (see `builtin_proto`). A string's length and an array's are not fields, so they have + /// none + pub fn field_class(&self, member: Member, field: AtomRef) -> Option { + let length = field == LENGTH_FIELD; + + match (member, self.class_kind(member)) { + (Member::Str, _) | (Member::Obj(_), ClassKind::Array) if length => None, + (Member::Obj(class), _) => Some(class), + (Member::Undefined | Member::Null | Member::Native(_), _) => None, + _ => self.builtin_proto(member).map(|(_, class)| class), + } + } + + /// The type that the lookup of `field` on `class` gives: the join of the field's types on + /// the class's objects, and on their prototypes (see `Memory::read_field`) + pub fn lookup_type(&self, class: usize, field: AtomRef) -> Type { + let objs = &self.types.classes[class].objs; + let memory = &self.cache.memory; + let types = objs + .iter() + .map(|&obj| memory.read_field(&Type::Ref(obj), field)); + types.fold(Type::Unknown, |all, ty| join(&all, &ty)) + } + + /// The prototype of a primitive or an array, as C, and its class: the `prototype` field of + /// the stdlib's String, Number, Boolean or Array (see `Memory::builtin_proto`) + pub fn builtin_proto(&self, member: Member) -> Option<(String, usize)> { + let name = match (member, self.class_kind(member)) { + (Member::Str, _) => "String", + (Member::Int | Member::Float, _) => "Number", + (Member::Bool, _) => "Boolean", + (_, ClassKind::Array) => "Array", + _ => return None, + }; + + let (stdlib_class, ctype) = self.binding(name)?; + let proto = self.types.field(ctype.class()?, PROTOTYPE_FIELD)?; + let value = self.field_access(&stdlib_class, proto); + Some((value, proto.ctype.class()?)) + } + + /// The classes that the prototype of an object of `class` can be. An array's prototype + /// is the stdlib's. Another object's is its `__proto__` field, if it has one + pub fn proto_classes(&self, class: usize) -> Vec { + if self.types.classes[class].kind == ClassKind::Array { + let proto = self.builtin_proto(Member::Obj(class)); + return proto.map(|(_, class)| class).into_iter().collect(); + } + + let Some(proto) = self.types.field(class, PROTO_FIELD) else { + return vec![]; + }; + + let members = self.types.members_of(proto.ctype); + let classes = members.into_iter().map(|member| CType::One(member).class()); + classes.flatten().collect() + } +} + +/// Every local, arg and reg of a JS function +pub fn slots(f: &RegFn) -> Vec { + let locals = (0..f.local_count).map(Operand::Local); + let args = (0..f.arg_count).map(Operand::Arg); + let regs = (0..f.reg_count).map(Operand::Reg); + locals.chain(args).chain(regs).collect() +} diff --git a/compiler/src/emit/layout.rs b/compiler/src/emit/types.rs similarity index 55% rename from compiler/src/emit/layout.rs rename to compiler/src/emit/types.rs index ca251ac..3d41d79 100644 --- a/compiler/src/emit/layout.rs +++ b/compiler/src/emit/types.rs @@ -1,21 +1,25 @@ -//! Layouts: what each type is in C +//! The C types of the program //! -//! Each member of a type becomes one C type (a `Member`). A type with one member is that C -//! type, and a type with several is a tagged union of them (a sum). Objects are grouped into -//! layout classes, one C struct each: two objects share a class if they have the same -//! fields, and each field has the same C type. A field can point to an object of another -//! class, so the classes are found by refinement: start from the field names, then split -//! every class whose objects disagree, until nothing splits +//! Infer gives each value a type, which can have several members: for example, a value that +//! is a number or undefined. In C, each member is one C type (see `Member`). A type with one +//! member is that C type. A type with several members is a tagged union: a tag that says +//! which member the value is, and a C union of the members. //! -//! A closure is a class of its own. A call picks its C function by the closure's address -//! (see `TypeCache::find`), so two closures must never look alike in C +//! Objects are put in classes, and each class is one C struct. Two objects are in the same +//! class if they have the same fields, and each field has the same C type. A field can point +//! to an object of another class, so the classes are found in rounds: first group objects by +//! their field names, then split each class whose objects disagree on the C type of a field, +//! until no class splits. +//! +//! A closure is always a class of its own. A call picks the C function to call by the +//! closure's address (see `TypeCache::find`), so two closures must never look alike in C. use std::collections::HashMap; use crate::infer::{Addr, Memory, ObjAddr, Shape, Type, join}; use crate::libqjs::parse::AtomRef; -/// One member of a type, in C +/// One member of a type, as a C type #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum Member { Undefined, @@ -25,74 +29,101 @@ pub enum Member { Float, Str, Obj(usize), // a pointer to an object of this class - Native(usize), // a native, by its index in `native::NATIVES`: known from the type, so no data + Native(usize), // a native function, by its index in `native::NATIVES`. The type says which, so it holds no data } -/// A type, in C: one member, or a sum of several +/// The C type of a value: one member, or a tagged union of several #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum Rep { +pub enum CType { One(Member), - Sum(usize), // an index into `Layout::sums` + Union(usize), // an index into `Types::unions` +} + +impl CType { + /// The C name of the type + pub fn c_name(self) -> String { + match self { + CType::One(Member::Undefined | Member::Native(_)) => "undefined_t".into(), + CType::One(Member::Null) => "null_t".into(), + CType::One(Member::Bool) => "bool".into(), + CType::One(Member::Int) => "int32_t".into(), + CType::One(Member::Float) => "double".into(), + CType::One(Member::Str) => "string_t".into(), + CType::One(Member::Obj(class)) => format!("struct c{class} *"), + CType::Union(id) => format!("u{id}"), + } + } + + /// The class, if the type is a pointer to an object of one class + pub fn class(self) -> Option { + match self { + CType::One(Member::Obj(class)) => Some(class), + _ => None, + } + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ClassKind { Obj, Array, - Closure(usize), // it runs this fn + Closure(usize), // a closure of this JS function } /// A field of the objects of a class #[derive(Debug, Clone)] pub struct ClassField { pub name: AtomRef, - pub rep: Rep, - pub shared: bool, // it shares another location: the struct holds a pointer to it - pub defined: bool, // every object has it, so it needs no `has_` flag + pub ctype: CType, + pub shared: bool, // the field shares another location, so the struct holds a pointer to it + pub defined: bool, // every object has the field, so the struct needs no `has_` flag for it } +/// A group of objects that are one C struct #[derive(Debug)] pub struct Class { pub kind: ClassKind, pub objs: Vec, pub fields: Vec, // sorted by name - pub elems: Rep, // for an array: its elements. Else unused + pub elems: CType, // the C type of an array's elements. Not used for other objects } +/// The classes and the unions of the program #[derive(Debug)] -pub struct Layout { +pub struct Types { pub classes: Vec, class_of: HashMap, - pub sums: Vec>, // the members of each sum, sorted. A member's tag is its index - sum_ids: HashMap, usize>, + pub unions: Vec>, // the members of each union, sorted. A member's tag is its index + union_ids: HashMap, usize>, } -impl Layout { +impl Types { + /// The classes of all objects. The unions of the classes' fields are added too, and + /// `Program::new` adds the rest pub fn new(memory: &Memory) -> Self { - let class_of = refine(memory); - let mut layout = Layout { + let mut types = Types { classes: vec![], - class_of, - sums: vec![], - sum_ids: HashMap::new(), + class_of: refine(memory), + unions: vec![], + union_ids: HashMap::new(), }; let mut objs: Vec = memory.objects().map(|(obj, _)| obj).collect(); objs.sort_unstable(); - let count = layout.class_of.values().max().map_or(0, |&n| n + 1); - let mut members: Vec> = vec![vec![]; count]; + let count = types.class_of.values().max().map_or(0, |&n| n + 1); + let mut objs_of_class: Vec> = vec![vec![]; count]; for obj in objs { - members[layout.class_of[&obj]].push(obj); + objs_of_class[types.class_of[&obj]].push(obj); } - for objs in members { - let class = layout.make_class(memory, objs); - layout.classes.push(class); + for objs in objs_of_class { + let class = types.make_class(memory, objs); + types.classes.push(class); } - layout + types } /// The class of the objects in `objs`, which refinement put together @@ -105,7 +136,8 @@ impl Layout { _ => ClassKind::Obj, }; - // The type of each field is the join over the class, so the C type holds all + // The type of a field is the join of its types in all the objects of the class, so + // that its C type can hold each of them let fields = match kind { ClassKind::Array => vec![], _ => (memory.fields(first).iter()) @@ -118,7 +150,7 @@ impl Layout { ClassField { name, - rep: self.rep(&ty), + ctype: self.add(&ty), shared: field.addr != Addr::Field(first, name), defined: field.defined, } @@ -128,12 +160,11 @@ impl Layout { let elems = match kind { ClassKind::Array => { - let ty = objs - .iter() - .fold(Type::Unknown, |all, &obj| join(&all, memory.elems(obj))); - self.rep(&ty) + let elems = objs.iter().map(|&obj| memory.elems(obj)); + let ty = elems.fold(Type::Unknown, |all, ty| join(&all, ty)); + self.add(&ty) } - _ => Rep::One(Member::Undefined), + _ => CType::One(Member::Undefined), }; Class { @@ -152,35 +183,49 @@ impl Layout { self.classes[class].fields.iter().find(|f| f.name == name) } - /// The C members of a value of type `ty`. A location that no value reached holds - /// undefined - pub fn members(&self, ty: &Type) -> Vec { + /// The members of a value of type `ty`. A location that no value reached holds undefined + pub fn type_members(&self, ty: &Type) -> Vec { members_with(ty, &self.class_of) } - /// What a value of type `ty` is in C - pub fn rep(&mut self, ty: &Type) -> Rep { - let members = self.members(ty); + /// The C type of a value of type `ty`. If it is a union that is not in the list yet, it + /// is added + pub fn add(&mut self, ty: &Type) -> CType { + let members = self.type_members(ty); if let [member] = members[..] { - return Rep::One(member); + return CType::One(member); } - let next = self.sums.len(); - let id = *self.sum_ids.entry(members.clone()).or_insert(next); + let next = self.unions.len(); + let id = *self.union_ids.entry(members.clone()).or_insert(next); if id == next { - self.sums.push(members); + self.unions.push(members); } - Rep::Sum(id) + CType::Union(id) } - /// The members of a value of C type `rep` - pub fn rep_members(&self, rep: Rep) -> Vec { - match rep { - Rep::One(member) => vec![member], - Rep::Sum(id) => self.sums[id].clone(), + /// The C type of a value of type `ty`. `Program::new` added every union first + pub fn of(&self, ty: &Type) -> CType { + let members = self.type_members(ty); + + if let [member] = members[..] { + return CType::One(member); + } + + match self.union_ids.get(&members) { + Some(&id) => CType::Union(id), + None => panic!("emit: no union was added for {members:?}"), + } + } + + /// The members of a value of C type `ctype` + pub fn members_of(&self, ctype: CType) -> Vec { + match ctype { + CType::One(member) => vec![member], + CType::Union(id) => self.unions[id].clone(), } } } diff --git a/compiler/src/emit/values.rs b/compiler/src/emit/values.rs new file mode 100644 index 0000000..0f815ca --- /dev/null +++ b/compiler/src/emit/values.rs @@ -0,0 +1,104 @@ +//! JS's rules on values, as C: the conversions and tests that depend only on a value's member + +use super::types::{CType, Member}; + +/// The literal of a member that has one value +pub fn unit_literal(member: Member) -> &'static str { + match member { + Member::Undefined | Member::Native(_) => "(undefined_t){0}", + Member::Null => "(null_t){0}", + _ => unreachable!(), + } +} + +/// The undefined value, and its C type +pub fn undefined() -> (String, CType) { + ( + unit_literal(Member::Undefined).into(), + CType::One(Member::Undefined), + ) +} + +/// The C literal of an int +pub fn int(n: i64) -> String { + match n == i32::MIN as i64 { + true => "INT32_MIN".into(), + false => n.to_string(), + } +} + +/// `expr`, a value of `member`, as a number (a `double`), as JS's ToNumber does. `None` for an +/// object: its conversion calls its methods, which is not supported +pub fn to_number(expr: &str, member: Member) -> Option { + Some(match member { + Member::Undefined => "NAN".into(), + Member::Null => "0.0".into(), + Member::Bool | Member::Int => format!("(double){expr}"), + Member::Float => expr.into(), + Member::Str => format!("__rt_string_to_number({expr})"), + Member::Obj(_) | Member::Native(_) => return None, + }) +} + +/// `expr`, a value of `member`, as an int32, as JS's ToInt32 does. `None` for an object +pub fn to_int32(expr: &str, member: Member) -> Option { + match member { + Member::Int => Some(expr.into()), + _ => Some(format!("__rt_to_int32({})", to_number(expr, member)?)), + } +} + +/// Whether `expr`, a value of `member`, is truthy +pub fn truthy(expr: &str, member: Member) -> String { + match member { + Member::Undefined | Member::Null => "false".into(), + Member::Bool => expr.into(), + Member::Int => format!("({expr} != 0)"), + Member::Float => format!("({expr} == {expr} && {expr} != 0)"), + Member::Str => format!("(__rt_string_length({expr}) != 0)"), + Member::Obj(_) | Member::Native(_) => "true".into(), + } +} + +/// A JS base type: the kinds of value that `===` tells apart +pub fn base(member: Member) -> u8 { + match member { + Member::Undefined => 0, + Member::Null => 1, + Member::Bool => 2, + Member::Int | Member::Float => 3, + Member::Str => 4, + Member::Obj(_) | Member::Native(_) => 5, + } +} + +/// `a === b`, for one member of each. Different kinds are never equal. Numbers compare as +/// numbers, strings by their text, and objects by identity +pub fn strict_eq(l: &str, lm: Member, r: &str, rm: Member) -> String { + match (lm, rm) { + _ if base(lm) != base(rm) => "false".into(), + (Member::Undefined | Member::Null, _) => "true".into(), + (Member::Str, _) => format!("__rt_string_eq({l}, {r})"), + (Member::Obj(_), Member::Obj(_)) => format!("((void *){l} == (void *){r})"), + (Member::Native(a), Member::Native(b)) => (a == b).to_string(), + (Member::Obj(_) | Member::Native(_), _) => "false".into(), + _ => format!("({l} == {r})"), + } +} + +/// An index of member `member` as an `int32_t` +pub fn as_index(index: &str, member: Member) -> String { + match member { + Member::Int => index.into(), + Member::Float => format!("(int32_t){index}"), + _ => unreachable!("an index is a number (see infer)"), + } +} + +/// Whether an index is an array index below `length`. A float must be a whole number +pub fn in_range(index: &str, member: Member, length: &str) -> String { + match member { + Member::Int => format!("{index} >= 0 && {index} < {length}"), + _ => format!("{index} >= 0 && {index} < {length} && {index} == (double)(int32_t){index}"), + } +}