IdString refcounting adds overhead and code bloat, and would be even more overhead if we try to make it make safe for multithreading, so it would be good to remove most or all of it.
Instead of refcounting, we could try keeping a global list of all Designs and make the opt_clean pass GC dead IdStrings. We would scan all Designs to identify live IdStrings, also treat all IdStrings generated by the ID() macro and ID:: constants as always live, and delete all the dead ones.
That runs into problem with compatibility with third-party plugins or API users keeping IdStrings in their own data structures, separate from any Design. If only ID() and ID:: constants appear there, that’s fine, but in general that might not be true. We could make more constructors such as IdString(const char *) create immortal IdStrings by default, but that doesn’t help if third-party code grabs arbitrary IdStrings out of a design and stores them somewhere. That seems like a sticking point for this approach and the similar approach jix suggested.
Fow now I’ll assume we have to keep compatible with that kind of behavior. Then I think we have to continue with IdString refcounting and reduce refcount churn in other ways. An easy way to do that is to convert IdStrings into const IdString & in many places. Additionally we could introduce a new type for non-owned IdStrings, say IdRef, that doesn’t require an underlying IdString to refer to, and proclaim that third-party code shall not store IdRefs between passes. We would not treat strings with a zero refcount as a cue to delete the string immediately. Instead we would use the opt_clean GC approach, but keeping all strings with nonzero IdString refcounts alive. Then we could change (probably the vast majority) of IdStrings in Yosys to IdRef. This would include new APIs for looking up and creating new IDs that return IdRef. We could keep RTLIL cell names etc as refcounted IdStrings so we don’t have to trace them explicitly.
How does that sound?