diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index 94edf40b..50d3c8d1 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -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; @@ -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 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; } @@ -1670,7 +1676,7 @@ std::optional 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; @@ -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 fidx = resolve_imported_function(m, primitive_name); + const std::string primitive_name = read_string(&interruptData); + const std::optional 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 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 fidx = resolve_imported_function(m, primitive_name); + const std::string primitive_name = read_string(&interruptData); + const std::optional 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 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 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) { diff --git a/src/Debug/debugger.h b/src/Debug/debugger.h index c612ddf8..6a241ab8 100644 --- a/src/Debug/debugger.h +++ b/src/Debug/debugger.h @@ -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 &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 debugMessages = {}; @@ -130,7 +150,7 @@ class Debugger { warduino::mutex *supervisor_mutex; // Mocking - std::unordered_map> + std::unordered_map, uint32_t, FNV1aVectorHash> overrides; // Checkpointing @@ -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); @@ -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); diff --git a/src/Interpreter/instructions.cpp b/src/Interpreter/instructions.cpp index eb05081a..d2df36d2 100644 --- a/src/Interpreter/instructions.cpp +++ b/src/Interpreter/instructions.cpp @@ -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(param_count) - 1; + ectx->stack[ectx->sp].value.uint32 = mock_result; return true; } }