1
0

Write docs; Improve disassembly output

This commit is contained in:
Jeff 2024-11-06 17:59:16 -05:00
parent dc9b451b37
commit a997665d1a
4 changed files with 112 additions and 33 deletions

View File

@ -58,7 +58,7 @@ pub struct Chunk {
locals: Vec<Local>,
current_scope: Scope,
scope_index: u8,
block_index: u8,
}
impl Chunk {
@ -70,7 +70,7 @@ impl Chunk {
constants: Vec::new(),
locals: Vec::new(),
current_scope: Scope::default(),
scope_index: 0,
block_index: 0,
}
}
@ -87,7 +87,7 @@ impl Chunk {
constants,
locals,
current_scope: Scope::default(),
scope_index: 0,
block_index: 0,
}
}
@ -192,8 +192,8 @@ impl Chunk {
}
pub fn begin_scope(&mut self) {
self.scope_index += 1;
self.current_scope.index = self.scope_index;
self.block_index += 1;
self.current_scope.block_index = self.block_index;
self.current_scope.depth += 1;
}
@ -201,9 +201,9 @@ impl Chunk {
self.current_scope.depth -= 1;
if self.current_scope.depth == 0 {
self.current_scope.index = 0;
self.current_scope.block_index = 0;
} else {
self.current_scope.index -= 1;
self.current_scope.block_index -= 1;
}
}
@ -334,16 +334,27 @@ impl PartialEq for Chunk {
}
}
/// A scoped variable.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Local {
/// The index of the identifier in the constants table.
pub identifier_index: u8,
/// The expected type of the local's value.
pub r#type: Option<Type>,
/// Whether the local is mutable.
pub is_mutable: bool,
/// Scope where the variable was declared.
pub scope: Scope,
/// Expected location of a local's value.
pub register_index: u8,
}
impl Local {
/// Creates a new Local instance.
pub fn new(
identifier_index: u8,
r#type: Option<Type>,
@ -361,41 +372,48 @@ impl Local {
}
}
/// Variable locality, as defined by its depth and block index.
///
/// The `block index` is a unique identifier for a block within a chunk. It is used to differentiate
/// between blocks that are not nested together but have the same depth, i.e. sibling scopes. If the
/// `block_index` is 0, then the scope is the root scope of the chunk. The `block_index` is always 0
/// when the `depth` is 0. See [Chunk::begin_scope][] and [Chunk::end_scope][] to see how scopes are
/// incremented and decremented.
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Scope {
/// The level of block nesting.
/// Level of block nesting.
pub depth: u8,
/// The nth scope in the chunk.
pub index: u8,
/// Index of the block in the chunk.
pub block_index: u8,
}
impl Scope {
pub fn new(index: u8, width: u8) -> Self {
Self {
depth: index,
index: width,
}
pub fn new(depth: u8, block_index: u8) -> Self {
Self { depth, block_index }
}
pub fn contains(&self, other: &Self) -> bool {
match self.depth.cmp(&other.depth) {
Ordering::Less => false,
Ordering::Greater => self.index >= other.index,
Ordering::Equal => self.index == other.index,
Ordering::Greater => self.block_index >= other.block_index,
Ordering::Equal => self.block_index == other.block_index,
}
}
}
impl Display for Scope {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.depth, self.index)
write!(f, "({}, {})", self.depth, self.block_index)
}
}
/// Builder that constructs a human-readable representation of a chunk.
pub struct ChunkDisassembler<'a> {
output: String,
chunk: &'a Chunk,
source: Option<&'a str>,
// Options
width: usize,
styled: bool,
indent: usize,
@ -405,8 +423,8 @@ impl<'a> ChunkDisassembler<'a> {
const INSTRUCTION_HEADER: [&'static str; 4] = [
"Instructions",
"------------",
"INDEX BYTECODE OPERATION INFO TYPE POSITION ",
"----- -------- ------------- ------------------------- --------- -----------",
" i BYTECODE OPERATION INFO TYPE POSITION ",
"--- -------- ------------- -------------------- --------------- ------------",
];
const CONSTANT_HEADER: [&'static str; 4] = [
@ -420,7 +438,7 @@ impl<'a> ChunkDisassembler<'a> {
"Locals",
"------",
"INDEX IDENTIFIER TYPE MUTABLE SCOPE REGISTER",
"----- ---------- -------- ------- ------- --------",
"----- ---------- ---------- ------- ------- --------",
];
/// The default width of the disassembly output. To correctly align the output, this should
@ -558,6 +576,10 @@ impl<'a> ChunkDisassembler<'a> {
self.push(border, false, false, false, false);
}
fn push_empty(&mut self) {
self.push("", false, false, false, true);
}
pub fn disassemble(mut self) -> String {
let top_border = "".to_string() + &"".repeat(self.width - 2) + "";
let section_border = "".to_string() + &"".repeat(self.width - 2) + "";
@ -576,6 +598,17 @@ impl<'a> ChunkDisassembler<'a> {
self.push_border(&top_border);
self.push_header(&name_display);
if let Some(source) = self.source {
self.push_empty();
self.push_details(
&source
.replace(" ", "")
.replace("\n\n", " ")
.replace('\n', " "),
);
self.push_empty();
}
let info_line = format!(
"{} instructions, {} constants, {} locals, returns {}",
self.chunk.instructions.len(),
@ -587,24 +620,33 @@ impl<'a> ChunkDisassembler<'a> {
.unwrap_or("none".to_string())
);
self.push(&info_line, true, false, false, true);
self.push(&info_line, true, false, true, true);
self.push_empty();
for line in &Self::INSTRUCTION_HEADER {
self.push_header(line);
}
for (index, (instruction, position)) in self.chunk.instructions.iter().enumerate() {
let bytecode = u32::from(instruction);
let bytecode = format!("{:02X}", u32::from(instruction));
let operation = instruction.operation().to_string();
let info = instruction.disassembly_info(self.chunk);
let type_display = instruction
.yielded_type(self.chunk)
.map(|r#type| r#type.to_string())
.map(|r#type| {
let type_string = r#type.to_string();
if type_string.len() > 15 {
format!("{type_string:.12}...")
} else {
type_string
}
})
.unwrap_or(String::with_capacity(0));
let position = position.to_string();
let instruction_display = format!(
"{index:<5} {bytecode:08X} {operation:13} {info:25} {type_display:9} {position:11}"
"{index:^3} {bytecode:>8} {operation:13} {info:20} {type_display:^15} {position:12}"
);
self.push_details(&instruction_display);
@ -635,10 +677,18 @@ impl<'a> ChunkDisassembler<'a> {
.unwrap_or_else(|| "unknown".to_string());
let type_display = r#type
.as_ref()
.map(|r#type| r#type.to_string())
.map(|r#type| {
let type_string = r#type.to_string();
if type_string.len() > 10 {
format!("{type_string:.7}...")
} else {
type_string
}
})
.unwrap_or("unknown".to_string());
let local_display = format!(
"{index:<5} {identifier_display:10} {type_display:8} {mutable:7} {scope:7} {register_index:8}"
"{index:<5} {identifier_display:10} {type_display:10} {mutable:7} {scope:7} {register_index:8}"
);
self.push_details(&local_display);

View File

@ -1,3 +1,4 @@
//! Tools used by the compiler to optimize a chunk's bytecode.
use std::{iter::Map, slice::Iter};
use crate::{Instruction, Operation, Span};
@ -6,24 +7,24 @@ type MapToOperation = fn(&(Instruction, Span)) -> Operation;
type OperationIter<'iter> = Map<Iter<'iter, (Instruction, Span)>, MapToOperation>;
/// Performs optimizations on a subset of instructions.
pub fn optimize(instructions: &mut [(Instruction, Span)]) -> usize {
Optimizer::new(instructions).optimize()
}
/// An instruction optimizer that mutably borrows instructions from a chunk.
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct Optimizer<'chunk> {
instructions: &'chunk mut [(Instruction, Span)],
}
impl<'chunk> Optimizer<'chunk> {
/// Creates a new optimizer with a mutable reference to some of a chunk's instructions.
pub fn new(instructions: &'chunk mut [(Instruction, Span)]) -> Self {
Self { instructions }
}
pub fn set_instructions(&mut self, instructions: &'chunk mut [(Instruction, Span)]) {
self.instructions = instructions;
}
/// Potentially mutates the instructions to optimize them.
pub fn optimize(&mut self) -> usize {
let mut optimizations = 0;
@ -44,6 +45,13 @@ impl<'chunk> Optimizer<'chunk> {
optimizations
}
/// Optimizes a comparison operation.
///
/// The instructions must be in the following order:
/// - `Operation::Equal | Operation::Less | Operation::LessEqual`
/// - `Operation::Jump`
/// - `Operation::LoadBoolean | Operation::LoadConstant`
/// - `Operation::LoadBoolean | Operation::LoadConstant`
fn optimize_comparison(&mut self) {
log::debug!("Optimizing comparison");

View File

@ -23,7 +23,16 @@ fn equality_assignment_long() {
(Instruction::r#return(true), Span(44, 44)),
],
vec![Value::integer(4), Value::string("a")],
vec![Local::new(1, None, false, Scope { depth: 0, index: 0 }, 0)]
vec![Local::new(
1,
None,
false,
Scope {
depth: 0,
block_index: 0
},
0
)]
)),
);

View File

@ -124,3 +124,15 @@ fn main() {
}
}
}
#[cfg(test)]
mod tests {
use clap::CommandFactory;
use super::*;
#[test]
fn verify_cli() {
Cli::command().debug_assert();
}
}