text: /**
  * @file tests/bin2llvmir/utils/tests/instcombine_tests.cpp
  * @brief Tests for the @c InstructionCombiningPass pass.
  * @copyright (c) 2017 Avast Software, licensed under the MIT license
  *
  * This is checking that LLVM's -instcombine is behaving as expected.
  * If this fails, something in LLVM changed and we need to react, because
  * otherwise it will start to screw up our code.
  */
  
  #include <llvm/Transforms/InstCombine/InstCombine.h>
  
  #include "retdec/bin2llvmir/optimizations/inst_opt/inst_opt_pass.h"
  #include "retdec/bin2llvmir/optimizations/unreachable_funcs/unreachable_funcs.h"
  #include "bin2llvmir/utils/llvmir_tests.h"
  
  using namespace ::testing;
  using namespace llvm;
  
  namespace retdec {
  namespace bin2llvmir {
  namespace tests {
  
  /**
   * @brief Tests for the @c UnreachableFuncs pass.
   */
  class InstCombinePassTests: public LlvmIrTests
  {
  	protected:
  		void runOnModule()
  		{
  			LlvmIrTests::runOnModule<InstructionCombiningPass>();
  			// This pass counters some undesirable LLVM optimizations.
  			LlvmIrTests::runOnModule<InstructionOptimizer>();
  		}
  };
  
  TEST_F(InstCombinePassTests, doNotUnpackStoresToAggregates)
  {
  	parseInput(R"(
  		; We do not want -instcombine to optimize
  		;
  		;     store [10000 x %V] %a, [10000 x %V]* %b
  		;
  		; into
  		;
  		;     b[0] = a[0]
  		;     b[1] = a[1]
  		;     b[2] = a[2]
  		;     ...
  		;     b[9999] = a[9999]
  		;
  		; See commit 1e0c8e9af462d54622b6a986e170d858fdabf5df.
  
  		%V = type { i32, i32, i32, float }
  
  		declare [10000 x %V] @decompiler_undefined_function()
  		declare void @decompiler_prevent_removal([10000 x %V]*)
  
  		define i32 @main(i32 %argc, i8** %argv) {
  		  %a = alloca [10000 x %V]
  		  %a_x = call [10000 x %V] @decompiler_undefined_function()
  		  store [10000 x %V] %a_x, [10000 x %V]* %a
  		  call void @decompiler_prevent_removal([10000 x %V]* %a)
  		  ret i32 0
  		}
  	)");
  
  	runOnModule();
  
  	std::string exp = R"(
  		%V = type { i32, i32, i32, float }
  
  		declare [10000 x %V] @decompiler_undefined_function()
  
  		declare void @decompiler_prevent_removal([10000 x %V]*)
  
  		define i32 @main(i32 %argc, i8** %argv) {
  		  %a = alloca [10000 x %V], align 8
  		  %a_x = call [10000 x %V] @decompiler_undefined_function()
  		  store [10000 x %V] %a_x, [10000 x %V]* %a, align 8
  		  call void @decompiler_prevent_removal([10000 x %V]* nonnull %a)
  		  ret i32 0
  		}
  	)";
  	checkModuleAgainstExpectedIr(exp);
  }
  
  TEST_F(InstCombinePassTests, handleExpressionInSwitchCondition)
  {
  	parseInput(R"(
  		@gv = global i32 0
  
  		define i32 @fnv() {
  		br label %lab_1
  		lab_1:
  		  %a = add i32 0, 0
  		br label %lab_2
  		lab_2:
  		  switch i32 add (i32 ptrtoint (i32* @gv to i32), i32 -63), label %lab_3 [
  			i32 0, label %lab_1
  			i32 34, label %lab_2
  		  ]
  		br label %lab_3
  		lab_3:
  		  ret i32 123
  		}
  	)");
  
  	runOnModule();
  
  	std::string exp = R"(
  		@gv = global i32 0
  
  		define i32 @fnv() {
  		  br label %lab_1
  
  		lab_1:                                            ; preds = %lab_2, %0
  		  br label %lab_2
  
  		lab_2:                                            ; preds = %lab_2, %lab_1
  		  switch i32 add (i32 ptrtoint (i32* @gv to i32), i32 -63), label %lab_3 [
  			i32 0, label %lab_1
  			i32 34, label %lab_2
  		  ]
  														  ; No predecessors!
  		  br label %lab_3
  
  		lab_3:                                            ; preds = %1, %lab_2
  		  ret i32 123
  		}
  	)";
  	checkModuleAgainstExpectedIr(exp);
  }
  
  TEST_F(InstCombinePassTests, unreachableBasicBlocksKeep)
  {
  	parseInput(R"(
  		; Instructions in unreachable BBs are *NOT* removed if metadata named
  		; 'llvmToAsmGlobalVariableName' exits.
  
  		@llvm2asm = global i64 0
  
  		define void @fnc() {
  				store volatile i64 123, i64* @llvm2asm, !asm !1
  				ret void
  				store volatile i64 456, i64* @llvm2asm, !asm !2
  				ret void
  				store volatile i64 789, i64* @llvm2asm, !asm !3
  				ret void
  		}
  
  		!llvmToAsmGlobalVariableName = !{!0}
  
  		!0 = !{!"llvm2asm"}
  		!1 = !{!"name", i64 123, i64 10, !"asm", !"annotation"}
  		!2 = !{!"name", i64 456, i64 10, !"asm", !"annotation"}
  		!3 = !{!"name", i64 789, i64 10, !"asm", !"annotation"}
  	)");
  
  	runOnModule();
  
  	std::string exp = R"(
  		@llvm2asm = global i64 0
  
  		define void @fnc() {
  
  		; 7b
  		  store volatile i64 123, i64* @llvm2asm, align 8, !asm !1
  		  ret void
  														  ; No predecessors!
  
  		; 1c8
  		  store volatile i64 456, i64* @llvm2asm, !asm !2
  		  ret void
  														  ; No predecessors!
  
  		; 315
  		  store volatile i64 789, i64* @llvm2asm, !asm !3
  		  ret void
  		}
  
  		!llvmToAsmGlobalVariableName = !{!0}
  
  		!0 = !{!"llvm2asm"}
  		!1 = !{!"name", i64 123, i64 10, !"asm", !"annotation"}
  		!2 = !{!"name", i64 456, i64 10, !"asm", !"annotation"}
  		!3 = !{!"name", i64 789, i64 10, !"asm", !"annotation"}
  	)";
  	checkModuleAgainstExpectedIr(exp);
  }
  
  TEST_F(InstCombinePassTests, unreachableBasicBlocksRemove)
  {
  	parseInput(R"(
  		; Instructions in unreachable BBs are removed if metadata named
  		; 'llvmToAsmGlobalVariableName' does not exit.
  
  		@llvm2asm = global i64 0
  
  		define void @fnc() {
  				store volatile i64 123, i64* @llvm2asm, !asm !0
  				ret void
  				store volatile i64 456, i64* @llvm2asm, !asm !1
  				ret void
  				store volatile i64 789, i64* @llvm2asm, !asm !2
  				ret void
  		}
  
  		!0 = !{!"name", i64 123, i64 10, !"asm", !"annotation"}
  		!1 = !{!"name", i64 456, i64 10, !"asm", !"annotation"}
  		!2 = !{!"name", i64 789, i64 10, !"asm", !"annotation"}
  	)");
  
  	runOnModule();
  
  	std::string exp = R"(
  		@llvm2asm = global i64 0
  
  		define void @fnc() {
  
  		; 7b
  		  store volatile i64 123, i64* @llvm2asm, align 8, !asm !0
  		  ret void
  														  ; No predecessors!
  		  ret void
  														  ; No predecessors!
  		  ret void
  		}
  
  		!0 = !{!"name", i64 123, i64 10, !"asm", !"annotation"}
  	)";
  	checkModuleAgainstExpectedIr(exp);
  }
  
  TEST_F(InstCombinePassTests, undesirableStoreOptimizatonIsNotPerformed)
  {
  	parseInput(R"(
  		@g = global i32 0
  
  		declare double @func()
  
  		define i32 @main(i32 %argc, i8** %argv) {
  			%func_res = call double @func()
  			%1 = fptrunc double %func_res to float
  			%2 = bitcast float %1 to i32
  			store i32 %2, i32* @g
  			ret i32 0
  		}
  	)");
  
  	runOnModule();
  
  	std::string exp = R"(
  		@g = global i32 0
  
  		declare double @func()
  
  		define i32 @main(i32 %argc, i8** %argv) {
  			%func_res = call double @func()
  			%1 = fptrunc double %func_res to float
  			%2 = bitcast float %1 to i32
  			store i32 %2, i32* @g
  			ret i32 0
  		}
  	)";
  	checkModuleAgainstExpectedIr(exp);
  }
  
  TEST_F(InstCombinePassTests, undesirableLoadOptimizatonIsNotPerformed_1)
  {
  	parseInput(R"(
  		@g = global i32 0
  
  		define float @func() {
  			%1 = load i32, i32* @g, align 4
  			%2 = bitcast i32 %1 to float
  			ret float %2
  		}
  	)");
  
  	runOnModule();
  
  	std::string exp = R"(
  		@g = global i32 0
  
  		define float @func() {
  			%1 = load i32, i32* @g, align 4
  			%2 = bitcast i32 %1 to float
  			ret float %2
  		}
  	)";
  	checkModuleAgainstExpectedIr(exp);
  }
  
  TEST_F(InstCombinePassTests, undesirableLoadOptimizatonIsNotPerformed_2)
  {
  	parseInput(R"(
  		@g = global i32 0
  
  		define i8 @func() {
  			%1 = load i32, i32* @g, align 4
  			%2 = inttoptr i32 %1 to i8*
  			%3 = load i8, i8* %2, align 1
  			ret i8 %3
  		}
  	)");
  
  	runOnModule();
  
  	std::string exp = R"(
  		@g = global i32 0
  
  		define i8 @func() {
  			%1 = load i32, i32* @g, align 4
  			%2 = inttoptr i32 %1 to i8*
  			%3 = load i8, i8* %2, align 1
  			ret i8 %3
  		}
  	)";
  	checkModuleAgainstExpectedIr(exp);
  }
  
  } // namespace tests
  } // namespace bin2llvmir
  } // namespace retdec
  