From c58bdbec5d3a2ee34794e7db403fc68be5702701 Mon Sep 17 00:00:00 2001 From: Eduardo Bart Date: Mon, 20 Nov 2023 12:30:22 -0300 Subject: [PATCH] feat: add soft yield runtime config using hints of SRAIW instruction --- lib/grpc-interfaces | 2 +- src/clua-cartesi.cpp | 1 + src/clua-machine-util.cpp | 1 + src/grpc-virtual-machine.cpp | 11 +--------- src/i-state-access.h | 5 +++++ src/interpret.cpp | 14 +++++++++--- src/interpret.h | 1 + src/json-util.cpp | 4 +++- src/jsonrpc-discover.json | 4 ++++ src/jsonrpc-remote-machine.cpp | 2 ++ src/machine-c-api.cpp | 1 + src/machine-c-api.h | 2 ++ src/machine-runtime-config.h | 1 + src/machine-state.h | 3 +++ src/machine.cpp | 2 ++ src/remote-machine.cpp | 2 +- src/state-access.h | 4 ++++ src/tests/machine-test.lua | 35 ++++++++++++++++++++++++++---- src/tests/util.lua | 4 ++-- uarch/uarch-machine-state-access.h | 5 +++++ 20 files changed, 82 insertions(+), 22 deletions(-) diff --git a/lib/grpc-interfaces b/lib/grpc-interfaces index a92e0436a..d7b37bbe2 160000 --- a/lib/grpc-interfaces +++ b/lib/grpc-interfaces @@ -1 +1 @@ -Subproject commit a92e0436a644f8990ed1830bcfd33caed2deb63e +Subproject commit d7b37bbe2aeab60cf9ae0d90b238b56ce5705a39 diff --git a/src/clua-cartesi.cpp b/src/clua-cartesi.cpp index ffc9e6b2b..afbffa37b 100644 --- a/src/clua-cartesi.cpp +++ b/src/clua-cartesi.cpp @@ -120,6 +120,7 @@ CM_API int luaopen_cartesi(lua_State *L) { clua_setintegerfield(L, CM_BREAK_REASON_HALTED, "BREAK_REASON_HALTED", -1); clua_setintegerfield(L, CM_BREAK_REASON_YIELDED_MANUALLY, "BREAK_REASON_YIELDED_MANUALLY", -1); clua_setintegerfield(L, CM_BREAK_REASON_YIELDED_AUTOMATICALLY, "BREAK_REASON_YIELDED_AUTOMATICALLY", -1); + clua_setintegerfield(L, CM_BREAK_REASON_YIELDED_SOFTLY, "BREAK_REASON_YIELDED_SOFTLY", -1); clua_setintegerfield(L, CM_BREAK_REASON_REACHED_TARGET_MCYCLE, "BREAK_REASON_REACHED_TARGET_MCYCLE", -1); clua_setintegerfield(L, CM_UARCH_BREAK_REASON_REACHED_TARGET_CYCLE, "UARCH_BREAK_REASON_REACHED_TARGET_CYCLE", -1); clua_setintegerfield(L, CM_UARCH_BREAK_REASON_UARCH_HALTED, "UARCH_BREAK_REASON_UARCH_HALTED", -1); diff --git a/src/clua-machine-util.cpp b/src/clua-machine-util.cpp index 9a6033f07..460a0d1ba 100644 --- a/src/clua-machine-util.cpp +++ b/src/clua-machine-util.cpp @@ -1305,6 +1305,7 @@ cm_machine_runtime_config *clua_check_cm_machine_runtime_config(lua_State *L, in check_cm_htif_runtime_config(L, tabidx, &config->htif); config->skip_root_hash_check = opt_boolean_field(L, tabidx, "skip_root_hash_check"); config->skip_version_check = opt_boolean_field(L, tabidx, "skip_version_check"); + config->soft_yield = opt_boolean_field(L, tabidx, "soft_yield"); managed.release(); lua_pop(L, 1); return config; diff --git a/src/grpc-virtual-machine.cpp b/src/grpc-virtual-machine.cpp index ddae4f03d..02905576a 100644 --- a/src/grpc-virtual-machine.cpp +++ b/src/grpc-virtual-machine.cpp @@ -342,16 +342,7 @@ interpreter_break_reason grpc_virtual_machine::do_run(uint64_t mcycle_end) { RunResponse response; ClientContext context; check_status(m_stub->get_stub()->Run(&context, request, &response)); - if (response.iflags_h()) { - return interpreter_break_reason::halted; - } else if (response.iflags_y()) { - return interpreter_break_reason::yielded_manually; - } else if (response.iflags_x()) { - return interpreter_break_reason::yielded_automatically; - } else { - assert(response.mcycle() == mcycle_end); - return interpreter_break_reason::reached_target_mcycle; - } + return static_cast(response.break_reason()); } void grpc_virtual_machine::do_store(const std::string &dir) { diff --git a/src/i-state-access.h b/src/i-state-access.h index 99571cb3e..043569819 100644 --- a/src/i-state-access.h +++ b/src/i-state-access.h @@ -730,6 +730,11 @@ class i_state_access { // CRTP return derived().do_flush_tlb_vaddr(vaddr); } + /// \brief Returns true if soft yield HINT instruction is enabled at runtime + bool get_soft_yield() { + return derived().do_get_soft_yield(); + } + #ifdef DUMP_COUNTERS auto &get_statistics() { return derived().do_get_statistics(); diff --git a/src/interpret.cpp b/src/interpret.cpp index 1986edbc9..d17941b56 100644 --- a/src/interpret.cpp +++ b/src/interpret.cpp @@ -2979,6 +2979,11 @@ static FORCE_INLINE execute_status execute_SRLIW(STATE_ACCESS &a, uint64_t &pc, template static FORCE_INLINE execute_status execute_SRAIW(STATE_ACCESS &a, uint64_t &pc, uint32_t insn) { dump_insn(a, pc, insn, "sraiw"); + // When rd=0 the instruction is a HINT, and we consider it as a soft yield when rs1 == 31 + if (unlikely(insn_get_rd(insn) == 0 && insn_get_rs1(insn) == 31 && a.get_soft_yield())) { + // Force the main interpreter loop to break + return advance_to_next_insn(a, pc, execute_status::success_and_yield); + } return execute_arithmetic_immediate(a, pc, insn, [](uint64_t rs1, int32_t imm) -> uint64_t { const int32_t rs1w = static_cast(rs1) >> (imm & 0b11111); return static_cast(rs1w); @@ -5513,7 +5518,7 @@ static void assert_no_brk(STATE_ACCESS &a) { /// \brief Interpreter hot loop template -NO_INLINE void interpret_loop(STATE_ACCESS &a, uint64_t mcycle_end, uint64_t mcycle) { +static NO_INLINE execute_status interpret_loop(STATE_ACCESS &a, uint64_t mcycle_end, uint64_t mcycle) { // The interpret loop is constantly reading and modifying the pc and mcycle variables, // because of this care is taken to make them stack variables that are propagated across inline functions, // helping the C++ compiler optimize them into registers instead of stack variables when compiling, @@ -5583,7 +5588,7 @@ NO_INLINE void interpret_loop(STATE_ACCESS &a, uint64_t mcycle_end, uint64_t mcy a.write_pc(pc); a.write_mcycle(mcycle); // Got an interruption that must be handled externally - return; + return status; } } } @@ -5602,6 +5607,7 @@ NO_INLINE void interpret_loop(STATE_ACCESS &a, uint64_t mcycle_end, uint64_t mcy // Commit machine state a.write_pc(pc); a.write_mcycle(mcycle); + return execute_status::success; } template @@ -5631,7 +5637,7 @@ interpreter_break_reason interpret(STATE_ACCESS &a, uint64_t mcycle_end) { // Run the interpreter loop, // the loop is outlined in a dedicated function so the compiler can optimize it better - interpret_loop(a, mcycle_end, mcycle); + const execute_status status = interpret_loop(a, mcycle_end, mcycle); // Detect and return the reason for stopping the interpreter loop if (a.read_iflags_H()) { @@ -5640,6 +5646,8 @@ interpreter_break_reason interpret(STATE_ACCESS &a, uint64_t mcycle_end) { return interpreter_break_reason::yielded_manually; } else if (a.read_iflags_X()) { return interpreter_break_reason::yielded_automatically; + } else if (status == execute_status::success_and_yield) { + return interpreter_break_reason::yielded_softly; } else { // Reached mcycle_end assert(a.read_mcycle() == mcycle_end); // LCOV_EXCL_LINE return interpreter_break_reason::reached_target_mcycle; diff --git a/src/interpret.h b/src/interpret.h index 4397a625a..b9af6a90a 100644 --- a/src/interpret.h +++ b/src/interpret.h @@ -42,6 +42,7 @@ enum class interpreter_break_reason { halted, yielded_manually, yielded_automatically, + yielded_softly, reached_target_mcycle }; diff --git a/src/json-util.cpp b/src/json-util.cpp index 81639f857..e8c0cdb4d 100644 --- a/src/json-util.cpp +++ b/src/json-util.cpp @@ -192,7 +192,7 @@ interpreter_break_reason interpreter_break_reason_from_name(const std::string &n using ibr = interpreter_break_reason; const static std::unordered_map g_ibr_name = {{"failed", ibr::failed}, {"halted", ibr::halted}, {"yielded_manually", ibr::yielded_manually}, {"yielded_automatically", ibr::yielded_automatically}, - {"reached_target_mcycle", ibr::reached_target_mcycle}}; + {"yielded_softly", ibr::yielded_softly}, {"reached_target_mcycle", ibr::reached_target_mcycle}}; auto got = g_ibr_name.find(name); if (got == g_ibr_name.end()) { throw std::domain_error{"invalid interpreter break reason"}; @@ -436,6 +436,7 @@ void ju_get_opt_field(const nlohmann::json &j, const K &key, machine_runtime_con ju_get_field(j[key], "htif"s, value.htif, path + to_string(key) + "/"); ju_get_opt_field(j[key], "skip_root_hash_check"s, value.skip_root_hash_check, path + to_string(key) + "/"); ju_get_opt_field(j[key], "skip_version_check"s, value.skip_version_check, path + to_string(key) + "/"); + ju_get_opt_field(j[key], "soft_yield"s, value.soft_yield, path + to_string(key) + "/"); } template void ju_get_opt_field(const nlohmann::json &j, const uint64_t &key, machine_runtime_config &value, @@ -1334,6 +1335,7 @@ void to_json(nlohmann::json &j, const machine_runtime_config &runtime) { {"htif", runtime.htif}, {"skip_root_hash_check", runtime.skip_root_hash_check}, {"skip_version_check", runtime.skip_version_check}, + {"soft_yield", runtime.soft_yield}, }; } diff --git a/src/jsonrpc-discover.json b/src/jsonrpc-discover.json index 3235dcf73..d7c5b6893 100644 --- a/src/jsonrpc-discover.json +++ b/src/jsonrpc-discover.json @@ -1195,6 +1195,7 @@ "halted", "yielded_manually", "yielded_automatically", + "yielded_softly", "reached_target_mcycle" ] }, @@ -1787,6 +1788,9 @@ }, "skip_version_check": { "type": "boolean" + }, + "soft_yield": { + "type": "boolean" } } }, diff --git a/src/jsonrpc-remote-machine.cpp b/src/jsonrpc-remote-machine.cpp index 320f9119a..398cd2461 100644 --- a/src/jsonrpc-remote-machine.cpp +++ b/src/jsonrpc-remote-machine.cpp @@ -756,6 +756,8 @@ static std::string interpreter_break_reason_name(cartesi::interpreter_break_reas return "yielded_manually"; case R::yielded_automatically: return "yielded_automatically"; + case R::yielded_softly: + return "yielded_softly"; case R::reached_target_mcycle: return "reached_target_mcycle"; } diff --git a/src/machine-c-api.cpp b/src/machine-c-api.cpp index 1ddab7ab0..896ad9095 100644 --- a/src/machine-c-api.cpp +++ b/src/machine-c-api.cpp @@ -368,6 +368,7 @@ cartesi::machine_runtime_config convert_from_c(const cm_machine_runtime_config * new_cpp_machine_runtime_config.htif = cartesi::htif_runtime_config{c_config->htif.no_console_putchar}; new_cpp_machine_runtime_config.skip_root_hash_check = c_config->skip_root_hash_check; new_cpp_machine_runtime_config.skip_version_check = c_config->skip_version_check; + new_cpp_machine_runtime_config.soft_yield = c_config->soft_yield; return new_cpp_machine_runtime_config; } diff --git a/src/machine-c-api.h b/src/machine-c-api.h index 77f3e2f48..d9174da7a 100644 --- a/src/machine-c-api.h +++ b/src/machine-c-api.h @@ -94,6 +94,7 @@ typedef enum { // NOLINT(modernize-use-using) CM_BREAK_REASON_HALTED, CM_BREAK_REASON_YIELDED_MANUALLY, CM_BREAK_REASON_YIELDED_AUTOMATICALLY, + CM_BREAK_REASON_YIELDED_SOFTLY, CM_BREAK_REASON_REACHED_TARGET_MCYCLE } CM_BREAK_REASON; @@ -367,6 +368,7 @@ typedef struct { // NOLINT(modernize-use-using) cm_htif_runtime_config htif; bool skip_root_hash_check; bool skip_version_check; + bool soft_yield; } cm_machine_runtime_config; /// \brief Machine instance handle diff --git a/src/machine-runtime-config.h b/src/machine-runtime-config.h index 9cc20307c..a98013865 100644 --- a/src/machine-runtime-config.h +++ b/src/machine-runtime-config.h @@ -38,6 +38,7 @@ struct machine_runtime_config { htif_runtime_config htif{}; bool skip_root_hash_check{}; bool skip_version_check{}; + bool soft_yield{}; }; /// \brief CONCURRENCY constants diff --git a/src/machine-state.h b/src/machine-state.h index c042a6ee1..f7135a74f 100644 --- a/src/machine-state.h +++ b/src/machine-state.h @@ -115,6 +115,9 @@ struct machine_state { uint64_t iyield; ///< CSR iyield. } htif; + /// Soft yield + bool soft_yield; + /// Map of physical memory ranges boost::container::static_vector pmas; diff --git a/src/machine.cpp b/src/machine.cpp index 7ecafc055..d17ffd223 100644 --- a/src/machine.cpp +++ b/src/machine.cpp @@ -307,6 +307,8 @@ machine::machine(const machine_config &c, const machine_runtime_config &r) : throw std::invalid_argument{"mimpid mismatch, emulator version is incompatible"}; } + m_s.soft_yield = r.soft_yield; + // General purpose registers for (int i = 1; i < X_REG_COUNT; i++) { write_x(i, m_c.processor.x[i]); diff --git a/src/remote-machine.cpp b/src/remote-machine.cpp index 8760c2327..dfe5d83ef 100644 --- a/src/remote-machine.cpp +++ b/src/remote-machine.cpp @@ -395,7 +395,7 @@ class handler_Run final : public handler { } auto limit = static_cast(req->limit()); RunResponse resp; - hctx.m->run(limit); + resp.set_break_reason(static_cast(hctx.m->run(limit))); resp.set_mcycle(hctx.m->read_mcycle()); resp.set_tohost(hctx.m->read_htif_tohost()); resp.set_iflags_h(hctx.m->read_iflags_H()); diff --git a/src/state-access.h b/src/state-access.h index e0bcdec02..5bca2ade8 100644 --- a/src/state-access.h +++ b/src/state-access.h @@ -600,6 +600,10 @@ class state_access : public i_state_access { do_flush_tlb_type(); } + bool do_get_soft_yield() { + return m_m.get_state().soft_yield; + } + #ifdef DUMP_COUNTERS machine_statistics &do_get_statistics() { return m_m.get_state().stats; diff --git a/src/tests/machine-test.lua b/src/tests/machine-test.lua index 4a484c73f..b9cf674ee 100755 --- a/src/tests/machine-test.lua +++ b/src/tests/machine-test.lua @@ -148,11 +148,11 @@ local function connect() end local remote -local function build_machine(type, config) +local function build_machine(type, config, runtime_config) config = config or { ram = { length = 1 << 20 }, } - local runtime = { + runtime_config = runtime_config or { concurrency = { update_merkle_tree = 0, }, @@ -160,9 +160,9 @@ local function build_machine(type, config) local new_machine if type ~= "local" then if not remote then remote = connect() end - new_machine = assert(remote.machine(config, runtime)) + new_machine = assert(remote.machine(config, runtime_config)) else - new_machine = assert(cartesi.machine(config, runtime)) + new_machine = assert(cartesi.machine(config, runtime_config)) end return new_machine end @@ -283,6 +283,33 @@ do_test("mcycle and root hash should match", function(machine) assert(root_hash == calculated_end_hash, "machine hash does not match after on end cycle") end) +if machine_type == "local" then + print("\n\ntesting soft yield") + test_util.make_do_test(build_machine, machine_type, { + ram = { length = 1 << 20 }, + }, { + soft_yield = true, + })("check soft yield", function(machine) + -- The following is a RISC-V bytecode that cause a soft yield immediately, + local function sraiw(rd, rs1, shamt) return 0x4000501b | (rd << 7) | (rs1 << 15) | (shamt << 20) end + local soft_yield_insn = sraiw(0, 31, 7) + + machine:write_memory(machine:read_pc(), string.pack(" = build_machine(type, config) + local machine = build_machine(type, config, runtime_config) f(machine) print("<<<<<<<<<<<<<<<< passed >>>>>>>>>>>>>>>") end diff --git a/uarch/uarch-machine-state-access.h b/uarch/uarch-machine-state-access.h index da33e7083..e7e0c467d 100644 --- a/uarch/uarch-machine-state-access.h +++ b/uarch/uarch-machine-state-access.h @@ -722,6 +722,11 @@ class uarch_machine_state_access : public i_state_access(); do_flush_tlb_type(); } + + bool do_get_soft_yield() { + // Soft yield is meaningless in microarchitecture + return false; + } }; } // namespace cartesi