Add three more unit tests that mirror the previous "with_alloca"
variants but where the store of the extra argument %val into the
pre-existing alloca happens BEFORE the call instead of after it. The
expected number of NEW allocas after the pass is the same as in the
corresponding store-after-call variant: the position of the store
relative to the call does not change the picker's decision.
Add three more unit tests, one for each of the three call patterns
introduced in the previous commit. Each test takes an extra i64
argument %val, has an alloca pre-existing in the entry block, stores
%val into the alloca right after the call, and at least one branch
returns by loading from the alloca.
The pre-existing alloca/store/load pattern does not write to memory
the call could read (the alloca is local and the call's pointer
argument is unrelated) and the call cannot write to the alloca, so
the picker should reach the same conclusion as in the simpler
counterpart tests: the variant of @call_rw_one_use generates no new
alloca, @call_rw_two_uses generates one new alloca alongside the
pre-existing one, and @call_ro_two_uses generates none.
Cover three representative cases for how the picker should treat
function calls in non-legacy mode:
- A call to a read+write function with a single use (an icmp that
feeds a conditional branch). The call has only one use, so the
picker does not pick it and no new alloca is created.
- The same shape, but with two uses of the call (the icmp and a
return). Because the function may read+write memory and the call
has more than one use, the picker picks the call: a new alloca is
created at the top of entry, the result of the call is stored into
it, and every user of the call uses a load from the alloca.
- The same shape as the second case but with a function that only
reads memory (and is also nounwind willreturn so that
Instruction::mayHaveSideEffects returns false). The picker does
not pick the call and no new alloca is created.
Make cannotBePointer recurse through an Add when both of its
operands cannot be pointers. This lets canDisambiguatePointerOperand
identify the only viable base pointer of an outer Add even when the
offset side is itself an Add (or a deeper tree of Adds) whose leaves
are all clearly-non-pointer values, e.g.
%offset = add i64 (%a*%b), (%c*%d)
%result = add i64 %arg, %offset
Two unit tests cover the simple two-mul case and a deeper
four-mul binary-tree variant.
Cover several scenarios where a single base pointer is reachable
only after the discovery walk in ArithmeticToGEPPass propagates
through many layers of Add instructions, each of which has
ambiguous pointer operands and must be disambiguated separately:
- A chain of 8 chained adds with a varied set of clearly-non-pointer
offset opcodes (mul, ashr, shl, and, or, xor, sdiv, srem), with
discovery seeded from the !revng.pointers metadata on the return.
- A chain seeded from a pointer argument (forward propagation).
- A chain feeding the integer argument of a call whose argument is
a pointer (backwards discovery from the call site).
- A chain where the pointer side alternates between operand 0 and
operand 1 of each add.
- A chain whose offset side is hidden behind freeze instructions,
so the cannotBePointer recursion through transparent casts is
exercised.
- A chain where every add reuses the same multiplication as offset,
exercising independent disambiguation per-add over a shared
sub-expression.
- A chain interrupted by ptrtoint/inttoptr round-trips between adds.
Introduce the ArithmeticToGEP canonicalisation pass. It detects
pointers (LLVM-typed pointer values, model pointers flagged by the
"revng.pointers" metadata, and pointer-tagged extract values) and
rewrites integer arithmetic computed on top of them as i8 GEPs,
followed by a ptrtoint when the original use needs an integer.
For each obvious pointer the pass also walks backwards through Add
chains and disambiguates the pointer operand of each Add when the
other operand cannot be a pointer, so the rewrite extends to integer
arithmetic that only later flows into a pointer use.
Add an llvm-lit test suite for the new-PassManager
SwitchToStatements pass, exercised via
`revng opt -passes=switch-to-statements-test` (registered by the
pass's registerCallbacks hook). The tests cover load/store
serialisation, side-effecting calls and a few representative
patterns where an instruction must be picked for serialisation
because its memory read is no longer available at one of its uses.
Implement a DDR rewriting for folding the `x[0]` into `*x`, in order
to avoid a useless verbose syntax. Due to concatenation of rewrites, we
will get `&(x[0])` transformed into `x`, which is the rewriting we are
aiming to.
Overhaul the pipeline configuration logic by collapsing all dynamic
configuration options, both for analyses and pipes into a single
dictionary. Change all the interfaces so that there is no longer
distinction between the configuration of an analysis and of pipes.
Expose these options to the command line via `--{name}-configuration`
options for each pipe/analysis that is applicable to the command-line
invocation.
Value types is the set of non-array object types. These types are
assignable, can be used as function parameters, and as return types
(along with void), and in other scenarios where the type must have usual
value-like behaviour with no weirdness (such as array decay).
* PrimitiveType is now an interface.
* VoidKind is now VoidType.
* FloatKind is now FloatType.
* The integer kinds are now IntegerType with IntegerKind.
* DecayOp - array or function to pointer decay. Not shown in C.
* BitCastOp - bit-preserving conversion. Emitted as bit_cast if
necessary.
* ExtendOp - zero- or sign-extending conversion from any integer type to
any wider integer type.
* TruncateOp - truncating conversion from any integer type to any
narrower integer type.
* PtrResizeOp - resizes a pointer without changing its pointee type.
Currently there is only a single default target used for all C emission.
However, the emitted code is actually compiled using multiple
implementations (32-bit and 64-bit targets), some of which use a 32-bit
wide long. This causes integer literals to be emitted with wrong sizes
on those targets. This change forces all 64-bit literals to be emitted
using long long, which is 64 bits wide in all relevant implementations.