diff --git a/compiler/src/emit/body.rs b/compiler/src/emit/body.rs deleted file mode 100644 index 799a3a8..0000000 --- a/compiler/src/emit/body.rs +++ /dev/null @@ -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) { - 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 `;`, 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 - } - } - } -} diff --git a/compiler/src/emit/calls.rs b/compiler/src/emit/calls.rs new file mode 100644 index 0000000..fac6f88 --- /dev/null +++ b/compiler/src/emit/calls.rs @@ -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 = 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 = (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 = (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(", ")), + ) + } +} diff --git a/compiler/src/emit/types.rs b/compiler/src/emit/classes.rs similarity index 55% rename from compiler/src/emit/types.rs rename to compiler/src/emit/classes.rs index 3d41d79..40e1737 100644 --- a/compiler/src/emit/types.rs +++ b/compiler/src/emit/classes.rs @@ -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 { - 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_of: HashMap, - pub unions: Vec>, // the members of each union, sorted. A member's tag is its index - union_ids: HashMap, 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![vec![]; count]; let mut objs: Vec = 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![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) -> 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 { + pub fn members(&self, ty: &Type) -> Vec { 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); - } - - let next = self.unions.len(); - let id = *self.union_ids.entry(members.clone()).or_insert(next); - - if id == next { - self.unions.push(members); - } - - CType::Union(id) + /// The C type of a value of type `ty` + pub fn ctype(&self, ty: &Type) -> CType { + ctype_with(ty, &self.class_of) } +} - /// 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); +/// 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, objs: Vec) -> 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 let [member] = members[..] { - return CType::One(member); - } + let kind = match memory.get(Addr::Object(first)) { + Type::Shape(Shape::Array(_)) => ClassKind::Array, + Type::Closure { fn_idx, .. } => ClassKind::Closure(*fn_idx), + _ => ClassKind::Obj, + }; - match self.union_ids.get(&members) { - Some(&id) => CType::Union(id), - None => panic!("emit: no union was added for {members:?}"), - } - } + 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()); - /// The members of a value of C type `ctype` - pub fn members_of(&self, ctype: CType) -> Vec { - match ctype { - CType::One(member) => vec![member], - CType::Union(id) => self.unions[id].clone(), - } + ClassField { + name, + ctype: ctype_with(&ty, class_of), + ty, + shared: field.addr != Addr::Field(first, name), + defined: field.defined, + } + }) + .collect(), + }; + + let elems = match kind { + ClassKind::Array => ctype_with(&join_over(&|obj| memory.elems(obj).clone()), class_of), + _ => CType::One(Member::Undefined), + }; + + Class { + kind, + objs, + fields, + elems, } } @@ -255,8 +197,16 @@ fn members_with(ty: &Type, class_of: &HashMap) -> Vec { members } +/// The C type of `ty`, given the class of each object +fn ctype_with(ty: &Type, class_of: &HashMap) -> 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 { let mut objs: Vec = memory.objects().map(|(obj, _)| obj).collect(); @@ -285,8 +235,8 @@ fn refine(memory: &Memory) -> HashMap { 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>) { + // The C type of each field, or of an array's elements, with the classes so far + let key = |obj: ObjAddr| -> (usize, Vec) { let types: Vec = match memory.get(Addr::Object(obj)) { Type::Shape(Shape::Array(elems)) => vec![(**elems).clone()], _ => memory @@ -298,7 +248,7 @@ fn refine(memory: &Memory) -> HashMap { ( class_of[&obj], - types.iter().map(|ty| members_with(ty, &class_of)).collect(), + types.iter().map(|ty| ctype_with(ty, &class_of)).collect(), ) }; diff --git a/compiler/src/emit/functions.rs b/compiler/src/emit/functions.rs index 4557ebe..4487729 100644 --- a/compiler/src/emit/functions.rs +++ b/compiler/src/emit/functions.rs @@ -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 { 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 = (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(); diff --git a/compiler/src/emit/instr.rs b/compiler/src/emit/instr.rs deleted file mode 100644 index 9a7c863..0000000 --- a/compiler/src/emit/instr.rs +++ /dev/null @@ -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 = 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> = 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 { - 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:?}"), - } -} diff --git a/compiler/src/emit/js.rs b/compiler/src/emit/js.rs index 2fac623..ad018bb 100644 --- a/compiler/src/emit/js.rs +++ b/compiler/src/emit/js.rs @@ -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`, 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_(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 = (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`: 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); } + + c } - /// A block: its instrs, its postfix copies, then its jump - fn block(&mut self, block: &BasicBlock, reachable: &[bool]) { + /// A block: its instrs, its postfix copies, then its jump. If an instr cannot run, nor + /// can the rest of the block + fn block(&self, block: &BasicBlock, reachable: &[bool]) -> String { use OpCode::*; 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 { + 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 = (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, + }) +} diff --git a/compiler/src/emit/mod.rs b/compiler/src/emit/mod.rs index 1dd0dbf..0bbd82c 100644 --- a/compiler/src/emit/mod.rs +++ b/compiler/src/emit/mod.rs @@ -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`). -//! - **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. +//! - **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`). //! -//! 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 \n#include \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 { diff --git a/compiler/src/emit/objects.rs b/compiler/src/emit/objects.rs index f905301..17776fd 100644 --- a/compiler/src/emit/objects.rs +++ b/compiler/src/emit/objects.rs @@ -1,290 +1,233 @@ -//! The C of the instrs on objects: fields and elements +//! The C of the instrs on objects, and of lookups //! //! A field is a member of its class's struct. If every object of the class has the field from //! the start, the field is used directly. Else the struct has a `has_` flag for the field, and -//! getting the field calls its lookup (see `functions`). A field that shares a location (see +//! getting the field calls its lookup. A field that shares a location (see //! `define_field_ref`) holds a pointer to it. +//! +//! An instr on a value that can be one of several members has C for each member (see +//! `Program::split`). -use super::{ - js::JsFunction, - types::{CType, ClassKind, Member}, - values::{as_index, in_range, undefined}, -}; +use super::classes::{ClassKind, Member}; +use super::js::{JsFunction, UNDEFINED, indent}; +use super::program::{Program, throw}; use crate::{ - infer::LENGTH_FIELD, - libqjs::parse::opcodes::OpCode, + infer::{LENGTH_FIELD, ObjAddr}, + libqjs::parse::{AtomRef, opcodes::OpCode}, regify::{Operand, RegInstr}, + transform::PROTO_FIELD, }; -// ──────────────────────────────────────── Fields ──────────────────────────────────────── - impl JsFunction<'_> { + pub(super) fn object_instr(&self, idx: usize, instr: &RegInstr) -> String { + use OpCode::*; + + match instr.op { + Object | Fclosure => { + let class = self.class_here(idx); + let alloc = format!("(struct c{class} *)__rt_alloc(sizeof(struct c{class}))"); + self.set(instr.dsts[0], &alloc) + } + ArrayFrom => self.array_from(idx, instr), + GetField | GetLength => self.get_field(instr), + DefineField | PutField => self.put_field(instr), + DefineFieldRef => self.share_field(instr), + CloseField => self.close_field(instr), + GetArrayEl => self.get_elem(instr), + PutArrayEl => self.put_elem(instr), + _ => unimplemented!("emit {instr:?}"), + } + } + + /// The class of the objects that instr `idx` makes, in this walk's context + fn class_here(&self, idx: usize) -> usize { + let context = self.context; + self.program.classes.class(ObjAddr { + context, + instr: idx, + }) + } + /// `[a, b, ...]`. The operands are the elements, last first - pub(super) fn array_from(&mut self, idx: usize, instr: &RegInstr) { + fn array_from(&self, idx: usize, instr: &RegInstr) -> String { let class = self.class_here(idx); - let elems = self.program.types.classes[class].elems; - let array = self.body.temp(CType::One(Member::Obj(class))); - let n = instr.args.len(); + let mut c = format!( + "struct c{class} *a;\nARRAY_NEW(a, {});\n", + instr.args.len() + ); - self.body - .line(format!("{array} = __rt_alloc(sizeof(struct c{class}));")); - self.body.line(format!( - "{array}->items = __rt_grow(NULL, &{array}->cap, {n}, sizeof *{array}->items);" - )); - self.body.line(format!("{array}->length = {n};")); - - for (i, &slot) in instr.args.iter().rev().enumerate() { - let (value, ctype) = self.get(slot); - self.body - .put(&format!("{array}->items[{i}] = "), &value, ctype, elems); + for (i, &elem) in instr.args.iter().rev().enumerate() { + c += &format!("SET(a->items[{i}], {});\n", self.get(elem)); } - self.set_member(instr.dsts[0], &array, Member::Obj(class)); + c += &self.set(instr.dsts[0], "a"); + format!("{{\n{}}}\n", indent(&c)) } /// `o.f` and `o.length`. A string has its own length, and so does an array. A primitive /// gets the field from the stdlib's prototype of its kind (see `Program::field_class`). /// Undefined and null throw - pub(super) fn get_field(&mut self, instr: &RegInstr) { + fn get_field(&self, instr: &RegInstr) -> String { + let program = self.program; + let dst = instr.dsts[0]; let field = match instr.op { OpCode::GetLength => LENGTH_FIELD, _ => instr.imm.atom(), }; - let dst = instr.dsts[0]; - let cases = self.program.cases(&[self.get(instr.args[0])]); - for case in &cases { - self.body.open(case); - let (obj, member) = (&case.operands[0].0, case.operands[0].1); - - match (member, self.program.field_class(member, field)) { + self.split(instr.args[0], |obj, member| { + match (member, program.field_class(member, field)) { (Member::Obj(_), Some(class)) => { - let (value, ctype) = self.program.field_value(class, field, obj); - self.set(dst, &value, ctype); + self.set(dst, &program.field_value(class, field, obj)) } (_, Some(class)) => { - let (proto, _) = self.program.builtin_proto(member).unwrap(); - let (value, ctype) = self.program.field_value(class, field, &proto); - self.set(dst, &value, ctype); + let (proto, _) = program.builtin_proto(member).unwrap(); + self.set(dst, &program.field_value(class, field, &proto)) } (Member::Str, None) if field == LENGTH_FIELD => { - let length = format!("__rt_string_length({obj})"); - self.set_member(dst, &length, Member::Int); - } - (Member::Obj(_), None) => { - self.set_member(dst, &format!("{obj}->length"), Member::Int) + self.set(dst, &format!("__rt_string_length({obj})")) } + (Member::Obj(_), None) => self.set(dst, &format!("{obj}->length")), (Member::Undefined | Member::Null, _) => { - self.body.throw("cannot read a field of undefined or null") + throw("cannot read a field of undefined or null") } - (Member::Native(_), _) => self.body.throw("a native function has no fields"), - _ => self.body.throw("fields of primitives need the stdlib"), + (Member::Native(_), _) => throw("a native function has no fields"), + _ => throw("fields of primitives need the stdlib"), } - } - - self.body.close(&cases); + }) } /// `o.f = v`, and a field in an object literal. Setting an array's `length` sets its /// length. A primitive has no fields to set, and undefined and null throw - pub(super) fn put_field(&mut self, instr: &RegInstr) { + fn put_field(&self, instr: &RegInstr) -> String { + let program = self.program; let field = instr.imm.atom(); - let (value, from) = self.get(instr.args[0]); - let cases = self.program.cases(&[self.get(instr.args[1])]); + let value = self.get(instr.args[0]); - for case in &cases { - self.body.open(case); - let (obj, member) = (&case.operands[0].0, case.operands[0].1); - - match (member, self.program.class_kind(member)) { + self.split(instr.args[1], |obj, member| { + match (member, program.class_kind(member)) { (Member::Obj(_), ClassKind::Array) if field == LENGTH_FIELD => { - self.set_length(obj, &value, from) + format!("ARRAY_SET_LENGTH({obj}, {value});\n") } (Member::Obj(class), _) => { - let f = self.program.types.field(class, field).unwrap(); - let target = self.program.field_access(obj, f); - self.body - .put(&format!("{target} = "), &value, from, f.ctype); + let f = program.classes.field(class, field).unwrap(); + let set = format!("SET({}, {value});\n", program.field_access(obj, f)); - if !f.defined { - let name = self.program.field_name(field); - self.body.line(format!("{obj}->has_{name} = true;")); + match f.defined { + true => set, + false => { + set + &format!("{obj}->has_{} = true;\n", program.field_name(field)) + } } } - _ => self - .body - .throw("cannot set a field of a primitive, undefined or null"), + _ => throw("cannot set a field of a primitive, undefined or null"), } - } - - self.body.close(&cases); - } - - /// `a.length = n`: the array gets `n` elements, and the new ones are holes. `n` must be a - /// whole number from 0, else it throws, as JS's RangeError does - fn set_length(&mut self, obj: &str, value: &str, from: CType) { - let cases = self.program.cases(&[(value.to_string(), from)]); - - for case in &cases { - self.body.open(case); - let (n, member) = (&case.operands[0].0, case.operands[0].1); - - let length = match member { - Member::Int => n.clone(), - Member::Float => format!("(int32_t){n}"), - _ => { - self.body.throw("an array's length must be a number"); - continue; - } - }; - - let check = in_range(n, member, "INT32_MAX"); - self.body.line(format!("if (!({check})) __rt_type_error(\"an array's length must be a whole number from 0\");")); - self.body.line(format!("{obj}->items = __rt_grow({obj}->items, &{obj}->cap, {length}, sizeof *{obj}->items);")); - self.body.line(format!("{obj}->length = {length};")); - } - - self.body.close(&cases); + }) } /// `define_field_ref`: the field becomes a pointer to the cell of the location in the /// first operand. That location is a shared local or arg, whose C local points to its /// cell, or a reg that holds an object whose field of the same name points to the cell /// (see regify/walk.rs) - pub(super) fn share_field(&mut self, instr: &RegInstr) { + fn share_field(&self, instr: &RegInstr) -> String { let name = self.program.field_name(instr.imm.atom()); - let location = instr.args[0]; - let cell = match location { - Operand::Reg(_) => match self.get(location) { - (holder, CType::One(Member::Obj(_))) => format!("{holder}->{name}"), - _ => unreachable!("a shared location is held by an object"), - }, + let cell = match instr.args[0] { + Operand::Reg(_) => format!("{}->{name}", self.get(instr.args[0])), slot => self.name(slot), }; - let cases = self.program.cases(&[self.get(instr.args[1])]); - - for case in &cases { - self.body.open(case); - self.body - .line(format!("{}->{name} = {cell};", case.operands[0].0)); - } - - self.body.close(&cases); + self.split(instr.args[1], |obj, _| format!("{obj}->{name} = {cell};\n")) } /// `close_field x`: the field, which points to a cell, gets a new cell with the same /// value. Closures that point to the old cell keep it (see transform/closure.rs) - pub(super) fn close_field(&mut self, instr: &RegInstr) { - let field = instr.imm.atom(); - let cases = self.program.cases(&[self.get(instr.args[0])]); - - for case in &cases { - self.body.open(case); - let (obj, member) = (&case.operands[0].0, case.operands[0].1); - let Member::Obj(class) = member else { - unreachable!("a shared location is held by an object") - }; - - let f = self.program.types.field(class, field).unwrap(); - assert!(f.shared, "close_field of a field that is not a cell"); - - let (ctype, name) = (f.ctype.c_name(), self.program.field_name(field)); - self.body.line(format!("{{ {ctype} *cell = __rt_alloc(sizeof({ctype})); *cell = *{obj}->{name}; {obj}->{name} = cell; }}")); - } - - self.body.close(&cases); + fn close_field(&self, instr: &RegInstr) -> String { + let name = self.program.field_name(instr.imm.atom()); + self.split(instr.args[0], |obj, _| { + format!("NEW_CELL({obj}->{name});\n") + }) } -} -// ─────────────────────────────────────── Elements ─────────────────────────────────────── - -impl JsFunction<'_> { /// `a[i]` on an array, and `s[i]` on a string. An index out of range gives undefined. /// Undefined and null throw - pub(super) fn get_elem(&mut self, instr: &RegInstr) { - let dst = instr.dsts[0]; - let operands = [self.get(instr.args[1]), self.get(instr.args[0])]; - let cases = self.program.cases(&operands); + fn get_elem(&self, instr: &RegInstr) -> String { + let (dst, index) = (self.get(instr.dsts[0]), self.get(instr.args[0])); - for case in &cases { - self.body.open(case); - let ((obj, obj_member), (index, index_member)) = (&case.operands[0], &case.operands[1]); - let at = as_index(index, *index_member); - - let (length, item, ctype) = match (*obj_member, self.program.class_kind(*obj_member)) { - (Member::Obj(class), ClassKind::Array) => ( - format!("{obj}->length"), - format!("{obj}->items[{at}]"), - self.program.types.classes[class].elems, - ), - (Member::Str, _) => ( - format!("__rt_string_length({obj})"), - format!("__rt_string_at({obj}, {at})"), - CType::One(Member::Str), - ), + self.split(instr.args[1], |obj, member| { + match (member, self.program.class_kind(member)) { + (Member::Obj(_), ClassKind::Array) => { + format!("ARRAY_GET({dst}, {obj}, {index});\n") + } + (Member::Str, _) => format!("STRING_GET({dst}, {obj}, {index});\n"), (Member::Undefined | Member::Null, _) => { - self.body - .throw("cannot read an element of undefined or null"); - continue; + throw("cannot read an element of undefined or null") } _ => unreachable!("infer gets elements only of arrays and strings"), - }; - - let (none, none_ctype) = undefined(); - let check = in_range(index, *index_member, &length); - self.body.line(format!("if ({check}) {{")); - self.body.indent(1); - self.set(dst, &item, ctype); - self.body.indent(-1); - self.body.line("} else {"); - self.body.indent(1); - self.set(dst, &none, none_ctype); - self.body.indent(-1); - self.body.line("}"); - } - - self.body.close(&cases); + } + }) } - /// `a[i] = v` on an array, which grows to hold `i`. Setting an element of a string, of - /// undefined or of null throws. So does an index that is not a whole number from 0, - /// because it would be a named field - pub(super) fn put_elem(&mut self, instr: &RegInstr) { - let (value, from) = self.get(instr.args[0]); - let operands = [self.get(instr.args[2]), self.get(instr.args[1])]; - let cases = self.program.cases(&operands); + /// `a[i] = v` on an array. Anything else throws + fn put_elem(&self, instr: &RegInstr) -> String { + let (value, index) = (self.get(instr.args[0]), self.get(instr.args[1])); - for case in &cases { - self.body.open(case); - let ((obj, obj_member), (index, index_member)) = (&case.operands[0], &case.operands[1]); + self.split(instr.args[2], |obj, member| { + match self.program.class_kind(member) { + ClassKind::Array => format!("ARRAY_PUT({obj}, {index}, {value});\n"), + _ => throw("cannot set an element of a value that is not an array"), + } + }) + } - let (Member::Obj(class), ClassKind::Array) = - (*obj_member, self.program.class_kind(*obj_member)) - else { - self.body - .throw("cannot set an element of a value that is not an array"); - continue; - }; - - let elems = self.program.types.classes[class].elems; - let at = as_index(index, *index_member); - let check = in_range(index, *index_member, "INT32_MAX"); - - self.body.line(format!( - "if (!({check})) __rt_type_error(\"an array index must be a whole number from 0\");" - )); - self.body.line(format!("if ({at} >= {obj}->length) {{")); - self.body.indent(1); - self.body.line(format!("{obj}->items = __rt_grow({obj}->items, &{obj}->cap, {at} + 1, sizeof *{obj}->items);")); - self.body.line(format!("{obj}->length = {at} + 1;")); - self.body.indent(-1); - self.body.line("}"); - self.body - .put(&format!("{obj}->items[{at}] = "), &value, from, elems); - } - - self.body.close(&cases); + /// The C of `code` for each member that a local, arg or reg can be (see `Program::split`) + pub fn split(&self, slot: Operand, code: impl Fn(&str, Member) -> String) -> String { + self.program.split(&self.get(slot), &self.ty(slot), code) } } + +/// The lookup of `field` on `o`, an object of `class`: its own field if it has it, else the +/// field on its prototype. An array's prototype is the stdlib's. Another object's is its +/// `__proto__` field, which it can lack. At the end of the chain, the field is undefined +pub fn lookup_body(program: &Program, class: usize, field: AtomRef) -> String { + let ctype = program.lookup_ctype(class, field).c_name(); + let ret = |value: &str| format!("return CAST({ctype}, {value});\n"); + let unless_lacks = |field: AtomRef| format!("if (o->has_{}) ", program.field_name(field)); + let if_lacks = |field: AtomRef| format!("if (!o->has_{}) ", program.field_name(field)); + let mut c = String::new(); + + if let Some(own) = program.classes.field(class, field) { + let value = program.field_access("o", own); + + match own.defined { + true => c += &ret(&value), + false => c += &(unless_lacks(field) + &ret(&value)), + } + } + + let proto = program.classes.field(class, PROTO_FIELD); + + c += &match (program.classes.classes[class].kind, proto) { + (ClassKind::Array, _) => match program.builtin_proto(Member::Obj(class)) { + Some((proto, proto_class)) => ret(&program.field_value(proto_class, field, &proto)), + None => ret(UNDEFINED), + }, + (_, None) => ret(UNDEFINED), + (_, Some(proto)) => { + let lacks = match proto.defined { + true => String::new(), + false => if_lacks(PROTO_FIELD) + &ret(UNDEFINED), + }; + + let value = program.field_access("o", proto); + let on_proto = program.split(&value, &proto.ty, |proto, member| match member { + Member::Obj(proto_class) => ret(&program.field_value(proto_class, field, proto)), + _ => ret(UNDEFINED), + }); + + lacks + &on_proto + } + }; + + indent(&c) +} diff --git a/compiler/src/emit/program.rs b/compiler/src/emit/program.rs index ea25d4d..29c814c 100644 --- a/compiler/src/emit/program.rs +++ b/compiler/src/emit/program.rs @@ -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, // statics: the text of each string literal pub globals: BTreeMap, // statics: the module bindings, which are locals of the entry, and their C types pub functions: Vec, @@ -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 = 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()] { - JsValue::String(s) => s.text(), - _ => continue, - }, + OpCode::PushConst | OpCode::PushConst8 => { + match &self.js_fn(walk).cpool[instr.imm.const_idx()] { + JsValue::String(s) => s.text(), + _ => continue, + } + } _ => continue, }; - strings.push(text); + if !strings.contains(&text) { + strings.push(text); + } } } - for text in strings { - if !self.strings.contains(&text) { - self.strings.push(text); - } - } + self.strings = strings; } - /// 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 = (self.functions.iter()) - .flat_map(|function| self.value_types(function)) - .collect(); + /// 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 ty in &types { - self.types.add(ty); - } - } - - /// 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 { - 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![], + 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, // 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 { - 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 { - 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 { - 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() } } diff --git a/compiler/src/emit/values.rs b/compiler/src/emit/values.rs deleted file mode 100644 index 0f815ca..0000000 --- a/compiler/src/emit/values.rs +++ /dev/null @@ -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 { - 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 { - 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}"), - } -} diff --git a/lib/rt/core.h b/lib/rt/core.h index 420a24b..f0c0605 100644 --- a/lib/rt/core.h +++ b/lib/rt/core.h @@ -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 +#include + +#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) diff --git a/lib/rt/jsrt.h b/lib/rt/jsrt.h index 2dbe7ea..8617169 100644 --- a/lib/rt/jsrt.h +++ b/lib/rt/jsrt.h @@ -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