tvl-depot/tvix/eval/src/chunk.rs
Griffin Smith 915ff5ac2a refactor(tvix/eval): Don't (ab)use PartialEq for Nix equality
Using rust's PartialEq trait to implement Nix equality semantics is
reasonably fraught with peril, both because the actual laws are
different than what nix expects, and (more importantly) because certain
things actually require extra context to compare for equality (for
example, thunks need to be forced). This converts the manual PartialEq
impl for Value (and all its descendants) to a *derived* PartialEq
impl (which requires a lot of extra PartialEq derives on miscellanious
other types within the codebase), and converts the previous
nix-semantics equality comparison into a new `nix_eq` method. This
returns an EvalResult, even though it can't currently return an error,
to allow it to fail when eg forcing thunks (which it will do soon).

Since the PartialEq impls for Value and NixAttrs are now quite boring,
this converts the generated proptests for those into handwritten ones
that cover `nix_eq` instead

Change-Id: If3da7171f88c22eda5b7a60030d8b00c3b76f672
Reviewed-on: https://cl.tvl.fyi/c/depot/+/6650
Autosubmit: grfn <grfn@gws.fyi>
Tested-by: BuildkiteCI
Reviewed-by: tazjin <tazjin@tvl.su>
2022-09-18 22:03:41 +00:00

170 lines
4.9 KiB
Rust

use std::io::Write;
use std::ops::Index;
use codemap::CodeMap;
use crate::opcode::{CodeIdx, ConstantIdx, OpCode};
use crate::value::Value;
/// Represents a source location from which one or more operations
/// were compiled.
///
/// The span itself is an index into a [codemap::Codemap], and the
/// structure tracks the number of operations that were yielded from
/// the same span.
///
/// At error reporting time, it becomes possible to either just fetch
/// the textual representation of that span from the codemap, or to
/// even re-parse the AST using rnix to create more semantically
/// interesting errors.
#[derive(Clone, Debug, PartialEq)]
struct SourceSpan {
/// Span into the [codemap::Codemap].
span: codemap::Span,
/// Number of instructions derived from this span.
count: usize,
}
/// A chunk is a representation of a sequence of bytecode
/// instructions, associated constants and additional metadata as
/// emitted by the compiler.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Chunk {
pub code: Vec<OpCode>,
pub constants: Vec<Value>,
spans: Vec<SourceSpan>,
}
impl Index<ConstantIdx> for Chunk {
type Output = Value;
fn index(&self, index: ConstantIdx) -> &Self::Output {
&self.constants[index.0]
}
}
impl Index<CodeIdx> for Chunk {
type Output = OpCode;
fn index(&self, index: CodeIdx) -> &Self::Output {
&self.code[index.0]
}
}
impl Chunk {
pub fn push_op(&mut self, data: OpCode, span: codemap::Span) -> CodeIdx {
let idx = self.code.len();
self.code.push(data);
self.push_span(span);
CodeIdx(idx)
}
/// Pop the last operation from the chunk and clean up its tracked
/// span. Used when the compiler backtracks.
pub fn pop_op(&mut self) {
// Simply drop the last op.
self.code.pop();
// If the last span only had this op, drop it, otherwise
// decrease its operation counter.
match self.spans.last_mut() {
// If the last span had more than one op, decrease the
// counter.
Some(span) if span.count > 1 => span.count -= 1,
// Otherwise, drop it.
Some(_) => {
self.spans.pop();
}
None => unreachable!(),
}
}
pub fn push_constant(&mut self, data: Value) -> ConstantIdx {
let idx = self.constants.len();
self.constants.push(data);
ConstantIdx(idx)
}
// Span tracking implementation
fn push_span(&mut self, span: codemap::Span) {
match self.spans.last_mut() {
// We do not need to insert the same span again, as this
// instruction was compiled from the same span as the last
// one.
Some(last) if last.span == span => last.count += 1,
// In all other cases, this is a new source span.
_ => self.spans.push(SourceSpan { span, count: 1 }),
}
}
/// Retrieve the [codemap::Span] from which the instruction at
/// `offset` was compiled.
pub fn get_span(&self, offset: CodeIdx) -> codemap::Span {
let mut pos = 0;
for span in &self.spans {
pos += span.count;
if pos > offset.0 {
return span.span;
}
}
panic!("compiler error: chunk missing span for offset {}", offset.0);
}
/// Retrieve the line from which the instruction at `offset` was
/// compiled in the specified codemap.
pub fn get_line(&self, codemap: &codemap::CodeMap, offset: CodeIdx) -> usize {
let span = self.get_span(offset);
// lines are 0-indexed in the codemap, but users probably want
// real line numbers
codemap.look_up_span(span).begin.line + 1
}
/// Write the disassembler representation of the operation at
/// `idx` to the specified writer.
pub fn disassemble_op<W: Write>(
&self,
writer: &mut W,
codemap: &CodeMap,
width: usize,
idx: CodeIdx,
) -> Result<(), std::io::Error> {
write!(writer, "{:#width$x}\t ", idx.0, width = width)?;
// Print continuation character if the previous operation was at
// the same line, otherwise print the line.
let line = self.get_line(codemap, idx);
if idx.0 > 0 && self.get_line(codemap, CodeIdx(idx.0 - 1)) == line {
write!(writer, " |\t")?;
} else {
write!(writer, "{:4}\t", line)?;
}
match self[idx] {
OpCode::OpConstant(idx) => writeln!(writer, "OpConstant({}@{})", self[idx], idx.0),
op => writeln!(writer, "{:?}", op),
}?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::test_utils::dummy_span;
use super::*;
#[test]
fn push_op() {
let mut chunk = Chunk::default();
chunk.push_op(OpCode::OpNull, dummy_span());
assert_eq!(chunk.code.last().unwrap(), &OpCode::OpNull);
}
}