Skip to content

Conversation

@dave-bartolomeo
Copy link
Contributor

@dave-bartolomeo dave-bartolomeo commented Dec 10, 2018

Note: This PR depends on PR #597. Once that PR is merged, most of the commits in this PR will disappear. I suggest reviewing only the most recent commits in this PR, starting with bcf5cac.

This change removes any IR instructions that can be statically proven unreachable. To detect unreachable IR, we first run a simple constant value analysis on the IR. Then, any ConditionalBranch with a constant condition has the appropriate edge marked as "infeasible". We define a class ReachableBlock as any IRBlock with a path from the entry block of the function. SSA construction has been modified to operate only on ReachableBlock and ReachableInstruction, which ensures that only reachable IR gets translated into SSA form. For any infeasible edge where its predecessor block is reachable, we replace the original target of the branch with an Unreached instruction, which lets us preserve the invariant that all ConditionalBranch instructions have both a true and a false edge, and allows guard inference to still work.

The changes to SSAConstruction.qll are not as scary as they look. They are almost entirely a mechanical replacement of OldIR::IRBlock with OldBlock, which is just an alias for ReachableBlock.

Note that the constant_func.ql test can determine that the two new test functions always return 0.

Removing unreachable code helps get rid of some common FPs in IR-based dataflow analysis, especially for constructs like while(true).

The AST dataflow library essentially ignores conversions, which is probably the right behavior. Converting an `int` to a `long` preserves the value, even if the bit pattern might be different. It's arguable whether narrowing conversions should be treated as dataflow, but we'll do so for now. We can revisit that if we see it cause problems.
This fixes a subtle bug in the construction of aliased SSA. `getResultMemoryAccess` was failing to return a `MemoryAccess` for a store to a variable whose address escaped. This is because no `VirtualIRVariable` was being created for such variables. The code was assuming that any access to such a variable would be via `UnknownMemoryAccess`. The result is that accesses to such variables were not being modeled in SSA at all.

Instead, the way to handle this is to have a `VariableMemoryAccess` even when the variable being accessed has escaped, and to have `VariableMemoryAccess::getVirtualVariable()` return the `UnknownVirtualVariable` for escaped variables. In the future, this will also let us be less conservative about inserting `Chi` nodes, because we'll be able to determine that there's an exact overlap between two accesses to the same escaped variable in some cases.
This commit adds a new model interface that describes the known side effects (or lack thereof) of a library function. Does it read memory, does it write memory, and do any of its parameters escape? Initially, we have models for just two Standard Library functions: `std::move` and `std::forward`, which neither read nor write memory, and do not escape their parameter.

IR construction has been updated to insert the correct side effect instruction (or no side effect instruction) based on the model.
Made `Node::getType()`, `Node::asParameter()`, and `Node::asUninitialized()` operate directly on the IR. This actually fixed several diffs compared to the AST dataflow, because `getType()` wasn't holding for nodes that weren't `Exprs`.

Made `Uninitialized` a `VariableInstruction`. This makes it consistent with `InitializeParameter`.
I've separated the model interface for memory side effects from the model for escaped addresses. It will be fairly common for a given model to extend both interfaces, but they are used for two different purposes.

I've also put each model interface and the non-member predicates that query it into a named module, which seemed cleaner than having predicates named `functionModelReadsMemory()` and `getFunctionModelParameterAliasBehavior()`.
This sort of fixes one FP and causes a new FN, but for the wrong reasons. The IR dataflow is tracking the reference itself, rather than the referred-to object. Once we can better model indirections, we can make this work correctly.

This change is still the right thing to do, because it ensures that the dataflow is looking at actual expression being computed by the instruction.
@dave-bartolomeo dave-bartolomeo requested a review from a team as a code owner December 10, 2018 08:54
@dave-bartolomeo dave-bartolomeo added this to the 1.19 milestone Dec 10, 2018
Copy link
Contributor

@jbj jbj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise LGTM. The CFG pruning for the AST-based CFG is notoriously slow for C/C++, so I'm glad to see that this version is much less ambitious. I'd still like to know if it performs okay and whether it's important to have three copies.


module Graph {
predicate isEntryBlock(ReachableBlock block) {
block = block.getFunctionIR().getEntryBlock()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The optimiser is often unlucky on joins like this one. Could block.getFunctionIR() be replaced with any(FunctionIR f)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

binInstr instanceof SubInstruction and result = sub(left, right) or
binInstr instanceof MulInstruction and result = mul(left, right) or
binInstr instanceof DivInstruction and result = div(left, right)
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a constant analysis that's used for reachability, isn't it even more important to support >, ==, and so on?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I've added support for equality and relational operators.

}

predicate isBlockReachable(IRBlock block) {
getAFeasiblePredecessor*(block) = block.getFunctionIR().getEntryBlock()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The optimiser is often unlucky on joins like this one. Could block.getFunctionIR() be replaced with any(FunctionIR f)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

private import NewIR

private class OldBlock = Reachability::ReachableBlock;
private class OldInstruction = Reachability::ReachableInstruction;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do the benefits outweigh the cost of running the reachability and constant analyses three times? There will certainly be cases where it becomes better when iterated, but it's also hundreds of lines of extra code we'll be running every time. Using pyrameterized modules also comes with a maintainability cost.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We actually only run it twice: Once on raw IR (used when building unaliased_ssa), and once on unaliased_ssa (used when building aliased_ssa). I've now removed the aliased_ssa instantiation, since it was unused.

This change moves the simple constant analysis that was used by the const_func test into a pyrameterized module for use on any stage of the IR. This will be used to detect unreachable code.
This change removes any IR instructions that can be statically proven unreachable. To detect unreachable IR, we first run a simple constant value analysis on the IR. Then, any `ConditionalBranch` with a constant condition has the appropriate edge marked as "infeasible". We define a class `ReachableBlock` as any `IRBlock` with a path from the entry block of the function. SSA construction has been modified to operate only on `ReachableBlock` and `ReachableInstruction`, which ensures that only reachable IR gets translated into SSA form. For any infeasible edge where its predecessor block is reachable, we replace the original target of the branch with an `Unreached` instruction, which lets us preserve the invariant that all `ConditionalBranch` instructions have both a true and a false edge, and allows guard inference to still work.

The changes to `SSAConstruction.qll` are not as scary as they look. They are almost entirely a mechanical replacement of `OldIR::IRBlock` with `OldBlock`, which is just an alias for `ReachableBlock`.

Note that the `constant_func.ql` test can determine that the two new test functions always return 0.

Removing unreachable code helps get rid of some common FPs in IR-based dataflow analysis, especially for constructs like `while(true)`.
We never actually consumed this iteration, since SSA construction only depends on the reachability instantiation of the previous IR layer.
@dave-bartolomeo
Copy link
Contributor Author

I believe I've addressed all feedback.

@jbj
Copy link
Contributor

jbj commented Dec 11, 2018

LGTM. Now we're just waiting for Jenkins and #597.

jbj
jbj previously approved these changes Dec 11, 2018
jbj
jbj previously approved these changes Dec 11, 2018
@adityasharad
Copy link
Collaborator

Looks like test output needs to be updated.

rdmarsh2
rdmarsh2 previously approved these changes Dec 11, 2018
Copy link
Contributor

@rdmarsh2 rdmarsh2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated test expectations; no code change

@dave-bartolomeo dave-bartolomeo merged commit be5ac2f into github:rc/1.19 Dec 12, 2018
@rdmarsh2 rdmarsh2 mentioned this pull request Dec 15, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants