use std::{cmp, mem};
use crate::{
eval::{
bc::{
addr::{BcAddr, BcAddrOffset},
bytecode::Bc,
instr::BcInstr,
instr_impl::{
InstrBr, InstrConst, InstrConst2, InstrConst3, InstrConst4, InstrContinue,
InstrForLoop, InstrIfBr, InstrIfNotBr, InstrLoadLocal, InstrLoadLocal2,
InstrLoadLocal3, InstrLoadLocal4, InstrLoadLocalAndConst, InstrLoadLocalCaptured,
InstrProfileBc, InstrStoreLocal, InstrStoreLocalCaptured,
},
instrs::{BcInstrsWriter, PatchAddr},
opcode::BcOpcode,
slow_arg::BcInstrSlowArg,
},
compiler::span::IrSpanned,
runtime::{call_stack::FrozenFileSpan, slots::LocalSlotId},
},
values::{FrozenHeap, FrozenRef, FrozenValue},
};
pub(crate) struct BcWriter<'f> {
profile: bool,
instrs: BcInstrsWriter,
slow_args: Vec<(BcAddr, BcInstrSlowArg)>,
stack_size: u32,
local_count: u32,
max_stack_size: u32,
queued_locals: Vec<IrSpanned<LocalSlotId>>,
queued_consts: Vec<IrSpanned<FrozenValue>>,
heap: &'f FrozenHeap,
}
impl<'f> BcWriter<'f> {
pub(crate) fn new(profile: bool, local_count: u32, heap: &'f FrozenHeap) -> BcWriter<'f> {
BcWriter {
profile,
instrs: BcInstrsWriter::new(),
slow_args: Vec::new(),
stack_size: 0,
local_count,
max_stack_size: 0,
queued_consts: Vec::new(),
queued_locals: Vec::new(),
heap,
}
}
pub(crate) fn finish(self) -> Bc {
let BcWriter {
profile: has_before_instr,
instrs,
slow_args: spans,
stack_size,
local_count,
max_stack_size,
queued_consts,
queued_locals,
heap,
} = self;
let _ = has_before_instr;
let _ = heap;
assert!(queued_locals.is_empty());
assert!(queued_consts.is_empty());
assert_eq!(stack_size, 0);
Bc {
instrs: instrs.finish(spans),
local_count,
max_stack_size,
}
}
fn ip(&self) -> BcAddr {
assert!(self.queued_consts.is_empty());
self.instrs.ip()
}
fn flush_instrs(&mut self) {
let mut queued_locals = mem::take(&mut self.queued_locals);
let mut queued_consts = mem::take(&mut self.queued_consts);
let mut locals_slice = queued_locals.as_slice();
let mut consts_slice = queued_consts.as_slice();
while let [l0, l1, l2, l3, rem @ ..] = locals_slice {
self.do_write_generic_explicit::<InstrLoadLocal4>(
BcInstrSlowArg {
span: l0.span.merge(&l1.span).merge(&l2.span).merge(&l3.span),
spans: vec![l0.span, l1.span, l2.span, l3.span],
},
[l0.node, l1.node, l2.node, l3.node],
);
locals_slice = rem;
}
match (locals_slice, consts_slice) {
([], _) => {}
([l0], [c0, rem @ ..]) => {
self.do_write_generic::<InstrLoadLocalAndConst>(l0.span, (l0.node, c0.node));
consts_slice = rem;
}
([l0], _) => {
self.do_write_generic::<InstrLoadLocal>(l0.span, l0.node);
}
([l0, l1], _) => {
self.do_write_generic_explicit::<InstrLoadLocal2>(
BcInstrSlowArg {
span: l0.span.merge(&l1.span),
spans: vec![l0.span, l1.span],
},
[l0.node, l1.node],
);
}
([l0, l1, l2], _) => {
self.do_write_generic_explicit::<InstrLoadLocal3>(
BcInstrSlowArg {
span: l0.span.merge(&l1.span).merge(&l2.span),
spans: vec![l0.span, l1.span, l2.span],
},
[l0.node, l1.node, l2.node],
);
}
_ => unreachable!(),
}
while let [v0, v1, v2, v3, rem @ ..] = consts_slice {
self.do_write_generic::<InstrConst4>(
v0.span.merge(&v1.span).merge(&v2.span).merge(&v3.span),
[v0.node, v1.node, v2.node, v3.node],
);
consts_slice = rem;
}
match consts_slice {
[] => {}
[v0] => {
self.do_write_generic::<InstrConst>(v0.span, v0.node);
}
[v0, v1] => {
self.do_write_generic::<InstrConst2>(v0.span.merge(&v1.span), [v0.node, v1.node]);
}
[v0, v1, v2] => {
self.do_write_generic::<InstrConst3>(
v0.span.merge(&v1.span).merge(&v2.span),
[v0.node, v1.node, v2.node],
);
}
_ => unreachable!(),
}
queued_locals.clear();
queued_consts.clear();
self.queued_locals = queued_locals;
self.queued_consts = queued_consts;
}
fn update_stack_size<I: BcInstr>(&mut self, arg: &I::Arg) {
self.stack_sub(I::npops(arg));
self.stack_add(I::npushs(arg));
}
fn do_write_generic_explicit<I: BcInstr>(
&mut self,
slow_arg: BcInstrSlowArg,
arg: I::Arg,
) -> (BcAddr, *const I::Arg) {
if self.profile {
self.instrs
.write::<InstrProfileBc>(BcOpcode::for_instr::<I>());
}
self.slow_args.push((self.ip(), slow_arg));
self.instrs.write::<I>(arg)
}
fn do_write_generic<I: BcInstr>(
&mut self,
span: FrozenFileSpan,
arg: I::Arg,
) -> (BcAddr, *const I::Arg) {
self.do_write_generic_explicit::<I>(
BcInstrSlowArg {
span,
..Default::default()
},
arg,
)
}
fn write_instr_ret_arg_explicit<I: BcInstr>(
&mut self,
slow_arg: BcInstrSlowArg,
arg: I::Arg,
) -> (BcAddr, *const I::Arg) {
assert_ne!(BcOpcode::for_instr::<I>(), BcOpcode::Const);
assert_ne!(BcOpcode::for_instr::<I>(), BcOpcode::Const2);
assert_ne!(BcOpcode::for_instr::<I>(), BcOpcode::Const3);
assert_ne!(BcOpcode::for_instr::<I>(), BcOpcode::Const4);
assert_ne!(BcOpcode::for_instr::<I>(), BcOpcode::LoadLocal);
assert_ne!(BcOpcode::for_instr::<I>(), BcOpcode::LoadLocal2);
assert_ne!(BcOpcode::for_instr::<I>(), BcOpcode::LoadLocal3);
assert_ne!(BcOpcode::for_instr::<I>(), BcOpcode::LoadLocal4);
assert_ne!(BcOpcode::for_instr::<I>(), BcOpcode::LoadLocalAndConst);
self.flush_instrs();
self.update_stack_size::<I>(&arg);
self.do_write_generic_explicit::<I>(slow_arg, arg)
}
fn write_instr_ret_arg<I: BcInstr>(
&mut self,
span: FrozenFileSpan,
arg: I::Arg,
) -> (BcAddr, *const I::Arg) {
self.write_instr_ret_arg_explicit::<I>(
BcInstrSlowArg {
span,
..Default::default()
},
arg,
)
}
pub(crate) fn write_instr_explicit<I: BcInstr>(
&mut self,
slow_arg: BcInstrSlowArg,
arg: I::Arg,
) {
assert_ne!(BcOpcode::for_instr::<I>(), BcOpcode::LoadLocalCaptured);
assert_ne!(BcOpcode::for_instr::<I>(), BcOpcode::StoreLocal);
assert_ne!(BcOpcode::for_instr::<I>(), BcOpcode::StoreLocalCaptured);
self.write_instr_ret_arg_explicit::<I>(slow_arg, arg);
}
pub(crate) fn write_instr<I: BcInstr>(&mut self, span: FrozenFileSpan, arg: I::Arg) {
self.write_instr_explicit::<I>(
BcInstrSlowArg {
span,
..Default::default()
},
arg,
);
}
pub(crate) fn write_const(&mut self, span: FrozenFileSpan, value: FrozenValue) {
self.queued_consts.push(IrSpanned { node: value, span });
self.stack_add(1);
}
pub(crate) fn write_load_local(&mut self, span: FrozenFileSpan, slot: LocalSlotId) {
assert!(slot.0 < self.local_count);
if !self.queued_consts.is_empty() {
self.flush_instrs();
}
self.queued_locals.push(IrSpanned { node: slot, span });
self.stack_add(1);
}
pub(crate) fn write_load_local_captured(&mut self, span: FrozenFileSpan, slot: LocalSlotId) {
assert!(slot.0 < self.local_count);
self.write_instr_ret_arg::<InstrLoadLocalCaptured>(span, slot);
}
pub(crate) fn write_store_local(&mut self, span: FrozenFileSpan, slot: LocalSlotId) {
assert!(slot.0 < self.local_count);
self.write_instr_ret_arg::<InstrStoreLocal>(span, slot);
}
pub(crate) fn write_store_local_captured(&mut self, span: FrozenFileSpan, slot: LocalSlotId) {
assert!(slot.0 < self.local_count);
self.write_instr_ret_arg::<InstrStoreLocalCaptured>(span, slot);
}
pub(crate) fn patch_addr(&mut self, addr: PatchAddr) {
self.flush_instrs();
self.instrs.patch_addr(addr);
}
pub(crate) fn patch_addrs(&mut self, addrs: Vec<PatchAddr>) {
for adds in addrs {
self.patch_addr(adds);
}
}
pub(crate) fn write_br(&mut self, span: FrozenFileSpan) -> PatchAddr {
let arg = self.write_instr_ret_arg::<InstrBr>(span, BcAddrOffset::FORWARD);
self.instrs.addr_to_patch(arg)
}
pub(crate) fn write_if_not_br(&mut self, span: FrozenFileSpan) -> PatchAddr {
let arg = self.write_instr_ret_arg::<InstrIfNotBr>(span, BcAddrOffset::FORWARD);
self.instrs.addr_to_patch(arg)
}
pub(crate) fn write_if_br(&mut self, span: FrozenFileSpan) -> PatchAddr {
let arg = self.write_instr_ret_arg::<InstrIfBr>(span, BcAddrOffset::FORWARD);
self.instrs.addr_to_patch(arg)
}
pub(crate) fn write_if(&mut self, span: FrozenFileSpan, then_block: impl FnOnce(&mut Self)) {
let patch_addr = self.write_if_not_br(span);
then_block(self);
self.patch_addr(patch_addr);
}
pub(crate) fn write_if_not(
&mut self,
span: FrozenFileSpan,
then_block: impl FnOnce(&mut Self),
) {
let patch_addr = self.write_if_br(span);
then_block(self);
self.patch_addr(patch_addr);
}
pub(crate) fn write_for(&mut self, span: FrozenFileSpan, body: impl FnOnce(&mut Self)) {
let arg = self.write_instr_ret_arg::<InstrForLoop>(span, BcAddrOffset::FORWARD);
let end_patch = self.instrs.addr_to_patch(arg);
let ss = self.stack_size();
body(self);
assert!(
self.stack_size() + 1 == ss,
"Loop body must consume stack variable"
);
self.write_instr::<InstrContinue>(span, ());
self.patch_addr(end_patch);
}
pub(crate) fn stack_add(&mut self, add: u32) {
self.stack_size += add;
self.max_stack_size = cmp::max(self.max_stack_size, self.stack_size);
}
pub(crate) fn stack_sub(&mut self, sub: u32) {
assert!(self.stack_size >= sub);
self.stack_size -= sub;
}
pub(crate) fn stack_size(&self) -> u32 {
self.stack_size
}
pub(crate) fn alloc_file_span(
&self,
span: FrozenFileSpan,
) -> FrozenRef<'static, FrozenFileSpan> {
self.heap.alloc_any(span)
}
}