From 27a5498083e9b35af1a216f2bce9e2c90d38d079 Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 17 Feb 2026 10:52:08 -0700 Subject: [PATCH] Step 569: add disassembly register view model --- editor/CMakeLists.txt | 9 ++ editor/src/DisassemblyRegisterViewModel.h | 125 +++++++++++++++++ editor/tests/step569_test.cpp | 156 ++++++++++++++++++++++ progress.md | 34 +++++ 4 files changed, 324 insertions(+) create mode 100644 editor/src/DisassemblyRegisterViewModel.h create mode 100644 editor/tests/step569_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 9133ee9..f4fbd32 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -4000,4 +4000,13 @@ target_link_libraries(step568_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step569_test tests/step569_test.cpp) +target_include_directories(step569_test PRIVATE src) +target_link_libraries(step569_test PRIVATE + nlohmann_json::nlohmann_json + unofficial::tree-sitter::tree-sitter + tree_sitter_python tree_sitter_cpp tree_sitter_elisp + tree_sitter_javascript tree_sitter_typescript + tree_sitter_java tree_sitter_rust tree_sitter_go) + # Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies) diff --git a/editor/src/DisassemblyRegisterViewModel.h b/editor/src/DisassemblyRegisterViewModel.h new file mode 100644 index 0000000..289b5c5 --- /dev/null +++ b/editor/src/DisassemblyRegisterViewModel.h @@ -0,0 +1,125 @@ +#pragma once +// Step 569: Disassembly + Register View Baseline + +#include +#include +#include +#include +#include + +struct DisassemblyInstruction { + std::uint64_t address = 0; + std::string opcode; + std::string operands; + bool isCurrentInstruction = false; +}; + +struct RegisterValue { + std::string name; + std::uint64_t value = 0; +}; + +class DisassemblyRegisterViewModel { +public: + bool setInstructions(const std::vector& instructions, std::string* error) { + if (!error) return false; + error->clear(); + if (instructions.empty()) return fail(error, "instruction_list_empty"); + + for (const auto& instruction : instructions) { + if (instruction.address == 0) return fail(error, "instruction_address_invalid"); + if (instruction.opcode.empty()) return fail(error, "instruction_opcode_missing"); + } + + instructions_ = instructions; + std::stable_sort(instructions_.begin(), + instructions_.end(), + [](const DisassemblyInstruction& a, const DisassemblyInstruction& b) { + return a.address < b.address; + }); + clearCurrentIp(); + return true; + } + + bool setCurrentInstructionPointer(std::uint64_t address, std::string* error) { + if (!error) return false; + error->clear(); + if (address == 0) return fail(error, "instruction_pointer_invalid"); + + bool found = false; + clearCurrentIp(); + for (auto& instruction : instructions_) { + if (instruction.address == address) { + instruction.isCurrentInstruction = true; + found = true; + break; + } + } + if (!found) return fail(error, "instruction_pointer_not_found"); + return true; + } + + std::vector windowAroundAddress(std::uint64_t centerAddress, + std::size_t radius) const { + std::vector window; + if (instructions_.empty()) return window; + + std::size_t centerIndex = instructions_.size(); + for (std::size_t i = 0; i < instructions_.size(); ++i) { + if (instructions_[i].address == centerAddress) { + centerIndex = i; + break; + } + } + if (centerIndex == instructions_.size()) return window; + + const std::size_t begin = centerIndex > radius ? centerIndex - radius : 0; + const std::size_t end = std::min(instructions_.size(), centerIndex + radius + 1); + for (std::size_t i = begin; i < end; ++i) window.push_back(instructions_[i]); + return window; + } + + bool setRegisterValue(const std::string& registerName, + std::uint64_t value, + std::string* error) { + if (!error) return false; + error->clear(); + if (registerName.empty()) return fail(error, "register_name_missing"); + registers_[registerName] = value; + return true; + } + + bool hasRegister(const std::string& registerName) const { + return registers_.count(registerName) != 0; + } + + std::uint64_t registerValueOrZero(const std::string& registerName) const { + auto it = registers_.find(registerName); + if (it == registers_.end()) return 0; + return it->second; + } + + std::vector sortedRegisters() const { + std::vector out; + for (const auto& kv : registers_) out.push_back({kv.first, kv.second}); + std::stable_sort(out.begin(), out.end(), [](const RegisterValue& a, const RegisterValue& b) { + return a.name < b.name; + }); + return out; + } + + const std::vector& instructions() const { return instructions_; } + +private: + std::vector instructions_; + std::map registers_; + + static bool fail(std::string* error, const char* code) { + *error = code; + return false; + } + + void clearCurrentIp() { + for (auto& instruction : instructions_) instruction.isCurrentInstruction = false; + } +}; diff --git a/editor/tests/step569_test.cpp b/editor/tests/step569_test.cpp new file mode 100644 index 0000000..57bf112 --- /dev/null +++ b/editor/tests/step569_test.cpp @@ -0,0 +1,156 @@ +// Step 569: Disassembly + Register View Baseline (12 tests) + +#include "DisassemblyRegisterViewModel.h" + +#include + +static int passed = 0, failed = 0; +#define TEST(name) { std::cout << " " << #name << "... "; } +#define PASS() { std::cout << "PASS\n"; ++passed; } +#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; } +#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {} + +static DisassemblyInstruction inst(std::uint64_t address, const std::string& opcode, const std::string& operands) { + return {address, opcode, operands, false}; +} + +void test_set_instructions_success() { + TEST(set_instructions_success); + DisassemblyRegisterViewModel model; + std::string error; + CHECK(model.setInstructions({inst(0x1000, "mov", "rax, rbx"), inst(0x1004, "ret", "")}, &error), "set should succeed"); + CHECK(model.instructions().size() == 2, "instruction count mismatch"); + PASS(); +} + +void test_set_instructions_rejects_empty() { + TEST(set_instructions_rejects_empty); + DisassemblyRegisterViewModel model; + std::string error; + CHECK(!model.setInstructions({}, &error), "empty instruction list should fail"); + CHECK(error == "instruction_list_empty", "wrong error"); + PASS(); +} + +void test_set_instructions_rejects_invalid_address() { + TEST(set_instructions_rejects_invalid_address); + DisassemblyRegisterViewModel model; + std::string error; + CHECK(!model.setInstructions({inst(0, "mov", "rax,rbx")}, &error), "zero address should fail"); + CHECK(error == "instruction_address_invalid", "wrong error"); + PASS(); +} + +void test_set_instructions_rejects_missing_opcode() { + TEST(set_instructions_rejects_missing_opcode); + DisassemblyRegisterViewModel model; + std::string error; + CHECK(!model.setInstructions({inst(0x1000, "", "rax,rbx")}, &error), "missing opcode should fail"); + CHECK(error == "instruction_opcode_missing", "wrong error"); + PASS(); +} + +void test_instructions_are_sorted_by_address() { + TEST(instructions_are_sorted_by_address); + DisassemblyRegisterViewModel model; + std::string error; + CHECK(model.setInstructions({inst(0x1010, "ret", ""), inst(0x1000, "push", "rbp")}, &error), "set should succeed"); + CHECK(model.instructions()[0].address == 0x1000, "first instruction should be lowest address"); + CHECK(model.instructions()[1].address == 0x1010, "second instruction should be highest address"); + PASS(); +} + +void test_set_current_instruction_pointer_success() { + TEST(set_current_instruction_pointer_success); + DisassemblyRegisterViewModel model; + std::string error; + CHECK(model.setInstructions({inst(0x1000, "nop", ""), inst(0x1001, "ret", "")}, &error), "set should succeed"); + CHECK(model.setCurrentInstructionPointer(0x1001, &error), "set ip should succeed"); + CHECK(!model.instructions()[0].isCurrentInstruction, "first instruction should not be current"); + CHECK(model.instructions()[1].isCurrentInstruction, "second instruction should be current"); + PASS(); +} + +void test_set_current_instruction_pointer_rejects_unknown_address() { + TEST(set_current_instruction_pointer_rejects_unknown_address); + DisassemblyRegisterViewModel model; + std::string error; + CHECK(model.setInstructions({inst(0x1000, "nop", "")}, &error), "set should succeed"); + CHECK(!model.setCurrentInstructionPointer(0x2000, &error), "unknown ip should fail"); + CHECK(error == "instruction_pointer_not_found", "wrong error"); + PASS(); +} + +void test_window_around_address_returns_radius_slice() { + TEST(window_around_address_returns_radius_slice); + DisassemblyRegisterViewModel model; + std::string error; + CHECK(model.setInstructions({inst(0x1000, "a", ""), inst(0x1001, "b", ""), inst(0x1002, "c", ""), inst(0x1003, "d", "")}, &error), "set should succeed"); + const auto window = model.windowAroundAddress(0x1001, 1); + CHECK(window.size() == 3, "window size mismatch"); + CHECK(window[0].address == 0x1000, "window start mismatch"); + CHECK(window[2].address == 0x1002, "window end mismatch"); + PASS(); +} + +void test_window_around_unknown_address_is_empty() { + TEST(window_around_unknown_address_is_empty); + DisassemblyRegisterViewModel model; + std::string error; + CHECK(model.setInstructions({inst(0x1000, "a", "")}, &error), "set should succeed"); + const auto window = model.windowAroundAddress(0x9999, 1); + CHECK(window.empty(), "window should be empty for unknown center"); + PASS(); +} + +void test_set_register_value_success() { + TEST(set_register_value_success); + DisassemblyRegisterViewModel model; + std::string error; + CHECK(model.setRegisterValue("rax", 123, &error), "set register should succeed"); + CHECK(model.hasRegister("rax"), "register should exist"); + CHECK(model.registerValueOrZero("rax") == 123, "register value mismatch"); + PASS(); +} + +void test_set_register_value_rejects_empty_name() { + TEST(set_register_value_rejects_empty_name); + DisassemblyRegisterViewModel model; + std::string error; + CHECK(!model.setRegisterValue("", 123, &error), "empty register name should fail"); + CHECK(error == "register_name_missing", "wrong error"); + PASS(); +} + +void test_sorted_registers_order_by_name() { + TEST(sorted_registers_order_by_name); + DisassemblyRegisterViewModel model; + std::string error; + CHECK(model.setRegisterValue("rbx", 2, &error), "set rbx failed"); + CHECK(model.setRegisterValue("rax", 1, &error), "set rax failed"); + const auto regs = model.sortedRegisters(); + CHECK(regs.size() == 2, "register count mismatch"); + CHECK(regs[0].name == "rax", "register order mismatch"); + CHECK(regs[1].name == "rbx", "register order mismatch"); + PASS(); +} + +int main() { + std::cout << "Step 569: Disassembly + Register View Baseline\n"; + + test_set_instructions_success(); // 1 + test_set_instructions_rejects_empty(); // 2 + test_set_instructions_rejects_invalid_address(); // 3 + test_set_instructions_rejects_missing_opcode(); // 4 + test_instructions_are_sorted_by_address(); // 5 + test_set_current_instruction_pointer_success(); // 6 + test_set_current_instruction_pointer_rejects_unknown_address();// 7 + test_window_around_address_returns_radius_slice(); // 8 + test_window_around_unknown_address_is_empty(); // 9 + test_set_register_value_success(); // 10 + test_set_register_value_rejects_empty_name(); // 11 + test_sorted_registers_order_by_name(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index d040394..4c49cb0 100644 --- a/progress.md +++ b/progress.md @@ -10336,3 +10336,37 @@ stack/debug correlation gating. **Phase 31a totals (564-568):** - **Steps completed:** 5 - **New tests in this phase plan:** 56/56 passing + +### Step 569: Disassembly + Register View Baseline +**Status:** PASS (12/12 tests) + +Implements a baseline disassembly/register inspection model for low-level runtime +debug views with deterministic instruction ordering and register state access. + +**Files added:** +- `editor/src/DisassemblyRegisterViewModel.h` - disassembly/register module: + - instruction schema with current-IP annotation state + - instruction-set validation and deterministic address ordering + - current instruction pointer selection and window slicing + - register set/update/query helpers with stable sorted output +- `editor/tests/step569_test.cpp` - 12 tests covering: + - instruction load success and validation failures + - deterministic instruction ordering behavior + - current-IP selection behavior and unknown-address rejection + - centered disassembly window behavior + - register set/query validation behavior + - register ordering behavior + +**Files modified:** +- `editor/CMakeLists.txt` - `step569_test` target + +**Verification run:** +- `cmake -S editor -B editor/build-native` - PASS +- `cmake --build editor/build-native --target step569_test step568_test` - PASS +- `./editor/build-native/step569_test` - PASS (12/12) +- `./editor/build-native/step568_test` - PASS (8/8) regression coverage + +**Architecture gate check:** +- `editor/src/DisassemblyRegisterViewModel.h` within header-size limit (`125` <= `600`) +- `editor/tests/step569_test.cpp` within test-file size guidance (`156` lines) +- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`