New macro based emit
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
//! is a number or undefined. In C, each member is one C type (see `Member`). A type with one
|
||||
//! member is that C type. A type with several members is a tagged union: a tag that says
|
||||
//! which member the value is, and a C union of the members.
|
||||
//! Infer gives each value a type, which can have several members: for example, a number or
|
||||
//! undefined. In C, each member has a C type of its own (see `Member`). A value whose type
|
||||
//! has one member is a plain C value of that member's C type. Any other value is a
|
||||
//! `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
|
||||
//! 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::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)]
|
||||
pub enum Member {
|
||||
Undefined,
|
||||
@@ -29,36 +29,28 @@ pub enum Member {
|
||||
Float,
|
||||
Str,
|
||||
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
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
/// The C type of a value: one member's, or a `jsrt_value`
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum CType {
|
||||
One(Member),
|
||||
Union(usize), // an index into `Types::unions`
|
||||
Value,
|
||||
}
|
||||
|
||||
impl CType {
|
||||
/// The C name of the type
|
||||
pub fn c_name(self) -> String {
|
||||
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::Bool) => "bool".into(),
|
||||
CType::One(Member::Int) => "int32_t".into(),
|
||||
CType::One(Member::Float) => "double".into(),
|
||||
CType::One(Member::Str) => "string_t".into(),
|
||||
CType::One(Member::Obj(class)) => format!("struct c{class} *"),
|
||||
CType::Union(id) => format!("u{id}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
CType::One(Member::Native(_)) => "native_t".into(),
|
||||
CType::Value => "jsrt_value".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,6 +66,7 @@ pub enum ClassKind {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClassField {
|
||||
pub name: AtomRef,
|
||||
pub ty: Type, // the join of the field's types in the class's objects
|
||||
pub ctype: CType,
|
||||
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
|
||||
@@ -88,91 +81,30 @@ pub struct Class {
|
||||
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)]
|
||||
pub struct Types {
|
||||
pub struct Classes {
|
||||
pub classes: Vec<Class>,
|
||||
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 {
|
||||
/// The classes of all objects. The unions of the classes' fields are added too, and
|
||||
/// `Program::new` adds the rest
|
||||
impl Classes {
|
||||
pub fn new(memory: &Memory) -> Self {
|
||||
let mut types = Types {
|
||||
classes: vec![],
|
||||
class_of: refine(memory),
|
||||
unions: vec![],
|
||||
union_ids: HashMap::new(),
|
||||
};
|
||||
|
||||
let class_of = refine(memory);
|
||||
let count = class_of.values().max().map_or(0, |&n| n + 1);
|
||||
let mut objs_of_class: Vec<Vec<ObjAddr>> = vec![vec![]; count];
|
||||
let mut objs: Vec<ObjAddr> = memory.objects().map(|(obj, _)| obj).collect();
|
||||
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 {
|
||||
objs_of_class[types.class_of[&obj]].push(obj);
|
||||
objs_of_class[class_of[&obj]].push(obj);
|
||||
}
|
||||
|
||||
for objs in objs_of_class {
|
||||
let class = types.make_class(memory, objs);
|
||||
types.classes.push(class);
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
let classes = objs_of_class.into_iter();
|
||||
let classes = classes
|
||||
.map(|objs| make_class(memory, &class_of, objs))
|
||||
.collect();
|
||||
Classes { classes, class_of }
|
||||
}
|
||||
|
||||
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
|
||||
pub fn type_members(&self, ty: &Type) -> Vec<Member> {
|
||||
pub fn members(&self, ty: &Type) -> Vec<Member> {
|
||||
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
|
||||
/// is added
|
||||
pub fn add(&mut self, ty: &Type) -> CType {
|
||||
let members = self.type_members(ty);
|
||||
|
||||
if let [member] = members[..] {
|
||||
return CType::One(member);
|
||||
/// The C type of a value of type `ty`
|
||||
pub fn ctype(&self, ty: &Type) -> CType {
|
||||
ctype_with(ty, &self.class_of)
|
||||
}
|
||||
}
|
||||
|
||||
let next = self.unions.len();
|
||||
let id = *self.union_ids.entry(members.clone()).or_insert(next);
|
||||
/// The class of the objects in `objs`, which refinement put together. The type of a field is
|
||||
/// the join of its types in all the objects of the class, so that its C type holds each one
|
||||
fn make_class(memory: &Memory, class_of: &HashMap<ObjAddr, usize>, objs: Vec<ObjAddr>) -> Class {
|
||||
let first = objs[0];
|
||||
let join_over = |ty_of: &dyn Fn(ObjAddr) -> Type| {
|
||||
objs.iter()
|
||||
.fold(Type::Unknown, |all, &obj| join(&all, &ty_of(obj)))
|
||||
};
|
||||
|
||||
if id == next {
|
||||
self.unions.push(members);
|
||||
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(),
|
||||
};
|
||||
|
||||
CType::Union(id)
|
||||
}
|
||||
let elems = match kind {
|
||||
ClassKind::Array => ctype_with(&join_over(&|obj| memory.elems(obj).clone()), class_of),
|
||||
_ => CType::One(Member::Undefined),
|
||||
};
|
||||
|
||||
/// 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`
|
||||
pub fn members_of(&self, ctype: CType) -> Vec<Member> {
|
||||
match ctype {
|
||||
CType::One(member) => vec![member],
|
||||
CType::Union(id) => self.unions[id].clone(),
|
||||
}
|
||||
Class {
|
||||
kind,
|
||||
objs,
|
||||
fields,
|
||||
elems,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,8 +197,16 @@ fn members_with(ty: &Type, class_of: &HashMap<ObjAddr, usize>) -> Vec<Member> {
|
||||
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
|
||||
/// 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
|
||||
fn refine(memory: &Memory) -> HashMap<ObjAddr, usize> {
|
||||
let mut objs: Vec<ObjAddr> = memory.objects().map(|(obj, _)| obj).collect();
|
||||
@@ -285,8 +235,8 @@ fn refine(memory: &Memory) -> HashMap<ObjAddr, usize> {
|
||||
loop {
|
||||
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
|
||||
let key = |obj: ObjAddr| -> (usize, Vec<Vec<Member>>) {
|
||||
// The C type of each field, or of an array's elements, with the classes so far
|
||||
let key = |obj: ObjAddr| -> (usize, Vec<CType>) {
|
||||
let types: Vec<Type> = match memory.get(Addr::Object(obj)) {
|
||||
Type::Shape(Shape::Array(elems)) => vec![(**elems).clone()],
|
||||
_ => memory
|
||||
@@ -298,7 +248,7 @@ fn refine(memory: &Memory) -> HashMap<ObjAddr, usize> {
|
||||
|
||||
(
|
||||
class_of[&obj],
|
||||
types.iter().map(|ty| members_with(ty, &class_of)).collect(),
|
||||
types.iter().map(|ty| ctype_with(ty, &class_of)).collect(),
|
||||
)
|
||||
};
|
||||
|
||||
@@ -7,16 +7,15 @@
|
||||
//! function (see `js`).
|
||||
//! - **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
|
||||
//! 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_complete`**, which the host calls when an IO op completes.
|
||||
|
||||
use super::body::Body;
|
||||
use super::js;
|
||||
use super::classes::{CType, ClassKind, Member};
|
||||
use super::program::Program;
|
||||
use super::types::{CType, ClassKind, Member};
|
||||
use super::values::{undefined, unit_literal};
|
||||
use crate::{infer::Type, libqjs::parse::AtomRef, regify::Operand, transform::PROTO_FIELD};
|
||||
use super::{js, objects};
|
||||
use crate::{infer::Type, libqjs::parse::AtomRef, regify::Operand};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Function {
|
||||
@@ -44,8 +43,8 @@ impl Function {
|
||||
match self {
|
||||
Function::Js(walk) => js::signature(program, *walk),
|
||||
Function::Lookup(class, field) => {
|
||||
let ctype = program.types.of(&program.lookup_type(*class, *field));
|
||||
format!("static {} {name}(struct c{class} *o)", ctype.c_name())
|
||||
let ctype = program.lookup_ctype(*class, *field).c_name();
|
||||
format!("static {ctype} {name}(struct c{class} *o)")
|
||||
}
|
||||
Function::Start => format!("void {name}(void)"),
|
||||
Function::Complete => format!("void {name}(int32_t h)"),
|
||||
@@ -55,7 +54,7 @@ impl Function {
|
||||
pub fn definition(&self, program: &Program) -> String {
|
||||
let body = match self {
|
||||
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::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
|
||||
fn start_body(program: &Program) -> String {
|
||||
let arg_count = program.fns[program.fns.len() - 1].arg_count;
|
||||
let undefined = (
|
||||
unit_literal(Member::Undefined).to_string(),
|
||||
Member::Undefined,
|
||||
);
|
||||
let call = host_call(program, program.entry, &vec![undefined; arg_count]);
|
||||
format!(" {call};\n")
|
||||
let args = vec![js::UNDEFINED.to_string(); arg_count];
|
||||
format!(" {};\n", host_call(program, program.entry, &args))
|
||||
}
|
||||
|
||||
/// Call the stdlib's `__complete` with `h`, the handle of the op, if the program has it
|
||||
fn complete_body(program: &Program) -> String {
|
||||
match complete_call(program) {
|
||||
Some(call) => format!(" (void)h;\n {call};\n"),
|
||||
Some(call) => format!(" {call};\n"),
|
||||
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> {
|
||||
let (callee, ctype) = program.binding("__complete")?;
|
||||
let class = ctype.class()?;
|
||||
let ClassKind::Closure(fn_idx) = program.types.classes[class].kind else {
|
||||
let CType::One(Member::Obj(class)) = ctype else {
|
||||
return None;
|
||||
};
|
||||
let ClassKind::Closure(fn_idx) = program.classes.classes[class].kind else {
|
||||
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 walk = program.cache.find(fn_idx, None, &types, program.fns);
|
||||
let args = [
|
||||
(callee, Member::Obj(class)),
|
||||
(
|
||||
unit_literal(Member::Undefined).to_string(),
|
||||
Member::Undefined,
|
||||
),
|
||||
("h".to_string(), Member::Int),
|
||||
];
|
||||
let args = [callee, js::UNDEFINED.to_string(), "h".to_string()];
|
||||
Some(host_call(program, walk, &args))
|
||||
}
|
||||
|
||||
/// 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())
|
||||
.map(|(i, (value, member))| {
|
||||
let param = program.slot_ctype(walk, Operand::Arg(i));
|
||||
program.convert(value, *member, param)
|
||||
.map(|(i, arg)| {
|
||||
let param = program.slot_ctype(walk, Operand::Arg(i)).c_name();
|
||||
format!("CAST({param}, {arg})")
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -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
@@ -2,34 +2,33 @@
|
||||
//!
|
||||
//! 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
|
||||
//! has the type that infer found in that walk. What the walk returns is in memory too (see
|
||||
//! `TypeCache::ret`).
|
||||
//! has the type that infer found in that walk.
|
||||
//!
|
||||
//! Each local, arg and reg is a C local, of the C type of its type. A local or arg that a
|
||||
//! closure shares is a pointer to a cell (see transform/closure.rs). The cell is made when the
|
||||
//! Each local, arg and reg is a C local, of the C type of its type. A local or arg that
|
||||
//! 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
|
||||
//! the function starts.
|
||||
//!
|
||||
//! 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
|
||||
//! 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::{
|
||||
body::Body,
|
||||
program::{Program, slots},
|
||||
types::{CType, Member},
|
||||
values::truthy,
|
||||
};
|
||||
use super::program::{Program, slots};
|
||||
use crate::{
|
||||
infer::{Context, Type},
|
||||
libqjs::parse::opcodes::OpCode,
|
||||
regify::{BasicBlock, Operand, RegFn},
|
||||
libqjs::parse::{ImmOperand, JsValue, opcodes::OpCode},
|
||||
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
|
||||
pub struct JsFunction<'a> {
|
||||
pub program: &'a Program<'a>,
|
||||
pub body: Body<'a>,
|
||||
pub walk: usize,
|
||||
pub context: Context, // the walk's function, and the call site that it is called from
|
||||
pub f: &'a RegFn,
|
||||
@@ -44,13 +43,13 @@ pub fn signature(program: &Program, walk: usize) -> String {
|
||||
|
||||
let params: Vec<String> = (0..f.arg_count)
|
||||
.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)) {
|
||||
"p"
|
||||
} else {
|
||||
"a"
|
||||
};
|
||||
format!("{} {prefix}{k}", ctype.c_name())
|
||||
format!("{ctype} {prefix}{k}")
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -59,36 +58,38 @@ pub fn signature(program: &Program, walk: usize) -> String {
|
||||
} else {
|
||||
params.join(", ")
|
||||
};
|
||||
let ret = program.types.of(program.cache.ret(walk));
|
||||
format!("static {} js{walk}({params})", ret.c_name())
|
||||
let ret = program.ret_ctype(walk).c_name();
|
||||
format!("static {ret} js{walk}({params})")
|
||||
}
|
||||
|
||||
/// The body of `js<walk>`: its locals, args and regs, then each block that can run
|
||||
pub fn body(program: &Program, walk: usize) -> String {
|
||||
let w = &program.cache.walks[walk];
|
||||
let f = program.js_fn(walk);
|
||||
let reachable = &program.cache.walks[walk].reachable;
|
||||
|
||||
let mut js = JsFunction {
|
||||
let js = JsFunction {
|
||||
program,
|
||||
body: Body::new(program),
|
||||
walk,
|
||||
context: w.context,
|
||||
context: program.cache.walks[walk].context,
|
||||
f,
|
||||
shared: f.captured_slots(),
|
||||
};
|
||||
|
||||
js.declare_slots();
|
||||
let mut c = indent(&js.declarations());
|
||||
|
||||
for (i, block) in f.blocks.iter().enumerate() {
|
||||
if w.reachable[i] {
|
||||
js.body.indent(-1);
|
||||
js.body.line(format!("b{i}:;"));
|
||||
js.body.indent(1);
|
||||
js.block(block, &w.reachable);
|
||||
if reachable[i] {
|
||||
c += &format!("b{i}:;\n");
|
||||
c += &indent(&js.block(block, 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<'_> {
|
||||
@@ -101,8 +102,17 @@ impl JsFunction<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_shared(&self, slot: Operand) -> bool {
|
||||
self.shared.contains(&slot)
|
||||
/// The value of a local, arg or reg, as C. A shared one is the value in its cell
|
||||
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
|
||||
@@ -110,64 +120,34 @@ impl JsFunction<'_> {
|
||||
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
|
||||
/// 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 mut c = String::new();
|
||||
|
||||
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 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(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) => {
|
||||
format!("{ctype} *{name} = g{k} = {cell};")
|
||||
format!("{ctype} *{name} = g{k} = {cell};\n")
|
||||
}
|
||||
(_, true) => format!("{ctype} *{name} = {cell};"),
|
||||
(_, false) => format!("{ctype} {name} = {{0}};"),
|
||||
(_, true) => format!("{ctype} *{name} = {cell};\n"),
|
||||
(_, false) => format!("{ctype} {name} = {{0}};\n"),
|
||||
};
|
||||
|
||||
self.body.line(line);
|
||||
}
|
||||
}
|
||||
|
||||
/// A block: its instrs, its postfix copies, then its jump
|
||||
fn block(&mut self, block: &BasicBlock, reachable: &[bool]) {
|
||||
c
|
||||
}
|
||||
|
||||
/// 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::*;
|
||||
|
||||
let last = &self.f.code[block.end - 1];
|
||||
@@ -176,82 +156,192 @@ impl JsFunction<'_> {
|
||||
Goto | Goto8 | Goto16 | IfFalse | IfFalse8 | IfTrue | IfTrue8
|
||||
);
|
||||
let end = if jumps { block.end - 1 } else { block.end };
|
||||
let mut c = String::new();
|
||||
|
||||
for idx in block.start..end {
|
||||
if !self.instr(idx) {
|
||||
return;
|
||||
match self.instr(idx) {
|
||||
Some(code) => c += &code,
|
||||
None => return c + "abort(); // not reached\n",
|
||||
}
|
||||
}
|
||||
|
||||
for copy in &block.postfix {
|
||||
let (value, ctype) = self.get(copy.args[0]);
|
||||
self.set(copy.dsts[0], &value, ctype);
|
||||
c += &self.set(copy.dsts[0], &self.get(copy.args[0]));
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
match last.op {
|
||||
c + &match last.op {
|
||||
IfFalse | IfFalse8 | IfTrue | IfTrue8 => match (can_go(0), can_go(1)) {
|
||||
(Some(target), Some(next)) => {
|
||||
let cond = self.truthy(last.args[0]);
|
||||
let not = if matches!(last.op, IfTrue | IfTrue8) {
|
||||
""
|
||||
} else {
|
||||
"!"
|
||||
};
|
||||
self.body.line(format!("if ({not}{cond}) goto b{target};"));
|
||||
self.body.line(format!("goto b{next};"));
|
||||
let cond = self.get(last.args[0]);
|
||||
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};")),
|
||||
(None, None) => self.body.line("abort(); // not reached"),
|
||||
(Some(only), None) | (None, Some(only)) => format!("goto b{only};\n"),
|
||||
(None, None) => "abort(); // not reached\n".into(),
|
||||
},
|
||||
Return | ReturnAsync | ReturnUndef => {}
|
||||
_ => {
|
||||
if let Some(next) = can_go(0) {
|
||||
self.body.line(format!("goto b{next};"));
|
||||
}
|
||||
}
|
||||
Return | ReturnAsync | ReturnUndef => String::new(),
|
||||
_ => can_go(0).map_or(String::new(), |next| format!("goto b{next};\n")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a local, arg or reg is truthy, in a new temp. Gives the temp's name
|
||||
fn truthy(&mut self, slot: Operand) -> String {
|
||||
let cond = self.body.temp(CType::One(Member::Bool));
|
||||
let cases = self.program.cases(&[self.get(slot)]);
|
||||
/// The C of instr `idx`. `None` if an operand has no value: then the instr cannot run
|
||||
fn instr(&self, idx: usize) -> Option<String> {
|
||||
use OpCode::*;
|
||||
|
||||
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];
|
||||
|
||||
if instr.op == OpCode::Nop {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The first operand of `define_field_ref` is a location, not a value
|
||||
let values = match instr.op {
|
||||
OpCode::DefineFieldRef => &instr.args[1..],
|
||||
DefineFieldRef => &instr.args[1..],
|
||||
_ => &instr.args[..],
|
||||
};
|
||||
|
||||
if values.iter().any(|slot| self.ty(*slot) == Type::Unknown) {
|
||||
self.body.line("abort(); // not reached");
|
||||
return false;
|
||||
if values.iter().any(|&slot| self.ty(slot) == Type::Unknown) {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.emit_instr(idx, instr);
|
||||
true
|
||||
let get = |i: usize| self.get(instr.args[i]);
|
||||
|
||||
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
@@ -1,52 +1,43 @@
|
||||
//! 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, and a tagged union for each type with
|
||||
//! several members (see `types`).
|
||||
//! - **Types:** a struct for each class of objects (see `classes`).
|
||||
//! - **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
|
||||
//! host's two entry points (see `functions`).
|
||||
//! 2. `emit` writes each of them, in an order that C can compile.
|
||||
//! - **Functions:** one for each walk of a JS function, one for each lookup, and the host's
|
||||
//! two entry points (see `functions`).
|
||||
//!
|
||||
//! 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 instr;
|
||||
mod js;
|
||||
mod objects;
|
||||
mod program;
|
||||
mod types;
|
||||
mod values;
|
||||
|
||||
use crate::{flatten::ModuleBindings, infer::TypeCache, libqjs::parse::Atom, regify::RegFn};
|
||||
|
||||
use classes::ClassKind;
|
||||
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`
|
||||
pub fn emit(fns: &[RegFn], cache: &TypeCache, atoms: &[Atom], bindings: &ModuleBindings) -> String {
|
||||
let program = Program::new(fns, cache, atoms, bindings);
|
||||
let classes = 0..program.types.classes.len();
|
||||
let unions = 0..program.types.unions.len();
|
||||
let classes = 0..program.classes.classes.len();
|
||||
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() {
|
||||
c += &format!("struct c{class};\n");
|
||||
}
|
||||
|
||||
c += "\n";
|
||||
|
||||
for union in unions {
|
||||
c += &union_definition(&program, union);
|
||||
}
|
||||
|
||||
c += "\n";
|
||||
c += &class_table(&program);
|
||||
|
||||
for class in classes {
|
||||
c += &struct_definition(&program, class);
|
||||
@@ -65,8 +56,7 @@ pub fn emit(fns: &[RegFn], cache: &TypeCache, atoms: &[Atom], bindings: &ModuleB
|
||||
c += "\n";
|
||||
|
||||
for function in &program.functions {
|
||||
c += &function.signature(&program);
|
||||
c += ";\n";
|
||||
c += &format!("{};\n", function.signature(&program));
|
||||
}
|
||||
|
||||
c += "\n";
|
||||
@@ -78,20 +68,28 @@ pub fn emit(fns: &[RegFn], cache: &TypeCache, atoms: &[Atom], bindings: &ModuleB
|
||||
c
|
||||
}
|
||||
|
||||
/// A tagged union: a tag, and a C union of the members. The tag is the member's index
|
||||
fn union_definition(program: &Program, id: usize) -> String {
|
||||
let members = program.types.unions[id].iter().enumerate();
|
||||
let members: String = members
|
||||
.map(|(tag, &member)| format!(" {} m{tag};", CType::One(member).c_name()))
|
||||
/// What the runtime needs to know of the classes (see lib/rt/core.h): the class of a pointer
|
||||
/// to an object, and whether a class is closures. The table ends in a 0, because C has no
|
||||
/// empty arrays
|
||||
fn class_table(program: &Program) -> String {
|
||||
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();
|
||||
|
||||
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
|
||||
/// 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 {
|
||||
let class = &program.types.classes[id];
|
||||
let class = &program.classes.classes[id];
|
||||
let mut c = format!("struct c{id} {{\n");
|
||||
|
||||
if class.kind == ClassKind::Array {
|
||||
|
||||
+158
-215
@@ -1,290 +1,233 @@
|
||||
//! The C of the instrs on objects: fields and elements
|
||||
//! The C of the instrs on objects, and of lookups
|
||||
//!
|
||||
//! A field is a member of its class's struct. If every object of the class has the field from
|
||||
//! the start, the field is used directly. Else the struct has a `has_` flag for the field, and
|
||||
//! getting the field calls its lookup (see `functions`). A field that shares a location (see
|
||||
//! getting the field calls its lookup. A field that shares a location (see
|
||||
//! `define_field_ref`) holds a pointer to it.
|
||||
//!
|
||||
//! An instr on a value that can be one of several members has C for each member (see
|
||||
//! `Program::split`).
|
||||
|
||||
use super::{
|
||||
js::JsFunction,
|
||||
types::{CType, ClassKind, Member},
|
||||
values::{as_index, in_range, undefined},
|
||||
};
|
||||
use super::classes::{ClassKind, Member};
|
||||
use super::js::{JsFunction, UNDEFINED, indent};
|
||||
use super::program::{Program, throw};
|
||||
use crate::{
|
||||
infer::LENGTH_FIELD,
|
||||
libqjs::parse::opcodes::OpCode,
|
||||
infer::{LENGTH_FIELD, ObjAddr},
|
||||
libqjs::parse::{AtomRef, opcodes::OpCode},
|
||||
regify::{Operand, RegInstr},
|
||||
transform::PROTO_FIELD,
|
||||
};
|
||||
|
||||
// ──────────────────────────────────────── Fields ────────────────────────────────────────
|
||||
|
||||
impl JsFunction<'_> {
|
||||
/// `[a, b, ...]`. The operands are the elements, last first
|
||||
pub(super) fn array_from(&mut self, idx: usize, instr: &RegInstr) {
|
||||
pub(super) fn object_instr(&self, idx: usize, instr: &RegInstr) -> String {
|
||||
use OpCode::*;
|
||||
|
||||
match instr.op {
|
||||
Object | Fclosure => {
|
||||
let class = self.class_here(idx);
|
||||
let elems = self.program.types.classes[class].elems;
|
||||
let array = self.body.temp(CType::One(Member::Obj(class)));
|
||||
let n = instr.args.len();
|
||||
|
||||
self.body
|
||||
.line(format!("{array} = __rt_alloc(sizeof(struct c{class}));"));
|
||||
self.body.line(format!(
|
||||
"{array}->items = __rt_grow(NULL, &{array}->cap, {n}, sizeof *{array}->items);"
|
||||
));
|
||||
self.body.line(format!("{array}->length = {n};"));
|
||||
|
||||
for (i, &slot) in instr.args.iter().rev().enumerate() {
|
||||
let (value, ctype) = self.get(slot);
|
||||
self.body
|
||||
.put(&format!("{array}->items[{i}] = "), &value, ctype, elems);
|
||||
let alloc = format!("(struct c{class} *)__rt_alloc(sizeof(struct c{class}))");
|
||||
self.set(instr.dsts[0], &alloc)
|
||||
}
|
||||
ArrayFrom => self.array_from(idx, instr),
|
||||
GetField | GetLength => self.get_field(instr),
|
||||
DefineField | PutField => self.put_field(instr),
|
||||
DefineFieldRef => self.share_field(instr),
|
||||
CloseField => self.close_field(instr),
|
||||
GetArrayEl => self.get_elem(instr),
|
||||
PutArrayEl => self.put_elem(instr),
|
||||
_ => unimplemented!("emit {instr:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
/// gets the field from the stdlib's prototype of its kind (see `Program::field_class`).
|
||||
/// Undefined and null throw
|
||||
pub(super) fn get_field(&mut self, instr: &RegInstr) {
|
||||
fn get_field(&self, instr: &RegInstr) -> String {
|
||||
let program = self.program;
|
||||
let dst = instr.dsts[0];
|
||||
let field = match instr.op {
|
||||
OpCode::GetLength => LENGTH_FIELD,
|
||||
_ => instr.imm.atom(),
|
||||
};
|
||||
let dst = instr.dsts[0];
|
||||
let cases = self.program.cases(&[self.get(instr.args[0])]);
|
||||
|
||||
for case in &cases {
|
||||
self.body.open(case);
|
||||
let (obj, member) = (&case.operands[0].0, case.operands[0].1);
|
||||
|
||||
match (member, self.program.field_class(member, field)) {
|
||||
self.split(instr.args[0], |obj, member| {
|
||||
match (member, program.field_class(member, field)) {
|
||||
(Member::Obj(_), Some(class)) => {
|
||||
let (value, ctype) = self.program.field_value(class, field, obj);
|
||||
self.set(dst, &value, ctype);
|
||||
self.set(dst, &program.field_value(class, field, obj))
|
||||
}
|
||||
(_, Some(class)) => {
|
||||
let (proto, _) = self.program.builtin_proto(member).unwrap();
|
||||
let (value, ctype) = self.program.field_value(class, field, &proto);
|
||||
self.set(dst, &value, ctype);
|
||||
let (proto, _) = program.builtin_proto(member).unwrap();
|
||||
self.set(dst, &program.field_value(class, field, &proto))
|
||||
}
|
||||
(Member::Str, None) if field == LENGTH_FIELD => {
|
||||
let length = format!("__rt_string_length({obj})");
|
||||
self.set_member(dst, &length, Member::Int);
|
||||
}
|
||||
(Member::Obj(_), None) => {
|
||||
self.set_member(dst, &format!("{obj}->length"), Member::Int)
|
||||
self.set(dst, &format!("__rt_string_length({obj})"))
|
||||
}
|
||||
(Member::Obj(_), None) => self.set(dst, &format!("{obj}->length")),
|
||||
(Member::Undefined | Member::Null, _) => {
|
||||
self.body.throw("cannot read a field of undefined or null")
|
||||
throw("cannot read a field of undefined or null")
|
||||
}
|
||||
(Member::Native(_), _) => self.body.throw("a native function has no fields"),
|
||||
_ => self.body.throw("fields of primitives need the stdlib"),
|
||||
(Member::Native(_), _) => throw("a native function has no fields"),
|
||||
_ => throw("fields of primitives need the stdlib"),
|
||||
}
|
||||
}
|
||||
|
||||
self.body.close(&cases);
|
||||
})
|
||||
}
|
||||
|
||||
/// `o.f = v`, and a field in an object literal. Setting an array's `length` sets its
|
||||
/// length. A primitive has no fields to set, and undefined and null throw
|
||||
pub(super) fn put_field(&mut self, instr: &RegInstr) {
|
||||
fn put_field(&self, instr: &RegInstr) -> String {
|
||||
let program = self.program;
|
||||
let field = instr.imm.atom();
|
||||
let (value, from) = self.get(instr.args[0]);
|
||||
let cases = self.program.cases(&[self.get(instr.args[1])]);
|
||||
let value = self.get(instr.args[0]);
|
||||
|
||||
for case in &cases {
|
||||
self.body.open(case);
|
||||
let (obj, member) = (&case.operands[0].0, case.operands[0].1);
|
||||
|
||||
match (member, self.program.class_kind(member)) {
|
||||
self.split(instr.args[1], |obj, member| {
|
||||
match (member, program.class_kind(member)) {
|
||||
(Member::Obj(_), ClassKind::Array) if field == LENGTH_FIELD => {
|
||||
self.set_length(obj, &value, from)
|
||||
format!("ARRAY_SET_LENGTH({obj}, {value});\n")
|
||||
}
|
||||
(Member::Obj(class), _) => {
|
||||
let f = self.program.types.field(class, field).unwrap();
|
||||
let target = self.program.field_access(obj, f);
|
||||
self.body
|
||||
.put(&format!("{target} = "), &value, from, f.ctype);
|
||||
let f = program.classes.field(class, field).unwrap();
|
||||
let set = format!("SET({}, {value});\n", program.field_access(obj, f));
|
||||
|
||||
if !f.defined {
|
||||
let name = self.program.field_name(field);
|
||||
self.body.line(format!("{obj}->has_{name} = true;"));
|
||||
match f.defined {
|
||||
true => set,
|
||||
false => {
|
||||
set + &format!("{obj}->has_{} = true;\n", program.field_name(field))
|
||||
}
|
||||
}
|
||||
_ => self
|
||||
.body
|
||||
.throw("cannot set a field of a primitive, undefined or null"),
|
||||
}
|
||||
_ => throw("cannot set a field of a primitive, undefined or null"),
|
||||
}
|
||||
|
||||
self.body.close(&cases);
|
||||
}
|
||||
|
||||
/// `a.length = n`: the array gets `n` elements, and the new ones are holes. `n` must be a
|
||||
/// whole number from 0, else it throws, as JS's RangeError does
|
||||
fn set_length(&mut self, obj: &str, value: &str, from: CType) {
|
||||
let cases = self.program.cases(&[(value.to_string(), from)]);
|
||||
|
||||
for case in &cases {
|
||||
self.body.open(case);
|
||||
let (n, member) = (&case.operands[0].0, case.operands[0].1);
|
||||
|
||||
let length = match member {
|
||||
Member::Int => n.clone(),
|
||||
Member::Float => format!("(int32_t){n}"),
|
||||
_ => {
|
||||
self.body.throw("an array's length must be a number");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let check = in_range(n, member, "INT32_MAX");
|
||||
self.body.line(format!("if (!({check})) __rt_type_error(\"an array's length must be a whole number from 0\");"));
|
||||
self.body.line(format!("{obj}->items = __rt_grow({obj}->items, &{obj}->cap, {length}, sizeof *{obj}->items);"));
|
||||
self.body.line(format!("{obj}->length = {length};"));
|
||||
}
|
||||
|
||||
self.body.close(&cases);
|
||||
})
|
||||
}
|
||||
|
||||
/// `define_field_ref`: the field becomes a pointer to the cell of the location in the
|
||||
/// first operand. That location is a shared local or arg, whose C local points to its
|
||||
/// cell, or a reg that holds an object whose field of the same name points to the cell
|
||||
/// (see regify/walk.rs)
|
||||
pub(super) fn share_field(&mut self, instr: &RegInstr) {
|
||||
fn share_field(&self, instr: &RegInstr) -> String {
|
||||
let name = self.program.field_name(instr.imm.atom());
|
||||
let location = instr.args[0];
|
||||
|
||||
let cell = match location {
|
||||
Operand::Reg(_) => match self.get(location) {
|
||||
(holder, CType::One(Member::Obj(_))) => format!("{holder}->{name}"),
|
||||
_ => unreachable!("a shared location is held by an object"),
|
||||
},
|
||||
let cell = match instr.args[0] {
|
||||
Operand::Reg(_) => format!("{}->{name}", self.get(instr.args[0])),
|
||||
slot => self.name(slot),
|
||||
};
|
||||
|
||||
let cases = self.program.cases(&[self.get(instr.args[1])]);
|
||||
|
||||
for case in &cases {
|
||||
self.body.open(case);
|
||||
self.body
|
||||
.line(format!("{}->{name} = {cell};", case.operands[0].0));
|
||||
}
|
||||
|
||||
self.body.close(&cases);
|
||||
self.split(instr.args[1], |obj, _| format!("{obj}->{name} = {cell};\n"))
|
||||
}
|
||||
|
||||
/// `close_field x`: the field, which points to a cell, gets a new cell with the same
|
||||
/// value. Closures that point to the old cell keep it (see transform/closure.rs)
|
||||
pub(super) fn close_field(&mut self, instr: &RegInstr) {
|
||||
let field = instr.imm.atom();
|
||||
let cases = self.program.cases(&[self.get(instr.args[0])]);
|
||||
|
||||
for case in &cases {
|
||||
self.body.open(case);
|
||||
let (obj, member) = (&case.operands[0].0, case.operands[0].1);
|
||||
let Member::Obj(class) = member else {
|
||||
unreachable!("a shared location is held by an object")
|
||||
};
|
||||
|
||||
let f = self.program.types.field(class, field).unwrap();
|
||||
assert!(f.shared, "close_field of a field that is not a cell");
|
||||
|
||||
let (ctype, name) = (f.ctype.c_name(), self.program.field_name(field));
|
||||
self.body.line(format!("{{ {ctype} *cell = __rt_alloc(sizeof({ctype})); *cell = *{obj}->{name}; {obj}->{name} = cell; }}"));
|
||||
fn close_field(&self, instr: &RegInstr) -> String {
|
||||
let name = self.program.field_name(instr.imm.atom());
|
||||
self.split(instr.args[0], |obj, _| {
|
||||
format!("NEW_CELL({obj}->{name});\n")
|
||||
})
|
||||
}
|
||||
|
||||
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.
|
||||
/// Undefined and null throw
|
||||
pub(super) fn get_elem(&mut self, instr: &RegInstr) {
|
||||
let dst = instr.dsts[0];
|
||||
let operands = [self.get(instr.args[1]), self.get(instr.args[0])];
|
||||
let cases = self.program.cases(&operands);
|
||||
fn get_elem(&self, instr: &RegInstr) -> String {
|
||||
let (dst, index) = (self.get(instr.dsts[0]), self.get(instr.args[0]));
|
||||
|
||||
for case in &cases {
|
||||
self.body.open(case);
|
||||
let ((obj, obj_member), (index, index_member)) = (&case.operands[0], &case.operands[1]);
|
||||
let at = as_index(index, *index_member);
|
||||
|
||||
let (length, item, ctype) = match (*obj_member, self.program.class_kind(*obj_member)) {
|
||||
(Member::Obj(class), ClassKind::Array) => (
|
||||
format!("{obj}->length"),
|
||||
format!("{obj}->items[{at}]"),
|
||||
self.program.types.classes[class].elems,
|
||||
),
|
||||
(Member::Str, _) => (
|
||||
format!("__rt_string_length({obj})"),
|
||||
format!("__rt_string_at({obj}, {at})"),
|
||||
CType::One(Member::Str),
|
||||
),
|
||||
self.split(instr.args[1], |obj, member| {
|
||||
match (member, self.program.class_kind(member)) {
|
||||
(Member::Obj(_), ClassKind::Array) => {
|
||||
format!("ARRAY_GET({dst}, {obj}, {index});\n")
|
||||
}
|
||||
(Member::Str, _) => format!("STRING_GET({dst}, {obj}, {index});\n"),
|
||||
(Member::Undefined | Member::Null, _) => {
|
||||
self.body
|
||||
.throw("cannot read an element of undefined or null");
|
||||
continue;
|
||||
throw("cannot read an element of undefined or null")
|
||||
}
|
||||
_ => unreachable!("infer gets elements only of arrays and strings"),
|
||||
};
|
||||
|
||||
let (none, none_ctype) = undefined();
|
||||
let check = in_range(index, *index_member, &length);
|
||||
self.body.line(format!("if ({check}) {{"));
|
||||
self.body.indent(1);
|
||||
self.set(dst, &item, ctype);
|
||||
self.body.indent(-1);
|
||||
self.body.line("} else {");
|
||||
self.body.indent(1);
|
||||
self.set(dst, &none, none_ctype);
|
||||
self.body.indent(-1);
|
||||
self.body.line("}");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
self.body.close(&cases);
|
||||
/// `a[i] = v` on an array. 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"),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// `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;
|
||||
let at = as_index(index, *index_member);
|
||||
let check = in_range(index, *index_member, "INT32_MAX");
|
||||
|
||||
self.body.line(format!(
|
||||
"if (!({check})) __rt_type_error(\"an array index must be a whole number from 0\");"
|
||||
));
|
||||
self.body.line(format!("if ({at} >= {obj}->length) {{"));
|
||||
self.body.indent(1);
|
||||
self.body.line(format!("{obj}->items = __rt_grow({obj}->items, &{obj}->cap, {at} + 1, sizeof *{obj}->items);"));
|
||||
self.body.line(format!("{obj}->length = {at} + 1;"));
|
||||
self.body.indent(-1);
|
||||
self.body.line("}");
|
||||
self.body
|
||||
.put(&format!("{obj}->items[{at}] = "), &value, from, elems);
|
||||
}
|
||||
|
||||
self.body.close(&cases);
|
||||
/// The C of `code` for each member that a local, arg or reg can be (see `Program::split`)
|
||||
pub fn split(&self, slot: Operand, code: impl Fn(&str, Member) -> String) -> String {
|
||||
self.program.split(&self.get(slot), &self.ty(slot), code)
|
||||
}
|
||||
}
|
||||
|
||||
/// The lookup of `field` on `o`, an object of `class`: its own field if it has it, else the
|
||||
/// field on its prototype. An array's prototype is the stdlib's. Another object's is its
|
||||
/// `__proto__` field, which it can lack. At the end of the chain, the field is undefined
|
||||
pub fn lookup_body(program: &Program, class: usize, field: AtomRef) -> String {
|
||||
let ctype = program.lookup_ctype(class, field).c_name();
|
||||
let ret = |value: &str| format!("return CAST({ctype}, {value});\n");
|
||||
let unless_lacks = |field: AtomRef| format!("if (o->has_{}) ", program.field_name(field));
|
||||
let if_lacks = |field: AtomRef| format!("if (!o->has_{}) ", program.field_name(field));
|
||||
let mut c = String::new();
|
||||
|
||||
if let Some(own) = program.classes.field(class, field) {
|
||||
let value = program.field_access("o", own);
|
||||
|
||||
match own.defined {
|
||||
true => c += &ret(&value),
|
||||
false => c += &(unless_lacks(field) + &ret(&value)),
|
||||
}
|
||||
}
|
||||
|
||||
let proto = program.classes.field(class, PROTO_FIELD);
|
||||
|
||||
c += &match (program.classes.classes[class].kind, proto) {
|
||||
(ClassKind::Array, _) => match program.builtin_proto(Member::Obj(class)) {
|
||||
Some((proto, proto_class)) => ret(&program.field_value(proto_class, field, &proto)),
|
||||
None => ret(UNDEFINED),
|
||||
},
|
||||
(_, None) => ret(UNDEFINED),
|
||||
(_, Some(proto)) => {
|
||||
let lacks = match proto.defined {
|
||||
true => String::new(),
|
||||
false => if_lacks(PROTO_FIELD) + &ret(UNDEFINED),
|
||||
};
|
||||
|
||||
let value = program.field_access("o", proto);
|
||||
let on_proto = program.split(&value, &proto.ty, |proto, member| match member {
|
||||
Member::Obj(proto_class) => ret(&program.field_value(proto_class, field, proto)),
|
||||
_ => ret(UNDEFINED),
|
||||
});
|
||||
|
||||
lacks + &on_proto
|
||||
}
|
||||
};
|
||||
|
||||
indent(&c)
|
||||
}
|
||||
|
||||
+131
-192
@@ -5,8 +5,9 @@
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use super::classes::{CType, ClassField, ClassKind, Classes, Member};
|
||||
use super::functions::Function;
|
||||
use super::types::{CType, ClassField, ClassKind, Member, Types};
|
||||
use super::js::indent;
|
||||
use crate::{
|
||||
flatten::ModuleBindings,
|
||||
infer::{LENGTH_FIELD, Type, TypeCache, join, module_binding},
|
||||
@@ -15,20 +16,6 @@ use crate::{
|
||||
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> {
|
||||
// The input: the JS functions, and what infer found
|
||||
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
|
||||
|
||||
// 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 globals: BTreeMap<usize, CType>, // statics: the module bindings, which are locals of the entry, and their C types
|
||||
pub functions: Vec<Function>,
|
||||
@@ -57,7 +44,7 @@ impl<'a> Program<'a> {
|
||||
atoms,
|
||||
bindings,
|
||||
entry: cache.find(fns.len() - 1, None, &[], fns),
|
||||
types: Types::new(&cache.memory),
|
||||
classes: Classes::new(&cache.memory),
|
||||
strings: vec![],
|
||||
globals: BTreeMap::new(),
|
||||
functions: vec![],
|
||||
@@ -66,23 +53,11 @@ impl<'a> Program<'a> {
|
||||
program.add_globals();
|
||||
program.add_functions();
|
||||
program.add_strings();
|
||||
program.add_unions();
|
||||
program
|
||||
}
|
||||
|
||||
/// Each module binding is a C global, so that any function can use the stdlib's
|
||||
fn add_globals(&mut self) {
|
||||
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
|
||||
/// 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) {
|
||||
let walks = (0..self.cache.walks.len()).filter(|&walk| self.cache.is_live(walk));
|
||||
let mut lookups = BTreeSet::new();
|
||||
@@ -99,8 +74,8 @@ impl<'a> Program<'a> {
|
||||
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
|
||||
/// class that can lack the field
|
||||
/// The lookups that a walk needs: one for each field that it gets, on each class that can
|
||||
/// lack the field
|
||||
fn add_lookups(&self, walk: usize, lookups: &mut BTreeSet<(usize, AtomRef)>) {
|
||||
for instr in self.instrs(walk) {
|
||||
let field = match instr.op {
|
||||
@@ -111,7 +86,7 @@ impl<'a> Program<'a> {
|
||||
|
||||
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) {
|
||||
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
|
||||
/// lookup goes on to their prototypes, so those need lookups too
|
||||
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)) {
|
||||
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) {
|
||||
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 Function::Js(walk) = function else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let cpool = &self.js_fn(walk).cpool;
|
||||
let mut strings = vec![];
|
||||
|
||||
for walk in walks {
|
||||
for instr in self.instrs(walk) {
|
||||
let text = match instr.op {
|
||||
OpCode::PushAtomValue => instr.imm.atom().text(self.atoms),
|
||||
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(),
|
||||
_ => continue,
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
if !strings.contains(&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
|
||||
/// 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);
|
||||
}
|
||||
self.strings = strings;
|
||||
}
|
||||
|
||||
/// The types of the values in a function: the locals, args and regs of a JS function,
|
||||
/// and what it returns, or what a lookup gives
|
||||
fn value_types(&self, function: &Function) -> Vec<Type> {
|
||||
match *function {
|
||||
Function::Js(walk) => {
|
||||
let slots = slots(self.js_fn(walk))
|
||||
.into_iter()
|
||||
.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![],
|
||||
/// Each module binding is a C global, so that any function can use the stdlib's
|
||||
fn add_globals(&mut self) {
|
||||
let entry = self.fns.len() - 1;
|
||||
|
||||
for &local in self.bindings.values() {
|
||||
let ty = self.cache.memory.get(module_binding(entry, local));
|
||||
self.globals.insert(local, self.classes.ctype(ty));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,25 +159,16 @@ impl<'a> Program<'a> {
|
||||
let blocks = blocks.filter(|(_, reachable)| **reachable);
|
||||
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
|
||||
pub fn js_fn(&self, walk: usize) -> &'a RegFn {
|
||||
&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
|
||||
pub fn slot_type(&self, walk: usize, slot: Operand) -> Type {
|
||||
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
|
||||
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 {
|
||||
match member {
|
||||
Member::Obj(class) => self.types.classes[class].kind,
|
||||
Member::Obj(class) => self.classes.classes[class].kind,
|
||||
_ => 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
|
||||
pub fn string(&self, text: &str) -> String {
|
||||
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
|
||||
pub fn binding(&self, name: &str) -> Option<(String, CType)> {
|
||||
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 ────────────────────────────────────────
|
||||
|
||||
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
|
||||
/// of the class has the field, that is the field itself. Else it is a call of the field's
|
||||
/// lookup
|
||||
pub fn field_value(&self, class: usize, field: AtomRef, obj: &str) -> (String, CType) {
|
||||
match self.types.field(class, field) {
|
||||
Some(f) if f.defined => (self.field_access(obj, f), f.ctype),
|
||||
_ => {
|
||||
let lookup = Function::Lookup(class, field).name(self);
|
||||
let ctype = self.types.of(&self.lookup_type(class, field));
|
||||
(format!("{lookup}({obj})"), ctype)
|
||||
}
|
||||
/// The value of `field` on `obj`, an object of `class`, as C. If every object of the
|
||||
/// class has the field, that is the field itself. Else it is a call of the field's lookup
|
||||
pub fn field_value(&self, class: usize, field: AtomRef, obj: &str) -> String {
|
||||
match self.classes.field(class, field) {
|
||||
Some(f) if f.defined => self.field_access(obj, f),
|
||||
_ => format!("{}({obj})", Function::Lookup(class, field).name(self)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 class's objects, and on their prototypes (see `Memory::read_field`)
|
||||
pub fn lookup_type(&self, class: usize, field: AtomRef) -> Type {
|
||||
let objs = &self.types.classes[class].objs;
|
||||
/// The C type that the lookup of `field` on `class` gives: of the join of the field's
|
||||
/// types on the class's objects, and on their prototypes (see `Memory::read_field`)
|
||||
pub fn lookup_ctype(&self, class: usize, field: AtomRef) -> CType {
|
||||
let objs = &self.classes.classes[class].objs;
|
||||
let memory = &self.cache.memory;
|
||||
let types = objs
|
||||
.iter()
|
||||
.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
|
||||
@@ -412,26 +343,34 @@ impl Program<'_> {
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let (stdlib_class, ctype) = self.binding(name)?;
|
||||
let proto = self.types.field(ctype.class()?, PROTOTYPE_FIELD)?;
|
||||
let value = self.field_access(&stdlib_class, proto);
|
||||
Some((value, proto.ctype.class()?))
|
||||
let (stdlib_class, CType::One(Member::Obj(class))) = self.binding(name)? else {
|
||||
return None;
|
||||
};
|
||||
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
|
||||
/// is the stdlib's. Another object's is its `__proto__` field, if it has one
|
||||
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));
|
||||
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![];
|
||||
};
|
||||
|
||||
let members = self.types.members_of(proto.ctype);
|
||||
let classes = members.into_iter().map(|member| CType::One(member).class());
|
||||
let members = self.classes.members(&proto.ty).into_iter();
|
||||
let classes = members.map(|member| match member {
|
||||
Member::Obj(class) => Some(class),
|
||||
_ => None,
|
||||
});
|
||||
classes.flatten().collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
// pointer to one is a real object: undefined and null are values of their own
|
||||
#pragma once
|
||||
@@ -10,6 +11,9 @@
|
||||
typedef struct { char _; } undefined_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
|
||||
typedef struct jsrt_string *string_t;
|
||||
|
||||
@@ -65,3 +69,456 @@ _Noreturn void __rt_type_error(const char *what) {
|
||||
}
|
||||
|
||||
#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
@@ -10,7 +10,8 @@
|
||||
// the allocator and errors
|
||||
// __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
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user