Skip to content

Commit

Permalink
feat!: add debug instruction break
Browse files Browse the repository at this point in the history
- store debug information in the `Program`
- improve pretty-printing of `Program`
  • Loading branch information
jan-ferdinand committed Oct 16, 2023
2 parents aca8747 + a109b89 commit df6dc4b
Show file tree
Hide file tree
Showing 3 changed files with 372 additions and 150 deletions.
69 changes: 19 additions & 50 deletions triton-vm/src/instruction.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::fmt::Display;
use std::fmt::Formatter;
Expand Down Expand Up @@ -49,6 +48,8 @@ pub enum LabelledInstruction {

/// Labels look like "`<name>:`" and are translated into absolute addresses.
Label(String),

Breakpoint,
}

impl LabelledInstruction {
Expand Down Expand Up @@ -77,6 +78,7 @@ impl Display for LabelledInstruction {
match self {
LabelledInstruction::Instruction(instr) => write!(f, "{instr}"),
LabelledInstruction::Label(label_name) => write!(f, "{label_name}:"),
LabelledInstruction::Breakpoint => write!(f, "break"),
}
}
}
Expand Down Expand Up @@ -257,9 +259,10 @@ impl<Dest: PartialEq + Default> AnInstruction<Dest> {
((opcode >> bit_number) & 1).into()
}

