111 lines
4.3 KiB
Rust
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(", ")),
|
|
)
|
|
}
|
|
}
|