In our reaching definition analysis we used to consider all the loads
not reached by any store as definitions. However we forgot to actually
register them as such, with the result that two consecutive loads from
the same CSV would end up being two free loads.
In `ConditionNumberingPass` we used to consider as resetting the last
basic block possibly interested in a certain numbered
condition. However, what we really meant, was that its successors were
resetting basic blocks. This commit fixes this issue.
* Introduce some additional helpers
* Spread some `const`ness
* Improve documentation
* New debugging information: "osr-bv". Prints every update operation
performed in `BVMap::update`.
* Remove dead code
* Whitespace fixes
* Some new TODOs
* Fix some typos in comments
In OSRA we used to track `GlobalVariable`s and `AllocaInst` only,
despite the `MemoryAccess` infrastructure supported memory accesses of
the type register + constant. Enabling this, we're able to handle the
following x86-64 snippet found in the `omnetpp` SPEC benchmark:
cmp DWORD PTR [rbx+0x8],0x5
ja elsewhere
mov eax,DWORD PTR [rbx+0x8]
This commit fixes a bug that appears only with debug builds of LLVM: in
RDA we were erasing a temporary common predecessor basic block before
removing the references to it in a `switch` statement.
This commit reduces the amount of constraint we propagate. We do this in
two ways. First, by computing the set of all the instruction that will
ever be affected by the current instruction (recursively). Second, by
preventing propagation on constraints across function calls.
In quick test on `ls` compiled for MIPS we reduce the execution time by
55% of the peak memory usage by 68%. This makes me quite happy.
This commit introduces two new passes:
* `GeneratedCodeBasicInfo`: recovers from the IR some basic information
like the size of delay slots in the input architecture, the name of
the program counter and so on. It can also identify the type of a
basic block (e.g., dispatcher, jump target...). *
* `FunctionCallIdentification`: identifies function calls and injects a
marker before the associated terminator instruction.
The idea of these two passes is to try to progressively move information
we used to keep in `JumpTargetManager` into the IR, so that it is more
easily accessible and passes do not need a reference to `JTM`.
In particular by having markers for function calls available during jump
target discovery we don't have to have duplicated and suboptimal
implementation of `isCall`.
This commit also introduce some additional helper functions and an
helper class to quickly.
This commit reworks quite heavily the `ConditionNumberingPass` and the
`ConditionalReachingDefinitionPass`.
* Increase debugging information for both passes.
* `ConditionNumberingPass`: rename the concept basic blocks "defining" a
condition to the concept of basic blocks "resetting" a condition.
* `ConditionNumberingPass`: add to the list of basic blocks resetting a
condition also the basic block post-dominating all the branches
associated to that condition. In this way,
`ConditionalReachingDefinitionPass` will not propagate the condition
after them.
* `ConditionalBasicBlockInfo::mergeDefinition`: merge policy for
condition bits is now simply or-merging them.
This commit introduces the following feature in
`ConditionalReachingDefintionPass`:
* Propagation of conditions associated to branch instructions across
basic blocks, unless a basic block defines them or the condition is
being propagated by theh current branch.
* Before propagating a definition, sopress in it all the conditions
defined in the target basic block.
* Before propagating a definition, force the condition being propagated
by the current branch to be active in it and its opposite do be
inactive.
`ConditionNumberingPass` now also keeps track of which basic blocks
define the value of a certain condition. This helps the conditional
reaching definition analysis to reset all the definitions associated to
that condition when they are propagated to a basic block which defines
that condition.
This commit handles two cases related to function calls where we want to
limit the propagation of reaching definitions. In the first case down
through function calls, in the second case back up through return
instructions.
For the call instructions, we choose to stop the propagation of reaching
definitions to the callee, since we assume each function should check
its arguments if they affect the control-flow. In particular, this
allows a larger coverage of the function body in case, being able to
enumerate all the calls, we consider dead code those parts that, in the
current program would nevere be executed. Right now we do it in all
cases, it would be more appropriate to do this only if the address of
the function is taken. Also, we should expand this also for tail calls.
For what concerns return instructions, a function called from a lot of
different locations in the code receives a huge number of reaching
definitions. If its close to a no-op, it will also propagate most of
them through the return path. This is an hack to limit how such
definitions spread around the code.
A proper solution, requires to detect the calling convention and allow
to propagate along return paths only return values.
This commit introduces the `noreturn` analysis, whose aim is to detect
all the basic blocks the are doomed to lead to a `noreturn` syscall such
as `execve` or `exit`.
* Implement `NoreturnAnalysis`.
* Include and initialize in the `Architecture` data structure all the
necessary information to detect `noreturn` syscalls. Specifically, the
name of the QEMU helper for syscalls, the name of the register holding
the syscall number and the syscall numbers representing `noreturn`
syscalls.
* `ReachingDefinitionsPass`: make reaching definitions available both in
reaching definitions mode and reached loads mode. This part needs
further cleanup. We also might be willing to implement this with a
`Boost.Bimap`.
* Use `SET` to collect information useful for the
`NoreturnAnalysis`. Also restructure how the `OperationsStack` works
to be more streamlined and keep track of multiple information about
the instruction currently being tracked.
* Clear all the data that's not part of the analysis results at the end
of the `runOnFunction` method
* Clear all the data that's part of the analysis results when the
`PassManager` tells us so (`Pass::releaseMemory`)
* Do not use the `clear()` method, since it doesn't release memory
* Add some debugging information
Record amount of reaching definitions for each load, even in
`ReachedLoads` mode, so that we can run the `pathSensitiveMerge` only
when we're sure we've collected all of them.
In `ConditionalReachingDefinitionsPass` switch from a `std::vector` of
pairs to an `std::unordered_map` of `llvm::SmallVector`. This is a
non-negligible impact on performances.
Three new passes have been introduced:
* `ReachingDefinitionsPass`: classical reaching definitions analysis
working on load/stores with the main difference that a load without a
definition behaves similarly to a definition and that we ignore
certain basic blocks (i.e., the dispatcher).
* `ConditionNumberingPass`: goes through all the branch instructions to
check if some of them use an equivalent condition, this is
particularly useful to understand that consecutive ARM instructions
using the same predicate are working on the same condition.
* `ConditionalReachingDefinitionsPass`: identical to
`ReachingDefinitionsPass` but uses information from
`ConditionNumberingPass` to stop certain definitions from reaching
certain loads.
The first and the last analyses have `Reached*` variants which expose
information from the point of view of the definintion instead of from
the point of view of the load.