Files
jsrt/compiler/src/emit/objects.rs
T
2026-10-24 15:58:51 +00:00

234 lines
9.5 KiB
Rust

//! 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. 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::classes::{ClassKind, Member};
use super::js::{JsFunction, UNDEFINED, indent};
use super::program::{Program, throw};
use crate::{
infer::{LENGTH_FIELD, ObjAddr},
libqjs::parse::{AtomRef, opcodes::OpCode},
regify::{Operand, RegInstr},
transform::PROTO_FIELD,
};
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
fn array_from(&self, idx: usize, instr: &RegInstr) -> String {
let class = self.class_here(idx);
let mut c = format!(
"struct c{class} *a;\nARRAY_NEW(a, {});\n",
instr.args.len()
);
for (i, &elem) in instr.args.iter().rev().enumerate() {
c += &format!("SET(a->items[{i}], {});\n", self.get(elem));
}
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
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(),
};
self.split(instr.args[0], |obj, member| {
match (member, program.field_class(member, field)) {
(Member::Obj(_), Some(class)) => {
self.set(dst, &program.field_value(class, field, obj))
}
(_, Some(class)) => {
let (proto, _) = program.builtin_proto(member).unwrap();
self.set(dst, &program.field_value(class, field, &proto))
}
(Member::Str, None) if field == LENGTH_FIELD => {
self.set(dst, &format!("__rt_string_length({obj})"))
}
(Member::Obj(_), None) => self.set(dst, &format!("{obj}->length")),
(Member::Undefined | Member::Null, _) => {
throw("cannot read a field of undefined or null")
}
(Member::Native(_), _) => throw("a native function has no fields"),
_ => throw("fields of primitives need the stdlib"),
}
})
}
/// `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
fn put_field(&self, instr: &RegInstr) -> String {
let program = self.program;
let field = instr.imm.atom();
let value = self.get(instr.args[0]);
self.split(instr.args[1], |obj, member| {
match (member, program.class_kind(member)) {
(Member::Obj(_), ClassKind::Array) if field == LENGTH_FIELD => {
format!("ARRAY_SET_LENGTH({obj}, {value});\n")
}
(Member::Obj(class), _) => {
let f = program.classes.field(class, field).unwrap();
let set = format!("SET({}, {value});\n", program.field_access(obj, f));
match f.defined {
true => set,
false => {
set + &format!("{obj}->has_{} = true;\n", program.field_name(field))
}
}
}
_ => throw("cannot set a field of a primitive, undefined or null"),
}
})
}
/// `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)
fn share_field(&self, instr: &RegInstr) -> String {
let name = self.program.field_name(instr.imm.atom());
let cell = match instr.args[0] {
Operand::Reg(_) => format!("{}->{name}", self.get(instr.args[0])),
slot => self.name(slot),
};
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)
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")
})
}
/// `a[i]` on an array, and `s[i]` on a string. An index out of range gives undefined.
/// Undefined and null throw
fn get_elem(&self, instr: &RegInstr) -> String {
let (dst, index) = (self.get(instr.dsts[0]), self.get(instr.args[0]));
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, _) => {
throw("cannot read an element of undefined or null")
}
_ => unreachable!("infer gets elements only of arrays and strings"),
}
})
}
/// `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]));
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"),
}
})
}
/// 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)
}