234 lines
9.5 KiB
Rust
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)
|
|
}
|