From 9feef38410311dfa8f2e88c14433e705b46f88bf Mon Sep 17 00:00:00 2001 From: Kittycannon Date: Sat, 21 Mar 2026 09:49:42 -0600 Subject: [PATCH 01/10] runtime progress --- spider-runtime.code-workspace | 3 +- src/spider/SpiderRuntime.cpp | 4 +- src/spider/runtime/Runtime.hpp | 2 +- src/spider/runtime/cpu/CPU.cpp | 16 ++ src/spider/runtime/cpu/Register.hpp | 20 ++- src/spider/runtime/debug/LiveDebug.cpp | 86 ++++++++++ src/spider/runtime/debug/LiveDebug.hpp | 7 + src/spider/runtime/native/machine.hpp | 6 +- src/spider/runtime/util/Terminal.cpp | 211 +++++++++++++++++++++++++ src/spider/runtime/util/Terminal.hpp | 146 +++++++++++++++++ 10 files changed, 488 insertions(+), 13 deletions(-) create mode 100644 src/spider/runtime/debug/LiveDebug.cpp create mode 100644 src/spider/runtime/debug/LiveDebug.hpp create mode 100644 src/spider/runtime/util/Terminal.cpp create mode 100644 src/spider/runtime/util/Terminal.hpp diff --git a/spider-runtime.code-workspace b/spider-runtime.code-workspace index 33de8c3..07b9241 100644 --- a/spider-runtime.code-workspace +++ b/spider-runtime.code-workspace @@ -15,6 +15,7 @@ ], "C_Cpp.default.includePath": [ "./src" - ] + ], + "terminal.integrated.defaultProfile.windows": "MSYS2 UCRT" } } \ No newline at end of file diff --git a/src/spider/SpiderRuntime.cpp b/src/spider/SpiderRuntime.cpp index 5151a6b..cffd95a 100644 --- a/src/spider/SpiderRuntime.cpp +++ b/src/spider/SpiderRuntime.cpp @@ -1,5 +1,7 @@ #include "SpiderRuntime.hpp" +#include + #include namespace spider { @@ -9,6 +11,6 @@ namespace spider { } int main() { - std::cout << "Hello World" << std::endl; + spider::liveDebugMain(); return 0; } diff --git a/src/spider/runtime/Runtime.hpp b/src/spider/runtime/Runtime.hpp index c4dc9c8..6541690 100644 --- a/src/spider/runtime/Runtime.hpp +++ b/src/spider/runtime/Runtime.hpp @@ -10,7 +10,7 @@ namespace spider { * This is where the Spider VM (Runtime) lives */ class Runtime { - private: + public: CPU cpu; RAM ram; diff --git a/src/spider/runtime/cpu/CPU.cpp b/src/spider/runtime/cpu/CPU.cpp index e69de29..fb8cdda 100644 --- a/src/spider/runtime/cpu/CPU.cpp +++ b/src/spider/runtime/cpu/CPU.cpp @@ -0,0 +1,16 @@ +#include "CPU.hpp" + +namespace spider { + + CPU::CPU() + : RA{}, RB{}, RC{}, RD{}, + RX{}, RY{}, R0{}, R1{}, + R2{}, R3{}, R4{}, R5{}, + R6{}, R7{}, R8{}, R9{}, + RF{}, RI{}, RS{}, RZ{}, + RE{}, RN{}, RV{}, RM{} + {} + + CPU::~CPU() {} + +} diff --git a/src/spider/runtime/cpu/Register.hpp b/src/spider/runtime/cpu/Register.hpp index a2acdd5..2948e22 100644 --- a/src/spider/runtime/cpu/Register.hpp +++ b/src/spider/runtime/cpu/Register.hpp @@ -23,21 +23,25 @@ namespace spider { f64 _f64; u8 _bytes[8]; - SPIDER_PACKED_STRUCT(struct { + struct { #if SPIDER_LITTLE_ENDIAN - u8 _u8; u64 : 56; + u8 _u8; // This looks like a cruel joke + u8 : 8; u8 : 8; u8 : 8; u8 : 8; u8 : 8; u8 : 8; u8 : 8; #else - u64 : 56; u8 _u8; + u8 : 8; u8 : 8; u8 : 8; u8 : 8; u8 : 8; u8 : 8; u8 : 8; + u8 _u8; #endif - }); + }; - SPIDER_PACKED_STRUCT(struct { + struct { #if SPIDER_LITTLE_ENDIAN - u16 _u16; u64 : 48; + u16 _u16; + u16 : 16; u16 : 16; u16 : 16; #else - u64 : 48; u16 _u16; + u16 : 16; u16 : 16; u16 : 16; + u16 _u16; #endif - }); + }; struct { #if SPIDER_LITTLE_ENDIAN diff --git a/src/spider/runtime/debug/LiveDebug.cpp b/src/spider/runtime/debug/LiveDebug.cpp new file mode 100644 index 0000000..56b23be --- /dev/null +++ b/src/spider/runtime/debug/LiveDebug.cpp @@ -0,0 +1,86 @@ +#include "LiveDebug.hpp" + +#include +#include + +#include +#include + +namespace spider { + + void drawHead(Terminal& t) { + t.move(1, 1) + .style(Terminal::RESET) + .print(" Spider Runtime Live Debug ") + .style(Terminal::RESET).print(" | ") + .style(Terminal::FG_CYAN).print(" Sintek Analytics @ 2026 ") + .style(Terminal::RESET).print(" | ") + .style(Terminal::FG_B_BLACK).print("Press ESC to exit") + .style(Terminal::FG_BLACK) + .style(Terminal::BG_YELLOW) + .move(3, 1).print(" || __ ||").print(" ") + .move(4, 1).print(" \\\\( )//").print(" SPIDER v0.1 ") + .move(5, 1).print(" //()\\\\ ").print(" alpha ") + .move(6, 1).print(" || || ").print(" ") + .style(Terminal::RESET) + ; + } + + void drawCPUTempl(Terminal& t, CPU& cpu) { + i32 r = 7, c = 1; + t.drawBox(r, c, 35, 18, "CPU"); + + const std::string regs[] = { + "RA", "RB", "RC", "RD", + "RX", "RY", "R0", "R1", + "R2", "R3", "R4", "R5", + "R6", "R7", "R8", "R9", + "RF", "RI", "RS", "RZ", + "RE", "RN", "RV", "RM" + }; + + r++; + c++; + + + + for(i32 i = 0; i < 8; i++) { + t.move(r + i * 2, c); + t.print(regs[i * 2]); + t.move(r + i * 2, c + 17); + t.print(regs[i * 2 + 1]); + } + } + + int liveDebugMain() { + Terminal t; + Runtime runtime(1024); + + t.println("Starting Spider live debug..."); + t.altbuff(true).cursor(false); + + drawHead(t); + drawCPUTempl(t, runtime.cpu); + + bool running = true; + int c = 1; + while (running) { + // Handle Input + u8 k = t.getKey(); + switch (k) { + case Terminal::ESC: + running = false; + break; + default: + t.move(5, c++); + t.print("A"); + t.flush(); + break; + } + } + + t.altbuff(false).println("Stopped Spider live debug.").flush(); + return 0; + } + +} diff --git a/src/spider/runtime/debug/LiveDebug.hpp b/src/spider/runtime/debug/LiveDebug.hpp new file mode 100644 index 0000000..69140cb --- /dev/null +++ b/src/spider/runtime/debug/LiveDebug.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace spider { + + int liveDebugMain(); + +} diff --git a/src/spider/runtime/native/machine.hpp b/src/spider/runtime/native/machine.hpp index 5d049b6..2b6fe1f 100644 --- a/src/spider/runtime/native/machine.hpp +++ b/src/spider/runtime/native/machine.hpp @@ -55,6 +55,7 @@ // ========================================================== // // PACKING // +// (not used now) // // ========================================================== // @@ -73,8 +74,9 @@ // Macros... #if defined(SPIDER_COMPILER_GCC_LIKE) - #define SPIDER_ATTRIBUTE_PACKED __attribute__((packed)) - #define SPIDER_PACKED_STRUCT(decl) decl SPIDER_ATTRIBUTE_PACKED + #define SPIDER_PACK_BEGIN + #define SPIDER_PACK_END + #define SPIDER_PACK_STRUCT __attribute__((packed)) #elif defined(SPIDER_COMPILER_MSVC) #define SPIDER_BEGIN_PACKED __pragma(pack(push, 1)) diff --git a/src/spider/runtime/util/Terminal.cpp b/src/spider/runtime/util/Terminal.cpp new file mode 100644 index 0000000..0883191 --- /dev/null +++ b/src/spider/runtime/util/Terminal.cpp @@ -0,0 +1,211 @@ +#include "Terminal.hpp" + +#include + +#if defined(SPIDER_OS_WINDOWS) +#include +#include +#endif + +#if defined(SPIDER_OS_LINUX) || defined(SPIDER_OS_MACOS) +#include +#include +#include +#endif + +#if defined(SPIDER_DISTRO_DESKTOP) + +namespace spider { + + Terminal::Terminal() { +#if defined(SPIDER_OS_WINDOWS) + SetConsoleOutputCP(CP_UTF8); + SetConsoleCP(CP_UTF8); +#endif + } + + Terminal::~Terminal() { + altbuff(false).style(RESET).cursor(true).flush(); + } + + Terminal& Terminal::style(const std::string_view code) { + std::cout << code; + return *this; + } + + Terminal& Terminal::move(i32 row, i32 col) { + std::cout << "\033[" << row << ";" << col << "H"; + return *this; + } + + Terminal& Terminal::altbuff(bool enable) { + std::cout << (enable ? "\033[?1049h" : "\033[?1049l"); + return *this; + } + + Terminal& Terminal::cls() { + std::cout << "\033[2J"; + return *this; + } + + Terminal& Terminal::scrollRange(i32 start, i32 end) { + std::cout << "\033[" << start << ";" << end << "r\033[?6h"; + return *this; + } + + Terminal& Terminal::undoSRange() { + std::cout << "\033[r\033[?6l\033[H"; + return *this; + } + + Terminal& Terminal::fill(const std::string_view color) { + this->style(color); + this->cls(); + return *this; + } + + Terminal& Terminal::clearRow(i32 row) { + // Move to row, column 1 + this->move(row, 1); + // \033[2K clears the entire line + std::cout << "\033[2K"; + return *this; + } + + Terminal& Terminal::clearRows(i32 start, i32 end) { + // Ensure we don't loop infinitely if start > end + if (start > end) std::swap(start, end); + + for (i32 i = start; i <= end; ++i) { + this->clearRow(i); + } + + // Optional: Move cursor back to the start of the cleared block + this->move(start, 1); + return *this; + } + + Terminal& Terminal::cursor(bool show) { + std::cout << (show ? "\033[?25h" : "\033[?25l"); + return *this; + } + + Terminal& Terminal::drawBox(i32 startRow, i32 startCol, i32 width, i32 height, std::string_view title) { + // 1. Draw the top border + move(startRow, startCol); + std::cout << "┌"; + for (i32 i = 0; i < width - 2; ++i) std::cout << "─"; + std::cout << "┐"; + + // 2. Draw the sides + for (i32 i = 1; i < height - 1; ++i) { + move(startRow + i, startCol); + std::cout << "│"; + move(startRow + i, startCol + width - 1); + std::cout << "│"; + } + + // 3. Draw the bottom border + move(startRow + height - 1, startCol); + std::cout << "└"; + for (i32 i = 0; i < width - 2; ++i) std::cout << "─"; + std::cout << "┘"; + + // 4. Overlay the title if provided + if (!title.empty()) { + move(startRow, startCol + (width - title.size() - 2) / 2); + std::cout << " " << title << " "; + } + + std::cout.flush(); + return *this; + } + + Terminal& Terminal::flush() { + std::cout << std::flush; + return *this; + } + + Terminal& Terminal::sink() { + std::cin.clear(); +#if defined(SPIDER_OS_WINDOWS) + while (_kbhit()) _getch(); +#endif +#if defined(SPIDER_OS_LINUX) || defined(SPIDER_OS_MACOS) + tcflush(STDIN_FILENO, TCIFLUSH); +#endif + return *this; + } + + std::pair Terminal::size() { + std::pair pair; +#if defined(SPIDER_OS_WINDOWS) + CONSOLE_SCREEN_BUFFER_INFO csbi; + if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) { + pair.first = csbi.srWindow.Right - csbi.srWindow.Left + 1; + pair.second = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; + } +#endif +#if defined(SPIDER_OS_LINUX) || defined(SPIDER_OS_MACOS) + struct winsize w; + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0) { + pair.first = w.ws_col; + pair.second = w.ws_row; + } +#endif + return pair; + } + + Terminal& Terminal::wait() { + sink(); + std::cin.get(); + return *this; + } + + u8 Terminal::getKey() { +#if defined(SPIDER_OS_WINDOWS) + i32 ch = _getch(); + if (ch == 0 || ch == 224) { + switch (_getch()) { + case 72: return Terminal::UP; + case 80: return Terminal::DOWN; + case 75: return Terminal::LEFT; + case 77: return Terminal::RIGHT; + } + } + if (ch == 13) return Terminal::ENTER; + if (ch == 27) return Terminal::ESC; + if (ch == 8) return Terminal::BACKSPACE; + return Terminal::UNKNOWN; +#endif +#if defined(SPIDER_OS_LINUX) || defined(SPIDER_OS_MACOS) + struct termios oldt, newt; + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + + u8 chd = Terminal::UNKNOWN; + i32 ch = getchar(); + if (ch == 27) { // ESC sequence + if (getchar() == '[') { + switch (getchar()) { + case 'A': chd = Terminal::UP; break; + case 'B': chd = Terminal::DOWN; break; + case 'D': chd = Terminal::LEFT; break; + case 'C': chd = Terminal::RIGHT; break; + } + } + } + else if (ch == 10) chd = Terminal::ENTER; + else if (ch == 127) chd = Terminal::BACKSPACE; + + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + + return chd; +#endif + } + +} + +#endif diff --git a/src/spider/runtime/util/Terminal.hpp b/src/spider/runtime/util/Terminal.hpp new file mode 100644 index 0000000..3532722 --- /dev/null +++ b/src/spider/runtime/util/Terminal.hpp @@ -0,0 +1,146 @@ +#pragma once + +#include + +#include +#include +#include +#include + +namespace spider { + + class Terminal { + public: + + // --- Constants --- + static constexpr const char* RESET = "\033[0m"; + static constexpr const char* FG_BLACK = "\033[30m"; static constexpr const char* FG_B_BLACK = "\033[90m"; + static constexpr const char* FG_RED = "\033[31m"; static constexpr const char* FG_B_RED = "\033[91m"; + static constexpr const char* FG_GREEN = "\033[32m"; static constexpr const char* FG_B_GREEN = "\033[92m"; + static constexpr const char* FG_YELLOW = "\033[33m"; static constexpr const char* FG_B_YELLOW = "\033[93m"; + static constexpr const char* FG_BLUE = "\033[34m"; static constexpr const char* FG_B_BLUE = "\033[94m"; + static constexpr const char* FG_MAGENTA = "\033[35m"; static constexpr const char* FG_B_MAGENTA = "\033[95m"; + static constexpr const char* FG_CYAN = "\033[36m"; static constexpr const char* FG_B_CYAN = "\033[96m"; + static constexpr const char* FG_WHITE = "\033[37m"; static constexpr const char* FG_B_WHITE = "\033[97m"; + + static constexpr const char* BG_BLACK = "\033[40m"; static constexpr const char* BG_B_BLACK = "\033[100m"; + static constexpr const char* BG_RED = "\033[41m"; static constexpr const char* BG_B_RED = "\033[101m"; + static constexpr const char* BG_GREEN = "\033[42m"; static constexpr const char* BG_B_GREEN = "\033[102m"; + static constexpr const char* BG_YELLOW = "\033[43m"; static constexpr const char* BG_B_YELLOW = "\033[103m"; + static constexpr const char* BG_BLUE = "\033[44m"; static constexpr const char* BG_B_BLUE = "\033[104m"; + static constexpr const char* BG_MAGENTA = "\033[45m"; static constexpr const char* BG_B_MAGENTA = "\033[105m"; + static constexpr const char* BG_CYAN = "\033[46m"; static constexpr const char* BG_B_CYAN = "\033[106m"; + static constexpr const char* BG_WHITE = "\033[47m"; static constexpr const char* BG_B_WHITE = "\033[107m"; + + public: + + static constexpr const u8 UP = 0x1; + static constexpr const u8 DOWN = 0x2; + static constexpr const u8 LEFT = 0x3; + static constexpr const u8 RIGHT = 0x4; + static constexpr const u8 ENTER = 0x5; + static constexpr const u8 ESC = 0x6; + static constexpr const u8 BACKSPACE = 0x7; + static constexpr const u8 UNKNOWN = 0x8; + + public: + + Terminal(); + + ~Terminal(); + + public: + + /** + * Sets a style + */ + Terminal& style(const std::string_view code); + + /** + * Fills the screen with a specific background color. + * @param color The background color constant (e.g., Terminal::BG_BLUE) + */ + Terminal& fill(const std::string_view color); + + /** + * Moves the cursor. + */ + Terminal& move(i32 row, i32 col); + + /** + * Clears the screen + */ + Terminal& cls(); + + Terminal& altbuff(bool enable); + + Terminal& scrollRange(i32 start, i32 end); + + Terminal& undoSRange(); + + /** + * Clears a specific row. + * @param row The 1-based index of the row to clear. + */ + Terminal& clearRow(i32 row); + + /** + * Clears a range of rows (inclusive). + * @param start The first row. + * @param end The last row. + */ + Terminal& clearRows(i32 start, i32 end); + + /** + * Shows / Hides the cursor. + */ + Terminal& cursor(bool show); + + public: + + Terminal& drawBox(i32 startRow, i32 startCol, i32 width, i32 height, std::string_view title); + + public: + + /** + * Flushes the output buffer + */ + Terminal& flush(); + + /** + * Clears the input buffer. + * Useful for some specific input cases + */ + Terminal& sink(); + + public: + + Terminal& wait(); + + u8 getKey(); + + std::pair size(); + + public: + + template + Terminal& print(const T& msg) { + std::cout << msg; + return *this; + } + + template + Terminal& println(const T& msg) { + std::cout << msg << "\n"; + return *this; + } + + template + Terminal& read(T& var) { + std::cin >> var; + return *this; + } + + }; + +} \ No newline at end of file From b4560c208f9e8188dca90275ede0ea395e849cc5 Mon Sep 17 00:00:00 2001 From: Kittycannon Date: Sat, 21 Mar 2026 10:08:24 -0600 Subject: [PATCH 02/10] integrated quaternions into code --- src/spider/runtime/math/Quat.cpp | 22 ++++++++++++++++ src/spider/runtime/math/Quat.hpp | 24 ++++++++++++++++++ src/spider/runtime/math/Quat_Multiply.cpp | 31 ----------------------- 3 files changed, 46 insertions(+), 31 deletions(-) create mode 100644 src/spider/runtime/math/Quat.cpp create mode 100644 src/spider/runtime/math/Quat.hpp delete mode 100644 src/spider/runtime/math/Quat_Multiply.cpp diff --git a/src/spider/runtime/math/Quat.cpp b/src/spider/runtime/math/Quat.cpp new file mode 100644 index 0000000..ae40493 --- /dev/null +++ b/src/spider/runtime/math/Quat.cpp @@ -0,0 +1,22 @@ +#include "Quat.hpp" + +#include + +namespace spider { + + int quatMain() { + Quat q1 = { 1.0f, 0.0f, 0.0f, 0.0f }; + Quat q2 = { 0.5f, 0.5f, 0.5f, 0.5f }; + + Quat result = quat_multiply(q1, q2); // Returns the result! + + std::cout << "Result: (" + << result.w << ", " + << result.x << ", " + << result.y << ", " + << result.z << ")" << std::endl; + + return 0; + } + +} diff --git a/src/spider/runtime/math/Quat.hpp b/src/spider/runtime/math/Quat.hpp new file mode 100644 index 0000000..676778c --- /dev/null +++ b/src/spider/runtime/math/Quat.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace spider { + + template + struct Quat { + T w, x, y, z; + }; + + /** + * Multiplies two quaternions together. + */ + template inline Quat quat_multiply(Quat A, Quat B) { + return { + B.w * A.w - B.x * A.x - B.y * A.y - B.z * A.z, + B.w * A.x + B.x * A.w - B.y * A.z + B.z * A.y, + B.w * A.y + B.x * A.z + B.y * A.w - B.z * A.x, + B.w * A.z - B.x * A.y + B.y * A.x + B.z * A.w + }; + } + +} diff --git a/src/spider/runtime/math/Quat_Multiply.cpp b/src/spider/runtime/math/Quat_Multiply.cpp deleted file mode 100644 index f41651c..0000000 --- a/src/spider/runtime/math/Quat_Multiply.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include - -template -struct Quaternion { - T w, x, y, z; -}; - -template -Quaternion quat_multiply(Quaternion A, Quaternion B) { - return { - B.w * A.w - B.x * A.x - B.y * A.y - B.z * A.z, - B.w * A.x + B.x * A.w - B.y * A.z + B.z * A.y, - B.w * A.y + B.x * A.z + B.y * A.w - B.z * A.x, - B.w * A.z - B.x * A.y + B.y * A.x + B.z * A.w - }; -} - -int main() { - Quaternion q1 = {1.0f, 0.0f, 0.0f, 0.0f}; - Quaternion q2 = {0.5f, 0.5f, 0.5f, 0.5f}; - - Quaternion result = quat_multiply(q1, q2); // Returns the result! - - std::cout << "Result: (" - << result.w << ", " - << result.x << ", " - << result.y << ", " - << result.z << ")" << std::endl; - - return 0; -} \ No newline at end of file From da1c090f19fc336e40ab2577b163677947db1af9 Mon Sep 17 00:00:00 2001 From: Kittycannon Date: Mon, 23 Mar 2026 07:22:00 -0600 Subject: [PATCH 03/10] Preparation for building instructions --- src/spider/runtime/common.hpp | 4 + src/spider/runtime/cpu/CPU.cpp | 3 +- src/spider/runtime/cpu/InstrReel.cpp | 56 +++- src/spider/runtime/cpu/InstrReel.hpp | 59 ++-- src/spider/runtime/cpu/InstrReelDyn.cpp | 192 ++++++++++++ src/spider/runtime/cpu/InstrReelDyn.hpp | 110 +++++++ src/spider/runtime/cpu/InstrReelFile.hpp | 0 src/spider/runtime/cpu/InstrReelFixed.cpp | 149 +++++++++ src/spider/runtime/cpu/InstrReelFixed.hpp | 43 +++ src/spider/runtime/debug/LiveDebug.cpp | 356 ++++++++++++++++++++-- src/spider/runtime/memory/ByteArray.cpp | 74 +++++ src/spider/runtime/memory/ByteArray.hpp | 49 +++ src/spider/runtime/memory/RAM.cpp | 16 + src/spider/runtime/memory/RAM.hpp | 10 + src/spider/runtime/memory/Types.hpp | 248 +++++++++++++++ src/spider/runtime/native/machine.hpp | 1 - src/spider/runtime/util/Terminal.cpp | 98 +++++- src/spider/runtime/util/Terminal.hpp | 86 ++++-- 18 files changed, 1454 insertions(+), 100 deletions(-) create mode 100644 src/spider/runtime/cpu/InstrReelDyn.cpp create mode 100644 src/spider/runtime/cpu/InstrReelDyn.hpp create mode 100644 src/spider/runtime/cpu/InstrReelFile.hpp create mode 100644 src/spider/runtime/cpu/InstrReelFixed.cpp create mode 100644 src/spider/runtime/cpu/InstrReelFixed.hpp create mode 100644 src/spider/runtime/memory/ByteArray.cpp create mode 100644 src/spider/runtime/memory/ByteArray.hpp create mode 100644 src/spider/runtime/memory/Types.hpp diff --git a/src/spider/runtime/common.hpp b/src/spider/runtime/common.hpp index 67f8ed8..3507fc0 100644 --- a/src/spider/runtime/common.hpp +++ b/src/spider/runtime/common.hpp @@ -8,6 +8,7 @@ namespace spider { + // Absolute Types using u8 = std::uint8_t; using u16 = std::uint16_t; using u32 = std::uint32_t; @@ -25,6 +26,9 @@ namespace spider { static_assert(sizeof(f32) == 4, "The f32 type must be exactly 4 bytes."); static_assert(sizeof(f64) == 8, "The f64 type must be exactly 8 bytes."); + // Utility types + using isize = std::size_t; + // Utility imports using std::vector; using std::deque; diff --git a/src/spider/runtime/cpu/CPU.cpp b/src/spider/runtime/cpu/CPU.cpp index fb8cdda..e9b456e 100644 --- a/src/spider/runtime/cpu/CPU.cpp +++ b/src/spider/runtime/cpu/CPU.cpp @@ -8,7 +8,8 @@ namespace spider { R2{}, R3{}, R4{}, R5{}, R6{}, R7{}, R8{}, R9{}, RF{}, RI{}, RS{}, RZ{}, - RE{}, RN{}, RV{}, RM{} + RE{}, RN{}, RV{}, RM{}, + ALU0{}, ALU1{} {} CPU::~CPU() {} diff --git a/src/spider/runtime/cpu/InstrReel.cpp b/src/spider/runtime/cpu/InstrReel.cpp index 3a6cfaa..88df155 100644 --- a/src/spider/runtime/cpu/InstrReel.cpp +++ b/src/spider/runtime/cpu/InstrReel.cpp @@ -1,16 +1,64 @@ #include "InstrReel.hpp" +#include + +#include + namespace spider { - InstrReel::InstrReel() {} + // Public Interface // + + InstrReel::InstrReel() : _mem(nullptr), _size(0), _offset(0), _total_size(0) {} InstrReel::~InstrReel() {} - u16 InstrReel::instrAt(u64 ip) const {} + // Instruction abstraction // - u8 InstrReel::dataAt(u64 ip) const {} + u8 InstrReel::atU8(u64 ip) { + // guard against access + u64 ip_p = ip - _offset; + if(ip_p + 1 > _size) return 0; - u8 InstrReel::feedNext(CPU& cpu) {} + // send byte + return _mem[ip]; + } + + u16 InstrReel::atU16(u64 ip) { + // guard against access + u64 ip_p = ip - _offset; + if(ip_p + 2 > _size) return 0; + + // build a 16-bit big endian number + u16 dat; + spider::loadLE(&dat, _mem + ip_p); + return dat; + } + + u32 InstrReel::atU32(u64 ip) { + // guard against access + u64 ip_p = ip - _offset; + if(ip_p + 4 > _size) return 0; + + // build a 32-bit big endian number + u32 dat; + spider::loadLE(&dat, _mem + ip_p); + return dat; + } + + u64 InstrReel::atU64(u64 ip) { + // guard against access + u64 ip_p = ip - _offset; + if(ip_p + 8 > _size) return 0; + + // build a 64-bit big endian number + u64 dat; + spider::loadLE(&dat, _mem + ip_p); + return dat; + } + + u64 InstrReel::size() { + return _total_size; + } // Static Utils // diff --git a/src/spider/runtime/cpu/InstrReel.hpp b/src/spider/runtime/cpu/InstrReel.hpp index 5e2175f..06a3d14 100644 --- a/src/spider/runtime/cpu/InstrReel.hpp +++ b/src/spider/runtime/cpu/InstrReel.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include namespace spider { @@ -8,43 +9,57 @@ namespace spider { * Implements an instruction reel. */ class InstrReel { - private: + protected: // Current accessing range // + + u8* _mem; + isize _size; + isize _offset; + isize _total_size; public: InstrReel(); - ~InstrReel(); + virtual ~InstrReel(); public: - - - public: - - /** - * Returns the two-byte instruction at the - * specific byte location. - */ - u16 instrAt(u64 ip) const; - /** * Obtains a byte of data at * the specific location. + * Reindexing may occur, continous access + * may incurr in less penalties. */ - u8 dataAt(u64 ip) const; - - public: + virtual u8 atU8(u64 ip); /** - * Fetches the data, and then - * feeds the instruction into the - * CPU. - * - * Returns how many steps it should - * move after. + * Obtains a byte of data at + * the specific location. + * Reindexing may occur, continous access + * may incurr in less penalties. */ - u8 feedNext(CPU& cpu); + virtual u16 atU16(u64 ip); + + /** + * Obtains a byte of data at + * the specific location. + * Reindexing may occur, continous access + * may incurr in less penalties. + */ + virtual u32 atU32(u64 ip); + + /** + * Obtains a byte of data at + * the specific location. + * Reindexing may occur, continous access + * may incurr in less penalties. + */ + virtual u64 atU64(u64 ip); + + /** + * Current size of the instructions. + */ + virtual u64 size(); public: // Static Utils // diff --git a/src/spider/runtime/cpu/InstrReelDyn.cpp b/src/spider/runtime/cpu/InstrReelDyn.cpp new file mode 100644 index 0000000..150e0b8 --- /dev/null +++ b/src/spider/runtime/cpu/InstrReelDyn.cpp @@ -0,0 +1,192 @@ +#include "InstrReelDyn.hpp" + +#include + +namespace spider { + + InstrReelDyn::InstrReelDyn(u64 length) : _use_count(0), _block_index(0) { + _total_size = length; + growToFit(length > 0 ? length - 1 : 0); + selectBlock(0); + } + + InstrReelDyn::InstrReelDyn(const u8* data, u64 length) {} + + InstrReelDyn::InstrReelDyn(const InstrReelDyn& copy) : _use_count(copy._use_count), _block_index(copy._block_index), _blocks(copy._blocks) { + if (_block_index < _blocks.size()) selectBlock(_block_index); + } + + InstrReelDyn::InstrReelDyn(InstrReelDyn&& move) noexcept : _use_count(move._use_count), _block_index(move._block_index), _blocks(std::move(move._blocks)) { + if (_block_index < _blocks.size()) selectBlock(_block_index); + } + + InstrReelDyn::~InstrReelDyn() { + // .. // + } + + InstrReelDyn& InstrReelDyn::operator=(const InstrReelDyn& copy) { + _use_count = copy._use_count; + _block_index = copy._block_index; + _blocks = copy._blocks; + if (_block_index < _blocks.size()) selectBlock(_block_index); + } + + InstrReelDyn& InstrReelDyn::operator=(InstrReelDyn&& move) noexcept { + _use_count = move._use_count; + _block_index = move._block_index; + _blocks = std::move(move._blocks); + if (_block_index < _blocks.size()) selectBlock(_block_index); + + move._use_count = 0; + move._block_index = 0; + move._mem = nullptr; + move._offset = 0; + move._size = 0; + move._total_size = 0; + } + + void InstrReelDyn::growToFit(isize index) { + while (_blocks.size() < (index + 1)) { + _blocks.emplace_back(); + } + } + + isize InstrReelDyn::selectIndex(u64 ip) { + return ip / 256; + } + + InstrReelDyn::ReelBlock* InstrReelDyn::selectBlock(isize index) { + // Update base class cache + auto ptr = &_blocks[index]; + _offset = index * 256; + _mem = ptr->data; + _size = 256; + _block_index = index; + + //_blocks[block_idx].access_count++; + return ptr; + } + + u8 InstrReelDyn::atU8(u64 ip) { + isize j = selectIndex(ip); + if (j >= _blocks.size()) return 0; + if (j != _block_index) { + this->selectBlock(j); + } + return _mem[ip - _offset]; + } + + u16 InstrReelDyn::atU16(u64 ip) { + isize j0 = selectIndex(ip); + isize j1 = selectIndex(ip + 1); + if (j1 >= _blocks.size()) return 0; + if (j0 == j1 && j0 != _block_index) { + selectBlock(j0); + } + if (j0 == j1 && j0 == _block_index) { + u16 dat; + spider::loadLE(&dat, _mem); + return dat; + } + + // general case, first part + u16 dat = 0; + const u8 size = sizeof(u16); + + // select first block and offset + selectBlock(j0); + u8 rem = ip % 256; + + for (u8 n = 0; n < size; n++) { + dat |= _mem[rem++] << (n * 8); + ip++; + if (!rem) selectBlock(++j0); + } + + return dat; + } + + u32 InstrReelDyn::atU32(u64 ip) { + isize j0 = selectIndex(ip); + isize j1 = selectIndex(ip + 3); + if (j1 >= _blocks.size()) return 0; + if (j0 == j1 && j0 != _block_index) { + selectBlock(j0); + } + if (j0 == j1 && j0 == _block_index) { + u32 dat; + spider::loadLE(&dat, _mem); + return dat; + } + + // general case, first part + u32 dat = 0; + const u8 size = sizeof(u32); + + // select first block and offset + selectBlock(j0); + u8 rem = ip % 256; + + for (u8 n = 0; n < size; n++) { + dat |= _mem[rem++] << (n * 8); + ip++; + if (!rem) selectBlock(++j0); + } + + return dat; + } + + u64 InstrReelDyn::atU64(u64 ip) { + isize j0 = selectIndex(ip); + isize j1 = selectIndex(ip + 3); + if (j1 >= _blocks.size()) return 0; + if (j0 == j1 && j0 != _block_index) { + selectBlock(j0); + } + if (j0 == j1 && j0 == _block_index) { + u64 dat; + spider::loadLE(&dat, _mem); + return dat; + } + + // general case, first part + u64 dat = 0; + const u8 size = sizeof(u64); + + // select first block and offset + selectBlock(j0); + u8 rem = ip % 256; + + for (u8 n = 0; n < size; n++) { + dat |= _mem[rem++] << (n * 8); + ip++; + if (!rem) selectBlock(++j0); + } + + return dat; + } + + void InstrReelDyn::at(u64 ip, u8 dat) {} + + void InstrReelDyn::at(u64 ip, u16 dat) {} + + void InstrReelDyn::at(u64 ip, u32 dat) {} + + void InstrReelDyn::at(u64 ip, u64 dat) {} + + /** + * Appends instruction at location. + */ + void InstrReelDyn::append(u64 ip, u16 bc) {} + + /** + * Appends instruction at the end. + */ + void InstrReelDyn::append(u16 bc) {} + + /** + * Removes instruction at location. + */ + void InstrReelDyn::remove(u64 ip) {} + +} diff --git a/src/spider/runtime/cpu/InstrReelDyn.hpp b/src/spider/runtime/cpu/InstrReelDyn.hpp new file mode 100644 index 0000000..75912da --- /dev/null +++ b/src/spider/runtime/cpu/InstrReelDyn.hpp @@ -0,0 +1,110 @@ +#pragma once + +#include + +namespace spider { + + /** + * Implements an instruction reel. + */ + class InstrReelDyn : public InstrReel { + private: + + struct ReelBlock { + u8 data[256] = {}; + }; + + private: + + u64 _use_count; + isize _block_index; + std::deque _blocks; + + public: + + InstrReelDyn(u64 length); + + InstrReelDyn(const u8* data, u64 length); + + InstrReelDyn(const InstrReelDyn& copy); + + InstrReelDyn(InstrReelDyn&& move) noexcept; + + virtual ~InstrReelDyn(); + + public: + + InstrReelDyn& operator=(const InstrReelDyn& copy); + + InstrReelDyn& operator=(InstrReelDyn&& move) noexcept; + + private: + + isize selectIndex(u64 ip); + + void growToFit(isize index); + + ReelBlock* selectBlock(isize index); + + public: + + /** + * Obtains a byte of data at + * the specific location. + * Reindexing may occur, continous access + * may incurr in less penalties. + */ + virtual u8 atU8(u64 ip) override; + + /** + * Obtains a byte of data at + * the specific location. + * Reindexing may occur, continous access + * may incurr in less penalties. + */ + virtual u16 atU16(u64 ip) override; + + /** + * Obtains a byte of data at + * the specific location. + * Reindexing may occur, continous access + * may incurr in less penalties. + */ + virtual u32 atU32(u64 ip) override; + + /** + * Obtains a byte of data at + * the specific location. + * Reindexing may occur, continous access + * may incurr in less penalties. + */ + virtual u64 atU64(u64 ip) override; + + public: + + void at(u64 ip, u8 dat); + + void at(u64 ip, u16 dat); + + void at(u64 ip, u32 dat); + + void at(u64 ip, u64 dat); + + /** + * Appends instruction at location. + */ + void append(u64 ip, u16 bc); + + /** + * Appends instruction at the end. + */ + void append(u16 bc); + + /** + * Removes instruction at location. + */ + void remove(u64 ip); + + }; + +} diff --git a/src/spider/runtime/cpu/InstrReelFile.hpp b/src/spider/runtime/cpu/InstrReelFile.hpp new file mode 100644 index 0000000..e69de29 diff --git a/src/spider/runtime/cpu/InstrReelFixed.cpp b/src/spider/runtime/cpu/InstrReelFixed.cpp new file mode 100644 index 0000000..0c41e7f --- /dev/null +++ b/src/spider/runtime/cpu/InstrReelFixed.cpp @@ -0,0 +1,149 @@ +#include "InstrReelFixed.hpp" + +#include + +#include + +namespace spider { + + // Constructors & Destructors // + + InstrReelFixed::InstrReelFixed(u64 length) { + this->_offset = 0; + this->_size = length; + this->_total_size = length; + + if (_size > 0) { + _mem = new u8[_size]; + std::memset(_mem, 0, _size); + } + } + + InstrReelFixed::InstrReelFixed(const u8* data, u64 length) { + this->_offset = 0; + this->_size = length; + this->_total_size = length; + + if (_size > 0) { + _mem = new u8[_size]; + std::copy(data, data + _size, _mem); + } + } + + InstrReelFixed::InstrReelFixed(const InstrReelFixed& other) { + _offset = other._offset; + _size = other._size; + _total_size = other._total_size; + _mem = new u8[_size]; + std::copy(other._mem, other._mem + _size, _mem); + } + + InstrReelFixed::InstrReelFixed(InstrReelFixed&& other) noexcept { + _mem = other._mem; + _offset = other._offset; + _size = other._size; + _total_size = other._total_size; + + other._mem = nullptr; + other._offset = 0; + other._size = 0; + other._total_size = 0; + } + + InstrReelFixed::~InstrReelFixed() { + delete[] _mem; + } + + // Assign Operators // + + InstrReelFixed& InstrReelFixed::operator=(const InstrReelFixed& other) { + if (this == &other) return *this; // lock self + + u8* new_mem = new u8[other._size]; + std::copy(other._mem, other._mem + other._size, new_mem); + + delete[] _mem; + _mem = new_mem; + _offset = other._offset; + _size = other._size; + _total_size = other._total_size; + + return *this; + } + + InstrReelFixed& InstrReelFixed::operator=(InstrReelFixed&& other) noexcept { + if (this == &other) return *this; // lock self + + delete[] _mem; + + _mem = other._mem; // steal + _offset = other._offset; + _size = other._size; + _total_size = other._total_size; + + other._mem = nullptr; // leave as husk + other._offset = 0; + other._size = 0; + other._total_size = 0; + + return *this; + } + + // Misc // + + void InstrReelFixed::at(u64 ip, u8 dat) { + if(ip + 1 > _size) return; + _mem[ip] = dat; + } + + void InstrReelFixed::at(u64 ip, u16 dat) { + if(ip + 2 > _size) return; + spider::storeLE(dat, _mem + ip); + } + + void InstrReelFixed::at(u64 ip, u32 dat) { + if(ip + 4 > _size) return; + spider::storeLE(dat, _mem + ip); + } + + void InstrReelFixed::at(u64 ip, u64 dat) { + if(ip + 8 > _size) return; + spider::storeLE(dat, _mem + ip); + } + + void InstrReelFixed::resize(u64 new_size) { + // Special case 1 + if (new_size == _size) return; + + // Special case 2 + if (new_size == 0) { + delete[] _mem; + _mem = nullptr; + _size = 0; + _total_size = 0; + return; + } + + // 1. Allocate the new block + u8* new_mem = new u8[new_size]; + + // 2. Zero-initialize + std::memset(new_mem, 0, new_size); + + // 3. Preserve data + // If shrinking, copy 'new_size' bytes. If growing, copy 'old_size' bytes. + u64 bytes_to_copy = (new_size < _size) ? new_size : _size; + + // 3.1 Previous size could be zero, where _mem would be null + if (_mem != nullptr) { + std::copy(_mem, _mem + bytes_to_copy, new_mem); + } + + // 4. Swap and Clean up + delete[] _mem; + _mem = new_mem; + _size = new_size; + _total_size = new_size; + } + +} diff --git a/src/spider/runtime/cpu/InstrReelFixed.hpp b/src/spider/runtime/cpu/InstrReelFixed.hpp new file mode 100644 index 0000000..dd8f4ec --- /dev/null +++ b/src/spider/runtime/cpu/InstrReelFixed.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include + +namespace spider { + + /** + * Implements an instruction reel. + */ + class InstrReelFixed : public InstrReel { + public: + + InstrReelFixed(u64 length); + + InstrReelFixed(const u8* data, u64 length); + + InstrReelFixed(const InstrReelFixed& copy); + + InstrReelFixed(InstrReelFixed&& move) noexcept; + + virtual ~InstrReelFixed(); + + public: + + InstrReelFixed& operator=(const InstrReelFixed& copy); + + InstrReelFixed& operator=(InstrReelFixed&& move) noexcept; + + public: + + void at(u64 ip, u8 dat); + + void at(u64 ip, u16 dat); + + void at(u64 ip, u32 dat); + + void at(u64 ip, u64 dat); + + void resize(u64 new_size); + + }; + +} diff --git a/src/spider/runtime/debug/LiveDebug.cpp b/src/spider/runtime/debug/LiveDebug.cpp index 56b23be..f1301d4 100644 --- a/src/spider/runtime/debug/LiveDebug.cpp +++ b/src/spider/runtime/debug/LiveDebug.cpp @@ -2,33 +2,45 @@ #include #include +#include #include #include - +#include +#include +#include +#include +#include namespace spider { void drawHead(Terminal& t) { t.move(1, 1) - .style(Terminal::RESET) - .print(" Spider Runtime Live Debug ") - .style(Terminal::RESET).print(" | ") - .style(Terminal::FG_CYAN).print(" Sintek Analytics @ 2026 ") - .style(Terminal::RESET).print(" | ") - .style(Terminal::FG_B_BLACK).print("Press ESC to exit") - .style(Terminal::FG_BLACK) - .style(Terminal::BG_YELLOW) - .move(3, 1).print(" || __ ||").print(" ") - .move(4, 1).print(" \\\\( )//").print(" SPIDER v0.1 ") - .move(5, 1).print(" //()\\\\ ").print(" alpha ") - .move(6, 1).print(" || || ").print(" ") - .style(Terminal::RESET) - ; + .style(Terminal::FG_YELLOW) + .print(" Spider Runtime Live Debug ") + .style(Terminal::RESET).print(" | ") + .style(Terminal::FG_B_CYAN).print(" Sintek Analytics @ 2026 ") + .style(Terminal::RESET).print(" | ") + .style(Terminal::FG_B_BLACK).print("Press ESC to exit") + .style(Terminal::FG_BLACK) + .style(Terminal::BG_YELLOW) + .move(3, 1).print(" // __ \\\\").print(" ") // 27 + .move(4, 1).print(" \\\\( )//").print(" SPIDER v0.1 ") + .move(5, 1).print(" //()\\\\ ").print(" alpha ") + .move(6, 1).print(" \\\\ // ").print(" ") + .style(Terminal::RESET) + .style(Terminal::FG_B_BLACK) // 4x8 for the menu + .move(3, 28).print("[ STEP ]") + .move(4, 28).print("[ STOP ]") + .move(5, 28).print("[ RUN ]") + .move(6, 28).print("[ MENU ]") + .style(Terminal::RESET) + ; } void drawCPUTempl(Terminal& t, CPU& cpu) { - i32 r = 7, c = 1; - t.drawBox(r, c, 35, 18, "CPU"); + i32 r = 8, c = 1; + i32 w = 35, h = 31; + t.drawBox(r, c, w, h, "CPU"); const std::string regs[] = { "RA", "RB", "RC", "RD", @@ -36,47 +48,335 @@ namespace spider { "R2", "R3", "R4", "R5", "R6", "R7", "R8", "R9", "RF", "RI", "RS", "RZ", - "RE", "RN", "RV", "RM" + "RE", "RN", "RV", "RM", + "ALU0", "ALU1" + }; + const std::string alt[] = { + Terminal::FG_WHITE, + Terminal::FG_B_BLACK, }; r++; c++; - - - for(i32 i = 0; i < 8; i++) { + t.move(r++, c); + t.style(Terminal::FG_B_YELLOW); + t.print_center(w - 2, "GP Registers"); + t.style(Terminal::RESET); + for (i32 i = 0; i < 8; i++) { + t.style(alt[i & 1]); t.move(r + i * 2, c); t.print(regs[i * 2]); t.move(r + i * 2, c + 17); t.print(regs[i * 2 + 1]); } + + t.move(r += 16, c); + t.style(Terminal::FG_B_CYAN); + t.print_center(w - 2, "System Registers"); + t.style(Terminal::RESET); + r++; + for (i32 j = 0, i = 8; i < 12; j++, i++) { + t.style(alt[j & 1]); + t.move(r + j * 2, c); + t.print(regs[i * 2]); + t.move(r + j * 2, c + 17); + t.print(regs[i * 2 + 1]); + } + + t.move(r += 8, c); + t.style(Terminal::FG_GREEN); + t.print_center(w - 2, "Extra Registers"); + t.style(Terminal::RESET); + r++; + for (i32 j = 0, i = 12; i < 13; j++, i++) { + t.style(alt[j & 1]); + t.move(r + j * 2, c); + t.print(regs[i * 2]); + t.move(r + j * 2, c + 17); + t.print(regs[i * 2 + 1]); + } + + t.flush(); + } + + void printU64Hex(u64 n) { + std::ios state(nullptr); + state.copyfmt(std::cout); + std::cout + << std::hex + << std::uppercase + << std::setfill('0') + << std::setw(16) + << n; + std::cout.copyfmt(state); + } + + void drawCPU(Terminal& t, CPU& cpu) { + i32 r = 8, c = 1; + + const register_t* regs[] = { + &cpu.RA, &cpu.RB, &cpu.RC, &cpu.RD, + &cpu.RX, &cpu.RY, &cpu.R0, &cpu.R1, + &cpu.R2, &cpu.R3, &cpu.R4, &cpu.R5, + &cpu.R6, &cpu.R7, &cpu.R8, &cpu.R9, + //&cpu.RF, &cpu.RI, &cpu.RS, &cpu.RZ, + //&cpu.RE, &cpu.RN, &cpu.RV, &cpu.RM, + &cpu.ALU0, &cpu.ALU1 + }; + const u64* sys_regs[] = { + &cpu.RF, &cpu.RI, &cpu.RS, &cpu.RZ, + &cpu.RE, &cpu.RN, &cpu.RV, &cpu.RM, + }; + const std::string alt[] = { + Terminal::FG_WHITE, + Terminal::FG_B_BLACK, + }; + + r++; + c++; + t.move(r++, c); + t.style(Terminal::RESET); + r++; + for (i32 i = 0; i < 8; i++) { + t.style(alt[i & 1]); + t.move(r + i * 2, c); + printU64Hex(regs[i * 2]->_u64); + t.move(r + i * 2, c + 17); + printU64Hex(regs[i * 2 + 1]->_u64); + } + + t.move(r += 16, c); + r++; + for (i32 j = 0, i = 8; i < 12; j++, i++) { + t.style(alt[j & 1]); + t.move(r + j * 2, c); + printU64Hex(*sys_regs[i * 2]); + t.move(r + j * 2, c + 17); + printU64Hex(*sys_regs[i * 2 + 1]); + } + + t.move(r += 8, c); + r++; + for (i32 j = 0; j < 1; j++) { + t.style(alt[j & 1]); + t.move(r + j * 2, c); + printU64Hex(regs[16 + j * 2]->_u64); + t.move(r + j * 2, c + 17); + printU64Hex(regs[16 + j * 2 + 1]->_u64); + } + + t.flush(); + } + + i32 addressWidth(isize ramSize) { + if (ramSize == 0) return 1; + isize maxAddr = ramSize - 1; + i32 digits = 0; + // Shift by increments of 4 (one hex nibble) + // We use a do-while to ensure at least 1 digit is returned for small RAMs + do { + digits++; + maxAddr >>= 4; + } while (maxAddr > 0); + + return digits; + } + + /** + * Draws a vertical scrollbar + * @param x The column where the bar should be placed (usually box_x + width - 1) + * @param y The starting row of the track (usually box_y + 1) + * @param trackHeight The internal height of the box (box_height - 2) + * @param progress The current progress + * @param total The total + */ + void drawScrollThumb(Terminal& term, i32 x, i32 y, i32 trackHeight, isize progress, isize total) { + if (total == 0 || trackHeight <= 0) return; + + // 1. Draw the background track (Light Shade: ░) + term.style(Terminal::FG_B_BLACK); // Dim the track + for (int i = 0; i < trackHeight; ++i) { + term.move(y + i, x).print("░"); + } + + // 2. Calculate Thumb Position + // Cap progress to total to avoid overflow + if (progress > total) progress = total; + + // Calculate ratio (0.0 to 1.0) + f64 ratio = f64(progress) / f64(total); + + // Map to track coordinates + i32 thumbOffset = i32(ratio * (trackHeight - 1)); + + // 3. Draw the Thumb (Full Block: █) + term.move(y + thumbOffset, x); + term.style(Terminal::FG_WHITE).print("█"); + term.style(Terminal::RESET); + } + + /** + * Draws a hex dump of memory within a styled terminal box. + * @param term Reference to your Terminal instance + * @param ram The RAM + * @param scrollPos The starting address to display + * @param x Starting column + * @param y Starting row + * @param width Width of the box + * @param height Height of the box + */ + void drawRAM(Terminal& term, RAM& ram, u64 scrollPos) { + // 1. Draw the container box + i32 y = 3; + i32 height = 36; + + // 2. Configuration for the hex layout + int addrWidth = addressWidth(ram.size()); + int bytesPerRow = 8; + int displayRows = height - 2; // Subtract top/bottom borders + i32 width = (2 + 2 + 16 + 7 + 3 + 8 + 4) + addrWidth; + i32 x = 37; + + // create box + term.drawBox(y, x, width, height, "RAM"); + drawScrollThumb(term, x + width - 2, y + 1, height - 2, scrollPos, ram.size()); + + // Ensure scrollPos is within bounds and aligned + if (scrollPos < 0) scrollPos = 0; + if (scrollPos > ram.size()) scrollPos = ram.size(); + + for (int i = 0; i < displayRows; ++i) { + isize currentRowAddr = scrollPos + (i * bytesPerRow); + + // address lock + if (currentRowAddr >= ram.size()) { + term.move(y + 1 + i, x + 1); + term.print(std::string(width - 3, ' ')); + continue; + } + + std::stringstream ssaddr; + std::stringstream ss; + + // setup ss + ssaddr << std::setfill('0') << std::uppercase << std::hex; + ss << std::setfill('0') << std::uppercase << std::hex; + + // address + ssaddr << std::setw(addrWidth) << currentRowAddr << " "; + + // Hex Bytes + std::string asciiPart = ""; + for (int j = 0; j < bytesPerRow; ++j) { + isize targetAddr = currentRowAddr + j; + if (targetAddr >= ram.size()) { + ss << ""; // Padding for end of memory + asciiPart += ""; + continue; + } + + u8 byte = ram[targetAddr]; + ss << std::setfill('0') << std::setw(2) << std::hex << (u32)byte << " "; + asciiPart += (std::isprint(byte) ? (char)byte : '.'); + } + + // --- Combine and Print --- + term.move(y + 1 + i, x + 2); // Move inside the box + term.style(Terminal::FG_B_CYAN).print(ssaddr.str()); // Hex part in Cyan + term.style(Terminal::FG_WHITE).print(ss.str()); + term.style(Terminal::FG_B_YELLOW).print(" | "); + term.style(Terminal::FG_WHITE).print(asciiPart); // ASCII part in White + } + + term.style(Terminal::RESET); + term.flush(); + } + + std::string getTimestamp() { + std::time_t t = std::time(nullptr); + std::tm lt; +#if defined(SPIDER_OS_WINDOWS) + localtime_s(<, &t); +#endif +#if defined(SPIDER_OS_LINUX) || defined(SPIDER_OS_MACOS) + localtime_r(&t, <); +#endif + return std::format("{:02}:{:02}:{:02} {:02}/{:02}/{}", + lt.tm_hour, lt.tm_min, lt.tm_sec, + lt.tm_mday, lt.tm_mon + 1, lt.tm_year + 1900); + } + + void drawTime(Terminal& t) { + //auto now = std::chrono::system_clock::now(); + //auto now_l = std::chrono::current_zone()->to_local(now); + //auto now_s = std::chrono::floor(now_l); + //std::string time_str = std::format("{:%H:%M:%S}", now_s); // Format: HH:mm:ss + //std::string date_str = std::format("{:%d/%m/%Y}", now_s); // Format: dd/MM/YYYY + + t.move(1, 76); + t.style(Terminal::RESET); + t.print(" | ").style(Terminal::FG_GREEN).print(getTimestamp()); + } + + void redraw(Terminal& t, Runtime& r, u64 scroll) { + // draw CPU, RAM + drawCPU(t, r.cpu); + drawRAM(t, r.ram, scroll); } int liveDebugMain() { Terminal t; Runtime runtime(1024); + bool running = true, update = true; + u64 ramScroll = 0; + u8 key = Terminal::UNKNOWN; + t.println("Starting Spider live debug..."); t.altbuff(true).cursor(false); + drawTime(t); drawHead(t); drawCPUTempl(t, runtime.cpu); - bool running = true; - int c = 1; + // delay for time + auto last_exec = std::chrono::steady_clock::now(); + auto delay = std::chrono::milliseconds(1000); + while (running) { + // draw time + auto now = std::chrono::steady_clock::now(); + if (now - last_exec >= delay) { + drawTime(t); + last_exec = now; + } + + // redraw something if it updated + if (update) { + redraw(t, runtime, ramScroll); + update = false; + } + // Handle Input - u8 k = t.getKey(); - switch (k) { + key = t.getKeyNb(); + switch (key) { case Terminal::ESC: running = false; break; + case Terminal::UP: + if (ramScroll >= 16) ramScroll -= 16; + update = true; + break; + case Terminal::DOWN: + if (runtime.ram.size() >= 16 && ramScroll <= runtime.ram.size() - 16) ramScroll += 16; + update = true; + break; default: - t.move(5, c++); - t.print("A"); - t.flush(); break; } + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } t.altbuff(false).println("Stopped Spider live debug.").flush(); diff --git a/src/spider/runtime/memory/ByteArray.cpp b/src/spider/runtime/memory/ByteArray.cpp new file mode 100644 index 0000000..507fa67 --- /dev/null +++ b/src/spider/runtime/memory/ByteArray.cpp @@ -0,0 +1,74 @@ +#include "ByteArray.hpp" + +#include + +namespace spider { + + ByteArray::ByteArray(isize length) : _mem(nullptr), _size(length) { + if (_size > 0) { + _mem = new u8[_size]; + std::memset(_mem, 0, _size); + } + } + + ByteArray::ByteArray(const ByteArray& other) : _mem(new u8[other._size]), _size(other._size) { + std::copy(other._mem, other._mem + _size, _mem); + } + + ByteArray::ByteArray(ByteArray&& other) noexcept : _mem(other._mem), _size(other._size) { + other._mem = nullptr; + other._size = 0; + } + + ByteArray::~ByteArray() { + delete[] _mem; + } + + ByteArray& ByteArray::operator=(const ByteArray& other) { + if (this == &other) return *this; // lock self + + u8* new_mem = new u8[other._size]; + std::copy(other._mem, other._mem + other._size, new_mem); + + delete[] _mem; + _mem = new_mem; + _size = other._size; + + return *this; + } + + ByteArray& ByteArray::operator=(ByteArray&& other) noexcept { + if (this == &other) return *this; // lock self + + delete[] _mem; + + _mem = other._mem; // time to steal! + _size = other._size; + + other._mem = nullptr; // leave as a husk + other._size = 0; + + return *this; + } + + u8& ByteArray::operator[](isize index) { + return _mem[index]; + } + + u8 ByteArray::operator[](isize index) const { + return _mem[index]; + } + + u8* ByteArray::data() { + return _mem; + } + + const u8* ByteArray::data() const { + return _mem; + } + + isize ByteArray::size() const { + return _size; + } + +} diff --git a/src/spider/runtime/memory/ByteArray.hpp b/src/spider/runtime/memory/ByteArray.hpp new file mode 100644 index 0000000..69ca2e7 --- /dev/null +++ b/src/spider/runtime/memory/ByteArray.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include + +namespace spider { + + /** + * A general purpose byte array + * with RAII semantics. + */ + class ByteArray { + private: + + u8* _mem; + isize _size; + + public: + + ByteArray(isize length); + + ByteArray(const ByteArray& other); + + ByteArray(ByteArray&& other) noexcept; + + ~ByteArray(); + + public: + + ByteArray& operator=(const ByteArray& other); + + ByteArray& operator=(ByteArray&& other) noexcept; + + public: + + u8& operator[](isize index); + + u8 operator[](isize index) const; + + public: + + u8* data(); + + const u8* data() const; + + isize size() const; + + }; + +} diff --git a/src/spider/runtime/memory/RAM.cpp b/src/spider/runtime/memory/RAM.cpp index c47d7f2..5c65303 100644 --- a/src/spider/runtime/memory/RAM.cpp +++ b/src/spider/runtime/memory/RAM.cpp @@ -111,4 +111,20 @@ namespace spider { return _size; } + u8* RAM::begin() { + return this->_mem; + } + + u8* RAM::end() { + return this->_mem + this->_size; + } + + const u8* RAM::begin() const { + return this->_mem; + } + + const u8* RAM::end() const { + return this->_mem + this->_size; + } + } diff --git a/src/spider/runtime/memory/RAM.hpp b/src/spider/runtime/memory/RAM.hpp index 6df9cdc..c62cd46 100644 --- a/src/spider/runtime/memory/RAM.hpp +++ b/src/spider/runtime/memory/RAM.hpp @@ -49,6 +49,16 @@ namespace spider { u64 size() const; + public: + + u8* begin(); + + u8* end(); + + const u8* begin() const; + + const u8* end() const; + }; } \ No newline at end of file diff --git a/src/spider/runtime/memory/Types.hpp b/src/spider/runtime/memory/Types.hpp new file mode 100644 index 0000000..7d250ee --- /dev/null +++ b/src/spider/runtime/memory/Types.hpp @@ -0,0 +1,248 @@ +#pragma once + +#include +#include + +#if __cplusplus >= 202002L + #include +#endif + +#if defined(SPIDER_COMPILER_MSVC) + #include +#endif + +namespace spider { + + /* + * This file contains cross platform type juggling. + * Optimized for each case. + * + * General assumtions is that unsigned and signed + * integers are the exact same bit representation. + * + * Floats to and from integers is the focus when + * juggling. + * + * Additionally, provides help selecting a specific + * type from raw bytes. + */ + + // Utilities // + +#if __cplusplus >= 202002L + using std::bit_cast; +#else + template + inline To bit_cast(const From& src) { + static_assert(sizeof(To) == sizeof(From), "bit_cast size mismatch"); + To dst; + std::memcpy(&dst, &src, sizeof(To)); + return dst; + } +#endif + + template + inline T byteswap(T v) { + static_assert(std::is_integral::value, "byteswap requires integral type"); + + using U = std::make_unsigned_t; + U u = static_cast(v); + + if constexpr (sizeof(T) == 1) { + return v; + } else if constexpr (sizeof(T) == 2) { + +#if defined(SPIDER_COMPILER_MSVC) + u = _byteswap_ushort(u); +#elif defined(SPIDER_COMPILER_GCC_LIKE) + u = __builtin_bswap16(u); +#else + u = (u >> 8) | (u << 8); +#endif + + } else if constexpr (sizeof(T) == 4) { + +#if defined(SPIDER_COMPILER_MSVC) + u = _byteswap_ulong(u); +#elif defined(SPIDER_COMPILER_GCC_LIKE) + u = __builtin_bswap32(u); +#else + u = + ((u & 0x000000FFu) << 24) | + ((u & 0x0000FF00u) << 8) | + ((u & 0x00FF0000u) >> 8) | + ((u & 0xFF000000u) >> 24); +#endif + + } else if constexpr (sizeof(T) == 8) { + +#if defined(SPIDER_COMPILER_MSVC) + u = _byteswap_uint64(u); +#elif defined(SPIDER_COMPILER_GCC_LIKE) + u = __builtin_bswap64(u); +#else + u = + ((u & 0x00000000000000FFull) << 56) | + ((u & 0x000000000000FF00ull) << 40) | + ((u & 0x0000000000FF0000ull) << 24) | + ((u & 0x00000000FF000000ull) << 8) | + ((u & 0x000000FF00000000ull) >> 8) | + ((u & 0x0000FF0000000000ull) >> 24) | + ((u & 0x00FF000000000000ull) >> 40) | + ((u & 0xFF00000000000000ull) >> 56); +#endif + + } else { + // Generic fallback (rare: non 1/2/4/8-byte types) + U result = 0; + for (size_t i = 0; i < sizeof(T); ++i) { + result |= ((u >> (i * 8)) & 0xFF) << ((sizeof(T) - 1 - i) * 8); + } + u = result; + } + + return static_cast(u); + } + + // Store Big Endian // + + template + inline void storeBE(T n, u8* bytes) { + static_assert(std::is_trivially_copyable::value); +#if SPIDER_BIG_ENDIAN + std::memcpy(bytes, &n, sizeof(T)); +#endif +#if SPIDER_LITTLE_ENDIAN + T tmp = byteswap(n); + std::memcpy(bytes, &tmp, sizeof(T)); +#endif +#if !SPIDER_BIG_ENDIAN && !SPIDER_LITTLE_ENDIAN + for (size_t i = 0; i < sizeof(T); ++i) { + bytes[i] = static_cast( + (static_cast>(n) >> ((sizeof(T) - 1 - i) * 8)) & 0xFF + ); + } +#endif + } + + template<> + inline void storeBE(f32 n, u8* bytes) { + u32 tmp = bit_cast(n); + storeBE(tmp, bytes); + } + + template<> + inline void storeBE(f64 n, u8* bytes) { + u64 tmp = bit_cast(n); + storeBE(tmp, bytes); + } + + // Load Big Endian // + + template + inline void loadBE(T* n, const u8* bytes) { + static_assert(std::is_trivially_copyable::value); +#if SPIDER_BIG_ENDIAN + std::memcpy(n, bytes, sizeof(T)); +#endif +#if SPIDER_LITTLE_ENDIAN + T tmp; + std::memcpy(&tmp, bytes, sizeof(T)); + *n = bswap(tmp); +#endif +#if !SPIDER_BIG_ENDIAN && !SPIDER_LITTLE_ENDIAN + using U = std::make_unsigned_t; + U result = 0; + for (size_t i = 0; i < sizeof(T); ++i) { + result |= static_cast(bytes[i]) << ((sizeof(T) - 1 - i) * 8); + } + *n = static_cast(result); +#endif + } + + template<> + inline void loadBE(f32* n, const u8* bytes) { + u32 tmp; + loadBE(&tmp, bytes); + *n = bit_cast(tmp); + } + + template<> + inline void loadBE(f64* n, const u8* bytes) { + u64 tmp; + loadBE(&tmp, bytes); + *n = bit_cast(tmp); + } + + // Store Little Endian // + + template + inline void storeLE(T n, u8* bytes) { + static_assert(std::is_trivially_copyable::value); +#if SPIDER_BIG_ENDIAN + T tmp = byteswap(n); + std::memcpy(bytes, &tmp, sizeof(T)); +#endif +#if SPIDER_LITTLE_ENDIAN + std::memcpy(bytes, &n, sizeof(T)); +#endif +#if !SPIDER_BIG_ENDIAN && !SPIDER_LITTLE_ENDIAN + for (size_t i = 0; i < sizeof(T); ++i) { + bytes[i] = static_cast( + (static_cast>(n) >> (i * 8)) & 0xFF + ); + } +#endif + } + + template<> + inline void storeLE(f32 n, u8* bytes) { + u32 tmp = bit_cast(n); + storeLE(tmp, bytes); + } + + template<> + inline void storeLE(f64 n, u8* bytes) { + u64 tmp = bit_cast(n); + storeLE(tmp, bytes); + } + + // Load Little Endian // + + template + inline void loadLE(T* n, const u8* bytes) { + static_assert(std::is_trivially_copyable::value); +#if SPIDER_BIG_ENDIAN + std::memcpy(n, bytes, sizeof(T)); +#endif +#if SPIDER_LITTLE_ENDIAN + T tmp; + std::memcpy(&tmp, bytes, sizeof(T)); + *n = bswap(tmp); +#endif +#if !SPIDER_BIG_ENDIAN && !SPIDER_LITTLE_ENDIAN + using U = std::make_unsigned_t; + U result = 0; + for (size_t i = 0; i < sizeof(T); ++i) { + result |= static_cast(bytes[i]) << (i * 8); + } + *n = static_cast(result); +#endif + } + + template<> + inline void loadLE(f32* n, const u8* bytes) { + u32 tmp; + loadLE(&tmp, bytes); + *n = bit_cast(tmp); + } + + template<> + inline void loadLE(f64* n, const u8* bytes) { + u64 tmp; + loadLE(&tmp, bytes); + *n = bit_cast(tmp); + } + +} + diff --git a/src/spider/runtime/native/machine.hpp b/src/spider/runtime/native/machine.hpp index 2b6fe1f..d48186c 100644 --- a/src/spider/runtime/native/machine.hpp +++ b/src/spider/runtime/native/machine.hpp @@ -58,7 +58,6 @@ // (not used now) // // ========================================================== // - // Find out what compiler the user is using #if defined(__clang__) #define SPIDER_COMPILER_CLANG diff --git a/src/spider/runtime/util/Terminal.cpp b/src/spider/runtime/util/Terminal.cpp index 0883191..52eb4c8 100644 --- a/src/spider/runtime/util/Terminal.cpp +++ b/src/spider/runtime/util/Terminal.cpp @@ -17,10 +17,45 @@ namespace spider { + // Style // + const char* Terminal::RESET = "\033[0m"; + const char* Terminal::BOLD = "\033[1m"; + const char* Terminal::ITALIC = "\033[3m"; + const char* Terminal::FAINT = "\033[2m"; + const char* Terminal::STRIKE = "\033[9m"; + + // Foreground // + const char* Terminal::FG_BLACK = "\033[30m"; const char* Terminal::FG_B_BLACK = "\033[90m"; + const char* Terminal::FG_RED = "\033[31m"; const char* Terminal::FG_B_RED = "\033[91m"; + const char* Terminal::FG_GREEN = "\033[32m"; const char* Terminal::FG_B_GREEN = "\033[92m"; + const char* Terminal::FG_YELLOW = "\033[33m"; const char* Terminal::FG_B_YELLOW = "\033[93m"; + const char* Terminal::FG_BLUE = "\033[34m"; const char* Terminal::FG_B_BLUE = "\033[94m"; + const char* Terminal::FG_MAGENTA = "\033[35m"; const char* Terminal::FG_B_MAGENTA = "\033[95m"; + const char* Terminal::FG_CYAN = "\033[36m"; const char* Terminal::FG_B_CYAN = "\033[96m"; + const char* Terminal::FG_WHITE = "\033[37m"; const char* Terminal::FG_B_WHITE = "\033[97m"; + + // Background // + const char* Terminal::BG_BLACK = "\033[40m"; const char* Terminal::BG_B_BLACK = "\033[100m"; + const char* Terminal::BG_RED = "\033[41m"; const char* Terminal::BG_B_RED = "\033[101m"; + const char* Terminal::BG_GREEN = "\033[42m"; const char* Terminal::BG_B_GREEN = "\033[102m"; + const char* Terminal::BG_YELLOW = "\033[43m"; const char* Terminal::BG_B_YELLOW = "\033[103m"; + const char* Terminal::BG_BLUE = "\033[44m"; const char* Terminal::BG_B_BLUE = "\033[104m"; + const char* Terminal::BG_MAGENTA = "\033[45m"; const char* Terminal::BG_B_MAGENTA = "\033[105m"; + const char* Terminal::BG_CYAN = "\033[46m"; const char* Terminal::BG_B_CYAN = "\033[106m"; + const char* Terminal::BG_WHITE = "\033[47m"; const char* Terminal::BG_B_WHITE = "\033[107m"; + Terminal::Terminal() { #if defined(SPIDER_OS_WINDOWS) + // Enable UTF-8 SetConsoleOutputCP(CP_UTF8); SetConsoleCP(CP_UTF8); + + // enable vtp + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD dwMode = 0; + GetConsoleMode(hOut, &dwMode); + dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + SetConsoleMode(hOut, dwMode); #endif } @@ -93,7 +128,7 @@ namespace spider { Terminal& Terminal::drawBox(i32 startRow, i32 startCol, i32 width, i32 height, std::string_view title) { // 1. Draw the top border move(startRow, startCol); - std::cout << "┌"; + std::cout << "┌"; for (i32 i = 0; i < width - 2; ++i) std::cout << "─"; std::cout << "┐"; @@ -116,7 +151,7 @@ namespace spider { move(startRow, startCol + (width - title.size() - 2) / 2); std::cout << " " << title << " "; } - + std::cout.flush(); return *this; } @@ -137,7 +172,7 @@ namespace spider { return *this; } - std::pair Terminal::size() { + std::pair Terminal::getSize() { std::pair pair; #if defined(SPIDER_OS_WINDOWS) CONSOLE_SCREEN_BUFFER_INFO csbi; @@ -171,6 +206,7 @@ namespace spider { case 80: return Terminal::DOWN; case 75: return Terminal::LEFT; case 77: return Terminal::RIGHT; + default: return Terminal::UNKNOWN; } } if (ch == 13) return Terminal::ENTER; @@ -185,24 +221,54 @@ namespace spider { newt.c_lflag &= ~(ICANON | ECHO); tcsetattr(STDIN_FILENO, TCSANOW, &newt); - u8 chd = Terminal::UNKNOWN; - i32 ch = getchar(); - if (ch == 27) { // ESC sequence - if (getchar() == '[') { - switch (getchar()) { - case 'A': chd = Terminal::UP; break; - case 'B': chd = Terminal::DOWN; break; - case 'D': chd = Terminal::LEFT; break; - case 'C': chd = Terminal::RIGHT; break; + u8 result = Terminal::UNKNOWN; + int ch = getchar(); + + if (ch == 27) { // Potential Escape Sequence + // Use a small timeout or check if more chars are in buffer + // to distinguish between 'Esc' key and 'Arrow' sequence + // Another Win for the Win API + struct timeval tv = { 0, 10000 }; // 10ms wait + fd_set fds; + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + + if (select(1, &fds, NULL, NULL, &tv) > 0) { + if (getchar() == '[') { + switch (getchar()) { + case 'A': result = Terminal::UP; break; + case 'B': result = Terminal::DOWN; break; + case 'D': result = Terminal::LEFT; break; + case 'C': result = Terminal::RIGHT; break; + } } + } else { + result = Terminal::ESC; } - } - else if (ch == 10) chd = Terminal::ENTER; - else if (ch == 127) chd = Terminal::BACKSPACE; + } else if (ch == 10) result = Terminal::ENTER; + else if (ch == 127) result = Terminal::BACKSPACE; + else result = (u8)ch; tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + return result; +#endif + } - return chd; + u8 Terminal::getKeyNb() { +#if defined(SPIDER_OS_WINDOWS) + if (_kbhit()) return getKey(); + return Terminal::UNKNOWN; +#endif +#if defined(SPIDER_OS_LINUX) || defined(SPIDER_OS_MACOS) + struct timeval tv = { 0, 0 }; + fd_set fds; + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + // select() returns > 0 if there is data to read + if (select(1, &fds, NULL, NULL, &tv) > 0) { + return getKey(); + } + return Terminal::UNKNOWN; #endif } diff --git a/src/spider/runtime/util/Terminal.hpp b/src/spider/runtime/util/Terminal.hpp index 3532722..108491e 100644 --- a/src/spider/runtime/util/Terminal.hpp +++ b/src/spider/runtime/util/Terminal.hpp @@ -5,43 +5,48 @@ #include #include #include -#include +#include namespace spider { class Terminal { public: - // --- Constants --- - static constexpr const char* RESET = "\033[0m"; - static constexpr const char* FG_BLACK = "\033[30m"; static constexpr const char* FG_B_BLACK = "\033[90m"; - static constexpr const char* FG_RED = "\033[31m"; static constexpr const char* FG_B_RED = "\033[91m"; - static constexpr const char* FG_GREEN = "\033[32m"; static constexpr const char* FG_B_GREEN = "\033[92m"; - static constexpr const char* FG_YELLOW = "\033[33m"; static constexpr const char* FG_B_YELLOW = "\033[93m"; - static constexpr const char* FG_BLUE = "\033[34m"; static constexpr const char* FG_B_BLUE = "\033[94m"; - static constexpr const char* FG_MAGENTA = "\033[35m"; static constexpr const char* FG_B_MAGENTA = "\033[95m"; - static constexpr const char* FG_CYAN = "\033[36m"; static constexpr const char* FG_B_CYAN = "\033[96m"; - static constexpr const char* FG_WHITE = "\033[37m"; static constexpr const char* FG_B_WHITE = "\033[97m"; + static const char* RESET; + static const char* BOLD; + static const char* ITALIC; + static const char* FAINT; + static const char* STRIKE; - static constexpr const char* BG_BLACK = "\033[40m"; static constexpr const char* BG_B_BLACK = "\033[100m"; - static constexpr const char* BG_RED = "\033[41m"; static constexpr const char* BG_B_RED = "\033[101m"; - static constexpr const char* BG_GREEN = "\033[42m"; static constexpr const char* BG_B_GREEN = "\033[102m"; - static constexpr const char* BG_YELLOW = "\033[43m"; static constexpr const char* BG_B_YELLOW = "\033[103m"; - static constexpr const char* BG_BLUE = "\033[44m"; static constexpr const char* BG_B_BLUE = "\033[104m"; - static constexpr const char* BG_MAGENTA = "\033[45m"; static constexpr const char* BG_B_MAGENTA = "\033[105m"; - static constexpr const char* BG_CYAN = "\033[46m"; static constexpr const char* BG_B_CYAN = "\033[106m"; - static constexpr const char* BG_WHITE = "\033[47m"; static constexpr const char* BG_B_WHITE = "\033[107m"; + static const char* FG_BLACK; static const char* FG_B_BLACK; + static const char* FG_RED; static const char* FG_B_RED; + static const char* FG_GREEN; static const char* FG_B_GREEN; + static const char* FG_YELLOW; static const char* FG_B_YELLOW; + static const char* FG_BLUE; static const char* FG_B_BLUE; + static const char* FG_MAGENTA; static const char* FG_B_MAGENTA; + static const char* FG_CYAN; static const char* FG_B_CYAN; + static const char* FG_WHITE; static const char* FG_B_WHITE; + + static const char* BG_BLACK; static const char* BG_B_BLACK; + static const char* BG_RED; static const char* BG_B_RED; + static const char* BG_GREEN; static const char* BG_B_GREEN; + static const char* BG_YELLOW; static const char* BG_B_YELLOW; + static const char* BG_BLUE; static const char* BG_B_BLUE; + static const char* BG_MAGENTA; static const char* BG_B_MAGENTA; + static const char* BG_CYAN; static const char* BG_B_CYAN; + static const char* BG_WHITE; static const char* BG_B_WHITE; public: - static constexpr const u8 UP = 0x1; - static constexpr const u8 DOWN = 0x2; - static constexpr const u8 LEFT = 0x3; - static constexpr const u8 RIGHT = 0x4; - static constexpr const u8 ENTER = 0x5; - static constexpr const u8 ESC = 0x6; - static constexpr const u8 BACKSPACE = 0x7; - static constexpr const u8 UNKNOWN = 0x8; + // Key Definitions (ASCII OK) // + static constexpr const u8 UP = 0x80; + static constexpr const u8 DOWN = 0x81; + static constexpr const u8 LEFT = 0x82; + static constexpr const u8 RIGHT = 0x83; + static constexpr const u8 ENTER = 0x84; + static constexpr const u8 ESC = 0x85; + static constexpr const u8 BACKSPACE = 0x86; + static constexpr const u8 UNKNOWN = 0xFF; public: @@ -119,7 +124,12 @@ namespace spider { u8 getKey(); - std::pair size(); + /** + * Get key non blocking + */ + u8 getKeyNb(); + + std::pair getSize(); public: @@ -135,6 +145,26 @@ namespace spider { return *this; } + template + Terminal& print_center(i32 width, const T& msg) { + // to string + std::ostringstream oss; + oss << msg; + std::string s = oss.str(); + + // then print + if (s.length() >= size(width)) { + std::cout << s; + } else { + i32 total_padding = width - s.length(); + i32 left_padding = total_padding / 2; + std::cout << std::string(left_padding, ' '); + std::cout << s; + std::cout << std::string(total_padding - left_padding, ' '); + } + return *this; + } + template Terminal& read(T& var) { std::cin >> var; From d4a1d5ad945ab6212732abfef8f0f47c4f2eb60d Mon Sep 17 00:00:00 2001 From: Kittycannon Date: Mon, 23 Mar 2026 09:17:53 -0600 Subject: [PATCH 04/10] oopsie fixes --- src/spider/runtime/cpu/InstrReelDyn.cpp | 2 ++ src/spider/runtime/memory/Types.hpp | 12 +++++++----- src/spider/runtime/util/Terminal.hpp | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/spider/runtime/cpu/InstrReelDyn.cpp b/src/spider/runtime/cpu/InstrReelDyn.cpp index 150e0b8..2e66a25 100644 --- a/src/spider/runtime/cpu/InstrReelDyn.cpp +++ b/src/spider/runtime/cpu/InstrReelDyn.cpp @@ -29,6 +29,7 @@ namespace spider { _block_index = copy._block_index; _blocks = copy._blocks; if (_block_index < _blocks.size()) selectBlock(_block_index); + return *this; } InstrReelDyn& InstrReelDyn::operator=(InstrReelDyn&& move) noexcept { @@ -43,6 +44,7 @@ namespace spider { move._offset = 0; move._size = 0; move._total_size = 0; + return *this; } void InstrReelDyn::growToFit(isize index) { diff --git a/src/spider/runtime/memory/Types.hpp b/src/spider/runtime/memory/Types.hpp index 7d250ee..6c80507 100644 --- a/src/spider/runtime/memory/Types.hpp +++ b/src/spider/runtime/memory/Types.hpp @@ -11,6 +11,8 @@ #include #endif +#include + namespace spider { /* @@ -148,7 +150,7 @@ namespace spider { #if SPIDER_LITTLE_ENDIAN T tmp; std::memcpy(&tmp, bytes, sizeof(T)); - *n = bswap(tmp); + *n = byteswap(tmp); #endif #if !SPIDER_BIG_ENDIAN && !SPIDER_LITTLE_ENDIAN using U = std::make_unsigned_t; @@ -213,12 +215,12 @@ namespace spider { inline void loadLE(T* n, const u8* bytes) { static_assert(std::is_trivially_copyable::value); #if SPIDER_BIG_ENDIAN - std::memcpy(n, bytes, sizeof(T)); -#endif -#if SPIDER_LITTLE_ENDIAN T tmp; std::memcpy(&tmp, bytes, sizeof(T)); - *n = bswap(tmp); + *n = byteswap(tmp); +#endif +#if SPIDER_LITTLE_ENDIAN + std::memcpy(n, bytes, sizeof(T)); #endif #if !SPIDER_BIG_ENDIAN && !SPIDER_LITTLE_ENDIAN using U = std::make_unsigned_t; diff --git a/src/spider/runtime/util/Terminal.hpp b/src/spider/runtime/util/Terminal.hpp index 108491e..3f183f1 100644 --- a/src/spider/runtime/util/Terminal.hpp +++ b/src/spider/runtime/util/Terminal.hpp @@ -153,7 +153,7 @@ namespace spider { std::string s = oss.str(); // then print - if (s.length() >= size(width)) { + if (s.length() >= isize(width)) { std::cout << s; } else { i32 total_padding = width - s.length(); From 3a6fc6cfb994fc2c7f0f73a179ede738a4a8d137 Mon Sep 17 00:00:00 2001 From: Kittycannon Date: Mon, 23 Mar 2026 17:08:26 -0600 Subject: [PATCH 05/10] cpu experimentation --- src/spider/runtime/cpu/CPU.cpp | 99 ++++++++++++++++++++++++++++++- src/spider/runtime/cpu/CPU.hpp | 103 +++++++++++++++++++++++++++++++-- 2 files changed, 195 insertions(+), 7 deletions(-) diff --git a/src/spider/runtime/cpu/CPU.cpp b/src/spider/runtime/cpu/CPU.cpp index e9b456e..cc92778 100644 --- a/src/spider/runtime/cpu/CPU.cpp +++ b/src/spider/runtime/cpu/CPU.cpp @@ -1,17 +1,110 @@ #include "CPU.hpp" +#include + +#include +#include + +#if __cplusplus >= 202002L +#include +#endif + namespace spider { CPU::CPU() - : RA{}, RB{}, RC{}, RD{}, + : RA{}, RB{}, RC{}, RD{}, RX{}, RY{}, R0{}, R1{}, R2{}, R3{}, R4{}, R5{}, R6{}, R7{}, R8{}, R9{}, RF{}, RI{}, RS{}, RZ{}, RE{}, RN{}, RV{}, RM{}, - ALU0{}, ALU1{} - {} + ALU0{}, ALU1{}, + _ram(nullptr), _reel(nullptr) { + } CPU::~CPU() {} + // Setup & Configuration // + + void CPU::hookRAM(RAM* ram) { + this->_ram = ram; + } + + void CPU::hookInstrReel(InstrReel* reel) { + this->_reel = reel; + } + + constexpr u64 CPU::getFlag(u64 mask) { + if (!mask) return 0; +#if __cplusplus >= 202002L + return (RF & mask) >> std::countr_zero(mask); +#elif defined(SPIDER_COMPILER_GCC_LIKE) + return (RF & mask) >> __builtin_ctzll(mask); +#elif defined(SPIDER_COMPILER_MSVC) + return (RF & mask) >> _BitScanForward64(mask); +#else + // If you have reached this part, + // please come up with a better alternative. + u64 bits = RF & mask; + while (mask && (mask >>= 1)) bits >>= 1; + return bits; +#endif + } + + // Addressing Modes // + + /** + * Implied Addressing Mode + */ + void CPU::imp() { + // Nothing // + } + + /** + * Immediate Addressing Mode + */ + void CPU::imm() { + u8 size = 2 << _size; + _next = &ALU0; + } + + /** + * Absolute Addressing Mode + */ + void CPU::abs() { + u8 size = 2 << getFlag(CPU::FLAG_MEMORY_MODE); + } + + /** + * Register Addressing Mode + */ + void CPU::reg() { + sizeof(CPU); + } + + /** + * Indrect Addressing Mode + */ + void CPU::ind() {} + + /** + * Pointer Addressing Mode + */ + void CPU::ptr() {} + + /** + * Indexed Addressing Mode + */ + void CPU::idx() {} + + /** + * Scaled Addressing Mode + */ + void CPU::sca() {} + + /** + * Displaced Addressing Mode + */ + void CPU::dis() {} + } diff --git a/src/spider/runtime/cpu/CPU.hpp b/src/spider/runtime/cpu/CPU.hpp index 0431c1f..7bba16f 100644 --- a/src/spider/runtime/cpu/CPU.hpp +++ b/src/spider/runtime/cpu/CPU.hpp @@ -1,15 +1,28 @@ #pragma once +#include #include namespace spider { class CPU { + public: // Flag Register Constants // + static constexpr const u64 FLAG_ENABLE = 0b0000000000000000000000000000000000000000000000000000000000000001; + static constexpr const u64 FLAG_INTERRUPT_SIGNAL = 0b0000000000000000000000000000000000000000000000000000000000000010; + static constexpr const u64 FLAG_INTERRUPT_REQUEST = 0b0000000000000000000000000000000000000000000000000000000000000100; + static constexpr const u64 FLAG_EXCEPTION = 0b0000000000000000000000000000000000000000000000000000000000001000; + static constexpr const u64 FLAG_MEMORY_MODE = 0b0000000000000000000000000000000000000000000000000000000000110000; + public: // General Purpose Registers - register_t RA, RB, RC, RD, - RX, RY, R0, R1, - R2, R3, R4, R5, - R6, R7, R8, R9; + union { + register_t GPR[16]; + struct { + register_t RA, RB, RC, RD, + RX, RY, R0, R1, + R2, R3, R4, R5, + R6, R7, R8, R9; + }; + }; public: // System Registers u64 RF; @@ -31,6 +44,33 @@ namespace spider { */ register_t ALU0, ALU1; + private: + + /** + * Pointer to the current RAM hooked into + * the CPU. + * + * It is unproved whether having the RAM directly + * into the CPU is better than not, or whether a + * virtual BUS is better. + * + * Alas, this way we can have a CPU state switch + * between memory and instruction banks. + */ + RAM* _ram; + + /** + * Pointer to the current Instruction Reel + * hooked into the CPU. + * + * Ditto as RAM. + */ + InstrReel* _reel; + + register_t* _next; + u8 _addrm : 6; + u8 _size : 2; + public: CPU(); @@ -47,6 +87,61 @@ namespace spider { CPU& operator=(CPU&& other) noexcept = default; + public: + + void hookRAM(RAM* ram); + + void hookInstrReel(InstrReel* reel); + + constexpr u64 getFlag(u64 mask); + + public: // Addressing Modes + + /** + * Implied Addressing Mode + */ + void imp(); + + /** + * Immediate Addressing Mode + */ + void imm(); + + /** + * Absolute Addressing Mode + */ + void abs(); + + /** + * Register Addressing Mode + */ + void reg(); + + /** + * Indrect Addressing Mode + */ + void ind(); + + /** + * Pointer Addressing Mode + */ + void ptr(); + + /** + * Indexed Addressing Mode + */ + void idx(); + + /** + * Scaled Addressing Mode + */ + void sca(); + + /** + * Displaced Addressing Mode + */ + void dis(); + public: // // From 4a659b5f0d1a70af7787515984dbfc7fb2a4b9e3 Mon Sep 17 00:00:00 2001 From: Kittycannon Date: Mon, 23 Mar 2026 22:29:05 -0600 Subject: [PATCH 06/10] moved reel to dedicated folder because it was hogging the cpu folder --- src/spider/runtime/cpu/CPU.cpp | 3 ++- src/spider/runtime/{cpu => reel}/InstrReel.cpp | 0 src/spider/runtime/{cpu => reel}/InstrReel.hpp | 0 src/spider/runtime/{cpu => reel}/InstrReelDyn.cpp | 0 src/spider/runtime/{cpu => reel}/InstrReelDyn.hpp | 2 +- src/spider/runtime/{cpu => reel}/InstrReelFile.hpp | 0 src/spider/runtime/{cpu => reel}/InstrReelFixed.cpp | 0 src/spider/runtime/{cpu => reel}/InstrReelFixed.hpp | 2 +- 8 files changed, 4 insertions(+), 3 deletions(-) rename src/spider/runtime/{cpu => reel}/InstrReel.cpp (100%) rename src/spider/runtime/{cpu => reel}/InstrReel.hpp (100%) rename src/spider/runtime/{cpu => reel}/InstrReelDyn.cpp (100%) rename src/spider/runtime/{cpu => reel}/InstrReelDyn.hpp (98%) rename src/spider/runtime/{cpu => reel}/InstrReelFile.hpp (100%) rename src/spider/runtime/{cpu => reel}/InstrReelFixed.cpp (100%) rename src/spider/runtime/{cpu => reel}/InstrReelFixed.hpp (94%) diff --git a/src/spider/runtime/cpu/CPU.cpp b/src/spider/runtime/cpu/CPU.cpp index cc92778..843e7a9 100644 --- a/src/spider/runtime/cpu/CPU.cpp +++ b/src/spider/runtime/cpu/CPU.cpp @@ -3,7 +3,8 @@ #include #include -#include + +#include #if __cplusplus >= 202002L #include diff --git a/src/spider/runtime/cpu/InstrReel.cpp b/src/spider/runtime/reel/InstrReel.cpp similarity index 100% rename from src/spider/runtime/cpu/InstrReel.cpp rename to src/spider/runtime/reel/InstrReel.cpp diff --git a/src/spider/runtime/cpu/InstrReel.hpp b/src/spider/runtime/reel/InstrReel.hpp similarity index 100% rename from src/spider/runtime/cpu/InstrReel.hpp rename to src/spider/runtime/reel/InstrReel.hpp diff --git a/src/spider/runtime/cpu/InstrReelDyn.cpp b/src/spider/runtime/reel/InstrReelDyn.cpp similarity index 100% rename from src/spider/runtime/cpu/InstrReelDyn.cpp rename to src/spider/runtime/reel/InstrReelDyn.cpp diff --git a/src/spider/runtime/cpu/InstrReelDyn.hpp b/src/spider/runtime/reel/InstrReelDyn.hpp similarity index 98% rename from src/spider/runtime/cpu/InstrReelDyn.hpp rename to src/spider/runtime/reel/InstrReelDyn.hpp index 75912da..1b5a4c7 100644 --- a/src/spider/runtime/cpu/InstrReelDyn.hpp +++ b/src/spider/runtime/reel/InstrReelDyn.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include namespace spider { diff --git a/src/spider/runtime/cpu/InstrReelFile.hpp b/src/spider/runtime/reel/InstrReelFile.hpp similarity index 100% rename from src/spider/runtime/cpu/InstrReelFile.hpp rename to src/spider/runtime/reel/InstrReelFile.hpp diff --git a/src/spider/runtime/cpu/InstrReelFixed.cpp b/src/spider/runtime/reel/InstrReelFixed.cpp similarity index 100% rename from src/spider/runtime/cpu/InstrReelFixed.cpp rename to src/spider/runtime/reel/InstrReelFixed.cpp diff --git a/src/spider/runtime/cpu/InstrReelFixed.hpp b/src/spider/runtime/reel/InstrReelFixed.hpp similarity index 94% rename from src/spider/runtime/cpu/InstrReelFixed.hpp rename to src/spider/runtime/reel/InstrReelFixed.hpp index dd8f4ec..505c0be 100644 --- a/src/spider/runtime/cpu/InstrReelFixed.hpp +++ b/src/spider/runtime/reel/InstrReelFixed.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include namespace spider { From 1c971a4e22c8e7b29c3e9d93327ea0b5b79c0eb7 Mon Sep 17 00:00:00 2001 From: Kittycannon Date: Wed, 25 Mar 2026 06:59:00 -0600 Subject: [PATCH 07/10] more or less almost done with the instr reels. --- makefile | 12 +- src/spider/runtime/cpu/CPU.cpp | 7 +- src/spider/runtime/cpu/CPU.hpp | 15 +- src/spider/runtime/debug/LiveDebug.cpp | 2 +- src/spider/runtime/memory/Types.hpp | 3 + src/spider/runtime/reel/InstrReel.cpp | 50 +--- src/spider/runtime/reel/InstrReel.hpp | 23 +- src/spider/runtime/reel/InstrReelDyn.cpp | 308 +++++++++++---------- src/spider/runtime/reel/InstrReelDyn.hpp | 48 ++-- src/spider/runtime/reel/InstrReelFixed.cpp | 90 ++++-- src/spider/runtime/reel/InstrReelFixed.hpp | 57 +++- 11 files changed, 338 insertions(+), 277 deletions(-) diff --git a/makefile b/makefile index 8ec5941..92cedfa 100644 --- a/makefile +++ b/makefile @@ -14,8 +14,16 @@ OBJEXT := o #Flags, Libraries and Includes ROOT := ./ -CFLAGS := -Wall -std=c++20 -DSPIDER_COMPILING -LFLAGS := -Wall -std=c++20 -static +CFLAGS := -std=c++20 -O2 \ + -Wall -Wextra \ + -Wshadow -Wnon-virtual-dtor -Wold-style-cast -Wcast-align \ + -Wunused -Woverloaded-virtual -Wconversion \ + -Wsign-conversion -Wnull-dereference -Wdouble-promotion \ + -Wformat=2 -Wimplicit-fallthrough -Wsuggest-override \ + -Wextra-semi -Wduplicated-cond -Wduplicated-branches \ + -Wlogical-op -Wuseless-cast +LFLAGS := -std=c++20 -static-libstdc++ -static-libgcc \ + -Wl,--fatal-warnings -Wl,--warn-common LIB := INC := -I./src/ diff --git a/src/spider/runtime/cpu/CPU.cpp b/src/spider/runtime/cpu/CPU.cpp index 843e7a9..3c9d3f5 100644 --- a/src/spider/runtime/cpu/CPU.cpp +++ b/src/spider/runtime/cpu/CPU.cpp @@ -19,7 +19,8 @@ namespace spider { R6{}, R7{}, R8{}, R9{}, RF{}, RI{}, RS{}, RZ{}, RE{}, RN{}, RV{}, RM{}, - ALU0{}, ALU1{}, + ALU0{}, + _dst(nullptr), _src(nullptr), _ram(nullptr), _reel(nullptr) { } @@ -65,8 +66,6 @@ namespace spider { * Immediate Addressing Mode */ void CPU::imm() { - u8 size = 2 << _size; - _next = &ALU0; } /** @@ -80,7 +79,7 @@ namespace spider { * Register Addressing Mode */ void CPU::reg() { - sizeof(CPU); + } /** diff --git a/src/spider/runtime/cpu/CPU.hpp b/src/spider/runtime/cpu/CPU.hpp index 7bba16f..fba9c8e 100644 --- a/src/spider/runtime/cpu/CPU.hpp +++ b/src/spider/runtime/cpu/CPU.hpp @@ -42,7 +42,16 @@ namespace spider { * This way we don't "write" into constant values, rather * we write into a writeable var which is "hidden" */ - register_t ALU0, ALU1; + register_t ALU0; + + union { + struct { + register_t* _dst; + register_t* _src; + }; + register_t* _opers[2]; + }; + private: @@ -67,10 +76,6 @@ namespace spider { */ InstrReel* _reel; - register_t* _next; - u8 _addrm : 6; - u8 _size : 2; - public: CPU(); diff --git a/src/spider/runtime/debug/LiveDebug.cpp b/src/spider/runtime/debug/LiveDebug.cpp index f1301d4..cfe94a4 100644 --- a/src/spider/runtime/debug/LiveDebug.cpp +++ b/src/spider/runtime/debug/LiveDebug.cpp @@ -122,7 +122,7 @@ namespace spider { &cpu.R6, &cpu.R7, &cpu.R8, &cpu.R9, //&cpu.RF, &cpu.RI, &cpu.RS, &cpu.RZ, //&cpu.RE, &cpu.RN, &cpu.RV, &cpu.RM, - &cpu.ALU0, &cpu.ALU1 + &cpu.ALU0, &cpu.ALU0 }; const u64* sys_regs[] = { &cpu.RF, &cpu.RI, &cpu.RS, &cpu.RZ, diff --git a/src/spider/runtime/memory/Types.hpp b/src/spider/runtime/memory/Types.hpp index 6c80507..635dc67 100644 --- a/src/spider/runtime/memory/Types.hpp +++ b/src/spider/runtime/memory/Types.hpp @@ -1,6 +1,9 @@ #pragma once #include + +#include + #include #if __cplusplus >= 202002L diff --git a/src/spider/runtime/reel/InstrReel.cpp b/src/spider/runtime/reel/InstrReel.cpp index 88df155..6f90e03 100644 --- a/src/spider/runtime/reel/InstrReel.cpp +++ b/src/spider/runtime/reel/InstrReel.cpp @@ -8,58 +8,10 @@ namespace spider { // Public Interface // - InstrReel::InstrReel() : _mem(nullptr), _size(0), _offset(0), _total_size(0) {} + InstrReel::InstrReel() {} InstrReel::~InstrReel() {} - // Instruction abstraction // - - u8 InstrReel::atU8(u64 ip) { - // guard against access - u64 ip_p = ip - _offset; - if(ip_p + 1 > _size) return 0; - - // send byte - return _mem[ip]; - } - - u16 InstrReel::atU16(u64 ip) { - // guard against access - u64 ip_p = ip - _offset; - if(ip_p + 2 > _size) return 0; - - // build a 16-bit big endian number - u16 dat; - spider::loadLE(&dat, _mem + ip_p); - return dat; - } - - u32 InstrReel::atU32(u64 ip) { - // guard against access - u64 ip_p = ip - _offset; - if(ip_p + 4 > _size) return 0; - - // build a 32-bit big endian number - u32 dat; - spider::loadLE(&dat, _mem + ip_p); - return dat; - } - - u64 InstrReel::atU64(u64 ip) { - // guard against access - u64 ip_p = ip - _offset; - if(ip_p + 8 > _size) return 0; - - // build a 64-bit big endian number - u64 dat; - spider::loadLE(&dat, _mem + ip_p); - return dat; - } - - u64 InstrReel::size() { - return _total_size; - } - // Static Utils // u16 InstrReel::unpackInstr(u16 bcode) { diff --git a/src/spider/runtime/reel/InstrReel.hpp b/src/spider/runtime/reel/InstrReel.hpp index 06a3d14..a60d9c3 100644 --- a/src/spider/runtime/reel/InstrReel.hpp +++ b/src/spider/runtime/reel/InstrReel.hpp @@ -9,13 +9,6 @@ namespace spider { * Implements an instruction reel. */ class InstrReel { - protected: // Current accessing range // - - u8* _mem; - isize _size; - isize _offset; - isize _total_size; - public: InstrReel(); @@ -30,7 +23,7 @@ namespace spider { * Reindexing may occur, continous access * may incurr in less penalties. */ - virtual u8 atU8(u64 ip); + virtual u8 readU8(u64 ip) = 0; /** * Obtains a byte of data at @@ -38,7 +31,7 @@ namespace spider { * Reindexing may occur, continous access * may incurr in less penalties. */ - virtual u16 atU16(u64 ip); + virtual u16 readU16(u64 ip) = 0; /** * Obtains a byte of data at @@ -46,7 +39,7 @@ namespace spider { * Reindexing may occur, continous access * may incurr in less penalties. */ - virtual u32 atU32(u64 ip); + virtual u32 readU32(u64 ip) = 0; /** * Obtains a byte of data at @@ -54,12 +47,18 @@ namespace spider { * Reindexing may occur, continous access * may incurr in less penalties. */ - virtual u64 atU64(u64 ip); + virtual u64 readU64(u64 ip) = 0; + + /** + * Reads a range of data, and + * outputs it. + */ + virtual void readRange(u64 ip, u8* out, u64 length) = 0; /** * Current size of the instructions. */ - virtual u64 size(); + virtual u64 size() = 0; public: // Static Utils // diff --git a/src/spider/runtime/reel/InstrReelDyn.cpp b/src/spider/runtime/reel/InstrReelDyn.cpp index 2e66a25..0aceeb2 100644 --- a/src/spider/runtime/reel/InstrReelDyn.cpp +++ b/src/spider/runtime/reel/InstrReelDyn.cpp @@ -4,20 +4,19 @@ namespace spider { - InstrReelDyn::InstrReelDyn(u64 length) : _use_count(0), _block_index(0) { - _total_size = length; - growToFit(length > 0 ? length - 1 : 0); - selectBlock(0); + InstrReelDyn::InstrReelDyn(u64 length) : _size(length) { + // Safe int ceil division + growTo((length >> 8) + ((length & 255) != 0)); } InstrReelDyn::InstrReelDyn(const u8* data, u64 length) {} - InstrReelDyn::InstrReelDyn(const InstrReelDyn& copy) : _use_count(copy._use_count), _block_index(copy._block_index), _blocks(copy._blocks) { - if (_block_index < _blocks.size()) selectBlock(_block_index); + InstrReelDyn::InstrReelDyn(const InstrReelDyn& copy) + : _blocks(copy._blocks), _size(copy._size) { } - InstrReelDyn::InstrReelDyn(InstrReelDyn&& move) noexcept : _use_count(move._use_count), _block_index(move._block_index), _blocks(std::move(move._blocks)) { - if (_block_index < _blocks.size()) selectBlock(_block_index); + InstrReelDyn::InstrReelDyn(InstrReelDyn&& move) noexcept + : _blocks(std::move(move._blocks)), _size(std::move(move._size)) { } InstrReelDyn::~InstrReelDyn() { @@ -25,170 +24,183 @@ namespace spider { } InstrReelDyn& InstrReelDyn::operator=(const InstrReelDyn& copy) { - _use_count = copy._use_count; - _block_index = copy._block_index; _blocks = copy._blocks; - if (_block_index < _blocks.size()) selectBlock(_block_index); + _size = copy._size; return *this; } InstrReelDyn& InstrReelDyn::operator=(InstrReelDyn&& move) noexcept { - _use_count = move._use_count; - _block_index = move._block_index; _blocks = std::move(move._blocks); - if (_block_index < _blocks.size()) selectBlock(_block_index); - - move._use_count = 0; - move._block_index = 0; - move._mem = nullptr; - move._offset = 0; - move._size = 0; - move._total_size = 0; + _size = std::move(move._size); return *this; } - void InstrReelDyn::growToFit(isize index) { - while (_blocks.size() < (index + 1)) { + void InstrReelDyn::growTo(u64 ip) { + u64 b_index = (ip >> 8) + 1; + while (_blocks.size() < b_index) { _blocks.emplace_back(); } + if (ip >= _size) _size = ip + 1; } - isize InstrReelDyn::selectIndex(u64 ip) { - return ip / 256; + std::pair InstrReelDyn::indexOf(u64 ip) { + return { ip >> 8, ip & 0xFF }; // { ip / 256, ip % 256 }; } - InstrReelDyn::ReelBlock* InstrReelDyn::selectBlock(isize index) { - // Update base class cache - auto ptr = &_blocks[index]; - _offset = index * 256; - _mem = ptr->data; - _size = 256; - _block_index = index; - - //_blocks[block_idx].access_count++; - return ptr; + bool InstrReelDyn::continous(u64 ip0, u64 ip1, u64* b_index, u16* s_index) { + auto i = indexOf(ip0); + *b_index = i.first; + *s_index = i.second; + return i.first == (ip1 >> 8); } - u8 InstrReelDyn::atU8(u64 ip) { - isize j = selectIndex(ip); - if (j >= _blocks.size()) return 0; - if (j != _block_index) { - this->selectBlock(j); - } - return _mem[ip - _offset]; - } - - u16 InstrReelDyn::atU16(u64 ip) { - isize j0 = selectIndex(ip); - isize j1 = selectIndex(ip + 1); - if (j1 >= _blocks.size()) return 0; - if (j0 == j1 && j0 != _block_index) { - selectBlock(j0); - } - if (j0 == j1 && j0 == _block_index) { - u16 dat; - spider::loadLE(&dat, _mem); - return dat; - } - - // general case, first part - u16 dat = 0; - const u8 size = sizeof(u16); - - // select first block and offset - selectBlock(j0); - u8 rem = ip % 256; - - for (u8 n = 0; n < size; n++) { - dat |= _mem[rem++] << (n * 8); - ip++; - if (!rem) selectBlock(++j0); - } - - return dat; - } - - u32 InstrReelDyn::atU32(u64 ip) { - isize j0 = selectIndex(ip); - isize j1 = selectIndex(ip + 3); - if (j1 >= _blocks.size()) return 0; - if (j0 == j1 && j0 != _block_index) { - selectBlock(j0); - } - if (j0 == j1 && j0 == _block_index) { - u32 dat; - spider::loadLE(&dat, _mem); - return dat; - } - - // general case, first part - u32 dat = 0; - const u8 size = sizeof(u32); - - // select first block and offset - selectBlock(j0); - u8 rem = ip % 256; - - for (u8 n = 0; n < size; n++) { - dat |= _mem[rem++] << (n * 8); - ip++; - if (!rem) selectBlock(++j0); - } - - return dat; - } - - u64 InstrReelDyn::atU64(u64 ip) { - isize j0 = selectIndex(ip); - isize j1 = selectIndex(ip + 3); - if (j1 >= _blocks.size()) return 0; - if (j0 == j1 && j0 != _block_index) { - selectBlock(j0); - } - if (j0 == j1 && j0 == _block_index) { - u64 dat; - spider::loadLE(&dat, _mem); - return dat; - } - - // general case, first part - u64 dat = 0; - const u8 size = sizeof(u64); - - // select first block and offset - selectBlock(j0); - u8 rem = ip % 256; - - for (u8 n = 0; n < size; n++) { - dat |= _mem[rem++] << (n * 8); - ip++; - if (!rem) selectBlock(++j0); - } - - return dat; - } - - void InstrReelDyn::at(u64 ip, u8 dat) {} - - void InstrReelDyn::at(u64 ip, u16 dat) {} - - void InstrReelDyn::at(u64 ip, u32 dat) {} - - void InstrReelDyn::at(u64 ip, u64 dat) {} + // Particular Cases /** - * Appends instruction at location. + * Obtains a byte of data at + * the specific location. + * Reindexing may occur, continous access + * may incurr in less penalties. */ - void InstrReelDyn::append(u64 ip, u16 bc) {} + u8 InstrReelDyn::readU8(u64 ip) { + if (ip + 1 > _size) return 0; + auto i = indexOf(ip); + return _blocks[i.first].data[i.second]; + } + + /** + * Obtains a byte of data at + * the specific location. + * Reindexing may occur, continous access + * may incurr in less penalties. + */ + u16 InstrReelDyn::readU16(u64 ip) { + if (ip + 2 > _size) return 0; + + u16 dat; + u64 b_index; + u16 s_index; + + if (continous(ip, ip + 1, &b_index, &s_index)) { + spider::loadLE(&dat, &_blocks[b_index].data[s_index]); + return dat; + } + + dat = 0; + for (isize i = 0; i < sizeof(dat); i++) { + auto& b = _blocks[(b_index + s_index) >> 8]; + dat |= u16(b.data[s_index++ & 0xFF]) << (i * 8); + } + return dat; + } + + /** + * Obtains a byte of data at + * the specific location. + * Reindexing may occur, continous access + * may incurr in less penalties. + */ + u32 InstrReelDyn::readU32(u64 ip) { + if (ip + 4 > _size) return 0; + + u32 dat; + u64 b_index; + u16 s_index; + + if (continous(ip, ip + 3, &b_index, &s_index)) { + spider::loadLE(&dat, &_blocks[b_index].data[s_index]); + return dat; + } + + dat = 0; + for (isize i = 0; i < sizeof(dat); i++) { + auto& b = _blocks[(b_index + s_index) >> 8]; + dat |= u32(b.data[s_index++ & 0xFF]) << (i * 8); + } + return dat; + } + + /** + * Obtains a byte of data at + * the specific location. + * Reindexing may occur, continous access + * may incurr in less penalties. + */ + u64 InstrReelDyn::readU64(u64 ip) { + if (ip + 8 > _size) return 0; + + u64 dat; + u64 b_index; + u16 s_index; + + if (continous(ip, ip + 7, &b_index, &s_index)) { + spider::loadLE(&dat, &_blocks[b_index].data[s_index]); + return dat; + } + + dat = 0; + for (isize i = 0; i < sizeof(dat); i++) { + auto& b = _blocks[(b_index + s_index) >> 8]; + dat |= u64(b.data[s_index++ & 0xFF]) << (i * 8); + } + return dat; + } + + /** + * Reads a range of data, and + * outputs it. + */ + void InstrReelDyn::readRange(u64 ip, u8* out, u64 length) { + if (ip + length > _size) { + std::memset(out, 0, length); + return; + } + + u64 b_index; + u16 s_index; + + if (continous(ip, ip + length, &b_index, &s_index)) { + std::memcpy(out, &_blocks[b_index].data[s_index], length); + return; + } + + u64 bytes_read = 0; + while (bytes_read < length) { + u64 remaining_in_block = 256 - s_index; + u64 chunk_size = std::min(remaining_in_block, length - bytes_read); + + // Perform bulk copy for the current segment + std::memcpy(out + bytes_read, &_blocks[b_index].data[s_index], chunk_size); + + // Advance pointers + bytes_read += chunk_size; + b_index++; + s_index = 0; // reset + } + } + + /** + * Current size of the instructions. + */ + u64 InstrReelDyn::size() { + return _size; + } + + // Mutation // + + // TODO! + + void InstrReelDyn::writeU8(u64 ip, u8 dat) {} + + void InstrReelDyn::writeU16(u64 ip, u16 dat) {} + + void InstrReelDyn::writeU32(u64 ip, u32 dat) {} + + void InstrReelDyn::writeU64(u64 ip, u64 dat) {} /** * Appends instruction at the end. */ void InstrReelDyn::append(u16 bc) {} - /** - * Removes instruction at location. - */ - void InstrReelDyn::remove(u64 ip) {} - } diff --git a/src/spider/runtime/reel/InstrReelDyn.hpp b/src/spider/runtime/reel/InstrReelDyn.hpp index 1b5a4c7..d5ed28c 100644 --- a/src/spider/runtime/reel/InstrReelDyn.hpp +++ b/src/spider/runtime/reel/InstrReelDyn.hpp @@ -16,9 +16,8 @@ namespace spider { private: - u64 _use_count; - isize _block_index; - std::deque _blocks; + deque _blocks; + u64 _size; public: @@ -40,11 +39,11 @@ namespace spider { private: - isize selectIndex(u64 ip); + std::pair indexOf(u64 ip); - void growToFit(isize index); + bool continous(u64 ip0, u64 ip1, u64* b_index, u16* s_index); - ReelBlock* selectBlock(isize index); + void growTo(u64 ip); public: @@ -54,7 +53,7 @@ namespace spider { * Reindexing may occur, continous access * may incurr in less penalties. */ - virtual u8 atU8(u64 ip) override; + virtual u8 readU8(u64 ip) override; /** * Obtains a byte of data at @@ -62,7 +61,7 @@ namespace spider { * Reindexing may occur, continous access * may incurr in less penalties. */ - virtual u16 atU16(u64 ip) override; + virtual u16 readU16(u64 ip) override; /** * Obtains a byte of data at @@ -70,7 +69,7 @@ namespace spider { * Reindexing may occur, continous access * may incurr in less penalties. */ - virtual u32 atU32(u64 ip) override; + virtual u32 readU32(u64 ip) override; /** * Obtains a byte of data at @@ -78,33 +77,34 @@ namespace spider { * Reindexing may occur, continous access * may incurr in less penalties. */ - virtual u64 atU64(u64 ip) override; + virtual u64 readU64(u64 ip) override; + + /** + * Reads a range of data, and + * outputs it. + */ + virtual void readRange(u64 ip, u8* out, u64 length) override; + + /** + * Current size of the instructions. + */ + virtual u64 size() override; public: - void at(u64 ip, u8 dat); + void writeU8(u64 ip, u8 dat); - void at(u64 ip, u16 dat); + void writeU16(u64 ip, u16 dat); - void at(u64 ip, u32 dat); + void writeU32(u64 ip, u32 dat); - void at(u64 ip, u64 dat); - - /** - * Appends instruction at location. - */ - void append(u64 ip, u16 bc); + void writeU64(u64 ip, u64 dat); /** * Appends instruction at the end. */ void append(u16 bc); - /** - * Removes instruction at location. - */ - void remove(u64 ip); - }; } diff --git a/src/spider/runtime/reel/InstrReelFixed.cpp b/src/spider/runtime/reel/InstrReelFixed.cpp index 0c41e7f..15e62eb 100644 --- a/src/spider/runtime/reel/InstrReelFixed.cpp +++ b/src/spider/runtime/reel/InstrReelFixed.cpp @@ -8,22 +8,16 @@ namespace spider { // Constructors & Destructors // - InstrReelFixed::InstrReelFixed(u64 length) { - this->_offset = 0; - this->_size = length; - this->_total_size = length; - + InstrReelFixed::InstrReelFixed(u64 length) + : _mem(nullptr), _size(length) { if (_size > 0) { _mem = new u8[_size]; std::memset(_mem, 0, _size); } } - InstrReelFixed::InstrReelFixed(const u8* data, u64 length) { - this->_offset = 0; - this->_size = length; - this->_total_size = length; - + InstrReelFixed::InstrReelFixed(const u8* data, u64 length) + : _mem(nullptr), _size(length) { if (_size > 0) { _mem = new u8[_size]; std::copy(data, data + _size, _mem); @@ -31,29 +25,77 @@ namespace spider { } InstrReelFixed::InstrReelFixed(const InstrReelFixed& other) { - _offset = other._offset; _size = other._size; - _total_size = other._total_size; _mem = new u8[_size]; std::copy(other._mem, other._mem + _size, _mem); } InstrReelFixed::InstrReelFixed(InstrReelFixed&& other) noexcept { _mem = other._mem; - _offset = other._offset; _size = other._size; - _total_size = other._total_size; other._mem = nullptr; - other._offset = 0; other._size = 0; - other._total_size = 0; } InstrReelFixed::~InstrReelFixed() { delete[] _mem; } + // General Case(s) // + + // Instruction abstraction // + + u8 InstrReelFixed::readU8(u64 ip) { + // guard against access + if(ip + 1 > _size) return 0; + + // send byte + return _mem[ip]; + } + + u16 InstrReelFixed::readU16(u64 ip) { + // guard against access + if(ip + 2 > _size) return 0; + + // build a 16-bit big endian number + u16 dat; + spider::loadLE(&dat, _mem + ip); + return dat; + } + + u32 InstrReelFixed::readU32(u64 ip) { + // guard against access + if(ip + 4 > _size) return 0; + + // build a 32-bit big endian number + u32 dat; + spider::loadLE(&dat, _mem + ip); + return dat; + } + + u64 InstrReelFixed::readU64(u64 ip) { + // guard against access + if(ip + 8 > _size) return 0; + + // build a 64-bit big endian number + u64 dat; + spider::loadLE(&dat, _mem + ip); + return dat; + } + + void InstrReelFixed::readRange(u64 ip, u8* out, u64 length) { + if(ip + length > _size) { + std::memset(out, 0, length); + return; + } + std::memcpy(out, _mem + ip, length); + } + + u64 InstrReelFixed::size() { + return _size; + } + // Assign Operators // InstrReelFixed& InstrReelFixed::operator=(const InstrReelFixed& other) { @@ -64,9 +106,7 @@ namespace spider { delete[] _mem; _mem = new_mem; - _offset = other._offset; _size = other._size; - _total_size = other._total_size; return *this; } @@ -77,36 +117,32 @@ namespace spider { delete[] _mem; _mem = other._mem; // steal - _offset = other._offset; _size = other._size; - _total_size = other._total_size; other._mem = nullptr; // leave as husk - other._offset = 0; other._size = 0; - other._total_size = 0; return *this; } // Misc // - void InstrReelFixed::at(u64 ip, u8 dat) { + void InstrReelFixed::writeU8(u64 ip, u8 dat) { if(ip + 1 > _size) return; _mem[ip] = dat; } - void InstrReelFixed::at(u64 ip, u16 dat) { + void InstrReelFixed::writeU16(u64 ip, u16 dat) { if(ip + 2 > _size) return; spider::storeLE(dat, _mem + ip); } - void InstrReelFixed::at(u64 ip, u32 dat) { + void InstrReelFixed::writeU32(u64 ip, u32 dat) { if(ip + 4 > _size) return; spider::storeLE(dat, _mem + ip); } - void InstrReelFixed::at(u64 ip, u64 dat) { + void InstrReelFixed::writeU64(u64 ip, u64 dat) { if(ip + 8 > _size) return; spider::storeLE(dat, _mem + ip); } @@ -120,7 +156,6 @@ namespace spider { delete[] _mem; _mem = nullptr; _size = 0; - _total_size = 0; return; } @@ -143,7 +178,6 @@ namespace spider { delete[] _mem; _mem = new_mem; _size = new_size; - _total_size = new_size; } } diff --git a/src/spider/runtime/reel/InstrReelFixed.hpp b/src/spider/runtime/reel/InstrReelFixed.hpp index 505c0be..f9b09a8 100644 --- a/src/spider/runtime/reel/InstrReelFixed.hpp +++ b/src/spider/runtime/reel/InstrReelFixed.hpp @@ -8,6 +8,10 @@ namespace spider { * Implements an instruction reel. */ class InstrReelFixed : public InstrReel { + private: + u8* _mem; + u64 _size; + public: InstrReelFixed(u64 length); @@ -28,13 +32,58 @@ namespace spider { public: - void at(u64 ip, u8 dat); + /** + * Obtains a byte of data at + * the specific location. + * Reindexing may occur, continous access + * may incurr in less penalties. + */ + virtual u8 readU8(u64 ip) override; - void at(u64 ip, u16 dat); + /** + * Obtains a byte of data at + * the specific location. + * Reindexing may occur, continous access + * may incurr in less penalties. + */ + virtual u16 readU16(u64 ip) override; - void at(u64 ip, u32 dat); + /** + * Obtains a byte of data at + * the specific location. + * Reindexing may occur, continous access + * may incurr in less penalties. + */ + virtual u32 readU32(u64 ip) override; - void at(u64 ip, u64 dat); + /** + * Obtains a byte of data at + * the specific location. + * Reindexing may occur, continous access + * may incurr in less penalties. + */ + virtual u64 readU64(u64 ip) override; + + /** + * Reads a range of data, and + * outputs it. + */ + virtual void readRange(u64 ip, u8* out, u64 length) override; + + /** + * Current size of the instructions. + */ + virtual u64 size() override; + + public: + + void writeU8(u64 ip, u8 dat); + + void writeU16(u64 ip, u16 dat); + + void writeU32(u64 ip, u32 dat); + + void writeU64(u64 ip, u64 dat); void resize(u64 new_size); From e24e8dfe2d47e73d393627b73354fbaf1fdfa26b Mon Sep 17 00:00:00 2001 From: Kittycannon Date: Wed, 25 Mar 2026 10:00:21 -0600 Subject: [PATCH 08/10] ready for addrm & instr --- src/spider/SpiderRuntime.cpp | 2 + src/spider/SpiderRuntime.hpp | 3 + src/spider/runtime/Runtime.cpp | 34 ++++++-- src/spider/runtime/Runtime.hpp | 9 ++ src/spider/runtime/common.hpp | 1 + src/spider/runtime/cpu/CPU.cpp | 116 +++++++++++++++++++++++-- src/spider/runtime/cpu/CPU.hpp | 74 +++++++++++++++- src/spider/runtime/debug/LiveDebug.cpp | 26 +++++- 8 files changed, 245 insertions(+), 20 deletions(-) diff --git a/src/spider/SpiderRuntime.cpp b/src/spider/SpiderRuntime.cpp index cffd95a..8c84608 100644 --- a/src/spider/SpiderRuntime.cpp +++ b/src/spider/SpiderRuntime.cpp @@ -6,6 +6,8 @@ namespace spider { + const u32 RUNTIME_VERSION_NO = 0x00000000; // v0.1 + const std::string RUNTIME_VERSION = "alpha v0.1"; } diff --git a/src/spider/SpiderRuntime.hpp b/src/spider/SpiderRuntime.hpp index 6f87fd0..744b6e2 100644 --- a/src/spider/SpiderRuntime.hpp +++ b/src/spider/SpiderRuntime.hpp @@ -4,6 +4,9 @@ namespace spider { + extern const u32 RUNTIME_VERSION_NO; + extern const std::string RUNTIME_VERSION; + class Runtime; class CPU; class RAM; diff --git a/src/spider/runtime/Runtime.cpp b/src/spider/runtime/Runtime.cpp index 461f093..e46bfc4 100644 --- a/src/spider/runtime/Runtime.cpp +++ b/src/spider/runtime/Runtime.cpp @@ -4,17 +4,33 @@ namespace spider { // Constructors & Destructors // - Runtime::Runtime() : ram(0) {} + Runtime::Runtime() : Runtime(0) {} - Runtime::Runtime(u64 ramSize) : ram(ramSize) {} + Runtime::Runtime(u64 ramSize) : ram(ramSize), reel(nullptr) { + cpu.hookRAM(&ram); + } - Runtime::~Runtime() {} + Runtime::~Runtime() { + delete reel; + } // Stepping/Running the Machine // - void Runtime::step() {} + void Runtime::step() { + cpu.fetchInstr(); + // TODO: Call instruction + } - void Runtime::step(u64 n) {} + void Runtime::step(u64 n) { + while(n >= 4) { + step(); + step(); + step(); + step(); + n -= 4; + } + while (n--) step(); + } void Runtime::run() {} @@ -26,4 +42,12 @@ namespace spider { ram.resize(length); } + /** + * Non-owning reel setup. + */ + void Runtime::hookReel(InstrReel* reel, bool own) { + cpu.hookInstrReel(reel); + if(own) this->reel = reel; + } + } diff --git a/src/spider/runtime/Runtime.hpp b/src/spider/runtime/Runtime.hpp index 6541690..dba8bbc 100644 --- a/src/spider/runtime/Runtime.hpp +++ b/src/spider/runtime/Runtime.hpp @@ -1,8 +1,11 @@ #pragma once #include + #include +#include + namespace spider { /** @@ -14,6 +17,7 @@ namespace spider { CPU cpu; RAM ram; + InstrReel* reel; public: @@ -68,6 +72,11 @@ namespace spider { */ void resizeRAM(u64 length); + /** + * Non-owning reel setup. + */ + void hookReel(InstrReel* reel, bool own = false); + }; } diff --git a/src/spider/runtime/common.hpp b/src/spider/runtime/common.hpp index 3507fc0..d8f3d61 100644 --- a/src/spider/runtime/common.hpp +++ b/src/spider/runtime/common.hpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace spider { diff --git a/src/spider/runtime/cpu/CPU.cpp b/src/spider/runtime/cpu/CPU.cpp index 3c9d3f5..17366ff 100644 --- a/src/spider/runtime/cpu/CPU.cpp +++ b/src/spider/runtime/cpu/CPU.cpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -14,13 +15,15 @@ namespace spider { CPU::CPU() : RA{}, RB{}, RC{}, RD{}, - RX{}, RY{}, R0{}, R1{}, - R2{}, R3{}, R4{}, R5{}, - R6{}, R7{}, R8{}, R9{}, - RF{}, RI{}, RS{}, RZ{}, - RE{}, RN{}, RV{}, RM{}, - ALU0{}, + RX{}, RY{}, R0{}, R1{}, + R2{}, R3{}, R4{}, R5{}, + R6{}, R7{}, R8{}, R9{}, + RF{}, RI{}, RS{}, RZ{}, + RE{}, RN{}, RV{}, RM{}, + ALU0{}, ALU1{}, _dst(nullptr), _src(nullptr), + _instr(0), _addrm(0), _size(0), + _store(0), _post(&CPU::imp), _ram(nullptr), _reel(nullptr) { } @@ -53,6 +56,44 @@ namespace spider { #endif } + // Interaction with Reel // + + CPU::Fn CPU::addrModes[] = { + &CPU::imm, &CPU::abs, + &CPU::reg, &CPU::ind, + &CPU::ptr, &CPU::idx, + &CPU::sca, &CPU::dis + }; + + void CPU::fetchInstr() { + u16 i = _reel->readU16(RI); + _instr = (i >> 7) & 0x1FF; + _addrm = (i >> 2) & 0x1F; + _size = i & 0x3; + RI += 2; + } + + void CPU::fetchOperDst() { + // Move the operand ptrs + _alu = &ALU0; + _opers[1] = _opers[0]; + + // call specific addressing mode + (this->*(CPU::addrModes[_addrm & 0x7]))(); + } + + void CPU::fetchOperSrc() { + // set ALU + _alu = &ALU1; + + // call specific addressing mode + (this->*(CPU::addrModes[_addrm & 0x7]))(); + + // modify the _addrm register + _addrm >>= 3; + _addrm++; + } + // Addressing Modes // /** @@ -66,13 +107,67 @@ namespace spider { * Immediate Addressing Mode */ void CPU::imm() { + switch(_size) { + case 0b00: + _alu->_u8 = _reel->readU8(RI); + break; + case 0b01: + _alu->_u16 = _reel->readU16(RI); + break; + case 0b10: + _alu->_u32 = _reel->readU32(RI); + break; + case 0b11: + _alu->_u64 = _reel->readU64(RI); + break; + } + + _opers[0] = _alu; + _post = &CPU::imp; + RI += 1 << _size; } /** * Absolute Addressing Mode */ - void CPU::abs() { - u8 size = 2 << getFlag(CPU::FLAG_MEMORY_MODE); + void CPU::abs() { // TODO cache ptr size + u8 dat_size = 1 << _size; + u8 ptr_size = 1 << getFlag(CPU::FLAG_MEMORY_MODE); + u64 ptr = 0; + + if(ptr_size + dat_size > _ram->size()) return; // TODO: avoid overflow + + switch(ptr_size) { + case 1: + ptr = _reel->readU8(RI); + break; + case 2: + ptr = _reel->readU16(RI); + break; + case 4: + ptr = _reel->readU32(RI); + break; + case 8: + ptr = _reel->readU64(RI); + break; + } + + switch(_size) { + case 0b00: + spider::loadLE(&_alu->_u8, &(*_ram)[ptr]); + break; + case 0b01: + spider::loadLE(&_alu->_u16, &(*_ram)[ptr]); + break; + case 0b10: + spider::loadLE(&_alu->_u32, &(*_ram)[ptr]); + break; + case 0b11: + spider::loadLE(&_alu->_u64, &(*_ram)[ptr]); + break; + } + + RI += dat_size; } /** @@ -107,4 +202,9 @@ namespace spider { */ void CPU::dis() {} + /** + * Post Write Action + */ + void CPU::psw() {} + } diff --git a/src/spider/runtime/cpu/CPU.hpp b/src/spider/runtime/cpu/CPU.hpp index fba9c8e..afc8984 100644 --- a/src/spider/runtime/cpu/CPU.hpp +++ b/src/spider/runtime/cpu/CPU.hpp @@ -6,6 +6,9 @@ namespace spider { class CPU { + public: // Helper types + using Fn = void (CPU::*)(); + public: // Flag Register Constants // static constexpr const u64 FLAG_ENABLE = 0b0000000000000000000000000000000000000000000000000000000000000001; static constexpr const u64 FLAG_INTERRUPT_SIGNAL = 0b0000000000000000000000000000000000000000000000000000000000000010; @@ -13,6 +16,10 @@ namespace spider { static constexpr const u64 FLAG_EXCEPTION = 0b0000000000000000000000000000000000000000000000000000000000001000; static constexpr const u64 FLAG_MEMORY_MODE = 0b0000000000000000000000000000000000000000000000000000000000110000; + public: // Map of addressing modes + + static CPU::Fn addrModes[]; + public: // General Purpose Registers union { register_t GPR[16]; @@ -42,16 +49,33 @@ namespace spider { * This way we don't "write" into constant values, rather * we write into a writeable var which is "hidden" */ - register_t ALU0; + register_t ALU0, ALU1; union { struct { register_t* _dst; register_t* _src; + register_t* _alu; }; register_t* _opers[2]; }; - + + // Holds the current instruction opcode + u16 _instr : 9; + + // Holds the current addressing modes, + // before they were used + u8 _addrm : 5; + + // Holds the current instruction size. + u8 _size : 2; + + // On _post that are not no-ops, it must + // write back DST to this memory location. + u64 _store; + + // Post execution callback + CPU::Fn _post; private: @@ -99,13 +123,52 @@ namespace spider { void hookInstrReel(InstrReel* reel); constexpr u64 getFlag(u64 mask); + + public: + + /** + * Fetches the instruction from the + * reel, and advances IR by two. + */ + void fetchInstr(); + + /** + * Fetches the destination operand, + * by calling the appropriate addressing + * mode. + * + * Will read the bottom 3 bits. + * For instructions with two operands, + * call Src first. + * + * The internal variable _addrm + * will not be modified. It will + * be important when writing + * back the result. + */ + void fetchOperDst(); + + /** + * Fetches the source operand. + * + * For use in two operand instructions. + * + * Will read the bottom 3 bits. It will + * then shift the _addrm 3 spaces + * to ensure it aligns with the DST + * next. + * + * Additionally, it will add 1 to _addrm + * to account with + */ + void fetchOperSrc(); public: // Addressing Modes /** * Implied Addressing Mode */ - void imp(); + void imp(); // Kept as it is a no-op /** * Immediate Addressing Mode @@ -147,6 +210,11 @@ namespace spider { */ void dis(); + /** + * Post-Write Action + */ + void psw(); + public: // // diff --git a/src/spider/runtime/debug/LiveDebug.cpp b/src/spider/runtime/debug/LiveDebug.cpp index cfe94a4..8e80d5c 100644 --- a/src/spider/runtime/debug/LiveDebug.cpp +++ b/src/spider/runtime/debug/LiveDebug.cpp @@ -1,5 +1,7 @@ #include "LiveDebug.hpp" +#include + #include #include #include @@ -122,7 +124,7 @@ namespace spider { &cpu.R6, &cpu.R7, &cpu.R8, &cpu.R9, //&cpu.RF, &cpu.RI, &cpu.RS, &cpu.RZ, //&cpu.RE, &cpu.RN, &cpu.RV, &cpu.RM, - &cpu.ALU0, &cpu.ALU0 + &cpu.ALU0, &cpu.ALU1 }; const u64* sys_regs[] = { &cpu.RF, &cpu.RI, &cpu.RS, &cpu.RZ, @@ -148,12 +150,12 @@ namespace spider { t.move(r += 16, c); r++; - for (i32 j = 0, i = 8; i < 12; j++, i++) { + for (i32 j = 0; j < 4; j++) { t.style(alt[j & 1]); t.move(r + j * 2, c); - printU64Hex(*sys_regs[i * 2]); + printU64Hex(*sys_regs[j * 2]); t.move(r + j * 2, c + 17); - printU64Hex(*sys_regs[i * 2 + 1]); + printU64Hex(*sys_regs[j * 2 + 1]); } t.move(r += 8, c); @@ -328,6 +330,17 @@ namespace spider { int liveDebugMain() { Terminal t; Runtime runtime(1024); + InstrReelFixed fix(100); + runtime.ram[0] = 0xFF; + runtime.ram[1] = 0xEE; + runtime.ram[2] = 0xDD; + runtime.ram[3] = 0xCC; + runtime.ram[4] = 0xBB; + runtime.ram[5] = 0xAA; + runtime.ram[6] = 0x99; + runtime.ram[7] = 0x88; + fix.writeU16(0, 0b0000111); + runtime.hookReel(&fix, false); bool running = true, update = true; u64 ramScroll = 0; @@ -372,6 +385,11 @@ namespace spider { if (runtime.ram.size() >= 16 && ramScroll <= runtime.ram.size() - 16) ramScroll += 16; update = true; break; + case Terminal::ENTER: + update = true; + runtime.cpu.fetchInstr(); + runtime.cpu.fetchOperDst(); + break; default: break; } From 291aa0a9494f2ba776eb6905c351dee8924e68c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20De=20Gante=20P=C3=A9rez?= Date: Wed, 25 Mar 2026 10:15:56 -0600 Subject: [PATCH 09/10] Add InstrMap.cpp generation to pygen --- autogen/InstructionMasks.hpp | 280 ++++++----- docs/Spider Instructions.xlsx | Bin 107379 -> 111172 bytes pygen.ipynb | 194 +++++++- src/spider/runtime/cpu/CPU.hpp | 222 +++++---- src/spider/runtime/cpu/InstrMap.cpp | 748 ++++++++++++++++++++++++++++ 5 files changed, 1187 insertions(+), 257 deletions(-) create mode 100644 src/spider/runtime/cpu/InstrMap.cpp diff --git a/autogen/InstructionMasks.hpp b/autogen/InstructionMasks.hpp index 9be8113..34a0909 100644 --- a/autogen/InstructionMasks.hpp +++ b/autogen/InstructionMasks.hpp @@ -16,107 +16,107 @@ constexpr u8 ADDR_MODE_MASKS[][2] = { { 0x1E, 0x00 }, // FIR { 0x1E, 0x00 }, // FZR { 0x1E, 0x1F }, // LSR - { 0x00, 0x00 }, // FVR - { 0x00, 0x00 }, // MOV - { 0x00, 0x00 }, // MOR + { 0x04, 0x00 }, // FVR + { 0x1E, 0xFF }, // MOV + { 0x04, 0x04 }, // MOR { 0x00, 0x00 }, // AMOV { 0x04, 0x04 }, // SWP { 0x04, 0x00 }, // AHM - { 0x00, 0x00 }, // COM - { 0x00, 0x00 }, // NEG - { 0x00, 0x00 }, // EXS - { 0x00, 0x00 }, // INC - { 0x00, 0x00 }, // DEC - { 0x00, 0x00 }, // ADD - { 0x00, 0x00 }, // SUB - { 0x00, 0x00 }, // MUL - { 0x00, 0x00 }, // UMUL - { 0x00, 0x00 }, // DIV - { 0x00, 0x00 }, // UDIV - { 0x00, 0x00 }, // MOD - { 0x00, 0x00 }, // UMOD - { 0x00, 0x00 }, // DMOD - { 0x00, 0x00 }, // UDMD - { 0x00, 0x00 }, // FBT - { 0x00, 0x00 }, // STB - { 0x00, 0x00 }, // CRB - { 0x00, 0x00 }, // TSB - { 0x00, 0x00 }, // BOOL - { 0x00, 0x00 }, // NOT - { 0x00, 0x00 }, // AND - { 0x00, 0x00 }, // OR - { 0x00, 0x00 }, // XOR - { 0x00, 0x00 }, // SHL - { 0x00, 0x00 }, // SHR - { 0x00, 0x00 }, // SSR - { 0x00, 0x00 }, // ROL - { 0x00, 0x00 }, // ROR - { 0x00, 0x00 }, // CNT - { 0x00, 0x00 }, // EQ - { 0x00, 0x00 }, // NE - { 0x00, 0x00 }, // GT - { 0x00, 0x00 }, // GE - { 0x00, 0x00 }, // LT - { 0x00, 0x00 }, // LE - { 0x00, 0x00 }, // JMP - { 0x00, 0x00 }, // JEQ - { 0x00, 0x00 }, // JNE - { 0x00, 0x00 }, // JIF - { 0x00, 0x00 }, // JMR - { 0x00, 0x00 }, // JER - { 0x00, 0x00 }, // JNR - { 0x00, 0x00 }, // JIR - { 0x00, 0x00 }, // SFB - { 0x00, 0x00 }, // LFB - { 0x00, 0x00 }, // JUF - { 0x00, 0x00 }, // JUR - { 0x00, 0x00 }, // PUSH - { 0x00, 0x00 }, // POP - { 0x00, 0x00 }, // ALLOC - { 0x00, 0x00 }, // HFREE - { 0x00, 0x00 }, // CALL + { 0xFF, 0x00 }, // COM + { 0xFF, 0x00 }, // NEG + { 0xFF, 0x00 }, // EXS + { 0xFF, 0x00 }, // INC + { 0xFF, 0x00 }, // DEC + { 0x1E, 0xFF }, // ADD + { 0x1E, 0xFF }, // SUB + { 0x1E, 0xFF }, // MUL + { 0x1E, 0xFF }, // UMUL + { 0x1E, 0xFF }, // DIV + { 0x1E, 0xFF }, // UDIV + { 0x1E, 0xFF }, // MOD + { 0x1E, 0xFF }, // UMOD + { 0x1E, 0xFF }, // DMOD + { 0x1E, 0xFF }, // UDMD + { 0xFF, 0x00 }, // FBT + { 0x1E, 0xFF }, // STB + { 0x1E, 0xFF }, // CRB + { 0x1E, 0xFF }, // TSB + { 0xFF, 0x00 }, // BOOL + { 0xFF, 0x00 }, // NOT + { 0x1E, 0xFF }, // AND + { 0x1E, 0xFF }, // OR + { 0x1E, 0xFF }, // XOR + { 0x1E, 0xFF }, // SHL + { 0x1E, 0xFF }, // SHR + { 0x1E, 0xFF }, // SSR + { 0x1E, 0xFF }, // ROL + { 0x1E, 0xFF }, // ROR + { 0xFF, 0x00 }, // CNT + { 0x1E, 0xFF }, // EQ + { 0x1E, 0xFF }, // NE + { 0x1E, 0xFF }, // GT + { 0x1E, 0xFF }, // GE + { 0x1E, 0xFF }, // LT + { 0x1E, 0xFF }, // LE + { 0xFF, 0x00 }, // JMP + { 0xFF, 0x00 }, // JEQ + { 0xFF, 0x00 }, // JNE + { 0x1E, 0xFF }, // JIF + { 0xFF, 0x00 }, // JMR + { 0xFF, 0x00 }, // JER + { 0xFF, 0x00 }, // JNR + { 0x1E, 0xFF }, // JIR + { 0x1E, 0xFF }, // SFB + { 0x1E, 0xFF }, // LFB + { 0x1E, 0xFF }, // JUF + { 0x1E, 0xFF }, // JUR + { 0xFF, 0x00 }, // PUSH + { 0xFF, 0x00 }, // POP + { 0xFF, 0x00 }, // ALLOC + { 0xFF, 0x00 }, // HFREE + { 0xFF, 0x00 }, // CALL { 0x00, 0x00 }, // RET - { 0x00, 0x00 }, // EDI - { 0x00, 0x00 }, // SHSS - { 0x00, 0x00 }, // FLI - { 0x00, 0x00 }, // FNEG - { 0x00, 0x00 }, // FADD - { 0x00, 0x00 }, // FSUB - { 0x00, 0x00 }, // FMUL - { 0x00, 0x00 }, // FDIV - { 0x00, 0x00 }, // FMOD - { 0x00, 0x00 }, // FDMOD - { 0x00, 0x00 }, // FEPS - { 0x00, 0x00 }, // FEEP - { 0x00, 0x00 }, // FEQ - { 0x00, 0x00 }, // FNE - { 0x00, 0x00 }, // FGT - { 0x00, 0x00 }, // FGE - { 0x00, 0x00 }, // FLT - { 0x00, 0x00 }, // FLE - { 0x00, 0x00 }, // F2D - { 0x00, 0x00 }, // D2F - { 0x00, 0x00 }, // I2F - { 0x00, 0x00 }, // I2D - { 0x00, 0x00 }, // L2F - { 0x00, 0x00 }, // L2D - { 0x00, 0x00 }, // F2I - { 0x00, 0x00 }, // F2L - { 0x00, 0x00 }, // D2I - { 0x00, 0x00 }, // D2L - { 0x00, 0x00 }, // SIN - { 0x00, 0x00 }, // COS - { 0x00, 0x00 }, // TAN - { 0x00, 0x00 }, // ASIN - { 0x00, 0x00 }, // ACOS - { 0x00, 0x00 }, // ATAN - { 0x00, 0x00 }, // ATAN2 - { 0x00, 0x00 }, // EXP - { 0x00, 0x00 }, // LOG - { 0x00, 0x00 }, // LOGAB - { 0x00, 0x00 }, // POW - { 0x00, 0x00 }, // SQRT - { 0x00, 0x00 }, // ROOT + { 0xFF, 0x00 }, // EDI + { 0xFF, 0x00 }, // SHSS + { 0xFF, 0x00 }, // FLI + { 0xFF, 0x00 }, // FNEG + { 0x1E, 0xFF }, // FADD + { 0x1E, 0xFF }, // FSUB + { 0x1E, 0xFF }, // FMUL + { 0x1E, 0xFF }, // FDIV + { 0x1E, 0xFF }, // FMOD + { 0x1E, 0xFF }, // FDMOD + { 0xFF, 0x00 }, // FEPS + { 0xFF, 0x00 }, // FEEP + { 0x1E, 0xFF }, // FEQ + { 0x1E, 0xFF }, // FNE + { 0x1E, 0xFF }, // FGT + { 0x1E, 0xFF }, // FGE + { 0x1E, 0xFF }, // FLT + { 0x1E, 0xFF }, // FLE + { 0xFF, 0x00 }, // F2D + { 0xFF, 0x00 }, // D2F + { 0xFF, 0x00 }, // I2F + { 0xFF, 0x00 }, // I2D + { 0xFF, 0x00 }, // L2F + { 0xFF, 0x00 }, // L2D + { 0xFF, 0x00 }, // F2I + { 0xFF, 0x00 }, // F2L + { 0xFF, 0x00 }, // D2I + { 0xFF, 0x00 }, // D2L + { 0xFF, 0x00 }, // SIN + { 0xFF, 0x00 }, // COS + { 0xFF, 0x00 }, // TAN + { 0xFF, 0x00 }, // ASIN + { 0xFF, 0x00 }, // ACOS + { 0xFF, 0x00 }, // ATAN + { 0x1E, 0xFF }, // ATAN2 + { 0xFF, 0x00 }, // EXP + { 0xFF, 0x00 }, // LOG + { 0x1E, 0xFF }, // LOGAB + { 0x1E, 0xFF }, // POW + { 0xFF, 0x00 }, // SQRT + { 0x1E, 0xFF }, // ROOT { 0x00, 0x00 }, // ADC { 0x00, 0x00 }, // SWC { 0x00, 0x00 }, // MWO @@ -127,6 +127,8 @@ constexpr u8 ADDR_MODE_MASKS[][2] = { { 0x00, 0x00 }, // MINV { 0x00, 0x00 }, // MTRA { 0x00, 0x00 }, // MDET + { 0x00, 0x00 }, // QMKA + { 0x00, 0x00 }, // QMUL { 0x00, 0x00 }, // XADD { 0x00, 0x00 }, // XSUB { 0x00, 0x00 }, // XAMA @@ -140,18 +142,18 @@ constexpr u8 TYPE_SIZE_MASKS[] = { 0x00, // NOP 0x00, // SPDR 0x01, // MMODE - 0x0F, // INT - 0x0C, // LRV - 0x0F, // FSR - 0x0F, // FIR - 0x0F, // FZR - 0x0F, // LSR - 0x0F, // FVR - 0x00, // MOV - 0x00, // MOR - 0x00, // AMOV - 0x00, // SWP - 0x0F, // AHM + 0x08, // INT + 0x08, // LRV + 0x08, // FSR + 0x08, // FIR + 0x08, // FZR + 0x08, // LSR + 0x08, // FVR + 0x0F, // MOV + 0x08, // MOR + 0x08, // AMOV + 0x08, // SWP + 0x08, // AHM 0x0F, // COM 0x0F, // NEG 0x0F, // EXS @@ -208,22 +210,22 @@ constexpr u8 TYPE_SIZE_MASKS[] = { 0x0F, // RET 0x0F, // EDI 0x0F, // SHSS - 0x00, // FLI - 0x00, // FNEG - 0x00, // FADD - 0x00, // FSUB - 0x00, // FMUL - 0x00, // FDIV - 0x00, // FMOD - 0x00, // FDMOD - 0x00, // FEPS - 0x00, // FEEP - 0x00, // FEQ - 0x00, // FNE - 0x00, // FGT - 0x00, // FGE - 0x00, // FLT - 0x00, // FLE + 0x0C, // FLI + 0x0C, // FNEG + 0x0C, // FADD + 0x0C, // FSUB + 0x0C, // FMUL + 0x0C, // FDIV + 0x0C, // FMOD + 0x0C, // FDMOD + 0x0C, // FEPS + 0x0C, // FEEP + 0x0C, // FEQ + 0x0C, // FNE + 0x0C, // FGT + 0x0C, // FGE + 0x0C, // FLT + 0x0C, // FLE 0x00, // F2D 0x00, // D2F 0x00, // I2F @@ -234,19 +236,19 @@ constexpr u8 TYPE_SIZE_MASKS[] = { 0x00, // F2L 0x00, // D2I 0x00, // D2L - 0x00, // SIN - 0x00, // COS - 0x00, // TAN - 0x00, // ASIN - 0x00, // ACOS - 0x00, // ATAN - 0x00, // ATAN2 - 0x00, // EXP - 0x00, // LOG - 0x00, // LOGAB - 0x00, // POW - 0x00, // SQRT - 0x00, // ROOT + 0x0C, // SIN + 0x0C, // COS + 0x0C, // TAN + 0x0C, // ASIN + 0x0C, // ACOS + 0x0C, // ATAN + 0x0C, // ATAN2 + 0x0C, // EXP + 0x0C, // LOG + 0x0C, // LOGAB + 0x0C, // POW + 0x0C, // SQRT + 0x0C, // ROOT 0x00, // ADC 0x00, // SWC 0x00, // MWO @@ -257,6 +259,8 @@ constexpr u8 TYPE_SIZE_MASKS[] = { 0x00, // MINV 0x00, // MTRA 0x00, // MDET + 0x00, // QMKA + 0x00, // QMUL 0x00, // XADD 0x00, // XSUB 0x00, // XAMA diff --git a/docs/Spider Instructions.xlsx b/docs/Spider Instructions.xlsx index e6643206690099ab862afcfc17f1561b75db77c5..94a7f57a29a55021c3ca35dec79738efdd2212d4 100644 GIT binary patch literal 111172 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
53lwmb5x056#Tmbl@F>%yEwD{5UDZ-g4 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 literal 107379 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
~t~ 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 a@3r%p%ry||$W#*lN(hL+o+J>8SN<<@qE4~^ diff --git a/pygen.ipynb b/pygen.ipynb index 4d88ae5..ac955c4 100644 --- a/pygen.ipynb +++ b/pygen.ipynb @@ -15,7 +15,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 34, "id": "b0fcd533", "metadata": {}, "outputs": [ @@ -68,7 +68,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 35, "id": "b33de8ac", "metadata": {}, "outputs": [ @@ -144,7 +144,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 36, "id": "58645013", "metadata": {}, "outputs": [ @@ -152,7 +152,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Real instructions : 126\n", + "Real instructions : 128\n", "Reserved slots : 14\n", "Duplicate check : PASSED\n", "\n", @@ -163,13 +163,14 @@ "Bit Wise 14\n", "Boolean 12\n", "Branch 12\n", - "Casts 10\n", "Floating Point 10\n", + "Casts 10\n", "Memory 9\n", "Trigonometric 7\n", "Exponential 6\n", "Matrix 6\n", "SIMD 5\n", + "Quaternion 2\n", "Easter Eggs 1\n", "\n", "First 5 instructions:\n", @@ -177,8 +178,8 @@ "0 000 NOP System 0 00 00\n", "1 001 SPDR System 0 00 00\n", "2 002 MMODE System 1 05 01\n", - "3 003 INT System 1 1F 0F\n", - "4 004 LRV System 1 1F 0C\n" + "3 003 INT System 1 1F 08\n", + "4 004 LRV System 1 1F 08\n" ] } ], @@ -214,6 +215,7 @@ " 'dis', # addressing mode: Displaced\n", " 'addr_mask_1', # accepted addressing mode mask for param 1\n", " 'addr_mask_2', # accepted addressing mode mask for param 2\n", + " 'ignores_addrm',# whether the instruction ignores addressing modes\n", " 'B', # type size: Byte (1 byte) supported?\n", " 'S', # type size: Short (2 bytes) supported?\n", " 'I', # type size: Int (4 bytes) supported?\n", @@ -221,6 +223,7 @@ " 'F', # type size: Float supported?\n", " 'D', # type size: Double supported?\n", " 'type_mask', # combined type size mask as hex string\n", + " 'expensive', # marks computationally expensive instructions\n", " 'operation', # human-readable description of what the instruction does\n", " 'skip_2', # trailing empty column\n", "]\n", @@ -280,7 +283,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 37, "id": "452bc76c", "metadata": {}, "outputs": [ @@ -289,7 +292,7 @@ "output_type": "stream", "text": [ "Masks written to: .//autogen/InstructionMasks.hpp\n", - "Lines generated : 268\n" + "Lines generated : 272\n" ] } ], @@ -354,7 +357,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 38, "id": "5aaebef0", "metadata": {}, "outputs": [ @@ -362,7 +365,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Instructions formatted: 126\n", + "Instructions formatted: 128\n", "\n", "--- Preview (first 2 instructions) ---\n", " // [System] 0x000 — NOP: No Operation\n", @@ -377,7 +380,7 @@ "\n", "\n", "CPU.hpp updated successfully at: .//src//spider/runtime/cpu/CPU.hpp\n", - "Total lines in updated file: 674\n" + "Total lines in updated file: 792\n" ] } ], @@ -445,6 +448,171 @@ "print(f'\\nCPU.hpp updated successfully at: {CPU_HPP_PATH}')\n", "print(f'Total lines in updated file: {len(updated.splitlines())}')\n" ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "instrmap_gen", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "InstrMap.cpp written to: .//src//spider/runtime/cpu/InstrMap.cpp\n", + " Size : 34,246 bytes\n", + " Array entries : 512 (128 populated, 384 nullptr)\n", + " Switch cases : 128\n", + " Line endings : LF-only verified\n" + ] + } + ], + "source": [ + "# ── Generate InstrMap.cpp ────────────────────────────────────────────────────\n", + "# Produces two dispatch implementations in one file:\n", + "# 1. CPUInstr InstrMap[512] — array of member-function pointers\n", + "# 2. void CPU::execute(u16) — switch/case version\n", + "#\n", + "# Both use UPPERCASE method names matching the mnemonic column.\n", + "\n", + "TABLE_SIZE = 512 # 9-bit opcode space\n", + "\n", + "# Build opcode -> mnemonic lookup from the cleaned instruction DataFrame.\n", + "opcode_to_mnem: dict[int, str] = {}\n", + "opcode_to_name: dict[int, str] = {}\n", + "opcode_to_group: dict[int, str] = {}\n", + "\n", + "for _, row in instrs_df.iterrows():\n", + " bc = str(row['byte_code']).strip()\n", + " opc = int(bc, 16)\n", + " opcode_to_mnem[opc] = str(row['mnemonic']).strip()\n", + " opcode_to_name[opc] = str(row['name']).strip()\n", + " opcode_to_group[opc] = str(row['group']).strip()\n", + "\n", + "# Also track reserved slots for annotation.\n", + "reserved_opcodes: set[int] = set()\n", + "for _, row in reserved_df.iterrows():\n", + " bc = str(row['byte_code']).strip()\n", + " if bc and bc != 'nan':\n", + " reserved_opcodes.add(int(bc, 16))\n", + "\n", + "# ── Assemble the file ───────────────────────────────────────────────────────\n", + "L = []\n", + "L.append('/**')\n", + "L.append(' * @file InstrMap.cpp')\n", + "L.append(' * @brief Spider VM instruction dispatch — array and switch implementations.')\n", + "L.append(' *')\n", + "L.append(' * AUTO-GENERATED by pygen.ipynb — DO NOT EDIT BY HAND.')\n", + "L.append(' *')\n", + "L.append(' * This file provides two equivalent dispatch mechanisms:')\n", + "L.append(' *')\n", + "L.append(' * 1. InstrMap[] — A lookup table of member-function pointers indexed by')\n", + "L.append(' * opcode. O(1) dispatch; suitable for platforms where')\n", + "L.append(' * indirect calls through function pointers are efficient.')\n", + "L.append(' *')\n", + "L.append(' * 2. CPU::execute(u16) — A switch/case over every opcode. Lets the')\n", + "L.append(' * compiler emit a jump table or branch tree; may be')\n", + "L.append(' * preferable on microcontrollers or when link-time')\n", + "L.append(' * optimisation can inline the handlers.')\n", + "L.append(' *')\n", + "L.append(' */')\n", + "L.append('')\n", + "L.append('#include \"CPU.hpp\"')\n", + "L.append('')\n", + "L.append('namespace spider {')\n", + "L.append('')\n", + "\n", + "# ── Version 1: Array: ────────────────────────────────────────────────────────\n", + "L.append('// =============================================================')\n", + "L.append('// Version 1 — Lookup table of member-function pointers')\n", + "L.append('// =============================================================')\n", + "L.append('')\n", + "L.append('/** Pointer-to-member type for a zero-argument CPU instruction. */')\n", + "L.append('using CPUInstr = void (CPU::*)();')\n", + "L.append('')\n", + "L.append('/**')\n", + "L.append(f' * Instruction dispatch table ({TABLE_SIZE} entries, 9-bit opcode space).')\n", + "L.append(' *')\n", + "L.append(' * Usage:')\n", + "L.append(' * u16 opcode = fetch();')\n", + "L.append(' * CPUInstr fn = InstrMap[opcode];')\n", + "L.append(' * if (fn) (cpu.*fn)();')\n", + "L.append(' */')\n", + "L.append(f'CPUInstr InstrMap[{TABLE_SIZE}] = {{')\n", + "\n", + "for opc in range(TABLE_SIZE):\n", + " mnem = opcode_to_mnem.get(opc)\n", + " if mnem:\n", + " name = opcode_to_name[opc]\n", + " L.append(f' &CPU::{mnem + \",\":<28s}// 0x{opc:03X} — {name}')\n", + " else:\n", + " tag = ''\n", + " if opc in reserved_opcodes:\n", + " tag = ' (reserved)'\n", + " L.append(f' {\"nullptr,\":<28s}// 0x{opc:03X}{tag}')\n", + "\n", + "L.append('};')\n", + "L.append('')\n", + "L.append('')\n", + "\n", + "# ── Version 2: Switch ──────────────────────────────────────────────────────\n", + "L.append('// =============================================================')\n", + "L.append('// Version 2 — Switch dispatch')\n", + "L.append('// =============================================================')\n", + "L.append('')\n", + "L.append('/**')\n", + "L.append(' * Execute the instruction identified by @p opcode.')\n", + "L.append(' *')\n", + "L.append(' * This is functionally equivalent to the InstrMap[] table above')\n", + "L.append(' * but expressed as a switch so the compiler can choose the best')\n", + "L.append(' * lowering strategy (jump table, binary search, etc.).')\n", + "L.append(' *')\n", + "L.append(' * @param opcode 9-bit instruction opcode (0x000 – 0x1FF).')\n", + "L.append(' */')\n", + "L.append('void CPU::execute(u16 opcode) {')\n", + "L.append(' switch (opcode) {')\n", + "\n", + "last_group = None\n", + "for opc in sorted(opcode_to_mnem.keys()):\n", + " mnem = opcode_to_mnem[opc]\n", + " group = opcode_to_group[opc]\n", + " if group != last_group:\n", + " L.append('')\n", + " L.append(f' // ── {group} ' + '─' * max(1, 44 - len(group)))\n", + " last_group = group\n", + " L.append(f' case 0x{opc:03X}: {mnem}(); break;')\n", + "\n", + "L.append('')\n", + "L.append(' default:')\n", + "L.append(' break;')\n", + "L.append(' }')\n", + "L.append('}')\n", + "L.append('')\n", + "L.append('} // namespace spider')\n", + "L.append('')\n", + "\n", + "INSTRMAP_SRC = '\\n'.join(L)\n", + "\n", + "# ── Write to file ───────────────────────────────────────────────────────────\n", + "INSTRMAP_PATH = f'{SRC_ROOT}/spider/runtime/cpu/InstrMap.cpp'\n", + "\n", + "with open(INSTRMAP_PATH, 'wb') as f:\n", + " f.write(INSTRMAP_SRC.encode('utf-8'))\n", + "\n", + "# Verify LF-only\n", + "with open(INSTRMAP_PATH, 'rb') as f:\n", + " raw_bytes = f.read()\n", + "assert b'\\r' not in raw_bytes, 'CRLF detected in InstrMap.cpp!'\n", + "\n", + "array_count = INSTRMAP_SRC.count('&CPU::')\n", + "switch_count = INSTRMAP_SRC.count('case 0x')\n", + "\n", + "print(f'InstrMap.cpp written to: {INSTRMAP_PATH}')\n", + "print(f' Size : {len(raw_bytes):,} bytes')\n", + "print(f' Array entries : {TABLE_SIZE} ({array_count} populated, {TABLE_SIZE - array_count} nullptr)')\n", + "print(f' Switch cases : {switch_count}')\n", + "print(f' Line endings : LF-only verified')\n" + ] } ], "metadata": { @@ -463,7 +631,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.7" + "version": "3.10.5" } }, "nbformat": 4, diff --git a/src/spider/runtime/cpu/CPU.hpp b/src/spider/runtime/cpu/CPU.hpp index afc8984..057c7ba 100644 --- a/src/spider/runtime/cpu/CPU.hpp +++ b/src/spider/runtime/cpu/CPU.hpp @@ -234,327 +234,327 @@ namespace spider { void MMODE(); // [System] 0x003 — INT: Interrupt - // Params: 1 | AddrMask1: 1F AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: 1F AddrMask2: 00 | TypeMask: 08 // Operation: Performs system interrupt no. (Dst) (See table) void INT(); // [System] 0x004 — LRV: Load Interrupt Vector Register - // Params: 1 | AddrMask1: 1F AddrMask2: 00 | TypeMask: 0C + // Params: 1 | AddrMask1: 1F AddrMask2: 00 | TypeMask: 08 // Operation: Dst -> RV void LRV(); // [System] 0x005 — FSR: Fetch System Register - // Params: 1 | AddrMask1: 1E AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: 1E AddrMask2: 00 | TypeMask: 08 // Operation: System Register at Dst -> Dst void FSR(); // [System] 0x006 — FIR: Fetch Instruction Register - // Params: 1 | AddrMask1: 1E AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: 1E AddrMask2: 00 | TypeMask: 08 // Operation: Instruction Register -> Dst void FIR(); // [System] 0x007 — FZR: Fetch Stack Base Register - // Params: 1 | AddrMask1: 1E AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: 1E AddrMask2: 00 | TypeMask: 08 // Operation: Stack Base Register -> Dst void FZR(); // [System] 0x008 — LSR: Load System Register - // Params: 2 | AddrMask1: 1E AddrMask2: 1F | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: 1F | TypeMask: 08 // Operation: Src -> System Register at Dst void LSR(); // [System] 0x009 — FVR: Fetch Interrupt Vector Register - // Params: 1 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: 04 AddrMask2: 00 | TypeMask: 08 // Operation: Interrupt Vector Register -> Dst void FVR(); // [Memory] 0x00A — MOV: Moves values - // Params: 2 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Src -> Dst void MOV(); // [Memory] 0x00B — MOR: Moves registers - // Params: 2 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 2 | AddrMask1: 04 AddrMask2: 04 | TypeMask: 08 // Operation: R Scr -> R Dst void MOR(); // [Memory] 0x00C — AMOV: Array Move, uses X and Y as ptrs, A as amount - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 08 // Operation: Array from X to Y, by A amount void AMOV(); // [Memory] 0x00D — SWP: Swap registers - // Params: 2 | AddrMask1: 04 AddrMask2: 04 | TypeMask: 00 + // Params: 2 | AddrMask1: 04 AddrMask2: 04 | TypeMask: 08 // Operation: Src <-> Dst void SWP(); // [Memory] 0x00E — AHM: Ask Host for Memory - // Params: 1 | AddrMask1: 04 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: 04 AddrMask2: 00 | TypeMask: 08 // Operation: Asks the host for a specific size of memory. Responds with 0 or 1 void AHM(); // [Integer] 0x010 — COM: One's complement - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0F // Operation: ~ Dst -> Dst void COM(); // [Integer] 0x011 — NEG: Two's complement - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0F // Operation: - Dst -> Dst void NEG(); // [Integer] 0x012 — EXS: Extend Sign - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0F // Operation: Last bit is copied and expanded for the next int size void EXS(); // [Integer] 0x013 — INC: Increment - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0F // Operation: Dst + 1 -> Dst void INC(); // [Integer] 0x014 — DEC: Decrement - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0F // Operation: Dst - 1 -> Dst void DEC(); // [Integer] 0x015 — ADD: Addition - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Dst + Src -> Dst void ADD(); // [Integer] 0x016 — SUB: Subtraction - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Dst - Src-> Dst void SUB(); // [Integer] 0x017 — MUL: Multiplication - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Signed Dst * Src -> Dst void MUL(); // [Integer] 0x018 — UMUL: Unsigned Multiplication - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Unsigned Dst * Src -> Dst void UMUL(); // [Integer] 0x019 — DIV: Division - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Signed Dst / Src -> Dst void DIV(); // [Integer] 0x01A — UDIV: Unsigned Division - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Unsigned Dst / Src -> Dst void UDIV(); // [Integer] 0x01B — MOD: Modulus - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Signed Dst % Src -> Dst void MOD(); // [Integer] 0x01C — UMOD: Unsigned Modulus - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Unsigned Dst % Src -> Dst void UMOD(); // [Integer] 0x01D — DMOD: Division and Modulus - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Signed Dst / Src -> X, Dst % Src -> Y void DMOD(); // [Integer] 0x01E — UDMD: Unsigned Division and Modulus - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Unsigned Dst / Src -> X, Dst % Src -> Y void UDMD(); // [System] 0x01F — FBT: Test and update Flag Register (Integer) Bits - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0F // Operation: Flags of Dst - void FBT(); // [Bit Wise] 0x020 — STB: Set Bit - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Src# bit is set on Dst void STB(); // [Bit Wise] 0x021 — CRB: Clear Bit - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Src# bit is cleared on Dst void CRB(); // [Bit Wise] 0x022 — TSB: Test Bit - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Src# bit is tested against Dst, updates Equal Flag void TSB(); // [Bit Wise] 0x023 — BOOL: Sets the booleaness of a value - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0F // Operation: Tests Dst != 0, updates Equal Flag void BOOL(); // [Bit Wise] 0x024 — NOT: Sets the inverse booleaness of a value (! BOOL) - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0F // Operation: Tests Dst == 0, updates Equal Flag void NOT(); // [Bit Wise] 0x025 — AND: Boolean AND operation - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Dst AND Src into Dst void AND(); // [Bit Wise] 0x026 — OR: Boolean OR operation - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Dst OR Src into Dst void OR(); // [Bit Wise] 0x027 — XOR: Boolean XOR operation - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Dst XOR Src into Dst void XOR(); // [Bit Wise] 0x028 — SHL: Arithmetic Shift Left - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Dst << Src into Dst void SHL(); // [Bit Wise] 0x029 — SHR: Arithmetic Shift Right - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Dst >> Src into Dst void SHR(); // [Bit Wise] 0x02A — SSR: Signed Shift Right - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Dst >>> Src into Dst void SSR(); // [Bit Wise] 0x02B — ROL: Rotate Left - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Dst ROL Src into Dst void ROL(); // [Bit Wise] 0x02C — ROR: Rotate Right - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Dst ROR Src into Dst void ROR(); // [Bit Wise] 0x02D — CNT: Counts bits - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0F // Operation: # of 1's into Dst void CNT(); // [Boolean] 0x030 — EQ: Equal - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Dst == Src into Dst void EQ(); // [Boolean] 0x031 — NE: Not Equal - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Dst != Src into Dst void NE(); // [Boolean] 0x032 — GT: Greater Than - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Dst > Src into Dst void GT(); // [Boolean] 0x033 — GE: Greater or Equal Than - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Dst >= Src into Dst void GE(); // [Boolean] 0x034 — LT: Lower Than - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Dst < Src into Dst void LT(); // [Boolean] 0x035 — LE: Lower or Equal Than - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Dst <= Src into Dst void LE(); // [Branch] 0x038 — JMP: Jump to absolute position - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0F // Operation: Dst -> Instruction Register void JMP(); // [Branch] 0x039 — JEQ: Jumps to position if EQ flag is set - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0F // Operation: Dst -> Instruction Register IF Flags.EQ void JEQ(); // [Branch] 0x03A — JNE: Jumps to position if EQ flag is cleared - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0F // Operation: Dst -> Instruction Register IF NOT Flags.EQ void JNE(); // [Branch] 0x03B — JIF: Jumps if value provided is booleanly true - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Dst -> Instruction Register IF Src void JIF(); // [Branch] 0x03C — JMR: Jump Relative - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0F // Operation: Dst + Instruction Register -> Instruction Register void JMR(); // [Branch] 0x03D — JER: Jumps to relative position if EQ flag is set - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0F // Operation: Dst + Instruction Register -> Instruction Register IF Flags.EQ void JER(); // [Branch] 0x03E — JNR: Jumps to relative position if EQ flag is cleared - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0F // Operation: Dst + Instruction Register -> Instruction Register IF NOT Flags.EQ void JNR(); // [Branch] 0x03F — JIR: Jumps to relative position if value provided is booleanly true - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: Dst + Instruction Register -> Instruction Register IF Src void JIR(); // [System] 0x040 — SFB: Store (User) Flag Bit - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: void SFB(); // [System] 0x041 — LFB: Load (User) Flag Bit - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: void LFB(); // [Branch] 0x042 — JUF: Jump to absolute position, if user flag is true - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: void JUF(); // [Branch] 0x043 — JUR: Jump to relative position, if user flag is true - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0F // Operation: void JUR(); // [Memory] 0x044 — PUSH: Push to stack - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0F // Operation: Dst -> pushed into stack void PUSH(); // [Memory] 0x045 — POP: Pop from stack - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0F // Operation: popped from stack -> Dst void POP(); // [Memory] 0x046 — ALLOC: Allocate to heap - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0F // Operation: Dst -> heap ptr of size Dst void ALLOC(); // [Memory] 0x047 — HFREE: Delete from heap - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0F // Operation: Frees heap ptr in Dst void HFREE(); // [Branch] 0x04A — CALL: Call function at instruction index - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0F // Operation: Performs a function call, step XX void CALL(); @@ -564,207 +564,207 @@ namespace spider { void RET(); // [System] 0x04C — EDI: Enable/Disable External Interrupts - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0F // Operation: bool( Dst ) -> Enable External Interrupts Bit void EDI(); // [System] 0x04D — SHSS: Set Hotswap Signal Bit - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 0F + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0F // Operation: bool( Dst ) -> Hot Swap Signal Bit void SHSS(); // [Floating Point] 0x050 — FLI: Float Load Immediate - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0C // Operation: void FLI(); // [Floating Point] 0x051 — FNEG: Float negate - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0C // Operation: - Dst -> Dst void FNEG(); // [Floating Point] 0x052 — FADD: Float add - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0C // Operation: Dst + Src -> Dst void FADD(); // [Floating Point] 0x053 — FSUB: Float subtract - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0C // Operation: Dst - Src-> Dst void FSUB(); // [Floating Point] 0x054 — FMUL: Float multiplication - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0C // Operation: Dst * Src -> Dst void FMUL(); // [Floating Point] 0x055 — FDIV: Float division - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0C // Operation: Dst / Src -> Dst void FDIV(); // [Floating Point] 0x056 — FMOD: Float modulus - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0C // Operation: Dst % Src -> Dst void FMOD(); // [Floating Point] 0x057 — FDMOD: Float division and modulus - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0C // Operation: Dst / Src -> X, Dst % Src -> Y void FDMOD(); // [Floating Point] 0x058 — FEPS: Sets the float epsilon value, for comparison - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0C // Operation: Dst -> Epsilon Register void FEPS(); // [Floating Point] 0x059 — FEEP: Float Enable/Disable Epsilon - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0C // Operation: bool( Dst ) -> Epsilon Enable Bit void FEEP(); // [Boolean] 0x05A — FEQ: Float Equal - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0C // Operation: Dst == Src into Dst void FEQ(); // [Boolean] 0x05B — FNE: Float Not Equal - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0C // Operation: Dst != Src into Dst void FNE(); // [Boolean] 0x05C — FGT: Float Greater Than - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0C // Operation: Dst > Src into Dst void FGT(); // [Boolean] 0x05D — FGE: Float Greater or Equal Than - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0C // Operation: Dst >= Src into Dst void FGE(); // [Boolean] 0x05E — FLT: Float Lower Than - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0C // Operation: Dst < Src into Dst void FLT(); // [Boolean] 0x05F — FLE: Float Lower or Equal Than - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0C // Operation: Dst <= Src into Dst void FLE(); // [Casts] 0x060 — F2D: F32 (Float) to F64 (Double) - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 00 // Operation: (cast) Dst -> Dst void F2D(); // [Casts] 0x061 — D2F: F64 (Double) to F32 (Float) - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 00 // Operation: (cast) Dst -> Dst void D2F(); // [Casts] 0x062 — I2F: I32 (Integer) to F32 (Float) - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 00 // Operation: (cast) Dst -> Dst void I2F(); // [Casts] 0x063 — I2D: I32 (Integer) to F64 (Double) - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 00 // Operation: (cast) Dst -> Dst void I2D(); // [Casts] 0x064 — L2F: I64 (Long) to F32 (Float) - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 00 // Operation: (cast) Dst -> Dst void L2F(); // [Casts] 0x065 — L2D: I64 (Long) to F64 (Double) - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 00 // Operation: (cast) Dst -> Dst void L2D(); // [Casts] 0x066 — F2I: F32 (Float) to I32 (Integer) - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 00 // Operation: (cast) Dst -> Dst void F2I(); // [Casts] 0x067 — F2L: F32 (Float) to I64 (Long) - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 00 // Operation: (cast) Dst -> Dst void F2L(); // [Casts] 0x068 — D2I: F64 (Double) to I32 (Integer) - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 00 // Operation: (cast) Dst -> Dst void D2I(); // [Casts] 0x069 — D2L: F64 (Double) to I64 (Long) - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 00 // Operation: (cast) Dst -> Dst void D2L(); // [Trigonometric] 0x06C — SIN: Sine Function - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0C // Operation: sin( Dst ) -> Dst void SIN(); // [Trigonometric] 0x06D — COS: Cosine Function - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0C // Operation: cos( Dst ) -> Dst void COS(); // [Trigonometric] 0x06E — TAN: Tangent Function - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0C // Operation: tan( Dst ) -> Dst void TAN(); // [Trigonometric] 0x06F — ASIN: Arc Sine Function - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0C // Operation: asin( Dst ) -> Dst void ASIN(); // [Trigonometric] 0x070 — ACOS: Arc Cosine Function - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0C // Operation: acos( Dst ) -> Dst void ACOS(); // [Trigonometric] 0x071 — ATAN: Arc Tangent Function - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0C // Operation: atan( Dst ) -> Dst void ATAN(); // [Trigonometric] 0x072 — ATAN2: Arc Tangent Function with 2 Arguments - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0C // Operation: atan( Dst, Src ) -> Dst void ATAN2(); // [Exponential] 0x074 — EXP: Exponential Function - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0C // Operation: exp( Dst ) -> Dst void EXP(); // [Exponential] 0x075 — LOG: Natural Logarithm - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0C // Operation: ln( Dst ) -> Dst void LOG(); // [Exponential] 0x076 — LOGAB: Logarithm A of B - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0C // Operation: log( Dst, Src ) -> Dst void LOGAB(); // [Exponential] 0x077 — POW: Power Function - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0C // Operation: pow( Dst, Src ) -> Dst void POW(); // [Exponential] 0x078 — SQRT: Square Root - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 1 | AddrMask1: FF AddrMask2: 00 | TypeMask: 0C // Operation: sqrt( Dst ) -> Dst void SQRT(); // [Exponential] 0x079 — ROOT: General Root - // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Params: 2 | AddrMask1: 1E AddrMask2: FF | TypeMask: 0C // Operation: pow( Dst, 1 / Src ) -> Dst void ROOT(); @@ -818,6 +818,16 @@ namespace spider { // Operation: void MDET(); + // [Quaternion] 0x086 — QMKA: Quaternion Make from Angles + // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Operation: + void QMKA(); + + // [Quaternion] 0x087 — QMUL: Quaternion Multiply + // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 + // Operation: + void QMUL(); + // [SIMD] 0x08A — XADD: SIMD Addition // Params: 0 | AddrMask1: 00 AddrMask2: 00 | TypeMask: 00 // Operation: diff --git a/src/spider/runtime/cpu/InstrMap.cpp b/src/spider/runtime/cpu/InstrMap.cpp new file mode 100644 index 0000000..ce2ce93 --- /dev/null +++ b/src/spider/runtime/cpu/InstrMap.cpp @@ -0,0 +1,748 @@ +/** + * @file InstrMap.cpp + * @brief Spider VM instruction dispatch — array and switch implementations. + * + * AUTO-GENERATED by pygen.ipynb — DO NOT EDIT BY HAND. + * + * This file provides two equivalent dispatch mechanisms: + * + * 1. InstrMap[] — A lookup table of member-function pointers indexed by + * opcode. O(1) dispatch; suitable for platforms where + * indirect calls through function pointers are efficient. + * + * 2. CPU::execute(u16) — A switch/case over every opcode. Lets the + * compiler emit a jump table or branch tree; may be + * preferable on microcontrollers or when link-time + * optimisation can inline the handlers. + * + */ + +#include "CPU.hpp" + +namespace spider { + +// ============================================================= +// Version 1 — Lookup table of member-function pointers +// ============================================================= + +/** Pointer-to-member type for a zero-argument CPU instruction. */ +using CPUInstr = void (CPU::*)(); + +/** + * Instruction dispatch table (512 entries, 9-bit opcode space). + * + * Usage: + * u16 opcode = fetch(); + * CPUInstr fn = InstrMap[opcode]; + * if (fn) (cpu.*fn)(); + */ +CPUInstr InstrMap[512] = { + &CPU::NOP, // 0x000 — No Operation + &CPU::SPDR, // 0x001 — Will place the Spider version of the interpreter in RA + &CPU::MMODE, // 0x002 — Set Memory Mode + &CPU::INT, // 0x003 — Interrupt + &CPU::LRV, // 0x004 — Load Interrupt Vector Register + &CPU::FSR, // 0x005 — Fetch System Register + &CPU::FIR, // 0x006 — Fetch Instruction Register + &CPU::FZR, // 0x007 — Fetch Stack Base Register + &CPU::LSR, // 0x008 — Load System Register + &CPU::FVR, // 0x009 — Fetch Interrupt Vector Register + &CPU::MOV, // 0x00A — Moves values + &CPU::MOR, // 0x00B — Moves registers + &CPU::AMOV, // 0x00C — Array Move, uses X and Y as ptrs, A as amount + &CPU::SWP, // 0x00D — Swap registers + &CPU::AHM, // 0x00E — Ask Host for Memory + nullptr, // 0x00F (reserved) + &CPU::COM, // 0x010 — One's complement + &CPU::NEG, // 0x011 — Two's complement + &CPU::EXS, // 0x012 — Extend Sign + &CPU::INC, // 0x013 — Increment + &CPU::DEC, // 0x014 — Decrement + &CPU::ADD, // 0x015 — Addition + &CPU::SUB, // 0x016 — Subtraction + &CPU::MUL, // 0x017 — Multiplication + &CPU::UMUL, // 0x018 — Unsigned Multiplication + &CPU::DIV, // 0x019 — Division + &CPU::UDIV, // 0x01A — Unsigned Division + &CPU::MOD, // 0x01B — Modulus + &CPU::UMOD, // 0x01C — Unsigned Modulus + &CPU::DMOD, // 0x01D — Division and Modulus + &CPU::UDMD, // 0x01E — Unsigned Division and Modulus + &CPU::FBT, // 0x01F — Test and update Flag Register (Integer) Bits + &CPU::STB, // 0x020 — Set Bit + &CPU::CRB, // 0x021 — Clear Bit + &CPU::TSB, // 0x022 — Test Bit + &CPU::BOOL, // 0x023 — Sets the booleaness of a value + &CPU::NOT, // 0x024 — Sets the inverse booleaness of a value (! BOOL) + &CPU::AND, // 0x025 — Boolean AND operation + &CPU::OR, // 0x026 — Boolean OR operation + &CPU::XOR, // 0x027 — Boolean XOR operation + &CPU::SHL, // 0x028 — Arithmetic Shift Left + &CPU::SHR, // 0x029 — Arithmetic Shift Right + &CPU::SSR, // 0x02A — Signed Shift Right + &CPU::ROL, // 0x02B — Rotate Left + &CPU::ROR, // 0x02C — Rotate Right + &CPU::CNT, // 0x02D — Counts bits + nullptr, // 0x02E (reserved) + nullptr, // 0x02F (reserved) + &CPU::EQ, // 0x030 — Equal + &CPU::NE, // 0x031 — Not Equal + &CPU::GT, // 0x032 — Greater Than + &CPU::GE, // 0x033 — Greater or Equal Than + &CPU::LT, // 0x034 — Lower Than + &CPU::LE, // 0x035 — Lower or Equal Than + nullptr, // 0x036 (reserved) + nullptr, // 0x037 (reserved) + &CPU::JMP, // 0x038 — Jump to absolute position + &CPU::JEQ, // 0x039 — Jumps to position if EQ flag is set + &CPU::JNE, // 0x03A — Jumps to position if EQ flag is cleared + &CPU::JIF, // 0x03B — Jumps if value provided is booleanly true + &CPU::JMR, // 0x03C — Jump Relative + &CPU::JER, // 0x03D — Jumps to relative position if EQ flag is set + &CPU::JNR, // 0x03E — Jumps to relative position if EQ flag is cleared + &CPU::JIR, // 0x03F — Jumps to relative position if value provided is booleanly true + &CPU::SFB, // 0x040 — Store (User) Flag Bit + &CPU::LFB, // 0x041 — Load (User) Flag Bit + &CPU::JUF, // 0x042 — Jump to absolute position, if user flag is true + &CPU::JUR, // 0x043 — Jump to relative position, if user flag is true + &CPU::PUSH, // 0x044 — Push to stack + &CPU::POP, // 0x045 — Pop from stack + &CPU::ALLOC, // 0x046 — Allocate to heap + &CPU::HFREE, // 0x047 — Delete from heap + nullptr, // 0x048 (reserved) + nullptr, // 0x049 (reserved) + &CPU::CALL, // 0x04A — Call function at instruction index + &CPU::RET, // 0x04B — Return from a function + &CPU::EDI, // 0x04C — Enable/Disable External Interrupts + &CPU::SHSS, // 0x04D — Set Hotswap Signal Bit + nullptr, // 0x04E (reserved) + nullptr, // 0x04F (reserved) + &CPU::FLI, // 0x050 — Float Load Immediate + &CPU::FNEG, // 0x051 — Float negate + &CPU::FADD, // 0x052 — Float add + &CPU::FSUB, // 0x053 — Float subtract + &CPU::FMUL, // 0x054 — Float multiplication + &CPU::FDIV, // 0x055 — Float division + &CPU::FMOD, // 0x056 — Float modulus + &CPU::FDMOD, // 0x057 — Float division and modulus + &CPU::FEPS, // 0x058 — Sets the float epsilon value, for comparison + &CPU::FEEP, // 0x059 — Float Enable/Disable Epsilon + &CPU::FEQ, // 0x05A — Float Equal + &CPU::FNE, // 0x05B — Float Not Equal + &CPU::FGT, // 0x05C — Float Greater Than + &CPU::FGE, // 0x05D — Float Greater or Equal Than + &CPU::FLT, // 0x05E — Float Lower Than + &CPU::FLE, // 0x05F — Float Lower or Equal Than + &CPU::F2D, // 0x060 — F32 (Float) to F64 (Double) + &CPU::D2F, // 0x061 — F64 (Double) to F32 (Float) + &CPU::I2F, // 0x062 — I32 (Integer) to F32 (Float) + &CPU::I2D, // 0x063 — I32 (Integer) to F64 (Double) + &CPU::L2F, // 0x064 — I64 (Long) to F32 (Float) + &CPU::L2D, // 0x065 — I64 (Long) to F64 (Double) + &CPU::F2I, // 0x066 — F32 (Float) to I32 (Integer) + &CPU::F2L, // 0x067 — F32 (Float) to I64 (Long) + &CPU::D2I, // 0x068 — F64 (Double) to I32 (Integer) + &CPU::D2L, // 0x069 — F64 (Double) to I64 (Long) + nullptr, // 0x06A (reserved) + nullptr, // 0x06B (reserved) + &CPU::SIN, // 0x06C — Sine Function + &CPU::COS, // 0x06D — Cosine Function + &CPU::TAN, // 0x06E — Tangent Function + &CPU::ASIN, // 0x06F — Arc Sine Function + &CPU::ACOS, // 0x070 — Arc Cosine Function + &CPU::ATAN, // 0x071 — Arc Tangent Function + &CPU::ATAN2, // 0x072 — Arc Tangent Function with 2 Arguments + nullptr, // 0x073 (reserved) + &CPU::EXP, // 0x074 — Exponential Function + &CPU::LOG, // 0x075 — Natural Logarithm + &CPU::LOGAB, // 0x076 — Logarithm A of B + &CPU::POW, // 0x077 — Power Function + &CPU::SQRT, // 0x078 — Square Root + &CPU::ROOT, // 0x079 — General Root + nullptr, // 0x07A (reserved) + nullptr, // 0x07B (reserved) + &CPU::ADC, // 0x07C — Add with Carry + &CPU::SWC, // 0x07D — Subtract with Carry (Borrow) + &CPU::MWO, // 0x07E — Multiply with Overflow + &CPU::UMO, // 0x07F — Unsigned Multiply with Overflow + &CPU::MADD, // 0x080 — Matrix Addition + &CPU::MSUB, // 0x081 — Matrix Subtraction + &CPU::MMUL, // 0x082 — Matrix Multiply + &CPU::MINV, // 0x083 — Matrix Inverse + &CPU::MTRA, // 0x084 — Matrix Transpose + &CPU::MDET, // 0x085 — Matrix Determinant + &CPU::QMKA, // 0x086 — Quaternion Make from Angles + &CPU::QMUL, // 0x087 — Quaternion Multiply + nullptr, // 0x088 + nullptr, // 0x089 + &CPU::XADD, // 0x08A — SIMD Addition + &CPU::XSUB, // 0x08B — SIMD Subtract + &CPU::XAMA, // 0x08C — SIMD Alternate Multiply-Add + &CPU::XMUL, // 0x08D — SIMD Multiply + &CPU::XDIV, // 0x08E — SIMD Divide + nullptr, // 0x08F + nullptr, // 0x090 + nullptr, // 0x091 + nullptr, // 0x092 + nullptr, // 0x093 + nullptr, // 0x094 + nullptr, // 0x095 + nullptr, // 0x096 + nullptr, // 0x097 + nullptr, // 0x098 + nullptr, // 0x099 + nullptr, // 0x09A + nullptr, // 0x09B + nullptr, // 0x09C + nullptr, // 0x09D + nullptr, // 0x09E + nullptr, // 0x09F + nullptr, // 0x0A0 + nullptr, // 0x0A1 + nullptr, // 0x0A2 + nullptr, // 0x0A3 + nullptr, // 0x0A4 + nullptr, // 0x0A5 + nullptr, // 0x0A6 + nullptr, // 0x0A7 + nullptr, // 0x0A8 + nullptr, // 0x0A9 + nullptr, // 0x0AA + nullptr, // 0x0AB + nullptr, // 0x0AC + nullptr, // 0x0AD + nullptr, // 0x0AE + nullptr, // 0x0AF + nullptr, // 0x0B0 + nullptr, // 0x0B1 + nullptr, // 0x0B2 + nullptr, // 0x0B3 + nullptr, // 0x0B4 + nullptr, // 0x0B5 + nullptr, // 0x0B6 + nullptr, // 0x0B7 + nullptr, // 0x0B8 + nullptr, // 0x0B9 + nullptr, // 0x0BA + nullptr, // 0x0BB + nullptr, // 0x0BC + nullptr, // 0x0BD + nullptr, // 0x0BE + nullptr, // 0x0BF + nullptr, // 0x0C0 + nullptr, // 0x0C1 + nullptr, // 0x0C2 + nullptr, // 0x0C3 + nullptr, // 0x0C4 + nullptr, // 0x0C5 + nullptr, // 0x0C6 + nullptr, // 0x0C7 + nullptr, // 0x0C8 + nullptr, // 0x0C9 + nullptr, // 0x0CA + nullptr, // 0x0CB + nullptr, // 0x0CC + nullptr, // 0x0CD + nullptr, // 0x0CE + nullptr, // 0x0CF + nullptr, // 0x0D0 + nullptr, // 0x0D1 + nullptr, // 0x0D2 + nullptr, // 0x0D3 + nullptr, // 0x0D4 + nullptr, // 0x0D5 + nullptr, // 0x0D6 + nullptr, // 0x0D7 + nullptr, // 0x0D8 + nullptr, // 0x0D9 + nullptr, // 0x0DA + nullptr, // 0x0DB + nullptr, // 0x0DC + nullptr, // 0x0DD + nullptr, // 0x0DE + nullptr, // 0x0DF + nullptr, // 0x0E0 + nullptr, // 0x0E1 + nullptr, // 0x0E2 + nullptr, // 0x0E3 + nullptr, // 0x0E4 + nullptr, // 0x0E5 + nullptr, // 0x0E6 + nullptr, // 0x0E7 + nullptr, // 0x0E8 + nullptr, // 0x0E9 + nullptr, // 0x0EA + nullptr, // 0x0EB + nullptr, // 0x0EC + nullptr, // 0x0ED + nullptr, // 0x0EE + nullptr, // 0x0EF + &CPU::UPY, // 0x0F0 — Will place "YUPI" in memory + nullptr, // 0x0F1 + nullptr, // 0x0F2 + nullptr, // 0x0F3 + nullptr, // 0x0F4 + nullptr, // 0x0F5 + nullptr, // 0x0F6 + nullptr, // 0x0F7 + nullptr, // 0x0F8 + nullptr, // 0x0F9 + nullptr, // 0x0FA + nullptr, // 0x0FB + nullptr, // 0x0FC + nullptr, // 0x0FD + nullptr, // 0x0FE + nullptr, // 0x0FF + nullptr, // 0x100 + nullptr, // 0x101 + nullptr, // 0x102 + nullptr, // 0x103 + nullptr, // 0x104 + nullptr, // 0x105 + nullptr, // 0x106 + nullptr, // 0x107 + nullptr, // 0x108 + nullptr, // 0x109 + nullptr, // 0x10A + nullptr, // 0x10B + nullptr, // 0x10C + nullptr, // 0x10D + nullptr, // 0x10E + nullptr, // 0x10F + nullptr, // 0x110 + nullptr, // 0x111 + nullptr, // 0x112 + nullptr, // 0x113 + nullptr, // 0x114 + nullptr, // 0x115 + nullptr, // 0x116 + nullptr, // 0x117 + nullptr, // 0x118 + nullptr, // 0x119 + nullptr, // 0x11A + nullptr, // 0x11B + nullptr, // 0x11C + nullptr, // 0x11D + nullptr, // 0x11E + nullptr, // 0x11F + nullptr, // 0x120 + nullptr, // 0x121 + nullptr, // 0x122 + nullptr, // 0x123 + nullptr, // 0x124 + nullptr, // 0x125 + nullptr, // 0x126 + nullptr, // 0x127 + nullptr, // 0x128 + nullptr, // 0x129 + nullptr, // 0x12A + nullptr, // 0x12B + nullptr, // 0x12C + nullptr, // 0x12D + nullptr, // 0x12E + nullptr, // 0x12F + nullptr, // 0x130 + nullptr, // 0x131 + nullptr, // 0x132 + nullptr, // 0x133 + nullptr, // 0x134 + nullptr, // 0x135 + nullptr, // 0x136 + nullptr, // 0x137 + nullptr, // 0x138 + nullptr, // 0x139 + nullptr, // 0x13A + nullptr, // 0x13B + nullptr, // 0x13C + nullptr, // 0x13D + nullptr, // 0x13E + nullptr, // 0x13F + nullptr, // 0x140 + nullptr, // 0x141 + nullptr, // 0x142 + nullptr, // 0x143 + nullptr, // 0x144 + nullptr, // 0x145 + nullptr, // 0x146 + nullptr, // 0x147 + nullptr, // 0x148 + nullptr, // 0x149 + nullptr, // 0x14A + nullptr, // 0x14B + nullptr, // 0x14C + nullptr, // 0x14D + nullptr, // 0x14E + nullptr, // 0x14F + nullptr, // 0x150 + nullptr, // 0x151 + nullptr, // 0x152 + nullptr, // 0x153 + nullptr, // 0x154 + nullptr, // 0x155 + nullptr, // 0x156 + nullptr, // 0x157 + nullptr, // 0x158 + nullptr, // 0x159 + nullptr, // 0x15A + nullptr, // 0x15B + nullptr, // 0x15C + nullptr, // 0x15D + nullptr, // 0x15E + nullptr, // 0x15F + nullptr, // 0x160 + nullptr, // 0x161 + nullptr, // 0x162 + nullptr, // 0x163 + nullptr, // 0x164 + nullptr, // 0x165 + nullptr, // 0x166 + nullptr, // 0x167 + nullptr, // 0x168 + nullptr, // 0x169 + nullptr, // 0x16A + nullptr, // 0x16B + nullptr, // 0x16C + nullptr, // 0x16D + nullptr, // 0x16E + nullptr, // 0x16F + nullptr, // 0x170 + nullptr, // 0x171 + nullptr, // 0x172 + nullptr, // 0x173 + nullptr, // 0x174 + nullptr, // 0x175 + nullptr, // 0x176 + nullptr, // 0x177 + nullptr, // 0x178 + nullptr, // 0x179 + nullptr, // 0x17A + nullptr, // 0x17B + nullptr, // 0x17C + nullptr, // 0x17D + nullptr, // 0x17E + nullptr, // 0x17F + nullptr, // 0x180 + nullptr, // 0x181 + nullptr, // 0x182 + nullptr, // 0x183 + nullptr, // 0x184 + nullptr, // 0x185 + nullptr, // 0x186 + nullptr, // 0x187 + nullptr, // 0x188 + nullptr, // 0x189 + nullptr, // 0x18A + nullptr, // 0x18B + nullptr, // 0x18C + nullptr, // 0x18D + nullptr, // 0x18E + nullptr, // 0x18F + nullptr, // 0x190 + nullptr, // 0x191 + nullptr, // 0x192 + nullptr, // 0x193 + nullptr, // 0x194 + nullptr, // 0x195 + nullptr, // 0x196 + nullptr, // 0x197 + nullptr, // 0x198 + nullptr, // 0x199 + nullptr, // 0x19A + nullptr, // 0x19B + nullptr, // 0x19C + nullptr, // 0x19D + nullptr, // 0x19E + nullptr, // 0x19F + nullptr, // 0x1A0 + nullptr, // 0x1A1 + nullptr, // 0x1A2 + nullptr, // 0x1A3 + nullptr, // 0x1A4 + nullptr, // 0x1A5 + nullptr, // 0x1A6 + nullptr, // 0x1A7 + nullptr, // 0x1A8 + nullptr, // 0x1A9 + nullptr, // 0x1AA + nullptr, // 0x1AB + nullptr, // 0x1AC + nullptr, // 0x1AD + nullptr, // 0x1AE + nullptr, // 0x1AF + nullptr, // 0x1B0 + nullptr, // 0x1B1 + nullptr, // 0x1B2 + nullptr, // 0x1B3 + nullptr, // 0x1B4 + nullptr, // 0x1B5 + nullptr, // 0x1B6 + nullptr, // 0x1B7 + nullptr, // 0x1B8 + nullptr, // 0x1B9 + nullptr, // 0x1BA + nullptr, // 0x1BB + nullptr, // 0x1BC + nullptr, // 0x1BD + nullptr, // 0x1BE + nullptr, // 0x1BF + nullptr, // 0x1C0 + nullptr, // 0x1C1 + nullptr, // 0x1C2 + nullptr, // 0x1C3 + nullptr, // 0x1C4 + nullptr, // 0x1C5 + nullptr, // 0x1C6 + nullptr, // 0x1C7 + nullptr, // 0x1C8 + nullptr, // 0x1C9 + nullptr, // 0x1CA + nullptr, // 0x1CB + nullptr, // 0x1CC + nullptr, // 0x1CD + nullptr, // 0x1CE + nullptr, // 0x1CF + nullptr, // 0x1D0 + nullptr, // 0x1D1 + nullptr, // 0x1D2 + nullptr, // 0x1D3 + nullptr, // 0x1D4 + nullptr, // 0x1D5 + nullptr, // 0x1D6 + nullptr, // 0x1D7 + nullptr, // 0x1D8 + nullptr, // 0x1D9 + nullptr, // 0x1DA + nullptr, // 0x1DB + nullptr, // 0x1DC + nullptr, // 0x1DD + nullptr, // 0x1DE + nullptr, // 0x1DF + nullptr, // 0x1E0 + nullptr, // 0x1E1 + nullptr, // 0x1E2 + nullptr, // 0x1E3 + nullptr, // 0x1E4 + nullptr, // 0x1E5 + nullptr, // 0x1E6 + nullptr, // 0x1E7 + nullptr, // 0x1E8 + nullptr, // 0x1E9 + nullptr, // 0x1EA + nullptr, // 0x1EB + nullptr, // 0x1EC + nullptr, // 0x1ED + nullptr, // 0x1EE + nullptr, // 0x1EF + nullptr, // 0x1F0 + nullptr, // 0x1F1 + nullptr, // 0x1F2 + nullptr, // 0x1F3 + nullptr, // 0x1F4 + nullptr, // 0x1F5 + nullptr, // 0x1F6 + nullptr, // 0x1F7 + nullptr, // 0x1F8 + nullptr, // 0x1F9 + nullptr, // 0x1FA + nullptr, // 0x1FB + nullptr, // 0x1FC + nullptr, // 0x1FD + nullptr, // 0x1FE + nullptr, // 0x1FF +}; + + +// ============================================================= +// Version 2 — Switch dispatch +// ============================================================= + +/** + * Execute the instruction identified by @p opcode. + * + * This is functionally equivalent to the InstrMap[] table above + * but expressed as a switch so the compiler can choose the best + * lowering strategy (jump table, binary search, etc.). + * + * @param opcode 9-bit instruction opcode (0x000 – 0x1FF). + */ +void CPU::execute(u16 opcode) { + switch (opcode) { + + // ── System ────────────────────────────────────── + case 0x000: NOP(); break; + case 0x001: SPDR(); break; + case 0x002: MMODE(); break; + case 0x003: INT(); break; + case 0x004: LRV(); break; + case 0x005: FSR(); break; + case 0x006: FIR(); break; + case 0x007: FZR(); break; + case 0x008: LSR(); break; + case 0x009: FVR(); break; + + // ── Memory ────────────────────────────────────── + case 0x00A: MOV(); break; + case 0x00B: MOR(); break; + case 0x00C: AMOV(); break; + case 0x00D: SWP(); break; + case 0x00E: AHM(); break; + + // ── Integer ───────────────────────────────────── + case 0x010: COM(); break; + case 0x011: NEG(); break; + case 0x012: EXS(); break; + case 0x013: INC(); break; + case 0x014: DEC(); break; + case 0x015: ADD(); break; + case 0x016: SUB(); break; + case 0x017: MUL(); break; + case 0x018: UMUL(); break; + case 0x019: DIV(); break; + case 0x01A: UDIV(); break; + case 0x01B: MOD(); break; + case 0x01C: UMOD(); break; + case 0x01D: DMOD(); break; + case 0x01E: UDMD(); break; + + // ── System ────────────────────────────────────── + case 0x01F: FBT(); break; + + // ── Bit Wise ──────────────────────────────────── + case 0x020: STB(); break; + case 0x021: CRB(); break; + case 0x022: TSB(); break; + case 0x023: BOOL(); break; + case 0x024: NOT(); break; + case 0x025: AND(); break; + case 0x026: OR(); break; + case 0x027: XOR(); break; + case 0x028: SHL(); break; + case 0x029: SHR(); break; + case 0x02A: SSR(); break; + case 0x02B: ROL(); break; + case 0x02C: ROR(); break; + case 0x02D: CNT(); break; + + // ── Boolean ───────────────────────────────────── + case 0x030: EQ(); break; + case 0x031: NE(); break; + case 0x032: GT(); break; + case 0x033: GE(); break; + case 0x034: LT(); break; + case 0x035: LE(); break; + + // ── Branch ────────────────────────────────────── + case 0x038: JMP(); break; + case 0x039: JEQ(); break; + case 0x03A: JNE(); break; + case 0x03B: JIF(); break; + case 0x03C: JMR(); break; + case 0x03D: JER(); break; + case 0x03E: JNR(); break; + case 0x03F: JIR(); break; + + // ── System ────────────────────────────────────── + case 0x040: SFB(); break; + case 0x041: LFB(); break; + + // ── Branch ────────────────────────────────────── + case 0x042: JUF(); break; + case 0x043: JUR(); break; + + // ── Memory ────────────────────────────────────── + case 0x044: PUSH(); break; + case 0x045: POP(); break; + case 0x046: ALLOC(); break; + case 0x047: HFREE(); break; + + // ── Branch ────────────────────────────────────── + case 0x04A: CALL(); break; + case 0x04B: RET(); break; + + // ── System ────────────────────────────────────── + case 0x04C: EDI(); break; + case 0x04D: SHSS(); break; + + // ── Floating Point ────────────────────────────── + case 0x050: FLI(); break; + case 0x051: FNEG(); break; + case 0x052: FADD(); break; + case 0x053: FSUB(); break; + case 0x054: FMUL(); break; + case 0x055: FDIV(); break; + case 0x056: FMOD(); break; + case 0x057: FDMOD(); break; + case 0x058: FEPS(); break; + case 0x059: FEEP(); break; + + // ── Boolean ───────────────────────────────────── + case 0x05A: FEQ(); break; + case 0x05B: FNE(); break; + case 0x05C: FGT(); break; + case 0x05D: FGE(); break; + case 0x05E: FLT(); break; + case 0x05F: FLE(); break; + + // ── Casts ─────────────────────────────────────── + case 0x060: F2D(); break; + case 0x061: D2F(); break; + case 0x062: I2F(); break; + case 0x063: I2D(); break; + case 0x064: L2F(); break; + case 0x065: L2D(); break; + case 0x066: F2I(); break; + case 0x067: F2L(); break; + case 0x068: D2I(); break; + case 0x069: D2L(); break; + + // ── Trigonometric ─────────────────────────────── + case 0x06C: SIN(); break; + case 0x06D: COS(); break; + case 0x06E: TAN(); break; + case 0x06F: ASIN(); break; + case 0x070: ACOS(); break; + case 0x071: ATAN(); break; + case 0x072: ATAN2(); break; + + // ── Exponential ───────────────────────────────── + case 0x074: EXP(); break; + case 0x075: LOG(); break; + case 0x076: LOGAB(); break; + case 0x077: POW(); break; + case 0x078: SQRT(); break; + case 0x079: ROOT(); break; + + // ── Integer ───────────────────────────────────── + case 0x07C: ADC(); break; + case 0x07D: SWC(); break; + case 0x07E: MWO(); break; + case 0x07F: UMO(); break; + + // ── Matrix ────────────────────────────────────── + case 0x080: MADD(); break; + case 0x081: MSUB(); break; + case 0x082: MMUL(); break; + case 0x083: MINV(); break; + case 0x084: MTRA(); break; + case 0x085: MDET(); break; + + // ── Quaternion ────────────────────────────────── + case 0x086: QMKA(); break; + case 0x087: QMUL(); break; + + // ── SIMD ──────────────────────────────────────── + case 0x08A: XADD(); break; + case 0x08B: XSUB(); break; + case 0x08C: XAMA(); break; + case 0x08D: XMUL(); break; + case 0x08E: XDIV(); break; + + // ── Easter Eggs ───────────────────────────────── + case 0x0F0: UPY(); break; + + default: + break; + } +} + +} // namespace spider From c6c63d63914755a914bc293fc7fa082cdb1ab331 Mon Sep 17 00:00:00 2001 From: Kittycannon Date: Wed, 25 Mar 2026 10:56:26 -0600 Subject: [PATCH 10/10] changes to pygen --- pygen.ipynb | 44 ++++++++++--------- src/spider/runtime/cpu/CPU.cpp | 12 +++-- src/spider/runtime/cpu/CPU.hpp | 24 ++++++++-- .../runtime/{cpu => instr}/InstrMap.cpp | 14 +++--- 4 files changed, 58 insertions(+), 36 deletions(-) rename src/spider/runtime/{cpu => instr}/InstrMap.cpp (99%) diff --git a/pygen.ipynb b/pygen.ipynb index ac955c4..d3d354b 100644 --- a/pygen.ipynb +++ b/pygen.ipynb @@ -15,7 +15,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 1, "id": "b0fcd533", "metadata": {}, "outputs": [ @@ -68,7 +68,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 2, "id": "b33de8ac", "metadata": {}, "outputs": [ @@ -144,7 +144,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 3, "id": "58645013", "metadata": {}, "outputs": [ @@ -283,7 +283,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 4, "id": "452bc76c", "metadata": {}, "outputs": [ @@ -357,7 +357,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 5, "id": "5aaebef0", "metadata": {}, "outputs": [ @@ -380,7 +380,7 @@ "\n", "\n", "CPU.hpp updated successfully at: .//src//spider/runtime/cpu/CPU.hpp\n", - "Total lines in updated file: 792\n" + "Total lines in updated file: 883\n" ] } ], @@ -451,7 +451,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 8, "id": "instrmap_gen", "metadata": {}, "outputs": [ @@ -459,8 +459,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "InstrMap.cpp written to: .//src//spider/runtime/cpu/InstrMap.cpp\n", - " Size : 34,246 bytes\n", + "InstrMap.cpp written to: .//src//spider/runtime/instr/InstrMap.cpp\n", + " Size : 34,157 bytes\n", " Array entries : 512 (128 populated, 384 nullptr)\n", " Switch cases : 128\n", " Line endings : LF-only verified\n" @@ -516,8 +516,8 @@ "L.append(' * optimisation can inline the handlers.')\n", "L.append(' *')\n", "L.append(' */')\n", - "L.append('')\n", - "L.append('#include \"CPU.hpp\"')\n", + "L.append('') # [CHANGE] Use absolute path to make paths more explicit\n", + "L.append('#include ')\n", "L.append('')\n", "L.append('namespace spider {')\n", "L.append('')\n", @@ -527,18 +527,19 @@ "L.append('// Version 1 — Lookup table of member-function pointers')\n", "L.append('// =============================================================')\n", "L.append('')\n", - "L.append('/** Pointer-to-member type for a zero-argument CPU instruction. */')\n", - "L.append('using CPUInstr = void (CPU::*)();')\n", + "# [CHANGE] Use CPU::Fn Instead\n", + "#L.append('/** Pointer-to-member type for a zero-argument CPU instruction. */')\n", + "#L.append('using CPUInstr = void (CPU::*)();')\n", "L.append('')\n", "L.append('/**')\n", "L.append(f' * Instruction dispatch table ({TABLE_SIZE} entries, 9-bit opcode space).')\n", "L.append(' *')\n", "L.append(' * Usage:')\n", "L.append(' * u16 opcode = fetch();')\n", - "L.append(' * CPUInstr fn = InstrMap[opcode];')\n", + "L.append(' * CPU::Fn fn = InstrMap[opcode];')\n", "L.append(' * if (fn) (cpu.*fn)();')\n", - "L.append(' */')\n", - "L.append(f'CPUInstr InstrMap[{TABLE_SIZE}] = {{')\n", + "L.append(' */') # [CHANGE] Made it part of the CPU & avoided explicit size.\n", + "L.append(f'CPU::Fn CPU::instrMap[] = {{')\n", "\n", "for opc in range(TABLE_SIZE):\n", " mnem = opcode_to_mnem.get(opc)\n", @@ -567,10 +568,10 @@ "L.append(' * but expressed as a switch so the compiler can choose the best')\n", "L.append(' * lowering strategy (jump table, binary search, etc.).')\n", "L.append(' *')\n", - "L.append(' * @param opcode 9-bit instruction opcode (0x000 – 0x1FF).')\n", + "L.append(' * @param opcode 9-bit instruction opcode (0x000 - 0x1FF).')\n", "L.append(' */')\n", - "L.append('void CPU::execute(u16 opcode) {')\n", - "L.append(' switch (opcode) {')\n", + "L.append('void CPU::executeSwLk() {')\n", + "L.append(' switch (_opcode) {')\n", "\n", "last_group = None\n", "for opc in sorted(opcode_to_mnem.keys()):\n", @@ -594,7 +595,8 @@ "INSTRMAP_SRC = '\\n'.join(L)\n", "\n", "# ── Write to file ───────────────────────────────────────────────────────────\n", - "INSTRMAP_PATH = f'{SRC_ROOT}/spider/runtime/cpu/InstrMap.cpp'\n", + "# [CHANGE] Write this in the instructions folder to avoid CPU file bloat\n", + "INSTRMAP_PATH = f'{SRC_ROOT}/spider/runtime/instr/InstrMap.cpp'\n", "\n", "with open(INSTRMAP_PATH, 'wb') as f:\n", " f.write(INSTRMAP_SRC.encode('utf-8'))\n", @@ -631,7 +633,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.5" + "version": "3.13.7" } }, "nbformat": 4, diff --git a/src/spider/runtime/cpu/CPU.cpp b/src/spider/runtime/cpu/CPU.cpp index 17366ff..3308b8f 100644 --- a/src/spider/runtime/cpu/CPU.cpp +++ b/src/spider/runtime/cpu/CPU.cpp @@ -22,7 +22,7 @@ namespace spider { RE{}, RN{}, RV{}, RM{}, ALU0{}, ALU1{}, _dst(nullptr), _src(nullptr), - _instr(0), _addrm(0), _size(0), + _opcode(0), _addrm(0), _size(0), _store(0), _post(&CPU::imp), _ram(nullptr), _reel(nullptr) { } @@ -67,7 +67,7 @@ namespace spider { void CPU::fetchInstr() { u16 i = _reel->readU16(RI); - _instr = (i >> 7) & 0x1FF; + _opcode = (i >> 7) & 0x1FF; _addrm = (i >> 2) & 0x1F; _size = i & 0x3; RI += 2; @@ -79,7 +79,7 @@ namespace spider { _opers[1] = _opers[0]; // call specific addressing mode - (this->*(CPU::addrModes[_addrm & 0x7]))(); + (this->*(CPU::addrModes[_addrm]))(); } void CPU::fetchOperSrc() { @@ -87,13 +87,17 @@ namespace spider { _alu = &ALU1; // call specific addressing mode - (this->*(CPU::addrModes[_addrm & 0x7]))(); + (this->*(CPU::addrModes[_addrm]))(); // modify the _addrm register _addrm >>= 3; _addrm++; } + void CPU::execute() { + (this->*(CPU::addrModes[_opcode]))(); + } + // Addressing Modes // /** diff --git a/src/spider/runtime/cpu/CPU.hpp b/src/spider/runtime/cpu/CPU.hpp index 057c7ba..a2e8aa5 100644 --- a/src/spider/runtime/cpu/CPU.hpp +++ b/src/spider/runtime/cpu/CPU.hpp @@ -16,9 +16,10 @@ namespace spider { static constexpr const u64 FLAG_EXCEPTION = 0b0000000000000000000000000000000000000000000000000000000000001000; static constexpr const u64 FLAG_MEMORY_MODE = 0b0000000000000000000000000000000000000000000000000000000000110000; - public: // Map of addressing modes + public: // Map of addressing modes & Instructions static CPU::Fn addrModes[]; + static CPU::Fn instrMap[]; public: // General Purpose Registers union { @@ -61,7 +62,7 @@ namespace spider { }; // Holds the current instruction opcode - u16 _instr : 9; + u16 _opcode : 9; // Holds the current addressing modes, // before they were used @@ -163,6 +164,23 @@ namespace spider { */ void fetchOperSrc(); + /** + * Executes an opcode, by means of directly + * accessing the instruction map and + * calling that function pointer. + */ + void execute(); + + /** + * Executes an opcode, by means of using + * a large switch statement. Only suitable + * for environments where the instruction + * map is not possible. + * + * This has yet to be proved!!! + */ + void executeSwLk(); + public: // Addressing Modes /** @@ -862,4 +880,4 @@ namespace spider { }; -} \ No newline at end of file +} diff --git a/src/spider/runtime/cpu/InstrMap.cpp b/src/spider/runtime/instr/InstrMap.cpp similarity index 99% rename from src/spider/runtime/cpu/InstrMap.cpp rename to src/spider/runtime/instr/InstrMap.cpp index ce2ce93..6d3e5de 100644 --- a/src/spider/runtime/cpu/InstrMap.cpp +++ b/src/spider/runtime/instr/InstrMap.cpp @@ -17,7 +17,7 @@ * */ -#include "CPU.hpp" +#include namespace spider { @@ -25,18 +25,16 @@ namespace spider { // Version 1 — Lookup table of member-function pointers // ============================================================= -/** Pointer-to-member type for a zero-argument CPU instruction. */ -using CPUInstr = void (CPU::*)(); /** * Instruction dispatch table (512 entries, 9-bit opcode space). * * Usage: * u16 opcode = fetch(); - * CPUInstr fn = InstrMap[opcode]; + * CPU::Fn fn = InstrMap[opcode]; * if (fn) (cpu.*fn)(); */ -CPUInstr InstrMap[512] = { +CPU::Fn CPU::instrMap[] = { &CPU::NOP, // 0x000 — No Operation &CPU::SPDR, // 0x001 — Will place the Spider version of the interpreter in RA &CPU::MMODE, // 0x002 — Set Memory Mode @@ -563,10 +561,10 @@ CPUInstr InstrMap[512] = { * but expressed as a switch so the compiler can choose the best * lowering strategy (jump table, binary search, etc.). * - * @param opcode 9-bit instruction opcode (0x000 – 0x1FF). + * @param opcode 9-bit instruction opcode (0x000 - 0x1FF). */ -void CPU::execute(u16 opcode) { - switch (opcode) { +void CPU::executeSwLk() { + switch (_opcode) { // ── System ────────────────────────────────────── case 0x000: NOP(); break;