From cba49642aa42959cb4448f74f7816c723c6f5378 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Wed, 28 Jan 2026 19:14:09 +0000 Subject: [PATCH] Make `keep_cache_t` process all modules up-front instead of on-demand We will want to query `keep_cache` from parallel threads. If we compute the results on-demand, that means we need synchronization for cache access in those queries, which adds complexity and overhead. Instead, prefill the cache with the status of all relevant modules. Note that this doesn't actually do more work --- we always consult `keep_cache` for all cells of all selected modules, so scanning all those cells and determining the kept status of all dependency modules is always required. Later in this PR we're going to parallelize `scan_module` itself, and that's also much easier to do when no other parallel threads are running. --- passes/opt/opt_clean.cc | 162 +++++++++++++++++++++++++--------------- 1 file changed, 103 insertions(+), 59 deletions(-) diff --git a/passes/opt/opt_clean.cc b/passes/opt/opt_clean.cc index f1d21435c..a8e2edd16 100644 --- a/passes/opt/opt_clean.cc +++ b/passes/opt/opt_clean.cc @@ -33,47 +33,95 @@ using RTLIL::id2cstr; struct keep_cache_t { - Design *design; - dict cache; - bool purge_mode = false; + dict keep_modules; + bool purge_mode; - void reset(Design *design = nullptr, bool purge_mode = false) - { - this->design = design; - this->purge_mode = purge_mode; - cache.clear(); - } - - bool query(Module *module) - { - log_assert(design != nullptr); - - if (module == nullptr) - return false; - - if (cache.count(module)) - return cache.at(module); - - cache[module] = true; - if (!module->get_bool_attribute(ID::keep)) { - bool found_keep = false; - for (auto cell : module->cells()) - if (query(cell, true /* ignore_specify */)) { - found_keep = true; - break; - } - for (auto wire : module->wires()) - if (wire->get_bool_attribute(ID::keep)) { - found_keep = true; - break; - } - cache[module] = found_keep; + keep_cache_t(bool purge_mode, const std::vector &selected_modules) + : purge_mode(purge_mode) { + std::vector scan_modules_worklist; + dict> dependents; + std::vector propagate_kept_modules_worklist; + for (RTLIL::Module *module : selected_modules) { + if (keep_modules.count(module)) + continue; + bool keep = scan_module(module, dependents, true, scan_modules_worklist); + keep_modules[module] = keep; + if (keep) + propagate_kept_modules_worklist.push_back(module); } - return cache[module]; + while (!scan_modules_worklist.empty()) { + RTLIL::Module *module = scan_modules_worklist.back(); + scan_modules_worklist.pop_back(); + if (keep_modules.count(module)) + continue; + bool keep = scan_module(module, dependents, false, scan_modules_worklist); + keep_modules[module] = keep; + if (keep) + propagate_kept_modules_worklist.push_back(module); + } + + while (!propagate_kept_modules_worklist.empty()) { + RTLIL::Module *module = propagate_kept_modules_worklist.back(); + propagate_kept_modules_worklist.pop_back(); + for (RTLIL::Module *dependent : dependents[module]) { + if (keep_modules[dependent]) + continue; + keep_modules[dependent] = true; + propagate_kept_modules_worklist.push_back(dependent); + } + } } - bool query(Cell *cell, bool ignore_specify = false) + bool query(Cell *cell) const + { + if (keep_cell(cell, purge_mode)) + return true; + if (cell->type.in(ID($specify2), ID($specify3), ID($specrule))) + return true; + if (cell->module && cell->module->design) { + RTLIL::Module *cell_module = cell->module->design->module(cell->type); + return cell_module != nullptr && keep_modules.at(cell_module); + } + return false; + } + +private: + bool scan_module(Module *module, dict> &dependents, + bool scan_all_cells, std::vector &worklist) const + { + bool keep = false; + if (module->get_bool_attribute(ID::keep)) { + if (!scan_all_cells) + return true; + keep = true; + } + + for (Cell *cell : module->cells()) { + if (keep_cell(cell, purge_mode)) { + if (!scan_all_cells) + return true; + keep = true; + } + if (module->design) { + RTLIL::Module *cell_module = module->design->module(cell->type); + if (cell_module != nullptr) { + dependents[cell_module].push_back(module); + worklist.push_back(cell_module); + } + } + } + if (!scan_all_cells && keep) + return true; + for (Wire *wire : module->wires()) { + if (wire->get_bool_attribute(ID::keep)) { + return true; + } + } + return keep; + } + + static bool keep_cell(Cell *cell, bool purge_mode) { if (cell->type.in(ID($assert), ID($assume), ID($live), ID($fair), ID($cover))) return true; @@ -81,9 +129,6 @@ struct keep_cache_t if (cell->type.in(ID($overwrite_tag))) return true; - if (!ignore_specify && cell->type.in(ID($specify2), ID($specify3), ID($specrule))) - return true; - if (cell->type == ID($print) || cell->type == ID($check)) return true; @@ -92,19 +137,14 @@ struct keep_cache_t if (!purge_mode && cell->type == ID($scopeinfo)) return true; - - if (cell->module && cell->module->design) - return query(cell->module->design->module(cell->type)); - return false; } }; -keep_cache_t keep_cache; CellTypes ct_reg, ct_all; int count_rm_cells, count_rm_wires; -void rmunused_module_cells(Module *module, bool verbose) +void rmunused_module_cells(Module *module, bool verbose, keep_cache_t &keep_cache) { SigMap sigmap(module); dict> mem2cells; @@ -595,7 +635,7 @@ bool rmunused_module_init(RTLIL::Module *module, bool verbose) return did_something; } -void rmunused_module(RTLIL::Module *module, bool purge_mode, bool verbose, bool rminit) +void rmunused_module(RTLIL::Module *module, bool purge_mode, bool verbose, bool rminit, keep_cache_t &keep_cache) { if (verbose) log("Finding unused cells or wires in module %s..\n", module->name); @@ -652,7 +692,7 @@ void rmunused_module(RTLIL::Module *module, bool purge_mode, bool verbose, bool if (!delcells.empty()) module->design->scratchpad_set_bool("opt.did_something", true); - rmunused_module_cells(module, verbose); + rmunused_module_cells(module, verbose, keep_cache); while (rmunused_module_signals(module, purge_mode, verbose)) { } if (rminit && rmunused_module_init(module, verbose)) @@ -695,7 +735,12 @@ struct OptCleanPass : public Pass { } extra_args(args, argidx, design); - keep_cache.reset(design, purge_mode); + std::vector selected_modules; + for (auto module : design->selected_whole_modules_warn()) { + if (!module->has_processes_warn()) + selected_modules.push_back(module); + } + keep_cache_t keep_cache(purge_mode, selected_modules); ct_reg.setup_internals_mem(); ct_reg.setup_internals_anyinit(); @@ -706,10 +751,8 @@ struct OptCleanPass : public Pass { count_rm_cells = 0; count_rm_wires = 0; - for (auto module : design->selected_whole_modules_warn()) { - if (module->has_processes_warn()) - continue; - rmunused_module(module, purge_mode, true, true); + for (auto module : selected_modules) { + rmunused_module(module, purge_mode, true, true, keep_cache); } if (count_rm_cells > 0 || count_rm_wires > 0) @@ -718,7 +761,6 @@ struct OptCleanPass : public Pass { design->optimize(); design->check(); - keep_cache.reset(); ct_reg.clear(); ct_all.clear(); log_pop(); @@ -758,7 +800,12 @@ struct CleanPass : public Pass { } extra_args(args, argidx, design); - keep_cache.reset(design); + std::vector selected_modules; + for (auto module : design->selected_unboxed_whole_modules()) { + if (!module->has_processes()) + selected_modules.push_back(module); + } + keep_cache_t keep_cache(purge_mode, selected_modules); ct_reg.setup_internals_mem(); ct_reg.setup_internals_anyinit(); @@ -769,10 +816,8 @@ struct CleanPass : public Pass { count_rm_cells = 0; count_rm_wires = 0; - for (auto module : design->selected_unboxed_whole_modules()) { - if (module->has_processes()) - continue; - rmunused_module(module, purge_mode, ys_debug(), true); + for (auto module : selected_modules) { + rmunused_module(module, purge_mode, ys_debug(), true, keep_cache); } log_suppressed(); @@ -782,7 +827,6 @@ struct CleanPass : public Pass { design->optimize(); design->check(); - keep_cache.reset(); ct_reg.clear(); ct_all.clear();