//! 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(", ")), ) } }