//! Emits C: one fn for each walk that the program calls, one struct for each //! cell (see `docs/model.md`, section 5). Cells and walks that C cannot tell //! apart share one struct and one fn (see `classes()`) //! //! A value's C representation is a function of its type (`rep()`), one for //! each kind of type. An object is a pointer to a struct of its cell. Each //! object starts with a header that holds the number of its cell, and the //! fields that can be absent start as absent //! //! The runtime (`lib/rt/`) gives the primitive types, the string ops, the //! `any_t` ops and the allocator use std::{ cell::RefCell, collections::{BTreeSet, HashMap, HashSet}, fmt::Write, }; use crate::{ infer::{ HOST, HOST_COMPLETE, InferResult, Place, TypeCache, cells::{Cell, Code, LENGTH_FIELD, ObjKind, read_value}, types::Type, }, libqjs::parse::{Atom, AtomRef, ImmOperand, JsValue, opcodes::OpCode}, native::{self, NATIVES, ProtoOf, atom_name, js_string_text}, regify::{BasicBlock, RegFn, RegInstr}, transform::{FIRST_ARG, PROTO_FIELD, THIS_ARG}, }; mod calls; mod classes; mod conv; mod instrs; mod objects; mod reps; use classes::*; use reps::*; /// The number of a class of cells in C: the number in the header of their /// objects, and the name of their struct type CellNum = u32; pub fn emit(fns: &[RegFn], cache: &TypeCache, atoms: &[Atom]) -> String { let (nums, cells, walk_rep) = classes(fns, cache); let mut e = Emitter { fns, cache, atoms, cells, nums, walk_rep, queued: vec![false; cache.walks.len()], queue: vec![], helpers: HashSet::new(), sums: RefCell::new(BTreeSet::new()), helper_protos: String::new(), helper_defs: String::new(), }; // Every struct is declared first, so that a field can point to any of // them let mut structs = String::new(); for id in 0..e.cells.len() as CellNum { let _ = writeln!(structs, "typedef struct {0} {0};", struct_name(id)); } for id in 0..e.cells.len() as CellNum { e.struct_def(id, &mut structs); } // Which cells are fns, by number, for the runtime's `typeof` let fn_cells: Vec<&str> = e.cells.iter().map(|cell| if cell.code().is_some() { "1" } else { "0" }).collect(); let mut statics = format!("const bool jsrt_fn_cells[] = {{ {} }};\n\n", fn_cells.join(", ")); // A built-in fn is one static object let mut init = String::new(); for cell in cache.builtins() { let id = e.num(cell); let name = struct_name(id); let _ = writeln!(statics, "static {name} {};", builtin_name(id)); let _ = writeln!(init, " init_{name}(&{}, {id});", builtin_name(id)); } for kind in ProtoOf::ALL { let ty = &cache.protos[kind as usize]; if *ty != Type::Unknown { let _ = writeln!(statics, "static {} {};", e.c_type(e.rep(ty)), proto_global(kind)); } } if cache.host_complete != Type::Unknown { let _ = writeln!(statics, "static {} {HOST_COMPLETE_GLOBAL};", e.c_type(e.rep(&cache.host_complete))); } // The walks that the code calls, from the host's calls. The host calls the // entry with nothing: its env and `this` are undefined let entry = cache.find(fns.len() - 1, HOST, &[], fns); let entry_args = vec!["(undefined_t){0}"; cache.walks[entry].req.arg_types.len()].join(", "); e.use_walk(entry); let complete = e.host_complete(); let mut fn_protos = String::new(); let mut bodies = String::new(); while let Some(w) = e.queue.pop() { let res = cache.walks[w].result.as_ref().unwrap(); let _ = writeln!(fn_protos, "{};", e.signature(w, res)); e.emit_fn(w, res, &mut bodies); } // The program is one file, so it also holds the runtime. The runtime goes // first (see `lib/rt/jsrt.h`) let mut out = String::from( "#define JSRT_IMPLEMENTATION\n#include \"jsrt.h\"\n#include \n#include \n\n// Natives\n", ); for n in NATIVES { let params: Vec = n.args.iter().map(|t| e.c_type(e.rep(t))).collect(); let _ = writeln!(out, "{} {}({});", e.c_type(e.rep(&n.ret)), n.name, params.join(", ")); } let _ = write!( out, "\n{}\n{structs}\n{statics}\n{fn_protos}\n{}\n{}{bodies}", sum_defs(&e.sums.borrow()), e.helper_protos, e.helper_defs ); // What the host calls (see `lib/rt/jsrt.h`) let _ = writeln!(out, "void jsrt_start(void) {{\n{init} {}({entry_args});\n}}\n", e.fn_name(entry)); let _ = writeln!(out, "void jsrt_complete(int32_t h) {{\n{complete}}}"); out } struct Emitter<'a> { fns: &'a [RegFn], cache: &'a TypeCache, atoms: &'a [Atom], cells: Vec, // a cell of each class, by number nums: HashMap, // the class of each cell walk_rep: Vec, // by walk: the walk whose fn it uses queued: Vec, // the walks that the code calls, by walk queue: Vec, // the walks that the code calls, which are not written yet helpers: HashSet, // the helpers made so far, by name sums: RefCell>, // the tagged unions that the C uses helper_protos: String, helper_defs: String, } impl<'a> Emitter<'a> { fn num(&self, cell: &Cell) -> CellNum { self.nums[cell] } /// The number of the cell of an `Obj` type fn num_of(&self, ty: &Type) -> CellNum { match ty { Type::Obj(cell) => self.num(cell), _ => unreachable!(), } } /// Walk `w` is called, so the walk whose fn it uses is written fn use_walk(&mut self, w: usize) { let w = self.walk_rep[w]; if !self.queued[w] { self.queued[w] = true; self.queue.push(w); } } fn signature(&self, w: usize, res: &InferResult) -> String { let params: Vec = (self.cache.walks[w].req.arg_types.iter().enumerate()) .map(|(i, ty)| format!("{} p{i}", self.c_type(self.rep(ty)))) .collect(); let ret = self.c_type(self.rep(&res.ret_type)); format!("static {ret} {}({})", self.fn_name(w), params.join(", ")) } fn emit_fn(&mut self, w: usize, res: &InferResult, out: &mut String) { let fn_id = self.cache.walks[w].req.fn_id; let f = &self.fns[fn_id]; let mut body = String::new(); // Args are values `var_count + i` (see regify) for (i, ty) in self.cache.walks[w].req.arg_types.iter().enumerate() { let arg = f.var_count + i; self.assign(&mut body, &format!("v{arg}"), &format!("p{i}"), self.rep(ty), self.rep(&res.val_types[arg])); } for (b, block) in f.blocks.iter().enumerate() { let _ = writeln!(body, "L{b}:;"); if !res.reachable[b] { let _ = writeln!(body, " abort();"); continue; } let code = &f.code[block.start..block.end]; let (instrs, last) = match code.split_last() { Some((last, rest)) if is_terminator(last.op) => (rest, Some(last)), _ => (code, None), }; for (i, instr) in instrs.iter().enumerate() { self.emit_instr(fn_id, res, block.start + i, instr, &mut body); } // Postfix copies carry values into the next block's stack slots for copy in &block.postfix { let (src, dst) = (copy.args[0], copy.dsts[0]); let (from, to) = (self.rep(&res.val_types[src]), self.rep(&res.val_types[dst])); self.assign(&mut body, &format!("v{dst}"), &format!("v{src}"), from, to); } let goto = |i: usize| format!("goto L{};", block.succ[i]); match last.map(|l| l.op) { Some(OpCode::IfFalse | OpCode::IfFalse8 | OpCode::IfTrue | OpCode::IfTrue8) => { let last = last.unwrap(); let cond = self.truthy(&format!("v{}", last.args[0]), &res.val_types[last.args[0]]); let not = if matches!(last.op, OpCode::IfFalse | OpCode::IfFalse8) { "!" } else { "" }; let _ = writeln!(body, " if ({not}{cond}) {} else {}", goto(0), goto(1)); } Some(OpCode::Goto | OpCode::Goto8 | OpCode::Goto16) => { let _ = writeln!(body, " {}", goto(0)); } Some(_) => self.emit_instr(fn_id, res, block.end - 1, last.unwrap(), &mut body), None if !block.succ.is_empty() => { let _ = writeln!(body, " {}", goto(0)); } None => {} } } let _ = writeln!(out, "{} {{", self.signature(w, res)); for (v, ty) in res.val_types.iter().enumerate() { let _ = writeln!(out, " {} v{v};", self.c_type(self.rep(ty))); } let _ = writeln!(out, "{body}}}\n"); } /// A new object of cell `id`, on the heap fn alloc(&self, id: CellNum) -> String { let name = struct_name(id); format!("init_{name}(jsrt_alloc(sizeof({name})), {id})") } /// The C fn of walk `w`: the fn of the walk of its class fn fn_name(&self, w: usize) -> String { let w = self.walk_rep[w]; format!("f{}_{w}", self.cache.walks[w].req.fn_id) } } /// `s` as a C string literal. Every byte that is not plain printable ASCII is /// an octal escape, so UTF-8 passes through unchanged fn c_str(s: &str) -> String { let mut lit = String::from("\""); for b in s.bytes() { match b { b'"' | b'\\' => lit.extend(['\\', b as char]), 0x20..0x7f => lit.push(b as char), _ => lit += &format!("\\{b:03o}"), } } lit + "\"" } fn is_num(rep: Rep) -> bool { matches!(rep, Rep::Int | Rep::Double) } /// The struct of the objects of cell `id` fn struct_name(id: CellNum) -> String { format!("S{id}") } /// The static object of the built-in fn of cell `id` fn builtin_name(id: CellNum) -> String { format!("b{id}") } /// The global that holds what the host calls when an IO op completes const HOST_COMPLETE_GLOBAL: &str = "jsrt_host_complete"; fn proto_global(kind: ProtoOf) -> String { format!("proto_{kind:?}").to_lowercase() } fn is_terminator(op: OpCode) -> bool { use OpCode::*; matches!( op, Goto | Goto8 | Goto16 | IfFalse | IfFalse8 | IfTrue | IfTrue8 | Return | ReturnAsync | ReturnUndef ) }