This commit is contained in:
2026-10-22 13:05:04 +00:00
parent 1e4647347f
commit a80bf6f19a
10 changed files with 1789 additions and 1519 deletions
+119
View File
@@ -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<str>) {
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 `<target><value>;`, 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
}
}
}
}
-371
View File
@@ -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<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
}
}
+184
View File
@@ -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<String> {
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<String> = (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(", "))
}
+265 -276
View File
@@ -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])];
self.split(&ops, &mut |fe, ms| {
let ((l, lm), (r, rm)) = (&ms[0], &ms[1]);
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(_));
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])];
self.split(&ops, &mut |fe, ms| {
let ((l, lm), (r, rm)) = (&ms[0], &ms[1]);
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);
let eq = match op {
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));
// 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(),
// 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),
_ => 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),
(_, (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);
}
/// The tests of one value: `!`, `typeof`, and whether it is undefined, null or a fn
self.body.close(&cases);
}
/// 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<Type> = 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;
self.body.close(&cases);
}
/// 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 = 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);
let mut types: Vec<Type> = 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![];
// 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]),
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(),
};
fe.e.convert(&value, rep, param)
})
.collect();
let param = program.slot_ctype(walk, Operand::Arg(i));
args.push(self.body.convert(&value, ctype, param));
}
let call = format!("w{w}({})", args.join(", "));
let call = format!("js{walk}({})", args.join(", "));
let ret = &program.cache.walks[walk].ret;
// 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");
// 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 = fe.e.layout.rep(&cache.walks[w].ret);
let ret = program.types.of(ret);
if instr.op != OpCode::CallConstructor {
fe.assign(dst, &call, ret);
return;
return self.set(dst, &call, ret);
}
let result = fe.temp(ret);
fe.line(format!("{result} = {call};"));
let this = fe.read(instr.args[THIS_ARG as usize]);
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)]);
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),
});
});
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),
}
}
/// 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`)
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;
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"),
}
}
for case in &cases {
self.body.open(case);
let args = case.operands.iter().zip(native.arg_types);
let args: Option<Vec<String>> = args
.map(|((v, m), param)| native_arg(v, *m, param))
.collect();
match args {
Some(args) => {
let call = format!("{}({})", native.name, args.join(", "));
fe.set(dst, &call, native_member(&native.ret_type));
});
self.set_member(dst, &call, native_member(&native.ret_type));
}
None => self.body.throw("a native got an arg of the wrong kind"),
}
}
/// 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!(),
}
}
+256
View File
@@ -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<walk>`, 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<Operand>, // the locals and args that closures share: each is a pointer to a cell
}
/// `static <ret> js<walk>(<params>)`. A shared arg's param is named `p<k>`, because its value
/// goes into a cell named `a<k>`
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<String> = (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<walk>`: 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
}
}
+88 -476
View File
@@ -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<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 ───────────────────────────────────────
use program::Program;
use types::{CType, ClassKind};
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 {
/// 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);
// 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");
}
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);
c += "\n";
for union in unions {
c += &union_definition(&program, union);
}
c += "\n";
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);
}
// 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};");
}
/// 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();
c.push('\n');
format!("typedef struct {{ uint8_t tag; union {{{members} }} as; }} u{id};\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} {{");
/// 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 {
let _ = writeln!(
c,
" int32_t length, cap;\n {} *items;",
self.ctype(class.elems)
c += &format!(
" int32_t length, cap;\n {} *items;\n",
class.elems.c_name()
);
}
for field in &class.fields {
let (ctype, name) = (self.ctype(field.rep), self.field_name(field.name));
let name = program.field_name(field.name);
let pointer = if field.shared { "*" } else { "" };
let _ = writeln!(c, " {ctype} {pointer}{name};");
c += &format!(" {} {pointer}{name};\n", field.ctype.c_name());
if !field.defined {
let _ = writeln!(c, " bool has_{name};");
c += &format!(" bool has_{name};\n");
}
}
// C has no empty structs
if class.kind != ClassKind::Array && class.fields.is_empty() {
c.push_str(" char _;\n");
c += " char _;\n";
}
c.push_str("};\n\n");
}
}
c + "};\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() {
/// A string literal, with its UTF-16 units
fn string_definition(id: usize, text: &str) -> String {
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));
}
}
let (length, units) = (units.len(), units.join(", "));
format!("static struct jsrt_string str{id} = {{{length}, {{{units}}}}};\n")
}
+196 -303
View File
@@ -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);
(Member::Native(_), _) => self.body.throw("a native function has no fields"),
_ => self.body.throw("fields of primitives need the stdlib"),
}
_ => 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
self.body.close(&cases);
}
/// `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"),
}
});
}
/// `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);
self.body.close(&cases);
}
/// `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};"));
}
/// `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)
self.body.close(&cases);
}
/// `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));
}
/// `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)
self.body.close(&cases);
}
/// `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;
(
let (length, item, ctype) = match (*obj_member, self.program.class_kind(*obj_member)) {
(Member::Obj(class), ClassKind::Array) => (
format!("{obj}->length"),
format!("{obj}->items[{}]", as_index(index, *im)),
elems,
)
}
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("}");
}
/// `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
self.body.close(&cases);
}
/// `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};"));
});
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);
}
}
/// 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");
}
+439
View File
@@ -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<String>, // statics: the text of each string literal
pub globals: BTreeMap<usize, CType>, // statics: the module bindings, which are locals of the entry, and their C types
pub functions: Vec<Function>,
}
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<String> = 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<Type> = (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<Type> {
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<Item = &'a RegInstr> + 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<String>, // 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<Case> {
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<String> {
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<usize> {
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<usize> {
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<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()
}
@@ -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<usize> {
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<ObjAddr>,
pub fields: Vec<ClassField>, // 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>,
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>,
pub unions: Vec<Vec<Member>>, // the members of each union, sorted. A member's tag is its index
union_ids: HashMap<Vec<Member>, 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<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];
let count = types.class_of.values().max().map_or(0, |&n| n + 1);
let mut objs_of_class: Vec<Vec<ObjAddr>> = 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<Member> {
/// The members of a value of type `ty`. A location that no value reached holds undefined
pub fn type_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);
/// 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<Member> {
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<Member> {
match ctype {
CType::One(member) => vec![member],
CType::Union(id) => self.unions[id].clone(),
}
}
}
+104
View File
@@ -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<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
pub fn to_int32(expr: &str, member: Member) -> Option<String> {
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}"),
}
}