From 92d49b648c5846db474c882bd320df09c0624cd2 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Fri, 3 Nov 2023 10:48:13 +0100 Subject: [PATCH] refactor: turn python script for computing opcodes into a rust test --- opcodes.py | 111 ----------------------- triton-vm/src/instruction.rs | 116 +++++++++++++++++++------ triton-vm/src/parser.rs | 2 +- triton-vm/src/table/processor_table.rs | 2 +- triton-vm/src/vm.rs | 2 +- 5 files changed, 93 insertions(+), 140 deletions(-) delete mode 100644 opcodes.py diff --git a/opcodes.py b/opcodes.py deleted file mode 100644 index e97a80f5..00000000 --- a/opcodes.py +++ /dev/null @@ -1,111 +0,0 @@ -import functools -from enum import IntEnum, IntFlag, auto - -class Instruction(IntEnum): - Halt = 0 - Pop = auto() - Push = auto() - Divine = auto() - Dup = auto() - Swap = auto() - Nop = auto() - Skiz = auto() - Call = auto() - Return = auto() - Recurse = auto() - Assert = auto() - ReadMem = auto() - WriteMem = auto() - Hash = auto() - DivineSibling = auto() - AssertVector = auto() - AbsorbInit = auto() - Absorb = auto() - Squeeze = auto() - Add = auto() - Mul = auto() - Invert = auto() - Eq = auto() - Split = auto() - Lt = auto() - And = auto() - Xor = auto() - Log2Floor = auto() - Pow = auto() - Div = auto() - PopCount = auto() - XxAdd = auto() - XxMul = auto() - XInvert = auto() - XbMul = auto() - ReadIo = auto() - WriteIo = auto() - -class InstructionBucket(IntFlag): - HasArg = auto() - ShrinkStack = auto() - U32 = auto() - -# === - -def in_bucket(instruction_bucket, instruction): - if instruction_bucket == InstructionBucket.HasArg: - return instruction in [Instruction.Pop, Instruction.Push, Instruction.Divine, - Instruction.Dup, Instruction.Swap, Instruction.Call] - if instruction_bucket == InstructionBucket.ShrinkStack: - return instruction in [Instruction.Pop, Instruction.Skiz, Instruction.Assert, Instruction.WriteMem, Instruction.WriteIo, - Instruction.Add, Instruction.Mul, Instruction.Eq, Instruction.XbMul, Instruction.Hash, - Instruction.Lt, Instruction.And, Instruction.Xor, Instruction.Pow] - if instruction_bucket == InstructionBucket.U32: - return instruction in [Instruction.Lt, Instruction.And, Instruction.Xor, Instruction.Log2Floor, - Instruction.Pow, Instruction.Div, Instruction.Split, Instruction.PopCount] - return False - -def flag_set(instruction): - instruction_flags = [bucket for bucket in InstructionBucket if in_bucket(bucket, instruction)] - return functools.reduce(lambda x, y: x | y, instruction_flags, 0) - -def opcode(instruction): - instruction_flag_set = flag_set(instruction) - index_within_flag_set = 0 - for inst in Instruction: - if inst < instruction and instruction_flag_set == flag_set(inst): - index_within_flag_set += 1 - return index_within_flag_set * 2**len(InstructionBucket) + instruction_flag_set - -def print_all_opcodes(): - for instruction in Instruction: - opc = opcode(instruction) - print(f"{opc:> 4} {opc:>08b} {str(instruction)[12:]}") - -def print_max_opcode(): - max_opc = max([opcode(instruction) for instruction in Instruction]) - print(f"highest opcode: {max_opc}") - print(f"#ibs: {len(bin(max_opc)[2:])}") - - -# === - -def opcodes_are_unique_test(): - get_indices = lambda x, xs: [i for (y, i) in zip(xs, range(len(xs))) if x == y] - - all_opcodes = [opcode(instruction) for instruction in Instruction] - for op_code in all_opcodes: - instruction_idcs = get_indices(op_code, all_opcodes) - if len(instruction_idcs) != 1: - print(f"Opcode {op_code} is assigned to instructions:") - for idx in instruction_idcs: - print(f" {Instruction[idx]}") - - assert(sorted(list(set(all_opcodes))) == sorted(list(all_opcodes))) - -def tests(): - opcodes_are_unique_test() - -# === - -tests() - -print_all_opcodes() -print() -print_max_opcode() diff --git a/triton-vm/src/instruction.rs b/triton-vm/src/instruction.rs index 28309da1..df5634ec 100644 --- a/triton-vm/src/instruction.rs +++ b/triton-vm/src/instruction.rs @@ -105,13 +105,13 @@ pub enum AnInstruction { Swap(OpStackElement), // Control flow + Halt, Nop, Skiz, Call(Dest), Return, Recurse, Assert, - Halt, // Memory access ReadMem, @@ -161,13 +161,13 @@ impl AnInstruction { Divine(_) => 9, Dup(_) => 17, Swap(_) => 25, + Halt => 0, Nop => 8, Skiz => 2, Call(_) => 33, Return => 16, Recurse => 24, Assert => 10, - Halt => 0, ReadMem => 32, WriteMem => 18, Hash => 26, @@ -204,13 +204,13 @@ impl AnInstruction { Divine(_) => "divine", Dup(_) => "dup", Swap(_) => "swap", + Halt => "halt", Nop => "nop", Skiz => "skiz", Call(_) => "call", Return => "return", Recurse => "recurse", Assert => "assert", - Halt => "halt", ReadMem => "read_mem", WriteMem => "write_mem", Hash => "hash", @@ -273,13 +273,13 @@ impl AnInstruction { Divine(x) => Divine(*x), Dup(x) => Dup(*x), Swap(x) => Swap(*x), + Halt => Halt, Nop => Nop, Skiz => Skiz, Call(label) => Call(f(label)), Return => Return, Recurse => Recurse, Assert => Assert, - Halt => Halt, ReadMem => ReadMem, WriteMem => WriteMem, Hash => Hash, @@ -328,13 +328,13 @@ impl AnInstruction { Divine(n) => n.index() as i32, Dup(_) => 1, Swap(_) => 0, + Halt => 0, Nop => 0, Skiz => -1, Call(_) => 0, Return => 0, Recurse => 0, Assert => -1, - Halt => 0, ReadMem => 1, WriteMem => -1, Hash => -5, @@ -483,13 +483,13 @@ const fn all_instructions_without_args() -> [AnInstruction; Instr Divine(ST0), Dup(ST0), Swap(ST0), + Halt, Nop, Skiz, Call(BFIELD_ZERO), Return, Recurse, Assert, - Halt, ReadMem, WriteMem, Hash, @@ -605,6 +605,88 @@ mod tests { use crate::NonDeterminism; use crate::Program; + #[derive(Debug, Clone, Copy, EnumCount, EnumIter)] + enum InstructionBucket { + HasArg, + ShrinksStack, + IsU32, + } + + impl InstructionBucket { + fn contains(&self, instruction: Instruction) -> bool { + match self { + InstructionBucket::HasArg => instruction.has_arg(), + InstructionBucket::ShrinksStack => instruction.shrinks_op_stack(), + InstructionBucket::IsU32 => instruction.is_u32_instruction(), + } + } + + fn bit_flag(&self) -> u32 { + match self { + InstructionBucket::HasArg => 0b001, + InstructionBucket::ShrinksStack => 0b010, + InstructionBucket::IsU32 => 0b100, + } + } + } + + impl Instruction { + #[must_use] + fn replace_default_argument_if_illegal(self) -> Self { + match self { + Pop(ST0) => Pop(ST1), + Divine(ST0) => Divine(ST1), + Swap(ST0) => Swap(ST1), + _ => self, + } + } + + fn flag_set(self) -> u32 { + InstructionBucket::iter() + .map(|bucket| match bucket.contains(self) { + true => bucket.bit_flag(), + false => 0, + }) + .fold(0, |x, y| x | y) + } + + fn computed_opcode(self) -> u32 { + let mut index_within_flag_set = 0; + for other_instruction in Instruction::iter() { + let other_instruction = other_instruction.replace_default_argument_if_illegal(); + if other_instruction == self { + break; + } + if other_instruction.flag_set() == self.flag_set() { + index_within_flag_set += 1; + } + } + + index_within_flag_set * 2_u32.pow(InstructionBucket::COUNT as u32) + self.flag_set() + } + } + + #[test] + fn computed_and_actual_opcodes_are_identical() { + for instruction in Instruction::iter() { + let instruction = instruction.replace_default_argument_if_illegal(); + println!( + "{opcode: >3} {name}", + opcode = instruction.computed_opcode(), + name = instruction.name() + ) + } + + for instruction in Instruction::iter() { + let instruction = instruction.replace_default_argument_if_illegal(); + assert_eq!( + instruction.computed_opcode(), + instruction.opcode(), + "{instruction}" + ); + } + } + #[test] fn opcodes_are_unique() { let mut opcodes_to_instruction_map = HashMap::new(); @@ -672,13 +754,6 @@ mod tests { } } - #[test] - fn print_all_instructions_and_opcodes() { - for instr in ALL_INSTRUCTIONS { - println!("{:>3} {: <10}", instr.opcode(), format!("{}", instr.name())); - } - } - #[test] /// Serves no other purpose than to increase code coverage results. fn run_constant_methods() { @@ -750,7 +825,7 @@ mod tests { fn opcodes_are_consistent_with_shrink_stack_indication_bit() { let shrink_stack_indicator_bit_mask = 2; for instruction in Instruction::iter() { - let instruction = replace_illegal_arguments_in_instruction(instruction); + let instruction = instruction.replace_default_argument_if_illegal(); let opcode = instruction.opcode(); println!("Testing instruction {instruction} with opcode {opcode}."); assert_eq!( @@ -800,7 +875,7 @@ mod tests { #[test] fn instructions_act_on_op_stack_as_indicated() { for test_instruction in all_instructions_without_args() { - let test_instruction = replace_illegal_arguments_in_instruction(test_instruction); + let test_instruction = test_instruction.replace_default_argument_if_illegal(); let (program, stack_size_before_test_instruction) = construct_test_program_for_instruction(test_instruction); let stack_size_after_test_instruction = terminal_op_stack_size_for_program(program); @@ -815,17 +890,6 @@ mod tests { } } - fn replace_illegal_arguments_in_instruction( - instruction: AnInstruction, - ) -> AnInstruction { - match instruction { - Pop(ST0) => Pop(ST3), - Divine(ST0) => Divine(ST1), - Swap(ST0) => Swap(ST1), - _ => instruction, - } - } - fn construct_test_program_for_instruction( instruction: AnInstruction, ) -> (Program, usize) { diff --git a/triton-vm/src/parser.rs b/triton-vm/src/parser.rs index a5d42508..c476f12f 100644 --- a/triton-vm/src/parser.rs +++ b/triton-vm/src/parser.rs @@ -220,13 +220,13 @@ fn an_instruction(s: &str) -> ParseResult> { let opstack_manipulation = alt((pop, push, dup, swap)); // Control flow + let halt = instruction("halt", Halt); let nop = instruction("nop", Nop); let skiz = instruction("skiz", Skiz); let call = call_instruction(); let return_ = instruction("return", Return); let recurse = instruction("recurse", Recurse); let assert_ = instruction("assert", Assert); - let halt = instruction("halt", Halt); let control_flow = alt((nop, skiz, call, return_, recurse, halt)); diff --git a/triton-vm/src/table/processor_table.rs b/triton-vm/src/table/processor_table.rs index 15ed0ead..865f9a68 100644 --- a/triton-vm/src/table/processor_table.rs +++ b/triton-vm/src/table/processor_table.rs @@ -2259,13 +2259,13 @@ impl ExtProcessorTable { Divine(_) => ExtProcessorTable::instruction_divine(circuit_builder), Dup(_) => ExtProcessorTable::instruction_dup(circuit_builder), Swap(_) => ExtProcessorTable::instruction_swap(circuit_builder), + Halt => ExtProcessorTable::instruction_halt(circuit_builder), Nop => ExtProcessorTable::instruction_nop(circuit_builder), Skiz => ExtProcessorTable::instruction_skiz(circuit_builder), Call(_) => ExtProcessorTable::instruction_call(circuit_builder), Return => ExtProcessorTable::instruction_return(circuit_builder), Recurse => ExtProcessorTable::instruction_recurse(circuit_builder), Assert => ExtProcessorTable::instruction_assert(circuit_builder), - Halt => ExtProcessorTable::instruction_halt(circuit_builder), ReadMem => ExtProcessorTable::instruction_read_mem(circuit_builder), WriteMem => ExtProcessorTable::instruction_write_mem(circuit_builder), Hash => ExtProcessorTable::instruction_hash(circuit_builder), diff --git a/triton-vm/src/vm.rs b/triton-vm/src/vm.rs index 7a3578c7..aafa9c7c 100644 --- a/triton-vm/src/vm.rs +++ b/triton-vm/src/vm.rs @@ -206,13 +206,13 @@ impl<'pgm> VMState<'pgm> { Divine(n) => self.divine(n)?, Dup(stack_element) => self.dup(stack_element), Swap(stack_element) => self.swap(stack_element)?, + Halt => self.halt(), Nop => self.nop(), Skiz => self.skiz()?, Call(address) => self.call(address), Return => self.return_from_call()?, Recurse => self.recurse()?, Assert => self.assert()?, - Halt => self.halt(), ReadMem => self.read_mem(), WriteMem => self.write_mem()?, Hash => self.hash()?,