New emit
This commit is contained in:
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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(", "))
|
||||||
|
}
|
||||||
+282
-293
@@ -1,14 +1,14 @@
|
|||||||
//! The C of each instr: literals, operators, tests, moves, calls and returns. Objects are in
|
//! The C of each instr: literals, operators, tests, moves, calls and returns. The instrs on
|
||||||
//! `objects`
|
//! objects are in `objects`
|
||||||
//!
|
//!
|
||||||
//! A rule takes its operands' members and gives the C of its result, and the member of that
|
//! An instr's C depends on the members of its operands. Each combination of members is one
|
||||||
//! result. Operands that an instr pops come top first (see regify), so a binary operator
|
//! case, with C of its own (see `Program::cases`). The operands that an instr pops come top
|
||||||
//! has its right side in `args[0]`
|
//! first (see regify), so a binary operator has its right side in `args[0]`
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
func::{FnEmitter, Writer},
|
js::JsFunction,
|
||||||
layout::{ClassKind, Member, Rep},
|
types::{CType, ClassKind, Member},
|
||||||
undefined, unit_literal,
|
values::{base, int, strict_eq, to_int32, to_number, truthy, undefined, unit_literal},
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
infer::{InstrRef, ObjAddr, Type},
|
infer::{InstrRef, ObjAddr, Type},
|
||||||
@@ -18,23 +18,11 @@ use crate::{
|
|||||||
transform::{FIRST_ARG, THIS_ARG},
|
transform::{FIRST_ARG, THIS_ARG},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// What a conversion of an object to a string or a number throws: it would call the object's
|
/// What converting an object to a string or a number throws. The conversion would call the
|
||||||
/// methods, which is not supported
|
/// object's methods, which is not supported
|
||||||
const NO_CONVERSION: &str = "converting an object to a string or a number 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
|
impl JsFunction<'_> {
|
||||||
fn base(member: Member) -> u8 {
|
|
||||||
match member {
|
|
||||||
Member::Undefined => 0,
|
|
||||||
Member::Null => 1,
|
|
||||||
Member::Bool => 2,
|
|
||||||
Member::Int | Member::Float => 3,
|
|
||||||
Member::Str => 4,
|
|
||||||
Member::Obj(_) | Member::Native(_) => 5,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FnEmitter<'_, '_> {
|
|
||||||
pub(super) fn emit_instr(&mut self, idx: usize, instr: &RegInstr) {
|
pub(super) fn emit_instr(&mut self, idx: usize, instr: &RegInstr) {
|
||||||
use OpCode::*;
|
use OpCode::*;
|
||||||
|
|
||||||
@@ -43,7 +31,7 @@ impl FnEmitter<'_, '_> {
|
|||||||
| PushI16 | PushI32 | PushTrue | PushFalse | PushAtomValue | PushEmptyString
|
| PushI16 | PushI32 | PushTrue | PushFalse | PushAtomValue | PushEmptyString
|
||||||
| PushConst | PushConst8 | Undefined | Null => {
|
| PushConst | PushConst8 | Undefined | Null => {
|
||||||
let (expr, member) = self.literal(instr);
|
let (expr, member) = self.literal(instr);
|
||||||
self.set(instr.dsts[0], &expr, member);
|
self.set_member(instr.dsts[0], &expr, member);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Operators
|
// Operators
|
||||||
@@ -58,17 +46,17 @@ impl FnEmitter<'_, '_> {
|
|||||||
Lnot | Typeof | IsUndefined | TypeofIsUndefined | IsNull | IsUndefinedOrNull
|
Lnot | Typeof | IsUndefined | TypeofIsUndefined | IsNull | IsUndefinedOrNull
|
||||||
| TypeofIsFunction => self.test(instr),
|
| 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 => {
|
SetLoc | SetName | ToPropkey => {
|
||||||
let (value, rep) = self.read(instr.args[0]);
|
let (value, ctype) = self.get(instr.args[0]);
|
||||||
self.assign(instr.dsts[0], &value, rep);
|
self.set(instr.dsts[0], &value, ctype);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Objects (see `objects`)
|
// Objects (see `objects`)
|
||||||
Object | Fclosure => {
|
Object | Fclosure => {
|
||||||
let class = self.class_here(idx);
|
let class = self.class_here(idx);
|
||||||
let alloc = format!("__rt_alloc(sizeof(struct c{class}))");
|
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),
|
ArrayFrom => self.array_from(idx, instr),
|
||||||
GetField | GetLength => self.get_field(instr),
|
GetField | GetLength => self.get_field(instr),
|
||||||
@@ -82,50 +70,50 @@ impl FnEmitter<'_, '_> {
|
|||||||
// Calls and returns
|
// Calls and returns
|
||||||
Call | CallMethod | CallConstructor => self.call(idx, instr),
|
Call | CallMethod | CallConstructor => self.call(idx, instr),
|
||||||
Return | ReturnAsync => {
|
Return | ReturnAsync => {
|
||||||
let value = self.read(instr.args[0]);
|
let (value, ctype) = self.get(instr.args[0]);
|
||||||
self.ret(value);
|
self.ret(&value, ctype);
|
||||||
|
}
|
||||||
|
ReturnUndef => {
|
||||||
|
let (value, ctype) = undefined();
|
||||||
|
self.ret(&value, ctype);
|
||||||
}
|
}
|
||||||
ReturnUndef => self.ret(undefined()),
|
|
||||||
|
|
||||||
_ => unimplemented!("emit {instr:?}"),
|
_ => unimplemented!("emit {instr:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The class of the objects that instr `idx` makes in this walk
|
/// Instr `idx` of this JS function
|
||||||
pub(super) fn class_here(&self, idx: usize) -> usize {
|
fn here(&self, idx: usize) -> InstrRef {
|
||||||
let req = &self.e.cache.walks[self.w].req;
|
InstrRef {
|
||||||
let made_at = InstrRef {
|
fn_idx: self.program.cache.walks[self.walk].req.fn_idx,
|
||||||
fn_idx: req.fn_idx,
|
|
||||||
instr_idx: 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
|
/// The class of the objects that instr `idx` makes, in this walk
|
||||||
/// other binding has one location
|
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) {
|
fn close_loc(&mut self, instr: &RegInstr) {
|
||||||
let ImmOperand::Loc(k) = instr.imm else {
|
let ImmOperand::Loc(k) = instr.imm else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
let slot = Operand::Local(k as usize);
|
let slot = Operand::Local(k as usize);
|
||||||
|
|
||||||
if self.is_captured(slot) {
|
if self.is_shared(slot) {
|
||||||
let (ctype, name) = (self.e.ctype(self.reps[&slot]), self.name(slot));
|
let (ctype, name) = (self.ctype(slot).c_name(), self.name(slot));
|
||||||
self.line(format!(
|
self.body.line(format!(
|
||||||
"{{ {ctype} *cell = __rt_alloc(sizeof({ctype})); *cell = *{name}; {name} = cell; }}"
|
"{{ {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 ─────────────────────────────────────
|
// ───────────────────────────────────── Literals ─────────────────────────────────────
|
||||||
|
|
||||||
/// A literal, and its member
|
/// A literal, and its member
|
||||||
@@ -142,18 +130,18 @@ impl FnEmitter<'_, '_> {
|
|||||||
(_, ImmOperand::I32(n)) => (int(n as i64), Member::Int),
|
(_, ImmOperand::I32(n)) => (int(n as i64), Member::Int),
|
||||||
(PushTrue, _) => ("true".into(), Member::Bool),
|
(PushTrue, _) => ("true".into(), Member::Bool),
|
||||||
(PushFalse, _) => ("false".into(), Member::Bool),
|
(PushFalse, _) => ("false".into(), Member::Bool),
|
||||||
(PushAtomValue, _) => (
|
(PushAtomValue, _) => {
|
||||||
self.e.string(&self.e.atom_text(instr.imm.atom())),
|
let text = instr.imm.atom().text(self.program.atoms);
|
||||||
Member::Str,
|
(self.program.string(&text), Member::Str)
|
||||||
),
|
}
|
||||||
(PushEmptyString, _) => (self.e.string(""), Member::Str),
|
(PushEmptyString, _) => (self.program.string(""), Member::Str),
|
||||||
(Undefined, _) => (unit_literal(Member::Undefined).into(), Member::Undefined),
|
(Undefined, _) => (unit_literal(Member::Undefined).into(), Member::Undefined),
|
||||||
(Null, _) => (unit_literal(Member::Null).into(), Member::Null),
|
(Null, _) => (unit_literal(Member::Null).into(), Member::Null),
|
||||||
_ => self.constant(instr),
|
_ => 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) {
|
fn constant(&mut self, instr: &RegInstr) -> (String, Member) {
|
||||||
match &self.f.cpool[instr.imm.const_idx()] {
|
match &self.f.cpool[instr.imm.const_idx()] {
|
||||||
JsValue::Int(n) => (int(*n as i64), Member::Int),
|
JsValue::Int(n) => (int(*n as i64), Member::Int),
|
||||||
@@ -163,7 +151,7 @@ impl FnEmitter<'_, '_> {
|
|||||||
Member::Float,
|
Member::Float,
|
||||||
),
|
),
|
||||||
JsValue::Float(x) => (format!("{x:?}"), 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::BoolTrue => ("true".into(), Member::Bool),
|
||||||
JsValue::BoolFalse => ("false".into(), Member::Bool),
|
JsValue::BoolFalse => ("false".into(), Member::Bool),
|
||||||
JsValue::Undefined => (unit_literal(Member::Undefined).into(), Member::Undefined),
|
JsValue::Undefined => (unit_literal(Member::Undefined).into(), Member::Undefined),
|
||||||
@@ -183,35 +171,48 @@ impl FnEmitter<'_, '_> {
|
|||||||
use OpCode::*;
|
use OpCode::*;
|
||||||
|
|
||||||
let (dst, op) = (instr.dsts[0], instr.op);
|
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])]);
|
||||||
|
let text = |m: Member| matches!(m, Member::Str | Member::Obj(_) | Member::Native(_));
|
||||||
|
|
||||||
self.split(&ops, &mut |fe, ms| {
|
for case in &cases {
|
||||||
let ((l, lm), (r, rm)) = (&ms[0], &ms[1]);
|
self.body.open(case);
|
||||||
let text = |m: Member| matches!(m, Member::Str | Member::Obj(_) | Member::Native(_));
|
let ((l, lm), (r, rm)) = (&case.operands[0], &case.operands[1]);
|
||||||
|
|
||||||
if op == Add && (text(*lm) || text(*rm)) {
|
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 {
|
let (Some(ls), Some(rs)) = (
|
||||||
return fe.throw(NO_CONVERSION);
|
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 {
|
let (Some(ln), Some(rn)) = (to_number(l, *lm), to_number(r, *rm)) else {
|
||||||
return fe.throw(NO_CONVERSION);
|
self.body.throw(NO_CONVERSION);
|
||||||
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Numbers are doubles: C's operators on doubles are JS's, `%` is C's `fmod`, and
|
// 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`)
|
// `**` differs only for NaN and infinite exponents (see `__rt_pow`)
|
||||||
let expr = match op {
|
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})"),
|
Mod => format!("fmod({ln}, {rn})"),
|
||||||
Pow => format!("__rt_pow({ln}, {rn})"),
|
Pow => format!("__rt_pow({ln}, {rn})"),
|
||||||
_ => format!("({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
|
/// `&`, `|`, `^`, `<<`, `>>` and `>>>`. Each operand becomes an int32, as JS's ToInt32
|
||||||
@@ -220,13 +221,17 @@ impl FnEmitter<'_, '_> {
|
|||||||
use OpCode::*;
|
use OpCode::*;
|
||||||
|
|
||||||
let (dst, op) = (instr.dsts[0], instr.op);
|
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| {
|
for case in &cases {
|
||||||
let ((l, lm), (r, rm)) = (&ms[0], &ms[1]);
|
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 {
|
let (Some(a), Some(b)) = (to_int32(l, *lm), to_int32(r, *rm)) else {
|
||||||
return fe.throw(NO_CONVERSION);
|
self.body.throw(NO_CONVERSION);
|
||||||
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let (expr, member) = match op {
|
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
|
/// `-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::*;
|
use OpCode::*;
|
||||||
|
|
||||||
let (dst, op) = (instr.dsts[0], instr.op);
|
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| {
|
for case in &cases {
|
||||||
let (v, m) = &ms[0];
|
self.body.open(case);
|
||||||
let Some(n) = fe.e.to_number(v, *m) else {
|
let (v, m) = &case.operands[0];
|
||||||
return fe.throw(NO_CONVERSION);
|
|
||||||
|
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) {
|
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, Member::Int) => (v.clone(), Member::Int),
|
||||||
(Plus, _) => (n, Member::Float),
|
(Plus, _) => (n, Member::Float),
|
||||||
(Neg, _) => (format!("(-{n})"), Member::Float),
|
(Neg, _) => (format!("(-{n})"), Member::Float),
|
||||||
@@ -272,8 +282,10 @@ impl FnEmitter<'_, '_> {
|
|||||||
_ => (format!("({n} - 1)"), Member::Float),
|
_ => (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
|
/// `v++` and `v--`: the old value, as a number, then the new one
|
||||||
@@ -284,42 +296,56 @@ impl FnEmitter<'_, '_> {
|
|||||||
} else {
|
} else {
|
||||||
"-"
|
"-"
|
||||||
};
|
};
|
||||||
let ops = [self.read(instr.args[0])];
|
let cases = self.program.cases(&[self.get(instr.args[0])]);
|
||||||
|
|
||||||
self.split(&ops, &mut |fe, ms| {
|
for case in &cases {
|
||||||
let (v, m) = &ms[0];
|
self.body.open(case);
|
||||||
|
let (v, m) = &case.operands[0];
|
||||||
|
|
||||||
let Some(n) = fe.e.to_number(v, *m) else {
|
let Some(n) = to_number(v, *m) else {
|
||||||
return fe.throw(NO_CONVERSION);
|
self.body.throw(NO_CONVERSION);
|
||||||
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
fe.set(old, &n, Member::Float);
|
self.set_member(old, &n, Member::Float);
|
||||||
fe.set(new, &format!("({n} {step} 1)"), 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
|
/// `<`, `<=`, `>` and `>=`. Two strings compare as strings, anything else as numbers
|
||||||
fn compare(&mut self, instr: &RegInstr) {
|
fn compare(&mut self, instr: &RegInstr) {
|
||||||
let (dst, op) = (instr.dsts[0], c_op(instr.op));
|
let dst = instr.dsts[0];
|
||||||
let ops = [self.read(instr.args[1]), self.read(instr.args[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| {
|
for case in &cases {
|
||||||
let ((l, lm), (r, rm)) = (&ms[0], &ms[1]);
|
self.body.open(case);
|
||||||
|
let ((l, lm), (r, rm)) = (&case.operands[0], &case.operands[1]);
|
||||||
|
|
||||||
if (lm, rm) == (&Member::Str, &Member::Str) {
|
if (*lm, *rm) == (Member::Str, Member::Str) {
|
||||||
return fe.set(
|
let expr = format!("(__rt_string_cmp({l}, {r}) {op} 0)");
|
||||||
dst,
|
self.set_member(dst, &expr, Member::Bool);
|
||||||
&format!("(__rt_string_cmp({l}, {r}) {op} 0)"),
|
continue;
|
||||||
Member::Bool,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let (Some(ln), Some(rn)) = (fe.e.to_number(l, *lm), fe.e.to_number(r, *rm)) else {
|
let (Some(ln), Some(rn)) = (to_number(l, *lm), to_number(r, *rm)) else {
|
||||||
return fe.throw(NO_CONVERSION);
|
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 `!=`
|
/// `===`, `!==`, `==` and `!=`
|
||||||
@@ -327,202 +353,195 @@ impl FnEmitter<'_, '_> {
|
|||||||
use OpCode::*;
|
use OpCode::*;
|
||||||
|
|
||||||
let (dst, op) = (instr.dsts[0], instr.op);
|
let (dst, op) = (instr.dsts[0], instr.op);
|
||||||
let ops = [self.read(instr.args[1]), self.read(instr.args[0])];
|
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);
|
||||||
|
|
||||||
self.split(&ops, &mut |fe, ms| {
|
for case in &cases {
|
||||||
let ((l, lm), (r, rm)) = (&ms[0], &ms[1]);
|
self.body.open(case);
|
||||||
let nullish = |m: Member| matches!(m, Member::Undefined | Member::Null);
|
let ((l, lm), (r, rm)) = (&case.operands[0], &case.operands[1]);
|
||||||
|
let numbers = (to_number(l, *lm), to_number(r, *rm));
|
||||||
|
|
||||||
let eq = match op {
|
// Undefined and null are equal to each other and to nothing else. Two values of
|
||||||
// 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
|
||||||
// one kind compare as `===` does. A string or a bool against a number compares
|
// numbers
|
||||||
// as numbers
|
let eq = match (op, numbers) {
|
||||||
Eq | Neq => match (*lm, *rm) {
|
(StrictEq | StrictNeq, _) => strict_eq(l, *lm, r, *rm),
|
||||||
_ if nullish(*lm) && nullish(*rm) => "true".into(),
|
_ if nullish(*lm) || nullish(*rm) => (nullish(*lm) && nullish(*rm)).to_string(),
|
||||||
_ if nullish(*lm) || nullish(*rm) => "false".into(),
|
_ if base(*lm) == base(*rm) => strict_eq(l, *lm, r, *rm),
|
||||||
_ if base(*lm) == base(*rm) => strict_eq(l, *lm, r, *rm),
|
(_, (Some(ln), Some(rn))) => format!("({ln} == {rn})"),
|
||||||
_ => match (fe.e.to_number(l, *lm), fe.e.to_number(r, *rm)) {
|
_ => {
|
||||||
(Some(ln), Some(rn)) => format!("({ln} == {rn})"),
|
self.body.throw(NO_CONVERSION);
|
||||||
_ => return fe.throw(NO_CONVERSION),
|
continue;
|
||||||
},
|
}
|
||||||
},
|
|
||||||
_ => strict_eq(l, *lm, r, *rm),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let expr = if matches!(op, Neq | StrictNeq) {
|
self.set_member(dst, &format!("({not}{eq})"), Member::Bool);
|
||||||
format!("(!{eq})")
|
}
|
||||||
} else {
|
|
||||||
eq
|
self.body.close(&cases);
|
||||||
};
|
|
||||||
fe.set(dst, &expr, Member::Bool);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The tests of one value: `!`, `typeof`, and whether it is undefined, null or a fn
|
/// The tests of one value: `!`, `typeof`, and whether it is undefined, null or a function
|
||||||
fn test(&mut self, instr: &RegInstr) {
|
fn test(&mut self, instr: &RegInstr) {
|
||||||
use OpCode::*;
|
use OpCode::*;
|
||||||
|
|
||||||
let (dst, op) = (instr.dsts[0], instr.op);
|
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| {
|
for case in &cases {
|
||||||
let (v, m) = (&ms[0].0, ms[0].1);
|
self.body.open(case);
|
||||||
let known = |b: bool| (b.to_string(), Member::Bool);
|
let (v, m) = (&case.operands[0].0, case.operands[0].1);
|
||||||
|
|
||||||
let (expr, member) = match op {
|
let known = match op {
|
||||||
Lnot => (format!("(!{})", fe.e.truthy(v, m)), Member::Bool),
|
IsUndefined | TypeofIsUndefined => m == Member::Undefined,
|
||||||
Typeof => {
|
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 {
|
let name = match m {
|
||||||
Member::Undefined => "undefined",
|
Member::Undefined => "undefined",
|
||||||
Member::Bool => "boolean",
|
Member::Bool => "boolean",
|
||||||
Member::Int | Member::Float => "number",
|
Member::Int | Member::Float => "number",
|
||||||
Member::Str => "string",
|
Member::Str => "string",
|
||||||
_ if fe.e.is_function(m) => "function",
|
_ if self.program.is_function(m) => "function",
|
||||||
_ => "object",
|
_ => "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 ───────────────────────────────────────
|
// ────────────────────────────────────── Calls ───────────────────────────────────────
|
||||||
|
|
||||||
/// A call. Its operands are the callee's params: the callee (its env), `this`, then the
|
/// A call. Its operands are the callee's params: the callee itself, `this`, then the args
|
||||||
/// args (see regify). Each closure that the callee can be has its own walk (see
|
/// (see regify). Each closure that the callee can be is called by its own case. A callee
|
||||||
/// `TypeCache::find`), so a callee that is a sum is split. A callee that is not a fn
|
/// that is not a function throws
|
||||||
/// throws
|
|
||||||
///
|
|
||||||
/// `new` gives what the constructor returns if that is an object, else the new object
|
|
||||||
/// itself, `this`
|
|
||||||
fn call(&mut self, idx: usize, instr: &RegInstr) {
|
fn call(&mut self, idx: usize, instr: &RegInstr) {
|
||||||
let (cache, fns) = (self.e.cache, self.e.fns);
|
let cases = self.program.cases(&[self.get(instr.args[0])]);
|
||||||
let in_call = Some(InstrRef {
|
|
||||||
fn_idx: cache.walks[self.w].req.fn_idx,
|
|
||||||
instr_idx: idx,
|
|
||||||
});
|
|
||||||
let arg_types: Vec<Type> = instr
|
|
||||||
.args
|
|
||||||
.iter()
|
|
||||||
.map(|slot| self.types[slot].clone())
|
|
||||||
.collect();
|
|
||||||
let dst = instr.dsts[0];
|
|
||||||
let ops = [self.read(instr.args[0])];
|
|
||||||
|
|
||||||
self.split(&ops, &mut |fe, ms| {
|
for case in &cases {
|
||||||
let (callee, member) = (&ms[0].0, ms[0].1);
|
self.body.open(case);
|
||||||
|
let (callee, member) = &case.operands[0];
|
||||||
|
|
||||||
if let Member::Native(idx) = member {
|
match (*member, self.program.class_kind(*member)) {
|
||||||
fe.native_call(idx, instr);
|
(Member::Native(native), _) => self.native_call(native, instr),
|
||||||
return;
|
(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))
|
self.body.close(&cases);
|
||||||
else {
|
|
||||||
fe.throw("not a function");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut types = arg_types.clone();
|
|
||||||
types[0] = Type::Ref(fe.e.layout.classes[class].objs[0]);
|
|
||||||
let w = cache.find(fn_idx, in_call, &types, fns);
|
|
||||||
fe.e.want(w);
|
|
||||||
|
|
||||||
// An arg it does not pass is undefined
|
|
||||||
let args: Vec<String> = (0..fns[fn_idx].arg_count)
|
|
||||||
.map(|i| {
|
|
||||||
let param = super::func::slot_rep(fe.e, w, Operand::Arg(i));
|
|
||||||
|
|
||||||
let (value, rep) = match i {
|
|
||||||
0 => (callee.clone(), Rep::One(member)),
|
|
||||||
_ if i < instr.args.len() => fe.read(instr.args[i]),
|
|
||||||
_ => undefined(),
|
|
||||||
};
|
|
||||||
|
|
||||||
fe.e.convert(&value, rep, param)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let call = format!("w{w}({})", args.join(", "));
|
|
||||||
|
|
||||||
// A callee that never returns gives no value
|
|
||||||
if cache.walks[w].ret == Type::Unknown {
|
|
||||||
fe.line(format!("{call};"));
|
|
||||||
fe.line("abort(); // does not return");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let ret = fe.e.layout.rep(&cache.walks[w].ret);
|
|
||||||
|
|
||||||
if instr.op != OpCode::CallConstructor {
|
|
||||||
fe.assign(dst, &call, ret);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = fe.temp(ret);
|
|
||||||
fe.line(format!("{result} = {call};"));
|
|
||||||
let this = fe.read(instr.args[THIS_ARG as usize]);
|
|
||||||
|
|
||||||
fe.split(&[(result, ret)], &mut |fe, ms| match ms[0].1 {
|
|
||||||
Member::Obj(_) => fe.set(dst, &ms[0].0, ms[0].1),
|
|
||||||
_ => fe.assign(dst, &this.0, this.1),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A call of a native: a C call, with the args only. An int and a float both pass for a
|
/// A call of `callee`, a closure of `class`. Infer walked the closure's JS function for
|
||||||
/// number. Any other arg of the wrong kind throws (see `native::ret_type`)
|
/// 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: 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![];
|
||||||
|
|
||||||
|
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(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let param = program.slot_ctype(walk, Operand::Arg(i));
|
||||||
|
args.push(self.body.convert(&value, ctype, param));
|
||||||
|
}
|
||||||
|
|
||||||
|
let call = format!("js{walk}({})", args.join(", "));
|
||||||
|
let ret = &program.cache.walks[walk].ret;
|
||||||
|
|
||||||
|
// 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 = program.types.of(ret);
|
||||||
|
|
||||||
|
if instr.op != OpCode::CallConstructor {
|
||||||
|
return self.set(dst, &call, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)]);
|
||||||
|
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
fn native_call(&mut self, idx: usize, instr: &RegInstr) {
|
||||||
let native = &NATIVES[idx];
|
let native = &NATIVES[idx];
|
||||||
let dst = instr.dsts[0];
|
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 case in &cases {
|
||||||
for i in 0..native.arg_types.len() {
|
self.body.open(case);
|
||||||
let slot = FIRST_ARG as usize + i;
|
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 instr.args.get(slot) {
|
match args {
|
||||||
Some(&arg) => ops.push(self.read(arg)),
|
Some(args) => {
|
||||||
None => ops.push(undefined()),
|
let call = format!("{}({})", native.name, args.join(", "));
|
||||||
}
|
self.set_member(dst, &call, native_member(&native.ret_type));
|
||||||
}
|
|
||||||
|
|
||||||
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"),
|
|
||||||
}
|
}
|
||||||
|
None => self.body.throw("a native got an arg of the wrong kind"),
|
||||||
}
|
}
|
||||||
|
|
||||||
let call = format!("{}({})", native.name, args.join(", "));
|
|
||||||
fe.set(dst, &call, native_member(&native.ret_type));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The kind of class of an object member
|
|
||||||
pub(super) fn class_kind(&self, member: Member) -> ClassKind {
|
|
||||||
match member {
|
|
||||||
Member::Obj(class) => self.e.layout.classes[class].kind,
|
|
||||||
_ => ClassKind::Obj,
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The C literal of an int
|
self.body.close(&cases);
|
||||||
fn int(n: i64) -> String {
|
|
||||||
if n == i32::MIN as i64 {
|
|
||||||
"INT32_MIN".into()
|
|
||||||
} else {
|
|
||||||
n.to_string()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -547,33 +566,3 @@ fn native_member(ty: &Type) -> Member {
|
|||||||
ty => unreachable!("a native returns {ty:?}"),
|
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!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
+106
-494
@@ -1,515 +1,127 @@
|
|||||||
//! Emits the program as one C file
|
//! 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
|
//! The C file holds only three things: types, statics and functions. So emit has two steps:
|
||||||
//! struct for each class of objects (see `layout`). Then each walk that the program can
|
|
||||||
//! call becomes one C function, which `func` prints from the plan
|
|
||||||
//!
|
//!
|
||||||
//! Every instr is one rule, on the members of its operands' types: an operand of one member
|
//! 1. `Program::new` finds all of them, from the JS functions and what infer found:
|
||||||
//! is used as it is, and an operand that is a sum is split by a `switch` on its tag, with the
|
//! - **Types:** a struct for each class of objects, and a tagged union for each type with
|
||||||
//! rule's C in each case. The result is written into the destination, which converts it:
|
//! several members (see `types`).
|
||||||
//! into a sum, that is a write of the tag and of the union's member
|
//! - **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 instr;
|
||||||
mod layout;
|
mod js;
|
||||||
mod objects;
|
mod objects;
|
||||||
|
mod program;
|
||||||
|
mod types;
|
||||||
|
mod values;
|
||||||
|
|
||||||
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
|
use crate::{flatten::ModuleBindings, infer::TypeCache, libqjs::parse::Atom, regify::RegFn};
|
||||||
use std::fmt::Write;
|
|
||||||
|
|
||||||
use crate::{
|
use program::Program;
|
||||||
flatten::ModuleBindings,
|
use types::{CType, ClassKind};
|
||||||
infer::{Type, TypeCache, module_binding},
|
|
||||||
libqjs::parse::{Atom, AtomRef},
|
|
||||||
regify::{Operand, RegFn},
|
|
||||||
transform::PROTOTYPE_FIELD,
|
|
||||||
};
|
|
||||||
|
|
||||||
use layout::{ClassField, ClassKind, Layout, Member, Rep};
|
|
||||||
|
|
||||||
/// The C of the program, which includes the runtime's `jsrt.h`
|
|
||||||
pub fn emit(fns: &[RegFn], cache: &TypeCache, atoms: &[Atom], bindings: &ModuleBindings) -> String {
|
|
||||||
let mut e = Emitter::new(fns, cache, atoms, bindings);
|
|
||||||
|
|
||||||
// 1. The host's two calls (see lib/rt/jsrt.h): the entry, when the program starts, and the
|
|
||||||
// stdlib's `__complete`, when an IO op completes
|
|
||||||
let start = e.host_start();
|
|
||||||
let complete = e.host_complete();
|
|
||||||
|
|
||||||
// 2. Every fn that a call can reach, and the read fns that they use. Printing a fn can
|
|
||||||
// want more of both
|
|
||||||
loop {
|
|
||||||
if let Some(w) = e.walk_queue.pop_front() {
|
|
||||||
func::emit_walk(&mut e, w);
|
|
||||||
} else if let Some((class, name)) = e.read_queue.pop_front() {
|
|
||||||
objects::emit_read(&mut e, class, name);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. The file
|
|
||||||
e.finish(&start, &complete)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Emitter<'a> {
|
|
||||||
fns: &'a [RegFn],
|
|
||||||
cache: &'a TypeCache,
|
|
||||||
atoms: &'a [Atom],
|
|
||||||
bindings: &'a ModuleBindings,
|
|
||||||
layout: Layout,
|
|
||||||
entry: usize, // the entry's walk
|
|
||||||
globals: BTreeMap<usize, Rep>, // the module bindings, which are C globals: each local of the entry, and its C type
|
|
||||||
|
|
||||||
// What printing wants, and has not printed yet
|
|
||||||
walk_queue: VecDeque<usize>,
|
|
||||||
walks_wanted: HashSet<usize>,
|
|
||||||
read_queue: VecDeque<(usize, AtomRef)>,
|
|
||||||
reads: HashMap<(usize, AtomRef), (usize, Rep)>, // the read fn of a field on a class, and what it gives
|
|
||||||
strings: Vec<String>, // the text of each string literal
|
|
||||||
string_ids: HashMap<String, usize>,
|
|
||||||
convs: HashMap<(Rep, Rep), usize>, // the conversion fn from a sum, by its reps
|
|
||||||
|
|
||||||
// What is printed, in the order that the file needs it
|
|
||||||
protos: String,
|
|
||||||
conv_defs: String,
|
|
||||||
read_defs: String,
|
|
||||||
walk_defs: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Emitter<'a> {
|
|
||||||
fn new(
|
|
||||||
fns: &'a [RegFn],
|
|
||||||
cache: &'a TypeCache,
|
|
||||||
atoms: &'a [Atom],
|
|
||||||
bindings: &'a ModuleBindings,
|
|
||||||
) -> Self {
|
|
||||||
let entry_fn = fns.len() - 1;
|
|
||||||
let mut layout = Layout::new(&cache.memory);
|
|
||||||
|
|
||||||
// The module bindings are C globals, so that any fn can read the stdlib's
|
|
||||||
let mut globals = BTreeMap::new();
|
|
||||||
|
|
||||||
for &local in bindings.values() {
|
|
||||||
let ty = cache.memory.get(module_binding(entry_fn, local));
|
|
||||||
globals.insert(local, layout.rep(ty));
|
|
||||||
}
|
|
||||||
|
|
||||||
Emitter {
|
|
||||||
fns,
|
|
||||||
cache,
|
|
||||||
atoms,
|
|
||||||
bindings,
|
|
||||||
layout,
|
|
||||||
entry: cache.find(entry_fn, None, &[], fns),
|
|
||||||
globals,
|
|
||||||
walk_queue: VecDeque::new(),
|
|
||||||
walks_wanted: HashSet::new(),
|
|
||||||
read_queue: VecDeque::new(),
|
|
||||||
reads: HashMap::new(),
|
|
||||||
strings: vec![],
|
|
||||||
string_ids: HashMap::new(),
|
|
||||||
convs: HashMap::new(),
|
|
||||||
protos: String::new(),
|
|
||||||
conv_defs: String::new(),
|
|
||||||
read_defs: String::new(),
|
|
||||||
walk_defs: String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Print walk `w` as a C fn, unless it is printed or wanted already
|
|
||||||
fn want(&mut self, w: usize) {
|
|
||||||
if self.walks_wanted.insert(w) {
|
|
||||||
self.walk_queue.push_back(w);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ────────────────────────────────────── Host calls ──────────────────────────────────────
|
|
||||||
|
|
||||||
impl Emitter<'_> {
|
|
||||||
/// The call of the entry, with no args: `env` and `this` are undefined
|
|
||||||
fn host_start(&mut self) -> String {
|
|
||||||
self.want(self.entry);
|
|
||||||
let arg_count = self.fns[self.fns.len() - 1].arg_count;
|
|
||||||
let entry = self.entry;
|
|
||||||
let args = self.args(entry, vec![undefined(); arg_count]);
|
|
||||||
format!("w{entry}({})", args.join(", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The call of the stdlib's `__complete` with an op's handle `h`, if the program has it
|
|
||||||
fn host_complete(&mut self) -> String {
|
|
||||||
let Some((complete, Rep::One(Member::Obj(class)))) = self.binding("__complete") else {
|
|
||||||
return String::new();
|
|
||||||
};
|
|
||||||
let ClassKind::Closure(fn_idx) = self.layout.classes[class].kind else {
|
|
||||||
return String::new();
|
|
||||||
};
|
|
||||||
|
|
||||||
let obj = self.layout.classes[class].objs[0];
|
|
||||||
let w = self.cache.find(
|
|
||||||
fn_idx,
|
|
||||||
None,
|
|
||||||
&[Type::Ref(obj), Type::Undefined, Type::Int],
|
|
||||||
self.fns,
|
|
||||||
);
|
|
||||||
self.want(w);
|
|
||||||
|
|
||||||
let args = vec![
|
|
||||||
(complete, Rep::One(Member::Obj(class))),
|
|
||||||
undefined(),
|
|
||||||
("h".into(), Rep::One(Member::Int)),
|
|
||||||
];
|
|
||||||
format!("w{w}({});", self.args(w, args).join(", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The args of a call of walk `w`: each value converted to its param's C type
|
|
||||||
fn args(&mut self, w: usize, values: Vec<(String, Rep)>) -> Vec<String> {
|
|
||||||
let mut args = vec![];
|
|
||||||
|
|
||||||
for (i, (value, rep)) in values.into_iter().enumerate() {
|
|
||||||
let param = func::slot_rep(self, w, Operand::Arg(i));
|
|
||||||
args.push(self.convert(&value, rep, param));
|
|
||||||
}
|
|
||||||
|
|
||||||
args
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The value of the stdlib's module binding `name`, as C, and its C type
|
|
||||||
fn binding(&self, name: &str) -> Option<(String, Rep)> {
|
|
||||||
let local = *self.bindings.get(name)?;
|
|
||||||
Some((format!("(*g{local})"), self.globals[&local]))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether a local of walk `w` is a module binding, which is a C global
|
|
||||||
fn is_global(&self, w: usize, slot: Operand) -> bool {
|
|
||||||
matches!(slot, Operand::Local(local) if w == self.entry && self.globals.contains_key(&local))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The prototype of a primitive or an array, as C, and its C type: the `prototype` of the
|
|
||||||
/// stdlib's class (see `Memory::builtin_proto`)
|
|
||||||
fn builtin_proto(&self, member: Member) -> Option<(String, Rep)> {
|
|
||||||
let class_name = match member {
|
|
||||||
Member::Str => "String",
|
|
||||||
Member::Int | Member::Float => "Number",
|
|
||||||
Member::Bool => "Boolean",
|
|
||||||
Member::Obj(class) if self.layout.classes[class].kind == ClassKind::Array => "Array",
|
|
||||||
_ => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let (class_value, Rep::One(Member::Obj(class))) = self.binding(class_name)? else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
let field = self.layout.field(class, PROTOTYPE_FIELD)?;
|
|
||||||
Some((self.field_access(&class_value, field), field.rep))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ──────────────────────────────────────── Values ────────────────────────────────────────
|
|
||||||
|
|
||||||
impl Emitter<'_> {
|
|
||||||
/// A string literal: a pointer to a static string
|
|
||||||
fn string(&mut self, text: &str) -> String {
|
|
||||||
let next = self.strings.len();
|
|
||||||
let id = *self.string_ids.entry(text.to_string()).or_insert(next);
|
|
||||||
|
|
||||||
if id == next {
|
|
||||||
self.strings.push(text.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
format!("(&str{id})")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn atom_text(&self, atom: AtomRef) -> String {
|
|
||||||
atom.text(self.atoms)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ctype(&self, rep: Rep) -> String {
|
|
||||||
match rep {
|
|
||||||
Rep::One(member) => member_ctype(member),
|
|
||||||
Rep::Sum(id) => format!("s{id}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The C name of a field
|
|
||||||
fn field_name(&self, atom: AtomRef) -> String {
|
|
||||||
let text: String = (self.atom_text(atom).chars().take(24))
|
|
||||||
.map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
match atom {
|
|
||||||
AtomRef::TaggedInt(n) => format!("f_{text}_i{n}"),
|
|
||||||
AtomRef::Predefined(n) => format!("f_{text}_p{n}"),
|
|
||||||
AtomRef::FileLocal(n) => format!("f_{text}_a{n}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A field of `obj`, to read or write. A field that shares a location holds a pointer to it
|
|
||||||
fn field_access(&self, obj: &str, field: &ClassField) -> String {
|
|
||||||
let name = self.field_name(field.name);
|
|
||||||
|
|
||||||
match field.shared {
|
|
||||||
true => format!("(*{obj}->{name})"),
|
|
||||||
false => format!("{obj}->{name}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `expr`, a value of `member`, as a number (a `double`), as JS's ToNumber does. `None`
|
|
||||||
/// for an object: its conversion calls its methods, which is not supported
|
|
||||||
fn to_number(&self, expr: &str, member: Member) -> Option<String> {
|
|
||||||
Some(match member {
|
|
||||||
Member::Undefined => "NAN".into(),
|
|
||||||
Member::Null => "0.0".into(),
|
|
||||||
Member::Bool | Member::Int => format!("(double){expr}"),
|
|
||||||
Member::Float => expr.into(),
|
|
||||||
Member::Str => format!("__rt_string_to_number({expr})"),
|
|
||||||
Member::Obj(_) | Member::Native(_) => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `expr`, a value of `member`, as an int32, as JS's ToInt32 does. `None` for an object
|
|
||||||
fn to_int32(&self, expr: &str, member: Member) -> Option<String> {
|
|
||||||
match member {
|
|
||||||
Member::Int => Some(expr.into()),
|
|
||||||
_ => Some(format!("__rt_to_int32({})", self.to_number(expr, member)?)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `expr`, a value of `member`, as a string, as JS's ToString does. `None` for an object:
|
|
||||||
/// its conversion calls its methods, which is not supported
|
|
||||||
fn to_string(&mut self, expr: &str, member: Member) -> Option<String> {
|
|
||||||
Some(match member {
|
|
||||||
Member::Undefined => self.string("undefined"),
|
|
||||||
Member::Null => self.string("null"),
|
|
||||||
Member::Bool => format!(
|
|
||||||
"({expr} ? {} : {})",
|
|
||||||
self.string("true"),
|
|
||||||
self.string("false")
|
|
||||||
),
|
|
||||||
Member::Int => format!("__rt_number_to_string((double){expr})"),
|
|
||||||
Member::Float => format!("__rt_number_to_string({expr})"),
|
|
||||||
Member::Str => expr.into(),
|
|
||||||
Member::Obj(_) | Member::Native(_) => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether `expr`, a value of `member`, is truthy
|
|
||||||
fn truthy(&self, expr: &str, member: Member) -> String {
|
|
||||||
match member {
|
|
||||||
Member::Undefined | Member::Null => "false".into(),
|
|
||||||
Member::Bool => expr.into(),
|
|
||||||
Member::Int => format!("({expr} != 0)"),
|
|
||||||
Member::Float => format!("({expr} == {expr} && {expr} != 0)"),
|
|
||||||
Member::Str => format!("(__rt_string_length({expr}) != 0)"),
|
|
||||||
Member::Obj(_) | Member::Native(_) => "true".into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether a value of `member` is a fn
|
|
||||||
fn is_function(&self, member: Member) -> bool {
|
|
||||||
match member {
|
|
||||||
Member::Obj(class) => matches!(self.layout.classes[class].kind, ClassKind::Closure(_)),
|
|
||||||
Member::Native(_) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ───────────────────────────────────── Conversions ──────────────────────────────────────
|
|
||||||
|
|
||||||
impl Emitter<'_> {
|
|
||||||
/// `expr`, a value of C type `from`, as a value of C type `to`. Types only grow, so
|
|
||||||
/// `to` holds every member of `from`
|
|
||||||
fn convert(&mut self, expr: &str, from: Rep, to: Rep) -> String {
|
|
||||||
if from == to {
|
|
||||||
return expr.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
match (from, to) {
|
|
||||||
(Rep::One(a), Rep::One(b)) => convert_member(expr, a, b),
|
|
||||||
(Rep::One(a), Rep::Sum(id)) => {
|
|
||||||
let (tag, b) = member_in(a, &self.layout.sums[id]);
|
|
||||||
format!(
|
|
||||||
"(s{id}){{.tag = {tag}, .as.m{tag} = {}}}",
|
|
||||||
convert_member(expr, a, b)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
(Rep::Sum(_), _) => {
|
|
||||||
let next = self.convs.len();
|
|
||||||
let id = *self.convs.entry((from, to)).or_insert(next);
|
|
||||||
|
|
||||||
if id == next {
|
|
||||||
self.print_conv(id, from, to);
|
|
||||||
}
|
|
||||||
|
|
||||||
format!("cv{id}({expr})")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The fn that converts a sum `from` into `to`
|
|
||||||
fn print_conv(&mut self, id: usize, from: Rep, to: Rep) {
|
|
||||||
let (from_c, to_c) = (self.ctype(from), self.ctype(to));
|
|
||||||
let mut body = format!("static {to_c} cv{id}({from_c} v) {{\n switch (v.tag) {{\n");
|
|
||||||
|
|
||||||
for (tag, member) in self.layout.rep_members(from).into_iter().enumerate() {
|
|
||||||
let value = self.convert(&format!("v.as.m{tag}"), Rep::One(member), to);
|
|
||||||
let _ = writeln!(body, " case {tag}: return {value};");
|
|
||||||
}
|
|
||||||
|
|
||||||
body.push_str(" }\n abort();\n}\n\n");
|
|
||||||
let _ = writeln!(self.protos, "static {to_c} cv{id}({from_c} v);");
|
|
||||||
self.conv_defs.push_str(&body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The undefined value, and its C type
|
|
||||||
fn undefined() -> (String, Rep) {
|
|
||||||
(
|
|
||||||
unit_literal(Member::Undefined).into(),
|
|
||||||
Rep::One(Member::Undefined),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn member_ctype(member: Member) -> String {
|
|
||||||
match member {
|
|
||||||
Member::Undefined | Member::Native(_) => "undefined_t".into(),
|
|
||||||
Member::Null => "null_t".into(),
|
|
||||||
Member::Bool => "bool".into(),
|
|
||||||
Member::Int => "int32_t".into(),
|
|
||||||
Member::Float => "double".into(),
|
|
||||||
Member::Str => "string_t".into(),
|
|
||||||
Member::Obj(class) => format!("struct c{class} *"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The literal of a member that has one value
|
|
||||||
fn unit_literal(member: Member) -> &'static str {
|
|
||||||
match member {
|
|
||||||
Member::Undefined | Member::Native(_) => "(undefined_t){0}",
|
|
||||||
Member::Null => "(null_t){0}",
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `expr` of member `a` as member `b`: the same, or an int as a float
|
|
||||||
fn convert_member(expr: &str, a: Member, b: Member) -> String {
|
|
||||||
match (a, b) {
|
|
||||||
_ if a == b => expr.into(),
|
|
||||||
(Member::Int, Member::Float) => format!("(double){expr}"),
|
|
||||||
_ => panic!("emit: a value of {a:?} where {b:?} was inferred"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The tag and member of a sum that holds a value of member `a`: its own, or a float for
|
|
||||||
/// an int
|
|
||||||
fn member_in(a: Member, members: &[Member]) -> (usize, Member) {
|
|
||||||
let find = |m: Member| members.iter().position(|&x| x == m).map(|tag| (tag, m));
|
|
||||||
|
|
||||||
find(a)
|
|
||||||
.or_else(|| (a == Member::Int).then(|| find(Member::Float)).flatten())
|
|
||||||
.unwrap_or_else(|| panic!("emit: a value of {a:?} where {members:?} was inferred"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────── The file ───────────────────────────────────────
|
|
||||||
|
|
||||||
const HEADER: &str = "// Generated by jsrt\n#define JSRT_IMPLEMENTATION\n#include \"jsrt.h\"\n\n#include <math.h>\n#include <stdlib.h>\n\n";
|
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 of the program, which includes the runtime's `jsrt.h`
|
||||||
/// The C file: the types, the statics, the fns, then the host's entry points
|
pub fn emit(fns: &[RegFn], cache: &TypeCache, atoms: &[Atom], bindings: &ModuleBindings) -> String {
|
||||||
fn finish(self, start: &str, complete: &str) -> String {
|
let program = Program::new(fns, cache, atoms, bindings);
|
||||||
let mut c = String::from(HEADER);
|
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);
|
// Types. Each struct is declared first, because a union can hold a pointer to one
|
||||||
self.print_statics(&mut c);
|
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);
|
|
||||||
|
|
||||||
// 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
|
c += "\n";
|
||||||
/// structs' fields
|
|
||||||
fn print_types(&self, c: &mut String) {
|
|
||||||
for id in 0..self.layout.classes.len() {
|
|
||||||
let _ = writeln!(c, "struct c{id};");
|
|
||||||
}
|
|
||||||
|
|
||||||
c.push('\n');
|
for union in unions {
|
||||||
|
c += &union_definition(&program, union);
|
||||||
for (id, members) in self.layout.sums.iter().enumerate() {
|
|
||||||
let mut fields = String::new();
|
|
||||||
|
|
||||||
for (tag, &member) in members.iter().enumerate() {
|
|
||||||
let _ = write!(fields, " {} m{tag};", member_ctype(member));
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = writeln!(
|
|
||||||
c,
|
|
||||||
"typedef struct {{ uint8_t tag; union {{{fields} }} as; }} s{id};"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
c.push('\n');
|
|
||||||
|
|
||||||
for (id, class) in self.layout.classes.iter().enumerate() {
|
|
||||||
let _ = writeln!(c, "struct c{id} {{");
|
|
||||||
|
|
||||||
if class.kind == ClassKind::Array {
|
|
||||||
let _ = writeln!(
|
|
||||||
c,
|
|
||||||
" int32_t length, cap;\n {} *items;",
|
|
||||||
self.ctype(class.elems)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for field in &class.fields {
|
|
||||||
let (ctype, name) = (self.ctype(field.rep), self.field_name(field.name));
|
|
||||||
let pointer = if field.shared { "*" } else { "" };
|
|
||||||
let _ = writeln!(c, " {ctype} {pointer}{name};");
|
|
||||||
|
|
||||||
if !field.defined {
|
|
||||||
let _ = writeln!(c, " bool has_{name};");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// C has no empty structs
|
|
||||||
if class.kind != ClassKind::Array && class.fields.is_empty() {
|
|
||||||
c.push_str(" char _;\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
c.push_str("};\n\n");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The string literals, whose UTF-16 units are in the C, and the module bindings
|
c += "\n";
|
||||||
fn print_statics(&self, c: &mut String) {
|
|
||||||
for (id, text) in self.strings.iter().enumerate() {
|
|
||||||
let units: Vec<String> = text.encode_utf16().map(|u| u.to_string()).collect();
|
|
||||||
let _ = writeln!(
|
|
||||||
c,
|
|
||||||
"static struct jsrt_string str{id} = {{{}, {{{}}}}};",
|
|
||||||
units.len(),
|
|
||||||
units.join(", ")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (local, rep) in &self.globals {
|
for class in classes {
|
||||||
let _ = writeln!(c, "static {} *g{local};", self.ctype(*rep));
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
c
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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();
|
||||||
|
|
||||||
|
format!("typedef struct {{ uint8_t tag; union {{{members} }} as; }} u{id};\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
c += &format!(
|
||||||
|
" int32_t length, cap;\n {} *items;\n",
|
||||||
|
class.elems.c_name()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for field in &class.fields {
|
||||||
|
let name = program.field_name(field.name);
|
||||||
|
let pointer = if field.shared { "*" } else { "" };
|
||||||
|
c += &format!(" {} {pointer}{name};\n", field.ctype.c_name());
|
||||||
|
|
||||||
|
if !field.defined {
|
||||||
|
c += &format!(" bool has_{name};\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// C has no empty structs
|
||||||
|
if class.kind != ClassKind::Array && class.fields.is_empty() {
|
||||||
|
c += " char _;\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
c + "};\n\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 (length, units) = (units.len(), units.join(", "));
|
||||||
|
format!("static struct jsrt_string str{id} = {{{length}, {{{units}}}}};\n")
|
||||||
}
|
}
|
||||||
|
|||||||
+196
-303
@@ -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
|
//! A field is a member of its class's struct. If every object of the class has the field from
|
||||||
//! is read directly. Any other field can be absent: it has a `has_` flag, and a read of it
|
//! the start, the field is used directly. Else the struct has a `has_` flag for the field, and
|
||||||
//! is a read fn, which reads its own field if it has one, else reads on its prototype. A
|
//! getting the field calls its lookup (see `functions`). A field that shares a location (see
|
||||||
//! field that shares a location (see `define_field_ref`) holds a pointer to it
|
//! `define_field_ref`) holds a pointer to it.
|
||||||
|
|
||||||
use std::fmt::Write as _;
|
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Emitter,
|
js::JsFunction,
|
||||||
func::{Code, FnEmitter, Writer},
|
types::{CType, ClassKind, Member},
|
||||||
layout::{ClassKind, Member, Rep},
|
values::{as_index, in_range, undefined},
|
||||||
undefined,
|
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
infer::{LENGTH_FIELD, Type, join},
|
infer::LENGTH_FIELD,
|
||||||
libqjs::parse::{AtomRef, opcodes::OpCode},
|
libqjs::parse::opcodes::OpCode,
|
||||||
regify::{Operand, RegInstr},
|
regify::{Operand, RegInstr},
|
||||||
transform::PROTO_FIELD,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ──────────────────────────────────────── Fields ────────────────────────────────────────
|
// ──────────────────────────────────────── Fields ────────────────────────────────────────
|
||||||
|
|
||||||
impl FnEmitter<'_, '_> {
|
impl JsFunction<'_> {
|
||||||
/// `[a, b, ...]`: the elements are the operands, last first
|
/// `[a, b, ...]`. The operands are the elements, last first
|
||||||
pub(super) fn array_from(&mut self, idx: usize, instr: &RegInstr) {
|
pub(super) fn array_from(&mut self, idx: usize, instr: &RegInstr) {
|
||||||
let class = self.class_here(idx);
|
let class = self.class_here(idx);
|
||||||
let elems = self.e.layout.classes[class].elems;
|
let elems = self.program.types.classes[class].elems;
|
||||||
let array = self.temp(Rep::One(Member::Obj(class)));
|
let array = self.body.temp(CType::One(Member::Obj(class)));
|
||||||
let n = instr.args.len();
|
let n = instr.args.len();
|
||||||
|
|
||||||
self.line(format!("{array} = __rt_alloc(sizeof(struct c{class}));"));
|
self.body
|
||||||
self.line(format!(
|
.line(format!("{array} = __rt_alloc(sizeof(struct c{class}));"));
|
||||||
|
self.body.line(format!(
|
||||||
"{array}->items = __rt_grow(NULL, &{array}->cap, {n}, sizeof *{array}->items);"
|
"{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() {
|
for (i, &slot) in instr.args.iter().rev().enumerate() {
|
||||||
let (value, rep) = self.read(slot);
|
let (value, ctype) = self.get(slot);
|
||||||
let value = self.e.convert(&value, rep, elems);
|
self.body
|
||||||
self.line(format!("{array}->items[{i}] = {value};"));
|
.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
|
/// `o.f` and `o.length`. A string has its own length, and so does an array. A primitive
|
||||||
/// and null throw. A primitive reads its kind's prototype (see `Emitter::builtin_proto`)
|
/// 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) {
|
pub(super) fn get_field(&mut self, instr: &RegInstr) {
|
||||||
let name = match instr.op {
|
let field = match instr.op {
|
||||||
OpCode::GetLength => LENGTH_FIELD,
|
OpCode::GetLength => LENGTH_FIELD,
|
||||||
_ => instr.imm.atom(),
|
_ => instr.imm.atom(),
|
||||||
};
|
};
|
||||||
let dst = instr.dsts[0];
|
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| {
|
for case in &cases {
|
||||||
let (obj, member) = (&ms[0].0, ms[0].1);
|
self.body.open(case);
|
||||||
|
let (obj, member) = (&case.operands[0].0, case.operands[0].1);
|
||||||
|
|
||||||
match (member, fe.class_kind(member)) {
|
match (member, self.program.field_class(member, field)) {
|
||||||
(Member::Str, _) if name == LENGTH_FIELD => {
|
(Member::Obj(_), Some(class)) => {
|
||||||
fe.set(dst, &format!("__rt_string_length({obj})"), Member::Int)
|
let (value, ctype) = self.program.field_value(class, field, obj);
|
||||||
|
self.set(dst, &value, ctype);
|
||||||
}
|
}
|
||||||
(Member::Obj(_), ClassKind::Array) if name == LENGTH_FIELD => {
|
(_, Some(class)) => {
|
||||||
fe.set(dst, &format!("{obj}->length"), Member::Int)
|
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), _) => {
|
(Member::Str, None) if field == LENGTH_FIELD => {
|
||||||
let (value, rep) = read_field(fe.e, class, name, obj);
|
let length = format!("__rt_string_length({obj})");
|
||||||
fe.assign(dst, &value, rep);
|
self.set_member(dst, &length, Member::Int);
|
||||||
|
}
|
||||||
|
(Member::Obj(_), None) => {
|
||||||
|
self.set_member(dst, &format!("{obj}->length"), Member::Int)
|
||||||
}
|
}
|
||||||
(Member::Undefined | Member::Null, _) => {
|
(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"),
|
(Member::Native(_), _) => self.body.throw("a native function has no fields"),
|
||||||
_ => match fe.e.builtin_proto(member) {
|
_ => self.body.throw("fields of primitives need the stdlib"),
|
||||||
Some((proto, Rep::One(Member::Obj(proto_class)))) => {
|
|
||||||
let (value, rep) = read_field(fe.e, proto_class, name, &proto);
|
|
||||||
fe.assign(dst, &value, rep);
|
|
||||||
}
|
|
||||||
_ => fe.throw("fields of primitives need the stdlib"),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
self.body.close(&cases);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `o.f = v`, and a field made in an object literal. A primitive has no fields to write,
|
/// `o.f = v`, and a field in an object literal. Setting an array's `length` sets its
|
||||||
/// and undefined and null throw. A write of an array's `length` sets its length
|
/// length. A primitive has no fields to set, and undefined and null throw
|
||||||
pub(super) fn put_field(&mut self, instr: &RegInstr) {
|
pub(super) fn put_field(&mut self, instr: &RegInstr) {
|
||||||
let name = instr.imm.atom();
|
let field = instr.imm.atom();
|
||||||
let value = self.read(instr.args[0]);
|
let (value, from) = self.get(instr.args[0]);
|
||||||
let ops = [self.read(instr.args[1])];
|
let cases = self.program.cases(&[self.get(instr.args[1])]);
|
||||||
|
|
||||||
self.split(&ops, &mut |fe, ms| {
|
for case in &cases {
|
||||||
let (obj, member) = (&ms[0].0, ms[0].1);
|
self.body.open(case);
|
||||||
|
let (obj, member) = (&case.operands[0].0, case.operands[0].1);
|
||||||
|
|
||||||
match (member, fe.class_kind(member)) {
|
match (member, self.program.class_kind(member)) {
|
||||||
(Member::Obj(_), ClassKind::Array) if name == LENGTH_FIELD => {
|
(Member::Obj(_), ClassKind::Array) if field == LENGTH_FIELD => {
|
||||||
fe.set_length(obj, &value);
|
self.set_length(obj, &value, from)
|
||||||
}
|
}
|
||||||
(Member::Obj(class), _) => {
|
(Member::Obj(class), _) => {
|
||||||
let field = fe.e.layout.field(class, name).unwrap().clone();
|
let f = self.program.types.field(class, field).unwrap();
|
||||||
let target = fe.e.field_access(obj, &field);
|
let target = self.program.field_access(obj, f);
|
||||||
let v = fe.e.convert(&value.0, value.1, field.rep);
|
self.body
|
||||||
fe.line(format!("{target} = {v};"));
|
.put(&format!("{target} = "), &value, from, f.ctype);
|
||||||
|
|
||||||
if !field.defined {
|
if !f.defined {
|
||||||
fe.line(format!("{obj}->has_{} = true;", fe.e.field_name(name)));
|
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"),
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
self.body.close(&cases);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `a.length = n`: the array gets `n` elements, the new ones holes. `n` must be an array
|
/// `a.length = n`: the array gets `n` elements, and the new ones are holes. `n` must be a
|
||||||
/// index: a whole number from 0, else it throws, as JS's RangeError
|
/// whole number from 0, else it throws, as JS's RangeError does
|
||||||
fn set_length(&mut self, obj: &str, value: &(String, Rep)) {
|
fn set_length(&mut self, obj: &str, value: &str, from: CType) {
|
||||||
self.split(std::slice::from_ref(value), &mut |fe, ms| {
|
let cases = self.program.cases(&[(value.to_string(), from)]);
|
||||||
let (n, member) = (&ms[0].0, ms[0].1);
|
|
||||||
|
for case in &cases {
|
||||||
|
self.body.open(case);
|
||||||
|
let (n, member) = (&case.operands[0].0, case.operands[0].1);
|
||||||
|
|
||||||
let length = match member {
|
let length = match member {
|
||||||
Member::Int => n.clone(),
|
Member::Int => n.clone(),
|
||||||
Member::Float => format!("(int32_t){n}"),
|
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");
|
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\");"));
|
self.body.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);"));
|
self.body.line(format!("{obj}->items = __rt_grow({obj}->items, &{obj}->cap, {length}, sizeof *{obj}->items);"));
|
||||||
fe.line(format!("{obj}->length = {length};"));
|
self.body.line(format!("{obj}->length = {length};"));
|
||||||
});
|
}
|
||||||
|
|
||||||
|
self.body.close(&cases);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `define_field_ref`: the field becomes the location that regify gives as the first
|
/// `define_field_ref`: the field becomes a pointer to the cell of the location in the
|
||||||
/// operand. That is a captured binding, whose C local points to its cell, or the reg of
|
/// first operand. That location is a shared local or arg, whose C local points to its
|
||||||
/// an object whose field of the same name points to one (see regify/walk.rs)
|
/// 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) {
|
pub(super) fn share_field(&mut self, instr: &RegInstr) {
|
||||||
let name = instr.imm.atom();
|
let name = self.program.field_name(instr.imm.atom());
|
||||||
let c_name = self.e.field_name(name);
|
|
||||||
let location = instr.args[0];
|
let location = instr.args[0];
|
||||||
let ops = [self.read(instr.args[1])];
|
|
||||||
|
|
||||||
self.split(&ops, &mut |fe, ms| {
|
let cell = match location {
|
||||||
let obj = &ms[0].0;
|
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 {
|
let cases = self.program.cases(&[self.get(instr.args[1])]);
|
||||||
Operand::Reg(_) => {
|
|
||||||
let holder = fe.read(location);
|
|
||||||
|
|
||||||
fe.split(&[holder], &mut |fe, ms| match ms[0].1 {
|
for case in &cases {
|
||||||
Member::Obj(_) => {
|
self.body.open(case);
|
||||||
fe.line(format!("{obj}->{c_name} = {}->{c_name};", ms[0].0))
|
self.body
|
||||||
}
|
.line(format!("{}->{name} = {cell};", case.operands[0].0));
|
||||||
_ => unreachable!(),
|
}
|
||||||
});
|
|
||||||
}
|
self.body.close(&cases);
|
||||||
slot => {
|
|
||||||
let cell = fe.name(slot);
|
|
||||||
fe.line(format!("{obj}->{c_name} = {cell};"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `close_field x`: the field, which shares a location, gets a new cell with its value.
|
/// `close_field x`: the field, which points to a cell, gets a new cell with the same
|
||||||
/// Closures that shared the old cell keep it (see transform/closure.rs)
|
/// value. Closures that point to the old cell keep it (see transform/closure.rs)
|
||||||
pub(super) fn close_field(&mut self, instr: &RegInstr) {
|
pub(super) fn close_field(&mut self, instr: &RegInstr) {
|
||||||
let name = instr.imm.atom();
|
let field = instr.imm.atom();
|
||||||
let ops = [self.read(instr.args[0])];
|
let cases = self.program.cases(&[self.get(instr.args[0])]);
|
||||||
|
|
||||||
self.split(&ops, &mut |fe, ms| {
|
for case in &cases {
|
||||||
let (obj, member) = (&ms[0].0, ms[0].1);
|
self.body.open(case);
|
||||||
let Member::Obj(class) = member else { unreachable!("a capture is held by an object") };
|
let (obj, member) = (&case.operands[0].0, case.operands[0].1);
|
||||||
let field = fe.e.layout.field(class, name).unwrap().clone();
|
let Member::Obj(class) = member else {
|
||||||
assert!(field.shared, "close_field of a field that shares no location");
|
unreachable!("a shared location is held by an object")
|
||||||
|
};
|
||||||
|
|
||||||
let (ctype, c_name) = (fe.e.ctype(field.rep), fe.e.field_name(name));
|
let f = self.program.types.field(class, field).unwrap();
|
||||||
fe.line(format!("{{ {ctype} *cell = __rt_alloc(sizeof({ctype})); *cell = *{obj}->{c_name}; {obj}->{c_name} = cell; }}"));
|
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 ───────────────────────────────────────
|
// ─────────────────────────────────────── Elements ───────────────────────────────────────
|
||||||
|
|
||||||
impl FnEmitter<'_, '_> {
|
impl JsFunction<'_> {
|
||||||
/// `a[i]` on an array, and `s[i]` on a string. An index that is not in it gives
|
/// `a[i]` on an array, and `s[i]` on a string. An index out of range gives undefined.
|
||||||
/// undefined. Undefined and null throw
|
/// Undefined and null throw
|
||||||
pub(super) fn get_elem(&mut self, instr: &RegInstr) {
|
pub(super) fn get_elem(&mut self, instr: &RegInstr) {
|
||||||
let dst = instr.dsts[0];
|
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| {
|
for case in &cases {
|
||||||
let ((obj, om), (index, im)) = (&ms[0], &ms[1]);
|
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)) {
|
let (length, item, ctype) = match (*obj_member, self.program.class_kind(*obj_member)) {
|
||||||
(Member::Obj(class), ClassKind::Array) => {
|
(Member::Obj(class), ClassKind::Array) => (
|
||||||
let elems = fe.e.layout.classes[class].elems;
|
format!("{obj}->length"),
|
||||||
(
|
format!("{obj}->items[{at}]"),
|
||||||
format!("{obj}->length"),
|
self.program.types.classes[class].elems,
|
||||||
format!("{obj}->items[{}]", as_index(index, *im)),
|
),
|
||||||
elems,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
(Member::Str, _) => (
|
(Member::Str, _) => (
|
||||||
format!("__rt_string_length({obj})"),
|
format!("__rt_string_length({obj})"),
|
||||||
format!("__rt_string_at({obj}, {})", as_index(index, *im)),
|
format!("__rt_string_at({obj}, {at})"),
|
||||||
Rep::One(Member::Str),
|
CType::One(Member::Str),
|
||||||
),
|
),
|
||||||
(Member::Undefined | Member::Null, _) => {
|
(Member::Undefined | Member::Null, _) => {
|
||||||
fe.throw("cannot read an element of undefined or null");
|
self.body
|
||||||
return;
|
.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)));
|
let (none, none_ctype) = undefined();
|
||||||
fe.indent(1);
|
let check = in_range(index, *index_member, &length);
|
||||||
fe.assign(dst, &item, rep);
|
self.body.line(format!("if ({check}) {{"));
|
||||||
fe.indent(-1);
|
self.body.indent(1);
|
||||||
fe.line("} else {");
|
self.set(dst, &item, ctype);
|
||||||
fe.indent(1);
|
self.body.indent(-1);
|
||||||
let (value, rep) = undefined();
|
self.body.line("} else {");
|
||||||
fe.assign(dst, &value, rep);
|
self.body.indent(1);
|
||||||
fe.indent(-1);
|
self.set(dst, &none, none_ctype);
|
||||||
fe.line("}");
|
self.body.indent(-1);
|
||||||
});
|
self.body.line("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.body.close(&cases);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `a[i] = v` on an array, which grows to hold `i`. A write to a string, or to undefined
|
/// `a[i] = v` on an array, which grows to hold `i`. Setting an element of a string, of
|
||||||
/// or null, throws, and so does an index that is not an array index, which would be a
|
/// undefined or of null throws. So does an index that is not a whole number from 0,
|
||||||
/// named field
|
/// because it would be a named field
|
||||||
pub(super) fn put_elem(&mut self, instr: &RegInstr) {
|
pub(super) fn put_elem(&mut self, instr: &RegInstr) {
|
||||||
let value = self.read(instr.args[0]);
|
let (value, from) = self.get(instr.args[0]);
|
||||||
let ops = [self.read(instr.args[2]), self.read(instr.args[1])];
|
let operands = [self.get(instr.args[2]), self.get(instr.args[1])];
|
||||||
|
let cases = self.program.cases(&operands);
|
||||||
|
|
||||||
self.split(&ops, &mut |fe, ms| {
|
for case in &cases {
|
||||||
let ((obj, om), (index, im)) = (&ms[0], &ms[1]);
|
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 {
|
let (Member::Obj(class), ClassKind::Array) =
|
||||||
fe.throw("cannot set an element of a value that is not an array");
|
(*obj_member, self.program.class_kind(*obj_member))
|
||||||
return;
|
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 elems = self.program.types.classes[class].elems;
|
||||||
let i = as_index(index, *im);
|
let at = as_index(index, *index_member);
|
||||||
let v = fe.e.convert(&value.0, value.1, elems);
|
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")));
|
self.body.line(format!(
|
||||||
fe.line(format!("if ({i} >= {obj}->length) {{"));
|
"if (!({check})) __rt_type_error(\"an array index must be a whole number from 0\");"
|
||||||
fe.indent(1);
|
));
|
||||||
fe.line(format!("{obj}->items = __rt_grow({obj}->items, &{obj}->cap, {i} + 1, sizeof *{obj}->items);"));
|
self.body.line(format!("if ({at} >= {obj}->length) {{"));
|
||||||
fe.line(format!("{obj}->length = {i} + 1;"));
|
self.body.indent(1);
|
||||||
fe.indent(-1);
|
self.body.line(format!("{obj}->items = __rt_grow({obj}->items, &{obj}->cap, {at} + 1, sizeof *{obj}->items);"));
|
||||||
fe.line("}");
|
self.body.line(format!("{obj}->length = {at} + 1;"));
|
||||||
fe.line(format!("{obj}->items[{i}] = {v};"));
|
self.body.indent(-1);
|
||||||
});
|
self.body.line("}");
|
||||||
}
|
self.body
|
||||||
}
|
.put(&format!("{obj}->items[{at}] = "), &value, from, elems);
|
||||||
|
|
||||||
/// 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)
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.body.close(&cases);
|
||||||
}
|
}
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
//! Infer gives each value a type, which can have several members: for example, a value that
|
||||||
//! type, and a type with several is a tagged union of them (a sum). Objects are grouped into
|
//! is a number or undefined. In C, each member is one C type (see `Member`). A type with one
|
||||||
//! layout classes, one C struct each: two objects share a class if they have the same
|
//! member is that C type. A type with several members is a tagged union: a tag that says
|
||||||
//! fields, and each field has the same C type. A field can point to an object of another
|
//! which member the value is, and a C union of the members.
|
||||||
//! class, so the classes are found by refinement: start from the field names, then split
|
|
||||||
//! every class whose objects disagree, until nothing splits
|
|
||||||
//!
|
//!
|
||||||
//! A closure is a class of its own. A call picks its C function by the closure's address
|
//! Objects are put in classes, and each class is one C struct. Two objects are in the same
|
||||||
//! (see `TypeCache::find`), so two closures must never look alike in C
|
//! 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 std::collections::HashMap;
|
||||||
|
|
||||||
use crate::infer::{Addr, Memory, ObjAddr, Shape, Type, join};
|
use crate::infer::{Addr, Memory, ObjAddr, Shape, Type, join};
|
||||||
use crate::libqjs::parse::AtomRef;
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub enum Member {
|
pub enum Member {
|
||||||
Undefined,
|
Undefined,
|
||||||
@@ -25,74 +29,101 @@ pub enum Member {
|
|||||||
Float,
|
Float,
|
||||||
Str,
|
Str,
|
||||||
Obj(usize), // a pointer to an object of this class
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub enum Rep {
|
pub enum CType {
|
||||||
One(Member),
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum ClassKind {
|
pub enum ClassKind {
|
||||||
Obj,
|
Obj,
|
||||||
Array,
|
Array,
|
||||||
Closure(usize), // it runs this fn
|
Closure(usize), // a closure of this JS function
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A field of the objects of a class
|
/// A field of the objects of a class
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ClassField {
|
pub struct ClassField {
|
||||||
pub name: AtomRef,
|
pub name: AtomRef,
|
||||||
pub rep: Rep,
|
pub ctype: CType,
|
||||||
pub shared: bool, // it shares another location: the struct holds a pointer to it
|
pub shared: bool, // the field shares another location, so the struct holds a pointer to it
|
||||||
pub defined: bool, // every object has it, so it needs no `has_` flag
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct Class {
|
pub struct Class {
|
||||||
pub kind: ClassKind,
|
pub kind: ClassKind,
|
||||||
pub objs: Vec<ObjAddr>,
|
pub objs: Vec<ObjAddr>,
|
||||||
pub fields: Vec<ClassField>, // sorted by name
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct Layout {
|
pub struct Types {
|
||||||
pub classes: Vec<Class>,
|
pub classes: Vec<Class>,
|
||||||
class_of: HashMap<ObjAddr, usize>,
|
class_of: HashMap<ObjAddr, usize>,
|
||||||
pub sums: Vec<Vec<Member>>, // the members of each sum, sorted. A member's tag is its index
|
pub unions: Vec<Vec<Member>>, // the members of each union, sorted. A member's tag is its index
|
||||||
sum_ids: HashMap<Vec<Member>, usize>,
|
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 {
|
pub fn new(memory: &Memory) -> Self {
|
||||||
let class_of = refine(memory);
|
let mut types = Types {
|
||||||
let mut layout = Layout {
|
|
||||||
classes: vec![],
|
classes: vec![],
|
||||||
class_of,
|
class_of: refine(memory),
|
||||||
sums: vec![],
|
unions: vec![],
|
||||||
sum_ids: HashMap::new(),
|
union_ids: HashMap::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut objs: Vec<ObjAddr> = memory.objects().map(|(obj, _)| obj).collect();
|
let mut objs: Vec<ObjAddr> = memory.objects().map(|(obj, _)| obj).collect();
|
||||||
objs.sort_unstable();
|
objs.sort_unstable();
|
||||||
|
|
||||||
let count = layout.class_of.values().max().map_or(0, |&n| n + 1);
|
let count = types.class_of.values().max().map_or(0, |&n| n + 1);
|
||||||
let mut members: Vec<Vec<ObjAddr>> = vec![vec![]; count];
|
let mut objs_of_class: Vec<Vec<ObjAddr>> = vec![vec![]; count];
|
||||||
|
|
||||||
for obj in objs {
|
for obj in objs {
|
||||||
members[layout.class_of[&obj]].push(obj);
|
objs_of_class[types.class_of[&obj]].push(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
for objs in members {
|
for objs in objs_of_class {
|
||||||
let class = layout.make_class(memory, objs);
|
let class = types.make_class(memory, objs);
|
||||||
layout.classes.push(class);
|
types.classes.push(class);
|
||||||
}
|
}
|
||||||
|
|
||||||
layout
|
types
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The class of the objects in `objs`, which refinement put together
|
/// The class of the objects in `objs`, which refinement put together
|
||||||
@@ -105,7 +136,8 @@ impl Layout {
|
|||||||
_ => ClassKind::Obj,
|
_ => 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 {
|
let fields = match kind {
|
||||||
ClassKind::Array => vec![],
|
ClassKind::Array => vec![],
|
||||||
_ => (memory.fields(first).iter())
|
_ => (memory.fields(first).iter())
|
||||||
@@ -118,7 +150,7 @@ impl Layout {
|
|||||||
|
|
||||||
ClassField {
|
ClassField {
|
||||||
name,
|
name,
|
||||||
rep: self.rep(&ty),
|
ctype: self.add(&ty),
|
||||||
shared: field.addr != Addr::Field(first, name),
|
shared: field.addr != Addr::Field(first, name),
|
||||||
defined: field.defined,
|
defined: field.defined,
|
||||||
}
|
}
|
||||||
@@ -128,12 +160,11 @@ impl Layout {
|
|||||||
|
|
||||||
let elems = match kind {
|
let elems = match kind {
|
||||||
ClassKind::Array => {
|
ClassKind::Array => {
|
||||||
let ty = objs
|
let elems = objs.iter().map(|&obj| memory.elems(obj));
|
||||||
.iter()
|
let ty = elems.fold(Type::Unknown, |all, ty| join(&all, ty));
|
||||||
.fold(Type::Unknown, |all, &obj| join(&all, memory.elems(obj)));
|
self.add(&ty)
|
||||||
self.rep(&ty)
|
|
||||||
}
|
}
|
||||||
_ => Rep::One(Member::Undefined),
|
_ => CType::One(Member::Undefined),
|
||||||
};
|
};
|
||||||
|
|
||||||
Class {
|
Class {
|
||||||
@@ -152,35 +183,49 @@ impl Layout {
|
|||||||
self.classes[class].fields.iter().find(|f| f.name == name)
|
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
|
/// The members of a value of type `ty`. A location that no value reached holds undefined
|
||||||
/// undefined
|
pub fn type_members(&self, ty: &Type) -> Vec<Member> {
|
||||||
pub fn members(&self, ty: &Type) -> Vec<Member> {
|
|
||||||
members_with(ty, &self.class_of)
|
members_with(ty, &self.class_of)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What a value of type `ty` is in C
|
/// The C type of a value of type `ty`. If it is a union that is not in the list yet, it
|
||||||
pub fn rep(&mut self, ty: &Type) -> Rep {
|
/// is added
|
||||||
let members = self.members(ty);
|
pub fn add(&mut self, ty: &Type) -> CType {
|
||||||
|
let members = self.type_members(ty);
|
||||||
|
|
||||||
if let [member] = members[..] {
|
if let [member] = members[..] {
|
||||||
return Rep::One(member);
|
return CType::One(member);
|
||||||
}
|
}
|
||||||
|
|
||||||
let next = self.sums.len();
|
let next = self.unions.len();
|
||||||
let id = *self.sum_ids.entry(members.clone()).or_insert(next);
|
let id = *self.union_ids.entry(members.clone()).or_insert(next);
|
||||||
|
|
||||||
if id == 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`
|
/// The C type of a value of type `ty`. `Program::new` added every union first
|
||||||
pub fn rep_members(&self, rep: Rep) -> Vec<Member> {
|
pub fn of(&self, ty: &Type) -> CType {
|
||||||
match rep {
|
let members = self.type_members(ty);
|
||||||
Rep::One(member) => vec![member],
|
|
||||||
Rep::Sum(id) => self.sums[id].clone(),
|
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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user