From aa6a976fcf55599ffdd5c48e16d637e900d26f78 Mon Sep 17 00:00:00 2001 From: fcarreiro Date: Fri, 6 Sep 2024 21:35:58 +0000 Subject: [PATCH] wip --- avm-transpiler/src/bit_traits.rs | 68 ++++++++ avm-transpiler/src/main.rs | 1 + avm-transpiler/src/opcodes.rs | 8 +- avm-transpiler/src/transpile.rs | 17 +- avm-transpiler/src/utils.rs | 13 +- .../vm/avm/tests/execution.test.cpp | 20 +-- .../vm/avm/trace/deserialization.cpp | 3 +- .../barretenberg/vm/avm/trace/execution.cpp | 11 +- .../barretenberg/vm/avm/trace/fixed_gas.cpp | 3 +- .../src/barretenberg/vm/avm/trace/opcode.cpp | 6 +- .../src/barretenberg/vm/avm/trace/opcode.hpp | 3 +- .../src/barretenberg/vm/avm/trace/trace.cpp | 3 +- yarn-project/simulator/src/avm/avm_gas.ts | 6 +- .../simulator/src/avm/bytecode_utils.ts | 4 +- .../simulator/src/avm/opcodes/instruction.ts | 60 ++++--- .../simulator/src/avm/opcodes/memory.test.ts | 12 +- .../simulator/src/avm/opcodes/memory.ts | 25 +-- .../bytecode_serialization.test.ts | 4 +- .../serialization/bytecode_serialization.ts | 148 +++++++++--------- .../instruction_serialization.test.ts | 4 +- .../instruction_serialization.ts | 9 +- 21 files changed, 283 insertions(+), 145 deletions(-) create mode 100644 avm-transpiler/src/bit_traits.rs diff --git a/avm-transpiler/src/bit_traits.rs b/avm-transpiler/src/bit_traits.rs new file mode 100644 index 00000000000..77a0b7eb2f6 --- /dev/null +++ b/avm-transpiler/src/bit_traits.rs @@ -0,0 +1,68 @@ +use acvm::{AcirField, FieldElement}; + +fn get_msb(n: u128) -> usize { + let mut n = n; + let mut msb = 0; + while n > 0 { + n >>= 1; + msb += 1; + } + msb +} + +pub trait BitsQueryable { + fn num_bits(&self) -> usize; +} + +impl BitsQueryable for FieldElement { + fn num_bits(&self) -> usize { + AcirField::num_bits(self) as usize + } +} + +impl BitsQueryable for u8 { + fn num_bits(&self) -> usize { + get_msb(*self as u128) + } +} + +impl BitsQueryable for u16 { + fn num_bits(&self) -> usize { + get_msb(*self as u128) + } +} + +impl BitsQueryable for u32 { + fn num_bits(&self) -> usize { + get_msb(*self as u128) + } +} + +impl BitsQueryable for u64 { + fn num_bits(&self) -> usize { + get_msb(*self as u128) + } +} + +impl BitsQueryable for u128 { + fn num_bits(&self) -> usize { + get_msb(*self) + } +} + +pub fn bits_needed_for(val: &T) -> usize { + let num_bits = val.num_bits(); + if num_bits < 8 { + 8 + } else if num_bits < 16 { + 16 + } else if num_bits < 32 { + 32 + } else if num_bits < 64 { + 64 + } else if num_bits < 128 { + 128 + } else { + 254 + } +} diff --git a/avm-transpiler/src/main.rs b/avm-transpiler/src/main.rs index 384348fc463..27a848ac231 100644 --- a/avm-transpiler/src/main.rs +++ b/avm-transpiler/src/main.rs @@ -6,6 +6,7 @@ use std::env; use std::fs; use std::path::Path; +mod bit_traits; mod instructions; mod opcodes; mod transpile; diff --git a/avm-transpiler/src/opcodes.rs b/avm-transpiler/src/opcodes.rs index 36bcd8b8268..88c1330b296 100644 --- a/avm-transpiler/src/opcodes.rs +++ b/avm-transpiler/src/opcodes.rs @@ -1,6 +1,6 @@ /// All AVM opcodes /// Keep updated with TS, cpp, and docs protocol specs! -#[allow(clippy::upper_case_acronyms, dead_code)] +#[allow(clippy::upper_case_acronyms, dead_code, non_camel_case_types)] #[derive(PartialEq, Copy, Clone, Debug, Eq, Hash)] pub enum AvmOpcode { // Compute @@ -42,7 +42,8 @@ pub enum AvmOpcode { INTERNALRETURN, // Memory SET, - MOV, + MOV_8, + MOV_16, CMOV, // World state SLOAD, @@ -129,7 +130,8 @@ impl AvmOpcode { AvmOpcode::INTERNALRETURN => "INTERNALRETURN", // Machine State - Memory AvmOpcode::SET => "SET", - AvmOpcode::MOV => "MOV", + AvmOpcode::MOV_8 => "MOV_8", + AvmOpcode::MOV_16 => "MOV_16", AvmOpcode::CMOV => "CMOV", // World State diff --git a/avm-transpiler/src/transpile.rs b/avm-transpiler/src/transpile.rs index 16de12d9a3a..420efbab480 100644 --- a/avm-transpiler/src/transpile.rs +++ b/avm-transpiler/src/transpile.rs @@ -9,12 +9,13 @@ use acvm::brillig_vm::brillig::{ use acvm::{AcirField, FieldElement}; use noirc_errors::debug_info::DebugInfo; +use crate::bit_traits::bits_needed_for; use crate::instructions::{ AvmInstruction, AvmOperand, AvmTypeTag, ALL_DIRECT, FIRST_OPERAND_INDIRECT, SECOND_OPERAND_INDIRECT, ZEROTH_OPERAND_INDIRECT, }; use crate::opcodes::AvmOpcode; -use crate::utils::{dbg_print_avm_program, dbg_print_brillig_program}; +use crate::utils::{dbg_print_avm_program, dbg_print_brillig_program, make_operand}; /// Transpile a Brillig program to AVM bytecode pub fn brillig_to_avm( @@ -216,7 +217,7 @@ pub fn brillig_to_avm( // We are adding a MOV instruction that moves a value to itself. // This should therefore not affect the program's execution. avm_instrs.push(AvmInstruction { - opcode: AvmOpcode::MOV, + opcode: AvmOpcode::MOV_8, indirect: Some(ALL_DIRECT), operands: vec![AvmOperand::U32 { value: 0x18ca }, AvmOperand::U32 { value: 0x18ca }], ..Default::default() @@ -741,10 +742,18 @@ fn generate_cast_instruction( /// Generates an AVM MOV instruction. fn generate_mov_instruction(indirect: Option, source: u32, dest: u32) -> AvmInstruction { + let bits_needed = [source, dest].iter().map(bits_needed_for).max().unwrap(); + + let mov_opcode = match bits_needed { + 8 => AvmOpcode::MOV_8, + 16 => AvmOpcode::MOV_16, + _ => panic!("MOV operands must fit in 16 bits but needed {}", bits_needed), + }; + AvmInstruction { - opcode: AvmOpcode::MOV, + opcode: mov_opcode, indirect, - operands: vec![AvmOperand::U32 { value: source }, AvmOperand::U32 { value: dest }], + operands: vec![make_operand(bits_needed, &source), make_operand(bits_needed, &dest)], ..Default::default() } } diff --git a/avm-transpiler/src/utils.rs b/avm-transpiler/src/utils.rs index 982a8479546..cc440feda05 100644 --- a/avm-transpiler/src/utils.rs +++ b/avm-transpiler/src/utils.rs @@ -7,7 +7,7 @@ use log::{debug, info, trace}; use acvm::acir::brillig::Opcode as BrilligOpcode; use acvm::acir::circuit::{AssertionPayload, Opcode, Program}; -use crate::instructions::AvmInstruction; +use crate::instructions::{AvmInstruction, AvmOperand}; use crate::opcodes::AvmOpcode; /// Extract the Brillig program from its `Program` wrapper. @@ -90,3 +90,14 @@ pub fn dbg_print_avm_program(avm_program: &[AvmInstruction]) { debug!("\t{0:?}: {1}", opcode, count); } } + +pub fn make_operand + Copy>(bits: usize, value: &T) -> AvmOperand { + match bits { + 8 => AvmOperand::U8 { value: Into::::into(*value) as u8 }, + 16 => AvmOperand::U16 { value: Into::::into(*value) as u16 }, + 32 => AvmOperand::U32 { value: Into::::into(*value) as u32 }, + 64 => AvmOperand::U64 { value: Into::::into(*value) as u64 }, + 128 => AvmOperand::U128 { value: Into::::into(*value) }, + _ => panic!("Invalid operand size for bits: {}", bits), + } +} diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/tests/execution.test.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/tests/execution.test.cpp index 065e53c2f09..5956687061d 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/tests/execution.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/tests/execution.test.cpp @@ -587,10 +587,10 @@ TEST_F(AvmExecutionTests, movOpcode) "01" // U8 "13" // val 19 "000000AB" // dst_offset 171 - + to_hex(OpCode::MOV) + // opcode MOV + + to_hex(OpCode::MOV_8) + // opcode MOV "00" // Indirect flag - "000000AB" // src_offset 171 - "00000021" // dst_offset 33 + "AB" // src_offset 171 + "21" // dst_offset 33 + to_hex(OpCode::RETURN) + // opcode RETURN "00" // Indirect flag "00000000" // ret offset 0 @@ -613,9 +613,9 @@ TEST_F(AvmExecutionTests, movOpcode) // MOV EXPECT_THAT( instructions.at(1), - AllOf(Field(&Instruction::op_code, OpCode::MOV), + AllOf(Field(&Instruction::op_code, OpCode::MOV_8), Field(&Instruction::operands, - ElementsAre(VariantWith(0), VariantWith(171), VariantWith(33))))); + ElementsAre(VariantWith(0), VariantWith(171), VariantWith(33))))); auto trace = gen_trace_from_instr(instructions); @@ -701,10 +701,10 @@ TEST_F(AvmExecutionTests, indMovOpcode) "01" // U8 "FF" // val 255 "0000000A" // dst_offset 10 - + to_hex(OpCode::MOV) + // opcode MOV + + to_hex(OpCode::MOV_8) + // opcode MOV "01" // Indirect flag - "00000001" // src_offset 1 --> direct offset 10 - "00000002" // dst_offset 2 --> direct offset 11 + "01" // src_offset 1 --> direct offset 10 + "02" // dst_offset 2 --> direct offset 11 + to_hex(OpCode::RETURN) + // opcode RETURN "00" // Indirect flag "00000000" // ret offset 0 @@ -717,9 +717,9 @@ TEST_F(AvmExecutionTests, indMovOpcode) // MOV EXPECT_THAT(instructions.at(3), - AllOf(Field(&Instruction::op_code, OpCode::MOV), + AllOf(Field(&Instruction::op_code, OpCode::MOV_8), Field(&Instruction::operands, - ElementsAre(VariantWith(1), VariantWith(1), VariantWith(2))))); + ElementsAre(VariantWith(1), VariantWith(1), VariantWith(2))))); auto trace = gen_trace_from_instr(instructions); diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/deserialization.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/deserialization.cpp index 189409b2409..25c92733338 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/deserialization.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/deserialization.cpp @@ -88,7 +88,8 @@ const std::unordered_map> OPCODE_WIRE_FORMAT = // Machine State - Memory // OpCode::SET is handled differently - { OpCode::MOV, { OperandType::INDIRECT, OperandType::UINT32, OperandType::UINT32 } }, + { OpCode::MOV_8, { OperandType::INDIRECT, OperandType::UINT8, OperandType::UINT8 } }, + { OpCode::MOV_16, { OperandType::INDIRECT, OperandType::UINT16, OperandType::UINT16 } }, { OpCode::CMOV, { OperandType::INDIRECT, OperandType::UINT32, OperandType::UINT32, OperandType::UINT32, OperandType::UINT32 } }, diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp index 851c14dacb6..468329ff80b 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp @@ -616,10 +616,15 @@ std::vector Execution::gen_trace(std::vector const& instructio std::get(inst.operands.at(0)), val, std::get(inst.operands.at(3)), in_tag); break; } - case OpCode::MOV: + case OpCode::MOV_8: trace_builder.op_mov(std::get(inst.operands.at(0)), - std::get(inst.operands.at(1)), - std::get(inst.operands.at(2))); + std::get(inst.operands.at(1)), + std::get(inst.operands.at(2))); + break; + case OpCode::MOV_16: + trace_builder.op_mov(std::get(inst.operands.at(0)), + std::get(inst.operands.at(1)), + std::get(inst.operands.at(2))); break; case OpCode::CMOV: trace_builder.op_cmov(std::get(inst.operands.at(0)), diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/fixed_gas.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/fixed_gas.cpp index 749a913feb6..fa4dd2f515e 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/fixed_gas.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/fixed_gas.cpp @@ -52,7 +52,8 @@ const std::unordered_map GAS_COST_TABLE = { { OpCode::INTERNALCALL, make_cost(AVM_INTERNALCALL_BASE_L2_GAS, 0, AVM_INTERNALCALL_DYN_L2_GAS, 0) }, { OpCode::INTERNALRETURN, make_cost(AVM_INTERNALRETURN_BASE_L2_GAS, 0, AVM_INTERNALRETURN_DYN_L2_GAS, 0) }, { OpCode::SET, make_cost(AVM_SET_BASE_L2_GAS, 0, AVM_SET_DYN_L2_GAS, 0) }, - { OpCode::MOV, make_cost(AVM_MOV_BASE_L2_GAS, 0, AVM_MOV_DYN_L2_GAS, 0) }, + { OpCode::MOV_8, make_cost(AVM_MOV_BASE_L2_GAS, 0, AVM_MOV_DYN_L2_GAS, 0) }, + { OpCode::MOV_16, make_cost(AVM_MOV_BASE_L2_GAS, 0, AVM_MOV_DYN_L2_GAS, 0) }, { OpCode::CMOV, make_cost(AVM_CMOV_BASE_L2_GAS, 0, AVM_CMOV_DYN_L2_GAS, 0) }, { OpCode::SLOAD, make_cost(AVM_SLOAD_BASE_L2_GAS, 0, AVM_SLOAD_DYN_L2_GAS, 0) }, { OpCode::SSTORE, make_cost(AVM_SSTORE_BASE_L2_GAS, 0, AVM_SSTORE_DYN_L2_GAS, 0) }, diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/opcode.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/opcode.cpp index 5a80d3ec678..f359fdc1e05 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/opcode.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/opcode.cpp @@ -104,8 +104,10 @@ std::string to_string(OpCode opcode) // Machine State - Memory case OpCode::SET: return "SET"; - case OpCode::MOV: - return "MOV"; + case OpCode::MOV_8: + return "MOV_8"; + case OpCode::MOV_16: + return "MOV_16"; case OpCode::CMOV: return "CMOV"; // World State diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/opcode.hpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/opcode.hpp index bb880e22a41..963fd23831b 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/opcode.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/opcode.hpp @@ -68,7 +68,8 @@ enum class OpCode : uint8_t { INTERNALRETURN, // Machine State - Memory SET, - MOV, + MOV_8, + MOV_16, CMOV, // World State diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp index f85b3f6656e..b08a1bec6d4 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp @@ -1817,7 +1817,8 @@ void AvmTraceBuilder::op_mov(uint8_t indirect, uint32_t src_offset, uint32_t dst mem_trace_builder.write_into_memory(call_ptr, clk, IntermRegister::IC, direct_dst_offset, val, tag, tag); // Constrain gas cost - gas_trace_builder.constrain_gas(clk, OpCode::MOV); + // FIXME: not great that we are having to choose one specific opcode here! + gas_trace_builder.constrain_gas(clk, OpCode::MOV_8); main_trace.push_back(Row{ .main_clk = clk, diff --git a/yarn-project/simulator/src/avm/avm_gas.ts b/yarn-project/simulator/src/avm/avm_gas.ts index da55b3f8c8a..ef854cd1f53 100644 --- a/yarn-project/simulator/src/avm/avm_gas.ts +++ b/yarn-project/simulator/src/avm/avm_gas.ts @@ -91,7 +91,8 @@ const BaseGasCosts: Record = { [Opcode.INTERNALCALL]: makeCost(c.AVM_INTERNALCALL_BASE_L2_GAS, 0), [Opcode.INTERNALRETURN]: makeCost(c.AVM_INTERNALRETURN_BASE_L2_GAS, 0), [Opcode.SET]: makeCost(c.AVM_SET_BASE_L2_GAS, 0), - [Opcode.MOV]: makeCost(c.AVM_MOV_BASE_L2_GAS, 0), + [Opcode.MOV_8]: makeCost(c.AVM_MOV_BASE_L2_GAS, 0), + [Opcode.MOV_16]: makeCost(c.AVM_MOV_BASE_L2_GAS, 0), [Opcode.CMOV]: makeCost(c.AVM_CMOV_BASE_L2_GAS, 0), [Opcode.SLOAD]: makeCost(c.AVM_SLOAD_BASE_L2_GAS, 0), [Opcode.SSTORE]: makeCost(c.AVM_SSTORE_BASE_L2_GAS, 0), @@ -156,7 +157,8 @@ const DynamicGasCosts: Record = { [Opcode.INTERNALCALL]: makeCost(c.AVM_INTERNALCALL_DYN_L2_GAS, 0), [Opcode.INTERNALRETURN]: makeCost(c.AVM_INTERNALRETURN_DYN_L2_GAS, 0), [Opcode.SET]: makeCost(c.AVM_SET_DYN_L2_GAS, 0), - [Opcode.MOV]: makeCost(c.AVM_MOV_DYN_L2_GAS, 0), + [Opcode.MOV_8]: makeCost(c.AVM_MOV_DYN_L2_GAS, 0), + [Opcode.MOV_16]: makeCost(c.AVM_MOV_DYN_L2_GAS, 0), [Opcode.CMOV]: makeCost(c.AVM_CMOV_DYN_L2_GAS, 0), [Opcode.SLOAD]: makeCost(c.AVM_SLOAD_DYN_L2_GAS, 0), [Opcode.SSTORE]: makeCost(c.AVM_SSTORE_DYN_L2_GAS, 0), diff --git a/yarn-project/simulator/src/avm/bytecode_utils.ts b/yarn-project/simulator/src/avm/bytecode_utils.ts index 52b0f31032e..0cadac08075 100644 --- a/yarn-project/simulator/src/avm/bytecode_utils.ts +++ b/yarn-project/simulator/src/avm/bytecode_utils.ts @@ -1,10 +1,10 @@ import { promisify } from 'util'; import { gunzip } from 'zlib'; -import { Mov } from '../avm/opcodes/memory.js'; +import { Opcode } from './serialization/instruction_serialization.js'; const AVM_MAGIC_SUFFIX = Buffer.from([ - Mov.opcode, // opcode + Opcode.MOV_8, // opcode 0x00, // indirect ...Buffer.from('000018ca', 'hex'), // srcOffset ...Buffer.from('000018ca', 'hex'), // dstOffset diff --git a/yarn-project/simulator/src/avm/opcodes/instruction.ts b/yarn-project/simulator/src/avm/opcodes/instruction.ts index 3eb22810bbd..ae211a73acf 100644 --- a/yarn-project/simulator/src/avm/opcodes/instruction.ts +++ b/yarn-project/simulator/src/avm/opcodes/instruction.ts @@ -4,11 +4,11 @@ import type { AvmContext } from '../avm_context.js'; import { getBaseGasCost, getDynamicGasCost, mulGas, sumGas } from '../avm_gas.js'; import { type MemoryOperations } from '../avm_memory_types.js'; import { type BufferCursor } from '../serialization/buffer_cursor.js'; -import { Opcode, type OperandType, deserialize, serialize } from '../serialization/instruction_serialization.js'; +import { type Serializable } from '../serialization/bytecode_serialization.js'; +import { Opcode, type OperandType, deserialize, serializeAs } from '../serialization/instruction_serialization.js'; type InstructionConstructor = { new (...args: any[]): Instruction; - wireFormat?: OperandType[]; }; /** @@ -37,29 +37,51 @@ export abstract class Instruction { return instructionStr; } + // Default deserialization which uses Class.opcode and Class.wireFormat. + public static deserialize( + this: InstructionConstructor & { wireFormat: OperandType[]; as: any }, + buf: BufferCursor | Buffer, + ): Instruction { + return this.as(this.wireFormat).deserialize(buf); + } + + // Default serialization which uses Class.opcode and Class.wireFormat. + public serialize(): Buffer { + const klass = this.constructor as any; + assert(klass.opcode !== undefined && klass.opcode !== null); + assert(klass.wireFormat !== undefined && klass.wireFormat !== null); + return this.as(klass.opcode, klass.wireFormat).serialize(); + } + /** - * Serialize the instruction to a Buffer according to its wire format specified in its subclass. - * If you want to use this, your subclass should specify a {@code static wireFormat: OperandType[]}. - * @param this - The instruction to serialize. - * @returns The serialized instruction. + * Returns a new instruction instance that can be serialized with the given opcode and wire format. + * @param opcode The opcode of the instruction. + * @param wireFormat The wire format of the instruction. + * @returns The new instruction instance. */ - public serialize(this: any): Buffer { - assert(!!this.constructor.wireFormat, 'wireFormat must be defined on the class'); - return serialize(this.constructor.wireFormat, this); + public as(opcode: Opcode, wireFormat: OperandType[]): Instruction & Serializable { + return Object.defineProperty(this, 'serialize', { + value: (): Buffer => { + return serializeAs(wireFormat, opcode, this); + }, + enumerable: false, + }); } /** - * Deserializes a subclass of Instruction from a Buffer. - * If you want to use this, your subclass should specify a {@code static wireFormat: OperandType[]}. - * @param this Class object to deserialize to. - * @param buf Buffer to read from. - * @returns Constructed instance of Class. + * Returns a new instruction class that can be deserialized with the given opcode and wire format. + * @param opcode The opcode of the instruction. + * @param wireFormat The wire format of the instruction. + * @returns The new instruction class. */ - public static deserialize(this: InstructionConstructor, buf: BufferCursor | Buffer): Instruction { - assert(!!this.wireFormat, 'wireFormat must be defined on the instruction class'); - const res = deserialize(buf, this.wireFormat); - const args = res.slice(1); // Remove opcode. - return new this(...args); + public static as(this: InstructionConstructor, wireFormat: OperandType[]) { + return Object.assign(this, { + deserialize: (buf: BufferCursor | Buffer): Instruction => { + const res = deserialize(buf, wireFormat); + const args = res.slice(1); // Remove opcode. + return new this(...args); + }, + }); } /** diff --git a/yarn-project/simulator/src/avm/opcodes/memory.test.ts b/yarn-project/simulator/src/avm/opcodes/memory.test.ts index 798994b765c..80e60b4ca5d 100644 --- a/yarn-project/simulator/src/avm/opcodes/memory.test.ts +++ b/yarn-project/simulator/src/avm/opcodes/memory.test.ts @@ -4,6 +4,7 @@ import { type AvmContext } from '../avm_context.js'; import { Field, TypeTag, Uint8, Uint16, Uint32, Uint64, Uint128 } from '../avm_memory_types.js'; import { InstructionExecutionError } from '../errors.js'; import { adjustCalldataIndex, initContext, initExecutionEnvironment } from '../fixtures/index.js'; +import { Opcode } from '../serialization/instruction_serialization.js'; import { Addressing, AddressingMode } from './addressing_mode.js'; import { CMov, CalldataCopy, Cast, Mov, Set } from './memory.js'; @@ -302,12 +303,15 @@ describe('Memory instructions', () => { const buf = Buffer.from([ Mov.opcode, // opcode 0x01, // indirect - ...Buffer.from('12345678', 'hex'), // srcOffset - ...Buffer.from('3456789a', 'hex'), // dstOffset + ...Buffer.from('12', 'hex'), // srcOffset + ...Buffer.from('34', 'hex'), // dstOffset ]); - const inst = new Mov(/*indirect=*/ 0x01, /*srcOffset=*/ 0x12345678, /*dstOffset=*/ 0x3456789a); + const inst = new Mov(/*indirect=*/ 0x01, /*srcOffset=*/ 0x12, /*dstOffset=*/ 0x34).as( + Opcode.MOV_8, + Mov.wireFormat8, + ); - expect(Mov.deserialize(buf)).toEqual(inst); + expect(Mov.as(Mov.wireFormat8).deserialize(buf)).toEqual(inst); expect(inst.serialize()).toEqual(buf); }); diff --git a/yarn-project/simulator/src/avm/opcodes/memory.ts b/yarn-project/simulator/src/avm/opcodes/memory.ts index 9684d7e12a2..91d85884ed5 100644 --- a/yarn-project/simulator/src/avm/opcodes/memory.ts +++ b/yarn-project/simulator/src/avm/opcodes/memory.ts @@ -2,7 +2,7 @@ import type { AvmContext } from '../avm_context.js'; import { Field, TaggedMemory, TypeTag } from '../avm_memory_types.js'; import { InstructionExecutionError } from '../errors.js'; import { BufferCursor } from '../serialization/buffer_cursor.js'; -import { Opcode, OperandType, deserialize, serialize } from '../serialization/instruction_serialization.js'; +import { Opcode, OperandType, deserialize, serializeAs } from '../serialization/instruction_serialization.js'; import { Addressing } from './addressing_mode.js'; import { Instruction } from './instruction.js'; import { TwoOperandInstruction } from './instruction_impl.js'; @@ -51,11 +51,11 @@ export class Set extends Instruction { getOperandTypeFromInTag(this.inTag), ...Set.wireFormatAfterConst, ]; - return serialize(format, this); + return serializeAs(format, this.opcode, this); } /** We need to use a custom deserialize function because of the variable length of the value. */ - public static override deserialize(this: typeof Set, buf: BufferCursor | Buffer): Set { + public static override deserialize(buf: BufferCursor | Buffer): Set { if (buf instanceof Buffer) { buf = new BufferCursor(buf); } @@ -65,7 +65,7 @@ export class Set extends Instruction { const afterConst = deserialize(buf, Set.wireFormatAfterConst); const res = [...beforeConst, ...val, ...afterConst]; const args = res.slice(1) as ConstructorParameters; // Remove opcode. - return new this(...args); + return new Set(...args); } public async execute(context: AvmContext): Promise { @@ -161,13 +161,20 @@ export class Cast extends TwoOperandInstruction { export class Mov extends Instruction { static readonly type: string = 'MOV'; - static readonly opcode: Opcode = Opcode.MOV; - // Informs (de)serialization. See Instruction.deserialize. - static readonly wireFormat: OperandType[] = [ + // FIXME: This is needed for gas. + static readonly opcode: Opcode = Opcode.MOV_8; + + static readonly wireFormat8: OperandType[] = [ OperandType.UINT8, OperandType.UINT8, - OperandType.UINT32, - OperandType.UINT32, + OperandType.UINT8, + OperandType.UINT8, + ]; + static readonly wireFormat16: OperandType[] = [ + OperandType.UINT8, + OperandType.UINT8, + OperandType.UINT16, + OperandType.UINT16, ]; constructor(private indirect: number, private srcOffset: number, private dstOffset: number) { diff --git a/yarn-project/simulator/src/avm/serialization/bytecode_serialization.test.ts b/yarn-project/simulator/src/avm/serialization/bytecode_serialization.test.ts index 4dd26fb13dd..b5dd46cf4cc 100644 --- a/yarn-project/simulator/src/avm/serialization/bytecode_serialization.test.ts +++ b/yarn-project/simulator/src/avm/serialization/bytecode_serialization.test.ts @@ -48,8 +48,8 @@ class InstB { describe('Bytecode Serialization', () => { it('Should deserialize using instruction set', () => { const instructionSet: InstructionSet = new Map([ - [InstA.opcode, InstA], - [InstB.opcode, InstB], + [InstA.opcode, InstA.deserialize], + [InstB.opcode, InstB.deserialize], ]); const a = new InstA(0x1234); const b = new InstB(0x5678n); diff --git a/yarn-project/simulator/src/avm/serialization/bytecode_serialization.ts b/yarn-project/simulator/src/avm/serialization/bytecode_serialization.ts index 48a29f21bbc..cad761b80cc 100644 --- a/yarn-project/simulator/src/avm/serialization/bytecode_serialization.ts +++ b/yarn-project/simulator/src/avm/serialization/bytecode_serialization.ts @@ -2,7 +2,7 @@ import { PedersenCommitment } from '../opcodes/commitment.js'; import { DAGasLeft, L2GasLeft } from '../opcodes/context_getters.js'; import { EcAdd } from '../opcodes/ec_add.js'; import { Keccak, KeccakF1600, Pedersen, Poseidon2, Sha256 } from '../opcodes/hashing.js'; -import type { Instruction } from '../opcodes/index.js'; +import { Instruction } from '../opcodes/index.js'; import { Add, Address, @@ -59,102 +59,104 @@ import { MultiScalarMul } from '../opcodes/multi_scalar_mul.js'; import { BufferCursor } from './buffer_cursor.js'; import { Opcode } from './instruction_serialization.js'; -interface DeserializableInstruction { - deserialize(buf: BufferCursor | Buffer): Instruction; - opcode: Opcode; +export type InstructionDeserializer = (buf: BufferCursor | Buffer) => Instruction; + +export interface Serializable { + serialize(): Buffer; +} + +export interface Deserializable { + deserialize: InstructionDeserializer; } -export type InstructionSet = Map; +export type InstructionSet = Map; // TODO(4359): This is a function so that Call and StaticCall can be lazily resolved. // This is a temporary solution until we solve the dependency cycle. const INSTRUCTION_SET = () => - new Map([ - [Add.opcode, Add], - [Sub.opcode, Sub], - [Mul.opcode, Mul], - [Div.opcode, Div], - [FieldDiv.opcode, FieldDiv], - [Eq.opcode, Eq], - [Lt.opcode, Lt], - [Lte.opcode, Lte], - [And.opcode, And], - [Or.opcode, Or], - [Xor.opcode, Xor], - [Not.opcode, Not], - [Shl.opcode, Shl], - [Shr.opcode, Shr], - [Cast.opcode, Cast], - [Address.opcode, Address], - [StorageAddress.opcode, StorageAddress], - [Sender.opcode, Sender], - [FunctionSelector.opcode, FunctionSelector], - [TransactionFee.opcode, TransactionFee], + new Map([ + [Add.opcode, Instruction.deserialize.bind(Add)], + [Sub.opcode, Instruction.deserialize.bind(Sub)], + [Mul.opcode, Instruction.deserialize.bind(Mul)], + [Div.opcode, Instruction.deserialize.bind(Div)], + [FieldDiv.opcode, Instruction.deserialize.bind(FieldDiv)], + [Eq.opcode, Instruction.deserialize.bind(Eq)], + [Lt.opcode, Instruction.deserialize.bind(Lt)], + [Lte.opcode, Instruction.deserialize.bind(Lte)], + [And.opcode, Instruction.deserialize.bind(And)], + [Or.opcode, Instruction.deserialize.bind(Or)], + [Xor.opcode, Instruction.deserialize.bind(Xor)], + [Not.opcode, Instruction.deserialize.bind(Not)], + [Shl.opcode, Instruction.deserialize.bind(Shl)], + [Shr.opcode, Instruction.deserialize.bind(Shr)], + [Cast.opcode, Instruction.deserialize.bind(Cast)], + [Address.opcode, Instruction.deserialize.bind(Address)], + [StorageAddress.opcode, Instruction.deserialize.bind(StorageAddress)], + [Sender.opcode, Instruction.deserialize.bind(Sender)], + [FunctionSelector.opcode, Instruction.deserialize.bind(FunctionSelector)], + [TransactionFee.opcode, Instruction.deserialize.bind(TransactionFee)], // Execution Environment - Globals - [ChainId.opcode, ChainId], - [Version.opcode, Version], - [BlockNumber.opcode, BlockNumber], - [Timestamp.opcode, Timestamp], - [FeePerL2Gas.opcode, FeePerL2Gas], - [FeePerDAGas.opcode, FeePerDAGas], + [ChainId.opcode, Instruction.deserialize.bind(ChainId)], + [Version.opcode, Instruction.deserialize.bind(Version)], + [BlockNumber.opcode, Instruction.deserialize.bind(BlockNumber)], + [Timestamp.opcode, Instruction.deserialize.bind(Timestamp)], + [FeePerL2Gas.opcode, Instruction.deserialize.bind(FeePerL2Gas)], + [FeePerDAGas.opcode, Instruction.deserialize.bind(FeePerDAGas)], // Execution Environment - Calldata - [CalldataCopy.opcode, CalldataCopy], + [CalldataCopy.opcode, Instruction.deserialize.bind(CalldataCopy)], // Machine State // Machine State - Gas - [L2GasLeft.opcode, L2GasLeft], - [DAGasLeft.opcode, DAGasLeft], + [L2GasLeft.opcode, Instruction.deserialize.bind(L2GasLeft)], + [DAGasLeft.opcode, Instruction.deserialize.bind(DAGasLeft)], // Machine State - Internal Control Flow - [Jump.opcode, Jump], - [JumpI.opcode, JumpI], - [InternalCall.opcode, InternalCall], - [InternalReturn.opcode, InternalReturn], - [Set.opcode, Set], - [Mov.opcode, Mov], - [CMov.opcode, CMov], + [Jump.opcode, Instruction.deserialize.bind(Jump)], + [JumpI.opcode, Instruction.deserialize.bind(JumpI)], + [InternalCall.opcode, Instruction.deserialize.bind(InternalCall)], + [InternalReturn.opcode, Instruction.deserialize.bind(InternalReturn)], + [Set.opcode, Set.deserialize.bind(Set)], + [Opcode.MOV_8, Mov.as(Mov.wireFormat8).deserialize], + [Opcode.MOV_16, Mov.as(Mov.wireFormat16).deserialize], + [CMov.opcode, Instruction.deserialize.bind(CMov)], // World State - [SLoad.opcode, SLoad], // Public Storage - [SStore.opcode, SStore], // Public Storage - [NoteHashExists.opcode, NoteHashExists], // Notes & Nullifiers - [EmitNoteHash.opcode, EmitNoteHash], // Notes & Nullifiers - [NullifierExists.opcode, NullifierExists], // Notes & Nullifiers - [EmitNullifier.opcode, EmitNullifier], // Notes & Nullifiers - [L1ToL2MessageExists.opcode, L1ToL2MessageExists], // Messages + [SLoad.opcode, Instruction.deserialize.bind(SLoad)], // Public Storage + [SStore.opcode, Instruction.deserialize.bind(SStore)], // Public Storage + [NoteHashExists.opcode, Instruction.deserialize.bind(NoteHashExists)], // Notes & Nullifiers + [EmitNoteHash.opcode, Instruction.deserialize.bind(EmitNoteHash)], // Notes & Nullifiers + [NullifierExists.opcode, Instruction.deserialize.bind(NullifierExists)], // Notes & Nullifiers + [EmitNullifier.opcode, Instruction.deserialize.bind(EmitNullifier)], // Notes & Nullifiers + [L1ToL2MessageExists.opcode, Instruction.deserialize.bind(L1ToL2MessageExists)], // Messages // Accrued Substate - [EmitUnencryptedLog.opcode, EmitUnencryptedLog], - [SendL2ToL1Message.opcode, SendL2ToL1Message], - [GetContractInstance.opcode, GetContractInstance], + [EmitUnencryptedLog.opcode, Instruction.deserialize.bind(EmitUnencryptedLog)], + [SendL2ToL1Message.opcode, Instruction.deserialize.bind(SendL2ToL1Message)], + [GetContractInstance.opcode, Instruction.deserialize.bind(GetContractInstance)], // Control Flow - Contract Calls - [Call.opcode, Call], - [StaticCall.opcode, StaticCall], - //[DelegateCall.opcode, DelegateCall], - [Return.opcode, Return], - [Revert.opcode, Revert], + [Call.opcode, Instruction.deserialize.bind(Call)], + [StaticCall.opcode, Instruction.deserialize.bind(StaticCall)], + //[DelegateCall.opcode, Instruction.deserialize.bind(DelegateCall)], + [Return.opcode, Instruction.deserialize.bind(Return)], + [Revert.opcode, Instruction.deserialize.bind(Revert)], // Misc - [DebugLog.opcode, DebugLog], + [DebugLog.opcode, Instruction.deserialize.bind(DebugLog)], // Gadgets - [EcAdd.opcode, EcAdd], - [Keccak.opcode, Keccak], - [Poseidon2.opcode, Poseidon2], - [Sha256.opcode, Sha256], - [Pedersen.opcode, Pedersen], - [MultiScalarMul.opcode, MultiScalarMul], - [PedersenCommitment.opcode, PedersenCommitment], + [EcAdd.opcode, Instruction.deserialize.bind(EcAdd)], + [Keccak.opcode, Instruction.deserialize.bind(Keccak)], + [Poseidon2.opcode, Instruction.deserialize.bind(Poseidon2)], + [Sha256.opcode, Instruction.deserialize.bind(Sha256)], + [Pedersen.opcode, Instruction.deserialize.bind(Pedersen)], + [MultiScalarMul.opcode, Instruction.deserialize.bind(MultiScalarMul)], + [PedersenCommitment.opcode, Instruction.deserialize.bind(PedersenCommitment)], // Conversions - [ToRadixLE.opcode, ToRadixLE], + [ToRadixLE.opcode, Instruction.deserialize.bind(ToRadixLE)], // Future Gadgets -- pending changes in noir // SHA256COMPRESSION, - [KeccakF1600.opcode, KeccakF1600], + [KeccakF1600.opcode, Instruction.deserialize.bind(KeccakF1600)], ]); -interface Serializable { - serialize(): Buffer; -} - /** * Serializes an array of instructions to bytecode. */ @@ -182,8 +184,8 @@ export function decodeFromBytecode( throw new Error(`Opcode ${Opcode[opcode]} (0x${opcode.toString(16)}) not implemented`); } - const instructionDeserializer: DeserializableInstruction = instructionDeserializerOrUndef; - const i: Instruction = instructionDeserializer.deserialize(cursor); + const instructionDeserializer: InstructionDeserializer = instructionDeserializerOrUndef; + const i: Instruction = instructionDeserializer(cursor); instructions.push(i); } diff --git a/yarn-project/simulator/src/avm/serialization/instruction_serialization.test.ts b/yarn-project/simulator/src/avm/serialization/instruction_serialization.test.ts index 3f7d8905c98..8839f86f2ee 100644 --- a/yarn-project/simulator/src/avm/serialization/instruction_serialization.test.ts +++ b/yarn-project/simulator/src/avm/serialization/instruction_serialization.test.ts @@ -1,5 +1,5 @@ import { BufferCursor } from './buffer_cursor.js'; -import { OperandType, deserialize, serialize } from './instruction_serialization.js'; +import { OperandType, deserialize, serializeAs } from './instruction_serialization.js'; class InstA { constructor(private a: number, private b: number, private c: number, private d: bigint, private e: bigint) {} @@ -18,7 +18,7 @@ class InstA { describe('Instruction Serialization', () => { it('Should serialize all types from OperandType[]', () => { const instance = new InstA(0x12, 0x1234, 0x12345678, 0x1234567887654321n, 0x1234567887654321abcdef0000fedcban); - const actual: Buffer = serialize(InstA.wireFormat, instance); + const actual: Buffer = serializeAs(InstA.wireFormat, InstA.opcode, instance); expect(actual).toEqual( Buffer.from( diff --git a/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts b/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts index 6208161ecc7..436d4893a2a 100644 --- a/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts +++ b/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts @@ -46,7 +46,8 @@ export enum Opcode { INTERNALRETURN, // Memory SET, - MOV, + MOV_8, + MOV_16, CMOV, // World state SLOAD, @@ -156,12 +157,10 @@ export function deserialize(cursor: BufferCursor | Buffer, operands: OperandType * @param cls The class to be serialized. * @returns */ -export function serialize(operands: OperandType[], cls: any): Buffer { +export function serializeAs(operands: OperandType[], opcode: Opcode, cls: any): Buffer { const chunks: Buffer[] = []; - // TODO: infer opcode not in this loop - assert(cls.constructor.opcode !== undefined && cls.constructor.opcode !== null); - const rawClassValues: any[] = [cls.constructor.opcode, ...Object.values(cls)]; + const rawClassValues: any[] = [opcode, ...Object.values(cls)]; assert( rawClassValues.length === operands.length, `Got ${rawClassValues.length} values but only ${operands.length} serialization operands are specified!`,