use bytecode_lang::{Inst, IntTy, Kind, Reg, ValType};
use hir_lang::{ExprId, NodeRef};
use intern_lang::Lookup;
use super::{Dst, Gen, Rel, Step, Task, Val};
#[derive(Clone, Copy, Debug)]
pub(crate) enum CoroStep {
Yield {
key: bool,
value: bool,
dst: Dst,
node: NodeRef,
},
YieldFrom { dst: Dst, node: NodeRef },
Await { dst: Dst, node: NodeRef },
Spawn { dst: Dst, node: NodeRef },
}
const AWAITING: i32 = 3;
const RETURNED: i32 = 4;
impl<L: Lookup> Gen<'_, '_, L> {
pub(crate) fn yield_expr(
&mut self,
e: ExprId,
key: Option<ExprId>,
value: Option<ExprId>,
dst: Dst,
) {
let node = NodeRef::Expr(e);
let mut ts: alloc::vec::Vec<Task> = alloc::vec::Vec::with_capacity(4);
if let Some(k) = key {
let d = if value.is_none_or(|v| self.simple(v)) {
Dst::Any
} else {
Dst::Temp
};
ts.push(Task::Expr(k, d));
}
if let Some(v) = value {
let dup = self.needs_dup(v);
ts.push(Task::Expr(v, Dst::Any));
if dup {
ts.push(Task::Deliver { dst: Dst::Any, dup });
}
}
ts.push(Task::Step(Step::Coro(CoroStep::Yield {
key: key.is_some(),
value: value.is_some(),
dst,
node,
})));
self.sched_all(ts);
}
pub(crate) fn coro_unary(&mut self, operand: ExprId, step: CoroStep) {
self.sched([Task::Expr(operand, Dst::Temp), Task::Step(Step::Coro(step))]);
}
pub(crate) fn coro_step(&mut self, s: CoroStep) -> Result<(), crate::error::CompileError> {
match s {
CoroStep::Yield {
key,
value,
dst,
node,
} => {
self.at(node);
let v = if value {
self.pop()
} else {
let r = self.fx.temp(ValType::Dyn);
self.emit(Inst::LoadNil { dst: r });
Val {
reg: r,
rel: Rel::Temp,
}
};
let k = key.then(|| self.pop());
let r = self.result_reg(dst);
match k {
Some(k) => self.emit(Inst::YieldKv {
dst: r,
key: k.reg,
src: v.reg,
}),
None => self.emit(Inst::Yield { dst: r, src: v.reg }),
}
if let Some(k) = k {
self.release(k);
}
self.release(v);
if self.php() && dst != Dst::Discard {
self.dup_into(r, r);
}
self.produced(r, dst);
}
CoroStep::YieldFrom { dst, node } => {
self.at(node);
let inner = self.pop();
let r = self.result_reg(dst);
self.yield_from(inner.reg, r);
self.release(inner);
self.produced(r, dst);
}
CoroStep::Await { dst, node } => {
self.at(node);
let v = self.pop();
let r = self.result_reg(dst);
self.emit(Inst::Await { dst: r, src: v.reg });
self.release(v);
self.produced(r, dst);
}
CoroStep::Spawn { dst, node } => {
self.at(node);
let callee = self.pop();
let win = self.fx.window(&[ValType::Dyn]);
self.emit(Inst::Spawn {
dst: win.1,
callee: callee.reg,
argc: 0,
});
self.release(callee);
self.deliver(
Val {
reg: win.1,
rel: Rel::Win(win.0),
},
dst,
false,
);
}
}
Ok(())
}
fn yield_from(&mut self, inner: Reg, r: Reg) {
let b = self.fx.temp(ValType::Bool);
let st = self.fx.temp(ValType::U8);
let code = self.fx.temp(ValType::U8);
let k = self.fx.temp(ValType::Dyn);
let v = self.fx.temp(ValType::Dyn);
let sent = self.fx.temp(ValType::Dyn);
let err = self.fx.temp(ValType::Dyn);
let (iterable, end, top, check, handler) = (
self.label(),
self.label(),
self.label(),
self.label(),
self.label(),
);
self.emit(Inst::IsKind {
dst: b,
src: inner,
kind: Kind::Coroutine,
});
self.jump_on(b, iterable, false);
let not_done = self.label();
self.emit(Inst::CoroStatus {
dst: st,
coro: inner,
});
self.state_is(b, st, code, RETURNED);
self.jump_on(b, not_done, false);
self.emit(Inst::CoroResult {
dst: r,
coro: inner,
});
self.jmp(end);
self.bind(not_done);
self.emit(Inst::LoadNil { dst: sent });
self.bind(top);
self.emit(Inst::Resume {
dst: v,
coro: inner,
src: sent,
});
self.bind(check);
self.emit(Inst::CoroStatus {
dst: st,
coro: inner,
});
let not_returned = self.label();
self.state_is(b, st, code, RETURNED);
self.jump_on(b, not_returned, false);
self.emit(Inst::Mov { dst: r, src: v });
self.jmp(end);
self.bind(not_returned);
let yielded = self.label();
self.state_is(b, st, code, AWAITING);
self.jump_on(b, yielded, false);
self.emit(Inst::Await { dst: sent, src: v });
self.jmp(top);
self.bind(yielded);
self.emit(Inst::CoroKey {
dst: k,
coro: inner,
});
let (start, stop) = (self.label(), self.label());
self.bind(start);
self.emit(Inst::YieldKv {
dst: sent,
key: k,
src: v,
});
self.bind(stop);
self.jmp(top);
self.fx.f.try_region(start, stop, handler, err);
self.bind(handler);
self.emit(Inst::ResumeThrow {
dst: v,
coro: inner,
src: err,
});
self.jmp(check);
self.bind(iterable);
let it = self.fx.temp(ValType::Dyn);
let next = self.label();
let done = self.label();
self.emit(Inst::DIterNew {
dst: it,
src: inner,
});
self.bind(next);
self.emit(Inst::IterNext {
has: b,
iter: it,
val: v,
});
self.jump_on(b, done, false);
self.emit(Inst::IterKey { dst: k, iter: it });
self.emit(Inst::YieldKv {
dst: sent,
key: k,
src: v,
});
self.jmp(next);
self.bind(done);
self.emit(Inst::LoadNil { dst: r });
self.bind(end);
for t in [it, err, sent, v, k, code, st, b] {
self.fx.release(t);
}
}
fn state_is(&mut self, b: Reg, st: Reg, code: Reg, state: i32) {
self.emit(Inst::LoadInt {
dst: code,
val: state,
ty: IntTy::U8,
});
self.emit(Inst::IEq {
dst: b,
lhs: st,
rhs: code,
ty: IntTy::U8,
});
}
}