//! 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) }