Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚀 Add checkpointing policy #253

Merged
merged 20 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e67afe9
Add basic checkpointing snapshotting policy
MaartenS11 Aug 26, 2024
5bde1ac
Remove unused duplicate interpret function
MaartenS11 Aug 26, 2024
2db0028
Add instructions_executed into a json object
MaartenS11 Aug 26, 2024
052acee
Checkpoint on step so the debugger knows the current state again + ch…
MaartenS11 Sep 1, 2024
55cdfd9
Added continue for debug interrupt that allows executing x instructio…
MaartenS11 Sep 2, 2024
4bb7c70
Take a checkpoint upon enabling checkpointing mode
MaartenS11 Sep 2, 2024
82fb31c
Fix uninitialized variable
MaartenS11 Sep 2, 2024
68e2a49
No checkpointing during WARDUINOinit
MaartenS11 Sep 9, 2024
6252bc4
Only send STEP! after actually completing a step and not before
MaartenS11 Sep 10, 2024
45b4c67
Allow setting the checkpoint interval instead of hardcoding it to 10
MaartenS11 Sep 25, 2024
e2dae97
Make checkpoint interval a 32bit integer
MaartenS11 Oct 8, 2024
9ce16b4
Send arguments + primitive index that was called in checkpoint messages
MaartenS11 Oct 21, 2024
16d3e97
Got rid of unneeded use of prev_pc_ptr
MaartenS11 Oct 23, 2024
7ec06c9
Clang-format
MaartenS11 Oct 30, 2024
b51d7f0
Use PRIu32 to display amount of instructions to step in continue for …
MaartenS11 Oct 30, 2024
541920f
Rename isPrimitiveBeingCalled and remove unnecessary Module argument
MaartenS11 Oct 30, 2024
c0e5418
Minor cleanup
MaartenS11 Oct 31, 2024
3e91428
Clang-format
MaartenS11 Oct 31, 2024
bcdac1e
Use raw string literal
MaartenS11 Oct 31, 2024
c6b91d4
Fix checkpointing so checkpoints are only taken after primitive calls
MaartenS11 Nov 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion platforms/Zephyr/prj.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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
127 changes: 118 additions & 9 deletions src/Debug/debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -259,16 +278,18 @@ 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: {
uint8_t *data = interruptData + 1;
uint16_t numberBytes = read_B16(&data);
uint8_t *state = interruptData + 3;
inspect(m, numberBytes, state);
this->channel->write("\n");
free(interruptData);
break;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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<uint32_t> 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) {
Expand Down Expand Up @@ -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();
Expand Down
37 changes: 32 additions & 5 deletions src/Debug/debugger.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <cstddef>
#include <cstdint>
#include <mutex>
#include <optional>
#include <queue> // std::queue
#include <set>
#include <thread>
Expand Down Expand Up @@ -61,6 +62,7 @@ enum InterruptTypes {
interruptSTEPOver = 0x05,
interruptBPAdd = 0x06,
interruptBPRem = 0x07,
interruptContinueFor = 0x08,
interruptInspect = 0x09,
interruptDUMP = 0x10,
interruptDUMPLocals = 0x11,
Expand All @@ -77,7 +79,7 @@ enum InterruptTypes {

// Pull Debugging
interruptSnapshot = 0x60,
interruptEnableSnapshots = 0x61,
interruptSetSnapshotPolicy = 0x61,
interruptLoadSnapshot = 0x62,
interruptMonitorProxies = 0x63,
interruptProxyCall = 0x64,
Expand All @@ -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<uint8_t *> debugMessages = {};
Expand All @@ -119,11 +128,20 @@ class Debugger {
bool connected_to_proxy = false;
warduino::mutex *supervisor_mutex;

bool asyncSnapshots;

// Mocking
std::unordered_map<uint32_t, std::unordered_map<uint32_t, uint32_t>>
overrides;

// Checkpointing
SnapshotPolicy snapshotPolicy;
uint32_t checkpointInterval; // #instructions between checkpoints
uint32_t instructions_executed; // #instructions since last checkpoint
std::optional<uint32_t> 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;
Expand Down Expand Up @@ -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);
Expand All @@ -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();

Expand Down Expand Up @@ -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; }
};
2 changes: 1 addition & 1 deletion src/Edward/proxy_supervisor.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

#include <cinttypes>
//#include <csignal>
// #include <csignal>
#include <mutex>
#include <set>
#include <thread>
Expand Down
9 changes: 8 additions & 1 deletion src/Interpreter/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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;
Expand Down