From e67afe9e7fd51e08806328c587bd77276a7708b1 Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Mon, 26 Aug 2024 16:48:35 +0200 Subject: [PATCH 01/20] Add basic checkpointing snapshotting policy A checkpoint is taken at primitives and every x instructions. When the VM is paused it also makes a checkpoint so the debugger knows where the other snapshots are relative to the current point in time and so it can also display what the current state of the vm is. TODO: Will need to be extended a bit when using multiverse debugging. Choicepoints should have checkpoints right after reading sensor values so you don't have to remember the sensor value. --- src/Debug/debugger.cpp | 44 ++++- src/Debug/debugger.h | 18 +- src/Interpreter/instructions.cpp | 273 +++++++++++++++++++++++++++++++ src/Interpreter/interpreter.cpp | 2 +- 4 files changed, 325 insertions(+), 12 deletions(-) diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index c2b2ce63..c54591b6 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -22,7 +22,7 @@ Debugger::Debugger(Channel *duplex) { this->channel = duplex; this->supervisor_mutex = new warduino::mutex(); this->supervisor_mutex->lock(); - this->asyncSnapshots = false; + this->snapshotPolicy = SnapshotPolicy::none; } // Public methods @@ -190,6 +190,11 @@ bool Debugger::checkDebugMessages(Module *m, RunningState *program_state) { exit(0); case interruptPAUSE: this->pauseRuntime(m); + // Make a checkpoint so the debugger knows the current state and + // knows how many instructions were executed since the last checkpoint. + if (snapshotPolicy == SnapshotPolicy::checkpointing) { + checkpoint(m); + } this->channel->write("PAUSE!\n"); free(interruptData); break; @@ -260,8 +265,8 @@ bool Debugger::checkDebugMessages(Module *m, RunningState *program_state) { free(interruptData); snapshot(m); break; - case interruptEnableSnapshots: - enableSnapshots(interruptData + 1); + case interruptSetSnapshotPolicy: + setSnapshotPolicy(interruptData + 1); free(interruptData); break; case interruptInspect: { @@ -921,15 +926,30 @@ void Debugger::inspect(Module *m, const uint16_t sizeStateArray, this->channel->write("}\n"); } -void Debugger::enableSnapshots(const uint8_t *interruptData) { - asyncSnapshots = *interruptData; +void Debugger::setSnapshotPolicy(const uint8_t *interruptData) { + snapshotPolicy = SnapshotPolicy{*interruptData}; } -void Debugger::sendAsyncSnapshots(Module *m) const { - if (asyncSnapshots) { +void Debugger::handleSnapshotPolicy(Module *m) { + if (snapshotPolicy == SnapshotPolicy::atEveryInstruction) { this->channel->write("SNAPSHOT "); snapshot(m); } + else if (snapshotPolicy == SnapshotPolicy::checkpointing) { + if (instructions_executed >= 10 || isPrimitiveBeingCalled(m)) { + checkpoint(m); + } + instructions_executed++; + } + else if (snapshotPolicy != SnapshotPolicy::none) { + this->channel->write("WARNING: Invalid snapshot policy."); + } +} + +void Debugger::checkpoint(Module *m) { + this->channel->write("CHECKPOINT, {\"instructions_executed\" = %d}, ", instructions_executed); + snapshot(m); + instructions_executed = 0; } void Debugger::freeState(Module *m, uint8_t *interruptData) { @@ -1517,3 +1537,13 @@ Debugger::~Debugger() { delete this->supervisor_mutex; delete this->supervisor; } +bool Debugger::isPrimitiveBeingCalled(Module *m) { + // TODO: Maybe primitives can also be called using the other call operators + uint8_t opcode = *m->pc_ptr; + if (opcode == 0x10) { // call opcode + uint8_t *pc_copy = m->pc_ptr + 1; + uint32_t fidx = read_LEB_32(&pc_copy); + return fidx < m->import_count; + } + return false; +} diff --git a/src/Debug/debugger.h b/src/Debug/debugger.h index c43a00af..f0f98c73 100644 --- a/src/Debug/debugger.h +++ b/src/Debug/debugger.h @@ -77,7 +77,7 @@ enum InterruptTypes { // Pull Debugging interruptSnapshot = 0x60, - interruptEnableSnapshots = 0x61, + interruptSetSnapshotPolicy = 0x61, interruptLoadSnapshot = 0x62, interruptMonitorProxies = 0x63, interruptProxyCall = 0x64, @@ -100,6 +100,12 @@ enum InterruptTypes { interruptStored = 0xa1, }; +enum class SnapshotPolicy : int { + none, // Don't automatically take snapshots. + atEveryInstruction, // Take a snapshot after every instruction. + checkpointing, // Take a snapshot every x instructions or at specific points were reversible primitives are used or sensor values are read. +}; + class Debugger { private: std::deque debugMessages = {}; @@ -119,7 +125,8 @@ class Debugger { bool connected_to_proxy = false; warduino::mutex *supervisor_mutex; - bool asyncSnapshots; + SnapshotPolicy snapshotPolicy; + uint32_t instructions_executed; std::unordered_map> overrides; @@ -196,6 +203,9 @@ class Debugger { bool operation(Module *m, operation op); + bool isPrimitiveBeingCalled(Module *m); + void checkpoint(Module *m); + public: // Public fields warduino::mutex messageQueueMutex; // mutual exclude debugMessages @@ -245,9 +255,9 @@ class Debugger { void snapshot(Module *m) const; - void enableSnapshots(const uint8_t *interruptData); + void setSnapshotPolicy(const uint8_t *interruptData); - void sendAsyncSnapshots(Module *m) const; + void handleSnapshotPolicy(Module *m); void proxify(); diff --git a/src/Interpreter/instructions.cpp b/src/Interpreter/instructions.cpp index 0e4ceb3c..88db6ca2 100644 --- a/src/Interpreter/instructions.cpp +++ b/src/Interpreter/instructions.cpp @@ -1277,3 +1277,276 @@ bool i_instr_callback([[maybe_unused]] Module *m, // TODO return true; } + +bool interpret(Module *m, bool waiting) { + uint8_t *block_ptr; + uint8_t opcode; + + // keep track of occurring errors + bool success = true; + + // set to true when finishes successfully + bool program_done = false; + + while ((!program_done && success) || waiting) { + if (m->warduino->program_state == WARDUINOstep) { + m->warduino->debugger->pauseRuntime(m); + } + + while (m->warduino->program_state != WARDUINOinit && + m->warduino->debugger->checkDebugMessages( + m, &m->warduino->program_state)) { + } + fflush(stdout); + // esp_task_wdt_reset(); + + // Resolve 1 callback event if queue is not empty and VM not paused, and + // no event currently resolving + CallbackHandler::resolve_event(); + + // Sleep interpret loop if 'paused' or 'waiting drone' + if (m->warduino->program_state == WARDUINOpause || + m->warduino->program_state == PROXYhalt) { + // wait until new debug messages arrive + if (m->warduino->program_state == WARDUINOpause) { + warduino::unique_lock lock( + m->warduino->debugger->messageQueueMutex); + m->warduino->debugger->messageQueueConditionVariable.wait( + lock, [m] { return m->warduino->debugger->freshMessages; }); + } + continue; + } + + // Program state is not paused + + // If BP and not the one we just unpaused + if (m->warduino->debugger->isBreakpoint(m->pc_ptr) && + m->warduino->debugger->skipBreakpoint != m->pc_ptr && + m->warduino->program_state != PROXYrun) { + m->warduino->debugger->pauseRuntime(m); + m->warduino->debugger->notifyBreakpoint(m, m->pc_ptr); + continue; + } + m->warduino->debugger->skipBreakpoint = nullptr; + + // Take snapshot before executing an instruction + m->warduino->debugger->handleSnapshotPolicy(m); + + opcode = *m->pc_ptr; + block_ptr = m->pc_ptr; + m->pc_ptr += 1; + + dbg_dump_stack(m); + dbg_trace(" PC: %p OPCODE: <%s> in %s\n", block_ptr, + opcode_repr(opcode), + m->pc_ptr > m->bytes && m->pc_ptr < m->bytes + m->byte_count + ? "module" + : "patch"); + + switch (opcode) { + // + // Control flow operators + // + case 0x00: // unreachable + sprintf(exception, "%s", "unreachable"); + success &= false; + case 0x01: // nop + continue; + case 0x02: // block + success &= i_instr_block(m, block_ptr); + continue; + case 0x03: // loop + success &= i_instr_loop(m, block_ptr); + continue; + case 0x04: // if + success &= i_instr_if(m, block_ptr); + continue; + case 0x05: // else + success &= i_instr_else(m); + continue; + case 0x0b: // end + success &= i_instr_end(m, &program_done); + continue; + case 0x0c: // br + success &= i_instr_br(m); + continue; + case 0x0d: // br_if + success &= i_instr_br_if(m); + continue; + case 0x0e: // br_table + success &= i_instr_br_table(m); + continue; + case 0x0f: // return + success &= i_instr_return(m); + continue; + + // + // Call operators + // + case 0x10: { // call + success &= i_instr_call(m); + continue; + } + case 0x11: { // call_indirect + success &= i_instr_call_indirect(m); + continue; + } + // + // Parametric operators + // + case 0x1a: // drop + success &= i_instr_drop(m); + continue; + case 0x1b: // select + success &= i_instr_select(m); + continue; + + // + // Variable access + // + case 0x20: // get_local + success &= i_instr_get_local(m); + continue; + case 0x21: // set_local + success &= i_instr_set_local(m); + continue; + case 0x22: // tee_local + success &= i_instr_tee_local(m); + continue; + case 0x23: // get_global + success &= i_instr_get_global(m); + continue; + case 0x24: // set_global + success &= i_instr_set_global(m); + continue; + + // + // Memory-related operators + // + case 0x3f: // current_memory + success &= i_instr_current_memory(m); + continue; + case 0x40: // grow_memory + success &= i_instr_grow_memory(m); + continue; + // Memory load operators + case 0x28 ... 0x35: + success &= i_instr_mem_load(m, opcode); + continue; + // Memory store operators + case 0x36 ... 0x3e: + success &= i_instr_mem_store(m, opcode); + continue; + + // + // Constants + // + case 0x41 ... 0x44: // i32.const + success &= i_instr_const(m, opcode); + continue; + + // + // Comparison operators + // + + // unary + case 0x45: // i32.eqz + case 0x50: // i64.eqz + success &= i_instr_unary_u32(m, opcode); + continue; + + // i32 binary + case 0x46 ... 0x4f: + success &= i_instr_math_u32(m, opcode); + continue; + case 0x51 ... 0x5a: + success &= i_instr_math_u64(m, opcode); + continue; + case 0x5b ... 0x60: + success &= i_instr_math_f32(m, opcode); + continue; + case 0x61 ... 0x66: + success &= i_instr_math_f64(m, opcode); + continue; + + // + // Numeric operators + // + + // unary i32 + case 0x67 ... 0x69: + success &= i_instr_unary_i32(m, opcode); + continue; + + // unary i64 + case 0x79 ... 0x7b: + success &= i_instr_unary_i64(m, opcode); + continue; + + case 0x8b ... 0x91: // unary f32 + case 0x99 ... 0x9f: // unary f64 + success &= i_instr_unary_floating(m, opcode); + continue; + + // i32 binary + case 0x6a ... 0x78: + success &= i_instr_binary_i32(m, opcode); + continue; + + // i64 binary + case 0x7c ... 0x8a: + success &= i_instr_binary_i64(m, opcode); + continue; + + // f32 binary + case 0x92 ... 0x98: + success &= i_instr_binary_f32(m, opcode); + continue; + + // f64 binary + case 0xa0 ... 0xa6: + success &= i_instr_binary_f64(m, opcode); + continue; + + // conversion operations + case 0xa7 ... 0xbb: + success &= i_instr_conversion(m, opcode); + continue; + + // callback operations + case 0xe0 ... 0xe3: + success &= i_instr_callback(m, opcode); + continue; + default: + sprintf(exception, "unrecognized opcode 0x%x", opcode); + if (m->options.return_exception) { + m->exception = strdup(exception); + } + return false; + } + } + + if (m->warduino->program_state == PROXYrun) { + dbg_info("Trap was thrown during proxy call.\n"); + RFC *rfc = m->warduino->debugger->topProxyCall(); + rfc->success = false; + rfc->exception = strdup(exception); + rfc->exception_size = strlen(exception); + m->warduino->debugger->sendProxyCallResult(m); + } + + // Resolve all unhandled callback events + while (CallbackHandler::resolving_event && CallbackHandler::resolve_event()) + ; + + dbg_trace("Interpretation ended %s with status %s\n", + program_done ? "expectedly" : "unexpectedly", + success ? "ok" : "error"); + if (!success && m->options.return_exception) { + m->exception = strdup(exception); + } else if (!success) { + FATAL("%s\n", exception); + } + + return success; +} diff --git a/src/Interpreter/interpreter.cpp b/src/Interpreter/interpreter.cpp index b5f3f4fe..961695d5 100644 --- a/src/Interpreter/interpreter.cpp +++ b/src/Interpreter/interpreter.cpp @@ -240,7 +240,7 @@ bool Interpreter::interpret(Module *m, bool waiting) { m->warduino->debugger->skipBreakpoint = nullptr; // Take snapshot before executing an instruction - m->warduino->debugger->sendAsyncSnapshots(m); + m->warduino->debugger->handleSnapshotPolicy(m); opcode = *m->pc_ptr; block_ptr = m->pc_ptr; From 5bde1ac899406544b481fbe842da4ad35d764598 Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Mon, 26 Aug 2024 17:27:11 +0200 Subject: [PATCH 02/20] Remove unused duplicate interpret function --- src/Interpreter/instructions.cpp | 273 ------------------------------- 1 file changed, 273 deletions(-) diff --git a/src/Interpreter/instructions.cpp b/src/Interpreter/instructions.cpp index 88db6ca2..0e4ceb3c 100644 --- a/src/Interpreter/instructions.cpp +++ b/src/Interpreter/instructions.cpp @@ -1277,276 +1277,3 @@ bool i_instr_callback([[maybe_unused]] Module *m, // TODO return true; } - -bool interpret(Module *m, bool waiting) { - uint8_t *block_ptr; - uint8_t opcode; - - // keep track of occurring errors - bool success = true; - - // set to true when finishes successfully - bool program_done = false; - - while ((!program_done && success) || waiting) { - if (m->warduino->program_state == WARDUINOstep) { - m->warduino->debugger->pauseRuntime(m); - } - - while (m->warduino->program_state != WARDUINOinit && - m->warduino->debugger->checkDebugMessages( - m, &m->warduino->program_state)) { - } - fflush(stdout); - // esp_task_wdt_reset(); - - // Resolve 1 callback event if queue is not empty and VM not paused, and - // no event currently resolving - CallbackHandler::resolve_event(); - - // Sleep interpret loop if 'paused' or 'waiting drone' - if (m->warduino->program_state == WARDUINOpause || - m->warduino->program_state == PROXYhalt) { - // wait until new debug messages arrive - if (m->warduino->program_state == WARDUINOpause) { - warduino::unique_lock lock( - m->warduino->debugger->messageQueueMutex); - m->warduino->debugger->messageQueueConditionVariable.wait( - lock, [m] { return m->warduino->debugger->freshMessages; }); - } - continue; - } - - // Program state is not paused - - // If BP and not the one we just unpaused - if (m->warduino->debugger->isBreakpoint(m->pc_ptr) && - m->warduino->debugger->skipBreakpoint != m->pc_ptr && - m->warduino->program_state != PROXYrun) { - m->warduino->debugger->pauseRuntime(m); - m->warduino->debugger->notifyBreakpoint(m, m->pc_ptr); - continue; - } - m->warduino->debugger->skipBreakpoint = nullptr; - - // Take snapshot before executing an instruction - m->warduino->debugger->handleSnapshotPolicy(m); - - opcode = *m->pc_ptr; - block_ptr = m->pc_ptr; - m->pc_ptr += 1; - - dbg_dump_stack(m); - dbg_trace(" PC: %p OPCODE: <%s> in %s\n", block_ptr, - opcode_repr(opcode), - m->pc_ptr > m->bytes && m->pc_ptr < m->bytes + m->byte_count - ? "module" - : "patch"); - - switch (opcode) { - // - // Control flow operators - // - case 0x00: // unreachable - sprintf(exception, "%s", "unreachable"); - success &= false; - case 0x01: // nop - continue; - case 0x02: // block - success &= i_instr_block(m, block_ptr); - continue; - case 0x03: // loop - success &= i_instr_loop(m, block_ptr); - continue; - case 0x04: // if - success &= i_instr_if(m, block_ptr); - continue; - case 0x05: // else - success &= i_instr_else(m); - continue; - case 0x0b: // end - success &= i_instr_end(m, &program_done); - continue; - case 0x0c: // br - success &= i_instr_br(m); - continue; - case 0x0d: // br_if - success &= i_instr_br_if(m); - continue; - case 0x0e: // br_table - success &= i_instr_br_table(m); - continue; - case 0x0f: // return - success &= i_instr_return(m); - continue; - - // - // Call operators - // - case 0x10: { // call - success &= i_instr_call(m); - continue; - } - case 0x11: { // call_indirect - success &= i_instr_call_indirect(m); - continue; - } - // - // Parametric operators - // - case 0x1a: // drop - success &= i_instr_drop(m); - continue; - case 0x1b: // select - success &= i_instr_select(m); - continue; - - // - // Variable access - // - case 0x20: // get_local - success &= i_instr_get_local(m); - continue; - case 0x21: // set_local - success &= i_instr_set_local(m); - continue; - case 0x22: // tee_local - success &= i_instr_tee_local(m); - continue; - case 0x23: // get_global - success &= i_instr_get_global(m); - continue; - case 0x24: // set_global - success &= i_instr_set_global(m); - continue; - - // - // Memory-related operators - // - case 0x3f: // current_memory - success &= i_instr_current_memory(m); - continue; - case 0x40: // grow_memory - success &= i_instr_grow_memory(m); - continue; - // Memory load operators - case 0x28 ... 0x35: - success &= i_instr_mem_load(m, opcode); - continue; - // Memory store operators - case 0x36 ... 0x3e: - success &= i_instr_mem_store(m, opcode); - continue; - - // - // Constants - // - case 0x41 ... 0x44: // i32.const - success &= i_instr_const(m, opcode); - continue; - - // - // Comparison operators - // - - // unary - case 0x45: // i32.eqz - case 0x50: // i64.eqz - success &= i_instr_unary_u32(m, opcode); - continue; - - // i32 binary - case 0x46 ... 0x4f: - success &= i_instr_math_u32(m, opcode); - continue; - case 0x51 ... 0x5a: - success &= i_instr_math_u64(m, opcode); - continue; - case 0x5b ... 0x60: - success &= i_instr_math_f32(m, opcode); - continue; - case 0x61 ... 0x66: - success &= i_instr_math_f64(m, opcode); - continue; - - // - // Numeric operators - // - - // unary i32 - case 0x67 ... 0x69: - success &= i_instr_unary_i32(m, opcode); - continue; - - // unary i64 - case 0x79 ... 0x7b: - success &= i_instr_unary_i64(m, opcode); - continue; - - case 0x8b ... 0x91: // unary f32 - case 0x99 ... 0x9f: // unary f64 - success &= i_instr_unary_floating(m, opcode); - continue; - - // i32 binary - case 0x6a ... 0x78: - success &= i_instr_binary_i32(m, opcode); - continue; - - // i64 binary - case 0x7c ... 0x8a: - success &= i_instr_binary_i64(m, opcode); - continue; - - // f32 binary - case 0x92 ... 0x98: - success &= i_instr_binary_f32(m, opcode); - continue; - - // f64 binary - case 0xa0 ... 0xa6: - success &= i_instr_binary_f64(m, opcode); - continue; - - // conversion operations - case 0xa7 ... 0xbb: - success &= i_instr_conversion(m, opcode); - continue; - - // callback operations - case 0xe0 ... 0xe3: - success &= i_instr_callback(m, opcode); - continue; - default: - sprintf(exception, "unrecognized opcode 0x%x", opcode); - if (m->options.return_exception) { - m->exception = strdup(exception); - } - return false; - } - } - - if (m->warduino->program_state == PROXYrun) { - dbg_info("Trap was thrown during proxy call.\n"); - RFC *rfc = m->warduino->debugger->topProxyCall(); - rfc->success = false; - rfc->exception = strdup(exception); - rfc->exception_size = strlen(exception); - m->warduino->debugger->sendProxyCallResult(m); - } - - // Resolve all unhandled callback events - while (CallbackHandler::resolving_event && CallbackHandler::resolve_event()) - ; - - dbg_trace("Interpretation ended %s with status %s\n", - program_done ? "expectedly" : "unexpectedly", - success ? "ok" : "error"); - if (!success && m->options.return_exception) { - m->exception = strdup(exception); - } else if (!success) { - FATAL("%s\n", exception); - } - - return success; -} From 2db002843e13d0b608d5996e09fd9186458b91a3 Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Mon, 26 Aug 2024 19:45:16 +0200 Subject: [PATCH 03/20] Add instructions_executed into a json object --- src/Debug/debugger.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index c54591b6..6b6fe093 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -264,6 +264,7 @@ bool Debugger::checkDebugMessages(Module *m, RunningState *program_state) { this->pauseRuntime(m); free(interruptData); snapshot(m); + this->channel->write("\n"); break; case interruptSetSnapshotPolicy: setSnapshotPolicy(interruptData + 1); @@ -274,6 +275,7 @@ bool Debugger::checkDebugMessages(Module *m, RunningState *program_state) { uint16_t numberBytes = read_B16(&data); uint8_t *state = interruptData + 3; inspect(m, numberBytes, state); + this->channel->write("\n"); free(interruptData); break; } @@ -923,7 +925,7 @@ void Debugger::inspect(Module *m, const uint16_t sizeStateArray, } } } - this->channel->write("}\n"); + this->channel->write("}"); } void Debugger::setSnapshotPolicy(const uint8_t *interruptData) { @@ -947,8 +949,9 @@ void Debugger::handleSnapshotPolicy(Module *m) { } void Debugger::checkpoint(Module *m) { - this->channel->write("CHECKPOINT, {\"instructions_executed\" = %d}, ", instructions_executed); + this->channel->write(R"(CHECKPOINT {"instructions_executed": %d, "snapshot": )", instructions_executed); snapshot(m); + this->channel->write("}\n"); instructions_executed = 0; } From 052acee9f57ddb04338dc4178f16161cf2e89b78 Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Sun, 1 Sep 2024 17:43:08 +0200 Subject: [PATCH 04/20] Checkpoint on step so the debugger knows the current state again + checkpoint after instead of before primitive calls --- platforms/Zephyr/prj.conf | 2 +- src/Debug/debugger.cpp | 23 +++++++++++++++++------ src/Debug/debugger.h | 9 +++++++-- src/Interpreter/interpreter.cpp | 4 ++++ 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/platforms/Zephyr/prj.conf b/platforms/Zephyr/prj.conf index 45bdf700..c5107231 100644 --- a/platforms/Zephyr/prj.conf +++ b/platforms/Zephyr/prj.conf @@ -29,4 +29,4 @@ CONFIG_CONSOLE_GETCHAR_BUFSIZE=4096 CONFIG_CONSOLE_PUTCHAR_BUFSIZE=4096 CONFIG_POSIX_API=y -CONFIG_MAIN_STACK_SIZE=4096 +CONFIG_MAIN_STACK_SIZE=8192 diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index 6b6fe093..a0f933ab 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -23,6 +23,7 @@ Debugger::Debugger(Channel *duplex) { this->supervisor_mutex = new warduino::mutex(); this->supervisor_mutex->lock(); this->snapshotPolicy = SnapshotPolicy::none; + this->instructions_executed = 0; } // Public methods @@ -193,7 +194,7 @@ bool Debugger::checkDebugMessages(Module *m, RunningState *program_state) { // Make a checkpoint so the debugger knows the current state and // knows how many instructions were executed since the last checkpoint. if (snapshotPolicy == SnapshotPolicy::checkpointing) { - checkpoint(m); + checkpoint(m, true); } this->channel->write("PAUSE!\n"); free(interruptData); @@ -938,17 +939,22 @@ void Debugger::handleSnapshotPolicy(Module *m) { snapshot(m); } else if (snapshotPolicy == SnapshotPolicy::checkpointing) { - if (instructions_executed >= 10 || isPrimitiveBeingCalled(m)) { + if (instructions_executed >= 10 || isPrimitiveBeingCalled(m, prev_pc_ptr)) { checkpoint(m); } instructions_executed++; + prev_pc_ptr = m->pc_ptr; } else if (snapshotPolicy != SnapshotPolicy::none) { this->channel->write("WARNING: Invalid snapshot policy."); } } -void Debugger::checkpoint(Module *m) { +void Debugger::checkpoint(Module *m, bool force) { + if (instructions_executed == 0 && !force) { + return; + } + this->channel->write(R"(CHECKPOINT {"instructions_executed": %d, "snapshot": )", instructions_executed); snapshot(m); this->channel->write("}\n"); @@ -1540,11 +1546,16 @@ Debugger::~Debugger() { delete this->supervisor_mutex; delete this->supervisor; } -bool Debugger::isPrimitiveBeingCalled(Module *m) { + +bool Debugger::isPrimitiveBeingCalled(Module *m, uint8_t *pc_ptr) { + if (!pc_ptr) { + return false; + } + // TODO: Maybe primitives can also be called using the other call operators - uint8_t opcode = *m->pc_ptr; + uint8_t opcode = *pc_ptr; if (opcode == 0x10) { // call opcode - uint8_t *pc_copy = m->pc_ptr + 1; + uint8_t *pc_copy = pc_ptr + 1; uint32_t fidx = read_LEB_32(&pc_copy); return fidx < m->import_count; } diff --git a/src/Debug/debugger.h b/src/Debug/debugger.h index f0f98c73..87f619d3 100644 --- a/src/Debug/debugger.h +++ b/src/Debug/debugger.h @@ -127,6 +127,7 @@ class Debugger { SnapshotPolicy snapshotPolicy; uint32_t instructions_executed; + uint8_t *prev_pc_ptr; std::unordered_map> overrides; @@ -203,8 +204,7 @@ class Debugger { bool operation(Module *m, operation op); - bool isPrimitiveBeingCalled(Module *m); - void checkpoint(Module *m); + bool isPrimitiveBeingCalled(Module *m, uint8_t *pc_ptr); public: // Public fields @@ -298,4 +298,9 @@ class Debugger { void addOverride(Module *m, uint8_t *interruptData); void removeOverride(Module *m, uint8_t *interruptData); + + void checkpoint(Module *m, bool force = false); + inline SnapshotPolicy getSnapshotPolicy(Module *m) { + return snapshotPolicy; + } }; diff --git a/src/Interpreter/interpreter.cpp b/src/Interpreter/interpreter.cpp index 961695d5..917f96e0 100644 --- a/src/Interpreter/interpreter.cpp +++ b/src/Interpreter/interpreter.cpp @@ -200,6 +200,10 @@ bool Interpreter::interpret(Module *m, bool waiting) { while ((!program_done && success) || waiting) { if (m->warduino->program_state == WARDUINOstep) { + // Upon completing a step in checkpointing mode, make a checkpoint. + if (m->warduino->debugger->getSnapshotPolicy(m) == SnapshotPolicy::checkpointing) { + m->warduino->debugger->checkpoint(m); + } m->warduino->debugger->pauseRuntime(m); } From 55cdfd9f799b7b75b7bd92cc2a33a83ae83d06d1 Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Mon, 2 Sep 2024 10:51:16 +0200 Subject: [PATCH 05/20] Added continue for debug interrupt that allows executing x instructions of the program --- src/Debug/debugger.cpp | 26 ++++++++++++++++++++++++++ src/Debug/debugger.h | 6 ++++++ src/Interpreter/interpreter.cpp | 4 ++++ 3 files changed, 36 insertions(+) diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index a0f933ab..1dfbdd09 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -24,6 +24,7 @@ Debugger::Debugger(Channel *duplex) { this->supervisor_mutex->lock(); this->snapshotPolicy = SnapshotPolicy::none; this->instructions_executed = 0; + this->remaining_instructions = -1; } // Public methods @@ -212,6 +213,15 @@ bool Debugger::checkDebugMessages(Module *m, RunningState *program_state) { this->handleInterruptBP(m, interruptData); free(interruptData); break; + case interruptContinueFor: { + uint8_t *data = interruptData + 1; + uint32_t amount = read_B32(&data); + printf("Continue for %d instruction(s)\n", amount); + remaining_instructions = (int32_t) amount; + *program_state = WARDUINOrun; + free(interruptData); + break; + } case interruptDUMP: this->pauseRuntime(m); this->dump(m); @@ -1561,3 +1571,19 @@ bool Debugger::isPrimitiveBeingCalled(Module *m, uint8_t *pc_ptr) { } return false; } +bool Debugger::handleContinueFor(Module *m) { + if (remaining_instructions < 0) + return false; + + if (remaining_instructions == 0) { + remaining_instructions = -1; + if (snapshotPolicy == SnapshotPolicy::checkpointing) { + checkpoint(m); + } + this->channel->write("DONE!\n"); + pauseRuntime(m); + return true; + } + remaining_instructions--; + return false; +} diff --git a/src/Debug/debugger.h b/src/Debug/debugger.h index 87f619d3..c3f82f7b 100644 --- a/src/Debug/debugger.h +++ b/src/Debug/debugger.h @@ -61,6 +61,7 @@ enum InterruptTypes { interruptSTEPOver = 0x05, interruptBPAdd = 0x06, interruptBPRem = 0x07, + interruptContinueFor = 0x08, interruptInspect = 0x09, interruptDUMP = 0x10, interruptDUMPLocals = 0x11, @@ -128,6 +129,9 @@ class Debugger { SnapshotPolicy snapshotPolicy; uint32_t instructions_executed; uint8_t *prev_pc_ptr; + int32_t remaining_instructions; + + std::unordered_map> overrides; std::unordered_map> overrides; @@ -259,6 +263,8 @@ class Debugger { void handleSnapshotPolicy(Module *m); + bool handleContinueFor(Module *m); + void proxify(); void handleProxyCall(Module *m, RunningState *program_state, diff --git a/src/Interpreter/interpreter.cpp b/src/Interpreter/interpreter.cpp index 917f96e0..0de5d56a 100644 --- a/src/Interpreter/interpreter.cpp +++ b/src/Interpreter/interpreter.cpp @@ -243,6 +243,10 @@ bool Interpreter::interpret(Module *m, bool waiting) { } m->warduino->debugger->skipBreakpoint = nullptr; + if (m->warduino->debugger->handleContinueFor(m)) { + continue; + } + // Take snapshot before executing an instruction m->warduino->debugger->handleSnapshotPolicy(m); From 4bb7c704c4990b789dc7acb3e6605a7d30081417 Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Mon, 2 Sep 2024 11:26:20 +0200 Subject: [PATCH 06/20] Take a checkpoint upon enabling checkpointing mode --- src/Debug/debugger.cpp | 7 +++++-- src/Debug/debugger.h | 4 ++-- src/Interpreter/interpreter.cpp | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index 1dfbdd09..c2d26aff 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -278,7 +278,7 @@ bool Debugger::checkDebugMessages(Module *m, RunningState *program_state) { this->channel->write("\n"); break; case interruptSetSnapshotPolicy: - setSnapshotPolicy(interruptData + 1); + setSnapshotPolicy(m, interruptData + 1); free(interruptData); break; case interruptInspect: { @@ -939,8 +939,11 @@ void Debugger::inspect(Module *m, const uint16_t sizeStateArray, this->channel->write("}"); } -void Debugger::setSnapshotPolicy(const uint8_t *interruptData) { +void Debugger::setSnapshotPolicy(Module *m, const uint8_t *interruptData) { snapshotPolicy = SnapshotPolicy{*interruptData}; + if (snapshotPolicy == SnapshotPolicy::checkpointing) { + checkpoint(m, true); + } } void Debugger::handleSnapshotPolicy(Module *m) { diff --git a/src/Debug/debugger.h b/src/Debug/debugger.h index c3f82f7b..893a8430 100644 --- a/src/Debug/debugger.h +++ b/src/Debug/debugger.h @@ -259,7 +259,7 @@ class Debugger { void snapshot(Module *m) const; - void setSnapshotPolicy(const uint8_t *interruptData); + void setSnapshotPolicy(Module *m, const uint8_t *interruptData); void handleSnapshotPolicy(Module *m); @@ -306,7 +306,7 @@ class Debugger { void removeOverride(Module *m, uint8_t *interruptData); void checkpoint(Module *m, bool force = false); - inline SnapshotPolicy getSnapshotPolicy(Module *m) { + inline SnapshotPolicy getSnapshotPolicy() { return snapshotPolicy; } }; diff --git a/src/Interpreter/interpreter.cpp b/src/Interpreter/interpreter.cpp index 0de5d56a..db824e82 100644 --- a/src/Interpreter/interpreter.cpp +++ b/src/Interpreter/interpreter.cpp @@ -201,7 +201,7 @@ bool Interpreter::interpret(Module *m, bool waiting) { while ((!program_done && success) || waiting) { if (m->warduino->program_state == WARDUINOstep) { // Upon completing a step in checkpointing mode, make a checkpoint. - if (m->warduino->debugger->getSnapshotPolicy(m) == SnapshotPolicy::checkpointing) { + if (m->warduino->debugger->getSnapshotPolicy() == SnapshotPolicy::checkpointing) { m->warduino->debugger->checkpoint(m); } m->warduino->debugger->pauseRuntime(m); From 82fb31cdd185e5388f936c2ef7c92827fb5e6f90 Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Mon, 2 Sep 2024 17:07:09 +0200 Subject: [PATCH 07/20] Fix uninitialized variable --- src/Debug/debugger.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index c2d26aff..5a0ff994 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -25,6 +25,7 @@ Debugger::Debugger(Channel *duplex) { this->snapshotPolicy = SnapshotPolicy::none; this->instructions_executed = 0; this->remaining_instructions = -1; + this->prev_pc_ptr = nullptr; } // Public methods From 68e2a49f3714feadb738920b4e0087c1105b4b3b Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Mon, 9 Sep 2024 22:30:08 +0200 Subject: [PATCH 08/20] No checkpointing during WARDUINOinit --- src/Interpreter/interpreter.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Interpreter/interpreter.cpp b/src/Interpreter/interpreter.cpp index db824e82..a873d57d 100644 --- a/src/Interpreter/interpreter.cpp +++ b/src/Interpreter/interpreter.cpp @@ -248,7 +248,9 @@ bool Interpreter::interpret(Module *m, bool waiting) { } // Take snapshot before executing an instruction - m->warduino->debugger->handleSnapshotPolicy(m); + if (m->warduino->program_state != WARDUINOinit) { + m->warduino->debugger->handleSnapshotPolicy(m); + } opcode = *m->pc_ptr; block_ptr = m->pc_ptr; From 6252bc4240329c774057035d9a5e0afca7a7cd72 Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Tue, 10 Sep 2024 16:03:02 +0200 Subject: [PATCH 09/20] Only send STEP! after actually completing a step and not before Also send any checkpoints right before the STEP!. --- src/Debug/debugger.cpp | 10 +++++++++- src/Debug/debugger.h | 4 ++-- src/Interpreter/interpreter.cpp | 5 +---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index 5a0ff994..c3620466 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -448,7 +448,6 @@ void Debugger::handleInterruptRUN(const Module *m, } void Debugger::handleSTEP(const Module *m, RunningState *program_state) { - this->channel->write("STEP!\n"); *program_state = WARDUINOstep; this->skipBreakpoint = m->pc_ptr; } @@ -1575,6 +1574,7 @@ bool Debugger::isPrimitiveBeingCalled(Module *m, uint8_t *pc_ptr) { } return false; } + bool Debugger::handleContinueFor(Module *m) { if (remaining_instructions < 0) return false; @@ -1591,3 +1591,11 @@ bool Debugger::handleContinueFor(Module *m) { remaining_instructions--; return false; } + +void Debugger::notifyCompleteStep(Module *m) const { + // Upon completing a step in checkpointing mode, make a checkpoint. + if (m->warduino->debugger->getSnapshotPolicy() == SnapshotPolicy::checkpointing) { + m->warduino->debugger->checkpoint(m); + } + this->channel->write("STEP!\n"); +} diff --git a/src/Debug/debugger.h b/src/Debug/debugger.h index 893a8430..1ecf6008 100644 --- a/src/Debug/debugger.h +++ b/src/Debug/debugger.h @@ -131,8 +131,6 @@ class Debugger { uint8_t *prev_pc_ptr; int32_t remaining_instructions; - std::unordered_map> overrides; - std::unordered_map> overrides; @@ -237,6 +235,8 @@ class Debugger { void pauseRuntime(const Module *m); // pause runtime for given module + void notifyCompleteStep(Module *m) const; // notify the debugger frontend that a step was taken + // Interrupts void addDebugMessage(size_t len, const uint8_t *buff); diff --git a/src/Interpreter/interpreter.cpp b/src/Interpreter/interpreter.cpp index a873d57d..e3e60b3b 100644 --- a/src/Interpreter/interpreter.cpp +++ b/src/Interpreter/interpreter.cpp @@ -200,10 +200,7 @@ bool Interpreter::interpret(Module *m, bool waiting) { while ((!program_done && success) || waiting) { if (m->warduino->program_state == WARDUINOstep) { - // Upon completing a step in checkpointing mode, make a checkpoint. - if (m->warduino->debugger->getSnapshotPolicy() == SnapshotPolicy::checkpointing) { - m->warduino->debugger->checkpoint(m); - } + m->warduino->debugger->notifyCompleteStep(m); m->warduino->debugger->pauseRuntime(m); } From 45b4c67a2c92a5299fff99154f799b032e9393ac Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Wed, 25 Sep 2024 10:44:15 +0200 Subject: [PATCH 10/20] Allow setting the checkpoint interval instead of hardcoding it to 10 --- src/Debug/debugger.cpp | 5 ++++- src/Debug/debugger.h | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index c3620466..c07fe9c7 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -23,6 +23,7 @@ Debugger::Debugger(Channel *duplex) { this->supervisor_mutex = new warduino::mutex(); this->supervisor_mutex->lock(); this->snapshotPolicy = SnapshotPolicy::none; + this->checkpointInterval = 10; this->instructions_executed = 0; this->remaining_instructions = -1; this->prev_pc_ptr = nullptr; @@ -942,6 +943,7 @@ void Debugger::inspect(Module *m, const uint16_t sizeStateArray, void Debugger::setSnapshotPolicy(Module *m, const uint8_t *interruptData) { snapshotPolicy = SnapshotPolicy{*interruptData}; if (snapshotPolicy == SnapshotPolicy::checkpointing) { + checkpointInterval = *(interruptData + 1); checkpoint(m, true); } } @@ -950,9 +952,10 @@ void Debugger::handleSnapshotPolicy(Module *m) { if (snapshotPolicy == SnapshotPolicy::atEveryInstruction) { this->channel->write("SNAPSHOT "); snapshot(m); + this->channel->write("\n"); } else if (snapshotPolicy == SnapshotPolicy::checkpointing) { - if (instructions_executed >= 10 || isPrimitiveBeingCalled(m, prev_pc_ptr)) { + if (instructions_executed >= checkpointInterval || isPrimitiveBeingCalled(m, prev_pc_ptr)) { checkpoint(m); } instructions_executed++; diff --git a/src/Debug/debugger.h b/src/Debug/debugger.h index 1ecf6008..36b29c56 100644 --- a/src/Debug/debugger.h +++ b/src/Debug/debugger.h @@ -127,6 +127,7 @@ class Debugger { warduino::mutex *supervisor_mutex; SnapshotPolicy snapshotPolicy; + uint8_t checkpointInterval; uint32_t instructions_executed; uint8_t *prev_pc_ptr; int32_t remaining_instructions; From e2dae971121620d1cf14ad5527d03fffabf063ea Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Tue, 8 Oct 2024 11:57:10 +0200 Subject: [PATCH 11/20] Make checkpoint interval a 32bit integer --- src/Debug/debugger.cpp | 5 +++-- src/Debug/debugger.h | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index c07fe9c7..b872e1b0 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -940,10 +940,11 @@ void Debugger::inspect(Module *m, const uint16_t sizeStateArray, this->channel->write("}"); } -void Debugger::setSnapshotPolicy(Module *m, const uint8_t *interruptData) { +void Debugger::setSnapshotPolicy(Module *m, uint8_t *interruptData) { snapshotPolicy = SnapshotPolicy{*interruptData}; if (snapshotPolicy == SnapshotPolicy::checkpointing) { - checkpointInterval = *(interruptData + 1); + uint8_t *ptr = interruptData + 1; + checkpointInterval = read_B32(&ptr); checkpoint(m, true); } } diff --git a/src/Debug/debugger.h b/src/Debug/debugger.h index 36b29c56..93f59ee5 100644 --- a/src/Debug/debugger.h +++ b/src/Debug/debugger.h @@ -127,7 +127,7 @@ class Debugger { warduino::mutex *supervisor_mutex; SnapshotPolicy snapshotPolicy; - uint8_t checkpointInterval; + uint32_t checkpointInterval; uint32_t instructions_executed; uint8_t *prev_pc_ptr; int32_t remaining_instructions; @@ -260,7 +260,7 @@ class Debugger { void snapshot(Module *m) const; - void setSnapshotPolicy(Module *m, const uint8_t *interruptData); + void setSnapshotPolicy(Module *m, uint8_t *interruptData); void handleSnapshotPolicy(Module *m); From 9ce16b4370a0c1312c431759f3452836e1cc1e83 Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Mon, 21 Oct 2024 14:49:30 +0200 Subject: [PATCH 12/20] Send arguments + primitive index that was called in checkpoint messages --- src/Debug/debugger.cpp | 32 ++++++++++++++++++++++++++------ src/Debug/debugger.h | 13 ++++++++----- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index b872e1b0..0d3cb370 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -27,6 +27,7 @@ Debugger::Debugger(Channel *duplex) { this->instructions_executed = 0; this->remaining_instructions = -1; this->prev_pc_ptr = nullptr; + this->fidx_called = {}; } // Public methods @@ -956,11 +957,19 @@ void Debugger::handleSnapshotPolicy(Module *m) { this->channel->write("\n"); } else if (snapshotPolicy == SnapshotPolicy::checkpointing) { - if (instructions_executed >= checkpointInterval || isPrimitiveBeingCalled(m, prev_pc_ptr)) { + if (instructions_executed >= checkpointInterval || fidx_called) { checkpoint(m); } instructions_executed++; prev_pc_ptr = m->pc_ptr; + + // Store arguments of last primitive call. + if ((fidx_called = isPrimitiveBeingCalled(m, m->pc_ptr))) { + const Type *type = m->functions[*fidx_called].type; + for (int32_t i = 0; i < type->param_count; i++) { + prim_args[type->param_count - i - 1] = m->stack[m->sp - i].value.uint32; + } + } } else if (snapshotPolicy != SnapshotPolicy::none) { this->channel->write("WARNING: Invalid snapshot policy."); @@ -972,7 +981,18 @@ void Debugger::checkpoint(Module *m, bool force) { return; } - this->channel->write(R"(CHECKPOINT {"instructions_executed": %d, "snapshot": )", instructions_executed); + this->channel->write(R"(CHECKPOINT {"instructions_executed": %d, )", instructions_executed); + if (fidx_called) { + this->channel->write("\"fidx_called\": %d, \"args\": [", *fidx_called); + const Block &func_block = m->functions[*fidx_called]; + bool comma = false; + for (uint32_t i = 0; i < func_block.type->param_count; i++) { + channel->write("%s%d", comma ? ", " : "", prim_args[i]); + comma = true; + } + this->channel->write("], "); + } + this->channel->write(R"("snapshot": )", instructions_executed); snapshot(m); this->channel->write("}\n"); instructions_executed = 0; @@ -1564,9 +1584,9 @@ Debugger::~Debugger() { delete this->supervisor; } -bool Debugger::isPrimitiveBeingCalled(Module *m, uint8_t *pc_ptr) { +std::optional Debugger::isPrimitiveBeingCalled(Module *m, uint8_t *pc_ptr) { if (!pc_ptr) { - return false; + return {}; } // TODO: Maybe primitives can also be called using the other call operators @@ -1574,9 +1594,9 @@ bool Debugger::isPrimitiveBeingCalled(Module *m, uint8_t *pc_ptr) { if (opcode == 0x10) { // call opcode uint8_t *pc_copy = pc_ptr + 1; uint32_t fidx = read_LEB_32(&pc_copy); - return fidx < m->import_count; + return fidx; } - return false; + return {}; } bool Debugger::handleContinueFor(Module *m) { diff --git a/src/Debug/debugger.h b/src/Debug/debugger.h index 93f59ee5..79be3195 100644 --- a/src/Debug/debugger.h +++ b/src/Debug/debugger.h @@ -4,6 +4,7 @@ #include #include #include +#include #include // std::queue #include #include @@ -108,7 +109,7 @@ enum class SnapshotPolicy : int { }; class Debugger { - private: +private: std::deque debugMessages = {}; // Help variables @@ -126,14 +127,16 @@ class Debugger { bool connected_to_proxy = false; warduino::mutex *supervisor_mutex; + std::unordered_map> + overrides; + SnapshotPolicy snapshotPolicy; uint32_t checkpointInterval; uint32_t instructions_executed; uint8_t *prev_pc_ptr; int32_t remaining_instructions; - - std::unordered_map> - overrides; + std::optional fidx_called; + uint32_t prim_args[8]; // Private methods @@ -207,7 +210,7 @@ class Debugger { bool operation(Module *m, operation op); - bool isPrimitiveBeingCalled(Module *m, uint8_t *pc_ptr); + std::optional isPrimitiveBeingCalled(Module *m, uint8_t *pc_ptr); public: // Public fields From 16d3e9708970af17e9568778ff387b406b411137 Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Wed, 23 Oct 2024 18:07:09 +0200 Subject: [PATCH 13/20] Got rid of unneeded use of prev_pc_ptr --- src/Debug/debugger.cpp | 4 +--- src/Debug/debugger.h | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index 0d3cb370..c7c5ada4 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -26,7 +26,6 @@ Debugger::Debugger(Channel *duplex) { this->checkpointInterval = 10; this->instructions_executed = 0; this->remaining_instructions = -1; - this->prev_pc_ptr = nullptr; this->fidx_called = {}; } @@ -961,12 +960,11 @@ void Debugger::handleSnapshotPolicy(Module *m) { checkpoint(m); } instructions_executed++; - prev_pc_ptr = m->pc_ptr; // Store arguments of last primitive call. if ((fidx_called = isPrimitiveBeingCalled(m, m->pc_ptr))) { const Type *type = m->functions[*fidx_called].type; - for (int32_t i = 0; i < type->param_count; i++) { + for (uint32_t i = 0; i < type->param_count; i++) { prim_args[type->param_count - i - 1] = m->stack[m->sp - i].value.uint32; } } diff --git a/src/Debug/debugger.h b/src/Debug/debugger.h index 79be3195..c09e32b1 100644 --- a/src/Debug/debugger.h +++ b/src/Debug/debugger.h @@ -127,9 +127,11 @@ class Debugger { bool connected_to_proxy = false; warduino::mutex *supervisor_mutex; + // Mocking std::unordered_map> overrides; + // Checkpointing SnapshotPolicy snapshotPolicy; uint32_t checkpointInterval; uint32_t instructions_executed; From 7ec06c90485b4bf1a1e306edf12a278ab26d72f6 Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Wed, 30 Oct 2024 20:31:56 +0100 Subject: [PATCH 14/20] Clang-format --- src/Debug/debugger.cpp | 26 ++++++++++++++------------ src/Debug/debugger.h | 17 +++++++++-------- src/Edward/proxy_supervisor.h | 2 +- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index c7c5ada4..a852a796 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -195,7 +195,8 @@ bool Debugger::checkDebugMessages(Module *m, RunningState *program_state) { case interruptPAUSE: this->pauseRuntime(m); // Make a checkpoint so the debugger knows the current state and - // knows how many instructions were executed since the last checkpoint. + // knows how many instructions were executed since the last + // checkpoint. if (snapshotPolicy == SnapshotPolicy::checkpointing) { checkpoint(m, true); } @@ -219,7 +220,7 @@ bool Debugger::checkDebugMessages(Module *m, RunningState *program_state) { uint8_t *data = interruptData + 1; uint32_t amount = read_B32(&data); printf("Continue for %d instruction(s)\n", amount); - remaining_instructions = (int32_t) amount; + remaining_instructions = (int32_t)amount; *program_state = WARDUINOrun; free(interruptData); break; @@ -954,8 +955,7 @@ void Debugger::handleSnapshotPolicy(Module *m) { this->channel->write("SNAPSHOT "); snapshot(m); this->channel->write("\n"); - } - else if (snapshotPolicy == SnapshotPolicy::checkpointing) { + } else if (snapshotPolicy == SnapshotPolicy::checkpointing) { if (instructions_executed >= checkpointInterval || fidx_called) { checkpoint(m); } @@ -965,11 +965,11 @@ void Debugger::handleSnapshotPolicy(Module *m) { if ((fidx_called = isPrimitiveBeingCalled(m, m->pc_ptr))) { const Type *type = m->functions[*fidx_called].type; for (uint32_t i = 0; i < type->param_count; i++) { - prim_args[type->param_count - i - 1] = m->stack[m->sp - i].value.uint32; + prim_args[type->param_count - i - 1] = + m->stack[m->sp - i].value.uint32; } } - } - else if (snapshotPolicy != SnapshotPolicy::none) { + } else if (snapshotPolicy != SnapshotPolicy::none) { this->channel->write("WARNING: Invalid snapshot policy."); } } @@ -979,7 +979,8 @@ void Debugger::checkpoint(Module *m, bool force) { return; } - this->channel->write(R"(CHECKPOINT {"instructions_executed": %d, )", instructions_executed); + this->channel->write(R"(CHECKPOINT {"instructions_executed": %d, )", + instructions_executed); if (fidx_called) { this->channel->write("\"fidx_called\": %d, \"args\": [", *fidx_called); const Block &func_block = m->functions[*fidx_called]; @@ -1582,7 +1583,8 @@ Debugger::~Debugger() { delete this->supervisor; } -std::optional Debugger::isPrimitiveBeingCalled(Module *m, uint8_t *pc_ptr) { +std::optional Debugger::isPrimitiveBeingCalled(Module *m, + uint8_t *pc_ptr) { if (!pc_ptr) { return {}; } @@ -1598,8 +1600,7 @@ std::optional Debugger::isPrimitiveBeingCalled(Module *m, uint8_t *pc_ } bool Debugger::handleContinueFor(Module *m) { - if (remaining_instructions < 0) - return false; + if (remaining_instructions < 0) return false; if (remaining_instructions == 0) { remaining_instructions = -1; @@ -1616,7 +1617,8 @@ bool Debugger::handleContinueFor(Module *m) { void Debugger::notifyCompleteStep(Module *m) const { // Upon completing a step in checkpointing mode, make a checkpoint. - if (m->warduino->debugger->getSnapshotPolicy() == SnapshotPolicy::checkpointing) { + if (m->warduino->debugger->getSnapshotPolicy() == + SnapshotPolicy::checkpointing) { m->warduino->debugger->checkpoint(m); } this->channel->write("STEP!\n"); diff --git a/src/Debug/debugger.h b/src/Debug/debugger.h index c09e32b1..fd262132 100644 --- a/src/Debug/debugger.h +++ b/src/Debug/debugger.h @@ -103,13 +103,15 @@ enum InterruptTypes { }; enum class SnapshotPolicy : int { - none, // Don't automatically take snapshots. - atEveryInstruction, // Take a snapshot after every instruction. - checkpointing, // Take a snapshot every x instructions or at specific points were reversible primitives are used or sensor values are read. + none, // Don't automatically take snapshots. + atEveryInstruction, // Take a snapshot after every instruction. + checkpointing, // Take a snapshot every x instructions or at specific + // points were reversible primitives are used or sensor + // values are read. }; class Debugger { -private: + private: std::deque debugMessages = {}; // Help variables @@ -241,7 +243,8 @@ class Debugger { void pauseRuntime(const Module *m); // pause runtime for given module - void notifyCompleteStep(Module *m) const; // notify the debugger frontend that a step was taken + void notifyCompleteStep( + Module *m) const; // notify the debugger frontend that a step was taken // Interrupts @@ -312,7 +315,5 @@ class Debugger { void removeOverride(Module *m, uint8_t *interruptData); void checkpoint(Module *m, bool force = false); - inline SnapshotPolicy getSnapshotPolicy() { - return snapshotPolicy; - } + inline SnapshotPolicy getSnapshotPolicy() { return snapshotPolicy; } }; diff --git a/src/Edward/proxy_supervisor.h b/src/Edward/proxy_supervisor.h index 7127fe69..40b0bb55 100644 --- a/src/Edward/proxy_supervisor.h +++ b/src/Edward/proxy_supervisor.h @@ -1,7 +1,7 @@ #pragma once #include -//#include +// #include #include #include #include From b51d7f0d002fb5a0c2be8de6a62aa27c3be36c3e Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Wed, 30 Oct 2024 21:09:26 +0100 Subject: [PATCH 15/20] Use PRIu32 to display amount of instructions to step in continue for debug message --- src/Debug/debugger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index a852a796..6fefe8ad 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -219,7 +219,7 @@ bool Debugger::checkDebugMessages(Module *m, RunningState *program_state) { case interruptContinueFor: { uint8_t *data = interruptData + 1; uint32_t amount = read_B32(&data); - printf("Continue for %d instruction(s)\n", amount); + debug("Continue for %" PRIu32 " instruction(s)\n", amount); remaining_instructions = (int32_t)amount; *program_state = WARDUINOrun; free(interruptData); From 541920f8489cb5a484551ebdf2075e3d91c98e58 Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Wed, 30 Oct 2024 22:00:59 +0100 Subject: [PATCH 16/20] Rename isPrimitiveBeingCalled and remove unnecessary Module argument --- src/Debug/debugger.cpp | 5 ++--- src/Debug/debugger.h | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index 6fefe8ad..e3f67f9d 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -962,7 +962,7 @@ void Debugger::handleSnapshotPolicy(Module *m) { instructions_executed++; // Store arguments of last primitive call. - if ((fidx_called = isPrimitiveBeingCalled(m, m->pc_ptr))) { + if ((fidx_called = getPrimitiveBeingCalled(m->pc_ptr))) { const Type *type = m->functions[*fidx_called].type; for (uint32_t i = 0; i < type->param_count; i++) { prim_args[type->param_count - i - 1] = @@ -1583,8 +1583,7 @@ Debugger::~Debugger() { delete this->supervisor; } -std::optional Debugger::isPrimitiveBeingCalled(Module *m, - uint8_t *pc_ptr) { +std::optional Debugger::getPrimitiveBeingCalled(uint8_t *pc_ptr) { if (!pc_ptr) { return {}; } diff --git a/src/Debug/debugger.h b/src/Debug/debugger.h index fd262132..62ec648a 100644 --- a/src/Debug/debugger.h +++ b/src/Debug/debugger.h @@ -214,7 +214,7 @@ class Debugger { bool operation(Module *m, operation op); - std::optional isPrimitiveBeingCalled(Module *m, uint8_t *pc_ptr); + std::optional getPrimitiveBeingCalled(uint8_t *pc_ptr); public: // Public fields From c0e54186357dd0a6d625ba62bd8a3fdc4802cead Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Thu, 31 Oct 2024 10:27:55 +0100 Subject: [PATCH 17/20] Minor cleanup --- src/Debug/debugger.cpp | 48 ++++++++++++++++++++++-------------------- src/Debug/debugger.h | 17 +++++++-------- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index e3f67f9d..8c6459f4 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -25,8 +25,8 @@ Debugger::Debugger(Channel *duplex) { this->snapshotPolicy = SnapshotPolicy::none; this->checkpointInterval = 10; this->instructions_executed = 0; - this->remaining_instructions = -1; this->fidx_called = {}; + this->remaining_instructions = -1; } // Public methods @@ -943,6 +943,8 @@ void Debugger::inspect(Module *m, const uint16_t sizeStateArray, void Debugger::setSnapshotPolicy(Module *m, uint8_t *interruptData) { snapshotPolicy = SnapshotPolicy{*interruptData}; + + // Make a checkpoint when you first enable checkpointing if (snapshotPolicy == SnapshotPolicy::checkpointing) { uint8_t *ptr = interruptData + 1; checkpointInterval = read_B32(&ptr); @@ -950,6 +952,21 @@ void Debugger::setSnapshotPolicy(Module *m, uint8_t *interruptData) { } } +std::optional getPrimitiveBeingCalled(uint8_t *pc_ptr) { + if (!pc_ptr) { + return {}; + } + + // TODO: Support call_indirect + uint8_t opcode = *pc_ptr; + if (opcode == 0x10) { // call opcode + uint8_t *pc_copy = pc_ptr + 1; + uint32_t fidx = read_LEB_32(&pc_copy); + return fidx; + } + return {}; +} + void Debugger::handleSnapshotPolicy(Module *m) { if (snapshotPolicy == SnapshotPolicy::atEveryInstruction) { this->channel->write("SNAPSHOT "); @@ -1576,28 +1593,6 @@ void Debugger::removeOverride(Module *m, uint8_t *interruptData) { overrides[fidx.value()].erase(arg); } -Debugger::~Debugger() { - this->disconnect_proxy(); - this->stop(); - delete this->supervisor_mutex; - delete this->supervisor; -} - -std::optional Debugger::getPrimitiveBeingCalled(uint8_t *pc_ptr) { - if (!pc_ptr) { - return {}; - } - - // TODO: Maybe primitives can also be called using the other call operators - uint8_t opcode = *pc_ptr; - if (opcode == 0x10) { // call opcode - uint8_t *pc_copy = pc_ptr + 1; - uint32_t fidx = read_LEB_32(&pc_copy); - return fidx; - } - return {}; -} - bool Debugger::handleContinueFor(Module *m) { if (remaining_instructions < 0) return false; @@ -1622,3 +1617,10 @@ void Debugger::notifyCompleteStep(Module *m) const { } this->channel->write("STEP!\n"); } + +Debugger::~Debugger() { + this->disconnect_proxy(); + this->stop(); + delete this->supervisor_mutex; + delete this->supervisor; +} diff --git a/src/Debug/debugger.h b/src/Debug/debugger.h index 62ec648a..12a0e396 100644 --- a/src/Debug/debugger.h +++ b/src/Debug/debugger.h @@ -106,8 +106,7 @@ enum class SnapshotPolicy : int { none, // Don't automatically take snapshots. atEveryInstruction, // Take a snapshot after every instruction. checkpointing, // Take a snapshot every x instructions or at specific - // points were reversible primitives are used or sensor - // values are read. + // points where primitives are used. }; class Debugger { @@ -135,12 +134,13 @@ class Debugger { // Checkpointing SnapshotPolicy snapshotPolicy; - uint32_t checkpointInterval; - uint32_t instructions_executed; - uint8_t *prev_pc_ptr; + uint32_t checkpointInterval; // #instructions between checkpoints + uint32_t instructions_executed; // #instructions since last checkpoint + std::optional fidx_called; // The primitive that was executed + uint32_t prim_args[8]; // The arguments of the executed prim + + // Continue for int32_t remaining_instructions; - std::optional fidx_called; - uint32_t prim_args[8]; // Private methods @@ -214,8 +214,6 @@ class Debugger { bool operation(Module *m, operation op); - std::optional getPrimitiveBeingCalled(uint8_t *pc_ptr); - public: // Public fields warduino::mutex messageQueueMutex; // mutual exclude debugMessages @@ -314,6 +312,7 @@ class Debugger { void addOverride(Module *m, uint8_t *interruptData); void removeOverride(Module *m, uint8_t *interruptData); + // Checkpointing void checkpoint(Module *m, bool force = false); inline SnapshotPolicy getSnapshotPolicy() { return snapshotPolicy; } }; From 3e9142847d5c3403fe9dacee8727c0f18e3cb65a Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Thu, 31 Oct 2024 10:30:14 +0100 Subject: [PATCH 18/20] Clang-format --- src/Debug/debugger.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Debug/debugger.h b/src/Debug/debugger.h index 12a0e396..98f28e48 100644 --- a/src/Debug/debugger.h +++ b/src/Debug/debugger.h @@ -134,10 +134,10 @@ class Debugger { // Checkpointing SnapshotPolicy snapshotPolicy; - uint32_t checkpointInterval; // #instructions between checkpoints - uint32_t instructions_executed; // #instructions since last checkpoint - std::optional fidx_called; // The primitive that was executed - uint32_t prim_args[8]; // The arguments of the executed prim + uint32_t checkpointInterval; // #instructions between checkpoints + uint32_t instructions_executed; // #instructions since last checkpoint + std::optional fidx_called; // The primitive that was executed + uint32_t prim_args[8]; // The arguments of the executed prim // Continue for int32_t remaining_instructions; From bcdac1e496b16568e17a2a99c457865c780f95e8 Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Thu, 31 Oct 2024 10:43:07 +0100 Subject: [PATCH 19/20] Use raw string literal --- src/Debug/debugger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index 8c6459f4..8477db65 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -999,7 +999,7 @@ void Debugger::checkpoint(Module *m, bool force) { this->channel->write(R"(CHECKPOINT {"instructions_executed": %d, )", instructions_executed); if (fidx_called) { - this->channel->write("\"fidx_called\": %d, \"args\": [", *fidx_called); + this->channel->write(R"("fidx_called": %d, "args": [)", *fidx_called); const Block &func_block = m->functions[*fidx_called]; bool comma = false; for (uint32_t i = 0; i < func_block.type->param_count; i++) { From c6b91d4db8e807d401c9aaf9e11ea03584032176 Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Wed, 20 Nov 2024 11:11:47 +0100 Subject: [PATCH 20/20] Fix checkpointing so checkpoints are only taken after primitive calls --- src/Debug/debugger.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index 8477db65..a8e3802f 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -952,7 +952,7 @@ void Debugger::setSnapshotPolicy(Module *m, uint8_t *interruptData) { } } -std::optional getPrimitiveBeingCalled(uint8_t *pc_ptr) { +std::optional getPrimitiveBeingCalled(Module *m, uint8_t *pc_ptr) { if (!pc_ptr) { return {}; } @@ -962,7 +962,9 @@ std::optional getPrimitiveBeingCalled(uint8_t *pc_ptr) { if (opcode == 0x10) { // call opcode uint8_t *pc_copy = pc_ptr + 1; uint32_t fidx = read_LEB_32(&pc_copy); - return fidx; + if (fidx < m->import_count) { + return fidx; + } } return {}; } @@ -979,7 +981,7 @@ void Debugger::handleSnapshotPolicy(Module *m) { instructions_executed++; // Store arguments of last primitive call. - if ((fidx_called = getPrimitiveBeingCalled(m->pc_ptr))) { + if ((fidx_called = getPrimitiveBeingCalled(m, m->pc_ptr))) { const Type *type = m->functions[*fidx_called].type; for (uint32_t i = 0; i < type->param_count; i++) { prim_args[type->param_count - i - 1] =