use alloc::vec::Vec;
use bytecode_lang::{ArgKind as LArgKind, CallShape, FuncId, ImportId, Inst, Reg, ValType};
use hir_lang::{Arg, ArgKind, ExprId, ItemKind, List, NodeRef, ParamKind, Res, Symbol};
use crate::unit::FnInfo;
use intern_lang::Lookup;
use super::place::PlaceStep;
use super::{Dst, Gen, Rel, Step, Task, Val};
use crate::error::{CompileError, Unsupported};
use crate::func::FnCx;
use crate::unit::{MAX_ARITY, PInfo};
pub(crate) type DynArgs = ((u32, Reg), Vec<LArgKind>, Vec<Task>);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Direct {
Call(FuncId),
Coro(FuncId),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ArgAt {
Pos(i64),
Name(Symbol),
}
#[derive(Clone, Debug)]
pub(crate) enum CallStep {
DirectFinish {
target: Direct,
win: (u32, Reg),
argc: u8,
rest: Option<(Reg, usize)>,
rest_named: Option<(Reg, usize)>,
dst: Dst,
node: NodeRef,
},
ImportFinish {
import: ImportId,
win: (u32, Reg),
argc: u8,
dst: Dst,
node: NodeRef,
},
ValueBegin {
args: Vec<Arg>,
recv: bool,
dst: Dst,
node: NodeRef,
},
ValueFinish {
win: (u32, Reg),
kinds: Vec<LArgKind>,
recv: bool,
dst: Dst,
node: NodeRef,
},
PlaceArg {
idx: u32,
callee: Reg,
at: ArgAt,
slot: Reg,
node: NodeRef,
},
Method { name: Symbol, node: NodeRef },
DynMethod { node: NodeRef },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum To {
Param(usize),
Rest,
RestNamedInRest(Symbol),
RestNamed(Symbol),
Ignore,
}
fn arrange(params: &[PInfo], args: &[Arg], ignore_extra: bool) -> Option<(Vec<To>, u64)> {
let mut filled = alloc::vec![false; params.len()];
let positional: Vec<usize> = (0..params.len())
.filter(|&i| params.get(i).is_some_and(PInfo::positional))
.collect();
let rest = params.iter().position(|p| p.kind == ParamKind::Rest);
let rest_named = params.iter().position(|p| p.kind == ParamKind::RestNamed);
if params.iter().any(|p| p.rest() && p.by_ref) {
return None;
}
let mut next = 0usize;
let mut named_seen = false;
let mut out = Vec::with_capacity(args.len());
for a in args {
match a.kind {
ArgKind::Positional => {
if named_seen {
return None;
}
match positional.get(next) {
Some(&j) => {
next += 1;
*filled.get_mut(j)? = true;
out.push(To::Param(j));
}
None => match rest {
Some(r) => {
*filled.get_mut(r)? = true;
out.push(To::Rest);
}
None if ignore_extra => out.push(To::Ignore),
None => return None,
},
}
}
ArgKind::Named(id) => {
named_seen = true;
match params
.iter()
.position(|p| p.nameable() && p.name == Some(id.sym))
{
Some(j) => {
let f = filled.get_mut(j)?;
if *f {
return None;
}
*f = true;
out.push(To::Param(j));
}
None => match (rest_named, rest) {
(Some(r), _) => {
*filled.get_mut(r)? = true;
out.push(To::RestNamed(id.sym));
}
(None, Some(r)) => {
*filled.get_mut(r)? = true;
out.push(To::RestNamedInRest(id.sym));
}
(None, None) => return None,
},
}
}
ArgKind::Spread | ArgKind::SpreadNamed => return None,
}
}
let mut mask = 0u64;
for (i, p) in params.iter().enumerate() {
let f = filled.get(i).copied().unwrap_or(false);
if p.required() && !f {
return None;
}
if f && i < 64 {
mask |= 1 << i;
}
}
Some((out, mask))
}
impl<L: Lookup> Gen<'_, '_, L> {
pub(crate) fn call(
&mut self,
e: ExprId,
callee: ExprId,
args: List<Arg>,
dst: Dst,
) -> Result<(), CompileError> {
let node = NodeRef::Expr(e);
let hir = self.cx.hir;
if let hir_lang::Expr::Path(p) = *hir.expr(callee) {
let path = *hir.path(p);
let cnode = NodeRef::Expr(callee);
if self.member_call(p, hir.list(args), dst, node)? {
return Ok(());
}
if path.unresolved == 0 {
match path.res {
Res::Extern(sym) => {
let text = self.cx.text(sym);
if let Some(crate::Lowering::Class { op, module, name }) =
self.cx.opts.intrinsics().get(&text).map(|d| d.lowering())
{
let host = (module.clone(), name.clone());
let op = *op;
let list = hir.list(args).to_vec();
return self.class_op(op, host, None, &list, dst, node);
}
let (module, name) = self.cx.host_name(sym);
return self.import_call(&module, &name, args, dst, node);
}
Res::Def(_) => {
if let Some(item) = self.cx.local_item(path.res, cnode)? {
if let ItemKind::Fn(def) = hir.item(item).kind {
if def.body.is_none() {
let it = *hir.item(item);
let name =
it.name.map(|n| self.cx.text(n.sym)).unwrap_or_default();
let module = match def.abi {
Some(a) => self.cx.text(a),
None => {
alloc::string::String::from(self.cx.opts.host_module())
}
};
return self.import_call(&module, &name, args, dst, node);
}
return self.direct_call(item, args, dst, node);
}
}
}
_ => {}
}
}
}
self.sched([
Task::Expr(callee, Dst::Temp),
Task::Step(Step::Call(CallStep::ValueBegin {
args: hir.list(args).to_vec(),
recv: false,
dst,
node,
})),
]);
Ok(())
}
pub(crate) fn method_call(
&mut self,
e: ExprId,
receiver: ExprId,
name: Option<Symbol>,
dyn_name: Option<ExprId>,
args: List<Arg>,
dst: Dst,
) {
let node = NodeRef::Expr(e);
let begin = Task::Step(Step::Call(CallStep::ValueBegin {
args: self.cx.hir.list(args).to_vec(),
recv: true,
dst,
node,
}));
match (name, dyn_name) {
(Some(name), _) => self.sched([
Task::Expr(receiver, Dst::Temp),
Task::Step(Step::Call(CallStep::Method { name, node })),
begin,
]),
(None, Some(n)) => self.sched([
Task::Expr(receiver, Dst::Temp),
Task::Expr(n, Dst::Any),
Task::Step(Step::Call(CallStep::DynMethod { node })),
begin,
]),
(None, None) => {}
}
}
fn import_call(
&mut self,
module: &str,
name: &str,
args: List<Arg>,
dst: Dst,
node: NodeRef,
) -> Result<(), CompileError> {
let list = self.cx.hir.list(args);
if list.iter().any(|a| a.kind != ArgKind::Positional) {
return Err(self.cx.unsupported(Unsupported::ExternArgs, node));
}
if list.len() > MAX_ARITY {
return Err(self.too_many_args(node));
}
let argc = u8::try_from(list.len()).unwrap_or(u8::MAX);
let import = self.cx.import(module, name, argc);
let shape = alloc::vec![ValType::Dyn; list.len() + 1];
let win = self.fx.window(&shape);
self.tasks
.push(Task::Step(Step::Call(CallStep::ImportFinish {
import,
win,
argc,
dst,
node,
})));
for (i, a) in list.iter().enumerate().rev() {
let slot = FnCx::slot(win.1, i + 1);
let t = self.store_expr_tasks(a.value, slot);
self.tasks.push(t[1].clone());
self.tasks.push(t[0].clone());
}
Ok(())
}
pub(crate) fn too_many_args(&self, node: NodeRef) -> CompileError {
CompileError::TooManyArgs {
node,
span: self.cx.span(node),
limit: MAX_ARITY as u32,
}
}
fn direct_call(
&mut self,
item: hir_lang::ItemId,
args: List<Arg>,
dst: Dst,
node: NodeRef,
) -> Result<(), CompileError> {
let Some(info) = self.cx.fns.get(&item).cloned() else {
return Ok(());
};
let list = self.cx.hir.list(args);
self.direct_with(&info, None, list, dst, node)
}
pub(crate) fn direct_with(
&mut self,
info: &FnInfo,
recv: Option<Val>,
list: &[Arg],
dst: Dst,
node: NodeRef,
) -> Result<(), CompileError> {
let Some(callable) = info.callable else {
if let Some(r) = recv {
self.release(r);
}
return Ok(());
};
let off = usize::from(recv.is_some());
let user = info.params.get(off..).unwrap_or(&[]);
let arranged = arrange(user, list, self.cx.opts.ignore_extra());
let Some((dests, mask)) = arranged else {
let f = self.fx.temp(ValType::Dyn);
self.emit(Inst::MakeClosure {
dst: f,
func: callable,
});
if let Some(r) = recv {
self.vals.push(r);
}
self.vals.push(Val {
reg: f,
rel: Rel::Temp,
});
return self.call_step(CallStep::ValueBegin {
args: list.to_vec(),
recv: recv.is_some(),
dst,
node,
});
};
let mask = (mask << off) | u64::from(recv.is_some());
let target = match info.gen_body {
Some(g) => Direct::Coro(g),
None => Direct::Call(callable),
};
let mut shape: Vec<ValType> = alloc::vec![ValType::Dyn];
shape.extend_from_slice(&info.sig);
let win = self.fx.window(&shape);
let n = info.params.len();
if let Some(r) = recv {
self.emit(Inst::Mov {
dst: FnCx::slot(win.1, 1),
src: r.reg,
});
self.release(r);
}
for i in off..n {
let filled = i < 64 && mask & (1 << i) != 0;
let is_rest = info.params.get(i).is_some_and(PInfo::rest);
if !filled && !is_rest {
self.emit(Inst::LoadNil {
dst: FnCx::slot(win.1, i + 1),
});
}
}
if info.has_mask {
let m = FnCx::slot(win.1, n + 1);
self.load_i64(m, mask as i64);
}
let mut rest = None;
let mut rest_named = None;
for (i, p) in info.params.iter().enumerate() {
match p.kind {
ParamKind::Rest => rest = Some((self.new_map(), i)),
ParamKind::RestNamed => rest_named = Some((self.new_map(), i)),
_ => {}
}
}
let argc = u8::try_from(info.sig.len()).unwrap_or(u8::MAX);
self.tasks
.push(Task::Step(Step::Call(CallStep::DirectFinish {
target,
win,
argc,
rest,
rest_named,
dst,
node,
})));
let mut ts: Vec<Task> = Vec::with_capacity(list.len() * 2);
for (a, to) in list.iter().zip(dests.iter()) {
match *to {
To::Param(j) => {
let j = j + off;
let slot = FnCx::slot(win.1, j + 1);
let by_ref = info.params.get(j).is_some_and(|p| p.by_ref);
if by_ref && a.place {
let (pts, idx) = self.place_tasks(a.value, true)?;
ts.extend(pts);
ts.push(Task::Step(Step::Place(PlaceStep::Ref { idx, node })));
ts.push(Task::Deliver {
dst: Dst::Reg(slot),
dup: false,
});
ts.push(Task::Step(Step::Place(PlaceStep::Drop { idx })));
} else if by_ref {
let dup = self.needs_dup(a.value);
ts.push(Task::Expr(a.value, Dst::Any));
ts.push(Task::NewCell { cell: slot, dup });
} else {
ts.extend(self.store_expr_tasks(a.value, slot));
}
}
To::Rest => {
if let Some((map, _)) = rest {
let dup = self.needs_dup(a.value);
ts.push(Task::Expr(a.value, Dst::Any));
ts.push(Task::MapPush { map, dup, node });
}
}
To::RestNamed(name) | To::RestNamedInRest(name) => {
let into = if matches!(to, To::RestNamed(_)) {
rest_named
} else {
rest
};
if let Some((map, _)) = into {
let dup = self.needs_dup(a.value);
ts.push(Task::Expr(a.value, Dst::Any));
ts.push(Task::MapSetName {
map,
name,
dup,
node,
});
}
}
To::Ignore => ts.push(Task::Expr(a.value, Dst::Discard)),
}
}
self.sched_all(ts);
Ok(())
}
fn value_begin(
&mut self,
args: &[Arg],
recv: bool,
dst: Dst,
node: NodeRef,
) -> Result<(), CompileError> {
let (win, kinds, mut ts) = self.dynamic_args(args, recv, node)?;
ts.push(Task::Step(Step::Call(CallStep::ValueFinish {
win,
kinds,
recv,
dst,
node,
})));
self.sched_all(ts);
Ok(())
}
pub(crate) fn dynamic_args(
&mut self,
list: &[Arg],
recv: bool,
node: NodeRef,
) -> Result<DynArgs, CompileError> {
let n = list.len() + usize::from(recv);
if n > MAX_ARITY {
return Err(self.too_many_args(node));
}
let callee = self.vals.last().copied().map_or(Reg(0), |v| v.reg);
let shape = alloc::vec![ValType::Dyn; n + 1];
let win = self.fx.window(&shape);
let mut kinds: Vec<LArgKind> = Vec::with_capacity(n);
if recv {
let at = self.vals.len().saturating_sub(2);
if let Some(&r) = self.vals.get(at) {
let slot = FnCx::slot(win.1, 1);
if self.php() {
self.dup_into(slot, r.reg);
} else {
self.emit(Inst::Mov {
dst: slot,
src: r.reg,
});
}
}
kinds.push(LArgKind::Positional);
}
let mut pos = i64::from(recv);
let mut spread = false;
let mut ts: Vec<Task> = Vec::with_capacity(list.len() * 2);
for (i, a) in list.iter().enumerate() {
let slot = FnCx::slot(win.1, i + 1 + usize::from(recv));
let at = match a.kind {
ArgKind::Positional => {
kinds.push(LArgKind::Positional);
pos += 1;
(!spread).then_some(ArgAt::Pos(pos - 1))
}
ArgKind::Named(id) => {
let text = self.cx.text(id.sym);
let s = self.cx.mb.string(&text);
kinds.push(LArgKind::Named(s));
Some(ArgAt::Name(id.sym))
}
ArgKind::Spread | ArgKind::SpreadNamed => {
kinds.push(if a.kind == ArgKind::Spread {
LArgKind::Spread
} else {
LArgKind::SpreadNamed
});
spread = true;
ts.push(Task::Expr(a.value, Dst::Reg(slot)));
ts.push(Task::At(node));
continue;
}
};
match (a.place, at) {
(true, Some(at)) => {
let (pts, idx) = self.place_tasks(a.value, true)?;
ts.extend(pts);
ts.push(Task::Step(Step::Call(CallStep::PlaceArg {
idx,
callee,
at,
slot,
node,
})));
}
_ => ts.extend(self.store_expr_tasks(a.value, slot)),
}
}
Ok((win, kinds, ts))
}
pub(crate) fn dynamic_call(&mut self, win: (u32, Reg), kinds: &[LArgKind], callee: Reg) {
let argc = u8::try_from(kinds.len()).unwrap_or(u8::MAX);
if kinds.iter().all(|k| *k == LArgKind::Positional) {
self.emit(Inst::DCall {
dst: win.1,
callee,
argc,
});
} else if CallShape::new(kinds.to_vec()).validate().is_ok() {
let shape = self.fx.f.call_shape(kinds);
self.emit(Inst::DCallShape {
dst: win.1,
callee,
shape,
});
} else {
self.raise_kind(bytecode_lang::ErrorKind::ArgumentError);
}
}
pub(crate) fn call_step(&mut self, s: CallStep) -> Result<(), CompileError> {
match s {
CallStep::DirectFinish {
target,
win,
argc,
rest,
rest_named,
dst,
node,
} => {
self.at(node);
for (map, i) in [rest, rest_named].into_iter().flatten() {
self.emit(Inst::Mov {
dst: FnCx::slot(win.1, i + 1),
src: map,
});
self.fx.release(map);
}
match target {
Direct::Call(func) => self.emit(Inst::Call {
dst: win.1,
func,
argc,
}),
Direct::Coro(func) => self.emit(Inst::CoroNew {
dst: win.1,
func,
argc,
}),
}
self.deliver(
Val {
reg: win.1,
rel: Rel::Win(win.0),
},
dst,
false,
);
}
CallStep::ImportFinish {
import,
win,
argc,
dst,
node,
} => {
self.at(node);
self.emit(Inst::CallImport {
dst: win.1,
import,
argc,
});
self.deliver(
Val {
reg: win.1,
rel: Rel::Win(win.0),
},
dst,
false,
);
}
CallStep::ValueBegin {
args,
recv,
dst,
node,
} => {
self.at(node);
self.value_begin(&args, recv, dst, node)?;
}
CallStep::ValueFinish {
win,
kinds,
recv,
dst,
node,
} => {
self.at(node);
let callee = self.pop();
self.dynamic_call(win, &kinds, callee.reg);
self.release(callee);
if recv {
let r = self.pop();
self.release(r);
}
self.deliver(
Val {
reg: win.1,
rel: Rel::Win(win.0),
},
dst,
false,
);
}
CallStep::PlaceArg {
idx,
callee,
at,
slot,
node,
} => {
self.at(node);
let b = self.fx.temp(ValType::Bool);
match at {
ArgAt::Pos(p) => {
let r = self.fx.temp(ValType::I64);
self.load_i64(r, p);
self.emit(Inst::DParamRef {
dst: b,
callee,
pos: r,
});
self.fx.release(r);
}
ArgAt::Name(name) => {
let n = self.name_ref(name);
self.emit(Inst::DParamRefNamed {
dst: b,
callee,
name: n,
});
}
}
let by_val = self.label();
let done = self.label();
self.jump_on(b, by_val, false);
self.fx.release(b);
self.tasks
.push(Task::Step(Step::Place(PlaceStep::Drop { idx })));
self.tasks.push(Task::Bind(done));
self.tasks.push(Task::Deliver {
dst: Dst::Reg(slot),
dup: self.php(),
});
self.tasks
.push(Task::Step(Step::Place(PlaceStep::Read { idx, node })));
self.tasks.push(Task::Bind(by_val));
self.tasks.push(Task::Jump(done));
self.tasks.push(Task::Deliver {
dst: Dst::Reg(slot),
dup: false,
});
self.tasks
.push(Task::Step(Step::Place(PlaceStep::Ref { idx, node })));
}
CallStep::Method { name, node } => {
self.at(node);
let recv = self.vals.last().copied().unwrap_or(Val {
reg: Reg(0),
rel: Rel::Keep,
});
let f = self.fx.temp(ValType::Dyn);
self.method_lookup(f, recv.reg, name);
self.vals.push(Val {
reg: f,
rel: Rel::Temp,
});
}
CallStep::DynMethod { node } => {
self.at(node);
let name = self.pop();
let recv = self.vals.last().copied().unwrap_or(Val {
reg: Reg(0),
rel: Rel::Keep,
});
let f = self.fx.temp(ValType::Dyn);
self.dyn_access(crate::unit::DynOp::Method, f, recv.reg, name.reg, None);
self.release(name);
self.vals.push(Val {
reg: f,
rel: Rel::Temp,
});
}
}
Ok(())
}
pub(crate) fn test_bit(&mut self, mask: Reg, i: usize, b: Reg) {
let bit = self.fx.temp(ValType::I64);
let zero = self.fx.temp(ValType::I64);
let v = 1i64.wrapping_shl(u32::try_from(i).unwrap_or(63));
self.load_i64(bit, v);
self.load_i64(zero, 0);
self.emit(Inst::IAnd {
dst: bit,
lhs: mask,
rhs: bit,
op: bytecode_lang::IntOp::new(bytecode_lang::IntTy::I64),
});
self.emit(Inst::INe {
dst: b,
lhs: bit,
rhs: zero,
ty: bytecode_lang::IntTy::I64,
});
self.fx.release(zero);
self.fx.release(bit);
}
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use hir_lang::{Arg, ArgKind, ExprId, Ident, ParamId, ParamKind, PatId, Span, Symbol};
use super::{To, arrange};
use crate::unit::PInfo;
fn sym(i: u32) -> Symbol {
Symbol::from_u32(i + 1).unwrap_or_else(|| panic!("symbol"))
}
fn param(i: u32, kind: ParamKind, default: bool) -> PInfo {
PInfo {
id: ParamId::from_index(i as usize),
kind,
name: Some(sym(i)),
by_ref: false,
default: default.then(|| ExprId::from_index(0).unwrap_or_else(|| panic!("id"))),
pat: PatId::from_index(0),
}
}
fn pos() -> Arg {
Arg::positional(ExprId::from_index(0).unwrap_or_else(|| panic!("id")))
}
fn named(i: u32) -> Arg {
Arg {
kind: ArgKind::Named(Ident::new(sym(i), Span::empty(0))),
..pos()
}
}
#[test]
fn positional_named_rest_and_defaults() {
let ps = [
param(0, ParamKind::Normal, false),
param(1, ParamKind::Normal, true),
param(2, ParamKind::Rest, false),
param(3, ParamKind::NamedOnly, true),
];
let (to, mask) =
arrange(&ps, &[pos(), pos(), pos(), named(3)], false).unwrap_or_else(|| panic!("fits"));
assert_eq!(to, [To::Param(0), To::Param(1), To::Rest, To::Param(3)]);
assert_eq!(mask, 0b1111);
assert!(arrange(&ps, &[pos()], false).is_some());
assert!(arrange(&ps, &[named(3)], false).is_none());
let (to, _) = arrange(&ps, &[pos(), named(9)], false).unwrap_or_else(|| panic!("fits"));
assert_eq!(to, [To::Param(0), To::RestNamedInRest(sym(9))]);
}
#[test]
fn duplicates_unknown_names_and_spreads_bind_at_run_time() {
let ps = [
param(0, ParamKind::Normal, false),
param(1, ParamKind::PositionalOnly, true),
];
assert!(arrange(&ps, &[named(0), pos()], false).is_none());
assert!(arrange(&ps, &[pos(), named(1)], false).is_none());
assert!(arrange(&ps, &[pos(), pos(), pos()], false).is_none());
let (to, _) = arrange(&ps, &[pos(), pos(), pos()], true).unwrap_or_else(|| panic!("fits"));
assert_eq!(to, [To::Param(0), To::Param(1), To::Ignore]);
let spread = Arg {
kind: ArgKind::Spread,
..pos()
};
assert!(arrange(&ps, &[spread], true).is_none());
let empty: Vec<Arg> = Vec::new();
assert!(arrange(&[], &empty, false).is_some());
}
}