use crate::runtime::Value;
use crate::runtime::function::Proto;
use crate::vm::isa::{Inst, Op};
pub(super) type Res<T> = Result<T, String>;
#[derive(Clone, Copy)]
pub(super) struct Window {
pub first: usize,
pub last: usize,
pub pivot: u32,
pub delta: i32,
}
#[derive(Clone, Copy)]
pub(super) enum Dist {
SJ,
BxFwd,
BxBack,
SBx,
}
struct Fixup {
at: usize,
target: usize,
dist: Dist,
}
pub(super) struct Asm<'p> {
pub p: &'p Proto,
dialect: &'static str,
windows: Vec<Window>,
temp_base: u32,
next_temp: u32,
temps_used: u32,
pub code: Vec<u32>,
lines: Vec<u32>,
first: Vec<Option<u32>>,
fixups: Vec<Fixup>,
pub consts: Vec<Value>,
targets: Vec<bool>,
pc: usize,
line: u32,
}
pub(super) struct Body {
pub code: Vec<u32>,
pub lines: Vec<u32>,
pub consts: Vec<Value>,
pub frame: u32,
pub pc_map: Vec<u32>,
}
impl<'p> Asm<'p> {
pub(super) fn new(
p: &'p Proto,
dialect: &'static str,
windows: Vec<Window>,
frame: u32,
) -> Self {
Asm {
p,
dialect,
windows,
temp_base: frame,
next_temp: 0,
temps_used: 0,
code: Vec::with_capacity(p.code.len() + 4),
lines: Vec::with_capacity(p.code.len() + 4),
first: vec![None; p.code.len()],
fixups: Vec::new(),
consts: p.consts.to_vec(),
targets: jump_targets(p),
pc: 0,
line: 0,
}
}
pub(super) fn err(&self, msg: impl std::fmt::Display) -> String {
format!(
"{}: {msg} (function at line {}, instruction {})",
self.dialect,
self.p.line_defined,
self.pc + 1
)
}
pub(super) fn begin(&mut self, pc: usize) {
self.pc = pc;
self.line = self.p.lines.get(pc).copied().unwrap_or(0);
self.next_temp = 0;
}
pub(super) fn pc(&self) -> usize {
self.pc
}
pub(super) fn is_target(&self, pc: usize) -> bool {
self.targets.get(pc).copied().unwrap_or(false)
}
pub(super) fn inst(&self, pc: usize) -> Option<Inst> {
self.p.code.get(pc).copied()
}
pub(super) fn reg_at(&self, pc: usize, r: u32) -> Res<u32> {
let mut m = r as i64;
for w in self
.windows
.iter()
.filter(|w| w.first <= pc && pc <= w.last)
{
if r >= w.pivot {
m += w.delta as i64;
} else if w.delta < 0 && r + 1 == w.pivot {
return Err(self.err(format_args!(
"register {r} is a loop slot PUC does not have"
)));
}
}
u32::try_from(m)
.ok()
.filter(|&m| m <= 255)
.ok_or_else(|| self.err(format_args!("register {r} maps outside the frame")))
}
pub(super) fn r(&self, r: u32) -> Res<u32> {
self.reg_at(self.pc, r)
}
pub(super) fn run(&self, r: u32, n: u32) -> Res<u32> {
let first = self.r(r)?;
if n > 1 && self.r(r + n - 1)? != first + n - 1 {
return Err(self.err(format_args!(
"register run {r}..{} crosses a loop's slots",
r + n - 1
)));
}
Ok(first)
}
pub(super) fn temp(&mut self) -> Res<u32> {
let t = self.temp_base + self.next_temp;
if t >= 255 {
return Err(self.err("no register left for a scratch value"));
}
self.next_temp += 1;
self.temps_used = self.temps_used.max(self.next_temp);
Ok(t)
}
pub(super) fn emit(&mut self, word: u32) {
if self.first[self.pc].is_none() {
self.first[self.pc] = Some(self.code.len() as u32);
}
self.code.push(word);
self.lines.push(self.line);
}
pub(super) fn emit_prologue(&mut self, word: u32, line: u32) {
self.code.push(word);
self.lines.push(line);
}
pub(super) fn jump(&mut self, word: u32, dist: Dist, target: i64) -> Res<()> {
if !(0..self.p.code.len() as i64).contains(&target) {
return Err(self.err(format_args!("jump target {target} outside the code")));
}
self.fixups.push(Fixup {
at: self.code.len(),
target: target as usize,
dist,
});
self.emit(word);
Ok(())
}
pub(super) fn konst(&mut self, v: Value) -> u32 {
let same = |k: &Value| match (k, &v) {
(Value::Int(a), Value::Int(b)) => a == b,
(Value::Float(a), Value::Float(b)) => a.to_bits() == b.to_bits(),
_ => false,
};
match self.consts.iter().position(same) {
Some(i) => i as u32,
None => {
self.consts.push(v);
self.consts.len() as u32 - 1
}
}
}
pub(super) fn short_str(&self, k: u32) -> bool {
matches!(self.consts.get(k as usize), Some(Value::Str(s)) if s.len() <= 40)
}
fn puc_pc(&self, pc: usize) -> u32 {
self.first
.iter()
.skip(pc)
.find_map(|p| *p)
.unwrap_or(self.code.len() as u32)
}
pub(super) fn finish(mut self) -> Res<Body> {
for f in std::mem::take(&mut self.fixups) {
let t = self.puc_pc(f.target) as i64;
let at = f.at as i64;
let d = match f.dist {
Dist::BxBack => at + 1 - t,
_ => t - (at + 1),
};
let w = self.code[f.at];
self.code[f.at] = match f.dist {
Dist::SJ if d.abs() < (1 << 24) => w | (((d + (1 << 24) - 1) as u32) << 7),
Dist::BxFwd | Dist::BxBack if (0..1 << 17).contains(&d) => w | ((d as u32) << 15),
Dist::SBx if d.abs() < (1 << 17) => w | (((d + (1 << 17) - 1) as u32) << 14),
_ => return Err(format!("{}: jump distance {d} does not fit", self.dialect)),
};
}
let pc_map = (0..=self.p.code.len()).map(|pc| self.puc_pc(pc)).collect();
let lines = if self.p.lines.is_empty() {
Vec::new()
} else {
self.lines
};
Ok(Body {
code: self.code,
lines,
consts: self.consts,
frame: self.temp_base + self.temps_used,
pc_map,
})
}
}
fn jump_targets(p: &Proto) -> Vec<bool> {
let n = p.code.len();
let mut t = vec![false; n + 2];
let mut mark = |pc: i64| {
if (0..t.len() as i64).contains(&pc) {
t[pc as usize] = true;
}
};
for (pc, i) in p.code.iter().enumerate() {
let (pc, bx) = (pc as i64, i.bx() as i64);
match i.op() {
Op::Jmp => mark(pc + 1 + i.sj() as i64),
Op::ForPrep => {
mark(pc + bx);
mark(pc + bx + 1);
}
Op::ForLoop | Op::TForLoop => mark(pc + 1 - bx),
Op::TForPrep => mark(pc + 1 + bx),
Op::LFalseSkip | Op::Eq | Op::Lt | Op::Le | Op::EqK | Op::Test | Op::TestSet => {
mark(pc + 2)
}
_ => {}
}
}
t
}
#[derive(Clone, Copy)]
pub(super) struct L {
pub op: Op,
pub a: u32,
pub b: u32,
pub c: u32,
pub k: bool,
pub bx: u32,
pub sbx: i32,
pub sj: i64,
}
impl L {
pub(super) fn of(i: Inst) -> L {
L {
op: i.op(),
a: i.a(),
b: i.b(),
c: i.c(),
k: i.k(),
bx: i.bx(),
sbx: i.sbx(),
sj: i.sj() as i64,
}
}
}
pub(super) fn setlist_offset(asm: &Asm, l: L) -> Res<u64> {
if !l.k {
return Ok(l.c as u64);
}
match asm.inst(asm.pc() + 1) {
Some(x) if x.op() == Op::ExtraArg => Ok(x.ax() as u64),
_ => Err(asm.err("SetList without its ExtraArg")),
}
}
pub(super) fn array_hint(p: &Proto, pc: usize, reg: u32, narr: u32) -> u32 {
for i in &p.code[pc + 1..] {
match i.op() {
Op::NewTable if i.a() == reg => break,
Op::SetList if i.a() == reg && i.b() == 0 => return narr.saturating_sub(1),
_ => {}
}
}
narr
}
pub(super) fn loop_windows(p: &Proto, generic: u32, numeric: Option<u32>) -> Res<Vec<Window>> {
let code = &p.code;
let mut out = Vec::new();
for (pc, i) in code.iter().enumerate() {
let (last, pivot) = match i.op() {
Op::TForPrep => (pc + 2 + i.bx() as usize, generic),
Op::ForPrep => match numeric {
Some(n) => (pc + i.bx() as usize, n),
None => continue,
},
_ => continue,
};
let want = if i.op() == Op::ForPrep {
Op::ForLoop
} else {
Op::TForLoop
};
if code.get(last).map(|x| x.op()) != Some(want) {
return Err(format!("loop prep at pc {} without its loop op", pc + 1));
}
out.push(Window {
first: pc,
last,
pivot: i.a() + pivot,
delta: -1,
});
}
Ok(out)
}