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