-
Notifications
You must be signed in to change notification settings - Fork 609
fix(avm): cpp addressing #13652
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
fix(avm): cpp addressing #13652
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,49 +30,72 @@ std::vector<Operand> Addressing::resolve(const Instruction& instruction, MemoryI | |
| assert(spec.num_addresses <= instruction.operands.size()); | ||
|
|
||
| // We retrieve, cache first because this is probably what we'll do in the circuit. | ||
| // However, we can't check the value and tag yet! This should be done only if it's used. | ||
| // However, we can't check that the base address is valid* yet! This should be done only if it's used. | ||
| // This is because the first few instructions might not YET have a valid stack pointer. | ||
| auto base_address = memory.get(0); | ||
| // *valid = valid_address and valid_tag. | ||
| MemoryValue base_address = memory.get(0); | ||
| event.base_address = base_address; | ||
|
|
||
| // First process relative addressing for all the addresses. | ||
| // First, we make sure that the operands themselves are valid addresses. | ||
| for (size_t i = 0; i < spec.num_addresses; ++i) { | ||
| // We are a bit flexible here, we don't check the tag, because we might get a U8 or U16 from the | ||
| // wire format but would want to interpret it as a MemoryAddress. We only need to check that "it fits". | ||
| // That's why we use as_ff() here. | ||
| if (!memory.is_valid_address(instruction.operands[i].as_ff())) { | ||
| throw AddressingException(AddressingEventError::OPERAND_INVALID_ADDRESS, i); | ||
| } | ||
| } | ||
|
|
||
| // Guarantees by this point: | ||
| // - all operands are valid addresses IF interpreted as a MemoryAddress. | ||
|
|
||
| // Then, we process relative addressing for all the addresses. | ||
| // That is, if relative addressing is used, after_relative[i] = base_address + operands[i]. | ||
| // We fist store the operands as is, and then we'll update them if they are relative. | ||
| event.after_relative = instruction.operands; | ||
| for (size_t i = 0; i < spec.num_addresses; ++i) { | ||
| if ((instruction.indirect >> i) & 1) { | ||
| if ((instruction.indirect >> (i + spec.num_addresses)) & 1) { | ||
| if (!memory.is_valid_address(base_address)) { | ||
| throw AddressingException(AddressingEventError::BASE_ADDRESS_INVALID_ADDRESS, i); | ||
| } | ||
|
|
||
| // We extend the address to FF to avoid overflows. | ||
| FF offset = event.after_relative[i]; | ||
| offset = offset + base_address; | ||
| event.after_relative[i] = Operand::from(offset); | ||
| offset += base_address; | ||
| // We store the offset as an FF operand. If the circuit needs to prove overflow, it will | ||
| // need the full value. | ||
| event.after_relative[i] = Operand::from<FF>(offset); | ||
| if (!memory.is_valid_address(offset)) { | ||
| // If this happens, it means that the relative computation overflowed. However both the base and | ||
| // operand addresses by themselves were valid. | ||
| throw AddressingException(AddressingEventError::RELATIVE_COMPUTATION_OOB, i); | ||
| } | ||
| } | ||
| // Now that we are sure that the offset is valid, we can update the value to be of the right type. | ||
| event.after_relative[i] = Operand::from(static_cast<MemoryAddress>(event.after_relative[i].as_ff())); | ||
| } | ||
|
|
||
| // Guarantees by this point: | ||
| // - all operands are valid addresses IF interpreted as MemoryAddress. | ||
| // - all after_relative values are valid addresses. | ||
|
|
||
| // Then indirection. | ||
| // That is, if indirection is used, resolved_operands[i] = memory[after_relative[i]]. | ||
| // We first store the after_relative values as is, and then we'll update them if they are indirect. | ||
| event.resolved_operands = event.after_relative; | ||
| for (size_t i = 0; i < spec.num_addresses; ++i) { | ||
| if ((instruction.indirect >> (i + spec.num_addresses)) & 1) { | ||
| FF offset = event.resolved_operands[i]; | ||
| if (!memory.is_valid_address(offset)) { | ||
| if ((instruction.indirect >> i) & 1) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Swapped with this one. |
||
| event.resolved_operands[i] = memory.get(event.after_relative[i].as<MemoryAddress>()); | ||
| if (!memory.is_valid_address(event.resolved_operands[i])) { | ||
| throw AddressingException(AddressingEventError::INDIRECT_INVALID_ADDRESS, i); | ||
| } | ||
| FF new_address = memory.get(static_cast<MemoryAddress>(offset)); | ||
| event.resolved_operands[i] = Operand::from(new_address); | ||
| } | ||
| } | ||
|
|
||
| // We guarantee that the resolved operands are valid addresses. | ||
| for (size_t i = 0; i < spec.num_addresses; ++i) { | ||
| if (!memory.is_valid_address(MemoryValue(event.resolved_operands[i]))) { | ||
| throw AddressingException(AddressingEventError::FINAL_ADDRESS_INVALID, i); | ||
| } | ||
| event.resolved_operands[i] = | ||
| Operand::from<uint32_t>(static_cast<MemoryAddress>(event.resolved_operands[i].as_ff())); | ||
| } | ||
| // Guarantees by this point: | ||
| // - all operands are valid addresses. | ||
| // - all after_relative values are valid addresses. | ||
| // - all resolved_operands are valid addresses. | ||
| } catch (const AddressingException& e) { | ||
| event.error = e; | ||
| } | ||
|
|
||
199 changes: 199 additions & 0 deletions
199
barretenberg/cpp/src/barretenberg/vm2/simulation/addressing.test.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,199 @@ | ||
| #include "barretenberg/vm2/simulation/addressing.hpp" | ||
|
|
||
| #include <gmock/gmock.h> | ||
| #include <gtest/gtest.h> | ||
|
|
||
| #include "barretenberg/vm2/common/memory_types.hpp" | ||
| #include "barretenberg/vm2/common/opcodes.hpp" | ||
| #include "barretenberg/vm2/simulation/events/event_emitter.hpp" | ||
| #include "barretenberg/vm2/simulation/lib/instruction_info.hpp" | ||
| #include "barretenberg/vm2/simulation/lib/serialization.hpp" | ||
| #include "barretenberg/vm2/simulation/memory.hpp" | ||
| #include "barretenberg/vm2/simulation/testing/mock_memory.hpp" | ||
| #include "barretenberg/vm2/testing/instruction_builder.hpp" | ||
|
|
||
| namespace bb::avm2::simulation { | ||
| namespace { | ||
|
|
||
| using ::bb::avm2::testing::InstructionBuilder; | ||
| using ::bb::avm2::testing::OperandBuilder; | ||
| using enum ::bb::avm2::WireOpCode; | ||
| using ::testing::ElementsAre; | ||
| using ::testing::ReturnRef; | ||
| using ::testing::StrictMock; | ||
|
|
||
| template <typename T> auto from = ::bb::avm2::simulation::Operand::from<T>; | ||
|
|
||
| TEST(AvmSimulationAddressingTest, AllDirectAndNonRelative) | ||
| { | ||
| InstructionInfoDB instruction_info_db; | ||
| NoopEventEmitter<AddressingEvent> event_emitter; | ||
| Addressing addressing(instruction_info_db, event_emitter); | ||
| MemoryValue zero_u32 = MemoryValue::from<uint32_t>(0); | ||
|
|
||
| { | ||
| const auto instr = InstructionBuilder(SET_8) | ||
| // dstOffset | ||
| .operand<uint8_t>(1) | ||
| // tag | ||
| .operand(MemoryTag::FF) | ||
| // value | ||
| .operand<uint8_t>(1) | ||
| .build(); | ||
|
|
||
| StrictMock<MockMemory> memory; | ||
| EXPECT_CALL(memory, get(0)).WillOnce(ReturnRef(zero_u32)); // call to get the base address. | ||
|
|
||
| const auto operands = addressing.resolve(instr, memory); | ||
| EXPECT_THAT(operands, | ||
| ElementsAre( | ||
| // dstOffset has been resolved and is now a MemoryAddress. | ||
| from<MemoryAddress>(1), | ||
| // tag is unchanged. | ||
| instr.operands.at(1), | ||
| // value is unchanged. | ||
| instr.operands.at(2))); | ||
| } | ||
| { | ||
| const auto instr = InstructionBuilder(ADD_16) | ||
| // aOffset | ||
| .operand<uint16_t>(1) | ||
| // bOffset | ||
| .operand<uint16_t>(2) | ||
| // dstOffset | ||
| .operand<uint16_t>(3) | ||
| .build(); | ||
|
|
||
| StrictMock<MockMemory> memory; | ||
| EXPECT_CALL(memory, get(0)).WillOnce(ReturnRef(zero_u32)); // call to get the base address. | ||
|
|
||
| const auto operands = addressing.resolve(instr, memory); | ||
| EXPECT_THAT(operands, ElementsAre(from<MemoryAddress>(1), from<MemoryAddress>(2), from<MemoryAddress>(3))); | ||
| } | ||
| } | ||
|
|
||
| TEST(AvmSimulationAddressingTest, RelativeAddressing) | ||
| { | ||
| InstructionInfoDB instruction_info_db; | ||
| NoopEventEmitter<AddressingEvent> event_emitter; | ||
| Addressing addressing(instruction_info_db, event_emitter); | ||
|
|
||
| // For relative addressing, we need a base address other than 0 | ||
| // Base pointer at address 100 | ||
| MemoryValue base_addr = MemoryValue::from<uint32_t>(100); | ||
|
|
||
| // Set up the ADD_8 instruction with relative addressing | ||
| const auto instr = InstructionBuilder(ADD_8) | ||
| // aOffset | ||
| .operand<uint8_t>(10) | ||
| .relative() | ||
| // bOffset | ||
| .operand<uint8_t>(20) | ||
| // dstOffset | ||
| .operand<uint8_t>(30) | ||
| .relative() | ||
| .build(); | ||
|
|
||
| StrictMock<MockMemory> memory; | ||
| EXPECT_CALL(memory, get(0)).WillOnce(ReturnRef(base_addr)); | ||
|
|
||
| const auto operands = addressing.resolve(instr, memory); | ||
|
|
||
| EXPECT_THAT(operands, | ||
| ElementsAre( | ||
| // aOffset resolved as base + 10 = 110 | ||
| from<MemoryAddress>(110), | ||
| // bOffset stays the same | ||
| from<MemoryAddress>(20), | ||
| // dstOffset resolved as base + 30 = 130 | ||
| from<MemoryAddress>(130))); | ||
| } | ||
|
|
||
| TEST(AvmSimulationAddressingTest, IndirectAddressing) | ||
| { | ||
| InstructionInfoDB instruction_info_db; | ||
| NoopEventEmitter<AddressingEvent> event_emitter; | ||
| Addressing addressing(instruction_info_db, event_emitter); | ||
|
|
||
| // For indirect addressing, memory locations contain the actual addresses | ||
| MemoryValue zero_u32 = MemoryValue::from<uint32_t>(0); | ||
|
|
||
| // Set up the ADD_8 instruction with indirect addressing | ||
| const auto instr = InstructionBuilder(ADD_8) | ||
| // aOffset - address 5 contains the actual address | ||
| .operand<uint8_t>(5) | ||
| .indirect() | ||
| // bOffset | ||
| .operand<uint8_t>(10) | ||
| // dstOffset - address 15 contains the actual address | ||
| .operand<uint8_t>(15) | ||
| .indirect() | ||
| .build(); | ||
|
|
||
| StrictMock<MockMemory> memory; | ||
| EXPECT_CALL(memory, get(0)).WillOnce(ReturnRef(zero_u32)); // Base address | ||
| MemoryValue addr_5_value = MemoryValue::from<uint32_t>(50); | ||
| EXPECT_CALL(memory, get(5)).WillOnce(ReturnRef(addr_5_value)); | ||
| MemoryValue addr_15_value = MemoryValue::from<uint32_t>(60); | ||
| EXPECT_CALL(memory, get(15)).WillOnce(ReturnRef(addr_15_value)); | ||
|
|
||
| const auto operands = addressing.resolve(instr, memory); | ||
|
|
||
| // Expect indirect offsets to be resolved by looking up their values in memory | ||
| EXPECT_THAT(operands, | ||
| ElementsAre( | ||
| // aOffset resolved indirectly: memory[5] = 50 | ||
| from<MemoryAddress>(50), | ||
| // bOffset stays the same | ||
| from<MemoryAddress>(10), | ||
| // dstOffset resolved indirectly: memory[15] = 60 | ||
| from<MemoryAddress>(60))); | ||
| } | ||
|
|
||
| TEST(AvmSimulationAddressingTest, IndirectAndRelativeAddressing) | ||
| { | ||
| InstructionInfoDB instruction_info_db; | ||
| NoopEventEmitter<AddressingEvent> event_emitter; | ||
| Addressing addressing(instruction_info_db, event_emitter); | ||
|
|
||
| // Base address is 100 | ||
| MemoryValue base_addr = MemoryValue::from<uint32_t>(100); | ||
|
|
||
| // Set up the ADD_8 instruction with both indirect and relative addressing | ||
| const auto instr = InstructionBuilder(ADD_8) | ||
| // aOffset (indirect and relative): address base+5 contains value | ||
| .operand<uint8_t>(5) | ||
| .indirect() | ||
| .relative() | ||
| // bOffset (indirect only): address 10 contains value | ||
| .operand<uint8_t>(10) | ||
| .indirect() | ||
| // dstOffset (relative only) | ||
| .operand<uint8_t>(15) | ||
| .relative() | ||
| .build(); | ||
|
|
||
| StrictMock<MockMemory> memory; | ||
| EXPECT_CALL(memory, get(0)).WillOnce(ReturnRef(base_addr)); // Base address (100) | ||
| // Address 105 (base+5) contains value 200 | ||
| MemoryValue addr_105_value = MemoryValue::from<uint32_t>(200); | ||
| EXPECT_CALL(memory, get(105)).WillOnce(ReturnRef(addr_105_value)); | ||
| // Address 10 contains value 60 | ||
| MemoryValue addr_10_value = MemoryValue::from<uint32_t>(60); | ||
| EXPECT_CALL(memory, get(10)).WillOnce(ReturnRef(addr_10_value)); | ||
|
|
||
| const auto operands = addressing.resolve(instr, memory); | ||
|
|
||
| // Expect combined indirect and relative addressing | ||
| EXPECT_THAT(operands, | ||
| ElementsAre( | ||
| // aOffset: relative (base+5=105) and indirect (memory[105]=200) | ||
| from<MemoryAddress>(200), | ||
| // bOffset: indirect only (memory[10]=60) | ||
| from<MemoryAddress>(60), | ||
| // dstOffset: relative only (base+15=115) | ||
| from<MemoryAddress>(115))); | ||
| } | ||
|
|
||
| } // namespace | ||
| } // namespace bb::avm2::simulation |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was the other way around