Files
jsrt/compiler/src/emit/mod.rs
T
2026-10-08 08:53:11 +00:00

311 lines
10 KiB
Rust

//! 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 <math.h>\n#include <stdlib.h>\n\n// Natives\n",
);
for n in NATIVES {
let params: Vec<String> = n.args.iter().map(|t| e.c_type(e.rep(t))).collect();
let _ = writeln!(out, "{} {}({});", e.c_type(e.rep(&n.ret)), n.name, params.join(", "));
}
let _ = write!(
out,
"\n{}\n{structs}\n{statics}\n{fn_protos}\n{}\n{}{bodies}",
sum_defs(&e.sums.borrow()),
e.helper_protos,
e.helper_defs
);
// 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<Cell>, // a cell of each class, by number
nums: HashMap<Cell, CellNum>, // the class of each cell
walk_rep: Vec<usize>, // by walk: the walk whose fn it uses
queued: Vec<bool>, // the walks that the code calls, by walk
queue: Vec<usize>, // the walks that the code calls, which are not written yet
helpers: HashSet<String>, // the helpers made so far, by name
sums: RefCell<BTreeSet<Kinds>>, // the tagged unions that the C uses
helper_protos: String,
helper_defs: String,
}
impl<'a> Emitter<'a> {
fn num(&self, cell: &Cell) -> CellNum {
self.nums[cell]
}
/// The number of the cell of an `Obj` type
fn num_of(&self, ty: &Type) -> CellNum {
match ty {
Type::Obj(cell) => self.num(cell),
_ => unreachable!(),
}
}
/// Walk `w` is called, so the walk whose fn it uses is written
fn use_walk(&mut self, w: usize) {
let w = self.walk_rep[w];
if !self.queued[w] {
self.queued[w] = true;
self.queue.push(w);
}
}
fn signature(&self, w: usize, res: &InferResult) -> String {
let params: Vec<String> = (self.cache.walks[w].req.arg_types.iter().enumerate())
.map(|(i, ty)| format!("{} p{i}", self.c_type(self.rep(ty))))
.collect();
let ret = self.c_type(self.rep(&res.ret_type));
format!("static {ret} {}({})", self.fn_name(w), params.join(", "))
}
fn emit_fn(&mut self, w: usize, res: &InferResult, out: &mut String) {
let fn_id = self.cache.walks[w].req.fn_id;
let f = &self.fns[fn_id];
let mut body = String::new();
// Args are values `var_count + i` (see regify)
for (i, ty) in self.cache.walks[w].req.arg_types.iter().enumerate() {
let arg = f.var_count + i;
self.assign(&mut body, &format!("v{arg}"), &format!("p{i}"), self.rep(ty), self.rep(&res.val_types[arg]));
}
for (b, block) in f.blocks.iter().enumerate() {
let _ = writeln!(body, "L{b}:;");
if !res.reachable[b] {
let _ = writeln!(body, " abort();");
continue;
}
let code = &f.code[block.start..block.end];
let (instrs, last) = match code.split_last() {
Some((last, rest)) if is_terminator(last.op) => (rest, Some(last)),
_ => (code, None),
};
for (i, instr) in instrs.iter().enumerate() {
self.emit_instr(fn_id, res, block.start + i, instr, &mut body);
}
// Postfix copies carry values into the next block's stack slots
for copy in &block.postfix {
let (src, dst) = (copy.args[0], copy.dsts[0]);
let (from, to) = (self.rep(&res.val_types[src]), self.rep(&res.val_types[dst]));
self.assign(&mut body, &format!("v{dst}"), &format!("v{src}"), from, to);
}
let goto = |i: usize| format!("goto L{};", block.succ[i]);
match last.map(|l| l.op) {
Some(OpCode::IfFalse | OpCode::IfFalse8 | OpCode::IfTrue | OpCode::IfTrue8) => {
let last = last.unwrap();
let cond = self.truthy(&format!("v{}", last.args[0]), &res.val_types[last.args[0]]);
let not = if matches!(last.op, OpCode::IfFalse | OpCode::IfFalse8) { "!" } else { "" };
let _ = writeln!(body, " if ({not}{cond}) {} else {}", goto(0), goto(1));
}
Some(OpCode::Goto | OpCode::Goto8 | OpCode::Goto16) => {
let _ = writeln!(body, " {}", goto(0));
}
Some(_) => self.emit_instr(fn_id, res, block.end - 1, last.unwrap(), &mut body),
None if !block.succ.is_empty() => {
let _ = writeln!(body, " {}", goto(0));
}
None => {}
}
}
let _ = writeln!(out, "{} {{", self.signature(w, res));
for (v, ty) in res.val_types.iter().enumerate() {
let _ = writeln!(out, " {} v{v};", self.c_type(self.rep(ty)));
}
let _ = writeln!(out, "{body}}}\n");
}
/// A new object of cell `id`, on the heap
fn alloc(&self, id: CellNum) -> String {
let name = struct_name(id);
format!("init_{name}(jsrt_alloc(sizeof({name})), {id})")
}
/// The C fn of walk `w`: the fn of the walk of its class
fn fn_name(&self, w: usize) -> String {
let w = self.walk_rep[w];
format!("f{}_{w}", self.cache.walks[w].req.fn_id)
}
}
/// `s` as a C string literal. Every byte that is not plain printable ASCII is
/// an octal escape, so UTF-8 passes through unchanged
fn c_str(s: &str) -> String {
let mut lit = String::from("\"");
for b in s.bytes() {
match b {
b'"' | b'\\' => lit.extend(['\\', b as char]),
0x20..0x7f => lit.push(b as char),
_ => lit += &format!("\\{b:03o}"),
}
}
lit + "\""
}
fn is_num(rep: Rep) -> bool {
matches!(rep, Rep::Int | Rep::Double)
}
/// The struct of the objects of cell `id`
fn struct_name(id: CellNum) -> String {
format!("S{id}")
}
/// The static object of the built-in fn of cell `id`
fn builtin_name(id: CellNum) -> String {
format!("b{id}")
}
/// 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
)
}