Skip to content

Commit

Permalink
Fix issue with module-level pass: handle declare
Browse files Browse the repository at this point in the history
Several (all?) testdata files were failing when the
`FunctionTerminationPass` was run via proxy (from the module-level
pass), instead of being run at the function level. Unfortunately, "run
from the module" is the only way we think we can handle the call-graph
level analysis.

The symptom was LLVM stalling out -- looping forever? -- when we asked
for `ScalarEvolutionAnalysis`. I opened up LLDB and stepped through,
and... it was fine! Looking at `simple.c`, the pass produced results for
`get_value` and `main` just fine.

...but our functions are not the only functions listed in a module! For
instance, `simple.c` invokes `malloc`; as a result, `malloc` gets a
`declare`d in our module, without a function body. And *that's* where
the analysis was stalling out -- our pass was running on `F.getName() ==
"malloc"`!

Apparently, function-level passes are only run across functions
`define`d in the module -- but the listing of module contents includes
functions `define`d or `declare`d. I haven't really checked this, but it
is consistent with what we've seen, and would explain the difference
between running in function/module modes.

My fix (this commit) is to return `Unknown` if the function does not
have a body, i.e. has no basic blocks. This gets us back to a passing --
though incorrect -- state.
  • Loading branch information
cceckman authored and anvayg committed Jun 12, 2024
1 parent 37c42c9 commit 008022a
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 9 deletions.
2 changes: 1 addition & 1 deletion build/default.so.do
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ FLAGS="$(cat ../compile_flags.txt)"
"$LLVM_DIR"/bin/clang++ \
$FLAGS \
-Wall -fdiagnostics-color=always -fvisibility-inlines-hidden \
-g -std=gnu++17 -fPIC \
-glldb -std=gnu++17 -fPIC \
--write-user-dependencies -MF"$DEPFILE" \
-o "$3" \
-l LLVM \
Expand Down
53 changes: 53 additions & 0 deletions notes/function-module.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@

Notes on debugging the issue from 06c96c44:

> Bisect issue with function-level termination
> The same pass (`FunctionBoundedTermination`) completes as expected when
> invoked from a function-level transform pass
> (`print<function-bounded-termination>`), but not when invoked from a
> module-level transform pass `print<bounded-termination>`
> Is this something weird with the proxy?
FunctionTerminationPass gets ScalarEvolutionAnalysis and LoopAnalysis.

On call_to_bounded_.loops, using print<bounded-termination> (module-level analysis), ScalarEvolutionAnalysis completes.
LoopAnalysis times out? No, it doesn't...

not for "the first function", whichever that is. `get_value`, looks like.
`simple.c` does not have any loops!

On `main`... Seems like ScalarEvolutionAnalysis is the problem?
No; both analyses complete... `getLoopFor`?

Also completes for `main`.

But! apparently we also run for `malloc` in `simple.loops`...
and that's where the problem comes up. ScalarEvolutionAnalysis on malloc
runs dominator-tree analysis, which gets stuck:

```
(lldb) expr F.getName()
(llvm::StringRef) $9 = (Data = "malloc", Length = 6)
(lldb) expr &F.BasicBlocks.Sentinel
(llvm::ilist_sentinel<llvm::ilist_detail::node_options<llvm::BasicBlock, false, false, void> > *) $10 = 0x000055555563d0a0
(lldb) expr F.BasicBlocks.Sentinel
(llvm::ilist_sentinel<llvm::ilist_detail::node_options<llvm::BasicBlock, false, false, void> >) $11 = {
llvm::ilist_node_impl<llvm::ilist_detail::node_options<llvm::BasicBlock, false, false, void> > = {
llvm::ilist_detail::node_options<llvm::BasicBlock, false, false, void>::node_base_type = {
Prev = 0x000055555563d0a0
Next = 0x000055555563d0a0
}
}
}
```

Which looks like "an empty body" (sentinel value of a linked list, pointing at itself).

Can we detect which functions are externally-linked, and avoid analyzing them?
Or should we avoid analyzing things that are "bodyless"?

Easy version: `if(F.empty()) { unknown }`


21 changes: 13 additions & 8 deletions src/BoundedTerminationPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,13 @@ detect_cgscc_recursion(llvm::Function &F, llvm::FunctionAnalysisManager &FAM) {
FunctionTerminationPass::Result
FunctionTerminationPass::run(llvm::Function &F,
llvm::FunctionAnalysisManager &FAM) {
if(F.empty()) {
return FunctionTerminationPass::Result{
.elt = DoesThisTerminate::Unknown,
.explanation = "has no basic blocks in this module",
};
}

llvm::ScalarEvolution &SE = FAM.getResult<llvm::ScalarEvolutionAnalysis>(F);
llvm::LoopInfo &loop_info = FAM.getResult<llvm::LoopAnalysis>(F);
// const auto &outer_result = detect_cgscc_recursion(F, FAM);
Expand Down Expand Up @@ -456,15 +463,13 @@ ModuleTerminationPass::run(llvm::Module &IR, llvm::ModuleAnalysisManager &AM) {
llvm::PreservedAnalyses
BoundedTerminationPrinter::run(llvm::Module &IR,
llvm::ModuleAnalysisManager &AM) {
OS << "Starting pass... \n";
auto &module_results = AM.getResult<ModuleTerminationPass>(IR);
OS << "got results... \n";
// for (const auto &[function, result] : module_results.per_function_results)
// {
// OS << "Function name: " << llvm::demangle(function->getName()) << "\n";
// OS << "Result: " << result.elt << "\n";
// OS << "Explanation: " << result.explanation << "\n\n";
// }
for (const auto &[function, result] : module_results.per_function_results)
{
OS << "Function name: " << llvm::demangle(function->getName()) << "\n";
OS << "Result: " << result.elt << "\n";
OS << "Explanation: " << result.explanation << "\n\n";
}

return llvm::PreservedAnalyses::all();
}
Expand Down

0 comments on commit 008022a

Please sign in to comment.