New macro based emit
This commit is contained in:
+157
-214
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user