Locked in type model

This commit is contained in:
2026-10-08 07:28:51 +00:00
parent 2b612d19ed
commit 316a2fcd75
13 changed files with 1707 additions and 1358 deletions
+231
View File
@@ -0,0 +1,231 @@
//! Field reads and writes, array elements, and prototype chains
use super::*;
impl<'a> Emitter<'a> {
/// `obj.field = value`. A primitive has no fields to write, and undefined
/// and null throw
pub(super) fn write_field(&mut self, res: &InferResult, instr: &RegInstr, out: &mut String) {
let (value, obj) = (instr.args[0], instr.args[1]);
let value_rep = self.rep(&res.val_types[value]);
let field = instr.imm.atom();
let value = format!("v{value}");
self.dispatch(&format!("v{obj}"), &res.val_types[obj], "jsrt_type_error();", out, |e, member, obj, _| {
match member {
Type::Obj(cell) if field == LENGTH_FIELD && cell.is_array() => {
let name = struct_name(e.num(cell));
Some(format!("set_length_{name}({obj}, {});", e.conv(&value, value_rep, Rep::Int)))
}
Type::Obj(cell) => {
let slot = e.slot_rep(&cell.field(field).unwrap().ty, cell.can_be_absent(field));
Some(format!("{obj}->{} = {};", e.field_name(field), e.conv(&value, value_rep, slot)))
}
Type::Undefined | Type::Null => None,
_ => Some(";".into()),
}
});
}
/// `dst = obj.field`: on an object, the read through its prototype chain;
/// on a primitive, the read on its kind's prototype, except that a string's
/// `length` is its own. Undefined and null throw
pub(super) fn read_field(&mut self, res: &InferResult, instr: &RegInstr, out: &mut String) {
let (obj, dst) = (instr.args[0], instr.dsts[0]);
let dst_rep = self.rep(&res.val_types[dst]);
let field = match instr.op {
OpCode::GetLength => LENGTH_FIELD,
_ => instr.imm.atom(),
};
self.dispatch(&format!("v{obj}"), &res.val_types[obj], "jsrt_type_error();", out, |e, member, obj, _| {
let (read, rep) = match member {
Type::Obj(cell) => e.read_on(cell, field, obj),
_ => e.read_primitive(member, obj, field)?,
};
Some(format!("v{dst} = {};", e.conv(&read, rep, dst_rep)))
});
}
/// `dst = a[i]` or `a[i] = value`, on an array, and `dst = s[i]` on a
/// string. A string cannot be written, and undefined and null throw
pub(super) fn element(&mut self, res: &InferResult, instr: &RegInstr, out: &mut String) {
let put = instr.op == OpCode::PutArrayEl;
let (index, obj) = (instr.args[put as usize], instr.args[put as usize + 1]);
let index = self.conv(&format!("v{index}"), self.rep(&res.val_types[index]), Rep::Int);
// A put has the value, a get the dst
let (v, v_rep) = match put {
true => (instr.args[0], self.rep(&res.val_types[instr.args[0]])),
false => (instr.dsts[0], self.rep(&res.val_types[instr.dsts[0]])),
};
self.dispatch(&format!("v{obj}"), &res.val_types[obj], "jsrt_type_error();", out, |e, member, obj, _| {
let id = match member {
Type::Obj(cell) => e.num(cell),
Type::Str(_) if !put => {
let c = format!("jsrt_string_at({obj}, {index})");
return Some(format!("v{v} = {};", e.conv(&c, Rep::OptStr, v_rep)));
}
_ => return None,
};
let (name, elem) = (struct_name(id), e.elem_rep(id));
Some(match put {
true => format!("set_elem_{name}({obj}, {index}, {});", e.conv(&format!("v{v}"), v_rep, elem)),
false => format!("v{v} = {};", e.conv(&format!("elem_{name}({obj}, {index})"), elem, v_rep)),
})
});
}
/// A read of `field` on the object of `cell` at `ptr`, and its rep. An own
/// field that is never absent is read in place. Any other read goes
/// through the prototype chain
pub(super) fn read_on(&mut self, cell: &Cell, field: AtomRef, ptr: &str) -> (String, Rep) {
if field == LENGTH_FIELD && cell.is_array() {
return (format!("{ptr}->length"), Rep::Int);
}
match cell.field(field) {
Some(f) if !cell.can_be_absent(field) => (format!("{ptr}->{}", self.field_name(field)), self.rep(&f.ty)),
_ => {
let (reader, rep) = self.field_reader(cell, field);
(format!("{reader}({ptr})"), rep)
}
}
}
/// A read of `field` on `e`, a primitive of type `ty`, and its rep: a
/// string's length, or a read on its kind's prototype. `None` if it throws
pub(super) fn read_primitive(&mut self, ty: &Type, e: &str, field: AtomRef) -> Option<(String, Rep)> {
let kind = ProtoOf::of(ty)?;
if kind == ProtoOf::String && field == LENGTH_FIELD {
return Some((format!("jsrt_string_length({e})"), Rep::Int));
}
Some(self.read_proto(kind, field))
}
/// A read of `field` on the prototype that the stdlib links for `kind`,
/// and its rep
pub(super) fn read_proto(&mut self, kind: ProtoOf, field: AtomRef) -> (String, Rep) {
match &self.cache.protos[kind as usize] {
Type::Unknown => ("(undefined_t){0}".into(), Rep::Undefined),
Type::Obj(cell) => self.read_on(&cell.clone(), field, &proto_global(kind)),
_ => self.proto_reader(kind, field),
}
}
/// The helper that reads `field` on the prototype that the stdlib links
/// for `kind`, where the prototype's type is a sum, and the rep it gives.
/// It reads on each member of the type
pub(super) fn proto_reader(&mut self, kind: ProtoOf, field: AtomRef) -> (String, Rep) {
let name = format!("get_{}_{}", proto_global(kind), self.field_name(field));
let proto = &self.cache.protos[kind as usize];
let ret = self.rep(&read_value(proto, field, &self.cache.protos));
if !self.helpers.insert(name.clone()) {
return (format!("{name}()"), ret);
}
let mut body = String::new();
self.dispatch(&proto_global(kind), proto, "jsrt_type_error();", &mut body, |e, member, proto, _| {
let Type::Obj(cell) = member else {
return None;
};
let (read, rep) = e.read_on(cell, field, proto);
Some(format!("return {};", e.conv(&read, rep, ret)))
});
self.helper(format!("static {} {name}(void)", self.c_type(ret)), body);
(format!("{name}()"), ret)
}
/// The helper that reads `field` on an object of `cell`, and the rep it
/// gives. It reads the own field, and where that can be absent, goes on to
/// the prototype. At the end of the chain, the field is undefined
pub(super) fn field_reader(&mut self, cell: &Cell, field: AtomRef) -> (String, Rep) {
let name = format!("get_{}_{}", struct_name(self.num(cell)), self.field_name(field));
let read = cell.read(field, &self.cache.protos);
let ret = self.rep(&read);
if !self.helpers.insert(name.clone()) {
return (name, ret);
}
let own = cell.field(field);
let absent = cell.can_be_absent(field);
let mut body = String::new();
if let Some(own) = &own {
let slot = self.slot_rep(&own.ty, absent);
let member = format!("o->{}", self.field_name(field));
let value = self.conv(&member, slot, ret);
match absent {
false => {
let _ = writeln!(body, " return {value};");
}
true => {
let _ = writeln!(body, " if ({}) return {value};", self.present(&member, slot));
}
}
}
// An array's prototype is the one that the stdlib links for arrays
if absent && cell.is_array() {
let (read, rep) = self.read_proto(ProtoOf::Array, field);
let _ = writeln!(body, " return {};", self.conv(&read, rep, ret));
} else if absent {
let proto_ty = cell.field(PROTO_FIELD).map_or(Type::Unknown, |f| f.ty);
let proto_absent = cell.can_be_absent(PROTO_FIELD);
let slot = self.slot_rep(&proto_ty, proto_absent);
let member = format!("o->{}", self.field_name(PROTO_FIELD));
let mut chain = String::new();
// On a prototype that is not an object, the chain ends
self.dispatch(&member, &proto_ty, "{}", &mut chain, |e, member, proto, _| {
let Type::Obj(proto_cell) = member else {
return None;
};
let (reader, rep) = e.field_reader(proto_cell, field);
Some(format!("return {};", e.conv(&format!("{reader}({proto})"), rep, ret)))
});
match (&proto_ty, proto_absent) {
(Type::Unknown, _) => {}
(_, true) => {
let _ = writeln!(body, " if ({}) {{\n{chain} }}", self.present(&member, slot));
}
(_, false) => body += &chain,
}
// The end of the chain. If the read cannot give undefined, the chain
// always finds the field before it
match read.members().contains(&Type::Undefined) {
true => {
let undefined = self.conv("(undefined_t){0}", Rep::Undefined, ret);
let _ = writeln!(body, " return {undefined};");
}
false => body += " abort();\n",
}
}
// A field that is defined, but that no value reaches: the read cannot
// run
if own.is_none() && !absent {
let _ = writeln!(body, " abort();");
}
let sig = format!("static {} {name}({} *o)", self.c_type(ret), struct_name(self.num(cell)));
self.helper(sig, body);
(name, ret)
}
}