Files
jsrt/compiler/src/emit/reps.rs
T
2026-10-08 07:28:51 +00:00

235 lines
9.6 KiB
Rust

//! How each type looks in C: its representation, the C type of that,
//! the struct of each class of cells, and the tagged union of each sum
use super::*;
/// A C representation
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Rep {
Undefined,
Null,
Bool,
Int,
Double,
Str, // a string, or in a field, the sentinel for absent
Obj(CellNum), // a pointer to an object of the cell, or in a field, the sentinel for absent
OptStr, // a sum of a string and null or undefined: `NULL` is null, and `JSRT_UNDEFINED` is undefined
OptObj(CellNum), // a sum of the objects of one cell and null or undefined: a pointer, with the same sentinels
Objs, // a sum of the objects of several cells, maybe with null or undefined: a pointer to the header
Sum(Kinds), // any other sum: a tagged union of its kinds (see `sum_defs()`)
Any, // any value: an `any_t`. The `Any` type, and the args of the runtime's ops on sums
}
/// A set of kinds of value, one bit for each `jsrt_kind` (see `lib/rt/any.h`)
pub(super) type Kinds = u8;
pub(super) const ABSENT: Kinds = 1 << 0;
/// The `jsrt_kind` of each kind, and its member in a tagged union, if it has a
/// value besides its kind
pub(super) const KINDS: [(&str, Option<(&str, &str)>); 8] = [
("ABSENT", None),
("UNDEFINED", None),
("NULL", None),
("BOOL", Some(("bool", "b"))),
("INT", Some(("int32_t", "i"))),
("FLOAT", Some(("double", "f"))),
("STRING", Some(("string_t", "s"))),
("OBJECT", Some(("void *", "o"))),
];
/// The kinds of the values of type `ty`
pub(super) fn kinds(ty: &Type) -> Kinds {
ty.members().iter().fold(0, |all, member| {
all | 1 << match member {
Type::Undefined => 1,
Type::Null => 2,
Type::Bool(_) => 3,
Type::Int => 4,
Type::Float => 5,
Type::Str(_) => 6,
_ => 7,
}
})
}
/// The C of the tagged union of each set of kinds in `sums`, and its
/// conversions to and from `any_t`. Every member of a union is at its start,
/// so a conversion copies the payload's bytes. A union has one kind of number
/// (see `join()`): the other converts. A kind that the union lacks is a
/// `TypeError`
pub(super) fn sum_defs(sums: &BTreeSet<Kinds>) -> String {
let mut out = String::new();
for &k in sums {
let has = |i: usize| k & 1 << i != 0;
let union: String = (0..8).filter(|&i| has(i)).filter_map(|i| KINDS[i].1).map(|(t, m)| format!(" {t} {m};")).collect();
let copy = |to: &str, from: &str| match union.is_empty() {
true => String::new(),
false => format!(" __builtin_memcpy(&{to}.as, &{from}.as, sizeof {to}.as);"),
};
let number = match (has(4), has(5)) {
(false, true) => " if (a.kind == JSRT_KIND_INT) { v.kind = JSRT_KIND_FLOAT; v.as.f = a.as.i; return v; }",
(true, false) => " if (a.kind == JSRT_KIND_FLOAT) { v.kind = JSRT_KIND_INT; v.as.i = (int32_t)a.as.f; return v; }",
_ => "",
};
let union = if union.is_empty() { String::new() } else { format!(" union {{{union} }} as;") };
let _ = writeln!(out, "typedef struct {{ jsrt_kind kind;{union} }} sum_{k};");
let _ = writeln!(out, "static inline any_t sum_{k}_any(sum_{k} v) {{ any_t a = {{ .kind = v.kind }};{} return a; }}", copy("a", "v"));
let _ = writeln!(
out,
"static inline sum_{k} any_sum_{k}(any_t a) {{ sum_{k} v = {{ .kind = a.kind }};{number} if (!({k}u >> a.kind & 1)) jsrt_type_error();{} return v; }}",
copy("v", "a")
);
}
out
}
impl<'a> Emitter<'a> {
/// The representation of a value of type `ty`
pub(super) fn rep(&self, ty: &Type) -> Rep {
match ty {
Type::Unknown | Type::Undefined => Rep::Undefined,
Type::Null => Rep::Null,
Type::Bool(_) => Rep::Bool,
Type::Int => Rep::Int,
Type::Float => Rep::Double,
Type::Str(_) => Rep::Str,
Type::Obj(cell) => Rep::Obj(self.num(cell)),
Type::Sum(_) => self.sum_rep(ty),
Type::Any => Rep::Any,
}
}
/// The representation of a sum. A string or the objects of one cell, with
/// null or undefined, is that pointer with sentinels. The objects of
/// several cells are a pointer to their header. Any other sum is an
/// `any_t`, which holds every kind
pub(super) fn sum_rep(&self, ty: &Type) -> Rep {
let others: Vec<&Type> = ty.members().iter().filter(|m| !m.is_nullish()).collect();
let mut classes: Vec<CellNum> = others.iter().filter_map(|m| self.cell_num(m)).collect();
classes.dedup();
match (&others[..], &classes[..]) {
([], _) => Rep::Sum(kinds(ty)),
([Type::Str(_)], _) => Rep::OptStr,
(_, [id]) if classes.len() == 1 && others.iter().all(|m| matches!(m, Type::Obj(_))) => Rep::OptObj(*id),
_ if others.iter().all(|m| matches!(m, Type::Obj(_))) => Rep::Objs,
_ => Rep::Sum(kinds(ty)),
}
}
/// The class of an `Obj` type
pub(super) fn cell_num(&self, ty: &Type) -> Option<CellNum> {
match ty {
Type::Obj(cell) => Some(self.num(cell)),
_ => None,
}
}
/// The representation of a field of type `ty`. A field that can be absent
/// needs a sentinel for absent: a pointer has one, and any other rep is a
/// tagged union that can also be absent
pub(super) fn slot_rep(&self, ty: &Type, absent: bool) -> Rep {
match self.rep(ty) {
rep @ (Rep::Str | Rep::Obj(_) | Rep::OptStr | Rep::OptObj(_) | Rep::Objs) => rep,
_ if absent => Rep::Sum(kinds(ty) | ABSENT),
rep => rep,
}
}
/// The representation of an element of an array of cell `id`: its type,
/// or undefined for a hole
pub(super) fn elem_rep(&self, id: CellNum) -> Rep {
self.rep(&self.cells[id as usize].read_elem())
}
pub(super) fn c_type(&self, rep: Rep) -> String {
match rep {
Rep::Undefined => "undefined_t".into(),
Rep::Null => "null_t".into(),
Rep::Bool => "bool".into(),
Rep::Int => "int32_t".into(),
Rep::Double => "double".into(),
Rep::Str | Rep::OptStr => "string_t".into(),
Rep::Obj(id) | Rep::OptObj(id) => format!("{} *", struct_name(id)),
Rep::Objs => "jsrt_header *".into(),
Rep::Sum(k) => {
self.sums.borrow_mut().insert(k);
format!("sum_{k}")
}
Rep::Any => "any_t".into(),
}
}
pub(super) fn field_name(&self, name: AtomRef) -> String {
let text = atom_name(name, self.atoms);
let text = text.chars().map(|c| if c.is_ascii_alphanumeric() { c } else { '_' });
format!("f_{}", text.collect::<String>())
}
/// The struct of cell `id`, and its helpers: `init_S`, which sets the
/// header and makes each field that can be absent absent, and for an
/// array, the helpers of its elements
pub(super) fn struct_def(&mut self, id: CellNum, out: &mut String) {
let cell = self.cells[id as usize].clone();
let name = struct_name(id);
let mut members = String::from("jsrt_header h;");
let mut body = String::from(" o->h.cell = cell;\n");
// An array's elements come after its header. `items` has room for
// `cap` of them. An element holds undefined for a hole
let elem = cell.is_array().then(|| self.elem_rep(id));
if let Some(elem) = elem {
let _ = write!(members, " int32_t length; int32_t cap; {} *items;", self.c_type(elem));
}
for (field, f) in &cell.info().fields {
let absent = cell.can_be_absent(*field);
let rep = self.slot_rep(&f.ty, absent);
let member = self.field_name(*field);
let _ = write!(members, " {} {member};", self.c_type(rep));
if absent {
let _ = writeln!(body, " o->{member} = {};", self.absent(rep));
}
}
let _ = writeln!(out, "struct {name} {{ {members} }};");
self.helper(
format!("static {name} *init_{name}({name} *o, uint32_t cell)"),
format!("{body} return o;\n"),
);
if let Some(elem) = elem {
let (t, hole) = (self.c_type(elem), self.conv("(undefined_t){0}", Rep::Undefined, elem));
self.helper(
format!("static void set_length_{name}({name} *a, int32_t n)"),
format!(
" if (n < 0) jsrt_type_error();\n\
\x20 a->items = jsrt_grow(a->items, &a->cap, n, sizeof *a->items);\n\
\x20 for (int32_t i = a->length; i < n; i++) a->items[i] = {hole};\n\
\x20 a->length = n;\n"
),
);
self.helper(
format!("static {t} elem_{name}({name} *a, int32_t i)"),
format!(" return i >= 0 && i < a->length ? a->items[i] : {hole};\n"),
);
self.helper(
format!("static void set_elem_{name}({name} *a, int32_t i, {t} v)"),
" if (i < 0) jsrt_type_error();\n if (i >= a->length) set_length_{NAME}(a, i + 1);\n a->items[i] = v;\n"
.replace("{NAME}", &name),
);
}
}
pub(super) fn helper(&mut self, sig: String, body: String) {
let _ = writeln!(self.helper_protos, "{sig};");
let _ = writeln!(self.helper_defs, "{sig} {{\n{body}}}\n");
}
}