Rewrite with working async

This commit is contained in:
2026-10-16 08:00:06 +00:00
parent d0c2f291e9
commit 1e4647347f
36 changed files with 5106 additions and 654 deletions
+397
View File
@@ -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");
}