Mid rewrite

This commit is contained in:
UditDey
2026-10-13 11:52:02 +00:00
parent 2f21347088
commit d0c2f291e9
24 changed files with 752 additions and 3527 deletions
-132
View File
@@ -1,132 +0,0 @@
//! Calls of fns, natives and proto links
use super::*;
impl<'a> Emitter<'a> {
/// A call from `place`. The callee's cell tells which code runs, and the
/// callee is its own env. A callee that is not a fn throws
pub(super) fn call(&mut self, res: &InferResult, place: Place, instr: &RegInstr, out: &mut String) {
let arg_types: Vec<Type> = instr.args.iter().map(|&a| res.val_types[a].clone()).collect();
let operands: Vec<(String, Rep)> = (instr.args.iter().zip(&arg_types))
.map(|(a, ty)| (format!("v{a}"), self.rep(ty)))
.collect();
let dst = (instr.dsts[0], self.rep(&res.val_types[instr.dsts[0]]));
self.dispatch(&operands[0].0, &arg_types[0], "jsrt_type_error();", out, |e, member, callee, rep| {
let Type::Obj(cell) = member else {
return None;
};
let mut arg_types = arg_types.clone();
let mut operands = operands.clone();
arg_types[0] = member.clone();
operands[0] = (callee.into(), rep);
let (call, ret) = e.call_code(cell.code()?, place, &arg_types, &operands);
Some(e.call_result(instr.op, &call, &ret, &operands[THIS_ARG as usize], dst))
});
}
/// The statement that runs `call`, which gives a value of type `ret`, and
/// puts the call's result in `dst` (its value, and its rep). `new` gives
/// what the constructor returns if that is an object, else the new object
/// itself, `this` (see infer)
pub(super) fn call_result(&self, op: OpCode, call: &str, ret: &Type, this: &(String, Rep), dst: (usize, Rep)) -> String {
let (dst, dst_rep) = (format!("v{}", dst.0), dst.1);
let ret_rep = self.rep(ret);
let members = ret.members();
let objects = members.iter().filter(|m| matches!(m, Type::Obj(_))).count();
let this = self.conv(&this.0, this.1, dst_rep);
match op {
_ if *ret == Type::Unknown => format!("{call};"),
OpCode::CallConstructor if objects == 0 => format!("{call}; {dst} = {this};"),
OpCode::CallConstructor if objects < members.len() => {
let is_object = match self.tagged("r", ret_rep) {
Some(a) => format!("__any_is_object({a})"),
None => format!("!{}", self.nullish("r", ret)),
};
let value = self.conv("r", ret_rep, dst_rep);
format!("{{ {} r = {call}; {dst} = {is_object} ? {value} : {this}; }}", self.c_type(ret_rep))
}
_ => format!("{dst} = {};", self.conv(call, ret_rep, dst_rep)),
}
}
/// The C call of `code` with these operands (the callee, `this`, then the
/// args, each with its rep) whose types are `arg_types`, and the type it
/// returns
pub(super) fn call_code(&mut self, code: Code, place: Place, arg_types: &[Type], operands: &[(String, Rep)]) -> (String, Type) {
// An operand as a value of rep `to`. One that the call does not pass is
// undefined
let operand = |e: &Self, i: usize, to: Rep| match operands.get(i) {
Some((value, rep)) => e.conv(value, *rep, to),
None => e.conv("(undefined_t){0}", Rep::Undefined, to),
};
match code {
Code::Fn(fn_id) => {
let w = self.cache.find(fn_id, place, arg_types, self.fns);
self.use_walk(w);
let walk = &self.cache.walks[w];
let args: Vec<String> = (walk.req.arg_types.iter().enumerate())
.map(|(i, param)| operand(self, i, self.rep(param)))
.collect();
let ret = walk.result.as_ref().unwrap().ret_type.clone();
(format!("{}({})", self.fn_name(w), args.join(", ")), ret)
}
// A native whose args cannot be of its params' kinds throws
Code::Native(id) if native::ret_type(id, &arg_types[FIRST_ARG as usize..]) == Type::Unknown => {
("jsrt_type_error()".into(), Type::Unknown)
}
Code::Native(id) => {
let native = &NATIVES[id];
let args: Vec<String> = (native.args.iter().enumerate())
.map(|(i, param)| operand(self, FIRST_ARG as usize + i, self.rep(param)))
.collect();
(format!("{}({})", native.name, args.join(", ")), native.ret.clone())
}
// Keeps the prototype where field reads on primitives find it
Code::ProtoLink(kind) => {
let value = operand(self, FIRST_ARG as usize, self.rep(&self.cache.protos[kind as usize]));
(format!("({} = {value}, (undefined_t){{0}})", proto_global(kind)), Type::Undefined)
}
// Keeps the fn where `jsrt_complete` finds it
Code::HostLink => {
let value = operand(self, FIRST_ARG as usize, self.rep(&self.cache.host_complete));
(format!("({HOST_COMPLETE_GLOBAL} = {value}, (undefined_t){{0}})"), Type::Undefined)
}
}
}
/// The body of `jsrt_complete(h)`: a call of the fn that the stdlib links
/// for the host, with op `h` (see `infer::host_calls()`)
pub(super) fn host_complete(&mut self) -> String {
let ty = self.cache.host_complete.clone();
let mut out = String::new();
if ty == Type::Unknown {
return out;
}
let rep = self.rep(&ty);
let operands = [(HOST_COMPLETE_GLOBAL.to_string(), rep), ("(undefined_t){0}".into(), Rep::Undefined), ("h".into(), Rep::Int)];
self.dispatch(HOST_COMPLETE_GLOBAL, &ty, "jsrt_type_error();", &mut out, |e, member, callee, rep| {
let Type::Obj(cell) = member else { return None };
let mut operands = operands.clone();
operands[0] = (callee.into(), rep);
let arg_types = [member.clone(), Type::Undefined, Type::Int];
Some(format!("{};", e.call_code(cell.code()?, HOST_COMPLETE, &arg_types, &operands).0))
});
out
}
}
-181
View File
@@ -1,181 +0,0 @@
//! Which cells and walks C cannot tell apart
use super::*;
/// Groups the cells and the walks that C cannot tell apart. Two cells are the
/// same in C if their kinds, code and fields are, with each cell in a field's
/// type replaced by its class. Two walks are the same if their fns, the types
/// of their values and instrs, their reachable blocks and the classes of the
/// walks they call are. Both start in one class each, which splits until no
/// class splits. Gives the class of each cell, a cell of each class, and for
/// each walk, the first walk of its class
pub(super) fn classes(fns: &[RegFn], cache: &TypeCache) -> (HashMap<Cell, CellNum>, Vec<Cell>, Vec<usize>) {
let cells: Vec<Cell> = cache.cells().cloned().collect();
let mut class: HashMap<Cell, CellNum> = cells.iter().map(|c| (c.clone(), 0)).collect();
let mut sig = vec![];
loop {
let mut ids: HashMap<Vec<u32>, CellNum> = HashMap::new();
let next: Vec<CellNum> = cells
.iter()
.map(|cell| {
sig.clear();
sig.push(class[cell]);
cell_sig(cell, &class, &mut sig);
let n = ids.len() as CellNum;
*ids.entry(sig.clone()).or_insert(n)
})
.collect();
let done = ids.len() == class.values().collect::<HashSet<_>>().len();
cells.iter().zip(next).for_each(|(cell, n)| *class.get_mut(cell).unwrap() = n);
if done {
break;
}
}
let mut reps: Vec<Option<Cell>> = vec![None; class.values().max().map_or(0, |&m| m as usize + 1)];
cells.iter().for_each(|cell| _ = reps[class[cell] as usize].get_or_insert_with(|| cell.clone()));
// Walks. A walk that no round reached has no fn, and its own class
let walks = &cache.walks;
let mut wclass: Vec<u32> = (0..walks.len()).map(|w| if walks[w].result.is_some() { 0 } else { w as u32 + 1 }).collect();
let callees: Vec<Vec<usize>> = (0..walks.len()).map(|w| callees_of(fns, cache, w)).collect();
let mut count = 0;
loop {
let mut ids: HashMap<Vec<u32>, u32> = HashMap::new();
let next: Vec<u32> = (0..walks.len())
.map(|w| {
sig.clear();
sig.push(wclass[w]);
if let Some(res) = &walks[w].result {
sig.push(walks[w].req.fn_id as u32);
walks[w].req.arg_types.iter().chain(&res.val_types).chain(&res.instr_types).for_each(|t| type_sig(t, &class, &mut sig));
type_sig(&res.ret_type, &class, &mut sig);
sig.extend(res.reachable.iter().map(|&r| r as u32));
sig.extend(callees[w].iter().map(|&c| wclass[c]));
}
let n = ids.len() as u32;
*ids.entry(sig.clone()).or_insert(n)
})
.collect();
wclass = next;
if ids.len() == count {
break;
}
count = ids.len();
}
let mut first: HashMap<u32, usize> = HashMap::new();
let walk_rep = (0..walks.len()).map(|w| *first.entry(wclass[w]).or_insert(w)).collect();
(class, reps.into_iter().map(Option::unwrap).collect(), walk_rep)
}
/// The walks that walk `w` calls, in the order that emit writes the calls
pub(super) fn callees_of(fns: &[RegFn], cache: &TypeCache, w: usize) -> Vec<usize> {
let Some(res) = &cache.walks[w].result else { return vec![] };
let fn_id = cache.walks[w].req.fn_id;
let f = &fns[fn_id];
let mut out = vec![];
let blocks = f.blocks.iter().enumerate().filter(|(b, _)| res.reachable[*b]);
let code = blocks.flat_map(|(_, block): (usize, &BasicBlock)| (block.start..block.end).map(|idx| (idx, &f.code[idx])));
for (idx, instr) in code {
if !matches!(instr.op, OpCode::Call | OpCode::CallMethod | OpCode::CallConstructor)
|| instr.args.iter().any(|&a| res.val_types[a] == Type::Unknown)
{
continue;
}
let mut args: Vec<Type> = instr.args.iter().map(|&a| res.val_types[a].clone()).collect();
for member in res.val_types[instr.args[0]].members() {
if let Type::Obj(cell) = member
&& let Some(Code::Fn(callee)) = cell.code()
{
args[0] = member.clone();
out.push(cache.find(callee, (fn_id, idx), &args, fns));
}
}
}
out
}
/// Append what C sees of `cell` to `sig`
pub(super) fn cell_sig(cell: &Cell, class: &HashMap<Cell, CellNum>, sig: &mut Vec<u32>) {
let info = cell.info();
match &info.kind {
ObjKind::Plain => sig.push(0),
ObjKind::Fn(code) => sig.extend(match *code {
Code::Fn(id) => [1, id as u32],
Code::Native(id) => [2, id as u32],
Code::ProtoLink(kind) => [3, kind as u32],
Code::HostLink => [5, 0],
}),
ObjKind::Array { elems } => {
sig.push(4);
type_sig(elems, class, sig);
}
}
sig.push(info.fields.len() as u32);
for (name, field) in &info.fields {
atom_sig(*name, sig);
sig.push(field.defined as u32);
type_sig(&field.ty, class, sig);
}
}
/// Append `ty` to `sig`, with each cell as its class. No encoding is a prefix
/// of another, so a list of them reads back one way
pub(super) fn type_sig(ty: &Type, class: &HashMap<Cell, CellNum>, sig: &mut Vec<u32>) {
match ty {
Type::Unknown => sig.push(0),
Type::Undefined => sig.push(1),
Type::Null => sig.push(2),
Type::Bool(b) => sig.push(3 + b.map_or(0, |b| 1 + b as u32)),
Type::Int => sig.push(6),
Type::Float => sig.push(7),
Type::Str(None) => sig.extend([8, u32::MAX]),
Type::Str(Some(atom)) => {
sig.push(8);
atom_sig(*atom, sig);
}
Type::Obj(cell) => sig.extend([10, class[cell]]),
Type::Sum(members) => {
let mut parts: Vec<Vec<u32>> = members
.iter()
.map(|m| {
let mut part = vec![];
type_sig(m, class, &mut part);
part
})
.collect();
parts.sort_unstable();
parts.dedup();
sig.extend([11, parts.len() as u32]);
parts.iter().for_each(|p| sig.extend(p));
}
Type::Any => sig.push(12),
}
}
pub(super) fn atom_sig(atom: AtomRef, sig: &mut Vec<u32>) {
sig.extend(match atom {
AtomRef::TaggedInt(n) => [0, n],
AtomRef::Predefined(n) => [1, n],
AtomRef::FileLocal(n) => [2, n as u32],
});
}
-282
View File
@@ -1,282 +0,0 @@
//! Conversions between representations, `dispatch` over the members of a
//! sum, and the tests on values that C needs
use super::*;
impl<'a> Emitter<'a> {
/// `e`, a value of rep `from`, as a value of rep `to`. `e` is evaluated
/// once, even where its value is not needed, because it can be a call
///
/// Types grow in a join, so most conversions go to a rep that holds more:
/// to a sum, or from `Int` to `Double`. A conversion to a rep that holds
/// less checks the kind, and a failed check is a `TypeError`: for example
/// a string that can be undefined, passed to a native's string param. A
/// value of a param that the caller does not pass is undefined
pub(super) fn conv(&self, e: &str, from: Rep, to: Rep) -> String {
if from == to {
return e.into();
}
// A tagged union is made in place from a value of one kind that it
// has. Else it goes through `any_t`, which the C compiler removes
match (from, to) {
(Rep::Sum(_), _) => return self.conv(&self.tagged(e, from).unwrap(), Rep::Any, to),
(_, Rep::Sum(k)) if let Some(made) = self.make_sum(e, from, k) => return made,
(_, Rep::Sum(_)) => return format!("any_{}({})", self.c_type(to), self.conv(e, from, Rep::Any)),
_ => {}
}
let c_to = self.c_type(to);
let after_e = |value: String| format!("((void)({e}), {value})");
match (from, to) {
(_, Rep::Any) => match from {
Rep::Undefined => after_e("__any_undefined()".into()),
Rep::Null => after_e("__any_null()".into()),
Rep::Bool => format!("__any_from_bool({e})"),
Rep::Int => format!("__any_from_int({e})"),
Rep::Double => format!("__any_from_float({e})"),
Rep::Str | Rep::OptStr => format!("__any_from_string({e})"),
Rep::Obj(_) | Rep::OptObj(_) | Rep::Objs => format!("__any_from_object({e})"),
Rep::Sum(_) | Rep::Any => unreachable!(),
},
(Rep::Any, _) => match to {
Rep::Undefined => after_e("(undefined_t){0}".into()),
Rep::Null => after_e("(null_t){0}".into()),
Rep::Bool => format!("__any_as_bool({e})"),
Rep::Int => format!("__any_as_int({e})"),
Rep::Double => format!("__any_as_float({e})"),
Rep::Str => format!("(({c_to})jsrt_some(__any_as_string({e})))"),
Rep::Obj(_) => format!("(({c_to})jsrt_some(__any_as_object({e})))"),
Rep::OptStr => format!("__any_as_string({e})"),
Rep::OptObj(_) | Rep::Objs => format!("(({c_to})__any_as_object({e}))"),
Rep::Sum(_) | Rep::Any => unreachable!(),
},
(Rep::Int, Rep::Double) => format!("(double){e}"),
(Rep::Double, Rep::Int) => format!("(int32_t){e}"),
(Rep::Str, Rep::OptStr) | (Rep::Obj(_), Rep::OptObj(_)) => e.into(),
(Rep::OptStr, Rep::Str) | (Rep::OptObj(_), Rep::Obj(_)) => format!("(({c_to})jsrt_some({e}))"),
(Rep::Obj(_) | Rep::OptObj(_), Rep::Objs) => format!("((jsrt_header *){e})"),
(Rep::Undefined, Rep::OptStr | Rep::OptObj(_) | Rep::Objs) => after_e(format!("(({c_to})JSRT_UNDEFINED)")),
(Rep::Null, Rep::OptStr | Rep::OptObj(_) | Rep::Objs) => after_e(format!("(({c_to})NULL)")),
// Reps that correct types never make meet
_ => after_e(format!("({c_to}){{0}} /* mismatch */")),
}
}
/// `dst = expr`, where `expr` is a value of rep `from`
pub(super) fn assign(&self, out: &mut String, dst: &str, expr: &str, from: Rep, to: Rep) {
let _ = writeln!(out, " {dst} = {};", self.conv(expr, from, to));
}
/// `e`, a value of rep `from`, as a tagged union of kinds `k`, if `from`
/// has one kind and `k` has it, with no sentinels to tell apart
pub(super) fn make_sum(&self, e: &str, from: Rep, k: Kinds) -> Option<String> {
let (kind, member) = match from {
Rep::Undefined => (1, None),
Rep::Null => (2, None),
Rep::Bool => (3, Some("b")),
Rep::Int => (4, Some("i")),
Rep::Double => (5, Some("f")),
Rep::Str => (6, Some("s")),
Rep::Obj(_) => (7, Some("o")),
_ => return None,
};
if k & 1 << kind == 0 {
return None;
}
let t = self.c_type(Rep::Sum(k));
let kind = KINDS[kind].0;
Some(match member {
Some(m) => format!("(({t}){{ .kind = JSRT_KIND_{kind}, .as.{m} = {e} }})"),
None => format!("((void)({e}), ({t}){{ .kind = JSRT_KIND_{kind} }})"),
})
}
/// `e`, a value of rep `rep`, as an `any_t`, if `rep` is a tagged value
pub(super) fn tagged(&self, e: &str, rep: Rep) -> Option<String> {
match rep {
Rep::Any => Some(e.into()),
Rep::Sum(_) => Some(format!("{}_any({e})", self.c_type(rep))),
_ => None,
}
}
/// The absent value of a field of rep `rep`
pub(super) fn absent(&self, rep: Rep) -> String {
match rep {
Rep::Any | Rep::Sum(_) => self.conv("__any_absent()", Rep::Any, rep),
_ => format!("(({})JSRT_ABSENT)", self.c_type(rep)),
}
}
/// A C bool that is true if `e`, a field of rep `rep`, is not absent
pub(super) fn present(&self, e: &str, rep: Rep) -> String {
match self.tagged(e, rep) {
Some(a) => format!("!__any_is_absent({a})"),
None => format!("((void *){e} != JSRT_ABSENT)"),
}
}
/// `e`, an `any_t` that holds an object of cell `id`, as a pointer to its
/// struct
pub(super) fn any_pointer(&self, e: &str, id: CellNum) -> String {
format!("(({} *)__any_as_object({e}))", struct_name(id))
}
/// A C bool that is true if `e`, a value of type `ty`, is null or
/// undefined
pub(super) fn nullish(&self, e: &str, ty: &Type) -> String {
let can_be = ty.members().iter().any(Type::is_nullish);
match self.rep(ty) {
Rep::Undefined | Rep::Null => "true".into(),
rep @ (Rep::Any | Rep::Sum(_)) => format!("__any_is_nullish({})", self.tagged(e, rep).unwrap()),
Rep::OptStr | Rep::OptObj(_) | Rep::Objs if can_be => {
format!("((void *){e} == NULL || (void *){e} == JSRT_UNDEFINED)")
}
_ => "false".into(),
}
}
/// Writes `body`'s statement for each member of `ty`, the type of `e`. A
/// statement gets the member, and `e` as a value of that member and its
/// rep. At runtime, a member is told apart by its kind, and an object by
/// the cell in its header. `body` gives `None` for a member on which the
/// op throws. A value that no statement takes runs `otherwise`
pub(super) fn dispatch(
&mut self,
e: &str,
ty: &Type,
otherwise: &str,
out: &mut String,
mut body: impl FnMut(&mut Self, &Type, &str, Rep) -> Option<String>,
) {
let rep = self.rep(ty);
if let [member] = ty.members() {
let stmt = body(self, member, e, rep);
let _ = writeln!(out, " {}", stmt.as_deref().unwrap_or(otherwise));
return;
}
let _ = write!(out, " ");
let mut objects = vec![];
for member in ty.members() {
let (value, member_rep) = self.member_value(e, rep, member);
match (member, body(self, member, &value, member_rep)) {
(_, None) => {}
// Cells of one class do the same
(Type::Obj(cell), Some(_)) if objects.iter().any(|(id, _)| *id == self.num(cell)) => {}
(Type::Obj(cell), Some(stmt)) => objects.push((self.num(cell), stmt)),
(_, Some(stmt)) => {
let _ = write!(out, "if ({}) {{ {stmt} }} else ", self.is_member(e, rep, member));
}
}
}
// The objects, by cell. A pointer to the objects of one cell needs no
// switch
let is_object = match self.tagged(e, rep) {
Some(a) => format!("__any_is_object({a})"),
None => format!("!{}", self.nullish(e, ty)),
};
match (rep, &objects[..]) {
(_, []) => {}
(Rep::OptObj(_), [(_, stmt)]) => {
let _ = write!(out, "if ({is_object}) {{ {stmt} }} else ");
}
_ => {
let cell = match self.tagged(e, rep) {
Some(a) => format!("((jsrt_header *)__any_as_object({a}))->cell"),
None => format!("{e}->cell"),
};
let _ = writeln!(out, "if ({is_object}) switch ({cell}) {{");
for (id, stmt) in &objects {
let _ = writeln!(out, " case {id}: {{ {stmt} }} break;");
}
let _ = write!(out, " default: {otherwise}\n }} else ");
}
}
let _ = writeln!(out, "{otherwise}");
}
/// `e`, a value of rep `rep`, as a value of `member`, a member of its type,
/// and that value's rep
pub(super) fn member_value(&self, e: &str, rep: Rep, member: &Type) -> (String, Rep) {
let member_rep = self.rep(member);
let value = match (rep, member) {
(Rep::Any | Rep::Sum(_), Type::Obj(cell)) => self.any_pointer(&self.tagged(e, rep).unwrap(), self.num(cell)),
(Rep::Any | Rep::Sum(_), _) => self.conv(e, rep, member_rep),
(Rep::Objs, Type::Obj(cell)) => format!("(({} *){e})", struct_name(self.num(cell))),
(_, Type::Undefined | Type::Null) => format!("({}){{0}}", self.c_type(member_rep)),
_ => e.into(),
};
(value, member_rep)
}
/// A C bool that is true if `e`, a value of rep `rep`, is of `member`, a
/// member of its type that is not an object
pub(super) fn is_member(&self, e: &str, rep: Rep, member: &Type) -> String {
let kind = match member {
Type::Undefined => "undefined",
Type::Null => "null",
Type::Bool(_) => "boolean",
Type::Int | Type::Float => "number",
_ => "string",
};
match (rep, member) {
(Rep::Any | Rep::Sum(_), _) => format!("__any_is_{kind}({})", self.tagged(e, rep).unwrap()),
(_, Type::Undefined) => format!("((void *){e} == JSRT_UNDEFINED)"),
(_, Type::Null) => format!("((void *){e} == NULL)"),
_ => format!("((void *){e} != NULL && (void *){e} != JSRT_UNDEFINED)"),
}
}
/// A C bool that is JS's truthiness of `e`, a value of type `ty`
pub(super) fn truthy(&self, e: &str, ty: &Type) -> String {
match self.rep(ty) {
Rep::Bool => e.into(),
Rep::Int => format!("({e} != 0)"),
Rep::Double => format!("({e} != 0 && {e} == {e})"),
Rep::Str => format!("(jsrt_string_length({e}) != 0)"),
Rep::OptStr => format!("(!{} && jsrt_string_length({e}) != 0)", self.nullish(e, ty)),
Rep::Obj(_) => "true".into(),
Rep::OptObj(_) | Rep::Objs => format!("!{}", self.nullish(e, ty)),
Rep::Undefined | Rep::Null => "false".into(),
rep @ (Rep::Any | Rep::Sum(_)) => format!("__any_truthy({})", self.tagged(e, rep).unwrap()),
}
}
/// `e`, a value of type `ty`, as a string, like the spec's ToString
pub(super) fn to_string(&self, e: &str, ty: &Type) -> String {
match self.rep(ty) {
Rep::Str => e.into(),
Rep::Int | Rep::Double => format!("__native_number_to_string((double){e})"),
Rep::Bool => {
let (t, f) = (self.string_lit("true"), self.string_lit("false"));
format!("({e} ? {t} : {f})")
}
Rep::Undefined => self.string_lit("undefined"),
Rep::Null => self.string_lit("null"),
rep => format!("__any_to_string({})", self.conv(e, rep, Rep::Any)),
}
}
pub(super) fn string_lit(&self, s: &str) -> String {
format!("jsrt_string_lit({})", c_str(s))
}
}
-221
View File
@@ -1,221 +0,0 @@
//! The C of each instr
use super::*;
impl<'a> Emitter<'a> {
pub(super) fn emit_instr(&mut self, fn_id: usize, res: &InferResult, idx: usize, instr: &RegInstr, out: &mut String) {
use OpCode::*;
if matches!(instr.op, Nop | Drop) {
return;
}
// No value reaches the instr, so it cannot run. An instr that values
// reach but that gives no value always throws, or is a call that
// never returns: its op's code below does that
if instr.args.iter().any(|&a| res.val_types[a] == Type::Unknown) {
let _ = writeln!(out, " abort(); /* not reached */");
return;
}
let ty = &res.instr_types[idx];
let v = |i: usize| format!("v{}", instr.args[i]);
let vt = |i: usize| &res.val_types[instr.args[i]];
let vr = |i: usize| self.rep(&res.val_types[instr.args[i]]);
let dst = instr.dsts.first().map(|d| format!("v{d}")).unwrap_or_default();
let dst_rep = instr.dsts.first().map_or(Rep::Undefined, |&d| self.rep(&res.val_types[d]));
let rep = self.rep(ty);
// A known value is its own literal. A call still runs, for what it
// does
let known = match ty {
Type::Bool(Some(b)) => Some(b.to_string()),
Type::Str(Some(atom)) => Some(self.string_lit(&atom_name(*atom, self.atoms))),
_ => None,
};
let is_ret = matches!(instr.op, Return | ReturnAsync | ReturnUndef);
let is_call = matches!(instr.op, Call | CallMethod | CallConstructor);
if let Some(lit) = known.filter(|_| !is_ret && !is_call) {
return self.assign(out, &dst, &lit, rep, dst_rep);
}
// Binary ops: the lhs was pushed first, so it is popped last
let (l, r) = (instr.args.len() == 2).then(|| (v(1), v(0))).unwrap_or_default();
let numbers = instr.args.len() == 2 && is_num(vr(0)) && is_num(vr(1));
let expr = match instr.op {
// ── Literals ──
PushMinus1 => "-1".into(),
Push0 => "0".into(),
Push1 => "1".into(),
Push2 => "2".into(),
Push3 => "3".into(),
Push4 => "4".into(),
Push5 => "5".into(),
Push6 => "6".into(),
Push7 => "7".into(),
PushI8 | PushI16 | PushI32 => match instr.imm {
ImmOperand::I8(n) => n.to_string(),
ImmOperand::I16(n) => n.to_string(),
ImmOperand::I32(n) => n.to_string(),
_ => unreachable!(),
},
PushConst | PushConst8 => match &self.fns[fn_id].cpool[instr.imm.const_idx()] {
JsValue::Int(n) => n.to_string(),
JsValue::Float(x) if x.is_finite() => format!("{x:?}"),
JsValue::Float(x) if x.is_nan() => "NAN".into(),
JsValue::Float(x) => format!("{}INFINITY", if *x < 0.0 { "-" } else { "" }),
JsValue::String(s) => self.string_lit(&js_string_text(s)),
JsValue::NativeFn(id) => format!("&{}", builtin_name(self.num(self.cache.builtin(Code::Native(*id))))),
JsValue::ProtoLink(kind) => format!("&{}", builtin_name(self.num(self.cache.builtin(Code::ProtoLink(*kind))))),
JsValue::HostLink => format!("&{}", builtin_name(self.num(self.cache.builtin(Code::HostLink)))),
_ => format!("({}){{0}}", self.c_type(rep)),
},
Undefined | Null => format!("({}){{0}}", self.c_type(rep)),
Object | Fclosure | Fclosure8 => self.alloc(self.num_of(ty)),
// The elements are the operands, last first
ArrayFrom => {
let id = self.num_of(ty);
let (name, elem) = (struct_name(id), self.elem_rep(id));
let _ = writeln!(out, " {dst} = {};\n set_length_{name}({dst}, {});", self.alloc(id), instr.args.len());
for (i, &v) in instr.args.iter().rev().enumerate() {
let value = self.conv(&format!("v{v}"), self.rep(&res.val_types[v]), elem);
let _ = writeln!(out, " {dst}->items[{i}] = {value};");
}
return;
}
GetArrayEl | PutArrayEl => return self.element(res, instr, out),
// ── Arithmetic ──
Add if matches!(ty, Type::Str(_)) => {
let (ls, rs) = (self.to_string(&l, vt(1)), self.to_string(&r, vt(0)));
format!("__native_string_concat({ls}, {rs})")
}
Add | Sub | Mul if numbers => {
let op = match instr.op { Add => "+", Sub => "-", _ => "*" };
format!("{l} {op} {r}")
}
Mod if numbers && rep == Rep::Int => format!("{l} % {r}"),
Mod if numbers => format!("fmod({l}, {r})"),
Pow if numbers => format!("pow({l}, {r})"),
Div if numbers => format!("(double){l} / {r}"),
// An operand that is not a number: JS's operator, at runtime
Add | Sub | Mul | Div | Mod | Pow => {
let name = match instr.op {
Add => "add", Sub => "sub", Mul => "mul", Div => "div", Mod => "mod", _ => "pow",
};
let (la, ra) = (self.conv(&l, vr(1), Rep::Any), self.conv(&r, vr(0), Rep::Any));
return self.assign(out, &dst, &format!("__any_{name}({la}, {ra})"), Rep::Any, dst_rep);
}
Neg | Plus | Inc | Dec | PostInc | PostDec if is_num(vr(0)) => {
let new = match instr.op {
Neg => format!("-{}", v(0)),
Plus => v(0),
Inc | PostInc => format!("{} + 1", v(0)),
_ => format!("{} - 1", v(0)),
};
// Pushes the old value, then the new one
if matches!(instr.op, PostInc | PostDec) {
let new_dst = format!("v{}", instr.dsts[1]);
self.assign(out, &new_dst, &new, rep, self.rep(&res.val_types[instr.dsts[1]]));
v(0)
} else {
new
}
}
Neg | Plus | Inc | Dec | PostInc | PostDec => {
let name = match instr.op {
Neg => "neg", Plus => "plus", Inc | PostInc => "inc", _ => "dec",
};
let operand = self.conv(&v(0), vr(0), Rep::Any);
// Pushes the old value as a number, then the new one
if matches!(instr.op, PostInc | PostDec) {
let new_dst = format!("v{}", instr.dsts[1]);
let new = format!("__any_{name}({operand})");
self.assign(out, &new_dst, &new, Rep::Any, self.rep(&res.val_types[instr.dsts[1]]));
return self.assign(out, &dst, &format!("__any_plus({operand})"), Rep::Any, dst_rep);
}
return self.assign(out, &dst, &format!("__any_{name}({operand})"), Rep::Any, dst_rep);
}
Not => match vr(0) {
Rep::Int => format!("~{}", v(0)),
r => format!("~__any_to_int32({})", self.conv(&v(0), r, Rep::Any)),
},
// ── Comparisons ──
Lt | Lte | Gt | Gte | Eq | Neq | StrictEq | StrictNeq => {
let op = match instr.op {
Lt => "<", Lte => "<=", Gt => ">", Gte => ">=",
Eq | StrictEq => "==", _ => "!=",
};
let eq = matches!(instr.op, Eq | Neq | StrictEq | StrictNeq);
match (vr(1), vr(0)) {
_ if numbers => format!("{l} {op} {r}"),
(Rep::Bool, Rep::Bool) => format!("{l} {op} {r}"),
(Rep::Str, Rep::Str) => match instr.op {
Eq | StrictEq => format!("jsrt_string_eq({l}, {r})"),
Neq | StrictNeq => format!("!jsrt_string_eq({l}, {r})"),
_ => format!("jsrt_string_cmp({l}, {r}) {op} 0"),
},
// Two objects are equal if they are one object
(Rep::Obj(_), Rep::Obj(_)) if eq => format!("(void *){l} {op} (void *){r}"),
(lr, rr) => {
let name = match instr.op {
Lt => "lt", Lte => "lte", Gt => "gt", Gte => "gte", Eq => "eq",
Neq => "neq", StrictEq => "strict_eq", _ => "strict_neq",
};
let (la, ra) = (self.conv(&l, lr, Rep::Any), self.conv(&r, rr, Rep::Any));
format!("__any_{name}({la}, {ra})")
}
}
}
Lnot => format!("!{}", self.truthy(&v(0), vt(0))),
// ── Types of values only known at runtime ──
Typeof => format!("__any_typeof({})", self.conv(&v(0), vr(0), Rep::Any)),
TypeofIsUndefined | TypeofIsFunction | IsUndefined | IsNull | IsUndefinedOrNull => {
let name = match instr.op {
TypeofIsFunction => "is_function",
IsNull => "is_null",
IsUndefinedOrNull => "is_nullish",
_ => "is_undefined",
};
format!("__any_{name}({})", self.conv(&v(0), vr(0), Rep::Any))
}
// ── Movers ──
SetLoc | SetName | ToPropkey => v(0),
// ── Objects ──
DefineField | PutField => return self.write_field(res, instr, out),
GetField | GetLength => return self.read_field(res, instr, out),
// ── Calls ──
Call | CallMethod | CallConstructor => return self.call(res, (fn_id, idx), instr, out),
// ── Returns ──
Return | ReturnAsync => {
let value = self.conv(&v(0), vr(0), self.rep(&res.ret_type));
let _ = writeln!(out, " return {value};");
return;
}
ReturnUndef => {
let value = self.conv("(undefined_t){0}", Rep::Undefined, self.rep(&res.ret_type));
let _ = writeln!(out, " return {value};");
return;
}
_ => unimplemented!("emit {instr:?}"),
};
self.assign(out, &dst, &expr, rep, dst_rep);
}
}
-310
View File
@@ -1,310 +0,0 @@
//! Emits C: one fn for each walk that the program calls, one struct for each
//! cell (see `docs/model.md`, section 5). Cells and walks that C cannot tell
//! apart share one struct and one fn (see `classes()`)
//!
//! A value's C representation is a function of its type (`rep()`), one for
//! each kind of type. An object is a pointer to a struct of its cell. Each
//! object starts with a header that holds the number of its cell, and the
//! fields that can be absent start as absent
//!
//! The runtime (`lib/rt/`) gives the primitive types, the string ops, the
//! `any_t` ops and the allocator
use std::{
cell::RefCell,
collections::{BTreeSet, HashMap, HashSet},
fmt::Write,
};
use crate::{
infer::{
HOST, HOST_COMPLETE, InferResult, Place, TypeCache,
cells::{Cell, Code, LENGTH_FIELD, ObjKind, read_value},
types::Type,
},
libqjs::parse::{Atom, AtomRef, ImmOperand, JsValue, opcodes::OpCode},
native::{self, NATIVES, ProtoOf, atom_name, js_string_text},
regify::{BasicBlock, RegFn, RegInstr},
transform::{FIRST_ARG, PROTO_FIELD, THIS_ARG},
};
mod calls;
mod classes;
mod conv;
mod instrs;
mod objects;
mod reps;
use classes::*;
use reps::*;
/// The number of a class of cells in C: the number in the header of their
/// objects, and the name of their struct
type CellNum = u32;
pub fn emit(fns: &[RegFn], cache: &TypeCache, atoms: &[Atom]) -> String {
let (nums, cells, walk_rep) = classes(fns, cache);
let mut e = Emitter {
fns,
cache,
atoms,
cells,
nums,
walk_rep,
queued: vec![false; cache.walks.len()],
queue: vec![],
helpers: HashSet::new(),
sums: RefCell::new(BTreeSet::new()),
helper_protos: String::new(),
helper_defs: String::new(),
};
// Every struct is declared first, so that a field can point to any of
// them
let mut structs = String::new();
for id in 0..e.cells.len() as CellNum {
let _ = writeln!(structs, "typedef struct {0} {0};", struct_name(id));
}
for id in 0..e.cells.len() as CellNum {
e.struct_def(id, &mut structs);
}
// Which cells are fns, by number, for the runtime's `typeof`
let fn_cells: Vec<&str> = e.cells.iter().map(|cell| if cell.code().is_some() { "1" } else { "0" }).collect();
let mut statics = format!("const bool jsrt_fn_cells[] = {{ {} }};\n\n", fn_cells.join(", "));
// A built-in fn is one static object
let mut init = String::new();
for cell in cache.builtins() {
let id = e.num(cell);
let name = struct_name(id);
let _ = writeln!(statics, "static {name} {};", builtin_name(id));
let _ = writeln!(init, " init_{name}(&{}, {id});", builtin_name(id));
}
for kind in ProtoOf::ALL {
let ty = &cache.protos[kind as usize];
if *ty != Type::Unknown {
let _ = writeln!(statics, "static {} {};", e.c_type(e.rep(ty)), proto_global(kind));
}
}
if cache.host_complete != Type::Unknown {
let _ = writeln!(statics, "static {} {HOST_COMPLETE_GLOBAL};", e.c_type(e.rep(&cache.host_complete)));
}
// The walks that the code calls, from the host's calls. The host calls the
// entry with nothing: its env and `this` are undefined
let entry = cache.find(fns.len() - 1, HOST, &[], fns);
let entry_args = vec!["(undefined_t){0}"; cache.walks[entry].req.arg_types.len()].join(", ");
e.use_walk(entry);
let complete = e.host_complete();
let mut fn_protos = String::new();
let mut bodies = String::new();
while let Some(w) = e.queue.pop() {
let res = cache.walks[w].result.as_ref().unwrap();
let _ = writeln!(fn_protos, "{};", e.signature(w, res));
e.emit_fn(w, res, &mut bodies);
}
// The program is one file, so it also holds the runtime. The runtime goes
// first (see `lib/rt/jsrt.h`)
let mut out = String::from(
"#define JSRT_IMPLEMENTATION\n#include \"jsrt.h\"\n#include <math.h>\n#include <stdlib.h>\n\n// Natives\n",
);
for n in NATIVES {
let params: Vec<String> = n.args.iter().map(|t| e.c_type(e.rep(t))).collect();
let _ = writeln!(out, "{} {}({});", e.c_type(e.rep(&n.ret)), n.name, params.join(", "));
}
let _ = write!(
out,
"\n{}\n{structs}\n{statics}\n{fn_protos}\n{}\n{}{bodies}",
sum_defs(&e.sums.borrow()),
e.helper_protos,
e.helper_defs
);
// What the host calls (see `lib/rt/jsrt.h`)
let _ = writeln!(out, "void jsrt_start(void) {{\n{init} {}({entry_args});\n}}\n", e.fn_name(entry));
let _ = writeln!(out, "void jsrt_complete(int32_t h) {{\n{complete}}}");
out
}
struct Emitter<'a> {
fns: &'a [RegFn],
cache: &'a TypeCache,
atoms: &'a [Atom],
cells: Vec<Cell>, // a cell of each class, by number
nums: HashMap<Cell, CellNum>, // the class of each cell
walk_rep: Vec<usize>, // by walk: the walk whose fn it uses
queued: Vec<bool>, // the walks that the code calls, by walk
queue: Vec<usize>, // the walks that the code calls, which are not written yet
helpers: HashSet<String>, // the helpers made so far, by name
sums: RefCell<BTreeSet<Kinds>>, // the tagged unions that the C uses
helper_protos: String,
helper_defs: String,
}
impl<'a> Emitter<'a> {
fn num(&self, cell: &Cell) -> CellNum {
self.nums[cell]
}
/// The number of the cell of an `Obj` type
fn num_of(&self, ty: &Type) -> CellNum {
match ty {
Type::Obj(cell) => self.num(cell),
_ => unreachable!(),
}
}
/// Walk `w` is called, so the walk whose fn it uses is written
fn use_walk(&mut self, w: usize) {
let w = self.walk_rep[w];
if !self.queued[w] {
self.queued[w] = true;
self.queue.push(w);
}
}
fn signature(&self, w: usize, res: &InferResult) -> String {
let params: Vec<String> = (self.cache.walks[w].req.arg_types.iter().enumerate())
.map(|(i, ty)| format!("{} p{i}", self.c_type(self.rep(ty))))
.collect();
let ret = self.c_type(self.rep(&res.ret_type));
format!("static {ret} {}({})", self.fn_name(w), params.join(", "))
}
fn emit_fn(&mut self, w: usize, res: &InferResult, out: &mut String) {
let fn_id = self.cache.walks[w].req.fn_id;
let f = &self.fns[fn_id];
let mut body = String::new();
// Args are values `var_count + i` (see regify)
for (i, ty) in self.cache.walks[w].req.arg_types.iter().enumerate() {
let arg = f.var_count + i;
self.assign(&mut body, &format!("v{arg}"), &format!("p{i}"), self.rep(ty), self.rep(&res.val_types[arg]));
}
for (b, block) in f.blocks.iter().enumerate() {
let _ = writeln!(body, "L{b}:;");
if !res.reachable[b] {
let _ = writeln!(body, " abort();");
continue;
}
let code = &f.code[block.start..block.end];
let (instrs, last) = match code.split_last() {
Some((last, rest)) if is_terminator(last.op) => (rest, Some(last)),
_ => (code, None),
};
for (i, instr) in instrs.iter().enumerate() {
self.emit_instr(fn_id, res, block.start + i, instr, &mut body);
}
// Postfix copies carry values into the next block's stack slots
for copy in &block.postfix {
let (src, dst) = (copy.args[0], copy.dsts[0]);
let (from, to) = (self.rep(&res.val_types[src]), self.rep(&res.val_types[dst]));
self.assign(&mut body, &format!("v{dst}"), &format!("v{src}"), from, to);
}
let goto = |i: usize| format!("goto L{};", block.succ[i]);
match last.map(|l| l.op) {
Some(OpCode::IfFalse | OpCode::IfFalse8 | OpCode::IfTrue | OpCode::IfTrue8) => {
let last = last.unwrap();
let cond = self.truthy(&format!("v{}", last.args[0]), &res.val_types[last.args[0]]);
let not = if matches!(last.op, OpCode::IfFalse | OpCode::IfFalse8) { "!" } else { "" };
let _ = writeln!(body, " if ({not}{cond}) {} else {}", goto(0), goto(1));
}
Some(OpCode::Goto | OpCode::Goto8 | OpCode::Goto16) => {
let _ = writeln!(body, " {}", goto(0));
}
Some(_) => self.emit_instr(fn_id, res, block.end - 1, last.unwrap(), &mut body),
None if !block.succ.is_empty() => {
let _ = writeln!(body, " {}", goto(0));
}
None => {}
}
}
let _ = writeln!(out, "{} {{", self.signature(w, res));
for (v, ty) in res.val_types.iter().enumerate() {
let _ = writeln!(out, " {} v{v};", self.c_type(self.rep(ty)));
}
let _ = writeln!(out, "{body}}}\n");
}
/// A new object of cell `id`, on the heap
fn alloc(&self, id: CellNum) -> String {
let name = struct_name(id);
format!("init_{name}(jsrt_alloc(sizeof({name})), {id})")
}
/// The C fn of walk `w`: the fn of the walk of its class
fn fn_name(&self, w: usize) -> String {
let w = self.walk_rep[w];
format!("f{}_{w}", self.cache.walks[w].req.fn_id)
}
}
/// `s` as a C string literal. Every byte that is not plain printable ASCII is
/// an octal escape, so UTF-8 passes through unchanged
fn c_str(s: &str) -> String {
let mut lit = String::from("\"");
for b in s.bytes() {
match b {
b'"' | b'\\' => lit.extend(['\\', b as char]),
0x20..0x7f => lit.push(b as char),
_ => lit += &format!("\\{b:03o}"),
}
}
lit + "\""
}
fn is_num(rep: Rep) -> bool {
matches!(rep, Rep::Int | Rep::Double)
}
/// The struct of the objects of cell `id`
fn struct_name(id: CellNum) -> String {
format!("S{id}")
}
/// The static object of the built-in fn of cell `id`
fn builtin_name(id: CellNum) -> String {
format!("b{id}")
}
/// The global that holds what the host calls when an IO op completes
const HOST_COMPLETE_GLOBAL: &str = "jsrt_host_complete";
fn proto_global(kind: ProtoOf) -> String {
format!("proto_{kind:?}").to_lowercase()
}
fn is_terminator(op: OpCode) -> bool {
use OpCode::*;
matches!(
op,
Goto | Goto8 | Goto16 | IfFalse | IfFalse8 | IfTrue | IfTrue8 | Return | ReturnAsync | ReturnUndef
)
}
-231
View File
@@ -1,231 +0,0 @@
//! Field reads and writes, array elements, and prototype chains
use super::*;
impl<'a> Emitter<'a> {
/// `obj.field = value`. A primitive has no fields to write, and undefined
/// and null throw
pub(super) fn write_field(&mut self, res: &InferResult, instr: &RegInstr, out: &mut String) {
let (value, obj) = (instr.args[0], instr.args[1]);
let value_rep = self.rep(&res.val_types[value]);
let field = instr.imm.atom();
let value = format!("v{value}");
self.dispatch(&format!("v{obj}"), &res.val_types[obj], "jsrt_type_error();", out, |e, member, obj, _| {
match member {
Type::Obj(cell) if field == LENGTH_FIELD && cell.is_array() => {
let name = struct_name(e.num(cell));
Some(format!("set_length_{name}({obj}, {});", e.conv(&value, value_rep, Rep::Int)))
}
Type::Obj(cell) => {
let slot = e.slot_rep(&cell.field(field).unwrap().ty, cell.can_be_absent(field));
Some(format!("{obj}->{} = {};", e.field_name(field), e.conv(&value, value_rep, slot)))
}
Type::Undefined | Type::Null => None,
_ => Some(";".into()),
}
});
}
/// `dst = obj.field`: on an object, the read through its prototype chain;
/// on a primitive, the read on its kind's prototype, except that a string's
/// `length` is its own. Undefined and null throw
pub(super) fn read_field(&mut self, res: &InferResult, instr: &RegInstr, out: &mut String) {
let (obj, dst) = (instr.args[0], instr.dsts[0]);
let dst_rep = self.rep(&res.val_types[dst]);
let field = match instr.op {
OpCode::GetLength => LENGTH_FIELD,
_ => instr.imm.atom(),
};
self.dispatch(&format!("v{obj}"), &res.val_types[obj], "jsrt_type_error();", out, |e, member, obj, _| {
let (read, rep) = match member {
Type::Obj(cell) => e.read_on(cell, field, obj),
_ => e.read_primitive(member, obj, field)?,
};
Some(format!("v{dst} = {};", e.conv(&read, rep, dst_rep)))
});
}
/// `dst = a[i]` or `a[i] = value`, on an array, and `dst = s[i]` on a
/// string. A string cannot be written, and undefined and null throw
pub(super) fn element(&mut self, res: &InferResult, instr: &RegInstr, out: &mut String) {
let put = instr.op == OpCode::PutArrayEl;
let (index, obj) = (instr.args[put as usize], instr.args[put as usize + 1]);
let index = self.conv(&format!("v{index}"), self.rep(&res.val_types[index]), Rep::Int);
// A put has the value, a get the dst
let (v, v_rep) = match put {
true => (instr.args[0], self.rep(&res.val_types[instr.args[0]])),
false => (instr.dsts[0], self.rep(&res.val_types[instr.dsts[0]])),
};
self.dispatch(&format!("v{obj}"), &res.val_types[obj], "jsrt_type_error();", out, |e, member, obj, _| {
let id = match member {
Type::Obj(cell) => e.num(cell),
Type::Str(_) if !put => {
let c = format!("jsrt_string_at({obj}, {index})");
return Some(format!("v{v} = {};", e.conv(&c, Rep::OptStr, v_rep)));
}
_ => return None,
};
let (name, elem) = (struct_name(id), e.elem_rep(id));
Some(match put {
true => format!("set_elem_{name}({obj}, {index}, {});", e.conv(&format!("v{v}"), v_rep, elem)),
false => format!("v{v} = {};", e.conv(&format!("elem_{name}({obj}, {index})"), elem, v_rep)),
})
});
}
/// A read of `field` on the object of `cell` at `ptr`, and its rep. An own
/// field that is never absent is read in place. Any other read goes
/// through the prototype chain
pub(super) fn read_on(&mut self, cell: &Cell, field: AtomRef, ptr: &str) -> (String, Rep) {
if field == LENGTH_FIELD && cell.is_array() {
return (format!("{ptr}->length"), Rep::Int);
}
match cell.field(field) {
Some(f) if !cell.can_be_absent(field) => (format!("{ptr}->{}", self.field_name(field)), self.rep(&f.ty)),
_ => {
let (reader, rep) = self.field_reader(cell, field);
(format!("{reader}({ptr})"), rep)
}
}
}
/// A read of `field` on `e`, a primitive of type `ty`, and its rep: a
/// string's length, or a read on its kind's prototype. `None` if it throws
pub(super) fn read_primitive(&mut self, ty: &Type, e: &str, field: AtomRef) -> Option<(String, Rep)> {
let kind = ProtoOf::of(ty)?;
if kind == ProtoOf::String && field == LENGTH_FIELD {
return Some((format!("jsrt_string_length({e})"), Rep::Int));
}
Some(self.read_proto(kind, field))
}
/// A read of `field` on the prototype that the stdlib links for `kind`,
/// and its rep
pub(super) fn read_proto(&mut self, kind: ProtoOf, field: AtomRef) -> (String, Rep) {
match &self.cache.protos[kind as usize] {
Type::Unknown => ("(undefined_t){0}".into(), Rep::Undefined),
Type::Obj(cell) => self.read_on(&cell.clone(), field, &proto_global(kind)),
_ => self.proto_reader(kind, field),
}
}
/// The helper that reads `field` on the prototype that the stdlib links
/// for `kind`, where the prototype's type is a sum, and the rep it gives.
/// It reads on each member of the type
pub(super) fn proto_reader(&mut self, kind: ProtoOf, field: AtomRef) -> (String, Rep) {
let name = format!("get_{}_{}", proto_global(kind), self.field_name(field));
let proto = &self.cache.protos[kind as usize];
let ret = self.rep(&read_value(proto, field, &self.cache.protos));
if !self.helpers.insert(name.clone()) {
return (format!("{name}()"), ret);
}
let mut body = String::new();
self.dispatch(&proto_global(kind), proto, "jsrt_type_error();", &mut body, |e, member, proto, _| {
let Type::Obj(cell) = member else {
return None;
};
let (read, rep) = e.read_on(cell, field, proto);
Some(format!("return {};", e.conv(&read, rep, ret)))
});
self.helper(format!("static {} {name}(void)", self.c_type(ret)), body);
(format!("{name}()"), ret)
}
/// The helper that reads `field` on an object of `cell`, and the rep it
/// gives. It reads the own field, and where that can be absent, goes on to
/// the prototype. At the end of the chain, the field is undefined
pub(super) fn field_reader(&mut self, cell: &Cell, field: AtomRef) -> (String, Rep) {
let name = format!("get_{}_{}", struct_name(self.num(cell)), self.field_name(field));
let read = cell.read(field, &self.cache.protos);
let ret = self.rep(&read);
if !self.helpers.insert(name.clone()) {
return (name, ret);
}
let own = cell.field(field);
let absent = cell.can_be_absent(field);
let mut body = String::new();
if let Some(own) = &own {
let slot = self.slot_rep(&own.ty, absent);
let member = format!("o->{}", self.field_name(field));
let value = self.conv(&member, slot, ret);
match absent {
false => {
let _ = writeln!(body, " return {value};");
}
true => {
let _ = writeln!(body, " if ({}) return {value};", self.present(&member, slot));
}
}
}
// An array's prototype is the one that the stdlib links for arrays
if absent && cell.is_array() {
let (read, rep) = self.read_proto(ProtoOf::Array, field);
let _ = writeln!(body, " return {};", self.conv(&read, rep, ret));
} else if absent {
let proto_ty = cell.field(PROTO_FIELD).map_or(Type::Unknown, |f| f.ty);
let proto_absent = cell.can_be_absent(PROTO_FIELD);
let slot = self.slot_rep(&proto_ty, proto_absent);
let member = format!("o->{}", self.field_name(PROTO_FIELD));
let mut chain = String::new();
// On a prototype that is not an object, the chain ends
self.dispatch(&member, &proto_ty, "{}", &mut chain, |e, member, proto, _| {
let Type::Obj(proto_cell) = member else {
return None;
};
let (reader, rep) = e.field_reader(proto_cell, field);
Some(format!("return {};", e.conv(&format!("{reader}({proto})"), rep, ret)))
});
match (&proto_ty, proto_absent) {
(Type::Unknown, _) => {}
(_, true) => {
let _ = writeln!(body, " if ({}) {{\n{chain} }}", self.present(&member, slot));
}
(_, false) => body += &chain,
}
// The end of the chain. If the read cannot give undefined, the chain
// always finds the field before it
match read.members().contains(&Type::Undefined) {
true => {
let undefined = self.conv("(undefined_t){0}", Rep::Undefined, ret);
let _ = writeln!(body, " return {undefined};");
}
false => body += " abort();\n",
}
}
// A field that is defined, but that no value reaches: the read cannot
// run
if own.is_none() && !absent {
let _ = writeln!(body, " abort();");
}
let sig = format!("static {} {name}({} *o)", self.c_type(ret), struct_name(self.num(cell)));
self.helper(sig, body);
(name, ret)
}
}
-234
View File
@@ -1,234 +0,0 @@
//! How each type looks in C: its representation, the C type of that,
//! the struct of each class of cells, and the tagged union of each sum
use super::*;
/// A C representation
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Rep {
Undefined,
Null,
Bool,
Int,
Double,
Str, // a string, or in a field, the sentinel for absent
Obj(CellNum), // a pointer to an object of the cell, or in a field, the sentinel for absent
OptStr, // a sum of a string and null or undefined: `NULL` is null, and `JSRT_UNDEFINED` is undefined
OptObj(CellNum), // a sum of the objects of one cell and null or undefined: a pointer, with the same sentinels
Objs, // a sum of the objects of several cells, maybe with null or undefined: a pointer to the header
Sum(Kinds), // any other sum: a tagged union of its kinds (see `sum_defs()`)
Any, // any value: an `any_t`. The `Any` type, and the args of the runtime's ops on sums
}
/// A set of kinds of value, one bit for each `jsrt_kind` (see `lib/rt/any.h`)
pub(super) type Kinds = u8;
pub(super) const ABSENT: Kinds = 1 << 0;
/// The `jsrt_kind` of each kind, and its member in a tagged union, if it has a
/// value besides its kind
pub(super) const KINDS: [(&str, Option<(&str, &str)>); 8] = [
("ABSENT", None),
("UNDEFINED", None),
("NULL", None),
("BOOL", Some(("bool", "b"))),
("INT", Some(("int32_t", "i"))),
("FLOAT", Some(("double", "f"))),
("STRING", Some(("string_t", "s"))),
("OBJECT", Some(("void *", "o"))),
];
/// The kinds of the values of type `ty`
pub(super) fn kinds(ty: &Type) -> Kinds {
ty.members().iter().fold(0, |all, member| {
all | 1 << match member {
Type::Undefined => 1,
Type::Null => 2,
Type::Bool(_) => 3,
Type::Int => 4,
Type::Float => 5,
Type::Str(_) => 6,
_ => 7,
}
})
}
/// The C of the tagged union of each set of kinds in `sums`, and its
/// conversions to and from `any_t`. Every member of a union is at its start,
/// so a conversion copies the payload's bytes. A union has one kind of number
/// (see `join()`): the other converts. A kind that the union lacks is a
/// `TypeError`
pub(super) fn sum_defs(sums: &BTreeSet<Kinds>) -> String {
let mut out = String::new();
for &k in sums {
let has = |i: usize| k & 1 << i != 0;
let union: String = (0..8).filter(|&i| has(i)).filter_map(|i| KINDS[i].1).map(|(t, m)| format!(" {t} {m};")).collect();
let copy = |to: &str, from: &str| match union.is_empty() {
true => String::new(),
false => format!(" __builtin_memcpy(&{to}.as, &{from}.as, sizeof {to}.as);"),
};
let number = match (has(4), has(5)) {
(false, true) => " if (a.kind == JSRT_KIND_INT) { v.kind = JSRT_KIND_FLOAT; v.as.f = a.as.i; return v; }",
(true, false) => " if (a.kind == JSRT_KIND_FLOAT) { v.kind = JSRT_KIND_INT; v.as.i = (int32_t)a.as.f; return v; }",
_ => "",
};
let union = if union.is_empty() { String::new() } else { format!(" union {{{union} }} as;") };
let _ = writeln!(out, "typedef struct {{ jsrt_kind kind;{union} }} sum_{k};");
let _ = writeln!(out, "static inline any_t sum_{k}_any(sum_{k} v) {{ any_t a = {{ .kind = v.kind }};{} return a; }}", copy("a", "v"));
let _ = writeln!(
out,
"static inline sum_{k} any_sum_{k}(any_t a) {{ sum_{k} v = {{ .kind = a.kind }};{number} if (!({k}u >> a.kind & 1)) jsrt_type_error();{} return v; }}",
copy("v", "a")
);
}
out
}
impl<'a> Emitter<'a> {
/// The representation of a value of type `ty`
pub(super) fn rep(&self, ty: &Type) -> Rep {
match ty {
Type::Unknown | Type::Undefined => Rep::Undefined,
Type::Null => Rep::Null,
Type::Bool(_) => Rep::Bool,
Type::Int => Rep::Int,
Type::Float => Rep::Double,
Type::Str(_) => Rep::Str,
Type::Obj(cell) => Rep::Obj(self.num(cell)),
Type::Sum(_) => self.sum_rep(ty),
Type::Any => Rep::Any,
}
}
/// The representation of a sum. A string or the objects of one cell, with
/// null or undefined, is that pointer with sentinels. The objects of
/// several cells are a pointer to their header. Any other sum is an
/// `any_t`, which holds every kind
pub(super) fn sum_rep(&self, ty: &Type) -> Rep {
let others: Vec<&Type> = ty.members().iter().filter(|m| !m.is_nullish()).collect();
let mut classes: Vec<CellNum> = others.iter().filter_map(|m| self.cell_num(m)).collect();
classes.dedup();
match (&others[..], &classes[..]) {
([], _) => Rep::Sum(kinds(ty)),
([Type::Str(_)], _) => Rep::OptStr,
(_, [id]) if classes.len() == 1 && others.iter().all(|m| matches!(m, Type::Obj(_))) => Rep::OptObj(*id),
_ if others.iter().all(|m| matches!(m, Type::Obj(_))) => Rep::Objs,
_ => Rep::Sum(kinds(ty)),
}
}
/// The class of an `Obj` type
pub(super) fn cell_num(&self, ty: &Type) -> Option<CellNum> {
match ty {
Type::Obj(cell) => Some(self.num(cell)),
_ => None,
}
}
/// The representation of a field of type `ty`. A field that can be absent
/// needs a sentinel for absent: a pointer has one, and any other rep is a
/// tagged union that can also be absent
pub(super) fn slot_rep(&self, ty: &Type, absent: bool) -> Rep {
match self.rep(ty) {
rep @ (Rep::Str | Rep::Obj(_) | Rep::OptStr | Rep::OptObj(_) | Rep::Objs) => rep,
_ if absent => Rep::Sum(kinds(ty) | ABSENT),
rep => rep,
}
}
/// The representation of an element of an array of cell `id`: its type,
/// or undefined for a hole
pub(super) fn elem_rep(&self, id: CellNum) -> Rep {
self.rep(&self.cells[id as usize].read_elem())
}
pub(super) fn c_type(&self, rep: Rep) -> String {
match rep {
Rep::Undefined => "undefined_t".into(),
Rep::Null => "null_t".into(),
Rep::Bool => "bool".into(),
Rep::Int => "int32_t".into(),
Rep::Double => "double".into(),
Rep::Str | Rep::OptStr => "string_t".into(),
Rep::Obj(id) | Rep::OptObj(id) => format!("{} *", struct_name(id)),
Rep::Objs => "jsrt_header *".into(),
Rep::Sum(k) => {
self.sums.borrow_mut().insert(k);
format!("sum_{k}")
}
Rep::Any => "any_t".into(),
}
}
pub(super) fn field_name(&self, name: AtomRef) -> String {
let text = atom_name(name, self.atoms);
let text = text.chars().map(|c| if c.is_ascii_alphanumeric() { c } else { '_' });
format!("f_{}", text.collect::<String>())
}
/// The struct of cell `id`, and its helpers: `init_S`, which sets the
/// header and makes each field that can be absent absent, and for an
/// array, the helpers of its elements
pub(super) fn struct_def(&mut self, id: CellNum, out: &mut String) {
let cell = self.cells[id as usize].clone();
let name = struct_name(id);
let mut members = String::from("jsrt_header h;");
let mut body = String::from(" o->h.cell = cell;\n");
// An array's elements come after its header. `items` has room for
// `cap` of them. An element holds undefined for a hole
let elem = cell.is_array().then(|| self.elem_rep(id));
if let Some(elem) = elem {
let _ = write!(members, " int32_t length; int32_t cap; {} *items;", self.c_type(elem));
}
for (field, f) in &cell.info().fields {
let absent = cell.can_be_absent(*field);
let rep = self.slot_rep(&f.ty, absent);
let member = self.field_name(*field);
let _ = write!(members, " {} {member};", self.c_type(rep));
if absent {
let _ = writeln!(body, " o->{member} = {};", self.absent(rep));
}
}
let _ = writeln!(out, "struct {name} {{ {members} }};");
self.helper(
format!("static {name} *init_{name}({name} *o, uint32_t cell)"),
format!("{body} return o;\n"),
);
if let Some(elem) = elem {
let (t, hole) = (self.c_type(elem), self.conv("(undefined_t){0}", Rep::Undefined, elem));
self.helper(
format!("static void set_length_{name}({name} *a, int32_t n)"),
format!(
" if (n < 0) jsrt_type_error();\n\
\x20 a->items = jsrt_grow(a->items, &a->cap, n, sizeof *a->items);\n\
\x20 for (int32_t i = a->length; i < n; i++) a->items[i] = {hole};\n\
\x20 a->length = n;\n"
),
);
self.helper(
format!("static {t} elem_{name}({name} *a, int32_t i)"),
format!(" return i >= 0 && i < a->length ? a->items[i] : {hole};\n"),
);
self.helper(
format!("static void set_elem_{name}({name} *a, int32_t i, {t} v)"),
" if (i < 0) jsrt_type_error();\n if (i >= a->length) set_length_{NAME}(a, i + 1);\n a->items[i] = v;\n"
.replace("{NAME}", &name),
);
}
}
pub(super) fn helper(&mut self, sig: String, body: String) {
let _ = writeln!(self.helper_protos, "{sig};");
let _ = writeln!(self.helper_defs, "{sig} {{\n{body}}}\n");
}
}