llvm/llvm/unittests/Analysis/SparsePropagation.cpp

//===- SparsePropagation.cpp - Unit tests for the generic solver ----------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "llvm/Analysis/SparsePropagation.h"
#include "llvm/ADT/PointerIntPair.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Module.h"
#include "gtest/gtest.h"
usingnamespacellvm;

namespace {
/// To enable interprocedural analysis, we assign LLVM values to the following
/// groups. The register group represents SSA registers, the return group
/// represents the return values of functions, and the memory group represents
/// in-memory values. An LLVM Value can technically be in more than one group.
/// It's necessary to distinguish these groups so we can, for example, track a
/// global variable separately from the value stored at its location.
enum class IPOGrouping {};

/// Our LatticeKeys are PointerIntPairs composed of LLVM values and groupings.
/// The PointerIntPair header provides a DenseMapInfo specialization, so using
/// these as LatticeKeys is fine.
TestLatticeKey;
} // namespace

namespace llvm {
/// A specialization of LatticeKeyInfo for TestLatticeKeys. The generic solver
/// must translate between LatticeKeys and LLVM Values when adding Values to
/// its work list and inspecting the state of control-flow related values.
template <> struct LatticeKeyInfo<TestLatticeKey> {};
} // namespace llvm

namespace {
/// This class defines a simple test lattice value that could be used for
/// solving problems similar to constant propagation. The value is maintained
/// as a PointerIntPair.
class TestLatticeVal {};

/// This class defines a simple test lattice function that could be used for
/// solving problems similar to constant propagation. The test lattice differs
/// from a "real" lattice in a few ways. First, it initializes all return
/// values, values stored in global variables, and arguments in the undefined
/// state. This means that there are no limitations on what we can track
/// interprocedurally. For simplicity, all global values in the tests will be
/// given internal linkage, since this is not something this lattice function
/// tracks. Second, it only handles the few instructions necessary for the
/// tests.
class TestLatticeFunc
    : public AbstractLatticeFunction<TestLatticeKey, TestLatticeVal> {};

/// This class defines the common data used for all of the tests. The tests
/// should add code to the module and then run the solver.
class SparsePropagationTest : public testing::Test {};
} // namespace

/// Test that we mark discovered functions executable.
///
/// define internal void @f() {
///   call void @g()
///   ret void
/// }
///
/// define internal void @g() {
///   call void @f()
///   ret void
/// }
///
/// For this test, we initially mark "f" executable, and the solver discovers
/// "g" because of the call in "f". The mutually recursive call in "g" also
/// tests that we don't add a block to the basic block work list if it is
/// already executable. Doing so would put the solver into an infinite loop.
TEST_F(SparsePropagationTest, MarkBlockExecutable) {}

/// Test that we propagate information through global variables.
///
/// @gv = internal global i64
///
/// define internal void @f() {
///   store i64 1, i64* @gv
///   ret void
/// }
///
/// define internal void @g() {
///   store i64 1, i64* @gv
///   ret void
/// }
///
/// For this test, we initially mark both "f" and "g" executable, and the
/// solver computes the lattice state of the global variable as constant.
TEST_F(SparsePropagationTest, GlobalVariableConstant) {}

/// Test that we propagate information through global variables.
///
/// @gv = internal global i64
///
/// define internal void @f() {
///   store i64 0, i64* @gv
///   ret void
/// }
///
/// define internal void @g() {
///   store i64 1, i64* @gv
///   ret void
/// }
///
/// For this test, we initially mark both "f" and "g" executable, and the
/// solver computes the lattice state of the global variable as overdefined.
TEST_F(SparsePropagationTest, GlobalVariableOverDefined) {}

/// Test that we propagate information through function returns.
///
/// define internal i64 @f(i1* %cond) {
/// if:
///   %0 = load i1, i1* %cond
///   br i1 %0, label %then, label %else
///
/// then:
///   ret i64 1
///
/// else:
///   ret i64 1
/// }
///
/// For this test, we initially mark "f" executable, and the solver computes
/// the return value of the function as constant.
TEST_F(SparsePropagationTest, FunctionDefined) {}

/// Test that we propagate information through function returns.
///
/// define internal i64 @f(i1* %cond) {
/// if:
///   %0 = load i1, i1* %cond
///   br i1 %0, label %then, label %else
///
/// then:
///   ret i64 0
///
/// else:
///   ret i64 1
/// }
///
/// For this test, we initially mark "f" executable, and the solver computes
/// the return value of the function as overdefined.
TEST_F(SparsePropagationTest, FunctionOverDefined) {}

/// Test that we propagate information through arguments.
///
/// define internal void @f() {
///   call void @g(i64 0, i64 1)
///   call void @g(i64 1, i64 1)
///   ret void
/// }
///
/// define internal void @g(i64 %a, i64 %b) {
///   ret void
/// }
///
/// For this test, we initially mark "f" executable, and the solver discovers
/// "g" because of the calls in "f". The solver computes the state of argument
/// "a" as overdefined and the state of "b" as constant.
///
/// In addition, this test demonstrates that ComputeInstructionState can alter
/// the state of multiple lattice values, in addition to the one associated
/// with the instruction definition. Each call instruction in this test updates
/// the state of arguments "a" and "b".
TEST_F(SparsePropagationTest, ComputeInstructionState) {}

/// Test that we can handle exceptional terminator instructions.
///
/// declare internal void @p()
///
/// declare internal void @g()
///
/// define internal void @f() personality ptr @p {
/// entry:
///   invoke void @g()
///           to label %exit unwind label %catch.pad
///
/// catch.pad:
///   %0 = catchswitch within none [label %catch.body] unwind to caller
///
/// catch.body:
///   %1 = catchpad within %0 []
///   catchret from %1 to label %exit
///
/// exit:
///   ret void
/// }
///
/// For this test, we initially mark the entry block executable. The solver
/// then discovers the rest of the blocks in the function are executable.
TEST_F(SparsePropagationTest, ExceptionalTerminatorInsts) {}