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>, locals: Vec<Local>,
current_scope: Scope, current_scope: Scope,
scope_index: u8, block_index: u8,
} }
impl Chunk { impl Chunk {
@ -70,7 +70,7 @@ impl Chunk {
constants: Vec::new(), constants: Vec::new(),
locals: Vec::new(), locals: Vec::new(),
current_scope: Scope::default(), current_scope: Scope::default(),
scope_index: 0, block_index: 0,
} }
} }
@ -87,7 +87,7 @@ impl Chunk {
constants, constants,
locals, locals,
current_scope: Scope::default(), current_scope: Scope::default(),
scope_index: 0, block_index: 0,
} }
} }
@ -192,8 +192,8 @@ impl Chunk {
} }
pub fn begin_scope(&mut self) { pub fn begin_scope(&mut self) {
self.scope_index += 1; self.block_index += 1;
self.current_scope.index = self.scope_index; self.current_scope.block_index = self.block_index;
self.current_scope.depth += 1; self.current_scope.depth += 1;
} }
@ -201,9 +201,9 @@ impl Chunk {
self.current_scope.depth -= 1; self.current_scope.depth -= 1;
if self.current_scope.depth == 0 { if self.current_scope.depth == 0 {
self.current_scope.index = 0; self.current_scope.block_index = 0;
} else { } 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)] #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Local { pub struct Local {
/// The index of the identifier in the constants table.
pub identifier_index: u8, pub identifier_index: u8,
/// The expected type of the local's value.
pub r#type: Option<Type>, pub r#type: Option<Type>,
/// Whether the local is mutable.
pub is_mutable: bool, pub is_mutable: bool,
/// Scope where the variable was declared.
pub scope: Scope, pub scope: Scope,
/// Expected location of a local's value.
pub register_index: u8, pub register_index: u8,
} }
impl Local { impl Local {
/// Creates a new Local instance.
pub fn new( pub fn new(
identifier_index: u8, identifier_index: u8,
r#type: Option<Type>, 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)] #[derive(Debug, Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Scope { pub struct Scope {
/// The level of block nesting. /// Level of block nesting.
pub depth: u8, pub depth: u8,
/// The nth scope in the chunk. /// Index of the block in the chunk.
pub index: u8, pub block_index: u8,
} }
impl Scope { impl Scope {
pub fn new(index: u8, width: u8) -> Self { pub fn new(depth: u8, block_index: u8) -> Self {
Self { Self { depth, block_index }
depth: index,
index: width,
}
} }
pub fn contains(&self, other: &Self) -> bool { pub fn contains(&self, other: &Self) -> bool {
match self.depth.cmp(&other.depth) { match self.depth.cmp(&other.depth) {
Ordering::Less => false, Ordering::Less => false,
Ordering::Greater => self.index >= other.index, Ordering::Greater => self.block_index >= other.block_index,
Ordering::Equal => self.index == other.index, Ordering::Equal => self.block_index == other.block_index,
} }
} }
} }
impl Display for Scope { impl Display for Scope {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 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> { pub struct ChunkDisassembler<'a> {
output: String, output: String,
chunk: &'a Chunk, chunk: &'a Chunk,
source: Option<&'a str>, source: Option<&'a str>,
// Options
width: usize, width: usize,
styled: bool, styled: bool,
indent: usize, indent: usize,
@ -405,8 +423,8 @@ impl<'a> ChunkDisassembler<'a> {
const INSTRUCTION_HEADER: [&'static str; 4] = [ const INSTRUCTION_HEADER: [&'static str; 4] = [
"Instructions", "Instructions",
"------------", "------------",
"INDEX BYTECODE OPERATION INFO TYPE POSITION ", " i BYTECODE OPERATION INFO TYPE POSITION ",
"----- -------- ------------- ------------------------- --------- -----------", "--- -------- ------------- -------------------- --------------- ------------",
]; ];
const CONSTANT_HEADER: [&'static str; 4] = [ const CONSTANT_HEADER: [&'static str; 4] = [
@ -419,8 +437,8 @@ impl<'a> ChunkDisassembler<'a> {
const LOCAL_HEADER: [&'static str; 4] = [ const LOCAL_HEADER: [&'static str; 4] = [
"Locals", "Locals",
"------", "------",
"INDEX IDENTIFIER TYPE MUTABLE SCOPE REGISTER", "INDEX IDENTIFIER TYPE MUTABLE SCOPE REGISTER",
"----- ---------- -------- ------- ------- --------", "----- ---------- ---------- ------- ------- --------",
]; ];
/// The default width of the disassembly output. To correctly align the output, this should /// 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); self.push(border, false, false, false, false);
} }
fn push_empty(&mut self) {
self.push("", false, false, false, true);
}
pub fn disassemble(mut self) -> String { pub fn disassemble(mut self) -> String {
let top_border = "".to_string() + &"".repeat(self.width - 2) + ""; let top_border = "".to_string() + &"".repeat(self.width - 2) + "";
let section_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_border(&top_border);
self.push_header(&name_display); 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!( let info_line = format!(
"{} instructions, {} constants, {} locals, returns {}", "{} instructions, {} constants, {} locals, returns {}",
self.chunk.instructions.len(), self.chunk.instructions.len(),
@ -587,24 +620,33 @@ impl<'a> ChunkDisassembler<'a> {
.unwrap_or("none".to_string()) .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 { for line in &Self::INSTRUCTION_HEADER {
self.push_header(line); self.push_header(line);
} }
for (index, (instruction, position)) in self.chunk.instructions.iter().enumerate() { 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 operation = instruction.operation().to_string();
let info = instruction.disassembly_info(self.chunk); let info = instruction.disassembly_info(self.chunk);
let type_display = instruction let type_display = instruction
.yielded_type(self.chunk) .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)); .unwrap_or(String::with_capacity(0));
let position = position.to_string(); let position = position.to_string();
let instruction_display = format!( 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); self.push_details(&instruction_display);
@ -635,10 +677,18 @@ impl<'a> ChunkDisassembler<'a> {
.unwrap_or_else(|| "unknown".to_string()); .unwrap_or_else(|| "unknown".to_string());
let type_display = r#type let type_display = r#type
.as_ref() .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()); .unwrap_or("unknown".to_string());
let local_display = format!( 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); 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 std::{iter::Map, slice::Iter};
use crate::{Instruction, Operation, Span}; use crate::{Instruction, Operation, Span};
@ -6,24 +7,24 @@ type MapToOperation = fn(&(Instruction, Span)) -> Operation;
type OperationIter<'iter> = Map<Iter<'iter, (Instruction, Span)>, MapToOperation>; type OperationIter<'iter> = Map<Iter<'iter, (Instruction, Span)>, MapToOperation>;
/// Performs optimizations on a subset of instructions.
pub fn optimize(instructions: &mut [(Instruction, Span)]) -> usize { pub fn optimize(instructions: &mut [(Instruction, Span)]) -> usize {
Optimizer::new(instructions).optimize() Optimizer::new(instructions).optimize()
} }
/// An instruction optimizer that mutably borrows instructions from a chunk.
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)] #[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct Optimizer<'chunk> { pub struct Optimizer<'chunk> {
instructions: &'chunk mut [(Instruction, Span)], instructions: &'chunk mut [(Instruction, Span)],
} }
impl<'chunk> Optimizer<'chunk> { 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 { pub fn new(instructions: &'chunk mut [(Instruction, Span)]) -> Self {
Self { instructions } Self { instructions }
} }
pub fn set_instructions(&mut self, instructions: &'chunk mut [(Instruction, Span)]) { /// Potentially mutates the instructions to optimize them.
self.instructions = instructions;
}
pub fn optimize(&mut self) -> usize { pub fn optimize(&mut self) -> usize {
let mut optimizations = 0; let mut optimizations = 0;
@ -44,6 +45,13 @@ impl<'chunk> Optimizer<'chunk> {
optimizations 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) { fn optimize_comparison(&mut self) {
log::debug!("Optimizing comparison"); log::debug!("Optimizing comparison");

View File

@ -23,7 +23,16 @@ fn equality_assignment_long() {
(Instruction::r#return(true), Span(44, 44)), (Instruction::r#return(true), Span(44, 44)),
], ],
vec![Value::integer(4), Value::string("a")], 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();
}
}