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 c2b2ce63..a8e3802f 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -22,7 +22,11 @@ Debugger::Debugger(Channel *duplex) { this->channel = duplex; this->supervisor_mutex = new warduino::mutex(); this->supervisor_mutex->lock(); - this->asyncSnapshots = false; + this->snapshotPolicy = SnapshotPolicy::none; + this->checkpointInterval = 10; + this->instructions_executed = 0; + this->fidx_called = {}; + this->remaining_instructions = -1; } // Public methods @@ -190,6 +194,12 @@ 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, true); + } this->channel->write("PAUSE!\n"); free(interruptData); break; @@ -206,6 +216,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); + debug("Continue for %" PRIu32 " instruction(s)\n", amount); + remaining_instructions = (int32_t)amount; + *program_state = WARDUINOrun; + free(interruptData); + break; + } case interruptDUMP: this->pauseRuntime(m); this->dump(m); @@ -259,9 +278,10 @@ bool Debugger::checkDebugMessages(Module *m, RunningState *program_state) { this->pauseRuntime(m); free(interruptData); snapshot(m); + this->channel->write("\n"); break; - case interruptEnableSnapshots: - enableSnapshots(interruptData + 1); + case interruptSetSnapshotPolicy: + setSnapshotPolicy(m, interruptData + 1); free(interruptData); break; case interruptInspect: { @@ -269,6 +289,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; } @@ -429,7 +450,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; } @@ -918,18 +938,82 @@ void Debugger::inspect(Module *m, const uint16_t sizeStateArray, } } } - this->channel->write("}\n"); + this->channel->write("}"); +} + +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); + checkpoint(m, true); + } } -void Debugger::enableSnapshots(const uint8_t *interruptData) { - asyncSnapshots = *interruptData; +std::optional getPrimitiveBeingCalled(Module *m, 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); + if (fidx < m->import_count) { + return fidx; + } + } + return {}; } -void Debugger::sendAsyncSnapshots(Module *m) const { - if (asyncSnapshots) { +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 >= checkpointInterval || fidx_called) { + checkpoint(m); + } + instructions_executed++; + + // Store arguments of last primitive call. + 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] = + m->stack[m->sp - i].value.uint32; + } + } + } else if (snapshotPolicy != SnapshotPolicy::none) { + this->channel->write("WARNING: Invalid snapshot policy."); + } +} + +void Debugger::checkpoint(Module *m, bool force) { + if (instructions_executed == 0 && !force) { + return; } + + this->channel->write(R"(CHECKPOINT {"instructions_executed": %d, )", + instructions_executed); + if (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++) { + 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; } void Debugger::freeState(Module *m, uint8_t *interruptData) { @@ -1511,6 +1595,31 @@ void Debugger::removeOverride(Module *m, uint8_t *interruptData) { overrides[fidx.value()].erase(arg); } +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; +} + +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"); +} + Debugger::~Debugger() { this->disconnect_proxy(); this->stop(); diff --git a/src/Debug/debugger.h b/src/Debug/debugger.h index c43a00af..98f28e48 100644 --- a/src/Debug/debugger.h +++ b/src/Debug/debugger.h @@ -4,6 +4,7 @@ #include #include #include +#include #include // std::queue #include #include @@ -61,6 +62,7 @@ enum InterruptTypes { interruptSTEPOver = 0x05, interruptBPAdd = 0x06, interruptBPRem = 0x07, + interruptContinueFor = 0x08, interruptInspect = 0x09, interruptDUMP = 0x10, interruptDUMPLocals = 0x11, @@ -77,7 +79,7 @@ enum InterruptTypes { // Pull Debugging interruptSnapshot = 0x60, - interruptEnableSnapshots = 0x61, + interruptSetSnapshotPolicy = 0x61, interruptLoadSnapshot = 0x62, interruptMonitorProxies = 0x63, interruptProxyCall = 0x64, @@ -100,6 +102,13 @@ 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 where primitives are used. +}; + class Debugger { private: std::deque debugMessages = {}; @@ -119,11 +128,20 @@ class Debugger { bool connected_to_proxy = false; warduino::mutex *supervisor_mutex; - bool asyncSnapshots; - + // Mocking std::unordered_map> overrides; + // 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 + + // Continue for + int32_t remaining_instructions; + // Private methods void printValue(const StackValue *v, uint32_t idx, bool end) const; @@ -223,6 +241,9 @@ 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); @@ -245,9 +266,11 @@ class Debugger { void snapshot(Module *m) const; - void enableSnapshots(const uint8_t *interruptData); + void setSnapshotPolicy(Module *m, uint8_t *interruptData); - void sendAsyncSnapshots(Module *m) const; + void handleSnapshotPolicy(Module *m); + + bool handleContinueFor(Module *m); void proxify(); @@ -288,4 +311,8 @@ 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; } }; 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 diff --git a/src/Interpreter/interpreter.cpp b/src/Interpreter/interpreter.cpp index b5f3f4fe..e3e60b3b 100644 --- a/src/Interpreter/interpreter.cpp +++ b/src/Interpreter/interpreter.cpp @@ -200,6 +200,7 @@ bool Interpreter::interpret(Module *m, bool waiting) { while ((!program_done && success) || waiting) { if (m->warduino->program_state == WARDUINOstep) { + m->warduino->debugger->notifyCompleteStep(m); m->warduino->debugger->pauseRuntime(m); } @@ -239,8 +240,14 @@ 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->sendAsyncSnapshots(m); + if (m->warduino->program_state != WARDUINOinit) { + m->warduino->debugger->handleSnapshotPolicy(m); + } opcode = *m->pc_ptr; block_ptr = m->pc_ptr;