Files
jsrt/compiler/src/emit/calls.rs
T
2026-10-24 15:58:51 +00:00

111 lines
4.3 KiB
Rust

//! The C of calls
//!
//! A call's operands are the callee's params: the callee itself, `this`, then the args (see
//! regify). Each closure that the callee can be is a case of its own (see `Program::split`),
//! and a callee that is not a function throws.
use super::classes::{ClassKind, Member};
use super::js::{JsFunction, UNDEFINED, indent};
use super::program::throw;
use crate::{
infer::{InstrRef, Type},
libqjs::parse::opcodes::OpCode,
native::NATIVES,
regify::{Operand, RegInstr},
transform::{FIRST_ARG, THIS_ARG},
};
impl JsFunction<'_> {
pub(super) fn call(&self, idx: usize, instr: &RegInstr) -> String {
self.split(instr.args[0], |callee, member| {
match (member, self.program.class_kind(member)) {
(Member::Native(native), _) => self.native_call(native, instr),
(Member::Obj(class), ClassKind::Closure(_)) => {
self.closure_call(idx, instr, callee, class)
}
_ => throw("not a function"),
}
})
}
/// A call of a closure of `class`, which is `callee`. Infer walked the closure's JS
/// function for the types of this call's args, and that walk is the C function to call.
/// An arg that the call does not pass is undefined. `new` gives what the constructor
/// returns if that is an object, else the new object itself, which is `this`
fn closure_call(&self, idx: usize, instr: &RegInstr, callee: &str, class: usize) -> String {
let program = self.program;
let ClassKind::Closure(fn_idx) = program.classes.classes[class].kind else {
unreachable!()
};
let mut types: Vec<Type> = instr.args.iter().map(|&slot| self.ty(slot)).collect();
types[0] = Type::Ref(program.classes.classes[class].objs[0]);
let call_site = Some(InstrRef {
fn_idx: self.context.fn_idx,
instr_idx: idx,
});
let walk = program.cache.find(fn_idx, call_site, &types, program.fns);
let args: Vec<String> = (0..program.fns[fn_idx].arg_count)
.map(|i| {
let param = program.slot_ctype(walk, Operand::Arg(i)).c_name();
let arg = match instr.args.get(i) {
_ if i == 0 => callee.to_string(),
Some(&slot) => self.get(slot),
None => UNDEFINED.into(),
};
format!("CAST({param}, {arg})")
})
.collect();
let call = format!("js{walk}({})", args.join(", "));
let ret = program.cache.ret(walk);
let dst = instr.dsts[0];
// A function that never returns gives no value
if *ret == Type::Unknown {
return format!("{call};\nabort(); // does not return\n");
}
if instr.op != OpCode::CallConstructor {
return self.set(dst, &call);
}
let this = self.get(instr.args[THIS_ARG as usize]);
let result = program.split("result", ret, |result, member| match member {
Member::Obj(_) => self.set(dst, result),
_ => self.set(dst, &this),
});
let ctype = program.ret_ctype(walk).c_name();
format!("{{\n {ctype} result = {call};\n{}}}\n", indent(&result))
}
/// A call of a native: a C call, with only the args. Each arg is converted to its
/// param's C type, and an arg of the wrong kind throws (see `ARG_INT`). An arg that
/// the call does not pass is undefined
fn native_call(&self, native: usize, instr: &RegInstr) -> String {
let native = &NATIVES[native];
let args: Vec<String> = (native.arg_types.iter().enumerate())
.map(|(i, param)| {
let arg = instr.args.get(FIRST_ARG as usize + i);
let arg = arg.map_or(UNDEFINED.into(), |&slot| self.get(slot));
match param {
Type::Int => format!("ARG_INT({arg})"),
Type::Float => format!("ARG_NUMBER({arg})"),
Type::Str(_) => format!("ARG_STRING({arg})"),
_ => format!("ARG_UNDEFINED({arg})"),
}
})
.collect();
self.set(
instr.dsts[0],
&format!("{}({})", native.name, args.join(", ")),
)
}
}