-
Notifications
You must be signed in to change notification settings - Fork 284
Add analysis cache #3204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add analysis cache #3204
Conversation
object_cachet is a cache for heavy-to-initalize data structures, which we use to share such objects across passes. The class_hierarchy is one instance that benefits from being created once then updating if necessary.
And also the move constructors/operators which didn't have the default implementations.
No functional changes here, just fixing a missing end-of-file newline.
By using the analysis cache we can avoid recomputing it unnecessarily, which can be very expensive for some symbol tables.
By using the analysis cache we can avoid recomputing it unnecessarily, which can be very expensive for some symbol tables.
…xceptions and remove_instanceof.
| rem.lower_instanceof(function.body); | ||
| } | ||
| symbol_table_baset &symbol_table = goto_model_function.get_symbol_table(); | ||
| const class_hierarchyt &class_hierarchy( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unusual to initialise a reference using brackets rather than = (repeated below)
| const goto_functionst &goto_functions) | ||
| : symbol_table(symbol_table), | ||
| goto_functions(goto_functions), | ||
| analysis_cache() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary change - suggest you leave the default construction implicit.
| goto_modelt(goto_modelt &&other): | ||
| symbol_table(std::move(other.symbol_table)), | ||
| goto_functions(std::move(other.goto_functions)) | ||
| goto_modelt(goto_modelt &&other) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps just replace with = default (same for move assignment)
| template <typename cached_type, typename... arguments> | ||
| cached_type &get_or_create(arguments &&... values) | ||
| { | ||
| // We're key'd off the current templates type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"The key for this object is the current template's type"
| cached_type &get_or_create(arguments &&... values) | ||
| { | ||
| // We're key'd off the current templates type. | ||
| const class_idt current_type_id = &cached_type_idt<cached_type>::id; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: class_id would match type name better than current_type_id (also in erase below).
|
I have read through the code and I don't think there is enough information for me to judge whether what is being done here is the most appropriate approach, mainly because I don't know what problem is being solved. I'll thus leave this to others who may have more context. |
|
@tautschnig with a large symbol table (specifically lots of class types) it can be expensive to repeatedly recreate |
|
@smowton Many thanks for the additional info. I would like to leave it to others to make the call on this, but from my very limited point of view the proposed changes create a maintenance problem: there is no data or regression test included, and the design decision to create this very general store seems somewhat arbitrary. If anyone is to revisit this bit of code in future, how would they know they aren't breaking anything? Specifically, to me the new member of |
|
I don't mind if it's a member of goto-model or an additional parameter to passes, but my reason for a general store is to avoid having to alter every driver program in order to share an analysis. For example, compare: Whereas with a generic store there's no need to alter the driver program: In short it avoids introducing spaghetti, duplicated per driver program, into the |
|
(NB. obviously fully developed this concept would include analyses that use other analyses, like reaching-definitions + dependency-graph for example) |
We might be touching upon matters of taste here when I say that I find precisely defined interfaces a valuable source of information? Taking it to the extreme, I'd feel tempted to rename
It surely does, but then I might question whether that's the right place? I can see that specifically the class hierarchy would be something that could be maintained/cached together with the other parts that make up a Having said the above, a more serious concern arises: consistency between the members of a |
|
So the LLVM approach is for transform passes to nominate analyses that they promise they don't perturb -- if I remember rightly the syntax is something like |
|
In any case it sounds like you're in favour of explicit dependencies at the pass interface level, so shall we just do that? Unless @kroening or @peterschrammel want to give an opinion about how you'd like to see analysis sharing done? |
|
Yes, I am in favour of explicit dependencies. But that could well just be me. |
|
@tautschnig it's not just you. I very much see (and like) the use case; it makes no sense to keep recomputing these things. One of the things that I'm deliberately doing in ASVAT, is creating analysis result objects (with const goto_modelt &'s) to avoid this. However, I think I'm with Michael w.r.t. where this should go, we might as well s/goto_modelt/worldt/ which is really the same as making everything global. I think explicit results objects and keeping things const-clean is the way forward. Given that we don't yet have a good handle on "normal forms" of goto-programs and structural invariants I worry that explict annotations of which transforms preserve which analysis is over-ambitious. I see that there is a bit of a difference between us and LLVM here. While they are very much a program transformation pipeline and the whole point is to change, modify and transform the program, our tools tend to follow a "transform out the hard stuff, instrument, then have the program very fixed while we do the actual analysis" pattern. |
|
@tautschnig @martin-cs @NathanJPhillips please see #3216 for a simpler alternative to this PR. |
|
Replaced by #3216 |
This adds an analysis cache, which is styled after the LLVM pass manager, and is intended to allow passes (such as goto-convert, remove-returns, etc) to create and share analyses they use on demand.
The cache is owned by goto_modelt (and borrowed by goto_model_functiont for function-level passes). Intended use: a cache that uses analysis
my_analysistmight callgoto_model.get_analysis_cache().get_or_create<my_analysist>(constructor_args...). This creates the analysis on first usage, or returns an existing one if present. Analyses can be destroyed if a pass invalidates one usingobject_cachet::erase.Analyses are currently assumed to be module-global, but we could extend this with per-function analyses in future. Only one analysis can be stored per type in order to avoid brittle string-typed keys or similar.
The real work here is done by @JohnDumbell, I've just cleaned it up a little to get it in front of reviewers while he's on holiday.
My commit message includes data points confirming performance improvements (if claimed).