fn map_call_address<F, NewDest: PartialEq + Default>(&self, f: F) -> AnInstruction<NewDest>
pub(crate) fn map_call_address<F, NewDest>(&self, f: F) -> AnInstruction<NewDest>
where
F: Fn(&Dest) -> NewDest,
NewDest: PartialEq + Default,
{
match self {
Pop => Pop,
Expand Down Expand Up @@ -407,6 +410,7 @@ impl Instruction {
Err(_) => None,
},
Swap(_) => match maybe_ord_16 {
Ok(ST0) => None,
Ok(ord_16) => Some(Swap(ord_16)),
Err(_) => None,
},
Expand Down Expand Up @@ -445,52 +449,13 @@ impl TryFrom<usize> for Instruction {
}
}

/// Convert a program with labels to a program with absolute addresses.
pub fn convert_all_labels_to_addresses(program: &[LabelledInstruction]) -> Vec<Instruction> {
let label_map = build_label_to_address_map(program);
program
.iter()
.flat_map(|instruction| convert_label_to_address_for_instruction(instruction, &label_map))
.collect()
}

pub(crate) fn build_label_to_address_map(program: &[LabelledInstruction]) -> HashMap<String, u64> {
use LabelledInstruction::*;

let mut label_map = HashMap::new();
let mut instruction_pointer = 0;
impl TryFrom<BFieldElement> for Instruction {
type Error = anyhow::Error;

for labelled_instruction in program.iter() {
match labelled_instruction {
Label(label) => match label_map.entry(label.clone()) {
Entry::Occupied(_) => panic!("Duplicate label: {label}"),
Entry::Vacant(entry) => {
entry.insert(instruction_pointer);
}
},
Instruction(instruction) => instruction_pointer += instruction.size() as u64,
}
fn try_from(opcode: BFieldElement) -> Result<Self> {
let opcode = u32::try_from(opcode)?;
opcode.try_into()
}
label_map
}

/// Convert a single instruction with a labelled call target to an instruction with an absolute
/// address as the call target. Discards all labels.
fn convert_label_to_address_for_instruction(
labelled_instruction: &LabelledInstruction,
label_map: &HashMap<String, u64>,
) -> Option<Instruction> {
let LabelledInstruction::Instruction(instruction) = labelled_instruction else {
return None;
};

let instruction_with_absolute_address = instruction.map_call_address(|label| {
label_map
.get(label)
.map(|&address| BFieldElement::new(address))
.unwrap_or_else(|| panic!("Label not found: {label}"))
});
Some(instruction_with_absolute_address)
}

const fn all_instructions_without_args() -> [AnInstruction<BFieldElement>; Instruction::COUNT] {
Expand Down Expand Up @@ -717,14 +682,18 @@ mod tests {

#[test]
fn change_arguments_of_various_instructions() {
let push = Instruction::Push(0_u64.into()).change_arg(7_u64.into());
let dup = Instruction::Dup(ST0).change_arg(1024_u64.into());
let swap = Instruction::Swap(ST0).change_arg(1337_u64.into());
let pop = Instruction::Pop.change_arg(7_u64.into());
let push = Push(0_u64.into()).change_arg(7_u64.into());
let dup = Dup(ST0).change_arg(1024_u64.into());
let swap = Swap(ST0).change_arg(1337_u64.into());
let swap_0 = Swap(ST0).change_arg(0_u64.into());
let swap_1 = Swap(ST0).change_arg(1_u64.into());
let pop = Pop.change_arg(7_u64.into());

assert!(push.is_some());
assert!(dup.is_none());
assert!(swap.is_none());
assert!(swap_0.is_none());
assert!(swap_1.is_some());
assert!(pop.is_none());
}

Expand Down
68 changes: 54 additions & 14 deletions triton-vm/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,13 @@ pub struct ParseError<'a> {
pub errors: VerboseError<&'a str>,
}

/// `InstructionToken` is either an instruction with a label, or a
/// label itself. It is intermediate object used in some middle
/// point of the compilation pipeline. You probably want
/// An intermediate object for the parsing / compilation pipeline. You probably want
/// [`LabelledInstruction`].
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum InstructionToken<'a> {
Instruction(AnInstruction<String>, &'a str),
Label(String, &'a str),
}

impl<'a> Display for InstructionToken<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
InstructionToken::Instruction(instr, _) => write!(f, "{instr}"),
InstructionToken::Label(label_name, _) => write!(f, "{label_name}:"),
}
}
Breakpoint(&'a str),
}

impl<'a> Display for ParseError<'a> {
Expand All @@ -60,6 +50,7 @@ impl<'a> InstructionToken<'a> {
match self {
InstructionToken::Instruction(_, token_str) => token_str,
InstructionToken::Label(_, token_str) => token_str,
InstructionToken::Breakpoint(token_str) => token_str,
}
}

Expand All @@ -68,6 +59,7 @@ impl<'a> InstructionToken<'a> {
match self {
Instruction(instr, _) => LabelledInstruction::Instruction(instr.to_owned()),
Label(label, _) => LabelledInstruction::Label(label.to_owned()),
Breakpoint(_) => LabelledInstruction::Breakpoint,
}
}
}
Expand Down Expand Up @@ -184,10 +176,9 @@ fn errors_for_labels_with_context(
/// error type, but we want `nom::error::VerboseError` as it allows `context()`.
type ParseResult<'input, Out> = IResult<&'input str, Out, VerboseError<&'input str>>;

///
pub fn tokenize(s: &str) -> ParseResult<Vec<InstructionToken>> {
let (s, _) = comment_or_whitespace0(s)?;
let (s, instructions) = many0(alt((label, labelled_instruction)))(s)?;
let (s, instructions) = many0(alt((label, labelled_instruction, breakpoint)))(s)?;
let (s, _) = context("expecting label, instruction or eof", eof)(s)?;

Ok((s, instructions))
Expand All @@ -213,6 +204,11 @@ fn label(label_s: &str) -> ParseResult<InstructionToken> {
Ok((s, InstructionToken::Label(addr, label_s)))
}

fn breakpoint(breakpoint_s: &str) -> ParseResult<InstructionToken> {
let (s, _) = token1("break")(breakpoint_s)?;
Ok((s, InstructionToken::Breakpoint(breakpoint_s)))
}

fn an_instruction(s: &str) -> ParseResult<AnInstruction<String>> {
// OpStack manipulation
let pop = instruction("pop", Pop);
Expand Down Expand Up @@ -1063,4 +1059,48 @@ pub(crate) mod tests {
let expected_instructions = vec![Instruction(Divine); DIGEST_LENGTH];
assert_eq!(expected_instructions, instructions);
}

#[test]
fn break_gets_turned_into_labelled_instruction() {
let instructions = triton_asm![break];
let expected_instructions = vec![Breakpoint];
assert_eq!(expected_instructions, instructions);
}

#[test]
fn break_does_not_propagate_to_full_program() {
let program = triton_program! { break halt break };
assert_eq!(1, program.len_bwords());
}

#[test]
fn printing_program_includes_debug_information() {
let source_code = "\
call foo\n\
break\n\
call bar\n\
halt\n\
foo:\n\
break\n\
call baz\n\
push 1\n\
nop\n\
return\n\
baz:\n\
hash\n\
return\n\
nop\n\
pop\n\
bar:\n\
divine\n\
skiz\n\
split\n\
break\n\
return\n\
";
let program = Program::from_code(source_code).unwrap();
let printed_program = format!("{program}");
assert_eq!(source_code, &printed_program);
println!("{program}");
}
}
Loading

0 comments on commit df6dc4b

Please sign in to comment.