This commit is contained in:
2026-10-22 13:05:04 +00:00
parent 1e4647347f
commit a80bf6f19a
10 changed files with 1789 additions and 1519 deletions
+196 -303
View File
@@ -1,397 +1,290 @@
//! The C of objects: fields, elements, and the read fns that follow a prototype chain
//! The C of the instrs on objects: fields and elements
//!
//! A field is a member of its class's struct. A field that every object has from the start
//! 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 _;
//! A field is a member of its class's struct. If every object of the class has the field from
//! the start, the field is used directly. Else the struct has a `has_` flag for the field, and
//! getting the field calls its lookup (see `functions`). A field that shares a location (see
//! `define_field_ref`) holds a pointer to it.
use super::{
Emitter,
func::{Code, FnEmitter, Writer},
layout::{ClassKind, Member, Rep},
undefined,
js::JsFunction,
types::{CType, ClassKind, Member},
values::{as_index, in_range, undefined},
};
use crate::{
infer::{LENGTH_FIELD, Type, join},
libqjs::parse::{AtomRef, opcodes::OpCode},
infer::LENGTH_FIELD,
libqjs::parse::opcodes::OpCode,
regify::{Operand, RegInstr},
transform::PROTO_FIELD,
};
// ──────────────────────────────────────── Fields ────────────────────────────────────────
impl FnEmitter<'_, '_> {
/// `[a, b, ...]`: the elements are the operands, last first
impl JsFunction<'_> {
/// `[a, b, ...]`. The operands are the elements, 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 elems = self.program.types.classes[class].elems;
let array = self.body.temp(CType::One(Member::Obj(class)));
let n = instr.args.len();
self.line(format!("{array} = __rt_alloc(sizeof(struct c{class}));"));
self.line(format!(
self.body
.line(format!("{array} = __rt_alloc(sizeof(struct c{class}));"));
self.body.line(format!(
"{array}->items = __rt_grow(NULL, &{array}->cap, {n}, sizeof *{array}->items);"
));
self.line(format!("{array}->length = {n};"));
self.body.line(format!("{array}->length = {n};"));
for (i, &slot) in instr.args.iter().rev().enumerate() {
let (value, rep) = self.read(slot);
let value = self.e.convert(&value, rep, elems);
self.line(format!("{array}->items[{i}] = {value};"));
let (value, ctype) = self.get(slot);
self.body
.put(&format!("{array}->items[{i}] = "), &value, ctype, elems);
}
self.set(instr.dsts[0], &array, Member::Obj(class));
self.set_member(instr.dsts[0], &array, Member::Obj(class));
}
/// `o.f`, and `o.length`. A string's length is its own, and so is an array's. Undefined
/// and null throw. A primitive reads its kind's prototype (see `Emitter::builtin_proto`)
/// `o.f` and `o.length`. A string has its own length, and so does an array. A primitive
/// gets the field from the stdlib's prototype of its kind (see `Program::field_class`).
/// Undefined and null throw
pub(super) fn get_field(&mut self, instr: &RegInstr) {
let name = match instr.op {
let field = match instr.op {
OpCode::GetLength => LENGTH_FIELD,
_ => instr.imm.atom(),
};
let dst = instr.dsts[0];
let ops = [self.read(instr.args[0])];
let cases = self.program.cases(&[self.get(instr.args[0])]);
self.split(&ops, &mut |fe, ms| {
let (obj, member) = (&ms[0].0, ms[0].1);
for case in &cases {
self.body.open(case);
let (obj, member) = (&case.operands[0].0, case.operands[0].1);
match (member, fe.class_kind(member)) {
(Member::Str, _) if name == LENGTH_FIELD => {
fe.set(dst, &format!("__rt_string_length({obj})"), Member::Int)
match (member, self.program.field_class(member, field)) {
(Member::Obj(_), Some(class)) => {
let (value, ctype) = self.program.field_value(class, field, obj);
self.set(dst, &value, ctype);
}
(Member::Obj(_), ClassKind::Array) if name == LENGTH_FIELD => {
fe.set(dst, &format!("{obj}->length"), Member::Int)
(_, Some(class)) => {
let (proto, _) = self.program.builtin_proto(member).unwrap();
let (value, ctype) = self.program.field_value(class, field, &proto);
self.set(dst, &value, ctype);
}
(Member::Obj(class), _) => {
let (value, rep) = read_field(fe.e, class, name, obj);
fe.assign(dst, &value, rep);
(Member::Str, None) if field == LENGTH_FIELD => {
let length = format!("__rt_string_length({obj})");
self.set_member(dst, &length, Member::Int);
}
(Member::Obj(_), None) => {
self.set_member(dst, &format!("{obj}->length"), Member::Int)
}
(Member::Undefined | Member::Null, _) => {
fe.throw("cannot read a field of undefined or null")
self.body.throw("cannot read a field of undefined or null")
}
(Member::Native(_), _) => fe.throw("a native fn has no fields"),
_ => 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"),
},
(Member::Native(_), _) => self.body.throw("a native function has no fields"),
_ => self.body.throw("fields of primitives need the stdlib"),
}
});
}
self.body.close(&cases);
}
/// `o.f = v`, and a field made in an object literal. A primitive has no fields to write,
/// and undefined and null throw. A write of an array's `length` sets its length
/// `o.f = v`, and a field in an object literal. Setting an array's `length` sets its
/// length. A primitive has no fields to set, and undefined and null throw
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])];
let field = instr.imm.atom();
let (value, from) = self.get(instr.args[0]);
let cases = self.program.cases(&[self.get(instr.args[1])]);
self.split(&ops, &mut |fe, ms| {
let (obj, member) = (&ms[0].0, ms[0].1);
for case in &cases {
self.body.open(case);
let (obj, member) = (&case.operands[0].0, case.operands[0].1);
match (member, fe.class_kind(member)) {
(Member::Obj(_), ClassKind::Array) if name == LENGTH_FIELD => {
fe.set_length(obj, &value);
match (member, self.program.class_kind(member)) {
(Member::Obj(_), ClassKind::Array) if field == LENGTH_FIELD => {
self.set_length(obj, &value, from)
}
(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};"));
let f = self.program.types.field(class, field).unwrap();
let target = self.program.field_access(obj, f);
self.body
.put(&format!("{target} = "), &value, from, f.ctype);
if !field.defined {
fe.line(format!("{obj}->has_{} = true;", fe.e.field_name(name)));
if !f.defined {
let name = self.program.field_name(field);
self.body.line(format!("{obj}->has_{name} = true;"));
}
}
_ => fe.throw("cannot set a field of a primitive, undefined or null"),
_ => self
.body
.throw("cannot set a field of a primitive, undefined or null"),
}
});
}
self.body.close(&cases);
}
/// `a.length = n`: the array gets `n` elements, the new ones holes. `n` must be an array
/// 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);
/// `a.length = n`: the array gets `n` elements, and the new ones are holes. `n` must be a
/// whole number from 0, else it throws, as JS's RangeError does
fn set_length(&mut self, obj: &str, value: &str, from: CType) {
let cases = self.program.cases(&[(value.to_string(), from)]);
for case in &cases {
self.body.open(case);
let (n, member) = (&case.operands[0].0, case.operands[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"),
_ => {
self.body.throw("an array's length must be a number");
continue;
}
};
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};"));
});
self.body.line(format!("if (!({check})) __rt_type_error(\"an array's length must be a whole number from 0\");"));
self.body.line(format!("{obj}->items = __rt_grow({obj}->items, &{obj}->cap, {length}, sizeof *{obj}->items);"));
self.body.line(format!("{obj}->length = {length};"));
}
self.body.close(&cases);
}
/// `define_field_ref`: the field becomes the location that regify gives as the first
/// 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)
/// `define_field_ref`: the field becomes a pointer to the cell of the location in the
/// first operand. That location is a shared local or arg, whose C local points to its
/// cell, or a reg that holds an object whose field of the same name points to the cell
/// (see regify/walk.rs)
pub(super) fn share_field(&mut self, instr: &RegInstr) {
let name = instr.imm.atom();
let c_name = self.e.field_name(name);
let name = self.program.field_name(instr.imm.atom());
let location = instr.args[0];
let ops = [self.read(instr.args[1])];
self.split(&ops, &mut |fe, ms| {
let obj = &ms[0].0;
let cell = match location {
Operand::Reg(_) => match self.get(location) {
(holder, CType::One(Member::Obj(_))) => format!("{holder}->{name}"),
_ => unreachable!("a shared location is held by an object"),
},
slot => self.name(slot),
};
match location {
Operand::Reg(_) => {
let holder = fe.read(location);
let cases = self.program.cases(&[self.get(instr.args[1])]);
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};"));
}
}
});
for case in &cases {
self.body.open(case);
self.body
.line(format!("{}->{name} = {cell};", case.operands[0].0));
}
self.body.close(&cases);
}
/// `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)
/// `close_field x`: the field, which points to a cell, gets a new cell with the same
/// value. Closures that point to 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])];
let field = instr.imm.atom();
let cases = self.program.cases(&[self.get(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");
for case in &cases {
self.body.open(case);
let (obj, member) = (&case.operands[0].0, case.operands[0].1);
let Member::Obj(class) = member else {
unreachable!("a shared location is held by an object")
};
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; }}"));
});
let f = self.program.types.field(class, field).unwrap();
assert!(f.shared, "close_field of a field that is not a cell");
let (ctype, name) = (f.ctype.c_name(), self.program.field_name(field));
self.body.line(format!("{{ {ctype} *cell = __rt_alloc(sizeof({ctype})); *cell = *{obj}->{name}; {obj}->{name} = cell; }}"));
}
self.body.close(&cases);
}
}
// ─────────────────────────────────────── Elements ───────────────────────────────────────
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
impl JsFunction<'_> {
/// `a[i]` on an array, and `s[i]` on a string. An index out of range 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])];
let operands = [self.get(instr.args[1]), self.get(instr.args[0])];
let cases = self.program.cases(&operands);
self.split(&ops, &mut |fe, ms| {
let ((obj, om), (index, im)) = (&ms[0], &ms[1]);
for case in &cases {
self.body.open(case);
let ((obj, obj_member), (index, index_member)) = (&case.operands[0], &case.operands[1]);
let at = as_index(index, *index_member);
let (length, item, rep) = match (*om, fe.class_kind(*om)) {
(Member::Obj(class), ClassKind::Array) => {
let elems = fe.e.layout.classes[class].elems;
(
format!("{obj}->length"),
format!("{obj}->items[{}]", as_index(index, *im)),
elems,
)
}
let (length, item, ctype) = match (*obj_member, self.program.class_kind(*obj_member)) {
(Member::Obj(class), ClassKind::Array) => (
format!("{obj}->length"),
format!("{obj}->items[{at}]"),
self.program.types.classes[class].elems,
),
(Member::Str, _) => (
format!("__rt_string_length({obj})"),
format!("__rt_string_at({obj}, {})", as_index(index, *im)),
Rep::One(Member::Str),
format!("__rt_string_at({obj}, {at})"),
CType::One(Member::Str),
),
(Member::Undefined | Member::Null, _) => {
fe.throw("cannot read an element of undefined or null");
return;
self.body
.throw("cannot read an element of undefined or null");
continue;
}
_ => unreachable!("infer reads elements only of arrays and strings"),
_ => unreachable!("infer gets elements only of arrays and strings"),
};
fe.line(format!("if ({}) {{", in_range(index, *im, &length)));
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("}");
});
let (none, none_ctype) = undefined();
let check = in_range(index, *index_member, &length);
self.body.line(format!("if ({check}) {{"));
self.body.indent(1);
self.set(dst, &item, ctype);
self.body.indent(-1);
self.body.line("} else {");
self.body.indent(1);
self.set(dst, &none, none_ctype);
self.body.indent(-1);
self.body.line("}");
}
self.body.close(&cases);
}
/// `a[i] = v` on an array, which grows to hold `i`. A write to a string, or to undefined
/// or null, throws, and so does an index that is not an array index, which would be a
/// named field
/// `a[i] = v` on an array, which grows to hold `i`. Setting an element of a string, of
/// undefined or of null throws. So does an index that is not a whole number from 0,
/// because it 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])];
let (value, from) = self.get(instr.args[0]);
let operands = [self.get(instr.args[2]), self.get(instr.args[1])];
let cases = self.program.cases(&operands);
self.split(&ops, &mut |fe, ms| {
let ((obj, om), (index, im)) = (&ms[0], &ms[1]);
for case in &cases {
self.body.open(case);
let ((obj, obj_member), (index, index_member)) = (&case.operands[0], &case.operands[1]);
let (Member::Obj(class), ClassKind::Array) = (*om, fe.class_kind(*om)) else {
fe.throw("cannot set an element of a value that is not an array");
return;
let (Member::Obj(class), ClassKind::Array) =
(*obj_member, self.program.class_kind(*obj_member))
else {
self.body
.throw("cannot set an element of a value that is not an array");
continue;
};
let elems = fe.e.layout.classes[class].elems;
let i = as_index(index, *im);
let v = fe.e.convert(&value.0, value.1, elems);
let elems = self.program.types.classes[class].elems;
let at = as_index(index, *index_member);
let check = in_range(index, *index_member, "INT32_MAX");
fe.line(format!("if (!({})) __rt_type_error(\"an array index must be a whole number from 0\");", in_range(index, *im, "INT32_MAX")));
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)
)),
self.body.line(format!(
"if (!({check})) __rt_type_error(\"an array index must be a whole number from 0\");"
));
self.body.line(format!("if ({at} >= {obj}->length) {{"));
self.body.indent(1);
self.body.line(format!("{obj}->items = __rt_grow({obj}->items, &{obj}->cap, {at} + 1, sizeof *{obj}->items);"));
self.body.line(format!("{obj}->length = {at} + 1;"));
self.body.indent(-1);
self.body.line("}");
self.body
.put(&format!("{obj}->items[{at}] = "), &value, from, elems);
}
self.body.close(&cases);
}
let proto = re.e.layout.field(class, PROTO_FIELD).cloned();
match (re.e.layout.classes[class].kind, proto) {
// An array's prototype is the stdlib's (see `Emitter::builtin_proto`)
(ClassKind::Array, _) => match re.e.builtin_proto(Member::Obj(class)) {
Some((proto, Rep::One(Member::Obj(proto_class)))) => {
let read = read_field(re.e, proto_class, name, &proto);
re.ret(read, rep);
}
_ => re.ret(undefined(), rep),
},
(_, None) => re.ret(undefined(), rep),
(_, Some(proto)) => {
if !proto.defined {
re.line(format!("if (!o->has_{}) {{", re.e.field_name(PROTO_FIELD)));
re.indent(1);
re.ret(undefined(), rep);
re.indent(-1);
re.line("}");
}
let value = re.e.field_access("o", &proto);
re.split(&[(value, proto.rep)], &mut |re, ms| match ms[0].1 {
Member::Obj(proto_class) => {
let read = read_field(re.e, proto_class, name, &ms[0].0);
re.ret(read, rep);
}
_ => re.ret(undefined(), rep),
});
re.line("abort();");
}
}
let body = std::mem::take(&mut re.code.out);
let _ = write!(re.e.read_defs, "{signature} {{\n{body}}}\n\n");
}