New macro based emit

This commit is contained in:
2026-10-24 15:58:51 +00:00
parent cf455b88a8
commit 95e5c1225e
12 changed files with 1206 additions and 1587 deletions
+157 -214
View File
@@ -1,290 +1,233 @@
//! The C of the instrs on objects: fields and elements
//! The C of the instrs on objects, and of lookups
//!
//! 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
//! getting the field calls its lookup. A field that shares a location (see
//! `define_field_ref`) holds a pointer to it.
//!
//! An instr on a value that can be one of several members has C for each member (see
//! `Program::split`).
use super::{
js::JsFunction,
types::{CType, ClassKind, Member},
values::{as_index, in_range, undefined},
};
use super::classes::{ClassKind, Member};
use super::js::{JsFunction, UNDEFINED, indent};
use super::program::{Program, throw};
use crate::{
infer::LENGTH_FIELD,
libqjs::parse::opcodes::OpCode,
infer::{LENGTH_FIELD, ObjAddr},
libqjs::parse::{AtomRef, opcodes::OpCode},
regify::{Operand, RegInstr},
transform::PROTO_FIELD,
};
// ──────────────────────────────────────── Fields ────────────────────────────────────────
impl JsFunction<'_> {
pub(super) fn object_instr(&self, idx: usize, instr: &RegInstr) -> String {
use OpCode::*;
match instr.op {
Object | Fclosure => {
let class = self.class_here(idx);
let alloc = format!("(struct c{class} *)__rt_alloc(sizeof(struct c{class}))");
self.set(instr.dsts[0], &alloc)
}
ArrayFrom => self.array_from(idx, instr),
GetField | GetLength => self.get_field(instr),
DefineField | PutField => self.put_field(instr),
DefineFieldRef => self.share_field(instr),
CloseField => self.close_field(instr),
GetArrayEl => self.get_elem(instr),
PutArrayEl => self.put_elem(instr),
_ => unimplemented!("emit {instr:?}"),
}
}
/// The class of the objects that instr `idx` makes, in this walk's context
fn class_here(&self, idx: usize) -> usize {
let context = self.context;
self.program.classes.class(ObjAddr {
context,
instr: idx,
})
}
/// `[a, b, ...]`. The operands are the elements, last first
pub(super) fn array_from(&mut self, idx: usize, instr: &RegInstr) {
fn array_from(&self, idx: usize, instr: &RegInstr) -> String {
let class = self.class_here(idx);
let elems = self.program.types.classes[class].elems;
let array = self.body.temp(CType::One(Member::Obj(class)));
let n = instr.args.len();
let mut c = format!(
"struct c{class} *a;\nARRAY_NEW(a, {});\n",
instr.args.len()
);
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.body.line(format!("{array}->length = {n};"));
for (i, &slot) in instr.args.iter().rev().enumerate() {
let (value, ctype) = self.get(slot);
self.body
.put(&format!("{array}->items[{i}] = "), &value, ctype, elems);
for (i, &elem) in instr.args.iter().rev().enumerate() {
c += &format!("SET(a->items[{i}], {});\n", self.get(elem));
}
self.set_member(instr.dsts[0], &array, Member::Obj(class));
c += &self.set(instr.dsts[0], "a");
format!("{{\n{}}}\n", indent(&c))
}
/// `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) {
fn get_field(&self, instr: &RegInstr) -> String {
let program = self.program;
let dst = instr.dsts[0];
let field = match instr.op {
OpCode::GetLength => LENGTH_FIELD,
_ => instr.imm.atom(),
};
let dst = instr.dsts[0];
let cases = self.program.cases(&[self.get(instr.args[0])]);
for case in &cases {
self.body.open(case);
let (obj, member) = (&case.operands[0].0, case.operands[0].1);
match (member, self.program.field_class(member, field)) {
self.split(instr.args[0], |obj, member| {
match (member, program.field_class(member, field)) {
(Member::Obj(_), Some(class)) => {
let (value, ctype) = self.program.field_value(class, field, obj);
self.set(dst, &value, ctype);
self.set(dst, &program.field_value(class, field, obj))
}
(_, 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);
let (proto, _) = program.builtin_proto(member).unwrap();
self.set(dst, &program.field_value(class, field, &proto))
}
(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)
self.set(dst, &format!("__rt_string_length({obj})"))
}
(Member::Obj(_), None) => self.set(dst, &format!("{obj}->length")),
(Member::Undefined | Member::Null, _) => {
self.body.throw("cannot read a field of undefined or null")
throw("cannot read a field of undefined or null")
}
(Member::Native(_), _) => self.body.throw("a native function has no fields"),
_ => self.body.throw("fields of primitives need the stdlib"),
(Member::Native(_), _) => throw("a native function has no fields"),
_ => throw("fields of primitives need the stdlib"),
}
}
self.body.close(&cases);
})
}
/// `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) {
fn put_field(&self, instr: &RegInstr) -> String {
let program = self.program;
let field = instr.imm.atom();
let (value, from) = self.get(instr.args[0]);
let cases = self.program.cases(&[self.get(instr.args[1])]);
let value = self.get(instr.args[0]);
for case in &cases {
self.body.open(case);
let (obj, member) = (&case.operands[0].0, case.operands[0].1);
match (member, self.program.class_kind(member)) {
self.split(instr.args[1], |obj, member| {
match (member, program.class_kind(member)) {
(Member::Obj(_), ClassKind::Array) if field == LENGTH_FIELD => {
self.set_length(obj, &value, from)
format!("ARRAY_SET_LENGTH({obj}, {value});\n")
}
(Member::Obj(class), _) => {
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);
let f = program.classes.field(class, field).unwrap();
let set = format!("SET({}, {value});\n", program.field_access(obj, f));
if !f.defined {
let name = self.program.field_name(field);
self.body.line(format!("{obj}->has_{name} = true;"));
match f.defined {
true => set,
false => {
set + &format!("{obj}->has_{} = true;\n", program.field_name(field))
}
}
}
_ => self
.body
.throw("cannot set a field of a primitive, undefined or null"),
_ => throw("cannot set a field of a primitive, undefined or null"),
}
}
self.body.close(&cases);
}
/// `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}"),
_ => {
self.body.throw("an array's length must be a number");
continue;
}
};
let check = in_range(n, member, "INT32_MAX");
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 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) {
fn share_field(&self, instr: &RegInstr) -> String {
let name = self.program.field_name(instr.imm.atom());
let location = instr.args[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"),
},
let cell = match instr.args[0] {
Operand::Reg(_) => format!("{}->{name}", self.get(instr.args[0])),
slot => self.name(slot),
};
let cases = self.program.cases(&[self.get(instr.args[1])]);
for case in &cases {
self.body.open(case);
self.body
.line(format!("{}->{name} = {cell};", case.operands[0].0));
}
self.body.close(&cases);
self.split(instr.args[1], |obj, _| format!("{obj}->{name} = {cell};\n"))
}
/// `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 field = instr.imm.atom();
let cases = self.program.cases(&[self.get(instr.args[0])]);
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 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);
fn close_field(&self, instr: &RegInstr) -> String {
let name = self.program.field_name(instr.imm.atom());
self.split(instr.args[0], |obj, _| {
format!("NEW_CELL({obj}->{name});\n")
})
}
}
// ─────────────────────────────────────── Elements ───────────────────────────────────────
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 operands = [self.get(instr.args[1]), self.get(instr.args[0])];
let cases = self.program.cases(&operands);
fn get_elem(&self, instr: &RegInstr) -> String {
let (dst, index) = (self.get(instr.dsts[0]), self.get(instr.args[0]));
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, 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}, {at})"),
CType::One(Member::Str),
),
self.split(instr.args[1], |obj, member| {
match (member, self.program.class_kind(member)) {
(Member::Obj(_), ClassKind::Array) => {
format!("ARRAY_GET({dst}, {obj}, {index});\n")
}
(Member::Str, _) => format!("STRING_GET({dst}, {obj}, {index});\n"),
(Member::Undefined | Member::Null, _) => {
self.body
.throw("cannot read an element of undefined or null");
continue;
throw("cannot read an element of undefined or null")
}
_ => unreachable!("infer gets elements only of arrays and strings"),
};
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`. 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, from) = self.get(instr.args[0]);
let operands = [self.get(instr.args[2]), self.get(instr.args[1])];
let cases = self.program.cases(&operands);
/// `a[i] = v` on an array. Anything else throws
fn put_elem(&self, instr: &RegInstr) -> String {
let (value, index) = (self.get(instr.args[0]), self.get(instr.args[1]));
for case in &cases {
self.body.open(case);
let ((obj, obj_member), (index, index_member)) = (&case.operands[0], &case.operands[1]);
self.split(instr.args[2], |obj, member| {
match self.program.class_kind(member) {
ClassKind::Array => format!("ARRAY_PUT({obj}, {index}, {value});\n"),
_ => throw("cannot set an element of a value that is not an array"),
}
})
}
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 = self.program.types.classes[class].elems;
let at = as_index(index, *index_member);
let check = in_range(index, *index_member, "INT32_MAX");
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);
/// The C of `code` for each member that a local, arg or reg can be (see `Program::split`)
pub fn split(&self, slot: Operand, code: impl Fn(&str, Member) -> String) -> String {
self.program.split(&self.get(slot), &self.ty(slot), code)
}
}
/// The lookup of `field` on `o`, an object of `class`: its own field if it has it, else the
/// field on its prototype. An array's prototype is the stdlib's. Another object's is its
/// `__proto__` field, which it can lack. At the end of the chain, the field is undefined
pub fn lookup_body(program: &Program, class: usize, field: AtomRef) -> String {
let ctype = program.lookup_ctype(class, field).c_name();
let ret = |value: &str| format!("return CAST({ctype}, {value});\n");
let unless_lacks = |field: AtomRef| format!("if (o->has_{}) ", program.field_name(field));
let if_lacks = |field: AtomRef| format!("if (!o->has_{}) ", program.field_name(field));
let mut c = String::new();
if let Some(own) = program.classes.field(class, field) {
let value = program.field_access("o", own);
match own.defined {
true => c += &ret(&value),
false => c += &(unless_lacks(field) + &ret(&value)),
}
}
let proto = program.classes.field(class, PROTO_FIELD);
c += &match (program.classes.classes[class].kind, proto) {
(ClassKind::Array, _) => match program.builtin_proto(Member::Obj(class)) {
Some((proto, proto_class)) => ret(&program.field_value(proto_class, field, &proto)),
None => ret(UNDEFINED),
},
(_, None) => ret(UNDEFINED),
(_, Some(proto)) => {
let lacks = match proto.defined {
true => String::new(),
false => if_lacks(PROTO_FIELD) + &ret(UNDEFINED),
};
let value = program.field_access("o", proto);
let on_proto = program.split(&value, &proto.ty, |proto, member| match member {
Member::Obj(proto_class) => ret(&program.field_value(proto_class, field, proto)),
_ => ret(UNDEFINED),
});
lacks + &on_proto
}
};
indent(&c)
}