Rewrite with working async
This commit is contained in:
@@ -0,0 +1,397 @@
|
||||
//! The C of objects: fields, elements, and the read fns that follow a prototype chain
|
||||
//!
|
||||
//! A field is a member of its class's struct. A field that every object has from the start
|
||||
//! is read directly. Any other field can be absent: it has a `has_` flag, and a read of it
|
||||
//! is a read fn, which reads its own field if it has one, else reads on its prototype. A
|
||||
//! field that shares a location (see `define_field_ref`) holds a pointer to it
|
||||
|
||||
use std::fmt::Write as _;
|
||||
|
||||
use super::{
|
||||
Emitter,
|
||||
func::{Code, FnEmitter, Writer},
|
||||
layout::{ClassKind, Member, Rep},
|
||||
undefined,
|
||||
};
|
||||
use crate::{
|
||||
infer::{LENGTH_FIELD, Type, join},
|
||||
libqjs::parse::{AtomRef, opcodes::OpCode},
|
||||
regify::{Operand, RegInstr},
|
||||
transform::PROTO_FIELD,
|
||||
};
|
||||
|
||||
// ──────────────────────────────────────── Fields ────────────────────────────────────────
|
||||
|
||||
impl FnEmitter<'_, '_> {
|
||||
/// `[a, b, ...]`: the elements are the operands, last first
|
||||
pub(super) fn array_from(&mut self, idx: usize, instr: &RegInstr) {
|
||||
let class = self.class_here(idx);
|
||||
let elems = self.e.layout.classes[class].elems;
|
||||
let array = self.temp(Rep::One(Member::Obj(class)));
|
||||
let n = instr.args.len();
|
||||
|
||||
self.line(format!("{array} = __rt_alloc(sizeof(struct c{class}));"));
|
||||
self.line(format!(
|
||||
"{array}->items = __rt_grow(NULL, &{array}->cap, {n}, sizeof *{array}->items);"
|
||||
));
|
||||
self.line(format!("{array}->length = {n};"));
|
||||
|
||||
for (i, &slot) in instr.args.iter().rev().enumerate() {
|
||||
let (value, rep) = self.read(slot);
|
||||
let value = self.e.convert(&value, rep, elems);
|
||||
self.line(format!("{array}->items[{i}] = {value};"));
|
||||
}
|
||||
|
||||
self.set(instr.dsts[0], &array, Member::Obj(class));
|
||||
}
|
||||
|
||||
/// `o.f`, and `o.length`. A string's length is its own, and so is an array's. Undefined
|
||||
/// and null throw. A primitive reads its kind's prototype (see `Emitter::builtin_proto`)
|
||||
pub(super) fn get_field(&mut self, instr: &RegInstr) {
|
||||
let name = match instr.op {
|
||||
OpCode::GetLength => LENGTH_FIELD,
|
||||
_ => instr.imm.atom(),
|
||||
};
|
||||
let dst = instr.dsts[0];
|
||||
let ops = [self.read(instr.args[0])];
|
||||
|
||||
self.split(&ops, &mut |fe, ms| {
|
||||
let (obj, member) = (&ms[0].0, ms[0].1);
|
||||
|
||||
match (member, fe.class_kind(member)) {
|
||||
(Member::Str, _) if name == LENGTH_FIELD => {
|
||||
fe.set(dst, &format!("__rt_string_length({obj})"), Member::Int)
|
||||
}
|
||||
(Member::Obj(_), ClassKind::Array) if name == LENGTH_FIELD => {
|
||||
fe.set(dst, &format!("{obj}->length"), Member::Int)
|
||||
}
|
||||
(Member::Obj(class), _) => {
|
||||
let (value, rep) = read_field(fe.e, class, name, obj);
|
||||
fe.assign(dst, &value, rep);
|
||||
}
|
||||
(Member::Undefined | Member::Null, _) => {
|
||||
fe.throw("cannot read a field of undefined or null")
|
||||
}
|
||||
(Member::Native(_), _) => fe.throw("a native fn has no fields"),
|
||||
_ => match fe.e.builtin_proto(member) {
|
||||
Some((proto, Rep::One(Member::Obj(proto_class)))) => {
|
||||
let (value, rep) = read_field(fe.e, proto_class, name, &proto);
|
||||
fe.assign(dst, &value, rep);
|
||||
}
|
||||
_ => fe.throw("fields of primitives need the stdlib"),
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// `o.f = v`, and a field made in an object literal. A primitive has no fields to write,
|
||||
/// and undefined and null throw. A write of an array's `length` sets its length
|
||||
pub(super) fn put_field(&mut self, instr: &RegInstr) {
|
||||
let name = instr.imm.atom();
|
||||
let value = self.read(instr.args[0]);
|
||||
let ops = [self.read(instr.args[1])];
|
||||
|
||||
self.split(&ops, &mut |fe, ms| {
|
||||
let (obj, member) = (&ms[0].0, ms[0].1);
|
||||
|
||||
match (member, fe.class_kind(member)) {
|
||||
(Member::Obj(_), ClassKind::Array) if name == LENGTH_FIELD => {
|
||||
fe.set_length(obj, &value);
|
||||
}
|
||||
(Member::Obj(class), _) => {
|
||||
let field = fe.e.layout.field(class, name).unwrap().clone();
|
||||
let target = fe.e.field_access(obj, &field);
|
||||
let v = fe.e.convert(&value.0, value.1, field.rep);
|
||||
fe.line(format!("{target} = {v};"));
|
||||
|
||||
if !field.defined {
|
||||
fe.line(format!("{obj}->has_{} = true;", fe.e.field_name(name)));
|
||||
}
|
||||
}
|
||||
_ => fe.throw("cannot set a field of a primitive, undefined or null"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// `a.length = n`: the array gets `n` elements, the new ones holes. `n` must be an array
|
||||
/// index: a whole number from 0, else it throws, as JS's RangeError
|
||||
fn set_length(&mut self, obj: &str, value: &(String, Rep)) {
|
||||
self.split(std::slice::from_ref(value), &mut |fe, ms| {
|
||||
let (n, member) = (&ms[0].0, ms[0].1);
|
||||
|
||||
let length = match member {
|
||||
Member::Int => n.clone(),
|
||||
Member::Float => format!("(int32_t){n}"),
|
||||
_ => return fe.throw("an array's length must be a number"),
|
||||
};
|
||||
|
||||
let check = in_range(n, member, "INT32_MAX");
|
||||
fe.line(format!("if (!({check})) __rt_type_error(\"an array's length must be a whole number from 0\");"));
|
||||
fe.line(format!("{obj}->items = __rt_grow({obj}->items, &{obj}->cap, {length}, sizeof *{obj}->items);"));
|
||||
fe.line(format!("{obj}->length = {length};"));
|
||||
});
|
||||
}
|
||||
|
||||
/// `define_field_ref`: the field becomes the location that regify gives as the first
|
||||
/// operand. That is a captured binding, whose C local points to its cell, or the reg of
|
||||
/// an object whose field of the same name points to one (see regify/walk.rs)
|
||||
pub(super) fn share_field(&mut self, instr: &RegInstr) {
|
||||
let name = instr.imm.atom();
|
||||
let c_name = self.e.field_name(name);
|
||||
let location = instr.args[0];
|
||||
let ops = [self.read(instr.args[1])];
|
||||
|
||||
self.split(&ops, &mut |fe, ms| {
|
||||
let obj = &ms[0].0;
|
||||
|
||||
match location {
|
||||
Operand::Reg(_) => {
|
||||
let holder = fe.read(location);
|
||||
|
||||
fe.split(&[holder], &mut |fe, ms| match ms[0].1 {
|
||||
Member::Obj(_) => {
|
||||
fe.line(format!("{obj}->{c_name} = {}->{c_name};", ms[0].0))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
});
|
||||
}
|
||||
slot => {
|
||||
let cell = fe.name(slot);
|
||||
fe.line(format!("{obj}->{c_name} = {cell};"));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// `close_field x`: the field, which shares a location, gets a new cell with its value.
|
||||
/// Closures that shared the old cell keep it (see transform/closure.rs)
|
||||
pub(super) fn close_field(&mut self, instr: &RegInstr) {
|
||||
let name = instr.imm.atom();
|
||||
let ops = [self.read(instr.args[0])];
|
||||
|
||||
self.split(&ops, &mut |fe, ms| {
|
||||
let (obj, member) = (&ms[0].0, ms[0].1);
|
||||
let Member::Obj(class) = member else { unreachable!("a capture is held by an object") };
|
||||
let field = fe.e.layout.field(class, name).unwrap().clone();
|
||||
assert!(field.shared, "close_field of a field that shares no location");
|
||||
|
||||
let (ctype, c_name) = (fe.e.ctype(field.rep), fe.e.field_name(name));
|
||||
fe.line(format!("{{ {ctype} *cell = __rt_alloc(sizeof({ctype})); *cell = *{obj}->{c_name}; {obj}->{c_name} = cell; }}"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────── Elements ───────────────────────────────────────
|
||||
|
||||
impl FnEmitter<'_, '_> {
|
||||
/// `a[i]` on an array, and `s[i]` on a string. An index that is not in it gives
|
||||
/// undefined. Undefined and null throw
|
||||
pub(super) fn get_elem(&mut self, instr: &RegInstr) {
|
||||
let dst = instr.dsts[0];
|
||||
let ops = [self.read(instr.args[1]), self.read(instr.args[0])];
|
||||
|
||||
self.split(&ops, &mut |fe, ms| {
|
||||
let ((obj, om), (index, im)) = (&ms[0], &ms[1]);
|
||||
|
||||
let (length, item, rep) = match (*om, fe.class_kind(*om)) {
|
||||
(Member::Obj(class), ClassKind::Array) => {
|
||||
let elems = fe.e.layout.classes[class].elems;
|
||||
(
|
||||
format!("{obj}->length"),
|
||||
format!("{obj}->items[{}]", as_index(index, *im)),
|
||||
elems,
|
||||
)
|
||||
}
|
||||
(Member::Str, _) => (
|
||||
format!("__rt_string_length({obj})"),
|
||||
format!("__rt_string_at({obj}, {})", as_index(index, *im)),
|
||||
Rep::One(Member::Str),
|
||||
),
|
||||
(Member::Undefined | Member::Null, _) => {
|
||||
fe.throw("cannot read an element of undefined or null");
|
||||
return;
|
||||
}
|
||||
_ => unreachable!("infer reads elements only of arrays and strings"),
|
||||
};
|
||||
|
||||
fe.line(format!("if ({}) {{", in_range(index, *im, &length)));
|
||||
fe.indent(1);
|
||||
fe.assign(dst, &item, rep);
|
||||
fe.indent(-1);
|
||||
fe.line("} else {");
|
||||
fe.indent(1);
|
||||
let (value, rep) = undefined();
|
||||
fe.assign(dst, &value, rep);
|
||||
fe.indent(-1);
|
||||
fe.line("}");
|
||||
});
|
||||
}
|
||||
|
||||
/// `a[i] = v` on an array, which grows to hold `i`. A write to a string, or to undefined
|
||||
/// or null, throws, and so does an index that is not an array index, which would be a
|
||||
/// named field
|
||||
pub(super) fn put_elem(&mut self, instr: &RegInstr) {
|
||||
let value = self.read(instr.args[0]);
|
||||
let ops = [self.read(instr.args[2]), self.read(instr.args[1])];
|
||||
|
||||
self.split(&ops, &mut |fe, ms| {
|
||||
let ((obj, om), (index, im)) = (&ms[0], &ms[1]);
|
||||
|
||||
let (Member::Obj(class), ClassKind::Array) = (*om, fe.class_kind(*om)) else {
|
||||
fe.throw("cannot set an element of a value that is not an array");
|
||||
return;
|
||||
};
|
||||
|
||||
let elems = fe.e.layout.classes[class].elems;
|
||||
let i = as_index(index, *im);
|
||||
let v = fe.e.convert(&value.0, value.1, elems);
|
||||
|
||||
fe.line(format!("if (!({})) __rt_type_error(\"an array index must be a whole number from 0\");", in_range(index, *im, "INT32_MAX")));
|
||||
fe.line(format!("if ({i} >= {obj}->length) {{"));
|
||||
fe.indent(1);
|
||||
fe.line(format!("{obj}->items = __rt_grow({obj}->items, &{obj}->cap, {i} + 1, sizeof *{obj}->items);"));
|
||||
fe.line(format!("{obj}->length = {i} + 1;"));
|
||||
fe.indent(-1);
|
||||
fe.line("}");
|
||||
fe.line(format!("{obj}->items[{i}] = {v};"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// An index of member `member` as an `int32_t`
|
||||
fn as_index(index: &str, member: Member) -> String {
|
||||
match member {
|
||||
Member::Int => index.into(),
|
||||
Member::Float => format!("(int32_t){index}"),
|
||||
_ => unreachable!("an index is a number (see infer)"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether an index is an array index below `length`. A float must be a whole number
|
||||
fn in_range(index: &str, member: Member, length: &str) -> String {
|
||||
match member {
|
||||
Member::Int => format!("{index} >= 0 && {index} < {length}"),
|
||||
_ => format!("{index} >= 0 && {index} < {length} && {index} == (double)(int32_t){index}"),
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────── Read fns ───────────────────────────────────────
|
||||
|
||||
/// The read of field `name` on `obj`, an object of `class`: the field itself if every object
|
||||
/// has it, else a call of the read fn. Gives the value and its C type
|
||||
fn read_field(e: &mut Emitter, class: usize, name: AtomRef, obj: &str) -> (String, Rep) {
|
||||
if let Some(field) = e.layout.field(class, name).filter(|f| f.defined) {
|
||||
return (e.field_access(obj, field), field.rep);
|
||||
}
|
||||
|
||||
let (id, rep) = read_fn(e, class, name);
|
||||
(format!("rd{id}({obj})"), rep)
|
||||
}
|
||||
|
||||
/// The read fn of field `name` on `class`, wanted the first time. Gives its number and what
|
||||
/// it gives
|
||||
fn read_fn(e: &mut Emitter, class: usize, name: AtomRef) -> (usize, Rep) {
|
||||
if let Some(&found) = e.reads.get(&(class, name)) {
|
||||
return found;
|
||||
}
|
||||
|
||||
// What it gives is the join of the reads on the class's objects (see `Memory::read_field`)
|
||||
let objs = e.layout.classes[class].objs.clone();
|
||||
let ty = objs.iter().fold(Type::Unknown, |all, &obj| {
|
||||
join(&all, &e.cache.memory.read_field(&Type::Ref(obj), name))
|
||||
});
|
||||
let rep = e.layout.rep(&ty);
|
||||
|
||||
let id = e.reads.len();
|
||||
e.reads.insert((class, name), (id, rep));
|
||||
e.read_queue.push_back((class, name));
|
||||
(id, rep)
|
||||
}
|
||||
|
||||
/// Prints read fns
|
||||
struct ReadEmitter<'e, 'a> {
|
||||
e: &'e mut Emitter<'a>,
|
||||
code: Code,
|
||||
}
|
||||
|
||||
impl<'a> Writer<'a> for ReadEmitter<'_, 'a> {
|
||||
fn code(&mut self) -> &mut Code {
|
||||
&mut self.code
|
||||
}
|
||||
|
||||
fn emitter(&mut self) -> &mut Emitter<'a> {
|
||||
self.e
|
||||
}
|
||||
}
|
||||
|
||||
impl ReadEmitter<'_, '_> {
|
||||
/// Return a value, as the read's type
|
||||
fn ret(&mut self, (value, from): (String, Rep), rep: Rep) {
|
||||
let value = self.e.convert(&value, from, rep);
|
||||
self.line(format!("return {value};"));
|
||||
}
|
||||
}
|
||||
|
||||
/// Print the read fn of field `name` on `class`: its own field if it has it, else the read on
|
||||
/// its prototype. At the end of the chain, the field is undefined. An array's prototype is
|
||||
/// the stdlib's (see `Emitter::builtin_proto`)
|
||||
pub fn emit_read(e: &mut Emitter, class: usize, name: AtomRef) {
|
||||
let (id, rep) = e.reads[&(class, name)];
|
||||
let signature = format!("static {} rd{id}(struct c{class} *o)", e.ctype(rep));
|
||||
let _ = writeln!(e.protos, "{signature};");
|
||||
|
||||
let code = Code::default();
|
||||
let mut re = ReadEmitter { e, code };
|
||||
re.indent(1);
|
||||
|
||||
if let Some(field) = re.e.layout.field(class, name).cloned() {
|
||||
let own = re.e.field_access("o", &field);
|
||||
let value = re.e.convert(&own, field.rep, rep);
|
||||
|
||||
match field.defined {
|
||||
true => re.line(format!("return {value};")),
|
||||
false => re.line(format!(
|
||||
"if (o->has_{}) return {value};",
|
||||
re.e.field_name(name)
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
let proto = re.e.layout.field(class, PROTO_FIELD).cloned();
|
||||
|
||||
match (re.e.layout.classes[class].kind, proto) {
|
||||
// An array's prototype is the stdlib's (see `Emitter::builtin_proto`)
|
||||
(ClassKind::Array, _) => match re.e.builtin_proto(Member::Obj(class)) {
|
||||
Some((proto, Rep::One(Member::Obj(proto_class)))) => {
|
||||
let read = read_field(re.e, proto_class, name, &proto);
|
||||
re.ret(read, rep);
|
||||
}
|
||||
_ => re.ret(undefined(), rep),
|
||||
},
|
||||
(_, None) => re.ret(undefined(), rep),
|
||||
(_, Some(proto)) => {
|
||||
if !proto.defined {
|
||||
re.line(format!("if (!o->has_{}) {{", re.e.field_name(PROTO_FIELD)));
|
||||
re.indent(1);
|
||||
re.ret(undefined(), rep);
|
||||
re.indent(-1);
|
||||
re.line("}");
|
||||
}
|
||||
|
||||
let value = re.e.field_access("o", &proto);
|
||||
|
||||
re.split(&[(value, proto.rep)], &mut |re, ms| match ms[0].1 {
|
||||
Member::Obj(proto_class) => {
|
||||
let read = read_field(re.e, proto_class, name, &ms[0].0);
|
||||
re.ret(read, rep);
|
||||
}
|
||||
_ => re.ret(undefined(), rep),
|
||||
});
|
||||
|
||||
re.line("abort();");
|
||||
}
|
||||
}
|
||||
|
||||
let body = std::mem::take(&mut re.code.out);
|
||||
let _ = write!(re.e.read_defs, "{signature} {{\n{body}}}\n\n");
|
||||
}
|
||||
Reference in New Issue
Block a user