Mid rewrite
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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],
|
||||
});
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user