Skip to content
89 changes: 60 additions & 29 deletions src/Debug/debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -974,14 +974,16 @@ void Debugger::inspect(Module *m, const uint16_t sizeStateArray,
this->channel->write("%s", addComma ? "," : "");
this->channel->write(R"("overrides": [)");
bool comma = false;
for (auto key : overrides) {
for (auto argResult : key.second) {
this->channel->write("%s", comma ? ", " : "");
this->channel->write(
R"({"fidx": %d, "arg": %d, "return_value": %d})",
key.first, argResult.first, argResult.second);
comma = true;
for (const auto &[key, return_value] : overrides) {
this->channel->write("%s", comma ? ", " : "");
const uint32_t fidx = key[key.size() - 1];
this->channel->write(R"({"fidx": %d, "args": [)", fidx);
for (uint32_t i = 0; i < key.size() - 1; i++) {
this->channel->write("%s%d", i > 0 ? ", " : "", key[i]);
}
this->channel->write(R"(], "return_value": %d})",
return_value);
comma = true;
}
this->channel->write("]");
addComma = true;
Expand Down Expand Up @@ -1482,10 +1484,14 @@ bool Debugger::saveState(Module *m, uint8_t *interruptData) {
uint8_t overrides_count = *program_state++;
for (uint32_t i = 0; i < overrides_count; i++) {
uint32_t fidx = read_B32(&program_state);
uint32_t arg = read_B32(&program_state);
uint32_t param_count = m->functions[fidx].type->param_count;
std::vector<uint32_t> key(param_count + 1);
for (uint32_t j = 0; j < param_count; j++) {
key[j] = read_B32(&program_state);
}
key[param_count] = fidx;
uint32_t return_value = read_B32(&program_state);
overrides[fidx][arg] = return_value;
debug("Override %d %d %d\n", fidx, arg, return_value);
overrides[key] = return_value;
}
break;
}
Expand Down Expand Up @@ -1670,7 +1676,7 @@ std::optional<uint32_t> resolve_imported_function(Module *m,
}

std::string read_string(uint8_t **pos) {
std::string str = "";
std::string str;
char c = *(*pos)++;
while (c != '\0') {
str += c;
Expand All @@ -1680,43 +1686,68 @@ std::string read_string(uint8_t **pos) {
}

void Debugger::addOverride(Module *m, uint8_t *interruptData) {
std::string primitive_name = read_string(&interruptData);
uint32_t arg = read_B32(&interruptData);
uint32_t result = read_B32(&interruptData);

std::optional<uint32_t> fidx = resolve_imported_function(m, primitive_name);
const std::string primitive_name = read_string(&interruptData);
const std::optional<uint32_t> fidx =
resolve_imported_function(m, primitive_name);
if (!fidx) {
channel->write(
"Cannot override the result for unknown function \"%s\".\n",
primitive_name.c_str());
channel->write("ack%x;0\n", interruptUnsetOverridePinValue);
return;
}

channel->write("Override %s(%d) = %d.\n", primitive_name.c_str(), arg,
result);
overrides[fidx.value()][arg] = result;
const uint32_t param_count = m->functions[fidx.value()].type->param_count;
std::vector<uint32_t> key(param_count + 1);
for (uint32_t i = 0; i < param_count; i++) {
key[i] = read_B32(&interruptData);
}
key[param_count] = fidx.value();

const uint32_t result = read_B32(&interruptData);
channel->write("ack%x;1\n", interruptSetOverridePinValue);
overrides[key] = result;
}

void Debugger::removeOverride(Module *m, uint8_t *interruptData) {
std::string primitive_name = read_string(&interruptData);
uint32_t arg = read_B32(&interruptData);

std::optional<uint32_t> fidx = resolve_imported_function(m, primitive_name);
const std::string primitive_name = read_string(&interruptData);
const std::optional<uint32_t> fidx =
resolve_imported_function(m, primitive_name);
if (!fidx) {
channel->write("Cannot remove override for unknown function \"%s\".\n",
primitive_name.c_str());
channel->write("ack%x;0\n", interruptUnsetOverridePinValue);
return;
}

if (overrides[fidx.value()].count(arg) == 0) {
channel->write("Override for %s(%d) not found.\n",
primitive_name.c_str(), arg);
const uint32_t param_count = m->functions[fidx.value()].type->param_count;
std::vector<uint32_t> key(param_count + 1);
for (uint32_t i = 0; i < param_count; i++) {
key[i] = read_B32(&interruptData);
}
key[param_count] = fidx.value();

if (overrides.erase(key) == 0) {
channel->write("ack%x;0\n", interruptUnsetOverridePinValue);
return;
}
channel->write("ack%x;1\n", interruptUnsetOverridePinValue);
}

channel->write("Removing override %s(%d) = %d.\n", primitive_name.c_str(),
arg, overrides[fidx.value()][arg]);
overrides[fidx.value()].erase(arg);
bool Debugger::getMockForArgs(Module *m, uint32_t fidx, uint32_t &result) {
const uint32_t param_count = m->functions[fidx].type->param_count;
std::vector<uint32_t> key(param_count + 1);
const ExecutionContext *ectx = m->warduino->execution_context;
for (uint32_t i = 0; i < param_count; i++) {
key[i] = ectx->stack[ectx->sp - (param_count - i - 1)].value.uint32;
}
key[param_count] = fidx;
const auto it = overrides.find(key);
if (it == overrides.end()) {
return false;
}
result = it->second;
return true;
}

bool Debugger::handleContinueFor(Module *m) {
Expand Down
37 changes: 27 additions & 10 deletions src/Debug/debugger.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,26 @@ enum class SnapshotPolicy : int {
// points where primitives are used.
};

/*
* FNV-1a 32bit:
* https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html
*/
struct FNV1aVectorHash {
size_t operator()(const std::vector<uint32_t> &values) const {
constexpr uint32_t FNV_offset_basis = 0x811c9dc5;
uint32_t result_hash = FNV_offset_basis;
for (const uint32_t v : values) {
for (int i = 0; i < 4; ++i) {
constexpr uint32_t FNV_prime = 0x01000193;
const uint8_t byte = (v >> (i * 8)) & 0xff;
result_hash ^= byte;
result_hash *= FNV_prime;
}
}
return result_hash;
}
};

class Debugger {
private:
std::deque<uint8_t *> debugMessages = {};
Expand All @@ -130,7 +150,7 @@ class Debugger {
warduino::mutex *supervisor_mutex;

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

// Checkpointing
Expand Down Expand Up @@ -206,6 +226,11 @@ class Debugger {

bool reset(Module *m);

//// Handle mocking

void addOverride(Module *m, uint8_t *interruptData);
void removeOverride(Module *m, uint8_t *interruptData);

//// Handle out-of-place debugging

void freeState(Module *m, uint8_t *interruptData);
Expand Down Expand Up @@ -308,15 +333,7 @@ class Debugger {
bool handlePushedEvent(char *bytes) const;

// Concolic Multiverse Debugging
inline bool isMocked(uint32_t fidx, uint32_t argument) {
return overrides.count(fidx) > 0 && overrides[fidx].count(argument) > 0;
}
inline uint32_t getMockedValue(uint32_t fidx, uint32_t argument) {
return overrides[fidx][argument];
}

void addOverride(Module *m, uint8_t *interruptData);
void removeOverride(Module *m, uint8_t *interruptData);
bool getMockForArgs(Module *m, uint32_t fidx, uint32_t &result);

// Checkpointing
void checkpoint(Module *m, bool force = false);
Expand Down
10 changes: 6 additions & 4 deletions src/Interpreter/instructions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -305,10 +305,12 @@ bool i_instr_call(Module *m) {

// Mocking only works on primitives, no need to check for it otherwise.
if (ectx->sp >= 0) {
uint32_t arg = ectx->stack[ectx->sp].value.uint32;
if (m->warduino->debugger->isMocked(fidx, arg)) {
ectx->stack[ectx->sp].value.uint32 =
m->warduino->debugger->getMockedValue(fidx, arg);
uint32_t mock_result;
if (m->warduino->debugger->getMockForArgs(m, fidx, mock_result)) {
const uint32_t param_count =
m->functions[fidx].type->param_count;
ectx->sp -= static_cast<int>(param_count) - 1;
ectx->stack[ectx->sp].value.uint32 = mock_result;
return true;
}
}
Expand Down
Loading