diff --git a/llvm/compiler-course/llvm-ir/shulpin_i_replacer_pass/AddCallReplacerPass.cpp b/llvm/compiler-course/llvm-ir/shulpin_i_replacer_pass/AddCallReplacerPass.cpp new file mode 100644 index 0000000000000..ad7ea555ffe9f --- /dev/null +++ b/llvm/compiler-course/llvm-ir/shulpin_i_replacer_pass/AddCallReplacerPass.cpp @@ -0,0 +1,70 @@ +#include "llvm/ADT/STLExtras.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/Module.h" +#include "llvm/Passes/PassBuilder.h" +#include "llvm/Passes/PassPlugin.h" +#include "llvm/Support/raw_ostream.h" + +namespace { +struct AddCallReplacerPass : llvm::PassInfoMixin { + llvm::PreservedAnalyses run(llvm::Module &M, llvm::ModuleAnalysisManager &) { + llvm::Function *addFunc = M.getFunction("add"); + bool canReplace = false; + llvm::Type *paramTy = nullptr; + if (addFunc && addFunc->arg_size() == 2) { + auto AI = addFunc->arg_begin(); + llvm::Type *t0 = AI->getType(); + ++AI; + llvm::Type *t1 = AI->getType(); + if (t0 == t1) { + canReplace = true; + paramTy = t0; + } + } + + for (llvm::Function &F : M) { + if (&F == addFunc) + continue; + for (llvm::BasicBlock &BB : F) { + for (llvm::Instruction &I : llvm::make_early_inc_range(BB)) { + if (!canReplace) + break; + if (auto *BI = llvm::dyn_cast(&I)) { + if (BI->getOpcode() == llvm::Instruction::Add && + BI->getType() == paramTy) { + llvm::IRBuilder<> Builder(&I); + llvm::Value *L = BI->getOperand(0); + llvm::Value *R = BI->getOperand(1); + llvm::CallInst *CI = Builder.CreateCall(addFunc, {L, R}, "sum"); + BI->replaceAllUsesWith(CI); + BI->eraseFromParent(); + } + } + } + } + } + + return llvm::PreservedAnalyses::all(); + } + + static bool isRequired() { return true; } +}; +} // namespace + +extern "C" LLVM_ATTRIBUTE_WEAK::llvm::PassPluginLibraryInfo +llvmGetPassPluginInfo() { + return {LLVM_PLUGIN_API_VERSION, "AddCallReplacer", "0.1", + [](llvm::PassBuilder &PB) { + PB.registerPipelineParsingCallback( + [](llvm::StringRef name, llvm::ModulePassManager &MPM, + llvm::ArrayRef) { + if (name == "ReplacerPass") { + MPM.addPass(AddCallReplacerPass()); + return true; + } + return false; + }); + }}; +} diff --git a/llvm/compiler-course/llvm-ir/shulpin_i_replacer_pass/CMakeLists.txt b/llvm/compiler-course/llvm-ir/shulpin_i_replacer_pass/CMakeLists.txt new file mode 100644 index 0000000000000..95743bf996a05 --- /dev/null +++ b/llvm/compiler-course/llvm-ir/shulpin_i_replacer_pass/CMakeLists.txt @@ -0,0 +1,14 @@ +set(Title "ReplacerPass") +set(Student "ShulpinIlya") +set(Group "FIIT1") +set(TARGET_NAME "${Title}_${Student}_${Group}_LLVM_IR") + +if (NOT WIN32 AND NOT CYGWIN) + file(GLOB_RECURSE SOURCES *.cpp *.h *.hpp) + add_llvm_pass_plugin(${TARGET_NAME} ${SOURCES} + DEPENDS + intrinsics_gen + BUILDTREE_ONLY + ) + set(LLVM_TEST_DEPENDS ${TARGET_NAME} ${LLVM_TEST_DEPENDS} PARENT_SCOPE) +endif() diff --git a/llvm/test/compiler-course/shulpin_i_replacer_pass/shulpin_add_is_not_declaired_test.ll b/llvm/test/compiler-course/shulpin_i_replacer_pass/shulpin_add_is_not_declaired_test.ll new file mode 100644 index 0000000000000..e429edecccccb --- /dev/null +++ b/llvm/test/compiler-course/shulpin_i_replacer_pass/shulpin_add_is_not_declaired_test.ll @@ -0,0 +1,17 @@ +; RUN: opt -load-pass-plugin %llvmshlibdir/ReplacerPass_ShulpinIlya_FIIT1_LLVM_IR%pluginext \ +; RUN: -passes="ReplacerPass" -S %s | FileCheck %s + +;===------------------------------------------------------------------------===; +; Test 8: @no_add_decl — No @add in module, i32 add instructions should remain +; Verifies that when the add helper function is absent, the pass does not +; replace `add i32` with a call to @add +;===------------------------------------------------------------------------===; + +; CHECK-LABEL: define i32 @no_add_decl(i32 %x, i32 %y) +; CHECK: %sum = add i32 %x, %y +; CHECK-NOT: call i32 @add + +define i32 @no_add_decl(i32 %x, i32 %y) { + %sum = add i32 %x, %y + ret i32 %sum +} \ No newline at end of file diff --git a/llvm/test/compiler-course/shulpin_i_replacer_pass/shulpin_i_replacer_pass_test.ll b/llvm/test/compiler-course/shulpin_i_replacer_pass/shulpin_i_replacer_pass_test.ll new file mode 100644 index 0000000000000..04ce136f57c20 --- /dev/null +++ b/llvm/test/compiler-course/shulpin_i_replacer_pass/shulpin_i_replacer_pass_test.ll @@ -0,0 +1,107 @@ +; RUN: opt -load-pass-plugin %llvmshlibdir/ReplacerPass_ShulpinIlya_FIIT1_LLVM_IR%pluginext \ +; RUN: -passes="ReplacerPass" -S %s | FileCheck %s + +;===------------------------------------------------------------------------===; +; Test 1: @add - Reference implementation of add function (should not change) +; Checks that function @add itself is not transformed by the pass +;===------------------------------------------------------------------------===; + +; CHECK: define i32 @add(i32 %a, i32 %b) +; CHECK: %result = add i32 %a, %b +; CHECK: ret i32 %result +; CHECK-NOT: add i32 + +define i32 @add(i32 %a, i32 %b) { + %result = add i32 %a, %b + ret i32 %result +} + +;===------------------------------------------------------------------------===; +; Test 2: @foo - Matching types, call to @add should replace the add instruction +; This should be transformed to a call to @add because the operand types match +;===------------------------------------------------------------------------===; + +; CHECK-LABEL: define i32 @foo(i32 %x, i32 %y) +; CHECK-NEXT: call i32 @add(i32 %x, i32 %y) +; CHECK-NEXT: ret i32 %sum +; CHECK-NOT: add i32 + +define i32 @foo(i32 %x, i32 %y) { + %sum = add i32 %x, %y + ret i32 %sum +} + + +;===------------------------------------------------------------------------===; +; Test 3: @bar - Same as @foo, another usage of i32 add replaced with @add +; Verifies that the pass replaces all i32 add instructions consistently +;===------------------------------------------------------------------------===; + +; CHECK-LABEL: define i32 @bar(i32 %m, i32 %n) +; CHECK-NEXT: %sum1 = call i32 @add(i32 %m, i32 %n) +; CHECK-NEXT: ret i32 %sum +; CHECK-NOT: call i32 @add + +define i32 @bar(i32 %m, i32 %n) { + %sum = add i32 %m, %n + ret i32 %sum +} + +;===------------------------------------------------------------------------===; +; Test 4: @goo - i64 add should not be replaced, because @add only accepts i32 +; Ensures type mismatch prevents transformation +;===------------------------------------------------------------------------===; + +; CHECK-LABEL: define i64 @goo(i64 %x, i64 %y) +; CHECK-NEXT: %sum = add i64 %x, %y +; CHECK-NEXT: ret i64 %sum + +define i64 @goo(i64 %x, i64 %y) { + %sum = add i64 %x, %y + ret i64 %sum +} + +;===------------------------------------------------------------------------===; +; Test 5: @foo_alt - No add instruction here, should remain unchanged +; Also checks that unrelated functions are not transformed +;===------------------------------------------------------------------------===; + + +; CHECK-NOT: define i64 @add( +; CHECK-LABEL: define i64 @foo_alt(i64 %x) + +define i64 @foo_alt(i64 %x) { + ret i64 %x +} + +;===------------------------------------------------------------------------===; +; Test 6: @baz - add of mismatched type (i1), should not be replaced +; Verifies the pass skips non-i32 instructions (e.g. bitwise bools) +;===------------------------------------------------------------------------===; + +; CHECK-LABEL: define i1 @baz(i1 %a, i1 %b) +; CHECK: %r = add i1 %a, %b +; CHECK-NOT: call i32 @add + +define i1 @baz(i1 %a, i1 %b) { + %r = add i1 %a, %b + ret i1 %r +} + +;===------------------------------------------------------------------------===; +; Test 7: @multiple_adds - Only i32 adds should be replaced, others kept +; This mixes types to ensure partial transformation +;===------------------------------------------------------------------------===; + +; CHECK-LABEL: define void @multiple_adds(i32 %a, i32 %b, i64 %x, i64 %y) +; CHECK: call i32 @add(i32 %a, i32 %b) +; CHECK: %b2 = add i64 %x, %y + +define void @multiple_adds(i32 %a, i32 %b, i64 %x, i64 %y) { + %b1 = add i32 %a, %b + %b2 = add i64 %x, %y + call void @use(i32 %b1, i64 %b2) + ret void +} + +declare void @use(i32, i64)