Rewrite with working async

This commit is contained in:
2026-10-16 08:00:06 +00:00
parent d0c2f291e9
commit 1e4647347f
36 changed files with 5106 additions and 654 deletions
+371
View File
@@ -0,0 +1,371 @@
//! 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<str>) {
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<Operand> {
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<Operand>,
pub reps: HashMap<Operand, Rep>, // the C type of each local, arg and reg
pub types: HashMap<Operand, Type>, // 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<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 <ret> w<w>(<params>)`. 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
}
}
+579
View File
@@ -0,0 +1,579 @@
//! The C of each instr: literals, operators, tests, moves, calls and returns. 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]`
use super::{
func::{FnEmitter, Writer},
layout::{ClassKind, Member, Rep},
undefined, unit_literal,
};
use crate::{
infer::{InstrRef, ObjAddr, Type},
libqjs::parse::{ImmOperand, JsValue, opcodes::OpCode},
native::NATIVES,
regify::{Operand, RegInstr},
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";
/// 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<'_, '_> {
pub(super) fn emit_instr(&mut self, idx: usize, instr: &RegInstr) {
use OpCode::*;
match instr.op {
PushMinus1 | Push0 | Push1 | Push2 | Push3 | Push4 | Push5 | Push6 | Push7 | PushI8
| PushI16 | PushI32 | PushTrue | PushFalse | PushAtomValue | PushEmptyString
| PushConst | PushConst8 | Undefined | Null => {
let (expr, member) = self.literal(instr);
self.set(instr.dsts[0], &expr, member);
}
// Operators
Add | Sub | Mul | Div | Mod | Pow => self.binary(instr),
And | Or | Xor | Shl | Sar | Shr => self.bitwise(instr),
Neg | Plus | Inc | Dec | Not => self.unary(instr),
PostInc | PostDec => self.post(instr),
Lt | Lte | Gt | Gte => self.compare(instr),
StrictEq | StrictNeq | Eq | Neq => self.equality(instr),
// Tests
Lnot | Typeof | IsUndefined | TypeofIsUndefined | IsNull | IsUndefinedOrNull
| TypeofIsFunction => self.test(instr),
// Moves. 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);
}
// 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));
}
ArrayFrom => self.array_from(idx, instr),
GetField | GetLength => self.get_field(instr),
DefineField | PutField => self.put_field(instr),
DefineFieldRef => self.share_field(instr),
CloseField => self.close_field(instr),
GetArrayEl => self.get_elem(instr),
PutArrayEl => self.put_elem(instr),
CloseLoc => self.close_loc(instr),
// Calls and returns
Call | CallMethod | CallConstructor => self.call(idx, instr),
Return | ReturnAsync => {
let value = self.read(instr.args[0]);
self.ret(value);
}
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: 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
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!(
"{{ {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
fn literal(&mut self, instr: &RegInstr) -> (String, Member) {
use OpCode::*;
match (instr.op, instr.imm) {
(PushMinus1 | Push0 | Push1 | Push2 | Push3 | Push4 | Push5 | Push6 | Push7, _) => {
let n = instr.op as i64 - Push0 as i64;
(n.to_string(), Member::Int)
}
(_, ImmOperand::I8(n)) => (int(n as i64), Member::Int),
(_, ImmOperand::I16(n)) => (int(n as i64), Member::Int),
(_, 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),
(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
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),
JsValue::Float(x) if x.is_nan() => ("NAN".into(), Member::Float),
JsValue::Float(x) if x.is_infinite() => (
if *x < 0.0 { "(-INFINITY)" } else { "INFINITY" }.into(),
Member::Float,
),
JsValue::Float(x) => (format!("{x:?}"), Member::Float),
JsValue::String(s) => (self.e.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),
JsValue::NativeFn(idx) => (
unit_literal(Member::Native(*idx)).into(),
Member::Native(*idx),
),
c => unimplemented!("the constant {c:?}"),
}
}
// ──────────────────────────────────── Operators ─────────────────────────────────────
/// `l op r`. `+` concatenates if either side is a string or an object. Anything else
/// converts both sides to numbers, which are doubles
fn binary(&mut self, instr: &RegInstr) {
use OpCode::*;
let (dst, op) = (instr.dsts[0], instr.op);
let ops = [self.read(instr.args[1]), self.read(instr.args[0])];
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(_));
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);
};
return fe.set(dst, &format!("__rt_string_concat({ls}, {rs})"), Member::Str);
}
let (Some(ln), Some(rn)) = (fe.e.to_number(l, *lm), fe.e.to_number(r, *rm)) else {
return fe.throw(NO_CONVERSION);
};
// 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)),
Mod => format!("fmod({ln}, {rn})"),
Pow => format!("__rt_pow({ln}, {rn})"),
_ => format!("({ln} / {rn})"),
};
fe.set(dst, &expr, Member::Float);
});
}
/// `&`, `|`, `^`, `<<`, `>>` and `>>>`. Each operand becomes an int32, as JS's ToInt32
/// does, and a shift counts its low 5 bits only. `>>>` gives a uint32, which is a float
fn bitwise(&mut self, instr: &RegInstr) {
use OpCode::*;
let (dst, op) = (instr.dsts[0], instr.op);
let ops = [self.read(instr.args[1]), self.read(instr.args[0])];
self.split(&ops, &mut |fe, ms| {
let ((l, lm), (r, rm)) = (&ms[0], &ms[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 (expr, member) = match op {
And => (format!("({a} & {b})"), Member::Int),
Or => (format!("({a} | {b})"), Member::Int),
Xor => (format!("({a} ^ {b})"), Member::Int),
Shl => (
format!("(int32_t)((uint32_t){a} << ({b} & 31))"),
Member::Int,
),
Sar => (format!("({a} >> ({b} & 31))"), Member::Int),
_ => (
format!("(double)((uint32_t){a} >> ({b} & 31))"),
Member::Float,
),
};
fe.set(dst, &expr, member);
});
}
/// `-v`, `+v`, `v + 1`, `v - 1` and `~v`, on the value as a number. Only `+v` keeps an int
/// an int: `-0` is a float, and so is anything that can leave the int32 range. `~` gives
/// an int32
fn unary(&mut self, instr: &RegInstr) {
use OpCode::*;
let (dst, op) = (instr.dsts[0], instr.op);
let ops = [self.read(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);
};
let (expr, member) = match (op, *m) {
(Not, _) => (format!("(~{})", fe.e.to_int32(v, *m).unwrap()), Member::Int),
(Plus, Member::Int) => (v.clone(), Member::Int),
(Plus, _) => (n, Member::Float),
(Neg, _) => (format!("(-{n})"), Member::Float),
(Inc, _) => (format!("({n} + 1)"), Member::Float),
_ => (format!("({n} - 1)"), Member::Float),
};
fe.set(dst, &expr, member);
});
}
/// `v++` and `v--`: the old value, as a number, then the new one
fn post(&mut self, instr: &RegInstr) {
let (old, new) = (instr.dsts[0], instr.dsts[1]);
let step = if instr.op == OpCode::PostInc {
"+"
} else {
"-"
};
let ops = [self.read(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);
};
fe.set(old, &n, Member::Float);
fe.set(new, &format!("({n} {step} 1)"), Member::Float);
});
}
/// `<`, `<=`, `>` 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])];
self.split(&ops, &mut |fe, ms| {
let ((l, lm), (r, rm)) = (&ms[0], &ms[1]);
if (lm, rm) == (&Member::Str, &Member::Str) {
return fe.set(
dst,
&format!("(__rt_string_cmp({l}, {r}) {op} 0)"),
Member::Bool,
);
}
let (Some(ln), Some(rn)) = (fe.e.to_number(l, *lm), fe.e.to_number(r, *rm)) else {
return fe.throw(NO_CONVERSION);
};
fe.set(dst, &format!("({ln} {op} {rn})"), Member::Bool);
});
}
/// `===`, `!==`, `==` and `!=`
fn equality(&mut self, instr: &RegInstr) {
use OpCode::*;
let (dst, op) = (instr.dsts[0], instr.op);
let ops = [self.read(instr.args[1]), self.read(instr.args[0])];
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);
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),
};
let expr = if matches!(op, Neq | StrictNeq) {
format!("(!{eq})")
} else {
eq
};
fe.set(dst, &expr, Member::Bool);
});
}
/// The tests of one value: `!`, `typeof`, and whether it is undefined, null or a fn
fn test(&mut self, instr: &RegInstr) {
use OpCode::*;
let (dst, op) = (instr.dsts[0], instr.op);
let ops = [self.read(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);
let (expr, member) = match op {
Lnot => (format!("(!{})", fe.e.truthy(v, m)), Member::Bool),
Typeof => {
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",
_ => "object",
};
(fe.e.string(name), Member::Str)
}
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);
});
}
// ────────────────────────────────────── 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`
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<Type> = instr
.args
.iter()
.map(|slot| self.types[slot].clone())
.collect();
let dst = instr.dsts[0];
let ops = [self.read(instr.args[0])];
self.split(&ops, &mut |fe, ms| {
let (callee, member) = (&ms[0].0, ms[0].1);
if let Member::Native(idx) = member {
fe.native_call(idx, instr);
return;
}
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<String> = (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),
});
});
}
/// 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`)
fn native_call(&mut self, idx: usize, instr: &RegInstr) {
let native = &NATIVES[idx];
let dst = instr.dsts[0];
let mut ops = vec![];
// 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;
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"),
}
}
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()
}
}
/// An arg of `member` as the native param `param`, if it can pass
fn native_arg(value: &str, member: Member, param: &Type) -> Option<String> {
match (member, param) {
(Member::Int, Type::Float) => Some(format!("(double){value}")),
(Member::Float, Type::Int) => Some(format!("__rt_to_int32({value})")),
(Member::Int, Type::Int) | (Member::Float, Type::Float) => Some(value.into()),
(Member::Str, Type::Str(_)) | (Member::Undefined, Type::Undefined) => Some(value.into()),
_ => None,
}
}
/// The member of a native's return type
fn native_member(ty: &Type) -> Member {
match ty {
Type::Undefined => Member::Undefined,
Type::Int => Member::Int,
Type::Float => Member::Float,
Type::Str(_) => Member::Str,
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!(),
}
}
+284
View File
@@ -0,0 +1,284 @@
//! Layouts: what each type is in C
//!
//! 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
//!
//! 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
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
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Member {
Undefined,
Null,
Bool,
Int,
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
}
/// A type, in C: one member, or a sum of several
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Rep {
One(Member),
Sum(usize), // an index into `Layout::sums`
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClassKind {
Obj,
Array,
Closure(usize), // it runs this fn
}
/// 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
}
#[derive(Debug)]
pub struct Class {
pub kind: ClassKind,
pub objs: Vec<ObjAddr>,
pub fields: Vec<ClassField>, // sorted by name
pub elems: Rep, // for an array: its elements. Else unused
}
#[derive(Debug)]
pub struct Layout {
pub classes: Vec<Class>,
class_of: HashMap<ObjAddr, usize>,
pub sums: Vec<Vec<Member>>, // the members of each sum, sorted. A member's tag is its index
sum_ids: HashMap<Vec<Member>, usize>,
}
impl Layout {
pub fn new(memory: &Memory) -> Self {
let class_of = refine(memory);
let mut layout = Layout {
classes: vec![],
class_of,
sums: vec![],
sum_ids: HashMap::new(),
};
let mut objs: Vec<ObjAddr> = 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<ObjAddr>> = vec![vec![]; count];
for obj in objs {
members[layout.class_of[&obj]].push(obj);
}
for objs in members {
let class = layout.make_class(memory, objs);
layout.classes.push(class);
}
layout
}
/// The class of the objects in `objs`, which refinement put together
fn make_class(&mut self, memory: &Memory, objs: Vec<ObjAddr>) -> Class {
let first = objs[0];
let kind = match memory.get(Addr::Object(first)) {
Type::Shape(Shape::Array(_)) => ClassKind::Array,
Type::Closure { fn_idx, .. } => ClassKind::Closure(*fn_idx),
_ => ClassKind::Obj,
};
// The type of each field is the join over the class, so the C type holds all
let fields = match kind {
ClassKind::Array => vec![],
_ => (memory.fields(first).iter())
.map(|&(name, field)| {
let types = objs.iter().map(|&obj| {
let field = memory.field(obj, name).unwrap();
memory.get(field.addr).clone()
});
let ty = types.fold(Type::Unknown, |all, ty| join(&all, &ty));
ClassField {
name,
rep: self.rep(&ty),
shared: field.addr != Addr::Field(first, name),
defined: field.defined,
}
})
.collect(),
};
let elems = match kind {
ClassKind::Array => {
let ty = objs
.iter()
.fold(Type::Unknown, |all, &obj| join(&all, memory.elems(obj)));
self.rep(&ty)
}
_ => Rep::One(Member::Undefined),
};
Class {
kind,
objs,
fields,
elems,
}
}
pub fn class(&self, obj: ObjAddr) -> usize {
self.class_of[&obj]
}
pub fn field(&self, class: usize, name: AtomRef) -> Option<&ClassField> {
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<Member> {
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);
if let [member] = members[..] {
return Rep::One(member);
}
let next = self.sums.len();
let id = *self.sum_ids.entry(members.clone()).or_insert(next);
if id == next {
self.sums.push(members);
}
Rep::Sum(id)
}
/// The members of a value of C type `rep`
pub fn rep_members(&self, rep: Rep) -> Vec<Member> {
match rep {
Rep::One(member) => vec![member],
Rep::Sum(id) => self.sums[id].clone(),
}
}
}
/// The members of `ty`, given the class of each object
fn members_with(ty: &Type, class_of: &HashMap<ObjAddr, usize>) -> Vec<Member> {
if *ty == Type::Unknown {
return vec![Member::Undefined];
}
let mut members: Vec<Member> = (ty.members().iter())
.map(|member| match member {
Type::Undefined => Member::Undefined,
Type::Null => Member::Null,
Type::Bool(_) => Member::Bool,
Type::Int => Member::Int,
Type::Float => Member::Float,
Type::Str(_) => Member::Str,
Type::Ref(obj) => Member::Obj(class_of[obj]),
Type::Native(idx) => Member::Native(*idx),
ty => unreachable!("{ty:?} is not a value"),
})
.collect();
members.sort_unstable();
members.dedup();
members
}
/// The class of every object. At first, objects with the same kind and field names share a
/// class. Then each round splits a class whose objects have fields of different members,
/// until a round splits nothing
fn refine(memory: &Memory) -> HashMap<ObjAddr, usize> {
let mut objs: Vec<ObjAddr> = memory.objects().map(|(obj, _)| obj).collect();
objs.sort_unstable();
// The first key: the kind, then each field's name and how it is held. A closure is
// its own class
let first_key = |obj: ObjAddr| -> (u8, Option<ObjAddr>, Vec<(AtomRef, bool, bool)>) {
match memory.get(Addr::Object(obj)) {
Type::Shape(Shape::Array(_)) => (0, None, vec![]),
entry => {
let fields = (memory.fields(obj).iter())
.map(|&(name, f)| (name, f.addr != Addr::Field(obj, name), f.defined))
.collect();
match entry {
Type::Closure { .. } => (1, Some(obj), fields),
_ => (2, None, fields),
}
}
}
};
let mut class_of = number(&objs, first_key);
loop {
let count = class_of.values().max().map_or(0, |&n| n + 1);
// A field's members, or an array's elements, with the classes so far
let key = |obj: ObjAddr| -> (usize, Vec<Vec<Member>>) {
let types: Vec<Type> = match memory.get(Addr::Object(obj)) {
Type::Shape(Shape::Array(elems)) => vec![(**elems).clone()],
_ => memory
.fields(obj)
.iter()
.map(|(_, f)| memory.get(f.addr).clone())
.collect(),
};
(
class_of[&obj],
types.iter().map(|ty| members_with(ty, &class_of)).collect(),
)
};
let next = number(&objs, key);
if next.values().max().map_or(0, |&n| n + 1) == count {
return class_of;
}
class_of = next;
}
}
/// A number for each object, the same for objects with the same key, in the order that the
/// keys first come
fn number<K: std::hash::Hash + Eq>(
objs: &[ObjAddr],
key: impl Fn(ObjAddr) -> K,
) -> HashMap<ObjAddr, usize> {
let mut ids: HashMap<K, usize> = HashMap::new();
objs.iter()
.map(|&obj| {
let next = ids.len();
(obj, *ids.entry(key(obj)).or_insert(next))
})
.collect()
}
+515
View File
@@ -0,0 +1,515 @@
//! 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
//!
//! 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
//!
//! A value that no write reached (a straggler `Unknown`) is undefined
mod func;
mod instr;
mod layout;
mod objects;
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
use std::fmt::Write;
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<usize, Rep>, // 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<usize>,
walks_wanted: HashSet<usize>,
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<String>, // the text of each string literal
string_ids: HashMap<String, usize>,
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<String> {
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<String> {
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<String> {
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<String> {
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 ───────────────────────────────────────
const HEADER: &str = "// Generated by jsrt\n#define JSRT_IMPLEMENTATION\n#include \"jsrt.h\"\n\n#include <math.h>\n#include <stdlib.h>\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);
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
}
/// 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.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");
}
}
/// 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<String> = text.encode_utf16().map(|u| u.to_string()).collect();
let _ = writeln!(
c,
"static struct jsrt_string str{id} = {{{}, {{{}}}}};",
units.len(),
units.join(", ")
);
}
for (local, rep) in &self.globals {
let _ = writeln!(c, "static {} *g{local};", self.ctype(*rep));
}
}
}
+397
View File
@@ -0,0 +1,397 @@
//! The C of objects: fields, elements, and the read fns that follow a prototype chain
//!
//! 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 _;
use super::{
Emitter,
func::{Code, FnEmitter, Writer},
layout::{ClassKind, Member, Rep},
undefined,
};
use crate::{
infer::{LENGTH_FIELD, Type, join},
libqjs::parse::{AtomRef, opcodes::OpCode},
regify::{Operand, RegInstr},
transform::PROTO_FIELD,
};
// ──────────────────────────────────────── Fields ────────────────────────────────────────
impl FnEmitter<'_, '_> {
/// `[a, b, ...]`: the elements are the operands, 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 n = instr.args.len();
self.line(format!("{array} = __rt_alloc(sizeof(struct c{class}));"));
self.line(format!(
"{array}->items = __rt_grow(NULL, &{array}->cap, {n}, sizeof *{array}->items);"
));
self.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};"));
}
self.set(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`)
pub(super) fn get_field(&mut self, instr: &RegInstr) {
let name = match instr.op {
OpCode::GetLength => LENGTH_FIELD,
_ => instr.imm.atom(),
};
let dst = instr.dsts[0];
let ops = [self.read(instr.args[0])];
self.split(&ops, &mut |fe, ms| {
let (obj, member) = (&ms[0].0, ms[0].1);
match (member, fe.class_kind(member)) {
(Member::Str, _) if name == LENGTH_FIELD => {
fe.set(dst, &format!("__rt_string_length({obj})"), Member::Int)
}
(Member::Obj(_), ClassKind::Array) if name == LENGTH_FIELD => {
fe.set(dst, &format!("{obj}->length"), Member::Int)
}
(Member::Obj(class), _) => {
let (value, rep) = read_field(fe.e, class, name, obj);
fe.assign(dst, &value, rep);
}
(Member::Undefined | Member::Null, _) => {
fe.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"),
},
}
});
}
/// `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
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])];
self.split(&ops, &mut |fe, ms| {
let (obj, member) = (&ms[0].0, ms[0].1);
match (member, fe.class_kind(member)) {
(Member::Obj(_), ClassKind::Array) if name == LENGTH_FIELD => {
fe.set_length(obj, &value);
}
(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};"));
if !field.defined {
fe.line(format!("{obj}->has_{} = true;", fe.e.field_name(name)));
}
}
_ => fe.throw("cannot set a field of a primitive, undefined or null"),
}
});
}
/// `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);
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"),
};
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};"));
});
}
/// `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)
pub(super) fn share_field(&mut self, instr: &RegInstr) {
let name = instr.imm.atom();
let c_name = self.e.field_name(name);
let location = instr.args[0];
let ops = [self.read(instr.args[1])];
self.split(&ops, &mut |fe, ms| {
let obj = &ms[0].0;
match location {
Operand::Reg(_) => {
let holder = fe.read(location);
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};"));
}
}
});
}
/// `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)
pub(super) fn close_field(&mut self, instr: &RegInstr) {
let name = instr.imm.atom();
let ops = [self.read(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");
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; }}"));
});
}
}
// ─────────────────────────────────────── 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
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])];
self.split(&ops, &mut |fe, ms| {
let ((obj, om), (index, im)) = (&ms[0], &ms[1]);
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,
)
}
(Member::Str, _) => (
format!("__rt_string_length({obj})"),
format!("__rt_string_at({obj}, {})", as_index(index, *im)),
Rep::One(Member::Str),
),
(Member::Undefined | Member::Null, _) => {
fe.throw("cannot read an element of undefined or null");
return;
}
_ => unreachable!("infer reads 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("}");
});
}
/// `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
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])];
self.split(&ops, &mut |fe, ms| {
let ((obj, om), (index, im)) = (&ms[0], &ms[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 elems = fe.e.layout.classes[class].elems;
let i = as_index(index, *im);
let v = fe.e.convert(&value.0, value.1, elems);
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)
)),
}
}
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");
}