New macro based emit

This commit is contained in:
2026-10-24 15:58:51 +00:00
parent cf455b88a8
commit 95e5c1225e
12 changed files with 1206 additions and 1587 deletions
-119
View File
@@ -1,119 +0,0 @@
//! The body of a C function, as it is written: its lines, with their indent, and its temps
use std::fmt::Write as _;
use super::program::{Case, Program};
use super::types::CType;
pub struct Body<'a> {
pub program: &'a Program<'a>,
text: String,
depth: usize, // the indent of the next line
temps: usize, // the number of temps so far, which names the next one
}
impl<'a> Body<'a> {
pub fn new(program: &'a Program<'a>) -> Self {
Body {
program,
text: String::new(),
depth: 1,
temps: 0,
}
}
/// The lines of the body
pub fn finish(self) -> String {
self.text
}
pub fn line(&mut self, text: impl AsRef<str>) {
let indent = self.depth * 4;
let _ = writeln!(self.text, "{:indent$}{}", "", text.as_ref());
}
pub fn indent(&mut self, by: isize) {
self.depth = self.depth.saturating_add_signed(by);
}
/// Throw a TypeError that says `what` (see `__rt_type_error`)
pub fn throw(&mut self, what: &str) {
self.line(format!("__rt_type_error(\"{what}\");"));
}
/// A new C local of C type `ctype`, which is not set. Gives its name
pub fn temp(&mut self, ctype: CType) -> String {
let name = format!("t{}", self.temps);
self.temps += 1;
self.line(format!("{} {name};", ctype.c_name()));
name
}
/// Start the C code of one case (see `Program::cases`): an `if` for the first case, and
/// an `else if` for the others. A case with no test is the only case, and needs neither
pub fn open(&mut self, case: &Case) {
if case.tests.is_empty() {
return;
}
let test = case.tests.join(" && ");
if case.first {
self.line(format!("if ({test}) {{"));
} else {
self.indent(-1);
self.line(format!("}} else if ({test}) {{"));
}
self.indent(1);
}
/// End the C code of the cases that `open` started
pub fn close(&mut self, cases: &[Case]) {
if !cases[0].tests.is_empty() {
self.indent(-1);
self.line("}");
}
}
/// The line `<target><value>;`, where `value`, of C type `from`, is converted to C type
/// `to`. `target` is, for example, `x = ` or `return `. A union is converted member by
/// member, from a copy, so that `value` is evaluated once
pub fn put(&mut self, target: &str, value: &str, from: CType, to: CType) {
if from == to {
return self.line(format!("{target}{value};"));
}
if let CType::One(member) = from {
let value = self.program.convert(value, member, to);
return self.line(format!("{target}{value};"));
}
let copy = self.temp(from);
self.line(format!("{copy} = {value};"));
let cases = self.program.cases(&[(copy, from)]);
for case in &cases {
self.open(case);
let (value, member) = &case.operands[0];
let value = self.program.convert(value, *member, to);
self.line(format!("{target}{value};"));
}
self.close(&cases);
}
/// `value`, of C type `from`, converted to C type `to`, as C. A union is converted into a
/// new temp, whose name is given
pub fn convert(&mut self, value: &str, from: CType, to: CType) -> String {
match from {
_ if from == to => value.to_string(),
CType::One(member) => self.program.convert(value, member, to),
CType::Union(_) => {
let temp = self.temp(to);
self.put(&format!("{temp} = "), value, from, to);
temp
}
}
}
}
+110
View File
@@ -0,0 +1,110 @@
//! The C of calls
//!
//! A call's operands are the callee's params: the callee itself, `this`, then the args (see
//! regify). Each closure that the callee can be is a case of its own (see `Program::split`),
//! and a callee that is not a function throws.
use super::classes::{ClassKind, Member};
use super::js::{JsFunction, UNDEFINED, indent};
use super::program::throw;
use crate::{
infer::{InstrRef, Type},
libqjs::parse::opcodes::OpCode,
native::NATIVES,
regify::{Operand, RegInstr},
transform::{FIRST_ARG, THIS_ARG},
};
impl JsFunction<'_> {
pub(super) fn call(&self, idx: usize, instr: &RegInstr) -> String {
self.split(instr.args[0], |callee, member| {
match (member, self.program.class_kind(member)) {
(Member::Native(native), _) => self.native_call(native, instr),
(Member::Obj(class), ClassKind::Closure(_)) => {
self.closure_call(idx, instr, callee, class)
}
_ => throw("not a function"),
}
})
}
/// A call of a closure of `class`, which is `callee`. Infer walked the closure's JS
/// function for the types of this call's args, and that walk is the C function to call.
/// An arg that the call does not pass is undefined. `new` gives what the constructor
/// returns if that is an object, else the new object itself, which is `this`
fn closure_call(&self, idx: usize, instr: &RegInstr, callee: &str, class: usize) -> String {
let program = self.program;
let ClassKind::Closure(fn_idx) = program.classes.classes[class].kind else {
unreachable!()
};
let mut types: Vec<Type> = instr.args.iter().map(|&slot| self.ty(slot)).collect();
types[0] = Type::Ref(program.classes.classes[class].objs[0]);
let call_site = Some(InstrRef {
fn_idx: self.context.fn_idx,
instr_idx: idx,
});
let walk = program.cache.find(fn_idx, call_site, &types, program.fns);
let args: Vec<String> = (0..program.fns[fn_idx].arg_count)
.map(|i| {
let param = program.slot_ctype(walk, Operand::Arg(i)).c_name();
let arg = match instr.args.get(i) {
_ if i == 0 => callee.to_string(),
Some(&slot) => self.get(slot),
None => UNDEFINED.into(),
};
format!("CAST({param}, {arg})")
})
.collect();
let call = format!("js{walk}({})", args.join(", "));
let ret = program.cache.ret(walk);
let dst = instr.dsts[0];
// A function that never returns gives no value
if *ret == Type::Unknown {
return format!("{call};\nabort(); // does not return\n");
}
if instr.op != OpCode::CallConstructor {
return self.set(dst, &call);
}
let this = self.get(instr.args[THIS_ARG as usize]);
let result = program.split("result", ret, |result, member| match member {
Member::Obj(_) => self.set(dst, result),
_ => self.set(dst, &this),
});
let ctype = program.ret_ctype(walk).c_name();
format!("{{\n {ctype} result = {call};\n{}}}\n", indent(&result))
}
/// A call of a native: a C call, with only the args. Each arg is converted to its
/// param's C type, and an arg of the wrong kind throws (see `ARG_INT`). An arg that
/// the call does not pass is undefined
fn native_call(&self, native: usize, instr: &RegInstr) -> String {
let native = &NATIVES[native];
let args: Vec<String> = (native.arg_types.iter().enumerate())
.map(|(i, param)| {
let arg = instr.args.get(FIRST_ARG as usize + i);
let arg = arg.map_or(UNDEFINED.into(), |&slot| self.get(slot));
match param {
Type::Int => format!("ARG_INT({arg})"),
Type::Float => format!("ARG_NUMBER({arg})"),
Type::Str(_) => format!("ARG_STRING({arg})"),
_ => format!("ARG_UNDEFINED({arg})"),
}
})
.collect();
self.set(
instr.dsts[0],
&format!("{}({})", native.name, args.join(", ")),
)
}
}
@@ -1,9 +1,9 @@
//! The C types of the program //! The C types of values, and the classes of objects
//! //!
//! Infer gives each value a type, which can have several members: for example, a value that //! Infer gives each value a type, which can have several members: for example, a number or
//! is a number or undefined. In C, each member is one C type (see `Member`). A type with one //! undefined. In C, each member has a C type of its own (see `Member`). A value whose type
//! member is that C type. A type with several members is a tagged union: a tag that says //! has one member is a plain C value of that member's C type. Any other value is a
//! which member the value is, and a C union of the members. //! `jsrt_value`, which holds a tag and the value (see lib/rt/core.h).
//! //!
//! Objects are put in classes, and each class is one C struct. Two objects are in the same //! Objects are put in classes, and each class is one C struct. Two objects are in the same
//! class if they have the same fields, and each field has the same C type. A field can point //! class if they have the same fields, and each field has the same C type. A field can point
@@ -19,7 +19,7 @@ use std::collections::HashMap;
use crate::infer::{Addr, Memory, ObjAddr, Shape, Type, join}; use crate::infer::{Addr, Memory, ObjAddr, Shape, Type, join};
use crate::libqjs::parse::AtomRef; use crate::libqjs::parse::AtomRef;
/// One member of a type, as a C type /// One member of a type, which has a C type of its own
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Member { pub enum Member {
Undefined, Undefined,
@@ -29,36 +29,28 @@ pub enum Member {
Float, Float,
Str, Str,
Obj(usize), // a pointer to an object of this class Obj(usize), // a pointer to an object of this class
Native(usize), // a native function, by its index in `native::NATIVES`. The type says which, so it holds no data Native(usize), // a native function, by its index in `native::NATIVES`
} }
/// The C type of a value: one member, or a tagged union of several /// The C type of a value: one member's, or a `jsrt_value`
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CType { pub enum CType {
One(Member), One(Member),
Union(usize), // an index into `Types::unions` Value,
} }
impl CType { impl CType {
/// The C name of the type
pub fn c_name(self) -> String { pub fn c_name(self) -> String {
match self { match self {
CType::One(Member::Undefined | Member::Native(_)) => "undefined_t".into(), CType::One(Member::Undefined) => "undefined_t".into(),
CType::One(Member::Null) => "null_t".into(), CType::One(Member::Null) => "null_t".into(),
CType::One(Member::Bool) => "bool".into(), CType::One(Member::Bool) => "bool".into(),
CType::One(Member::Int) => "int32_t".into(), CType::One(Member::Int) => "int32_t".into(),
CType::One(Member::Float) => "double".into(), CType::One(Member::Float) => "double".into(),
CType::One(Member::Str) => "string_t".into(), CType::One(Member::Str) => "string_t".into(),
CType::One(Member::Obj(class)) => format!("struct c{class} *"), CType::One(Member::Obj(class)) => format!("struct c{class} *"),
CType::Union(id) => format!("u{id}"), CType::One(Member::Native(_)) => "native_t".into(),
} CType::Value => "jsrt_value".into(),
}
/// The class, if the type is a pointer to an object of one class
pub fn class(self) -> Option<usize> {
match self {
CType::One(Member::Obj(class)) => Some(class),
_ => None,
} }
} }
} }
@@ -74,6 +66,7 @@ pub enum ClassKind {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ClassField { pub struct ClassField {
pub name: AtomRef, pub name: AtomRef,
pub ty: Type, // the join of the field's types in the class's objects
pub ctype: CType, pub ctype: CType,
pub shared: bool, // the field shares another location, so the struct holds a pointer to it pub shared: bool, // the field shares another location, so the struct holds a pointer to it
pub defined: bool, // every object has the field, so the struct needs no `has_` flag for it pub defined: bool, // every object has the field, so the struct needs no `has_` flag for it
@@ -88,91 +81,30 @@ pub struct Class {
pub elems: CType, // the C type of an array's elements. Not used for other objects pub elems: CType, // the C type of an array's elements. Not used for other objects
} }
/// The classes and the unions of the program /// The classes of all objects
#[derive(Debug)] #[derive(Debug)]
pub struct Types { pub struct Classes {
pub classes: Vec<Class>, pub classes: Vec<Class>,
class_of: HashMap<ObjAddr, usize>, class_of: HashMap<ObjAddr, usize>,
pub unions: Vec<Vec<Member>>, // the members of each union, sorted. A member's tag is its index
union_ids: HashMap<Vec<Member>, usize>,
} }
impl Types { impl Classes {
/// The classes of all objects. The unions of the classes' fields are added too, and
/// `Program::new` adds the rest
pub fn new(memory: &Memory) -> Self { pub fn new(memory: &Memory) -> Self {
let mut types = Types { let class_of = refine(memory);
classes: vec![], let count = class_of.values().max().map_or(0, |&n| n + 1);
class_of: refine(memory), let mut objs_of_class: Vec<Vec<ObjAddr>> = vec![vec![]; count];
unions: vec![],
union_ids: HashMap::new(),
};
let mut objs: Vec<ObjAddr> = memory.objects().map(|(obj, _)| obj).collect(); let mut objs: Vec<ObjAddr> = memory.objects().map(|(obj, _)| obj).collect();
objs.sort_unstable(); objs.sort_unstable();
let count = types.class_of.values().max().map_or(0, |&n| n + 1);
let mut objs_of_class: Vec<Vec<ObjAddr>> = vec![vec![]; count];
for obj in objs { for obj in objs {
objs_of_class[types.class_of[&obj]].push(obj); objs_of_class[class_of[&obj]].push(obj);
} }
for objs in objs_of_class { let classes = objs_of_class.into_iter();
let class = types.make_class(memory, objs); let classes = classes
types.classes.push(class); .map(|objs| make_class(memory, &class_of, objs))
} .collect();
Classes { classes, class_of }
types
}
/// The class of the objects in `objs`, which refinement put together
fn make_class(&mut self, memory: &Memory, objs: Vec<ObjAddr>) -> Class {
let first = objs[0];
let kind = match memory.get(Addr::Object(first)) {
Type::Shape(Shape::Array(_)) => ClassKind::Array,
Type::Closure { fn_idx, .. } => ClassKind::Closure(*fn_idx),
_ => ClassKind::Obj,
};
// The type of a field is the join of its types in all the objects of the class, so
// that its C type can hold each of them
let fields = match kind {
ClassKind::Array => vec![],
_ => (memory.fields(first).iter())
.map(|&(name, field)| {
let types = objs.iter().map(|&obj| {
let field = memory.field(obj, name).unwrap();
memory.get(field.addr).clone()
});
let ty = types.fold(Type::Unknown, |all, ty| join(&all, &ty));
ClassField {
name,
ctype: self.add(&ty),
shared: field.addr != Addr::Field(first, name),
defined: field.defined,
}
})
.collect(),
};
let elems = match kind {
ClassKind::Array => {
let elems = objs.iter().map(|&obj| memory.elems(obj));
let ty = elems.fold(Type::Unknown, |all, ty| join(&all, ty));
self.add(&ty)
}
_ => CType::One(Member::Undefined),
};
Class {
kind,
objs,
fields,
elems,
}
} }
pub fn class(&self, obj: ObjAddr) -> usize { pub fn class(&self, obj: ObjAddr) -> usize {
@@ -184,49 +116,59 @@ impl Types {
} }
/// The members of a value of type `ty`. A location that no value reached holds undefined /// The members of a value of type `ty`. A location that no value reached holds undefined
pub fn type_members(&self, ty: &Type) -> Vec<Member> { pub fn members(&self, ty: &Type) -> Vec<Member> {
members_with(ty, &self.class_of) members_with(ty, &self.class_of)
} }
/// The C type of a value of type `ty`. If it is a union that is not in the list yet, it /// The C type of a value of type `ty`
/// is added pub fn ctype(&self, ty: &Type) -> CType {
pub fn add(&mut self, ty: &Type) -> CType { ctype_with(ty, &self.class_of)
let members = self.type_members(ty);
if let [member] = members[..] {
return CType::One(member);
}
let next = self.unions.len();
let id = *self.union_ids.entry(members.clone()).or_insert(next);
if id == next {
self.unions.push(members);
}
CType::Union(id)
}
/// The C type of a value of type `ty`. `Program::new` added every union first
pub fn of(&self, ty: &Type) -> CType {
let members = self.type_members(ty);
if let [member] = members[..] {
return CType::One(member);
}
match self.union_ids.get(&members) {
Some(&id) => CType::Union(id),
None => panic!("emit: no union was added for {members:?}"),
} }
} }
/// The members of a value of C type `ctype` /// The class of the objects in `objs`, which refinement put together. The type of a field is
pub fn members_of(&self, ctype: CType) -> Vec<Member> { /// the join of its types in all the objects of the class, so that its C type holds each one
match ctype { fn make_class(memory: &Memory, class_of: &HashMap<ObjAddr, usize>, objs: Vec<ObjAddr>) -> Class {
CType::One(member) => vec![member], let first = objs[0];
CType::Union(id) => self.unions[id].clone(), let join_over = |ty_of: &dyn Fn(ObjAddr) -> Type| {
objs.iter()
.fold(Type::Unknown, |all, &obj| join(&all, &ty_of(obj)))
};
let kind = match memory.get(Addr::Object(first)) {
Type::Shape(Shape::Array(_)) => ClassKind::Array,
Type::Closure { fn_idx, .. } => ClassKind::Closure(*fn_idx),
_ => ClassKind::Obj,
};
let fields = match kind {
ClassKind::Array => vec![],
_ => (memory.fields(first).iter())
.map(|&(name, field)| {
let ty =
join_over(&|obj| memory.get(memory.field(obj, name).unwrap().addr).clone());
ClassField {
name,
ctype: ctype_with(&ty, class_of),
ty,
shared: field.addr != Addr::Field(first, name),
defined: field.defined,
} }
})
.collect(),
};
let elems = match kind {
ClassKind::Array => ctype_with(&join_over(&|obj| memory.elems(obj).clone()), class_of),
_ => CType::One(Member::Undefined),
};
Class {
kind,
objs,
fields,
elems,
} }
} }
@@ -255,8 +197,16 @@ fn members_with(ty: &Type, class_of: &HashMap<ObjAddr, usize>) -> Vec<Member> {
members members
} }
/// The C type of `ty`, given the class of each object
fn ctype_with(ty: &Type, class_of: &HashMap<ObjAddr, usize>) -> CType {
match members_with(ty, class_of)[..] {
[member] => CType::One(member),
_ => CType::Value,
}
}
/// The class of every object. At first, objects with the same kind and field names share a /// The class of every object. At first, objects with the same kind and field names share a
/// class. Then each round splits a class whose objects have fields of different members, /// class. Then each round splits a class whose objects have fields of different C types,
/// until a round splits nothing /// until a round splits nothing
fn refine(memory: &Memory) -> HashMap<ObjAddr, usize> { fn refine(memory: &Memory) -> HashMap<ObjAddr, usize> {
let mut objs: Vec<ObjAddr> = memory.objects().map(|(obj, _)| obj).collect(); let mut objs: Vec<ObjAddr> = memory.objects().map(|(obj, _)| obj).collect();
@@ -285,8 +235,8 @@ fn refine(memory: &Memory) -> HashMap<ObjAddr, usize> {
loop { loop {
let count = class_of.values().max().map_or(0, |&n| n + 1); let count = class_of.values().max().map_or(0, |&n| n + 1);
// A field's members, or an array's elements, with the classes so far // The C type of each field, or of an array's elements, with the classes so far
let key = |obj: ObjAddr| -> (usize, Vec<Vec<Member>>) { let key = |obj: ObjAddr| -> (usize, Vec<CType>) {
let types: Vec<Type> = match memory.get(Addr::Object(obj)) { let types: Vec<Type> = match memory.get(Addr::Object(obj)) {
Type::Shape(Shape::Array(elems)) => vec![(**elems).clone()], Type::Shape(Shape::Array(elems)) => vec![(**elems).clone()],
_ => memory _ => memory
@@ -298,7 +248,7 @@ fn refine(memory: &Memory) -> HashMap<ObjAddr, usize> {
( (
class_of[&obj], class_of[&obj],
types.iter().map(|ty| members_with(ty, &class_of)).collect(), types.iter().map(|ty| ctype_with(ty, &class_of)).collect(),
) )
}; };
+23 -99
View File
@@ -7,16 +7,15 @@
//! function (see `js`). //! function (see `js`).
//! - **A lookup**, which gets a field that some objects of a class do not have. It gives the //! - **A lookup**, which gets a field that some objects of a class do not have. It gives the
//! object's own field if the object has it, else it looks on the object's prototype, and so //! object's own field if the object has it, else it looks on the object's prototype, and so
//! on along the prototype chain. At the end of the chain, the field is undefined. //! on along the prototype chain. At the end of the chain, the field is undefined (see
//! `objects`).
//! - **`jsrt_start`**, which the host calls to run the program (see lib/rt/jsrt.h). //! - **`jsrt_start`**, which the host calls to run the program (see lib/rt/jsrt.h).
//! - **`jsrt_complete`**, which the host calls when an IO op completes. //! - **`jsrt_complete`**, which the host calls when an IO op completes.
use super::body::Body; use super::classes::{CType, ClassKind, Member};
use super::js;
use super::program::Program; use super::program::Program;
use super::types::{CType, ClassKind, Member}; use super::{js, objects};
use super::values::{undefined, unit_literal}; use crate::{infer::Type, libqjs::parse::AtomRef, regify::Operand};
use crate::{infer::Type, libqjs::parse::AtomRef, regify::Operand, transform::PROTO_FIELD};
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum Function { pub enum Function {
@@ -44,8 +43,8 @@ impl Function {
match self { match self {
Function::Js(walk) => js::signature(program, *walk), Function::Js(walk) => js::signature(program, *walk),
Function::Lookup(class, field) => { Function::Lookup(class, field) => {
let ctype = program.types.of(&program.lookup_type(*class, *field)); let ctype = program.lookup_ctype(*class, *field).c_name();
format!("static {} {name}(struct c{class} *o)", ctype.c_name()) format!("static {ctype} {name}(struct c{class} *o)")
} }
Function::Start => format!("void {name}(void)"), Function::Start => format!("void {name}(void)"),
Function::Complete => format!("void {name}(int32_t h)"), Function::Complete => format!("void {name}(int32_t h)"),
@@ -55,7 +54,7 @@ impl Function {
pub fn definition(&self, program: &Program) -> String { pub fn definition(&self, program: &Program) -> String {
let body = match self { let body = match self {
Function::Js(walk) => js::body(program, *walk), Function::Js(walk) => js::body(program, *walk),
Function::Lookup(class, field) => lookup_body(program, *class, *field), Function::Lookup(class, field) => objects::lookup_body(program, *class, *field),
Function::Start => start_body(program), Function::Start => start_body(program),
Function::Complete => complete_body(program), Function::Complete => complete_body(program),
}; };
@@ -64,120 +63,45 @@ impl Function {
} }
} }
/// The lookup of `field` on `o`, an object of `class`: its own field, then its prototype
fn lookup_body(program: &Program, class: usize, field: AtomRef) -> String {
let mut body = Body::new(program);
let ctype = program.types.of(&program.lookup_type(class, field));
let (undefined, undefined_ctype) = undefined();
let own_field = program.types.field(class, field);
let proto_field = program.types.field(class, PROTO_FIELD);
// 1. Its own field, if it has it
if let Some(own) = own_field {
let value = program.field_access("o", own);
if own.defined {
body.put("return ", &value, own.ctype, ctype);
} else {
body.line(format!("if (o->has_{}) {{", program.field_name(field)));
body.indent(1);
body.put("return ", &value, own.ctype, ctype);
body.indent(-1);
body.line("}");
}
}
// 2. Its prototype. An array's is the stdlib's. Another object's is its `__proto__`
// field, which it can lack
let proto = match program.types.classes[class].kind {
ClassKind::Array => (program.builtin_proto(Member::Obj(class)))
.map(|(value, class)| (value, CType::One(Member::Obj(class)))),
_ => proto_field.map(|own| (program.field_access("o", own), own.ctype)),
};
let Some(proto) = proto else {
body.put("return ", &undefined, undefined_ctype, ctype);
return body.finish();
};
if proto_field.is_some_and(|own| !own.defined) {
body.line(format!(
"if (!o->has_{}) {{",
program.field_name(PROTO_FIELD)
));
body.indent(1);
body.put("return ", &undefined, undefined_ctype, ctype);
body.indent(-1);
body.line("}");
}
// 3. The field on the prototype, for each class that the prototype can be. Anything
// else, such as null, ends the chain
let cases = program.cases(&[proto]);
for case in &cases {
body.open(case);
let (value, from) = match case.operands[0] {
(ref proto, Member::Obj(class)) => program.field_value(class, field, proto),
_ => (undefined.clone(), undefined_ctype),
};
body.put("return ", &value, from, ctype);
}
body.close(&cases);
body.line("abort();");
body.finish()
}
/// Call the entry, with undefined args /// Call the entry, with undefined args
fn start_body(program: &Program) -> String { fn start_body(program: &Program) -> String {
let arg_count = program.fns[program.fns.len() - 1].arg_count; let arg_count = program.fns[program.fns.len() - 1].arg_count;
let undefined = ( let args = vec![js::UNDEFINED.to_string(); arg_count];
unit_literal(Member::Undefined).to_string(), format!(" {};\n", host_call(program, program.entry, &args))
Member::Undefined,
);
let call = host_call(program, program.entry, &vec![undefined; arg_count]);
format!(" {call};\n")
} }
/// Call the stdlib's `__complete` with `h`, the handle of the op, if the program has it /// Call the stdlib's `__complete` with `h`, the handle of the op, if the program has it
fn complete_body(program: &Program) -> String { fn complete_body(program: &Program) -> String {
match complete_call(program) { match complete_call(program) {
Some(call) => format!(" (void)h;\n {call};\n"), Some(call) => format!(" {call};\n"),
None => " (void)h;\n".into(), None => " (void)h;\n".into(),
} }
} }
/// The call of `__complete`. Infer made the same call (see `TypeCache::call_host_complete`) /// The call of `__complete`, with the closure as its own env and `this` undefined. Infer
/// made the same call (see `TypeCache::call_host_complete`)
fn complete_call(program: &Program) -> Option<String> { fn complete_call(program: &Program) -> Option<String> {
let (callee, ctype) = program.binding("__complete")?; let (callee, ctype) = program.binding("__complete")?;
let class = ctype.class()?; let CType::One(Member::Obj(class)) = ctype else {
let ClassKind::Closure(fn_idx) = program.types.classes[class].kind else { return None;
};
let ClassKind::Closure(fn_idx) = program.classes.classes[class].kind else {
return None; return None;
}; };
let obj = program.types.classes[class].objs[0]; let obj = program.classes.classes[class].objs[0];
let types = [Type::Ref(obj), Type::Undefined, Type::Int]; let types = [Type::Ref(obj), Type::Undefined, Type::Int];
let walk = program.cache.find(fn_idx, None, &types, program.fns); let walk = program.cache.find(fn_idx, None, &types, program.fns);
let args = [ let args = [callee, js::UNDEFINED.to_string(), "h".to_string()];
(callee, Member::Obj(class)),
(
unit_literal(Member::Undefined).to_string(),
Member::Undefined,
),
("h".to_string(), Member::Int),
];
Some(host_call(program, walk, &args)) Some(host_call(program, walk, &args))
} }
/// A call of a walk's C function, with each arg converted to its param's C type /// A call of a walk's C function, with each arg converted to its param's C type
fn host_call(program: &Program, walk: usize, args: &[(String, Member)]) -> String { fn host_call(program: &Program, walk: usize, args: &[String]) -> String {
let args: Vec<String> = (args.iter().enumerate()) let args: Vec<String> = (args.iter().enumerate())
.map(|(i, (value, member))| { .map(|(i, arg)| {
let param = program.slot_ctype(walk, Operand::Arg(i)); let param = program.slot_ctype(walk, Operand::Arg(i)).c_name();
program.convert(value, *member, param) format!("CAST({param}, {arg})")
}) })
.collect(); .collect();
-570
View File
@@ -1,570 +0,0 @@
//! The C of each instr: literals, operators, tests, moves, calls and returns. The instrs on
//! objects are in `objects`
//!
//! An instr's C depends on the members of its operands. Each combination of members is one
//! case, with C of its own (see `Program::cases`). The operands that an instr pops come top
//! first (see regify), so a binary operator has its right side in `args[0]`
use super::{
js::JsFunction,
types::{CType, ClassKind, Member},
values::{base, int, strict_eq, to_int32, to_number, truthy, undefined, unit_literal},
};
use crate::{
infer::{CallSite, InstrRef, ObjAddr, Type},
libqjs::parse::{ImmOperand, JsValue, opcodes::OpCode},
native::NATIVES,
regify::{Operand, RegInstr},
transform::{FIRST_ARG, THIS_ARG},
};
/// What converting an object to a string or a number throws. The conversion would call the
/// object's methods, which is not supported
pub const NO_CONVERSION: &str = "converting an object to a string or a number is not supported";
impl JsFunction<'_> {
pub(super) fn emit_instr(&mut self, idx: usize, instr: &RegInstr) {
use OpCode::*;
match instr.op {
PushMinus1 | Push0 | Push1 | Push2 | Push3 | Push4 | Push5 | Push6 | Push7 | PushI8
| PushI16 | PushI32 | PushTrue | PushFalse | PushAtomValue | PushEmptyString
| PushConst | PushConst8 | Undefined | Null => {
let (expr, member) = self.literal(instr);
self.set_member(instr.dsts[0], &expr, member);
}
// Operators
Add | Sub | Mul | Div | Mod | Pow => self.binary(instr),
And | Or | Xor | Shl | Sar | Shr => self.bitwise(instr),
Neg | Plus | Inc | Dec | Not => self.unary(instr),
PostInc | PostDec => self.post(instr),
Lt | Lte | Gt | Gte => self.compare(instr),
StrictEq | StrictNeq | Eq | Neq => self.equality(instr),
// Tests
Lnot | Typeof | IsUndefined | TypeofIsUndefined | IsNull | IsUndefinedOrNull
| TypeofIsFunction => self.test(instr),
// Copies. A key stays as it is: a number is an index
SetLoc | SetName | ToPropkey => {
let (value, ctype) = self.get(instr.args[0]);
self.set(instr.dsts[0], &value, ctype);
}
// Objects (see `objects`)
Object | Fclosure => {
let class = self.class_here(idx);
let alloc = format!("__rt_alloc(sizeof(struct c{class}))");
self.set_member(instr.dsts[0], &alloc, Member::Obj(class));
}
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),
CloseLoc => self.close_loc(instr),
// Calls and returns
Call | CallMethod | CallConstructor => self.call(idx, instr),
Return | ReturnAsync => {
let (value, ctype) = self.get(instr.args[0]);
self.ret(&value, ctype);
}
ReturnUndef => {
let (value, ctype) = undefined();
self.ret(&value, ctype);
}
_ => unimplemented!("emit {instr:?}"),
}
}
/// The call site of the call at instr `idx`
fn call_site(&self, idx: usize) -> CallSite {
Some(InstrRef {
fn_idx: self.context.fn_idx,
instr_idx: idx,
})
}
/// The class of the objects that instr `idx` makes, in this walk's context
pub(super) fn class_here(&self, idx: usize) -> usize {
let obj = ObjAddr {
context: self.context,
instr: idx,
};
self.program.types.class(obj)
}
/// `close_loc x`: a local that closures share gets a new cell with the same value.
/// Closures that point to the old cell keep it (see transform/closure.rs). A local that
/// is not shared has no cell, so nothing changes
fn close_loc(&mut self, instr: &RegInstr) {
let ImmOperand::Loc(k) = instr.imm else {
unreachable!()
};
let slot = Operand::Local(k as usize);
if self.is_shared(slot) {
let (ctype, name) = (self.ctype(slot).c_name(), self.name(slot));
self.body.line(format!(
"{{ {ctype} *cell = __rt_alloc(sizeof({ctype})); *cell = *{name}; {name} = cell; }}"
));
}
}
// ───────────────────────────────────── Literals ─────────────────────────────────────
/// A literal, and its member
fn literal(&mut self, instr: &RegInstr) -> (String, Member) {
use OpCode::*;
match (instr.op, instr.imm) {
(PushMinus1 | Push0 | Push1 | Push2 | Push3 | Push4 | Push5 | Push6 | Push7, _) => {
let n = instr.op as i64 - Push0 as i64;
(n.to_string(), Member::Int)
}
(_, ImmOperand::I8(n)) => (int(n as i64), Member::Int),
(_, ImmOperand::I16(n)) => (int(n as i64), Member::Int),
(_, ImmOperand::I32(n)) => (int(n as i64), Member::Int),
(PushTrue, _) => ("true".into(), Member::Bool),
(PushFalse, _) => ("false".into(), Member::Bool),
(PushAtomValue, _) => {
let text = instr.imm.atom().text(self.program.atoms);
(self.program.string(&text), Member::Str)
}
(PushEmptyString, _) => (self.program.string(""), Member::Str),
(Undefined, _) => (unit_literal(Member::Undefined).into(), Member::Undefined),
(Null, _) => (unit_literal(Member::Null).into(), Member::Null),
_ => self.constant(instr),
}
}
/// A constant from the JS function's constant pool, and its member
fn constant(&mut self, instr: &RegInstr) -> (String, Member) {
match &self.f.cpool[instr.imm.const_idx()] {
JsValue::Int(n) => (int(*n as i64), Member::Int),
JsValue::Float(x) if x.is_nan() => ("NAN".into(), Member::Float),
JsValue::Float(x) if x.is_infinite() => (
if *x < 0.0 { "(-INFINITY)" } else { "INFINITY" }.into(),
Member::Float,
),
JsValue::Float(x) => (format!("{x:?}"), Member::Float),
JsValue::String(s) => (self.program.string(&s.text()), Member::Str),
JsValue::BoolTrue => ("true".into(), Member::Bool),
JsValue::BoolFalse => ("false".into(), Member::Bool),
JsValue::Undefined => (unit_literal(Member::Undefined).into(), Member::Undefined),
JsValue::NativeFn(idx) => (
unit_literal(Member::Native(*idx)).into(),
Member::Native(*idx),
),
c => unimplemented!("the constant {c:?}"),
}
}
// ──────────────────────────────────── Operators ─────────────────────────────────────
/// `l op r`. `+` concatenates if either side is a string or an object. Anything else
/// converts both sides to numbers, which are doubles
fn binary(&mut self, instr: &RegInstr) {
use OpCode::*;
let (dst, op) = (instr.dsts[0], instr.op);
let cases = self
.program
.cases(&[self.get(instr.args[1]), self.get(instr.args[0])]);
let text = |m: Member| matches!(m, Member::Str | Member::Obj(_) | Member::Native(_));
for case in &cases {
self.body.open(case);
let ((l, lm), (r, rm)) = (&case.operands[0], &case.operands[1]);
if op == Add && (text(*lm) || text(*rm)) {
let (Some(ls), Some(rs)) = (
self.program.to_string(l, *lm),
self.program.to_string(r, *rm),
) else {
self.body.throw(NO_CONVERSION);
continue;
};
self.set_member(dst, &format!("__rt_string_concat({ls}, {rs})"), Member::Str);
continue;
}
let (Some(ln), Some(rn)) = (to_number(l, *lm), to_number(r, *rm)) else {
self.body.throw(NO_CONVERSION);
continue;
};
// Numbers are doubles: C's operators on doubles are JS's, `%` is C's `fmod`, and
// `**` differs only for NaN and infinite exponents (see `__rt_pow`)
let expr = match op {
Add => format!("({ln} + {rn})"),
Sub => format!("({ln} - {rn})"),
Mul => format!("({ln} * {rn})"),
Mod => format!("fmod({ln}, {rn})"),
Pow => format!("__rt_pow({ln}, {rn})"),
_ => format!("({ln} / {rn})"),
};
self.set_member(dst, &expr, Member::Float);
}
self.body.close(&cases);
}
/// `&`, `|`, `^`, `<<`, `>>` and `>>>`. Each operand becomes an int32, as JS's ToInt32
/// does, and a shift counts its low 5 bits only. `>>>` gives a uint32, which is a float
fn bitwise(&mut self, instr: &RegInstr) {
use OpCode::*;
let (dst, op) = (instr.dsts[0], instr.op);
let cases = self
.program
.cases(&[self.get(instr.args[1]), self.get(instr.args[0])]);
for case in &cases {
self.body.open(case);
let ((l, lm), (r, rm)) = (&case.operands[0], &case.operands[1]);
let (Some(a), Some(b)) = (to_int32(l, *lm), to_int32(r, *rm)) else {
self.body.throw(NO_CONVERSION);
continue;
};
let (expr, member) = match op {
And => (format!("({a} & {b})"), Member::Int),
Or => (format!("({a} | {b})"), Member::Int),
Xor => (format!("({a} ^ {b})"), Member::Int),
Shl => (
format!("(int32_t)((uint32_t){a} << ({b} & 31))"),
Member::Int,
),
Sar => (format!("({a} >> ({b} & 31))"), Member::Int),
_ => (
format!("(double)((uint32_t){a} >> ({b} & 31))"),
Member::Float,
),
};
self.set_member(dst, &expr, member);
}
self.body.close(&cases);
}
/// `-v`, `+v`, `v + 1`, `v - 1` and `~v`, on the value as a number. Only `+v` keeps an int
/// an int: `-0` is a float, and so is anything that can leave the int32 range. `~` gives
/// an int32
fn unary(&mut self, instr: &RegInstr) {
use OpCode::*;
let (dst, op) = (instr.dsts[0], instr.op);
let cases = self.program.cases(&[self.get(instr.args[0])]);
for case in &cases {
self.body.open(case);
let (v, m) = &case.operands[0];
let (Some(n), Some(int)) = (to_number(v, *m), to_int32(v, *m)) else {
self.body.throw(NO_CONVERSION);
continue;
};
let (expr, member) = match (op, *m) {
(Not, _) => (format!("(~{int})"), Member::Int),
(Plus, Member::Int) => (v.clone(), Member::Int),
(Plus, _) => (n, Member::Float),
(Neg, _) => (format!("(-{n})"), Member::Float),
(Inc, _) => (format!("({n} + 1)"), Member::Float),
_ => (format!("({n} - 1)"), Member::Float),
};
self.set_member(dst, &expr, member);
}
self.body.close(&cases);
}
/// `v++` and `v--`: the old value, as a number, then the new one
fn post(&mut self, instr: &RegInstr) {
let (old, new) = (instr.dsts[0], instr.dsts[1]);
let step = if instr.op == OpCode::PostInc {
"+"
} else {
"-"
};
let cases = self.program.cases(&[self.get(instr.args[0])]);
for case in &cases {
self.body.open(case);
let (v, m) = &case.operands[0];
let Some(n) = to_number(v, *m) else {
self.body.throw(NO_CONVERSION);
continue;
};
self.set_member(old, &n, Member::Float);
self.set_member(new, &format!("({n} {step} 1)"), Member::Float);
}
self.body.close(&cases);
}
/// `<`, `<=`, `>` and `>=`. Two strings compare as strings, anything else as numbers
fn compare(&mut self, instr: &RegInstr) {
let dst = instr.dsts[0];
let op = match instr.op {
OpCode::Lt => "<",
OpCode::Lte => "<=",
OpCode::Gt => ">",
_ => ">=",
};
let cases = self
.program
.cases(&[self.get(instr.args[1]), self.get(instr.args[0])]);
for case in &cases {
self.body.open(case);
let ((l, lm), (r, rm)) = (&case.operands[0], &case.operands[1]);
if (*lm, *rm) == (Member::Str, Member::Str) {
let expr = format!("(__rt_string_cmp({l}, {r}) {op} 0)");
self.set_member(dst, &expr, Member::Bool);
continue;
}
let (Some(ln), Some(rn)) = (to_number(l, *lm), to_number(r, *rm)) else {
self.body.throw(NO_CONVERSION);
continue;
};
self.set_member(dst, &format!("({ln} {op} {rn})"), Member::Bool);
}
self.body.close(&cases);
}
/// `===`, `!==`, `==` and `!=`
fn equality(&mut self, instr: &RegInstr) {
use OpCode::*;
let (dst, op) = (instr.dsts[0], instr.op);
let not = if matches!(op, Neq | StrictNeq) {
"!"
} else {
""
};
let cases = self
.program
.cases(&[self.get(instr.args[1]), self.get(instr.args[0])]);
let nullish = |m: Member| matches!(m, Member::Undefined | Member::Null);
for case in &cases {
self.body.open(case);
let ((l, lm), (r, rm)) = (&case.operands[0], &case.operands[1]);
let numbers = (to_number(l, *lm), to_number(r, *rm));
// Undefined and null are equal to each other and to nothing else. Two values of
// one kind compare as `===` does. A string or a bool against a number compares as
// numbers
let eq = match (op, numbers) {
(StrictEq | StrictNeq, _) => strict_eq(l, *lm, r, *rm),
_ if nullish(*lm) || nullish(*rm) => (nullish(*lm) && nullish(*rm)).to_string(),
_ if base(*lm) == base(*rm) => strict_eq(l, *lm, r, *rm),
(_, (Some(ln), Some(rn))) => format!("({ln} == {rn})"),
_ => {
self.body.throw(NO_CONVERSION);
continue;
}
};
self.set_member(dst, &format!("({not}{eq})"), Member::Bool);
}
self.body.close(&cases);
}
/// The tests of one value: `!`, `typeof`, and whether it is undefined, null or a function
fn test(&mut self, instr: &RegInstr) {
use OpCode::*;
let (dst, op) = (instr.dsts[0], instr.op);
let cases = self.program.cases(&[self.get(instr.args[0])]);
for case in &cases {
self.body.open(case);
let (v, m) = (&case.operands[0].0, case.operands[0].1);
let known = match op {
IsUndefined | TypeofIsUndefined => m == Member::Undefined,
IsNull => m == Member::Null,
IsUndefinedOrNull => matches!(m, Member::Undefined | Member::Null),
TypeofIsFunction => self.program.is_function(m),
Lnot => {
self.set_member(dst, &format!("(!{})", truthy(v, m)), Member::Bool);
continue;
}
_ => {
let name = match m {
Member::Undefined => "undefined",
Member::Bool => "boolean",
Member::Int | Member::Float => "number",
Member::Str => "string",
_ if self.program.is_function(m) => "function",
_ => "object",
};
let name = self.program.string(name);
self.set_member(dst, &name, Member::Str);
continue;
}
};
self.set_member(dst, &known.to_string(), Member::Bool);
}
self.body.close(&cases);
}
// ────────────────────────────────────── Calls ───────────────────────────────────────
/// A call. Its operands are the callee's params: the callee itself, `this`, then the args
/// (see regify). Each closure that the callee can be is called by its own case. A callee
/// that is not a function throws
fn call(&mut self, idx: usize, instr: &RegInstr) {
let cases = self.program.cases(&[self.get(instr.args[0])]);
for case in &cases {
self.body.open(case);
let (callee, member) = &case.operands[0];
match (*member, self.program.class_kind(*member)) {
(Member::Native(native), _) => self.native_call(native, instr),
(Member::Obj(class), ClassKind::Closure(_)) => {
self.closure_call(idx, instr, callee, class)
}
_ => self.body.throw("not a function"),
}
}
self.body.close(&cases);
}
/// A call of `callee`, a closure of `class`. Infer walked the closure's JS function for
/// the types of this call's args, and that walk is the C function to call. An arg that
/// the call does not pass is undefined. `new` gives what the constructor returns if that
/// is an object, else the new object itself, which is `this`
fn closure_call(&mut self, idx: usize, instr: &RegInstr, callee: &str, class: usize) {
let program = self.program;
let dst = instr.dsts[0];
let ClassKind::Closure(fn_idx) = program.types.classes[class].kind else {
unreachable!()
};
let mut types: Vec<Type> = instr.args.iter().map(|&s| self.ty(s)).collect();
types[0] = Type::Ref(program.types.classes[class].objs[0]);
let walk = (program.cache).find(fn_idx, self.call_site(idx), &types, program.fns);
let mut args = vec![];
for i in 0..program.fns[fn_idx].arg_count {
let (value, ctype) = match i {
0 => (callee.to_string(), CType::One(Member::Obj(class))),
_ if i < instr.args.len() => self.get(instr.args[i]),
_ => undefined(),
};
let param = program.slot_ctype(walk, Operand::Arg(i));
args.push(self.body.convert(&value, ctype, param));
}
let call = format!("js{walk}({})", args.join(", "));
let ret = program.cache.ret(walk);
// A function that never returns gives no value
if *ret == Type::Unknown {
self.body.line(format!("{call};"));
self.body.line("abort(); // does not return");
return;
}
let ret = program.types.of(ret);
if instr.op != OpCode::CallConstructor {
return self.set(dst, &call, ret);
}
let result = self.body.temp(ret);
self.body.line(format!("{result} = {call};"));
let (this, this_ctype) = self.get(instr.args[THIS_ARG as usize]);
let cases = program.cases(&[(result, ret)]);
for case in &cases {
self.body.open(case);
match case.operands[0] {
(ref obj, Member::Obj(class)) => self.set_member(dst, obj, Member::Obj(class)),
_ => self.set(dst, &this, this_ctype),
}
}
self.body.close(&cases);
}
/// A call of a native: a C call, with only the args. An int and a float both pass for a
/// number. An arg of any other wrong kind throws (see `native::ret_type`). An arg that the
/// call does not pass is undefined
fn native_call(&mut self, idx: usize, instr: &RegInstr) {
let native = &NATIVES[idx];
let dst = instr.dsts[0];
let slots = (0..native.arg_types.len()).map(|i| instr.args.get(FIRST_ARG as usize + i));
let operands: Vec<(String, CType)> = slots
.map(|slot| slot.map_or_else(undefined, |&slot| self.get(slot)))
.collect();
let cases = self.program.cases(&operands);
for case in &cases {
self.body.open(case);
let args = case.operands.iter().zip(native.arg_types);
let args: Option<Vec<String>> = args
.map(|((v, m), param)| native_arg(v, *m, param))
.collect();
match args {
Some(args) => {
let call = format!("{}({})", native.name, args.join(", "));
self.set_member(dst, &call, native_member(&native.ret_type));
}
None => self.body.throw("a native got an arg of the wrong kind"),
}
}
self.body.close(&cases);
}
}
/// An arg of `member` as the native param `param`, if it can pass
fn native_arg(value: &str, member: Member, param: &Type) -> Option<String> {
match (member, param) {
(Member::Int, Type::Float) => Some(format!("(double){value}")),
(Member::Float, Type::Int) => Some(format!("__rt_to_int32({value})")),
(Member::Int, Type::Int) | (Member::Float, Type::Float) => Some(value.into()),
(Member::Str, Type::Str(_)) | (Member::Undefined, Type::Undefined) => Some(value.into()),
_ => None,
}
}
/// The member of a native's return type
fn native_member(ty: &Type) -> Member {
match ty {
Type::Undefined => Member::Undefined,
Type::Int => Member::Int,
Type::Float => Member::Float,
Type::Str(_) => Member::Str,
ty => unreachable!("a native returns {ty:?}"),
}
}
+208 -118
View File
@@ -2,34 +2,33 @@
//! //!
//! Infer walks a JS function once for each context, which is the function in one call site, //! Infer walks a JS function once for each context, which is the function in one call site,
//! and each set of arg types. Each walk is one C function, `js<walk>`, in which every value //! and each set of arg types. Each walk is one C function, `js<walk>`, in which every value
//! has the type that infer found in that walk. What the walk returns is in memory too (see //! has the type that infer found in that walk.
//! `TypeCache::ret`).
//! //!
//! Each local, arg and reg is a C local, of the C type of its type. A local or arg that a //! Each local, arg and reg is a C local, of the C type of its type. A local or arg that
//! closure shares is a pointer to a cell (see transform/closure.rs). The cell is made when the //! closures share is a pointer to a cell (see transform/closure.rs). The cell is made when the
//! function starts, and again at each `close_loc`. A shared arg is copied into its cell when //! function starts, and again at each `close_loc`. A shared arg is copied into its cell when
//! the function starts. //! the function starts.
//! //!
//! Each block that can run is a C label. Its instrs come in order, then its postfix copies, //! Each block that can run is a C label. Its instrs come in order, then its postfix copies,
//! then its jump. If infer found that a condition is always true or always false, the jump //! then its jump. If infer found that a condition is always true or always false, the jump
//! goes directly to the one block that can run. //! goes directly to the one block that can run.
//!
//! Most instrs are one line: `SET(dst, JS_<op>(operands))`. The runtime's macros convert
//! each operand and the result, by their C types (see lib/rt/core.h).
use super::{ use super::program::{Program, slots};
body::Body,
program::{Program, slots},
types::{CType, Member},
values::truthy,
};
use crate::{ use crate::{
infer::{Context, Type}, infer::{Context, Type},
libqjs::parse::opcodes::OpCode, libqjs::parse::{ImmOperand, JsValue, opcodes::OpCode},
regify::{BasicBlock, Operand, RegFn}, regify::{BasicBlock, Operand, RegFn, RegInstr},
}; };
/// The undefined value, as C
pub const UNDEFINED: &str = "(undefined_t){0}";
/// A walk of a JS function, as its C function is written /// A walk of a JS function, as its C function is written
pub struct JsFunction<'a> { pub struct JsFunction<'a> {
pub program: &'a Program<'a>, pub program: &'a Program<'a>,
pub body: Body<'a>,
pub walk: usize, pub walk: usize,
pub context: Context, // the walk's function, and the call site that it is called from pub context: Context, // the walk's function, and the call site that it is called from
pub f: &'a RegFn, pub f: &'a RegFn,
@@ -44,13 +43,13 @@ pub fn signature(program: &Program, walk: usize) -> String {
let params: Vec<String> = (0..f.arg_count) let params: Vec<String> = (0..f.arg_count)
.map(|k| { .map(|k| {
let ctype = program.slot_ctype(walk, Operand::Arg(k)); let ctype = program.slot_ctype(walk, Operand::Arg(k)).c_name();
let prefix = if shared.contains(&Operand::Arg(k)) { let prefix = if shared.contains(&Operand::Arg(k)) {
"p" "p"
} else { } else {
"a" "a"
}; };
format!("{} {prefix}{k}", ctype.c_name()) format!("{ctype} {prefix}{k}")
}) })
.collect(); .collect();
@@ -59,36 +58,38 @@ pub fn signature(program: &Program, walk: usize) -> String {
} else { } else {
params.join(", ") params.join(", ")
}; };
let ret = program.types.of(program.cache.ret(walk)); let ret = program.ret_ctype(walk).c_name();
format!("static {} js{walk}({params})", ret.c_name()) format!("static {ret} js{walk}({params})")
} }
/// The body of `js<walk>`: its locals, args and regs, then each block that can run /// The body of `js<walk>`: its locals, args and regs, then each block that can run
pub fn body(program: &Program, walk: usize) -> String { pub fn body(program: &Program, walk: usize) -> String {
let w = &program.cache.walks[walk];
let f = program.js_fn(walk); let f = program.js_fn(walk);
let reachable = &program.cache.walks[walk].reachable;
let mut js = JsFunction { let js = JsFunction {
program, program,
body: Body::new(program),
walk, walk,
context: w.context, context: program.cache.walks[walk].context,
f, f,
shared: f.captured_slots(), shared: f.captured_slots(),
}; };
js.declare_slots(); let mut c = indent(&js.declarations());
for (i, block) in f.blocks.iter().enumerate() { for (i, block) in f.blocks.iter().enumerate() {
if w.reachable[i] { if reachable[i] {
js.body.indent(-1); c += &format!("b{i}:;\n");
js.body.line(format!("b{i}:;")); c += &indent(&js.block(block, reachable));
js.body.indent(1);
js.block(block, &w.reachable);
} }
} }
js.body.finish() c
}
/// Each line of `code`, indented once
pub fn indent(code: &str) -> String {
code.lines().map(|line| format!(" {line}\n")).collect()
} }
impl JsFunction<'_> { impl JsFunction<'_> {
@@ -101,8 +102,17 @@ impl JsFunction<'_> {
} }
} }
pub fn is_shared(&self, slot: Operand) -> bool { /// The value of a local, arg or reg, as C. A shared one is the value in its cell
self.shared.contains(&slot) pub fn get(&self, slot: Operand) -> String {
match self.shared.contains(&slot) {
true => format!("(*{})", self.name(slot)),
false => self.name(slot),
}
}
/// `dst = value`, converted to the C type of `dst`
pub fn set(&self, dst: Operand, value: &str) -> String {
format!("SET({}, {value});\n", self.get(dst))
} }
/// The type of a local, arg or reg /// The type of a local, arg or reg
@@ -110,64 +120,34 @@ impl JsFunction<'_> {
self.program.slot_type(self.walk, slot) self.program.slot_type(self.walk, slot)
} }
/// The C type of a local, arg or reg
pub fn ctype(&self, slot: Operand) -> CType {
self.program.slot_ctype(self.walk, slot)
}
/// The value of a local, arg or reg, as C, and its C type
pub fn get(&self, slot: Operand) -> (String, CType) {
match self.is_shared(slot) {
true => (format!("(*{})", self.name(slot)), self.ctype(slot)),
false => (self.name(slot), self.ctype(slot)),
}
}
/// Set a local, arg or reg to `value`, of C type `from`
pub fn set(&mut self, dst: Operand, value: &str, from: CType) {
let star = if self.is_shared(dst) { "*" } else { "" };
let target = format!("{star}{} = ", self.name(dst));
let to = self.ctype(dst);
self.body.put(&target, value, from, to);
}
/// Set a local, arg or reg to `value`, of one member
pub fn set_member(&mut self, dst: Operand, value: &str, member: Member) {
self.set(dst, value, CType::One(member));
}
/// Return `value`, of C type `from`, as the C type of what the walk returns
pub fn ret(&mut self, value: &str, from: CType) {
let to = self.program.types.of(self.program.cache.ret(self.walk));
self.body.put("return ", value, from, to);
}
/// A C local for each local and reg, and a cell for each shared local and arg. A module /// A C local for each local and reg, and a cell for each shared local and arg. A module
/// binding, which is a local of the entry, also puts its cell in its C global /// binding, which is a local of the entry, also puts its cell in its C global
fn declare_slots(&mut self) { fn declarations(&self) -> String {
let entry = self.walk == self.program.entry; let entry = self.walk == self.program.entry;
let mut c = String::new();
for slot in slots(self.f) { for slot in slots(self.f) {
let ctype = self.ctype(slot).c_name(); let ctype = self.program.slot_ctype(self.walk, slot).c_name();
let name = self.name(slot); let name = self.name(slot);
let cell = format!("__rt_alloc(sizeof({ctype}))"); let cell = format!("__rt_alloc(sizeof({ctype}))");
let line = match (slot, self.is_shared(slot)) { c += &match (slot, self.shared.contains(&slot)) {
(Operand::Arg(_), false) => continue, (Operand::Arg(_), false) => continue,
(Operand::Arg(k), true) => format!("{ctype} *{name} = {cell}; *{name} = p{k};"), (Operand::Arg(k), true) => format!("{ctype} *{name} = {cell}; *{name} = p{k};\n"),
(Operand::Local(k), true) if entry && self.program.globals.contains_key(&k) => { (Operand::Local(k), true) if entry && self.program.globals.contains_key(&k) => {
format!("{ctype} *{name} = g{k} = {cell};") format!("{ctype} *{name} = g{k} = {cell};\n")
} }
(_, true) => format!("{ctype} *{name} = {cell};"), (_, true) => format!("{ctype} *{name} = {cell};\n"),
(_, false) => format!("{ctype} {name} = {{0}};"), (_, false) => format!("{ctype} {name} = {{0}};\n"),
}; };
self.body.line(line);
}
} }
/// A block: its instrs, its postfix copies, then its jump c
fn block(&mut self, block: &BasicBlock, reachable: &[bool]) { }
/// A block: its instrs, its postfix copies, then its jump. If an instr cannot run, nor
/// can the rest of the block
fn block(&self, block: &BasicBlock, reachable: &[bool]) -> String {
use OpCode::*; use OpCode::*;
let last = &self.f.code[block.end - 1]; let last = &self.f.code[block.end - 1];
@@ -176,82 +156,192 @@ impl JsFunction<'_> {
Goto | Goto8 | Goto16 | IfFalse | IfFalse8 | IfTrue | IfTrue8 Goto | Goto8 | Goto16 | IfFalse | IfFalse8 | IfTrue | IfTrue8
); );
let end = if jumps { block.end - 1 } else { block.end }; let end = if jumps { block.end - 1 } else { block.end };
let mut c = String::new();
for idx in block.start..end { for idx in block.start..end {
if !self.instr(idx) { match self.instr(idx) {
return; Some(code) => c += &code,
None => return c + "abort(); // not reached\n",
} }
} }
for copy in &block.postfix { for copy in &block.postfix {
let (value, ctype) = self.get(copy.args[0]); c += &self.set(copy.dsts[0], &self.get(copy.args[0]));
self.set(copy.dsts[0], &value, ctype);
} }
// succ[0] is the jump's target, and succ[1] is the next block // succ[0] is the jump's target, and succ[1] is the next block
let can_go = |i: usize| block.succ.get(i).filter(|&&s| reachable[s]).copied(); let can_go = |i: usize| block.succ.get(i).filter(|&&s| reachable[s]).copied();
match last.op { c + &match last.op {
IfFalse | IfFalse8 | IfTrue | IfTrue8 => match (can_go(0), can_go(1)) { IfFalse | IfFalse8 | IfTrue | IfTrue8 => match (can_go(0), can_go(1)) {
(Some(target), Some(next)) => { (Some(target), Some(next)) => {
let cond = self.truthy(last.args[0]);
let not = if matches!(last.op, IfTrue | IfTrue8) { let not = if matches!(last.op, IfTrue | IfTrue8) {
"" ""
} else { } else {
"!" "!"
}; };
self.body.line(format!("if ({not}{cond}) goto b{target};")); let cond = self.get(last.args[0]);
self.body.line(format!("goto b{next};")); format!("if ({not}TRUTHY({cond})) goto b{target};\ngoto b{next};\n")
} }
(Some(only), None) | (None, Some(only)) => self.body.line(format!("goto b{only};")), (Some(only), None) | (None, Some(only)) => format!("goto b{only};\n"),
(None, None) => self.body.line("abort(); // not reached"), (None, None) => "abort(); // not reached\n".into(),
}, },
Return | ReturnAsync | ReturnUndef => {} Return | ReturnAsync | ReturnUndef => String::new(),
_ => { _ => can_go(0).map_or(String::new(), |next| format!("goto b{next};\n")),
if let Some(next) = can_go(0) {
self.body.line(format!("goto b{next};"));
}
}
} }
} }
/// Whether a local, arg or reg is truthy, in a new temp. Gives the temp's name /// The C of instr `idx`. `None` if an operand has no value: then the instr cannot run
fn truthy(&mut self, slot: Operand) -> String { fn instr(&self, idx: usize) -> Option<String> {
let cond = self.body.temp(CType::One(Member::Bool)); use OpCode::*;
let cases = self.program.cases(&[self.get(slot)]);
for case in &cases {
self.body.open(case);
let (value, member) = &case.operands[0];
self.body
.line(format!("{cond} = {};", truthy(value, *member)));
}
self.body.close(&cases);
cond
}
/// Write the C of instr `idx`. Gives false if an operand has no value: then the instr
/// cannot run, and nor can the rest of its block
fn instr(&mut self, idx: usize) -> bool {
let instr = &self.f.code[idx]; let instr = &self.f.code[idx];
if instr.op == OpCode::Nop {
return true;
}
// The first operand of `define_field_ref` is a location, not a value // The first operand of `define_field_ref` is a location, not a value
let values = match instr.op { let values = match instr.op {
OpCode::DefineFieldRef => &instr.args[1..], DefineFieldRef => &instr.args[1..],
_ => &instr.args[..], _ => &instr.args[..],
}; };
if values.iter().any(|slot| self.ty(*slot) == Type::Unknown) { if values.iter().any(|&slot| self.ty(slot) == Type::Unknown) {
self.body.line("abort(); // not reached"); return None;
return false;
} }
self.emit_instr(idx, instr); let get = |i: usize| self.get(instr.args[i]);
true
Some(match instr.op {
Nop => String::new(),
PushMinus1 | Push0 | Push1 | Push2 | Push3 | Push4 | Push5 | Push6 | Push7 | PushI8
| PushI16 | PushI32 | PushTrue | PushFalse | PushAtomValue | PushEmptyString
| PushConst | PushConst8 | Undefined | Null => {
self.set(instr.dsts[0], &self.literal(instr))
}
// Copies. A key stays as it is: a number is an index
SetLoc | SetName | ToPropkey => self.set(instr.dsts[0], &get(0)),
PostInc | PostDec => {
let name = if instr.op == PostInc {
"POST_INC"
} else {
"POST_DEC"
};
let (old, new) = (self.get(instr.dsts[0]), self.get(instr.dsts[1]));
format!("{name}({old}, {new}, {});\n", get(0))
}
Return | ReturnAsync => self.ret(&get(0)),
ReturnUndef => self.ret(UNDEFINED),
// A new cell for a shared local (see transform/closure.rs). A local that is not
// shared has no cell, so nothing changes
CloseLoc => match instr.imm {
ImmOperand::Loc(k) if self.shared.contains(&Operand::Local(k as usize)) => {
format!("NEW_CELL({});\n", self.name(Operand::Local(k as usize)))
}
_ => String::new(),
},
Call | CallMethod | CallConstructor => self.call(idx, instr),
// An operator or a test: the runtime's macro of the same name. The operands that
// an instr pops come top first (see regify), so they are reversed
op => match operator(op) {
Some(name) => {
let operands: Vec<String> = (0..instr.args.len()).rev().map(get).collect();
self.set(instr.dsts[0], &format!("{name}({})", operands.join(", ")))
}
None => self.object_instr(idx, instr),
},
})
}
/// `return value`, converted to the C type that the walk returns
fn ret(&self, value: &str) -> String {
let ctype = self.program.ret_ctype(self.walk).c_name();
format!("return CAST({ctype}, {value});\n")
}
/// A literal, as C. Each has a cast, so that its C type is the one that its member has
fn literal(&self, instr: &RegInstr) -> String {
use OpCode::*;
match (instr.op, instr.imm) {
(PushMinus1 | Push0 | Push1 | Push2 | Push3 | Push4 | Push5 | Push6 | Push7, _) => {
int(instr.op as i64 - Push0 as i64)
}
(_, ImmOperand::I8(n)) => int(n as i64),
(_, ImmOperand::I16(n)) => int(n as i64),
(_, ImmOperand::I32(n)) => int(n as i64),
(PushTrue, _) => "(bool)1".into(),
(PushFalse, _) => "(bool)0".into(),
(PushAtomValue, _) => self
.program
.string(&instr.imm.atom().text(self.program.atoms)),
(PushEmptyString, _) => self.program.string(""),
(Undefined, _) => UNDEFINED.into(),
(Null, _) => "(null_t){0}".into(),
_ => match &self.f.cpool[instr.imm.const_idx()] {
JsValue::Int(n) => int(*n as i64),
JsValue::Float(x) if x.is_nan() => "((double)NAN)".into(),
JsValue::Float(x) if x.is_infinite() && *x > 0.0 => "((double)INFINITY)".into(),
JsValue::Float(x) if x.is_infinite() => "(-(double)INFINITY)".into(),
JsValue::Float(x) => format!("{x:?}"),
JsValue::String(s) => self.program.string(&s.text()),
JsValue::BoolTrue => "(bool)1".into(),
JsValue::BoolFalse => "(bool)0".into(),
JsValue::Undefined => UNDEFINED.into(),
JsValue::NativeFn(idx) => format!("(native_t){{{idx}}}"),
c => unimplemented!("the constant {c:?}"),
},
} }
} }
}
/// An int, as C
fn int(n: i64) -> String {
match n == i32::MIN as i64 {
true => "(int32_t)INT32_MIN".into(),
false => format!("(int32_t){n}"),
}
}
/// The runtime's macro for an operator or a test (see lib/rt/core.h)
fn operator(op: OpCode) -> Option<&'static str> {
use OpCode::*;
Some(match op {
Add => "ADD",
Sub => "SUB",
Mul => "MUL",
Div => "DIV",
Mod => "MOD",
Pow => "POW",
Neg => "NEG",
Plus => "PLUS",
Inc => "INC",
Dec => "DEC",
Not => "NOT",
And => "AND",
Or => "OR",
Xor => "XOR",
Shl => "SHL",
Sar => "SAR",
Shr => "SHR",
Lt => "LT",
Lte => "LTE",
Gt => "GT",
Gte => "GTE",
StrictEq => "STRICT_EQ",
StrictNeq => "STRICT_NEQ",
Eq => "EQ",
Neq => "NEQ",
Lnot => "LNOT",
Typeof => "TYPEOF",
IsUndefined | TypeofIsUndefined => "IS_UNDEFINED",
IsNull => "IS_NULL",
IsUndefinedOrNull => "IS_NULLISH",
TypeofIsFunction => "IS_FUNCTION",
_ => return None,
})
}
+31 -33
View File
@@ -1,52 +1,43 @@
//! Emits the program as one C file //! Emits the program as one C file
//! //!
//! The C file holds only three things: types, statics and functions. So emit has two steps: //! The C file holds only three things: types, statics and functions. `Program::new` finds
//! all of them, from the JS functions and what infer found, and `emit` writes each of them,
//! in an order that C can compile:
//! //!
//! 1. `Program::new` finds all of them, from the JS functions and what infer found: //! - **Types:** a struct for each class of objects (see `classes`).
//! - **Types:** a struct for each class of objects, and a tagged union for each type with
//! several members (see `types`).
//! - **Statics:** each string literal, and a C global for each module binding. //! - **Statics:** each string literal, and a C global for each module binding.
//! - **Functions:** one for each walk of a JS function, one for each lookup, and the //! - **Functions:** one for each walk of a JS function, one for each lookup, and the host's
//! host's two entry points (see `functions`). //! two entry points (see `functions`).
//! 2. `emit` writes each of them, in an order that C can compile.
//! //!
//! A value that no write reached is undefined. //! The C of a JS function is mostly macro calls. The runtime's macros know how each kind of
//! value behaves (see lib/rt/core.h), so emit only says which operator to use.
mod body; mod calls;
mod classes;
mod functions; mod functions;
mod instr;
mod js; mod js;
mod objects; mod objects;
mod program; mod program;
mod types;
mod values;
use crate::{flatten::ModuleBindings, infer::TypeCache, libqjs::parse::Atom, regify::RegFn}; use crate::{flatten::ModuleBindings, infer::TypeCache, libqjs::parse::Atom, regify::RegFn};
use classes::ClassKind;
use program::Program; use program::Program;
use types::{CType, ClassKind};
const HEADER: &str = "// Generated by jsrt\n#define JSRT_IMPLEMENTATION\n#include \"jsrt.h\"\n\n#include <math.h>\n#include <stdlib.h>\n\n"; const HEADER: &str = "// Generated by jsrt\n#define JSRT_IMPLEMENTATION\n#include \"jsrt.h\"\n\n";
/// The C of the program, which includes the runtime's `jsrt.h` /// The C of the program, which includes the runtime's `jsrt.h`
pub fn emit(fns: &[RegFn], cache: &TypeCache, atoms: &[Atom], bindings: &ModuleBindings) -> String { pub fn emit(fns: &[RegFn], cache: &TypeCache, atoms: &[Atom], bindings: &ModuleBindings) -> String {
let program = Program::new(fns, cache, atoms, bindings); let program = Program::new(fns, cache, atoms, bindings);
let classes = 0..program.types.classes.len(); let classes = 0..program.classes.classes.len();
let unions = 0..program.types.unions.len();
let mut c = String::from(HEADER); let mut c = String::from(HEADER);
// Types. Each struct is declared first, because a union can hold a pointer to one // Types. Each struct is declared first, because a struct can point to another
for class in classes.clone() { for class in classes.clone() {
c += &format!("struct c{class};\n"); c += &format!("struct c{class};\n");
} }
c += "\n"; c += &class_table(&program);
for union in unions {
c += &union_definition(&program, union);
}
c += "\n";
for class in classes { for class in classes {
c += &struct_definition(&program, class); c += &struct_definition(&program, class);
@@ -65,8 +56,7 @@ pub fn emit(fns: &[RegFn], cache: &TypeCache, atoms: &[Atom], bindings: &ModuleB
c += "\n"; c += "\n";
for function in &program.functions { for function in &program.functions {
c += &function.signature(&program); c += &format!("{};\n", function.signature(&program));
c += ";\n";
} }
c += "\n"; c += "\n";
@@ -78,20 +68,28 @@ pub fn emit(fns: &[RegFn], cache: &TypeCache, atoms: &[Atom], bindings: &ModuleB
c c
} }
/// A tagged union: a tag, and a C union of the members. The tag is the member's index /// What the runtime needs to know of the classes (see lib/rt/core.h): the class of a pointer
fn union_definition(program: &Program, id: usize) -> String { /// to an object, and whether a class is closures. The table ends in a 0, because C has no
let members = program.types.unions[id].iter().enumerate(); /// empty arrays
let members: String = members fn class_table(program: &Program) -> String {
.map(|(tag, &member)| format!(" {} m{tag};", CType::One(member).c_name())) let classes = &program.classes.classes;
let tags: String = (0..classes.len())
.map(|id| format!(" struct c{id} *: {id}u, \\\n"))
.collect();
let closures: String = (classes.iter())
.map(|class| matches!(class.kind, ClassKind::Closure(_)) as u8)
.map(|is_function| format!("{is_function}, "))
.collect(); .collect();
format!("typedef struct {{ uint8_t tag; union {{{members} }} as; }} u{id};\n") let class = format!("#define JSRT_CLASS(x) _Generic((x), \\\n{tags} default: 0u)\n");
let is_function = format!("const bool jsrt_class_is_function[] = {{{closures}0}};\n");
format!("\n{class}\n{is_function}\n")
} }
/// The struct of a class: an array's length and elements, then the fields. A field that /// The struct of a class: an array's length and elements, then the fields. A field that
/// shares a location is a pointer to it. A field that some objects lack has a `has_` flag /// shares a location is a pointer to it. A field that some objects lack has a `has_` flag
fn struct_definition(program: &Program, id: usize) -> String { fn struct_definition(program: &Program, id: usize) -> String {
let class = &program.types.classes[id]; let class = &program.classes.classes[id];
let mut c = format!("struct c{id} {{\n"); let mut c = format!("struct c{id} {{\n");
if class.kind == ClassKind::Array { if class.kind == ClassKind::Array {
+158 -215
View File
@@ -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 //! 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 //! 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. //! `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::{ use super::classes::{ClassKind, Member};
js::JsFunction, use super::js::{JsFunction, UNDEFINED, indent};
types::{CType, ClassKind, Member}, use super::program::{Program, throw};
values::{as_index, in_range, undefined},
};
use crate::{ use crate::{
infer::LENGTH_FIELD, infer::{LENGTH_FIELD, ObjAddr},
libqjs::parse::opcodes::OpCode, libqjs::parse::{AtomRef, opcodes::OpCode},
regify::{Operand, RegInstr}, regify::{Operand, RegInstr},
transform::PROTO_FIELD,
}; };
// ──────────────────────────────────────── Fields ────────────────────────────────────────
impl JsFunction<'_> { impl JsFunction<'_> {
/// `[a, b, ...]`. The operands are the elements, last first pub(super) fn object_instr(&self, idx: usize, instr: &RegInstr) -> String {
pub(super) fn array_from(&mut self, idx: usize, instr: &RegInstr) { use OpCode::*;
match instr.op {
Object | Fclosure => {
let class = self.class_here(idx); let class = self.class_here(idx);
let elems = self.program.types.classes[class].elems; let alloc = format!("(struct c{class} *)__rt_alloc(sizeof(struct c{class}))");
let array = self.body.temp(CType::One(Member::Obj(class))); self.set(instr.dsts[0], &alloc)
let n = instr.args.len(); }
ArrayFrom => self.array_from(idx, instr),
self.body GetField | GetLength => self.get_field(instr),
.line(format!("{array} = __rt_alloc(sizeof(struct c{class}));")); DefineField | PutField => self.put_field(instr),
self.body.line(format!( DefineFieldRef => self.share_field(instr),
"{array}->items = __rt_grow(NULL, &{array}->cap, {n}, sizeof *{array}->items);" CloseField => self.close_field(instr),
)); GetArrayEl => self.get_elem(instr),
self.body.line(format!("{array}->length = {n};")); PutArrayEl => self.put_elem(instr),
_ => unimplemented!("emit {instr:?}"),
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);
} }
self.set_member(instr.dsts[0], &array, Member::Obj(class)); /// 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 /// `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`). /// gets the field from the stdlib's prototype of its kind (see `Program::field_class`).
/// Undefined and null throw /// 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 { let field = match instr.op {
OpCode::GetLength => LENGTH_FIELD, OpCode::GetLength => LENGTH_FIELD,
_ => instr.imm.atom(), _ => instr.imm.atom(),
}; };
let dst = instr.dsts[0];
let cases = self.program.cases(&[self.get(instr.args[0])]);
for case in &cases { self.split(instr.args[0], |obj, member| {
self.body.open(case); match (member, program.field_class(member, field)) {
let (obj, member) = (&case.operands[0].0, case.operands[0].1);
match (member, self.program.field_class(member, field)) {
(Member::Obj(_), Some(class)) => { (Member::Obj(_), Some(class)) => {
let (value, ctype) = self.program.field_value(class, field, obj); self.set(dst, &program.field_value(class, field, obj))
self.set(dst, &value, ctype);
} }
(_, Some(class)) => { (_, Some(class)) => {
let (proto, _) = self.program.builtin_proto(member).unwrap(); let (proto, _) = program.builtin_proto(member).unwrap();
let (value, ctype) = self.program.field_value(class, field, &proto); self.set(dst, &program.field_value(class, field, &proto))
self.set(dst, &value, ctype);
} }
(Member::Str, None) if field == LENGTH_FIELD => { (Member::Str, None) if field == LENGTH_FIELD => {
let length = format!("__rt_string_length({obj})"); self.set(dst, &format!("__rt_string_length({obj})"))
self.set_member(dst, &length, Member::Int);
}
(Member::Obj(_), None) => {
self.set_member(dst, &format!("{obj}->length"), Member::Int)
} }
(Member::Obj(_), None) => self.set(dst, &format!("{obj}->length")),
(Member::Undefined | Member::Null, _) => { (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"), (Member::Native(_), _) => throw("a native function has no fields"),
_ => self.body.throw("fields of primitives need the stdlib"), _ => 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 /// `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 /// 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 field = instr.imm.atom();
let (value, from) = self.get(instr.args[0]); let value = self.get(instr.args[0]);
let cases = self.program.cases(&[self.get(instr.args[1])]);
for case in &cases { self.split(instr.args[1], |obj, member| {
self.body.open(case); match (member, program.class_kind(member)) {
let (obj, member) = (&case.operands[0].0, case.operands[0].1);
match (member, self.program.class_kind(member)) {
(Member::Obj(_), ClassKind::Array) if field == LENGTH_FIELD => { (Member::Obj(_), ClassKind::Array) if field == LENGTH_FIELD => {
self.set_length(obj, &value, from) format!("ARRAY_SET_LENGTH({obj}, {value});\n")
} }
(Member::Obj(class), _) => { (Member::Obj(class), _) => {
let f = self.program.types.field(class, field).unwrap(); let f = program.classes.field(class, field).unwrap();
let target = self.program.field_access(obj, f); let set = format!("SET({}, {value});\n", program.field_access(obj, f));
self.body
.put(&format!("{target} = "), &value, from, f.ctype);
if !f.defined { match f.defined {
let name = self.program.field_name(field); true => set,
self.body.line(format!("{obj}->has_{name} = true;")); 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 /// `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 /// 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 /// cell, or a reg that holds an object whose field of the same name points to the cell
/// (see regify/walk.rs) /// (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 name = self.program.field_name(instr.imm.atom());
let location = instr.args[0];
let cell = match location { let cell = match instr.args[0] {
Operand::Reg(_) => match self.get(location) { Operand::Reg(_) => format!("{}->{name}", self.get(instr.args[0])),
(holder, CType::One(Member::Obj(_))) => format!("{holder}->{name}"),
_ => unreachable!("a shared location is held by an object"),
},
slot => self.name(slot), slot => self.name(slot),
}; };
let cases = self.program.cases(&[self.get(instr.args[1])]); self.split(instr.args[1], |obj, _| format!("{obj}->{name} = {cell};\n"))
for case in &cases {
self.body.open(case);
self.body
.line(format!("{}->{name} = {cell};", case.operands[0].0));
}
self.body.close(&cases);
} }
/// `close_field x`: the field, which points to a cell, gets a new cell with the same /// `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) /// value. Closures that point to the old cell keep it (see transform/closure.rs)
pub(super) fn close_field(&mut self, instr: &RegInstr) { fn close_field(&self, instr: &RegInstr) -> String {
let field = instr.imm.atom(); let name = self.program.field_name(instr.imm.atom());
let cases = self.program.cases(&[self.get(instr.args[0])]); self.split(instr.args[0], |obj, _| {
format!("NEW_CELL({obj}->{name});\n")
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);
}
}
// ─────────────────────────────────────── Elements ───────────────────────────────────────
impl JsFunction<'_> {
/// `a[i]` on an array, and `s[i]` on a string. An index out of range gives undefined. /// `a[i]` on an array, and `s[i]` on a string. An index out of range gives undefined.
/// Undefined and null throw /// Undefined and null throw
pub(super) fn get_elem(&mut self, instr: &RegInstr) { fn get_elem(&self, instr: &RegInstr) -> String {
let dst = instr.dsts[0]; let (dst, index) = (self.get(instr.dsts[0]), self.get(instr.args[0]));
let operands = [self.get(instr.args[1]), self.get(instr.args[0])];
let cases = self.program.cases(&operands);
for case in &cases { self.split(instr.args[1], |obj, member| {
self.body.open(case); match (member, self.program.class_kind(member)) {
let ((obj, obj_member), (index, index_member)) = (&case.operands[0], &case.operands[1]); (Member::Obj(_), ClassKind::Array) => {
let at = as_index(index, *index_member); format!("ARRAY_GET({dst}, {obj}, {index});\n")
}
let (length, item, ctype) = match (*obj_member, self.program.class_kind(*obj_member)) { (Member::Str, _) => format!("STRING_GET({dst}, {obj}, {index});\n"),
(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),
),
(Member::Undefined | Member::Null, _) => { (Member::Undefined | Member::Null, _) => {
self.body throw("cannot read an element of undefined or null")
.throw("cannot read an element of undefined or null");
continue;
} }
_ => unreachable!("infer gets elements only of arrays and strings"), _ => 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 (none, none_ctype) = undefined(); let value = program.field_access("o", proto);
let check = in_range(index, *index_member, &length); let on_proto = program.split(&value, &proto.ty, |proto, member| match member {
self.body.line(format!("if ({check}) {{")); Member::Obj(proto_class) => ret(&program.field_value(proto_class, field, proto)),
self.body.indent(1); _ => ret(UNDEFINED),
self.set(dst, &item, ctype); });
self.body.indent(-1);
self.body.line("} else {"); lacks + &on_proto
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);
for case in &cases {
self.body.open(case);
let ((obj, obj_member), (index, index_member)) = (&case.operands[0], &case.operands[1]);
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; indent(&c)
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);
}
} }
+131 -192
View File
@@ -5,8 +5,9 @@
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
use super::classes::{CType, ClassField, ClassKind, Classes, Member};
use super::functions::Function; use super::functions::Function;
use super::types::{CType, ClassField, ClassKind, Member, Types}; use super::js::indent;
use crate::{ use crate::{
flatten::ModuleBindings, flatten::ModuleBindings,
infer::{LENGTH_FIELD, Type, TypeCache, join, module_binding}, infer::{LENGTH_FIELD, Type, TypeCache, join, module_binding},
@@ -15,20 +16,6 @@ use crate::{
transform::{PROTO_FIELD, PROTOTYPE_FIELD}, transform::{PROTO_FIELD, PROTOTYPE_FIELD},
}; };
/// Strings that the C code can need for any program: the names that `typeof` gives, and the
/// results of converting undefined, null and the bools to strings
const FIXED_STRINGS: &[&str] = &[
"undefined",
"null",
"true",
"false",
"boolean",
"number",
"string",
"function",
"object",
];
pub struct Program<'a> { pub struct Program<'a> {
// The input: the JS functions, and what infer found // The input: the JS functions, and what infer found
pub fns: &'a [RegFn], pub fns: &'a [RegFn],
@@ -38,7 +25,7 @@ pub struct Program<'a> {
pub entry: usize, // the walk of the entry: the JS function that runs the program pub entry: usize, // the walk of the entry: the JS function that runs the program
// The output // The output
pub types: Types, pub classes: Classes, // types: a struct for each class of objects
pub strings: Vec<String>, // statics: the text of each string literal pub strings: Vec<String>, // statics: the text of each string literal
pub globals: BTreeMap<usize, CType>, // statics: the module bindings, which are locals of the entry, and their C types pub globals: BTreeMap<usize, CType>, // statics: the module bindings, which are locals of the entry, and their C types
pub functions: Vec<Function>, pub functions: Vec<Function>,
@@ -57,7 +44,7 @@ impl<'a> Program<'a> {
atoms, atoms,
bindings, bindings,
entry: cache.find(fns.len() - 1, None, &[], fns), entry: cache.find(fns.len() - 1, None, &[], fns),
types: Types::new(&cache.memory), classes: Classes::new(&cache.memory),
strings: vec![], strings: vec![],
globals: BTreeMap::new(), globals: BTreeMap::new(),
functions: vec![], functions: vec![],
@@ -66,23 +53,11 @@ impl<'a> Program<'a> {
program.add_globals(); program.add_globals();
program.add_functions(); program.add_functions();
program.add_strings(); program.add_strings();
program.add_unions();
program program
} }
/// Each module binding is a C global, so that any function can use the stdlib's /// One C function for each walk in infer's last round, one for each lookup, and the
fn add_globals(&mut self) { /// host's two entry points
let entry = self.fns.len() - 1;
for &local in self.bindings.values() {
let ty = self.cache.memory.get(module_binding(entry, local));
let ctype = self.types.add(ty);
self.globals.insert(local, ctype);
}
}
/// One C function for each walk in infer's last round, one for each
/// lookup, and the host's two entry points
fn add_functions(&mut self) { fn add_functions(&mut self) {
let walks = (0..self.cache.walks.len()).filter(|&walk| self.cache.is_live(walk)); let walks = (0..self.cache.walks.len()).filter(|&walk| self.cache.is_live(walk));
let mut lookups = BTreeSet::new(); let mut lookups = BTreeSet::new();
@@ -99,8 +74,8 @@ impl<'a> Program<'a> {
self.functions.extend([Function::Start, Function::Complete]); self.functions.extend([Function::Start, Function::Complete]);
} }
/// The lookups that a JS function's walk needs: one for each field that it gets, on each /// The lookups that a walk needs: one for each field that it gets, on each class that can
/// class that can lack the field /// lack the field
fn add_lookups(&self, walk: usize, lookups: &mut BTreeSet<(usize, AtomRef)>) { fn add_lookups(&self, walk: usize, lookups: &mut BTreeSet<(usize, AtomRef)>) {
for instr in self.instrs(walk) { for instr in self.instrs(walk) {
let field = match instr.op { let field = match instr.op {
@@ -111,7 +86,7 @@ impl<'a> Program<'a> {
let ty = self.slot_type(walk, instr.args[0]); let ty = self.slot_type(walk, instr.args[0]);
for member in self.types.type_members(&ty) { for member in self.classes.members(&ty) {
if let Some(class) = self.field_class(member, field) { if let Some(class) = self.field_class(member, field) {
self.add_lookup(class, field, lookups); self.add_lookup(class, field, lookups);
} }
@@ -122,7 +97,7 @@ impl<'a> Program<'a> {
/// The lookup of `field` on `class`, if its objects can lack the field. If they can, the /// The lookup of `field` on `class`, if its objects can lack the field. If they can, the
/// lookup goes on to their prototypes, so those need lookups too /// lookup goes on to their prototypes, so those need lookups too
fn add_lookup(&self, class: usize, field: AtomRef, lookups: &mut BTreeSet<(usize, AtomRef)>) { fn add_lookup(&self, class: usize, field: AtomRef, lookups: &mut BTreeSet<(usize, AtomRef)>) {
let defined = self.types.field(class, field).is_some_and(|f| f.defined); let defined = self.classes.field(class, field).is_some_and(|f| f.defined);
if defined || !lookups.insert((class, field)) { if defined || !lookups.insert((class, field)) {
return; return;
@@ -133,63 +108,45 @@ impl<'a> Program<'a> {
} }
} }
/// The text of every string literal: the fixed ones, and the ones in the JS functions /// The text of every string literal in the walks
fn add_strings(&mut self) { fn add_strings(&mut self) {
let mut strings: Vec<String> = FIXED_STRINGS.iter().map(|s| s.to_string()).collect(); let walks = self.functions.iter().filter_map(|function| match function {
Function::Js(walk) => Some(*walk),
_ => None,
});
for &function in &self.functions { let mut strings = vec![];
let Function::Js(walk) = function else {
continue;
};
let cpool = &self.js_fn(walk).cpool;
for walk in walks {
for instr in self.instrs(walk) { for instr in self.instrs(walk) {
let text = match instr.op { let text = match instr.op {
OpCode::PushAtomValue => instr.imm.atom().text(self.atoms), OpCode::PushAtomValue => instr.imm.atom().text(self.atoms),
OpCode::PushEmptyString => String::new(), OpCode::PushEmptyString => String::new(),
OpCode::PushConst | OpCode::PushConst8 => match &cpool[instr.imm.const_idx()] { OpCode::PushConst | OpCode::PushConst8 => {
match &self.js_fn(walk).cpool[instr.imm.const_idx()] {
JsValue::String(s) => s.text(), JsValue::String(s) => s.text(),
_ => continue, _ => continue,
}, }
}
_ => continue, _ => continue,
}; };
if !strings.contains(&text) {
strings.push(text); strings.push(text);
} }
} }
for text in strings {
if !self.strings.contains(&text) {
self.strings.push(text);
}
}
} }
/// The union of every C type that the functions use, if it has several members. The self.strings = strings;
/// classes' fields have theirs already (see `Types::new`)
fn add_unions(&mut self) {
let types: Vec<Type> = (self.functions.iter())
.flat_map(|function| self.value_types(function))
.collect();
for ty in &types {
self.types.add(ty);
}
} }
/// The types of the values in a function: the locals, args and regs of a JS function, /// Each module binding is a C global, so that any function can use the stdlib's
/// and what it returns, or what a lookup gives fn add_globals(&mut self) {
fn value_types(&self, function: &Function) -> Vec<Type> { let entry = self.fns.len() - 1;
match *function {
Function::Js(walk) => { for &local in self.bindings.values() {
let slots = slots(self.js_fn(walk)) let ty = self.cache.memory.get(module_binding(entry, local));
.into_iter() self.globals.insert(local, self.classes.ctype(ty));
.map(|slot| self.slot_type(walk, slot));
slots.chain([self.cache.ret(walk).clone()]).collect()
}
Function::Lookup(class, field) => vec![self.lookup_type(class, field)],
Function::Start | Function::Complete => vec![],
} }
} }
@@ -202,25 +159,16 @@ impl<'a> Program<'a> {
let blocks = blocks.filter(|(_, reachable)| **reachable); let blocks = blocks.filter(|(_, reachable)| **reachable);
blocks.flat_map(|(block, _)| &f.code[block.start..block.end]) blocks.flat_map(|(block, _)| &f.code[block.start..block.end])
} }
}
// ────────────────────────────────────── C values ───────────────────────────────────────
/// One combination of members of some operands. When an operand is a union, the C code tests
/// its tag, and then uses the member that the tag says
#[derive(Clone)]
pub struct Case {
pub operands: Vec<(String, Member)>, // each operand, as C, as a value of one member
pub tests: Vec<String>, // the C tests that the operands are these members: one for each union
pub first: bool, // the first case of its operands: an `if`. The others are `else if`
}
impl<'a> Program<'a> {
/// The JS function of a walk /// The JS function of a walk
pub fn js_fn(&self, walk: usize) -> &'a RegFn { pub fn js_fn(&self, walk: usize) -> &'a RegFn {
&self.fns[self.cache.walks[walk].context.fn_idx] &self.fns[self.cache.walks[walk].context.fn_idx]
} }
}
// ─────────────────────────────────────── Values ────────────────────────────────────────
impl Program<'_> {
/// The type of a local, arg or reg of a JS function, in one walk /// The type of a local, arg or reg of a JS function, in one walk
pub fn slot_type(&self, walk: usize, slot: Operand) -> Type { pub fn slot_type(&self, walk: usize, slot: Operand) -> Type {
let addr = self.cache.addr(walk, slot, self.fns); let addr = self.cache.addr(walk, slot, self.fns);
@@ -229,79 +177,21 @@ impl<'a> Program<'a> {
/// The C type of a local, arg or reg of a JS function, in one walk /// The C type of a local, arg or reg of a JS function, in one walk
pub fn slot_ctype(&self, walk: usize, slot: Operand) -> CType { pub fn slot_ctype(&self, walk: usize, slot: Operand) -> CType {
self.types.of(&self.slot_type(walk, slot)) self.classes.ctype(&self.slot_type(walk, slot))
}
/// The C type of what a walk returns
pub fn ret_ctype(&self, walk: usize) -> CType {
self.classes.ctype(self.cache.ret(walk))
} }
pub fn class_kind(&self, member: Member) -> ClassKind { pub fn class_kind(&self, member: Member) -> ClassKind {
match member { match member {
Member::Obj(class) => self.types.classes[class].kind, Member::Obj(class) => self.classes.classes[class].kind,
_ => ClassKind::Obj, _ => ClassKind::Obj,
} }
} }
/// Whether a value of `member` is a function
pub fn is_function(&self, member: Member) -> bool {
let closure = matches!(self.class_kind(member), ClassKind::Closure(_));
closure || matches!(member, Member::Native(_))
}
/// Every combination of the members of `operands`. Each operand is C, and its C type
pub fn cases(&self, operands: &[(String, CType)]) -> Vec<Case> {
let mut cases = vec![Case {
operands: vec![],
tests: vec![],
first: true,
}];
for (value, ctype) in operands {
let mut next = vec![];
for case in &cases {
for (tag, member) in self.types.members_of(*ctype).into_iter().enumerate() {
let mut case = case.clone();
match ctype {
CType::One(_) => case.operands.push((value.clone(), member)),
CType::Union(_) => {
case.operands.push((format!("{value}.as.m{tag}"), member));
case.tests.push(format!("{value}.tag == {tag}"));
}
}
case.first = next.is_empty();
next.push(case);
}
}
cases = next;
}
cases
}
/// `value`, of `member`, as a value of C type `to`. Types only grow, so `to` holds
/// `member`, or holds a float if `member` is an int
pub fn convert(&self, value: &str, member: Member, to: CType) -> String {
let members = self.types.members_of(to);
let same = members.iter().position(|&m| m == member);
let float = members.iter().position(|&m| m == Member::Float);
let float = float.filter(|_| member == Member::Int);
let Some(tag) = same.or(float) else {
panic!("emit: a value of {member:?} where {members:?} was inferred");
};
let value = match members[tag] == member {
true => value.to_string(),
false => format!("(double){value}"),
};
match to {
CType::One(_) => value,
CType::Union(id) => format!("(u{id}){{.tag = {tag}, .as.m{tag} = {value}}}"),
}
}
/// A string literal, as C: a pointer to its static /// A string literal, as C: a pointer to its static
pub fn string(&self, text: &str) -> String { pub fn string(&self, text: &str) -> String {
match self.strings.iter().position(|s| s == text) { match self.strings.iter().position(|s| s == text) {
@@ -310,23 +200,6 @@ impl<'a> Program<'a> {
} }
} }
/// `value`, of `member`, as a string, as JS's ToString does. `None` for an object: its
/// conversion calls its methods, which is not supported
pub fn to_string(&self, value: &str, member: Member) -> Option<String> {
Some(match member {
Member::Undefined => self.string("undefined"),
Member::Null => self.string("null"),
Member::Bool => {
let (t, f) = (self.string("true"), self.string("false"));
format!("({value} ? {t} : {f})")
}
Member::Int => format!("__rt_number_to_string((double){value})"),
Member::Float => format!("__rt_number_to_string({value})"),
Member::Str => value.into(),
Member::Obj(_) | Member::Native(_) => return None,
})
}
/// The value of the stdlib's module binding `name`, as C, and its C type /// The value of the stdlib's module binding `name`, as C, and its C type
pub fn binding(&self, name: &str) -> Option<(String, CType)> { pub fn binding(&self, name: &str) -> Option<(String, CType)> {
let local = *self.bindings.get(name)?; let local = *self.bindings.get(name)?;
@@ -334,6 +207,68 @@ impl<'a> Program<'a> {
} }
} }
// ─────────────────────────────────────── Members ───────────────────────────────────────
impl Program<'_> {
/// The C of `code` for each member that `value`, of type `ty`, can be. `code` gets the
/// value as C of that member's C type. A value with one member is that member, so this
/// is its code. Else the value is a `jsrt_value`, and a `switch` on its tag has a case
/// for each member. A number in a `jsrt_value` is a double, so an int is a float there
pub fn split(&self, value: &str, ty: &Type, code: impl Fn(&str, Member) -> String) -> String {
let members = self.classes.members(ty);
if let [member] = members[..] {
return code(value, member);
}
let float = members.contains(&Member::Float);
let natives = members.iter().filter(|m| matches!(m, Member::Native(_)));
if natives.count() > 1 {
unimplemented!("a value that can be one of several natives");
}
let mut c = format!("switch ({value}.tag) {{\n");
for member in members {
let member = match member {
Member::Int if float => continue,
Member::Int => Member::Float,
member => member,
};
// An object comes out as a `void *`, so it is cast to its struct
let ctype = CType::One(member).c_name();
let as_member = match member {
Member::Obj(_) => format!("(({ctype})CAST({ctype}, {value}))"),
_ => format!("CAST({ctype}, {value})"),
};
let body = indent(&code(&as_member, member));
c += &format!("case {}: {{\n{body} break;\n}}\n", tag(member));
}
c + "default: abort();\n}\n"
}
}
/// The tag of a member in a `jsrt_value` (see lib/rt/core.h)
fn tag(member: Member) -> String {
match member {
Member::Undefined => "JSRT_UNDEFINED".into(),
Member::Null => "JSRT_NULL".into(),
Member::Bool => "JSRT_BOOL".into(),
Member::Int | Member::Float => "JSRT_NUMBER".into(),
Member::Str => "JSRT_STRING".into(),
Member::Native(_) => "JSRT_NATIVE".into(),
Member::Obj(class) => format!("JSRT_OBJECT + {class}"),
}
}
/// A throw of a TypeError that says `what` (see `__rt_type_error`)
pub fn throw(what: &str) -> String {
format!("__rt_type_error(\"{what}\");\n")
}
// ─────────────────────────────────────── Fields ──────────────────────────────────────── // ─────────────────────────────────────── Fields ────────────────────────────────────────
impl Program<'_> { impl Program<'_> {
@@ -361,17 +296,12 @@ impl Program<'_> {
} }
} }
/// The value of `field` on `obj`, an object of `class`, and its C type. If every object /// The value of `field` on `obj`, an object of `class`, as C. If every object of the
/// of the class has the field, that is the field itself. Else it is a call of the field's /// class has the field, that is the field itself. Else it is a call of the field's lookup
/// lookup pub fn field_value(&self, class: usize, field: AtomRef, obj: &str) -> String {
pub fn field_value(&self, class: usize, field: AtomRef, obj: &str) -> (String, CType) { match self.classes.field(class, field) {
match self.types.field(class, field) { Some(f) if f.defined => self.field_access(obj, f),
Some(f) if f.defined => (self.field_access(obj, f), f.ctype), _ => format!("{}({obj})", Function::Lookup(class, field).name(self)),
_ => {
let lookup = Function::Lookup(class, field).name(self);
let ctype = self.types.of(&self.lookup_type(class, field));
(format!("{lookup}({obj})"), ctype)
}
} }
} }
@@ -390,15 +320,16 @@ impl Program<'_> {
} }
} }
/// The type that the lookup of `field` on `class` gives: the join of the field's types on /// The C type that the lookup of `field` on `class` gives: of the join of the field's
/// the class's objects, and on their prototypes (see `Memory::read_field`) /// types on the class's objects, and on their prototypes (see `Memory::read_field`)
pub fn lookup_type(&self, class: usize, field: AtomRef) -> Type { pub fn lookup_ctype(&self, class: usize, field: AtomRef) -> CType {
let objs = &self.types.classes[class].objs; let objs = &self.classes.classes[class].objs;
let memory = &self.cache.memory; let memory = &self.cache.memory;
let types = objs let types = objs
.iter() .iter()
.map(|&obj| memory.read_field(&Type::Ref(obj), field)); .map(|&obj| memory.read_field(&Type::Ref(obj), field));
types.fold(Type::Unknown, |all, ty| join(&all, &ty)) self.classes
.ctype(&types.fold(Type::Unknown, |all, ty| join(&all, &ty)))
} }
/// The prototype of a primitive or an array, as C, and its class: the `prototype` field of /// The prototype of a primitive or an array, as C, and its class: the `prototype` field of
@@ -412,26 +343,34 @@ impl Program<'_> {
_ => return None, _ => return None,
}; };
let (stdlib_class, ctype) = self.binding(name)?; let (stdlib_class, CType::One(Member::Obj(class))) = self.binding(name)? else {
let proto = self.types.field(ctype.class()?, PROTOTYPE_FIELD)?; return None;
let value = self.field_access(&stdlib_class, proto); };
Some((value, proto.ctype.class()?)) let proto = self.classes.field(class, PROTOTYPE_FIELD)?;
let CType::One(Member::Obj(proto_class)) = proto.ctype else {
return None;
};
Some((self.field_access(&stdlib_class, proto), proto_class))
} }
/// The classes that the prototype of an object of `class` can be. An array's prototype /// The classes that the prototype of an object of `class` can be. An array's prototype
/// is the stdlib's. Another object's is its `__proto__` field, if it has one /// is the stdlib's. Another object's is its `__proto__` field, if it has one
pub fn proto_classes(&self, class: usize) -> Vec<usize> { pub fn proto_classes(&self, class: usize) -> Vec<usize> {
if self.types.classes[class].kind == ClassKind::Array { if self.classes.classes[class].kind == ClassKind::Array {
let proto = self.builtin_proto(Member::Obj(class)); let proto = self.builtin_proto(Member::Obj(class));
return proto.map(|(_, class)| class).into_iter().collect(); return proto.map(|(_, class)| class).into_iter().collect();
} }
let Some(proto) = self.types.field(class, PROTO_FIELD) else { let Some(proto) = self.classes.field(class, PROTO_FIELD) else {
return vec![]; return vec![];
}; };
let members = self.types.members_of(proto.ctype); let members = self.classes.members(&proto.ty).into_iter();
let classes = members.into_iter().map(|member| CType::One(member).class()); let classes = members.map(|member| match member {
Member::Obj(class) => Some(class),
_ => None,
});
classes.flatten().collect() classes.flatten().collect()
} }
} }
-104
View File
@@ -1,104 +0,0 @@
//! JS's rules on values, as C: the conversions and tests that depend only on a value's member
use super::types::{CType, Member};
/// The literal of a member that has one value
pub fn unit_literal(member: Member) -> &'static str {
match member {
Member::Undefined | Member::Native(_) => "(undefined_t){0}",
Member::Null => "(null_t){0}",
_ => unreachable!(),
}
}
/// The undefined value, and its C type
pub fn undefined() -> (String, CType) {
(
unit_literal(Member::Undefined).into(),
CType::One(Member::Undefined),
)
}
/// The C literal of an int
pub fn int(n: i64) -> String {
match n == i32::MIN as i64 {
true => "INT32_MIN".into(),
false => n.to_string(),
}
}
/// `expr`, a value of `member`, as a number (a `double`), as JS's ToNumber does. `None` for an
/// object: its conversion calls its methods, which is not supported
pub fn to_number(expr: &str, member: Member) -> Option<String> {
Some(match member {
Member::Undefined => "NAN".into(),
Member::Null => "0.0".into(),
Member::Bool | Member::Int => format!("(double){expr}"),
Member::Float => expr.into(),
Member::Str => format!("__rt_string_to_number({expr})"),
Member::Obj(_) | Member::Native(_) => return None,
})
}
/// `expr`, a value of `member`, as an int32, as JS's ToInt32 does. `None` for an object
pub fn to_int32(expr: &str, member: Member) -> Option<String> {
match member {
Member::Int => Some(expr.into()),
_ => Some(format!("__rt_to_int32({})", to_number(expr, member)?)),
}
}
/// Whether `expr`, a value of `member`, is truthy
pub fn truthy(expr: &str, member: Member) -> String {
match member {
Member::Undefined | Member::Null => "false".into(),
Member::Bool => expr.into(),
Member::Int => format!("({expr} != 0)"),
Member::Float => format!("({expr} == {expr} && {expr} != 0)"),
Member::Str => format!("(__rt_string_length({expr}) != 0)"),
Member::Obj(_) | Member::Native(_) => "true".into(),
}
}
/// A JS base type: the kinds of value that `===` tells apart
pub fn base(member: Member) -> u8 {
match member {
Member::Undefined => 0,
Member::Null => 1,
Member::Bool => 2,
Member::Int | Member::Float => 3,
Member::Str => 4,
Member::Obj(_) | Member::Native(_) => 5,
}
}
/// `a === b`, for one member of each. Different kinds are never equal. Numbers compare as
/// numbers, strings by their text, and objects by identity
pub fn strict_eq(l: &str, lm: Member, r: &str, rm: Member) -> String {
match (lm, rm) {
_ if base(lm) != base(rm) => "false".into(),
(Member::Undefined | Member::Null, _) => "true".into(),
(Member::Str, _) => format!("__rt_string_eq({l}, {r})"),
(Member::Obj(_), Member::Obj(_)) => format!("((void *){l} == (void *){r})"),
(Member::Native(a), Member::Native(b)) => (a == b).to_string(),
(Member::Obj(_) | Member::Native(_), _) => "false".into(),
_ => format!("({l} == {r})"),
}
}
/// An index of member `member` as an `int32_t`
pub fn as_index(index: &str, member: Member) -> String {
match member {
Member::Int => index.into(),
Member::Float => format!("(int32_t){index}"),
_ => unreachable!("an index is a number (see infer)"),
}
}
/// Whether an index is an array index below `length`. A float must be a whole number
pub fn in_range(index: &str, member: Member, length: &str) -> String {
match member {
Member::Int => format!("{index} >= 0 && {index} < {length}"),
_ => format!("{index} >= 0 && {index} < {length} && {index} == (double)(int32_t){index}"),
}
}
+458 -1
View File
@@ -1,4 +1,5 @@
// Values: the types that every other part uses, the allocator and errors. // Values: the types that every other part uses, the allocator, errors, and
// JS's operators on every kind of value.
// An object is a struct of its class, which the emitted C defines, and every // An object is a struct of its class, which the emitted C defines, and every
// pointer to one is a real object: undefined and null are values of their own // pointer to one is a real object: undefined and null are values of their own
#pragma once #pragma once
@@ -10,6 +11,9 @@
typedef struct { char _; } undefined_t; typedef struct { char _; } undefined_t;
typedef struct { char _; } null_t; typedef struct { char _; } null_t;
// A native fn as a value: its index in the compiler's list (see native.rs)
typedef struct { int32_t idx; } native_t;
// A string: UTF-16 code units, as JS counts them // A string: UTF-16 code units, as JS counts them
typedef struct jsrt_string *string_t; typedef struct jsrt_string *string_t;
@@ -65,3 +69,456 @@ _Noreturn void __rt_type_error(const char *what) {
} }
#endif // JSRT_IMPLEMENTATION #endif // JSRT_IMPLEMENTATION
// ─────────────────────────────────── Values and operators ────────────────────────────────
//
// Infer gives each value a type. A value whose type has one member is a plain
// C value of that member's C type: undefined_t, null_t, bool, int32_t, double,
// string_t, native_t, or a pointer to an object's struct. A value whose type
// has several members is a jsrt_value: a tag, which says which kind it is, and
// the value. A number in a jsrt_value is always a double
//
// Each macro gives a result of one exact C type, because SET picks the
// conversion by it: a comparison is cast to bool, and a bitwise result to int32_t
//
// The macros here take a value of any of these C types. VALUE(x) makes a
// jsrt_value of any value, and each operator is one fn on jsrt_values. With a
// plain C value, the tag is a constant, so the C compiler removes the tag and
// the switch, and only the code for that kind is left
//
// The emitted C defines two things that the macros use:
// JSRT_CLASS(x) the class of `x`, a pointer to an object
// jsrt_class_is_function[k] whether the objects of class `k` are closures
//
// The array macros use their args more than once, so an arg must be a name
//
// The operators call str.h's and number.h's fns, so this part comes after them
#include <math.h>
#include <string.h>
#include "str.h"
#include "number.h"
enum {
JSRT_UNDEFINED,
JSRT_NULL,
JSRT_BOOL,
JSRT_NUMBER,
JSRT_STRING,
JSRT_NATIVE,
JSRT_OBJECT, // an object of class k has the tag JSRT_OBJECT + k
};
typedef struct {
uint32_t tag;
union {
bool b;
double n;
string_t s;
int32_t native;
void *o;
} as;
} jsrt_value;
extern const bool jsrt_class_is_function[];
#define JSRT_INLINE static inline __attribute__((always_inline))
#define JSRT_NO_CONVERSION "converting an object to a string or a number is not supported"
// ─────────────────────────────── Making and reading values ───────────────────────────────
// Any value, as a jsrt_value
#define VALUE(x) \
_Generic((x), \
undefined_t: jsrt_of_undefined, \
null_t: jsrt_of_null, \
bool: jsrt_of_bool, \
int32_t: jsrt_of_int, \
double: jsrt_of_number, \
string_t: jsrt_of_string, \
native_t: jsrt_of_native, \
jsrt_value: jsrt_of_value, \
default: jsrt_of_object)((x), JSRT_CLASS(x))
// A jsrt_value, as a value of the C type of `like`. Its kind must be one that
// the C type holds: infer makes sure of it
#define FROM_VALUE(like, v) \
_Generic((like), \
undefined_t: jsrt_to_undefined, \
null_t: jsrt_to_null, \
bool: jsrt_to_bool, \
int32_t: jsrt_to_int, \
double: jsrt_to_number, \
string_t: jsrt_to_string, \
native_t: jsrt_to_native, \
jsrt_value: jsrt_to_value, \
default: jsrt_to_object)(v)
// `dst = x`, with `x` converted to the C type of `dst`
#define SET(dst, x) ((dst) = FROM_VALUE(dst, VALUE(x)))
// `x`, converted to the C type `T`
#define CAST(T, x) FROM_VALUE((T){0}, VALUE(x))
JSRT_INLINE jsrt_value jsrt_of_undefined(undefined_t x, uint32_t class) {
(void)x, (void)class;
return (jsrt_value){.tag = JSRT_UNDEFINED};
}
JSRT_INLINE jsrt_value jsrt_of_null(null_t x, uint32_t class) {
(void)x, (void)class;
return (jsrt_value){.tag = JSRT_NULL};
}
JSRT_INLINE jsrt_value jsrt_of_bool(bool x, uint32_t class) {
(void)class;
return (jsrt_value){.tag = JSRT_BOOL, .as.b = x};
}
JSRT_INLINE jsrt_value jsrt_of_int(int32_t x, uint32_t class) {
(void)class;
return (jsrt_value){.tag = JSRT_NUMBER, .as.n = x};
}
JSRT_INLINE jsrt_value jsrt_of_number(double x, uint32_t class) {
(void)class;
return (jsrt_value){.tag = JSRT_NUMBER, .as.n = x};
}
JSRT_INLINE jsrt_value jsrt_of_string(string_t x, uint32_t class) {
(void)class;
return (jsrt_value){.tag = JSRT_STRING, .as.s = x};
}
JSRT_INLINE jsrt_value jsrt_of_native(native_t x, uint32_t class) {
(void)class;
return (jsrt_value){.tag = JSRT_NATIVE, .as.native = x.idx};
}
JSRT_INLINE jsrt_value jsrt_of_value(jsrt_value x, uint32_t class) {
(void)class;
return x;
}
JSRT_INLINE jsrt_value jsrt_of_object(const void *x, uint32_t class) {
return (jsrt_value){.tag = JSRT_OBJECT + class, .as.o = (void *)x};
}
// A read of a value as a kind that it is not is a bug in the compiler. The tag
// is a constant wherever the kind is known, so the check costs nothing there
JSRT_INLINE void jsrt_check(bool ok) {
if (!ok) __rt_type_error("jsrt: a value was read as a kind that it is not");
}
JSRT_INLINE undefined_t jsrt_to_undefined(jsrt_value v) {
jsrt_check(v.tag == JSRT_UNDEFINED);
return (undefined_t){0};
}
JSRT_INLINE null_t jsrt_to_null(jsrt_value v) {
jsrt_check(v.tag == JSRT_NULL);
return (null_t){0};
}
JSRT_INLINE bool jsrt_to_bool(jsrt_value v) {
jsrt_check(v.tag == JSRT_BOOL);
return v.as.b;
}
JSRT_INLINE int32_t jsrt_to_int(jsrt_value v) {
jsrt_check(v.tag == JSRT_NUMBER);
return (int32_t)v.as.n;
}
JSRT_INLINE double jsrt_to_number(jsrt_value v) {
jsrt_check(v.tag == JSRT_NUMBER);
return v.as.n;
}
JSRT_INLINE string_t jsrt_to_string(jsrt_value v) {
jsrt_check(v.tag == JSRT_STRING);
return v.as.s;
}
JSRT_INLINE native_t jsrt_to_native(jsrt_value v) {
jsrt_check(v.tag == JSRT_NATIVE);
return (native_t){v.as.native};
}
JSRT_INLINE jsrt_value jsrt_to_value(jsrt_value v) { return v; }
JSRT_INLINE void *jsrt_to_object(jsrt_value v) {
jsrt_check(v.tag >= JSRT_OBJECT);
return v.as.o;
}
// ────────────────────────────────────── Conversions ──────────────────────────────────────
// The strings that conversions and `typeof` give, made once
enum { JSRT_S_UNDEFINED, JSRT_S_NULL, JSRT_S_TRUE, JSRT_S_FALSE, JSRT_S_BOOLEAN,
JSRT_S_NUMBER, JSRT_S_STRING, JSRT_S_FUNCTION, JSRT_S_OBJECT };
static inline string_t jsrt_name(int name) {
static const char *const texts[] = {"undefined", "null", "true", "false", "boolean",
"number", "string", "function", "object"};
static string_t made[sizeof texts / sizeof *texts];
return made[name] ? made[name] : (made[name] = __rt_string_lit(texts[name]));
}
// JS's ToNumber. An object's conversion would call its methods, which is not supported
JSRT_INLINE double jsrt_number(jsrt_value v) {
switch (v.tag) {
case JSRT_UNDEFINED: return NAN;
case JSRT_NULL: return 0;
case JSRT_BOOL: return v.as.b;
case JSRT_NUMBER: return v.as.n;
case JSRT_STRING: return __rt_string_to_number(v.as.s);
default: __rt_type_error(JSRT_NO_CONVERSION);
}
}
// JS's ToString
JSRT_INLINE string_t jsrt_string(jsrt_value v) {
switch (v.tag) {
case JSRT_UNDEFINED: return jsrt_name(JSRT_S_UNDEFINED);
case JSRT_NULL: return jsrt_name(JSRT_S_NULL);
case JSRT_BOOL: return jsrt_name(v.as.b ? JSRT_S_TRUE : JSRT_S_FALSE);
case JSRT_NUMBER: return __rt_number_to_string(v.as.n);
case JSRT_STRING: return v.as.s;
default: __rt_type_error(JSRT_NO_CONVERSION);
}
}
// JS's ToBoolean
JSRT_INLINE bool jsrt_truthy(jsrt_value v) {
switch (v.tag) {
case JSRT_UNDEFINED: case JSRT_NULL: return false;
case JSRT_BOOL: return v.as.b;
case JSRT_NUMBER: return v.as.n == v.as.n && v.as.n != 0;
case JSRT_STRING: return __rt_string_length(v.as.s) != 0;
default: return true;
}
}
JSRT_INLINE bool jsrt_is_function(jsrt_value v) {
if (v.tag == JSRT_NATIVE) return true;
return v.tag >= JSRT_OBJECT && jsrt_class_is_function[v.tag - JSRT_OBJECT];
}
JSRT_INLINE string_t jsrt_typeof(jsrt_value v) {
switch (v.tag) {
case JSRT_UNDEFINED: return jsrt_name(JSRT_S_UNDEFINED);
case JSRT_BOOL: return jsrt_name(JSRT_S_BOOLEAN);
case JSRT_NUMBER: return jsrt_name(JSRT_S_NUMBER);
case JSRT_STRING: return jsrt_name(JSRT_S_STRING);
default: return jsrt_name(jsrt_is_function(v) ? JSRT_S_FUNCTION : JSRT_S_OBJECT);
}
}
// ─────────────────────────────────────── Operators ───────────────────────────────────────
// `a + b`: a concatenation if either side is a string or an object, else a sum
JSRT_INLINE jsrt_value jsrt_add(jsrt_value a, jsrt_value b) {
if (a.tag >= JSRT_STRING || b.tag >= JSRT_STRING) {
string_t s = __rt_string_concat(jsrt_string(a), jsrt_string(b));
return (jsrt_value){.tag = JSRT_STRING, .as.s = s};
}
return (jsrt_value){.tag = JSRT_NUMBER, .as.n = jsrt_number(a) + jsrt_number(b)};
}
// `a < b`, or `a <= b` if `or_equal`. Two strings compare as strings, anything else
// as numbers
JSRT_INLINE bool jsrt_less(jsrt_value a, jsrt_value b, bool or_equal) {
if (a.tag == JSRT_STRING && b.tag == JSRT_STRING) {
int32_t cmp = __rt_string_cmp(a.as.s, b.as.s);
return or_equal ? cmp <= 0 : cmp < 0;
}
double x = jsrt_number(a), y = jsrt_number(b);
return or_equal ? x <= y : x < y;
}
// `a === b`. Values of different kinds are never equal, and objects of
// different classes are different objects
JSRT_INLINE bool jsrt_strict_eq(jsrt_value a, jsrt_value b) {
if (a.tag != b.tag) return false;
switch (a.tag) {
case JSRT_UNDEFINED: case JSRT_NULL: return true;
case JSRT_BOOL: return a.as.b == b.as.b;
case JSRT_NUMBER: return a.as.n == b.as.n;
case JSRT_STRING: return __rt_string_eq(a.as.s, b.as.s);
case JSRT_NATIVE: return a.as.native == b.as.native;
default: return a.as.o == b.as.o;
}
}
// `a == b`. Undefined and null are equal to each other and to nothing else. Two
// objects compare as `===` does, and so do two values of one kind. Anything
// else compares as numbers
JSRT_INLINE bool jsrt_loose_eq(jsrt_value a, jsrt_value b) {
bool a_nullish = a.tag <= JSRT_NULL, b_nullish = b.tag <= JSRT_NULL;
bool objects = a.tag >= JSRT_NATIVE && b.tag >= JSRT_NATIVE;
if (a_nullish || b_nullish) return a_nullish && b_nullish;
if (objects || a.tag == b.tag) return jsrt_strict_eq(a, b);
return jsrt_number(a) == jsrt_number(b);
}
#define NUMBER(x) _Generic((x), double: (x), default: jsrt_number(VALUE(x)))
#define INT32(x) _Generic((x), int32_t: (x), default: __rt_to_int32(NUMBER(x)))
#define STRING(x) jsrt_string(VALUE(x))
#define TRUTHY(x) jsrt_truthy(VALUE(x))
// Arithmetic. Every number is a double, so every result is a double, except
// `+x` of an int
#define ADD(a, b) jsrt_add(VALUE(a), VALUE(b))
#define SUB(a, b) (NUMBER(a) - NUMBER(b))
#define MUL(a, b) (NUMBER(a) * NUMBER(b))
#define DIV(a, b) (NUMBER(a) / NUMBER(b))
#define MOD(a, b) fmod(NUMBER(a), NUMBER(b))
#define POW(a, b) __rt_pow(NUMBER(a), NUMBER(b))
#define NEG(x) (-NUMBER(x))
#define PLUS(x) _Generic((x), int32_t: (x), default: NUMBER(x))
#define INC(x) (NUMBER(x) + 1)
#define DEC(x) (NUMBER(x) - 1)
// `x++` and `x--`: `old` gets x as a number, and `new` gets the next number
#define POST_INC(old, new, x) \
do { \
double n = NUMBER(x); \
SET(old, n); \
SET(new, n + 1); \
} while (0)
#define POST_DEC(old, new, x) \
do { \
double n = NUMBER(x); \
SET(old, n); \
SET(new, n - 1); \
} while (0)
// Bitwise operators: on int32s, and a shift counts the low 5 bits only. `>>>`
// gives a uint32, which is a double
#define NOT(x) ((int32_t)~INT32(x))
#define AND(a, b) ((int32_t)(INT32(a) & INT32(b)))
#define OR(a, b) ((int32_t)(INT32(a) | INT32(b)))
#define XOR(a, b) ((int32_t)(INT32(a) ^ INT32(b)))
#define SHL(a, b) ((int32_t)((uint32_t)INT32(a) << (INT32(b) & 31)))
#define SAR(a, b) ((int32_t)(INT32(a) >> (INT32(b) & 31)))
#define SHR(a, b) ((double)((uint32_t)INT32(a) >> (INT32(b) & 31)))
// Comparisons. `a > b` is `b < a`, and `a >= b` is `b <= a`
#define LT(a, b) jsrt_less(VALUE(a), VALUE(b), false)
#define LTE(a, b) jsrt_less(VALUE(a), VALUE(b), true)
#define GT(a, b) jsrt_less(VALUE(b), VALUE(a), false)
#define GTE(a, b) jsrt_less(VALUE(b), VALUE(a), true)
#define STRICT_EQ(a, b) jsrt_strict_eq(VALUE(a), VALUE(b))
#define STRICT_NEQ(a, b) ((bool)!STRICT_EQ(a, b))
#define EQ(a, b) jsrt_loose_eq(VALUE(a), VALUE(b))
#define NEQ(a, b) ((bool)!EQ(a, b))
// Tests of one value
#define LNOT(x) ((bool)!TRUTHY(x))
#define TYPEOF(x) jsrt_typeof(VALUE(x))
#define IS_UNDEFINED(x) ((bool)(VALUE(x).tag == JSRT_UNDEFINED))
#define IS_NULL(x) ((bool)(VALUE(x).tag == JSRT_NULL))
#define IS_NULLISH(x) ((bool)(VALUE(x).tag <= JSRT_NULL))
#define IS_FUNCTION(x) jsrt_is_function(VALUE(x))
// ──────────────────────────────────── Natives' args ──────────────────────────────────────
// An arg of a native, as its param's C type. An int and a float both pass for a
// number, and an arg of any other wrong kind throws
#define ARG_INT(x) \
_Generic((x), int32_t: (x), default: __rt_to_int32(jsrt_arg_number(VALUE(x))))
#define ARG_NUMBER(x) jsrt_arg_number(VALUE(x))
#define ARG_STRING(x) jsrt_arg_string(VALUE(x))
#define ARG_UNDEFINED(x) jsrt_arg_undefined(VALUE(x))
#define JSRT_WRONG_ARG "a native got an arg of the wrong kind"
JSRT_INLINE double jsrt_arg_number(jsrt_value v) {
if (v.tag != JSRT_NUMBER) __rt_type_error(JSRT_WRONG_ARG);
return v.as.n;
}
JSRT_INLINE string_t jsrt_arg_string(jsrt_value v) {
if (v.tag != JSRT_STRING) __rt_type_error(JSRT_WRONG_ARG);
return v.as.s;
}
JSRT_INLINE undefined_t jsrt_arg_undefined(jsrt_value v) {
if (v.tag != JSRT_UNDEFINED) __rt_type_error(JSRT_WRONG_ARG);
return (undefined_t){0};
}
// ──────────────────────────────────── Arrays and cells ───────────────────────────────────
//
// Every array's struct has `length`, `cap` and `items`, so these work on any of them
// Whether `i`, a number, is an index below `length`
#define JSRT_IN_RANGE(i, length) ((i) >= 0 && (i) < (length) && (i) == (double)(int32_t)(i))
#define JSRT_BAD_INDEX "an array index must be a whole number from 0"
#define JSRT_BAD_LENGTH "an array's length must be a whole number from 0"
// `a = [...]` with room for `n` elements, which the caller sets
#define ARRAY_NEW(a, n) \
do { \
(a) = __rt_alloc(sizeof *(a)); \
(a)->items = __rt_grow(NULL, &(a)->cap, (n), sizeof *(a)->items); \
(a)->length = (n); \
} while (0)
// `dst = a[i]`: undefined if `i` is not an index in the array
#define ARRAY_GET(dst, a, i) \
do { \
double index = NUMBER(i); \
if (JSRT_IN_RANGE(index, (a)->length)) SET(dst, (a)->items[(int32_t)index]); \
else SET(dst, (undefined_t){0}); \
} while (0)
// `dst = s[i]`: undefined if `i` is not an index in the string
#define STRING_GET(dst, s, i) \
do { \
double index = NUMBER(i); \
if (!JSRT_IN_RANGE(index, __rt_string_length(s))) SET(dst, (undefined_t){0}); \
else SET(dst, __rt_string_at((s), (int32_t)index)); \
} while (0)
// `a[i] = v`. The array grows to hold `i`, and the elements between are holes.
// An `i` that is not an index would be a named field, which is not supported
#define ARRAY_PUT(a, i, v) \
do { \
double index = NUMBER(i); \
if (!JSRT_IN_RANGE(index, INT32_MAX)) __rt_type_error(JSRT_BAD_INDEX); \
int32_t at = (int32_t)index; \
if (at >= (a)->length) { \
(a)->items = __rt_grow((a)->items, &(a)->cap, at + 1, sizeof *(a)->items); \
(a)->length = at + 1; \
} \
SET((a)->items[at], v); \
} while (0)
// `a.length = n`: the array gets `n` elements, and the new ones are holes. `n`
// must be a whole number from 0, as JS's RangeError says
#define ARRAY_SET_LENGTH(a, n) \
do { \
double length = NUMBER(n); \
if (!JSRT_IN_RANGE(length, INT32_MAX)) __rt_type_error(JSRT_BAD_LENGTH); \
(a)->items = __rt_grow((a)->items, &(a)->cap, (int32_t)length, sizeof *(a)->items); \
(a)->length = (int32_t)length; \
} while (0)
// A new cell for `p`, a pointer to a cell, with the same value. Closures that
// point to the old cell keep it (see transform/closure.rs)
#define NEW_CELL(p) \
do { \
void *cell = __rt_alloc(sizeof *(p)); \
memcpy(cell, (p), sizeof *(p)); \
(p) = cell; \
} while (0)
+2 -1
View File
@@ -10,7 +10,8 @@
// the allocator and errors // the allocator and errors
// __native_* the platform's IO, which another platform's runtime replaces // __native_* the platform's IO, which another platform's runtime replaces
// //
// core.h values, the allocator, errors // core.h values, the allocator, errors, and JS's operators on every
// kind of value
// str.h strings // str.h strings
// number.h numbers, and their conversions to and from strings // number.h numbers, and their conversions to and from strings
// io.h IO natives, async on io_uring (uring.h), and the host's loop // io.h IO natives, async on io_uring (uring.h), and the host's loop