//===- RemarkTest.cpp - Remark unit tests -------------------------------===// // // 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 "mlir/IR/Diagnostics.h" #include "mlir/IR/MLIRContext.h" #include "mlir/IR/Remarks.h" #include "mlir/Remark/RemarkStreamer.h" #include "mlir/Support/TypeID.h" #include "llvm/ADT/StringRef.h" #include "llvm/IR/LLVMRemarkStreamer.h" #include "llvm/Remarks/RemarkFormat.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/LogicalResult.h" #include "llvm/Support/YAMLParser.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include using namespace llvm; using namespace mlir; using namespace testing; namespace { TEST(Remark, TestOutputOptimizationRemark) { std::string categoryVectorizer("Vectorizer"); std::string categoryRegister("Register"); std::string categoryUnroll("Unroll"); std::string categoryInliner("Inliner"); std::string categoryReroller("Reroller"); std::string myPassname1("myPass1"); SmallString<64> tmpPathStorage; sys::fs::createUniquePath("remarks-%%%%%%.yaml", tmpPathStorage, /*MakeAbsolute=*/true); std::string yamlFile = std::string(tmpPathStorage.data(), tmpPathStorage.size()); ASSERT_FALSE(yamlFile.empty()); { MLIRContext context; Location loc = UnknownLoc::get(&context); context.printOpOnDiagnostic(true); context.printStackTraceOnDiagnostic(true); // Setup the remark engine mlir::remark::RemarkCategories cats{/*passed=*/categoryVectorizer, /*missed=*/categoryUnroll, /*analysis=*/categoryRegister, /*failed=*/categoryInliner}; LogicalResult isEnabled = mlir::remark::enableOptimizationRemarksWithLLVMStreamer( context, yamlFile, llvm::remarks::Format::YAML, cats); ASSERT_TRUE(succeeded(isEnabled)) << "Failed to enable remark engine"; // PASS: something succeeded remark::passed(loc, remark::RemarkOpts::name("Pass1") .category(categoryVectorizer) .subCategory(myPassname1) .function("bar")) << "vectorized loop" << remark::metric("tripCount", 128); // ANALYSIS: neutral insight remark::analysis( loc, remark::RemarkOpts::name("Analysis1").category(categoryRegister)) << "Kernel uses 168 registers"; // MISSED: explain why + suggest a fix remark::missed(loc, remark::RemarkOpts::name("Miss1") .category(categoryUnroll) .subCategory(myPassname1)) << remark::reason("not profitable at this size") << remark::suggest("increase unroll factor to >=4"); // FAILURE: action attempted but failed remark::failed(loc, remark::RemarkOpts::name("Failed1") .category(categoryInliner) .subCategory(myPassname1)) << remark::reason("failed due to unsupported pattern"); // FAILURE: Won't show up remark::failed(loc, remark::RemarkOpts::name("Failed2") .category(categoryReroller) .subCategory(myPassname1)) << remark::reason("failed due to rerolling pattern"); } // Read the file auto bufferOrErr = MemoryBuffer::getFile(yamlFile); ASSERT_TRUE(static_cast(bufferOrErr)) << "Failed to open remarks file"; std::string content = bufferOrErr.get()->getBuffer().str(); EXPECT_THAT(content, HasSubstr("--- !Passed")); EXPECT_THAT(content, HasSubstr("Name: Pass1")); EXPECT_THAT(content, HasSubstr("Pass: 'Vectorizer:myPass1'")); EXPECT_THAT(content, HasSubstr("Function: bar")); EXPECT_THAT(content, HasSubstr("Remark: vectorized loop")); EXPECT_THAT(content, HasSubstr("tripCount: '128'")); EXPECT_THAT(content, HasSubstr("--- !Analysis")); EXPECT_THAT(content, HasSubstr("Pass: Register")); EXPECT_THAT(content, HasSubstr("Name: Analysis1")); EXPECT_THAT(content, HasSubstr("Function: ''")); EXPECT_THAT(content, HasSubstr("Remark: Kernel uses 168 registers")); EXPECT_THAT(content, HasSubstr("--- !Missed")); EXPECT_THAT(content, HasSubstr("Pass: 'Unroll:myPass1'")); EXPECT_THAT(content, HasSubstr("Name: Miss1")); EXPECT_THAT(content, HasSubstr("Function: ''")); EXPECT_THAT(content, HasSubstr("Reason: not profitable at this size")); EXPECT_THAT(content, HasSubstr("Suggestion: 'increase unroll factor to >=4'")); EXPECT_THAT(content, HasSubstr("--- !Failure")); EXPECT_THAT(content, HasSubstr("Pass: 'Inliner:myPass1'")); EXPECT_THAT(content, HasSubstr("Name: Failed1")); EXPECT_THAT(content, HasSubstr("Function: ''")); EXPECT_THAT(content, HasSubstr("Reason: failed due to unsupported pattern")); EXPECT_THAT(content, Not(HasSubstr("Failed2"))); EXPECT_THAT(content, Not(HasSubstr("Reroller"))); // Also verify document order to avoid false positives. size_t iPassed = content.find("--- !Passed"); size_t iAnalysis = content.find("--- !Analysis"); size_t iMissed = content.find("--- !Missed"); size_t iFailure = content.find("--- !Failure"); ASSERT_NE(iPassed, std::string::npos); ASSERT_NE(iAnalysis, std::string::npos); ASSERT_NE(iMissed, std::string::npos); ASSERT_NE(iFailure, std::string::npos); EXPECT_LT(iPassed, iAnalysis); EXPECT_LT(iAnalysis, iMissed); EXPECT_LT(iMissed, iFailure); } TEST(Remark, TestNoOutputOptimizationRemark) { const auto *pass1Msg = "My message"; std::string categoryFailName("myImportantCategory"); std::string myPassname1("myPass1"); std::string funcName("myFunc"); SmallString<64> tmpPathStorage; sys::fs::createUniquePath("remarks-%%%%%%.yaml", tmpPathStorage, /*MakeAbsolute=*/true); std::string yamlFile = std::string(tmpPathStorage.data(), tmpPathStorage.size()); ASSERT_FALSE(yamlFile.empty()); std::error_code ec = llvm::sys::fs::remove(yamlFile, /*IgnoreNonExisting=*/true); if (ec) { FAIL() << "Failed to remove file " << yamlFile << ": " << ec.message(); } { MLIRContext context; Location loc = UnknownLoc::get(&context); remark::failed(loc, remark::RemarkOpts::name("myfail") .category(categoryFailName) .subCategory(myPassname1)) << remark::reason(pass1Msg); } // No setup, so no output file should be created // check! bool fileExists = llvm::sys::fs::exists(yamlFile); EXPECT_FALSE(fileExists) << "Expected no YAML file to be created without setupOptimizationRemarks"; } TEST(Remark, TestOutputOptimizationRemarkDiagnostic) { std::string categoryVectorizer("Vectorizer"); std::string categoryRegister("Register"); std::string categoryUnroll("Unroll"); std::string myPassname1("myPass1"); std::string fName("foo"); llvm::SmallVector seenMsg; { MLIRContext context; Location loc = UnknownLoc::get(&context); context.printOpOnDiagnostic(true); context.printStackTraceOnDiagnostic(true); // Register a handler that captures the diagnostic. ScopedDiagnosticHandler handler(&context, [&](Diagnostic &diag) { seenMsg.push_back(diag.str()); return success(); }); // Setup the remark engine mlir::remark::RemarkCategories cats{/*passed=*/categoryVectorizer, /*missed=*/categoryUnroll, /*analysis=*/categoryRegister, /*failed=*/categoryUnroll}; LogicalResult isEnabled = remark::enableOptimizationRemarks(context, nullptr, cats, true); ASSERT_TRUE(succeeded(isEnabled)) << "Failed to enable remark engine"; // PASS: something succeeded remark::passed(loc, remark::RemarkOpts::name("pass1") .category(categoryVectorizer) .function(fName) .subCategory(myPassname1)) << "vectorized loop" << remark::metric("tripCount", 128); // ANALYSIS: neutral insight remark::analysis(loc, remark::RemarkOpts::name("Analysis1") .category(categoryRegister) .function(fName)) << "Kernel uses 168 registers"; // MISSED: explain why + suggest a fix int target = 128; int tripBad = 4; int threshold = 256; remark::missed(loc, {"", categoryUnroll, "unroller2", ""}) << remark::reason("tripCount={0} < threshold={1}", tripBad, threshold); remark::missed(loc, {"", categoryUnroll, "", ""}) << remark::reason("tripCount={0} < threshold={1}", tripBad, threshold) << remark::suggest("increase unroll to {0}", target); // FAILURE: action attempted but failed remark::failed(loc, {"", categoryUnroll, "", ""}) << remark::reason("failed due to unsupported pattern"); } // clang-format off unsigned long expectedSize = 5; ASSERT_EQ(seenMsg.size(), expectedSize); EXPECT_EQ(seenMsg[0], "[Passed] pass1 | Category:Vectorizer:myPass1 | Function=foo | Remark=\"vectorized loop\", tripCount=128"); EXPECT_EQ(seenMsg[1], "[Analysis] Analysis1 | Category:Register | Function=foo | Remark=\"Kernel uses 168 registers\""); EXPECT_EQ(seenMsg[2], "[Missed] | Category:Unroll:unroller2 | Reason=\"tripCount=4 < threshold=256\""); EXPECT_EQ(seenMsg[3], "[Missed] | Category:Unroll | Reason=\"tripCount=4 < threshold=256\", Suggestion=\"increase unroll to 128\""); EXPECT_EQ(seenMsg[4], "[Failure] | Category:Unroll | Reason=\"failed due to unsupported pattern\""); // clang-format on } /// Custom remark streamer that prints remarks to stderr. class MyCustomStreamer : public remark::detail::MLIRRemarkStreamerBase { public: MyCustomStreamer() = default; void streamOptimizationRemark(const remark::detail::Remark &remark) override { llvm::errs() << "Custom remark: "; remark.print(llvm::errs(), true); llvm::errs() << "\n"; } }; TEST(Remark, TestCustomOptimizationRemarkDiagnostic) { testing::internal::CaptureStderr(); const auto *pass1Msg = "My message"; const auto *pass2Msg = "My another message"; const auto *pass3Msg = "Do not show this message"; std::string categoryLoopunroll("LoopUnroll"); std::string categoryInline("Inliner"); std::string myPassname1("myPass1"); std::string myPassname2("myPass2"); std::string funcName("myFunc"); std::string seenMsg = ""; { MLIRContext context; Location loc = UnknownLoc::get(&context); // Setup the remark engine mlir::remark::RemarkCategories cats{/*passed=*/categoryLoopunroll, /*missed=*/std::nullopt, /*analysis=*/std::nullopt, /*failed=*/categoryLoopunroll}; LogicalResult isEnabled = remark::enableOptimizationRemarks( context, std::make_unique(), cats, true); ASSERT_TRUE(succeeded(isEnabled)) << "Failed to enable remark engine"; // Remark 1: pass, category LoopUnroll remark::passed(loc, {"", categoryLoopunroll, myPassname1, ""}) << pass1Msg; // Remark 2: failure, category LoopUnroll remark::failed(loc, {"", categoryLoopunroll, myPassname2, ""}) << remark::reason(pass2Msg); // Remark 3: pass, category Inline (should not be printed) remark::passed(loc, {"", categoryInline, myPassname1, ""}) << pass3Msg; } llvm::errs().flush(); std::string errOut = ::testing::internal::GetCapturedStderr(); // Expect exactly two "Custom remark:" lines. auto first = errOut.find("Custom remark:"); EXPECT_NE(first, std::string::npos); auto second = errOut.find("Custom remark:", first + 1); EXPECT_NE(second, std::string::npos); auto third = errOut.find("Custom remark:", second + 1); EXPECT_EQ(third, std::string::npos); // Containment checks for messages. EXPECT_NE(errOut.find(pass1Msg), std::string::npos); // printed EXPECT_NE(errOut.find(pass2Msg), std::string::npos); // printed EXPECT_EQ(errOut.find(pass3Msg), std::string::npos); // filtered out } } // namespace