diff --git a/clang/lib/DPCT/CMakeLists.txt b/clang/lib/DPCT/CMakeLists.txt index b74c77697383..2802c61f9863 100644 --- a/clang/lib/DPCT/CMakeLists.txt +++ b/clang/lib/DPCT/CMakeLists.txt @@ -233,6 +233,7 @@ add_clang_library(DPCT MigrateScript/GenMakefile.cpp RulesInclude/InclusionHeaders.cpp IncMigration/IncrementalMigrationUtility.cpp + IncMigration/ReMigration.cpp UserDefinedRules/UserDefinedRules.cpp UserDefinedRules/PatternRewriter.cpp MigrateScript/MigrateBuildScript.cpp diff --git a/clang/lib/DPCT/DPCT.cpp b/clang/lib/DPCT/DPCT.cpp index 85ce5abc7f91..41cb6abac9bb 100644 --- a/clang/lib/DPCT/DPCT.cpp +++ b/clang/lib/DPCT/DPCT.cpp @@ -521,7 +521,7 @@ static void loadMainSrcFileInfo(clang::tooling::UnifiedPath OutRoot) { DpctGlobalInfo::getYamlFileName()); auto PreTU = std::make_shared(); if (llvm::sys::fs::exists(YamlFilePath)) { - if (loadFromYaml(YamlFilePath, *PreTU) != 0) { + if (loadTUFromYaml(YamlFilePath, *PreTU) != 0) { llvm::errs() << getLoadYamlFailWarning(YamlFilePath); } @@ -1400,6 +1400,8 @@ int runDPCT(int argc, const char **argv) { dpctExit(MigrationSucceeded, false); } + tryLoadingUpstreamChangesAndUserChanges(); + ReplTy ReplCUDA, ReplSYCL; volatile int RunCount = 0; do { diff --git a/clang/lib/DPCT/FileGenerator/GenFiles.cpp b/clang/lib/DPCT/FileGenerator/GenFiles.cpp index a2d95abc735b..327644e4c299 100644 --- a/clang/lib/DPCT/FileGenerator/GenFiles.cpp +++ b/clang/lib/DPCT/FileGenerator/GenFiles.cpp @@ -1210,7 +1210,7 @@ void loadYAMLIntoFileInfo(clang::tooling::UnifiedPath Path) { auto PreTU = std::make_shared(); if (fs::exists(YamlFilePath.getCanonicalPath())) { if (clang::dpct::DpctGlobalInfo::isIncMigration()) { - if (loadFromYaml(YamlFilePath, *PreTU) == 0) { + if (loadTUFromYaml(YamlFilePath, *PreTU) == 0) { DpctGlobalInfo::getInstance().insertReplInfoFromYAMLToFileInfo( OriginPath, std::move(PreTU)); } else { diff --git a/clang/lib/DPCT/IncMigration/ExternalReplacement.cpp b/clang/lib/DPCT/IncMigration/ExternalReplacement.cpp index 6ee889b28bad..93ee7f8bbb72 100644 --- a/clang/lib/DPCT/IncMigration/ExternalReplacement.cpp +++ b/clang/lib/DPCT/IncMigration/ExternalReplacement.cpp @@ -10,19 +10,19 @@ // -Load replacement from external (disk file) // -Merge replacement in current migration with previous migration. +#include "ExternalReplacement.h" #include "AnalysisInfo.h" +#include "IncMigration/IncrementalMigrationUtility.h" #include "Utility.h" + +#include "clang/Tooling/Core/Diagnostic.h" #include "clang/Tooling/Core/Replacement.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/ReplacementsYaml.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" - -#include "ExternalReplacement.h" -#include "IncMigration/IncrementalMigrationUtility.h" -#include "clang/Tooling/Core/Diagnostic.h" -#include "clang/Tooling/Refactoring.h" -#include "clang/Tooling/ReplacementsYaml.h" #include "llvm/Support/YAMLTraits.h" #include "llvm/Support/raw_os_ostream.h" @@ -75,8 +75,8 @@ int save2Yaml( return 0; } -int loadFromYaml(const clang::tooling::UnifiedPath &Input, - clang::tooling::TranslationUnitReplacements &TU) { +template +static int loadFromYaml(const clang::tooling::UnifiedPath &Input, T &Content) { llvm::ErrorOr> Buffer = llvm::MemoryBuffer::getFile(Input.getCanonicalPath()); if (!Buffer) { @@ -84,9 +84,20 @@ int loadFromYaml(const clang::tooling::UnifiedPath &Input, << Buffer.getError().message() << "\n"; return -1; } - llvm::yaml::Input YAMLIn(Buffer.get()->getBuffer()); - YAMLIn >> TU; + YAMLIn >> Content; + if (YAMLIn.error()) { + Content = T(); + return -1; + } + return 0; +} + +int loadTUFromYaml(const clang::tooling::UnifiedPath &Input, + clang::tooling::TranslationUnitReplacements &TU) { + int Status = loadFromYaml(Input, TU); + if (Status) + return Status; bool IsSrcFileChanged = false; for (const auto &digest : TU.MainSourceFilesDigest) { @@ -98,7 +109,7 @@ int loadFromYaml(const clang::tooling::UnifiedPath &Input, } } - if (IsSrcFileChanged || YAMLIn.error()) { + if (IsSrcFileChanged) { // File doesn't appear to be a header change description. Ignore it. TU = clang::tooling::TranslationUnitReplacements(); return -1; @@ -107,6 +118,16 @@ int loadFromYaml(const clang::tooling::UnifiedPath &Input, return 0; } +void loadGDCFromYaml(const clang::tooling::UnifiedPath &Input, + clang::dpct::GitDiffChanges &GDC) { + int status = loadFromYaml(Input, GDC); + if (status) { + llvm::errs() << "Failed to load git diff Changes from " + << Input.getCanonicalPath() << "\n"; + GDC = clang::dpct::GitDiffChanges(); + } +} + void mergeAndUniqueReps( Replacements &Replaces, const std::vector &PreRepls) { diff --git a/clang/lib/DPCT/IncMigration/ExternalReplacement.h b/clang/lib/DPCT/IncMigration/ExternalReplacement.h index 20e819d16fc1..99c9376394b1 100644 --- a/clang/lib/DPCT/IncMigration/ExternalReplacement.h +++ b/clang/lib/DPCT/IncMigration/ExternalReplacement.h @@ -9,8 +9,8 @@ #ifndef __EXTERNAL_REPLACEMENT_H__ #define __EXTERNAL_REPLACEMENT_H__ -#include "clang/Tooling/Core/Replacement.h" -#include "llvm/ADT/StringRef.h" +#include "IncMigration/ReMigration.h" + #include #include @@ -31,8 +31,10 @@ namespace dpct { int mergeExternalReps(clang::tooling::UnifiedPath InRootSrcFilePath, clang::tooling::UnifiedPath OutRootSrcFilePath, clang::tooling::Replacements &Replaces); -int loadFromYaml(const clang::tooling::UnifiedPath &Input, - clang::tooling::TranslationUnitReplacements &TU); +int loadTUFromYaml(const clang::tooling::UnifiedPath &Input, + clang::tooling::TranslationUnitReplacements &TU); +void loadGDCFromYaml(const clang::tooling::UnifiedPath &Input, + clang::dpct::GitDiffChanges &GDC); int save2Yaml( clang::tooling::UnifiedPath &YamlFile, clang::tooling::UnifiedPath &SrcFileName, @@ -46,6 +48,7 @@ void mergeAndUniqueReps( clang::tooling::Replacements &Replaces, const std::vector &PreRepls); +void tryLoadingUpstreamChangesAndUserChanges(); } // namespace dpct } // namespace clang diff --git a/clang/lib/DPCT/IncMigration/IncrementalMigrationUtility.cpp b/clang/lib/DPCT/IncMigration/IncrementalMigrationUtility.cpp index 3f578a4f284a..a75e0c8ec9f4 100644 --- a/clang/lib/DPCT/IncMigration/IncrementalMigrationUtility.cpp +++ b/clang/lib/DPCT/IncMigration/IncrementalMigrationUtility.cpp @@ -376,7 +376,7 @@ bool canContinueMigration(std::string &Msg) { if (!llvm::sys::fs::exists(YamlFilePath.getCanonicalPath())) return true; - if (loadFromYaml(YamlFilePath.getCanonicalPath(), *PreTU) != 0) { + if (loadTUFromYaml(YamlFilePath.getCanonicalPath(), *PreTU) != 0) { llvm::errs() << getLoadYamlFailWarning(YamlFilePath.getCanonicalPath()); return true; } diff --git a/clang/lib/DPCT/IncMigration/ReMigration.cpp b/clang/lib/DPCT/IncMigration/ReMigration.cpp new file mode 100644 index 000000000000..149a7f83213c --- /dev/null +++ b/clang/lib/DPCT/IncMigration/ReMigration.cpp @@ -0,0 +1,524 @@ +//===----------------------- ReMigration.cpp ------------------------------===// +// +// 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 "ReMigration.h" +#include "AnalysisInfo.h" +#include "ExternalReplacement.h" +#include "TextModification.h" + +#include "clang/AST/Expr.h" +#include "clang/Tooling/Core/Replacement.h" +#include "clang/Tooling/Core/UnifiedPath.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/raw_ostream.h" + +#include +#include +#include + +std::optional< + std::function> + getLineStringHook = std::nullopt; +std::optional> + getLineNumberHook = std::nullopt; +std::optional> + getLineBeginOffsetHook = std::nullopt; + +namespace clang::dpct { +using namespace clang::tooling; +static GitDiffChanges UpstreamChanges; +static GitDiffChanges UserChanges; +AddFileHunk::AddFileHunk(std::string NewFilePath) + : Hunk(AddFile), NewFilePath(UnifiedPath(NewFilePath).getCanonicalPath()) {} +DeleteFileHunk::DeleteFileHunk(std::string OldFilePath) + : Hunk(DeleteFile), + OldFilePath(UnifiedPath(OldFilePath).getCanonicalPath()) {} +GitDiffChanges &getUpstreamChanges() { return UpstreamChanges; } +GitDiffChanges &getUserChanges() { return UserChanges; } +static void dumpGitDiffChanges(const GitDiffChanges &GHC) { + llvm::errs() << "GitDiffChanges:\n"; + llvm::errs() << " ModifyFileHunks:\n"; + for (const auto &Hunk : GHC.ModifyFileHunks) { + llvm::errs() << " - FilePath: " << Hunk.getFilePath() << "\n"; + llvm::errs() << " Offset: " << Hunk.getOffset() << "\n"; + llvm::errs() << " Length: " << Hunk.getLength() << "\n"; + llvm::errs() << " ReplacementText: " << Hunk.getReplacementText() + << "\n"; + } + llvm::errs() << " AddFileHunks:\n"; + for (const auto &Hunk : GHC.AddFileHunks) { + llvm::errs() << " - NewFilePath: " << Hunk.getNewFilePath() << "\n"; + } + llvm::errs() << " DeleteFileHunks:\n"; + for (const auto &Hunk : GHC.DeleteFileHunks) { + llvm::errs() << " - OldFilePath: " << Hunk.getOldFilePath() << "\n"; + } + llvm::errs() << " MoveFileHunks:\n"; + for (const auto &Hunk : GHC.MoveFileHunks) { + llvm::errs() << " - FilePath: " << Hunk.getFilePath() << "\n"; + llvm::errs() << " Offset: " << Hunk.getOffset() << "\n"; + llvm::errs() << " Length: " << Hunk.getLength() << "\n"; + llvm::errs() << " ReplacementText: " << Hunk.getReplacementText() + << "\n"; + llvm::errs() << " NewFilePath: " << Hunk.getNewFilePath() << "\n"; + } +} + +void tryLoadingUpstreamChangesAndUserChanges() { + llvm::SmallString<128> UpstreamChangesFilePath( + DpctGlobalInfo::getInRoot().getCanonicalPath()); + llvm::SmallString<128> UserChangesFilePath( + DpctGlobalInfo::getInRoot().getCanonicalPath()); + llvm::sys::path::append(UpstreamChangesFilePath, "UpstreamChanges.yaml"); + llvm::sys::path::append(UserChangesFilePath, "UserChanges.yaml"); + + if (llvm::sys::fs::exists(UpstreamChangesFilePath)) { + ::loadGDCFromYaml(UpstreamChangesFilePath, getUpstreamChanges()); + } + if (llvm::sys::fs::exists(UserChangesFilePath)) { + ::loadGDCFromYaml(UserChangesFilePath, getUserChanges()); + } +} + +static StringRef getLineString(UnifiedPath FilePath, unsigned LineNumber) { +#ifndef NDEBUG + if (getLineStringHook.has_value()) { + return getLineStringHook.value()(FilePath, LineNumber); + } +#endif + auto FileInfo = DpctGlobalInfo::getInstance().insertFile(FilePath); + StringRef Line = FileInfo->getLineString(LineNumber); + return Line; +} + +static unsigned getLineNumber(UnifiedPath FilePath, unsigned Offset) { +#ifndef NDEBUG + if (getLineNumberHook.has_value()) { + return getLineNumberHook.value()(FilePath, Offset); + } +#endif + auto FileInfo = DpctGlobalInfo::getInstance().insertFile(FilePath); + return FileInfo->getLineNumber(Offset); +} + +static unsigned getLineBeginOffset(UnifiedPath FilePath, unsigned LineNumber) { +#ifndef NDEBUG + if (getLineBeginOffsetHook.has_value()) { + return getLineBeginOffsetHook.value()(FilePath, LineNumber); + } +#endif + auto FileInfo = DpctGlobalInfo::getInstance().insertFile(FilePath); + return FileInfo->getLineInfo(LineNumber).Offset; +} + +/// Calculate the new Repls of the input \p NewRepl after \p Repls is applied to +/// the files. +/// This shfit may have conflicts. +/// Since \p NewRepl (from Repl_C_y) is line based and \p Repls (from Repl_A) is +/// character based, we assume that if there is a conflict, the range from +/// Repl_C_y is always covering the range from Repl_A. Then we just ignore the +/// \p NewRepl (from Repl_C_y) since the old CUDA code is changed, so the +/// migration repl is out-of-date. +/// \param Repls Replacements to apply. +/// \param NewRepl Replacements before applying \p Repls. +/// \return The result Repls. +clang::tooling::Replacements +calculateUpdatedRanges(const clang::tooling::Replacements &Repls, + const clang::tooling::Replacements &NewRepl) { + // Assumption: no overlap in the each groups. + clang::tooling::Replacements Result; + for (const auto &R : NewRepl) { + // Check if the range (BOffset, EOffset - BOffset) is overlapped with any + // repl in Repls + std::optional MaxNotGreater = std::nullopt; + for (const auto &ExistingR : Repls) { + if (ExistingR.getOffset() <= R.getOffset()) + MaxNotGreater = ExistingR; + else + break; + } + if (MaxNotGreater.has_value()) { + if (MaxNotGreater->getOffset() + MaxNotGreater->getLength() > + R.getOffset()) + continue; // has overlap + } + + unsigned int BOffset = Repls.getShiftedCodePosition(R.getOffset()); + unsigned int EOffset = + Repls.getShiftedCodePosition(R.getOffset() + R.getLength()); + if (BOffset > EOffset) + continue; + llvm::cantFail(Result.add(Replacement( + R.getFilePath(), BOffset, EOffset - BOffset, R.getReplacementText()))); + } + return Result; +} + +std::map> +groupReplcementsByFile(const std::vector &Repls) { + std::map> Result; + for (const auto &R : Repls) { + Result[R.getFilePath().str()].push_back(R); + } + return Result; +} + +// If repl range is cross lines, we treat the \n itself belongs to current line. +// Example: +// aaabbbccc +// dddeeefff +// ggghhhiii +// +// Original repl: +// (ccc\ndddeeefff\nggg) =>(jjj\nkkk) +// +// Splitted repls: +// (ccc\n) =>(jjj\nkkk) +// (dddeeefff\n) => "" +// (ggg) => "" +std::vector +splitReplInOrderToNotCrossLines(const std::vector &InRepls) { + std::string FilePath = InRepls[0].getFilePath().str(); + std::vector Result; + + for (const auto &Repl : InRepls) { + unsigned StartOffset = Repl.getOffset(); + unsigned EndOffset = StartOffset + Repl.getLength(); + unsigned StartLine = getLineNumber(FilePath, StartOffset); + unsigned EndLine = getLineNumber(FilePath, EndOffset); + + if (StartLine == EndLine) { + // Single line replacement + Result.push_back(Repl); + continue; + } + + // Cross-line replacement + unsigned CurrentOffset = StartOffset; + + // The first line + unsigned LineEndOffset = getLineBeginOffset(FilePath, StartLine + 1); + unsigned FirstLineLength = LineEndOffset - StartOffset; + Result.emplace_back(FilePath, CurrentOffset, FirstLineLength, + Repl.getReplacementText()); + CurrentOffset += FirstLineLength; + + // middle lines + for (unsigned Line = StartLine + 1; Line < EndLine; ++Line) { + LineEndOffset = getLineBeginOffset(FilePath, Line + 1); + unsigned lineLength = LineEndOffset - CurrentOffset; + Result.emplace_back(Repl.getFilePath(), CurrentOffset, lineLength, ""); + CurrentOffset += lineLength; + } + + // The last line + unsigned LastLineLength = EndOffset - CurrentOffset; + if (LastLineLength > 0) { + Result.emplace_back(Repl.getFilePath(), CurrentOffset, LastLineLength, + ""); + } + } + + return Result; +} + +std::map +convertReplcementsLineString(const std::vector &InRepls) { + std::vector Replacements = + splitReplInOrderToNotCrossLines(InRepls); + UnifiedPath FilePath(InRepls[0].getFilePath()); + + // group replacement by line + std::map> ReplacementsByLine; + for (const auto &Repl : Replacements) { + unsigned LineNum = getLineNumber(FilePath, Repl.getOffset()); + ReplacementsByLine[LineNum].push_back(Repl); + } + + // process each line + std::map Result; + for (auto &[LineNum, Repls] : ReplacementsByLine) { + std::sort(Repls.begin(), Repls.end()); + + std::string OriginalLineStr = getLineString(FilePath, LineNum).str(); + unsigned LineStartOffset = getLineBeginOffset(FilePath, LineNum); + std::string NewLineStr; + unsigned Pos = 0; + for (const auto &Repl : Repls) { + unsigned StrOffset = Repl.getOffset() - LineStartOffset; + NewLineStr += OriginalLineStr.substr(Pos, StrOffset - Pos); + NewLineStr += Repl.getReplacementText().str(); + Pos = StrOffset + Repl.getLength(); + } + NewLineStr += OriginalLineStr.substr(Pos); + Result[LineNum] = NewLineStr; + } + return Result; +} + +static std::map +convertReplcementsLineString(const tooling::Replacements &Repls) { + std::vector ReplsVec; + for (const auto &R : Repls) { + ReplsVec.push_back(R); + } + return convertReplcementsLineString(ReplsVec); +} + +std::vector +mergeMapsByLine(const std::map &MapA, + const std::map &MapB, + const UnifiedPath &FilePath) { + auto genReplacement = [&](unsigned LineNumber, + const std::string &LineContent) { + unsigned Offset = getLineBeginOffset(FilePath, LineNumber); + unsigned StrLen = getLineString(FilePath, LineNumber).size(); + return Replacement(FilePath.getCanonicalPath(), Offset, StrLen, + LineContent); + }; + + std::vector Result; + auto ItA = MapA.begin(); + auto ItB = MapB.begin(); + + while (ItA != MapA.end() || ItB != MapB.end()) { + if (ItA == MapA.end()) { + Result.push_back(genReplacement(ItB->first, ItB->second)); + ++ItB; + } else if (ItB == MapB.end()) { + Result.push_back(genReplacement(ItA->first, ItA->second)); + ++ItA; + } else if (ItA->first < ItB->first) { + Result.push_back(genReplacement(ItA->first, ItA->second)); + ++ItA; + } else if (ItB->first < ItA->first) { + Result.push_back(genReplacement(ItB->first, ItB->second)); + ++ItB; + } else { + // Conflict line(s) + std::vector ConflictA; + std::vector ConflictB; + unsigned ConflictOffset = getLineBeginOffset(FilePath, ItA->first); + unsigned ConflictLength = 0; + + // Collect continuous conflicting lines + while (ItA != MapA.end() && ItB != MapB.end() && + ItA->first == ItB->first) { + ConflictA.push_back(ItA->second); + ConflictB.push_back(ItB->second); + ++ItA; + ++ItB; + ConflictLength += getLineString(FilePath, ItA->first).size(); + } + + // generate merged string + std::string Merged = "<<<<<<<\n"; + for (const auto &L : ConflictA) + Merged += L; + Merged += "=======\n"; + for (const auto &L : ConflictB) + Merged += L; + Merged += ">>>>>>>\n"; + + Result.emplace_back(FilePath.getCanonicalPath(), ConflictOffset, + ConflictLength, Merged); + } + } + return Result; +} + +static bool hasConflict(const Replacement &R1, const Replacement &R2) { + if (R1.getFilePath() != R2.getFilePath()) + return false; + if (R1.getOffset() == R2.getOffset()) { + if (R1.getLength() && R2.getLength()) { + return true; + } + } + if ((R1.getOffset() < R2.getOffset() && + R1.getOffset() + R1.getLength() > R2.getOffset()) || + (R2.getOffset() < R1.getOffset() && + R2.getOffset() + R2.getLength() > R1.getOffset())) { + return true; + } + return false; +} + +// Merge Repl_C1 and Repl_C2. If has conflict, keep repl from Repl_C2. +std::vector mergeC1AndC2( + const std::vector &Repl_C1, const GitDiffChanges &Repl_C2, + const std::map + &FileNameMap) { + std::vector Result; + std::vector Repl_C2_vec; + std::for_each( + Repl_C2.ModifyFileHunks.begin(), Repl_C2.ModifyFileHunks.end(), + [&Repl_C2_vec, FileNameMap](const Replacement &Hunk) { + UnifiedPath OldFilePath = + FileNameMap.at(UnifiedPath(Hunk.getFilePath())); + Replacement Replacement(OldFilePath.getCanonicalPath(), + Hunk.getOffset(), Hunk.getLength(), + Hunk.getReplacementText()); + Repl_C2_vec.push_back(Replacement); + }); + for (const auto &ReplInC1 : Repl_C1) { + bool HasConflict = false; + for (const auto &ReplInC2 : Repl_C2_vec) { + if (HasConflict = hasConflict(ReplInC1, ReplInC2)) + break; + } + if (!HasConflict) { + Result.push_back(ReplInC1); + } + } + Result.insert(Result.end(), Repl_C2_vec.begin(), Repl_C2_vec.end()); + return Result; +} + +// clang-format off +// Repl A +// [CUDA code 1] -----------------------------------------> [CUDA code 2] +// | / | +// | Repl C1 / | Repl B +// | ________/ | +// V / V +// [SYCL code 1] / [SYCL code 2] +// | / | +// | Repl C2 Repl D / | +// | / | +// V shift (may have conlifct) V merge | +// [SYCL code 1.1] ----------------> [SYCL code 1.1] ----------> | +// (based on CUDA code 1) (based on CUDA code 2) | +// V +// [SYCL code 2.1] +// +// Repl_A: Read from gitdiff2yaml generated files. +// Repl_B: Curent in-memory migration replacements. +// Repl C1: Read from MainSourceFiles.yaml (and *.h.yaml) file(s). +// Repl_C2: Read from gitdiff2yaml generated files. +// +// Repl_A has 4 parts: +// Repl_A_1: New added files. +// Repl_A_2: Replacements in modified files. +// Repl_A_3: Deleted files. +// Repl_A_4: Replacements in moved files. +// +// clang-format on +// +// Merge process: +// 1. Merge Repl_C1 and Repl_C2 directly, named Repl_C. If there is conlict, +// we keep Repl_C2. +// Repl_C can be divided in to 2 parts: +// Repl_C_x: Replacements which in ranges of Repl_A_3 or delete hunks in +// Repl_A_2/Repl_A_4. +// Repl_C_y: Other replacements. +// Repl_C_x will be ignored during this merge. +// 2. Shfit Repl_C_y with Repl_A, called Repl_D. +// 3. Merge Repl_D and Repl_B. May have conflicts. +std::map> reMigrationMerge( + const GitDiffChanges &Repl_A, const std::vector &Repl_B, + const std::vector &Repl_C1, const GitDiffChanges &Repl_C2, + const std::map + &FileNameMap) { + assert(Repl_C2.AddFileHunks.empty() && Repl_C2.DeleteFileHunks.empty() && + Repl_C2.MoveFileHunks.empty() && + "Repl_C2 should only have ModifiyFileHunks."); + // Merge Repl_C1 and Repl_C2. If has conflict, keep repl from Repl_C2. + // TODO: Repl_C1 has name like file1.cpp, file2.cpp, file3.cu, file4.cuh + // but Repl_C2 has name like file1.cpp, file2.cpp.dp.cpp, file3.dp.cpp, file4.dp.hpp + // we need convert the filename in Repl_C2 to CUDA style + std::vector Repl_C = mergeC1AndC2(Repl_C1, Repl_C2, FileNameMap); + + // Convert vector in Repl_A to map for quick lookup. + std::map> + DeletedParts; + std::map ModifiedParts; + for (const auto &Hunk : Repl_A.ModifyFileHunks) { + if (Hunk.getLength() != 0 && Hunk.getReplacementText().size() == 0) { + DeletedParts[Hunk.getFilePath().str()][Hunk.getOffset()] = + Hunk.getLength(); + } + llvm::cantFail(ModifiedParts[Hunk.getFilePath().str()].add( + Replacement(Hunk.getFilePath().str(), Hunk.getOffset(), + Hunk.getLength(), Hunk.getReplacementText()))); + } + for (const auto &Hunk : Repl_A.MoveFileHunks) { + if (Hunk.getLength() != 0 && Hunk.getReplacementText().size() == 0) { + DeletedParts[Hunk.getFilePath().str()][Hunk.getOffset()] = + Hunk.getLength(); + } + llvm::cantFail(ModifiedParts[Hunk.getFilePath().str()].add( + Replacement(Hunk.getFilePath().str(), Hunk.getOffset(), + Hunk.getLength(), Hunk.getReplacementText()))); + } + for (const auto &Hunk : Repl_A.DeleteFileHunks) { + DeletedParts[Hunk.getOldFilePath()] = std::map(); + } + + // Get Repl_C_y + std::map Repl_C_y; + for (const auto &Repl : Repl_C) { + // The gitdiff changes are line-based while clang replacements are + // character-based. So here assume there is no overlap (only repl totally + // covered by delete hunk) between delete hunks and replacements. + const auto &It = DeletedParts.find(Repl.getFilePath().str()); + if (It == DeletedParts.end()) { + llvm::cantFail(Repl_C_y[Repl.getFilePath().str()].add( + Replacement(Repl.getFilePath().str(), Repl.getOffset(), + Repl.getLength(), Repl.getReplacementText()))); + continue; + } + + // Check if the replacement is in a deleted part. + for (const auto &Part : It->second) { + if (hasConflict(Repl, Replacement(Repl.getFilePath(), Part.first, + Part.second, ""))) + break; + } + llvm::cantFail(Repl_C_y[Repl.getFilePath().str()].add( + Replacement(Repl.getFilePath().str(), Repl.getOffset(), + Repl.getLength(), Repl.getReplacementText()))); + } + + // Shift Repl_C_y with Repl_A(ModifiedParts) + std::map Repl_D; + for (const auto &Item : Repl_C_y) { + const auto &FilePath = Item.first; + const auto &Repls = Item.second; + // Check if the file has modified parts. + const auto &It = ModifiedParts.find(FilePath); + if (It == ModifiedParts.end()) { + Repl_D[FilePath] = Repls; + continue; + } + Repl_D[FilePath] = calculateUpdatedRanges(It->second, Repls); + } + + // Group Repl_B by file + const auto Repl_B_by_file = groupReplcementsByFile(Repl_B); + // Merge Repl_D and Repl_B + // Convert the repls to a map then merge line by line + std::map> Result; + for (const auto &Pair : Repl_B_by_file) { + std::map ReplBInLines = + convertReplcementsLineString(Pair.second); + if (Repl_D.find(Pair.first) != Repl_D.end()) { + // Merge Repl_D and Repl_B + std::map ReplDInLines = + convertReplcementsLineString(Repl_D[Pair.first]); + Result[Pair.first] = + mergeMapsByLine(ReplBInLines, ReplDInLines, Pair.first); + } else { + // No Repl_D for this file, just add Repl_B. + Result[Pair.first] = Pair.second; + } + } + + return Result; +} +} // namespace clang::dpct diff --git a/clang/lib/DPCT/IncMigration/ReMigration.h b/clang/lib/DPCT/IncMigration/ReMigration.h new file mode 100644 index 000000000000..e76a2fd21ee2 --- /dev/null +++ b/clang/lib/DPCT/IncMigration/ReMigration.h @@ -0,0 +1,255 @@ +//===----------------------- ReMigration.h --------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef __DPCT_REMIGRATION_H__ +#define __DPCT_REMIGRATION_H__ + +#include "clang/Tooling/Core/Replacement.h" +#include "clang/Tooling/ReplacementsYaml.h" + +extern std::optional< + std::function> + getLineStringHook; +extern std::optional< + std::function> + getLineNumberHook; +extern std::optional< + std::function> + getLineBeginOffsetHook; + +namespace clang { +namespace dpct { +class Hunk { +public: + enum HunkType : unsigned { + ModifyFile = 0, + AddFile, + DeleteFile, + MoveFile, + Unspecified + }; + +private: + HunkType HT = Unspecified; + +public: + Hunk(HunkType HT) : HT(HT) {} + HunkType getHunkType() const { return HT; } + virtual ~Hunk() = default; +}; + +class ModifyFileHunk : public Hunk, public tooling::Replacement { +public: + ModifyFileHunk() : Hunk(ModifyFile), Replacement() {} + ModifyFileHunk(const std::string &FilePath, unsigned Offset, unsigned Length, + const std::string &ReplacementText) + : Hunk(ModifyFile), + Replacement(FilePath, Offset, Length, ReplacementText) {} + ModifyFileHunk(const Replacement &R) : Hunk(ModifyFile), Replacement(R) {} +}; + +class AddFileHunk : public Hunk { + std::string NewFilePath; + +public: + AddFileHunk() : Hunk(AddFile) {} + AddFileHunk(std::string NewFilePath); + const std::string &getNewFilePath() const { return NewFilePath; } +}; + +class DeleteFileHunk : public Hunk { + std::string OldFilePath; + +public: + DeleteFileHunk() : Hunk(DeleteFile) {} + DeleteFileHunk(std::string OldFilePath); + const std::string &getOldFilePath() const { return OldFilePath; } +}; + +class MoveFileHunk : public Hunk, public tooling::Replacement { + std::string NewFilePath; + +public: + MoveFileHunk() : Hunk(MoveFile), Replacement() {} + MoveFileHunk(const std::string &FilePath, unsigned Offset, unsigned Length, + const std::string &ReplacementText, + const std::string &NewFilePath) + : Hunk(MoveFile), Replacement(FilePath, Offset, Length, ReplacementText), + NewFilePath(std::move(NewFilePath)) {} + MoveFileHunk(const Replacement &R, const std::string &NewFilePath) + : Hunk(MoveFile), Replacement(R), NewFilePath(NewFilePath) {} + std::string getNewFilePath() const { return NewFilePath; } +}; + +struct GitDiffChanges { + std::vector ModifyFileHunks; + std::vector AddFileHunks; + std::vector DeleteFileHunks; + std::vector MoveFileHunks; +}; +GitDiffChanges &getUpstreamChanges(); +GitDiffChanges &getUserChanges(); + +clang::tooling::Replacements +calculateUpdatedRanges(const clang::tooling::Replacements &Repls, + const clang::tooling::Replacements &NewRepl); +std::map> +groupReplcementsByFile(const std::vector &Repls); +std::vector splitReplInOrderToNotCrossLines( + const std::vector &InRepls); +std::map +convertReplcementsLineString(const std::vector &Repls); +std::vector +mergeMapsByLine(const std::map &MapA, + const std::map &MapB, + const clang::tooling::UnifiedPath &FilePath); +std::vector +mergeC1AndC2(const std::vector &Repl_C1, + const GitDiffChanges &Repl_C2, + const std::map &FileNameMap); + +std::map> +reMigrationMerge( + const GitDiffChanges &Repl_A, + const std::vector &Repl_B, + const std::vector &Repl_C1, + const GitDiffChanges &Repl_C2, + const std::map &FileNameMap); +} // namespace dpct +} // namespace clang + +LLVM_YAML_IS_SEQUENCE_VECTOR(clang::dpct::ModifyFileHunk) +LLVM_YAML_IS_SEQUENCE_VECTOR(clang::dpct::AddFileHunk) +LLVM_YAML_IS_SEQUENCE_VECTOR(clang::dpct::DeleteFileHunk) +LLVM_YAML_IS_SEQUENCE_VECTOR(clang::dpct::MoveFileHunk) +LLVM_YAML_DECLARE_ENUM_TRAITS(clang::dpct::Hunk::HunkType) +namespace llvm { +namespace yaml { +inline void ScalarEnumerationTraits::enumeration( + IO &io, clang::dpct::Hunk::HunkType &value) { + io.enumCase(value, "ModifyFile", clang::dpct::Hunk::HunkType::ModifyFile); + io.enumCase(value, "AddFile", clang::dpct::Hunk::HunkType::AddFile); + io.enumCase(value, "DeleteFile", clang::dpct::Hunk::HunkType::DeleteFile); + io.enumCase(value, "MoveFile", clang::dpct::Hunk::HunkType::MoveFile); + io.enumCase(value, "Unspecified", clang::dpct::Hunk::HunkType::Unspecified); +} + +template <> struct MappingTraits { + struct NormalizedModifyFileHunk { + NormalizedModifyFileHunk(const IO &io) : Offset(0), Length(0) {} + NormalizedModifyFileHunk(const IO &io, clang::dpct::ModifyFileHunk &H) + : FilePath(H.getFilePath()), Offset(H.getOffset()), + Length(H.getLength()), ReplacementText(H.getReplacementText()) {} + + clang::dpct::ModifyFileHunk denormalize(const IO &) { + clang::dpct::ModifyFileHunk H(FilePath, Offset, Length, ReplacementText); + return H; + } + + std::string FilePath; + unsigned int Offset; + unsigned int Length; + std::string ReplacementText; + }; + + static void mapping(IO &Io, clang::dpct::ModifyFileHunk &H) { + MappingNormalization + Keys(Io, H); + Io.mapRequired("FilePath", Keys->FilePath); + Io.mapRequired("Offset", Keys->Offset); + Io.mapRequired("Length", Keys->Length); + Io.mapRequired("ReplacementText", Keys->ReplacementText); + } +}; + +template <> struct MappingTraits { + struct NormalizedAddFileHunk { + NormalizedAddFileHunk(const IO &io) {} + NormalizedAddFileHunk(const IO &io, clang::dpct::AddFileHunk &H) + : NewFilePath(H.getNewFilePath()) {} + + clang::dpct::AddFileHunk denormalize(const IO &io) { + clang::dpct::AddFileHunk H(NewFilePath); + return H; + } + + std::string NewFilePath; + }; + static void mapping(IO &Io, clang::dpct::AddFileHunk &H) { + MappingNormalization Keys( + Io, H); + Io.mapRequired("NewFilePath", Keys->NewFilePath); + } +}; + +template <> struct MappingTraits { + struct NormalizedDeleteFileHunk { + NormalizedDeleteFileHunk(const IO &io) {} + NormalizedDeleteFileHunk(const IO &io, clang::dpct::DeleteFileHunk &H) + : OldFilePath(H.getOldFilePath()) {} + + clang::dpct::DeleteFileHunk denormalize(const IO &io) { + clang::dpct::DeleteFileHunk H(OldFilePath); + return H; + } + + std::string OldFilePath; + }; + static void mapping(IO &Io, clang::dpct::DeleteFileHunk &H) { + MappingNormalization + Keys(Io, H); + Io.mapRequired("OldFilePath", Keys->OldFilePath); + } +}; + +template <> struct MappingTraits { + struct NormalizedMoveFileHunk { + NormalizedMoveFileHunk(const IO &io) : Offset(0), Length(0) {} + NormalizedMoveFileHunk(const IO &io, clang::dpct::MoveFileHunk &H) + : FilePath(H.getFilePath()), Offset(H.getOffset()), + Length(H.getLength()), ReplacementText(H.getReplacementText()), + NewFilePath(H.getNewFilePath()) {} + + clang::dpct::MoveFileHunk denormalize(const IO &io) { + clang::dpct::MoveFileHunk H(FilePath, Offset, Length, ReplacementText, + NewFilePath); + return H; + } + + std::string FilePath; + unsigned int Offset; + unsigned int Length; + std::string ReplacementText; + std::string NewFilePath; + }; + + static void mapping(IO &Io, clang::dpct::MoveFileHunk &H) { + MappingNormalization + Keys(Io, H); + Io.mapRequired("FilePath", Keys->FilePath); + Io.mapRequired("Offset", Keys->Offset); + Io.mapRequired("Length", Keys->Length); + Io.mapRequired("ReplacementText", Keys->ReplacementText); + Io.mapRequired("NewFilePath", Keys->NewFilePath); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &Io, clang::dpct::GitDiffChanges &GDC) { + Io.mapOptional("ModifyFileHunks", GDC.ModifyFileHunks); + Io.mapOptional("AddFileHunks", GDC.AddFileHunks); + Io.mapOptional("DeleteFileHunks", GDC.DeleteFileHunks); + Io.mapOptional("MoveFileHunks", GDC.MoveFileHunks); + } +}; +} // namespace yaml +} // namespace llvm + +#endif // __DPCT_REMIGRATION_H__ diff --git a/clang/tools/dpct/gitdiff2yaml/1.ref.txt b/clang/tools/dpct/gitdiff2yaml/1.ref.txt new file mode 100644 index 000000000000..23273daa15be --- /dev/null +++ b/clang/tools/dpct/gitdiff2yaml/1.ref.txt @@ -0,0 +1,5 @@ +--- +AddFileHunks: + - NewFilePath: 'b.txt' + - NewFilePath: 'c.txt' +... diff --git a/clang/tools/dpct/gitdiff2yaml/2.ref.txt b/clang/tools/dpct/gitdiff2yaml/2.ref.txt new file mode 100644 index 000000000000..6da1bae78ec3 --- /dev/null +++ b/clang/tools/dpct/gitdiff2yaml/2.ref.txt @@ -0,0 +1,4 @@ +--- +DeleteFileHunks: + - OldFilePath: 'b.txt' +... diff --git a/clang/tools/dpct/gitdiff2yaml/3.ref.txt b/clang/tools/dpct/gitdiff2yaml/3.ref.txt new file mode 100644 index 000000000000..7579b9fee24b --- /dev/null +++ b/clang/tools/dpct/gitdiff2yaml/3.ref.txt @@ -0,0 +1,8 @@ +--- +MoveFileHunks: + - FilePath: 'b.txt' + Offset: 12 + Length: 4 + ReplacementText: "777\n" + NewFilePath: 'c.txt' +... diff --git a/clang/tools/dpct/gitdiff2yaml/4.ref.txt b/clang/tools/dpct/gitdiff2yaml/4.ref.txt new file mode 100644 index 000000000000..58fe43dd35c5 --- /dev/null +++ b/clang/tools/dpct/gitdiff2yaml/4.ref.txt @@ -0,0 +1,27 @@ +--- +ModifyFileHunks: + - FilePath: 'a.txt' + Offset: 8 + Length: 8 + ReplacementText: "111\n222\n" + - FilePath: 'a.txt' + Offset: 20 + Length: 4 + ReplacementText: "333\n" + - FilePath: 'a.txt' + Offset: 56 + Length: 0 + ReplacementText: "444\n" + - FilePath: 'a.txt' + Offset: 92 + Length: 4 + ReplacementText: "" + - FilePath: 'b.txt' + Offset: 40 + Length: 12 + ReplacementText: "" + - FilePath: 'b.txt' + Offset: 92 + Length: 0 + ReplacementText: "111\n222\n333\n" +... diff --git a/clang/tools/dpct/gitdiff2yaml/gitdiff2yaml.cpp b/clang/tools/dpct/gitdiff2yaml/gitdiff2yaml.cpp new file mode 100644 index 000000000000..6228182237f7 --- /dev/null +++ b/clang/tools/dpct/gitdiff2yaml/gitdiff2yaml.cpp @@ -0,0 +1,423 @@ +//===--- gitdiff2yaml.cpp -------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// Usage: +// $ g++ gitdiff2yaml.cpp -o gitdiff2yaml +// $ cd /path/to/your/git/repo +// $ ./gitdiff2yaml +// This will output the clang replacements in YAML format. +// Limitation: +// (1) The workspace and the staging area should be clean before running +// this tool. +// (2) The line ending in the file should be '\n'. +//===----------------------------------------------------------------------===// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +const std::string LineEnd = "\\n"; + +std::string execGitCommand(const std::string &CMD) { + std::array Buffer; + std::unique_ptr Pipe(popen(CMD.c_str(), "r"), pclose); + if (!Pipe) { + throw std::runtime_error("popen() failed!"); + } + + std::string Result; + while (fgets(Buffer.data(), Buffer.size(), Pipe.get()) != nullptr) { + Result += Buffer.data(); + } + return Result; +} + +struct Replacement { + std::string NewFilePath; + std::string OldFilePath; + unsigned Offset = 0; + unsigned Length = 0; + std::string ReplacementText; +}; + +struct HunkContext { + unsigned OldCurrentLine = 0; + bool InHunk = false; + bool FastForward = false; + std::string CurrentNewFilePath; + std::string CurrentOldFilePath; +}; + +bool startsWith(const std::string &Str, const std::string &Prefix) { + return Str.size() >= Prefix.size() && + Str.compare(0, Prefix.size(), Prefix) == 0; +} + +bool parseHunkHeader(const std::string &Line, HunkContext &HC) { + const std::string HunkHeaderPrefix = "@@ -"; + if (!startsWith(Line, HunkHeaderPrefix)) + return false; + + // E.g., + // @@ -0,0 +1,3 @@ + // ^ + // |-- OldEnd + size_t OldEnd = Line.find(' ', HunkHeaderPrefix.size()); + std::string OldPart = + Line.substr(HunkHeaderPrefix.size(), OldEnd - HunkHeaderPrefix.size()); + size_t Comma = OldPart.find(','); + if (Comma == std::string::npos) { + throw std::runtime_error("Invalid hunk header format: " + Line); + } + HC.OldCurrentLine = std::stoi(OldPart.substr(0, Comma)); + return true; +} + +// vec[0] is -1, which is a placeholder. +// vec[i] /*i is the line number (1-based)*/ is the offset at the line +// beginning. +// Assumption: the line ending in the file is '\n'. +std::vector calculateOldOffset(const std::string &OldFileContent) { + std::vector Ret; + Ret.push_back(-1); // Placeholder for Ret[0]. + std::istringstream OldFileStream(OldFileContent); + std::string Line; + unsigned Offset = 0; + + while (std::getline(OldFileStream, Line)) { + Ret.push_back(Offset); + Offset += Line.size() + + 1; // std::getline does not include the newline character. And we + // assume the line ending is '\n'. So add 1 here. + } + + return Ret; +} + +// 1. Assume the line ending in the file is '\n'. +// 2. The pair (---, +++) may occurs multiple times in one hunk, so we use a +// variable to save the delete (-) operation. The continuous delete operations +// are treated as one operation. +// 3. After the delete operation, if the next line is one or more '+' +// operations, we make them as a replace-replacement. If the next line is a +// context line, the delete operation is a delete-replacement. Then clear the +// variable. +// 4. If we meet insertions ('+') when the variable is empty, we treat it as an +// insert-replacement. +void processHunkBody(const std::string &Line, HunkContext &Ctx, + std::vector &Repls, + const std::vector &CurrentOldFileOffset) { + static std::optional< + std::pair> + DeleteInfo; + static std::optional< + std::pair> + AddInfo; + + auto addRepl = [&]() { + Replacement R; + if (DeleteInfo.has_value() && AddInfo.has_value()) { + // replace-replacement + R.OldFilePath = Ctx.CurrentOldFilePath; + R.NewFilePath = Ctx.CurrentNewFilePath; + R.Length = DeleteInfo->second; + R.Offset = CurrentOldFileOffset[DeleteInfo->first]; + R.ReplacementText = AddInfo->second; + DeleteInfo.reset(); + AddInfo.reset(); + } else if (DeleteInfo.has_value()) { + // delete-replacement + R.OldFilePath = Ctx.CurrentOldFilePath; + R.NewFilePath = Ctx.CurrentNewFilePath; + R.Length = DeleteInfo->second; + R.Offset = CurrentOldFileOffset[DeleteInfo->first]; + R.ReplacementText = ""; + DeleteInfo.reset(); + } else if (AddInfo.has_value()) { + // insert-replacement + R.OldFilePath = Ctx.CurrentOldFilePath; + R.NewFilePath = Ctx.CurrentNewFilePath; + R.Length = 0; + R.Offset = CurrentOldFileOffset[AddInfo->first]; + R.ReplacementText = AddInfo->second; + AddInfo.reset(); + } + Repls.push_back(R); + }; + + // Hunk end + if (Line.empty()) { + addRepl(); + Ctx.InHunk = false; + return; + } + + switch (Line[0]) { + case ' ': { + addRepl(); + Ctx.OldCurrentLine++; + break; + } + case '-': { + if (!DeleteInfo.has_value()) { + auto Item = std::pair( + Ctx.OldCurrentLine, + Line.length()); // +1 for the newline character, -1 for the + // '-' in the line beginng + DeleteInfo = Item; + } else { + DeleteInfo->second += + (Line.length()); // +1 for the newline character, -1 for the + // '-' in the line beginng + } + Ctx.OldCurrentLine++; + break; + } + case '+': { + if (!AddInfo.has_value()) { + auto Item = std::pair(Ctx.OldCurrentLine, + Line.substr(1) + LineEnd); + AddInfo = Item; + } else { + AddInfo->second += (Line.substr(1) + LineEnd); + } + break; + } + } +} + +std::vector parseDiff(const std::string &diffOutput, + const std::string &RepoRoot) { + std::vector replacements; + std::istringstream iss(diffOutput); + std::string line; + + HunkContext HC; + std::vector CurrentOldFileOffset; + + while (std::getline(iss, line)) { + if (startsWith(line, "diff --git")) { + HC.FastForward = false; + continue; + } + if (HC.FastForward) + continue; + + if (startsWith(line, "---")) { + HC.CurrentOldFilePath = + line.substr(4) == "/dev/null" ? "/dev/null" : line.substr(6); + if (HC.CurrentOldFilePath != "/dev/null") { + std::ifstream FileStream(RepoRoot + "/" + HC.CurrentOldFilePath); + if (!FileStream.is_open()) { + throw std::runtime_error("Failed to open file: " + RepoRoot + "/" + + HC.CurrentOldFilePath); + } + std::stringstream Buffer; + Buffer << FileStream.rdbuf(); + CurrentOldFileOffset = calculateOldOffset(Buffer.str()); + } + continue; + } + if (startsWith(line, "+++")) { + HC.CurrentNewFilePath = + line.substr(4) == "/dev/null" ? "/dev/null" : line.substr(6); + if (HC.CurrentOldFilePath == "/dev/null" || + HC.CurrentNewFilePath == "/dev/null") { + HC.FastForward = true; + Replacement R; + R.OldFilePath = HC.CurrentOldFilePath; + R.NewFilePath = HC.CurrentNewFilePath; + replacements.emplace_back(R); + } + continue; + } + + if (parseHunkHeader(line, HC)) { + // Hunk start + HC.InHunk = true; + continue; + } + + if (HC.InHunk) { + processHunkBody(line, HC, replacements, CurrentOldFileOffset); + continue; + } + } + + return replacements; +} + +struct ModifyHunk { + std::string FilePath; + unsigned Offset = 0; + unsigned Length = 0; + std::string ReplacementText; +}; + +struct AddHunk { + std::string NewFilePath; +}; + +struct DeleteHunk { + std::string OldFilePath; +}; + +struct MoveHunk { + std::string FilePath; + unsigned Offset = 0; + unsigned Length = 0; + std::string ReplacementText; + std::string NewFilePath; +}; + +void printYaml(std::ostream &stream, const std::vector &Repls) { + std::vector ModifyHunks; + std::vector AddHunks; + std::vector DeleteHunks; + std::vector MoveHunks; + + for (const auto &R : Repls) { + if (R.OldFilePath == "/dev/null" && R.NewFilePath != "/dev/null") { + // Add replacement + AddHunk AH; + AH.NewFilePath = R.NewFilePath; + AddHunks.push_back(AH); + continue; + } + if (R.OldFilePath != "/dev/null" && R.NewFilePath == "/dev/null") { + // Delete replacement + DeleteHunk DH; + DH.OldFilePath = R.OldFilePath; + DeleteHunks.push_back(DH); + continue; + } + if (R.OldFilePath == R.NewFilePath && R.OldFilePath != "/dev/null") { + // Modify replacement + ModifyHunk MH; + MH.FilePath = R.OldFilePath; + MH.Offset = R.Offset; + MH.Length = R.Length; + MH.ReplacementText = R.ReplacementText; + ModifyHunks.push_back(MH); + continue; + } + if (R.OldFilePath != R.NewFilePath) { + // Move replacement + MoveHunk MH; + MH.FilePath = R.OldFilePath; + MH.Offset = R.Offset; + MH.Length = R.Length; + MH.ReplacementText = R.ReplacementText; + MH.NewFilePath = R.NewFilePath; + MoveHunks.push_back(MH); + continue; + } + throw std::runtime_error("Invalid replacement: " + R.OldFilePath + " -> " + + R.NewFilePath); + } + + stream << "---" << std::endl; + if (!ModifyHunks.empty()) + stream << "ModifyFileHunks:" << std::endl; + for (const auto &H : ModifyHunks) { + stream << " - FilePath: " << "'" << H.FilePath << "'" << std::endl; + stream << " Offset: " << H.Offset << std::endl; + stream << " Length: " << H.Length << std::endl; + stream << " ReplacementText: " << "\"" << H.ReplacementText << "\"" + << std::endl; + } + if (!AddHunks.empty()) + stream << "AddFileHunks:" << std::endl; + for (const auto &H : AddHunks) { + stream << " - NewFilePath: " << "'" << H.NewFilePath << "'" + << std::endl; + } + if (!DeleteHunks.empty()) + stream << "DeleteFileHunks:" << std::endl; + for (const auto &H : DeleteHunks) { + stream << " - OldFilePath: " << "'" << H.OldFilePath << "'" + << std::endl; + } + if (!MoveHunks.empty()) + stream << "MoveFileHunks:" << std::endl; + for (const auto &H : MoveHunks) { + stream << " - FilePath: " << "'" << H.FilePath << "'" << std::endl; + stream << " Offset: " << H.Offset << std::endl; + stream << " Length: " << H.Length << std::endl; + stream << " ReplacementText: " << "\"" << H.ReplacementText << "\"" + << std::endl; + stream << " NewFilePath: " << "'" << H.NewFilePath << "'" + << std::endl; + } + stream << "..." << std::endl; +} + +} // namespace + +int main(int argc, char *argv[]) { + if (argc != 2 && argc != 4) { + std::cerr << "Usage: gitdiff2yaml [-o outputfile]" + << std::endl; + return 1; + } + bool OutputToFile = false; + if (argc == 4) { + if (std::string(argv[2]) != "-o") { + std::cerr << "Invalid option: " << argv[2] << std::endl; + return 1; + } + OutputToFile = true; + } + + std::string OldCommitID = argv[1]; + + std::string RepoRoot = execGitCommand("git rev-parse --show-toplevel"); + RepoRoot = RepoRoot.substr(0, RepoRoot.size() - 1); // Remove the last '\n' + + std::string NewCommitID = execGitCommand("git log -1 --format=\"%H\""); + std::string DiffOutput = execGitCommand("git diff " + OldCommitID); + + execGitCommand("git reset --hard " + OldCommitID); + std::vector Repls = parseDiff(DiffOutput, RepoRoot); + + // Erase emtpy replacements + Repls.erase(std::remove_if(Repls.begin(), Repls.end(), + [](Replacement x) { + return (x.NewFilePath == "" && + x.OldFilePath == "" && x.Offset == 0 && + x.Length == 0 && + x.ReplacementText == ""); + }), + Repls.end()); + + if (OutputToFile) { + std::ofstream OutFile(argv[3]); + if (!OutFile.is_open()) { + std::cerr << "Failed to open output file: " << argv[3] << std::endl; + return 1; + } + printYaml(OutFile, Repls); + OutFile.close(); + } else { + printYaml(std::cout, Repls); + } + + execGitCommand("git reset --hard " + NewCommitID); + + return 0; +} diff --git a/clang/tools/dpct/gitdiff2yaml/test.py b/clang/tools/dpct/gitdiff2yaml/test.py new file mode 100644 index 000000000000..7c4dc291cb5b --- /dev/null +++ b/clang/tools/dpct/gitdiff2yaml/test.py @@ -0,0 +1,101 @@ +# ===----- test.py --------------------------------------------------------=== # +# +# 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 +# +# ===----------------------------------------------------------------------=== # + +from pathlib import Path +import os +import shutil +import subprocess + +def run_command(cmd): + try: + result = subprocess.run( + cmd, + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + return result.stdout + except subprocess.CalledProcessError as e: + print(f"Command fail: {e.stderr}") + return None + +def compare_with_diff(ref_file): + result = subprocess.run( + ["diff", "-u", ref_file, "temp.txt"], + capture_output=True, + text=True + ) + + if result.returncode == 0: + return True + else: + print("Found difference:\n" + result.stdout) + return False + +# Clean up previous test directory if it exists +test_dir = Path("tests") +if test_dir.exists() and test_dir.is_dir(): + shutil.rmtree(test_dir) + +# Check if gitdiff2yaml exists +gitdiff2yaml_path = Path("gitdiff2yaml") +if not gitdiff2yaml_path.exists(): + print("gitdiff2yaml does not exist. Please build it first.") + exit(1) + +run_command(["unzip", "tests.zip"]) +os.chdir("tests") + +# Test 1 +os.chdir("1") +old_commit_id = run_command(["git", "rev-parse", "HEAD~1"]).strip() +result = run_command(["../../gitdiff2yaml", old_commit_id, "-o", "temp.txt"]) +test1_res = compare_with_diff("../../1.ref.txt") +if test1_res: + print("Test 1 passed") +else: + print("Test 1 failed") +os.chdir("..") + +# Test 2 +os.chdir("2") +old_commit_id = run_command(["git", "rev-parse", "HEAD~1"]).strip() +result = run_command(["../../gitdiff2yaml", old_commit_id, "-o", "temp.txt"]) +test1_res = compare_with_diff("../../2.ref.txt") +if test1_res: + print("Test 2 passed") +else: + print("Test 2 failed") +os.chdir("..") + +# Test 3 +os.chdir("3") +old_commit_id = run_command(["git", "rev-parse", "HEAD~1"]).strip() +result = run_command(["../../gitdiff2yaml", old_commit_id, "-o", "temp.txt"]) +test1_res = compare_with_diff("../../3.ref.txt") +if test1_res: + print("Test 3 passed") +else: + print("Test 3 failed") +os.chdir("..") + +# Test 4 +os.chdir("4") +old_commit_id = run_command(["git", "rev-parse", "HEAD~1"]).strip() +result = run_command(["../../gitdiff2yaml", old_commit_id, "-o", "temp.txt"]) +test1_res = compare_with_diff("../../4.ref.txt") +if test1_res: + print("Test 4 passed") +else: + print("Test 4 failed") +os.chdir("..") + +# Clean up +os.chdir("..") +shutil.rmtree(test_dir) diff --git a/clang/tools/dpct/gitdiff2yaml/tests.zip b/clang/tools/dpct/gitdiff2yaml/tests.zip new file mode 100644 index 000000000000..dec688f804e8 Binary files /dev/null and b/clang/tools/dpct/gitdiff2yaml/tests.zip differ diff --git a/clang/unittests/CMakeLists.txt b/clang/unittests/CMakeLists.txt index 85d265426ec8..1e23a76a88af 100644 --- a/clang/unittests/CMakeLists.txt +++ b/clang/unittests/CMakeLists.txt @@ -23,32 +23,36 @@ function(add_clang_unittest test_dirname) add_unittest(ClangUnitTests ${test_dirname} ${ARGN}) endfunction() -add_subdirectory(Basic) -add_subdirectory(Lex) -add_subdirectory(Driver) -if(CLANG_ENABLE_STATIC_ANALYZER) - add_subdirectory(Analysis) - add_subdirectory(StaticAnalyzer) -endif() -add_subdirectory(ASTMatchers) -add_subdirectory(AST) -add_subdirectory(CrossTU) -add_subdirectory(Tooling) -add_subdirectory(Format) -add_subdirectory(Frontend) -add_subdirectory(Rewrite) -add_subdirectory(Sema) -add_subdirectory(CodeGen) -if(HAVE_CLANG_REPL_SUPPORT) - add_subdirectory(Interpreter) -endif() -# FIXME: libclang unit tests are disabled on Windows due -# to failures, mostly in libclang.VirtualFileOverlay_*. -if(NOT WIN32 AND CLANG_TOOL_LIBCLANG_BUILD) - add_subdirectory(libclang) -endif() -add_subdirectory(DirectoryWatcher) -add_subdirectory(Index) -add_subdirectory(InstallAPI) -add_subdirectory(Serialization) -add_subdirectory(Support) +# SYCLomatic_CUSTOMIZATION begin +add_subdirectory(DPCT) +# SYCLomatic_CUSTOMIZATION else +# add_subdirectory(Basic) +# add_subdirectory(Lex) +# add_subdirectory(Driver) +# if(CLANG_ENABLE_STATIC_ANALYZER) +# add_subdirectory(Analysis) +# add_subdirectory(StaticAnalyzer) +# endif() +# add_subdirectory(ASTMatchers) +# add_subdirectory(AST) +# add_subdirectory(CrossTU) +# add_subdirectory(Tooling) +# add_subdirectory(Format) +# add_subdirectory(Frontend) +# add_subdirectory(Rewrite) +# add_subdirectory(Sema) +# add_subdirectory(CodeGen) +# if(HAVE_CLANG_REPL_SUPPORT) +# add_subdirectory(Interpreter) +# endif() +# # FIXME: libclang unit tests are disabled on Windows due +# # to failures, mostly in libclang.VirtualFileOverlay_*. +# if(NOT WIN32 AND CLANG_TOOL_LIBCLANG_BUILD) +# add_subdirectory(libclang) +# endif() +# add_subdirectory(DirectoryWatcher) +# add_subdirectory(Index) +# add_subdirectory(InstallAPI) +# add_subdirectory(Serialization) +# add_subdirectory(Support) +# SYCLomatic_CUSTOMIZATION end diff --git a/clang/unittests/DPCT/CMakeLists.txt b/clang/unittests/DPCT/CMakeLists.txt new file mode 100644 index 000000000000..2e2f8f04df62 --- /dev/null +++ b/clang/unittests/DPCT/CMakeLists.txt @@ -0,0 +1,17 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_unittest(DpctTests + ReMigrationTest.cpp + ) + +target_link_libraries(DpctTests + PRIVATE + DPCT + clangBasic + clangFrontend + clangRewrite + clangTooling + clangToolingCore + ) diff --git a/clang/unittests/DPCT/ReMigrationTest.cpp b/clang/unittests/DPCT/ReMigrationTest.cpp new file mode 100644 index 000000000000..cf59301d3ac2 --- /dev/null +++ b/clang/unittests/DPCT/ReMigrationTest.cpp @@ -0,0 +1,531 @@ +#include "../../lib/DPCT/IncMigration/ReMigration.h" +#include "clang/Tooling/Core/Replacement.h" +#include "clang/Tooling/Core/UnifiedPath.h" +#include "gtest/gtest.h" +#include + +using namespace llvm; +using namespace clang::tooling; +using namespace clang::dpct; + +class ReMigrationTest1 : public ::testing::Test { +protected: + void SetUp() override {} + void TearDown() override {} +}; + +TEST_F(ReMigrationTest1, calculateUpdatedRanges) { + // clang-format off + // Old base: +/* +aaa bb ccc +aaa bb ccc +aaa bb ccc +aaa bb ccc +aaa bb ccc +aaa bb ccc +aaa bb ccc +*/ + // Old migrated: +/* +aaa zzz ccc +ppp aaa bb ccc +aaa yyy ccc +aaa bb ccc +aaxxx bb ccc +aaa bb ccc +qqq aaa bb ccc +*/ + // New base: +/* +aaa bb ccc +Hi bb Everyone +aaa bb ccc +aaa bb Everyone +Hi Hello Everyone +aaa Hi bb ccc +aaa bb ccc +*/ + // clang-format on + + Replacements NewRepl; + Replacements Repls; + Replacements Expected; + + llvm::cantFail(NewRepl.add(Replacement("file1.cpp", 4, 2, "zzz"))); + llvm::cantFail(NewRepl.add(Replacement("file1.cpp", 11, 0, "ppp "))); + llvm::cantFail(NewRepl.add(Replacement("file1.cpp", 26, 2, "yyy"))); + llvm::cantFail(NewRepl.add(Replacement("file1.cpp", 46, 1, "xxx"))); + llvm::cantFail(NewRepl.add(Replacement("file1.cpp", 65, 1, "\nqqq"))); + + llvm::cantFail( + Repls.add(Replacement("file1.cpp", 11, 11, "Hi bb Everyone\n"))); + llvm::cantFail( + Repls.add(Replacement("file1.cpp", 33, 11, "aaa bb Everyone\n"))); + llvm::cantFail( + Repls.add(Replacement("file1.cpp", 44, 11, "Hi Hello Everyone\n"))); + llvm::cantFail( + Repls.add(Replacement("file1.cpp", 55, 11, "aaa Hi bb ccc\n"))); + + llvm::cantFail(Expected.add(Replacement("file1.cpp", 4, 2, "zzz"))); + llvm::cantFail(Expected.add(Replacement("file1.cpp", 30, 2, "yyy"))); + + Replacements Result = calculateUpdatedRanges(Repls, NewRepl); + ASSERT_EQ(Expected.size(), Result.size()); + size_t Num = Expected.size(); + auto ExpectedIt = Expected.begin(); + auto ResultIt = Result.begin(); + for (size_t i = 0; i < Num; ++i) { + EXPECT_EQ(ExpectedIt->getFilePath(), ResultIt->getFilePath()); + EXPECT_EQ(ExpectedIt->getOffset(), ResultIt->getOffset()); + EXPECT_EQ(ExpectedIt->getLength(), ResultIt->getLength()); + EXPECT_EQ(ExpectedIt->getReplacementText(), ResultIt->getReplacementText()); + ExpectedIt++; + ResultIt++; + } +} + +TEST_F(ReMigrationTest1, groupReplcementsByFile) { + std::string FilePath1 = + clang::tooling::UnifiedPath("file1.cpp").getCanonicalPath().str(); + std::string FilePath2 = + clang::tooling::UnifiedPath("file2.cpp").getCanonicalPath().str(); + std::string FilePath3 = + clang::tooling::UnifiedPath("file3.cpp").getCanonicalPath().str(); + + std::vector Repls = {{FilePath1, 0, 0, ""}, + {FilePath2, 3, 0, ""}, + {FilePath1, 1, 0, ""}, + {FilePath3, 4, 0, ""}, + {FilePath2, 2, 0, ""}}; + std::map> Expected = { + {FilePath1, + { + {FilePath1, 0, 0, ""}, + {FilePath1, 1, 0, ""}, + }}, + {FilePath2, + { + {FilePath2, 2, 0, ""}, + {FilePath2, 3, 0, ""}, + }}, + {FilePath3, {{FilePath3, 4, 0, ""}}}}; + + auto Result = groupReplcementsByFile(Repls); + EXPECT_EQ(Expected.size(), Result.size()); + + size_t Num = Expected.size(); + auto ExpectedIt = Expected.begin(); + auto ResultIt = Result.begin(); + for (size_t i = 0; i < Num; ++i) { + EXPECT_EQ(ExpectedIt->first, ResultIt->first); + EXPECT_EQ(ExpectedIt->second.size(), ResultIt->second.size()); + size_t SubNum = ExpectedIt->second.size(); + std::sort(ExpectedIt->second.begin(), ExpectedIt->second.end()); + std::sort(ResultIt->second.begin(), ResultIt->second.end()); + for (size_t j = 0; j < SubNum; ++j) { + EXPECT_EQ(ExpectedIt->second[j].getFilePath(), + ResultIt->second[j].getFilePath()); + EXPECT_EQ(ExpectedIt->second[j].getOffset(), + ResultIt->second[j].getOffset()); + EXPECT_EQ(ExpectedIt->second[j].getLength(), + ResultIt->second[j].getLength()); + EXPECT_EQ(ExpectedIt->second[j].getReplacementText(), + ResultIt->second[j].getReplacementText()); + } + ExpectedIt++; + ResultIt++; + } +} + +class ReMigrationTest2 : public ::testing::Test { +protected: + void SetUp() override {} + void TearDown() override {} + // clang-format off +/* +aaabbbbccc +dddeeeefff +ggghhhhiii +zzzzzzzzzz +yyyyyyyyyy +*/ + // clang-format on + static StringRef getLineStringUnittest(clang::tooling::UnifiedPath FilePath, + unsigned LineNumber) { + static std::vector LineString = { + "aaabbbbccc\n", "dddeeeefff\n", "ggghhhhiii\n", "zzzzzzzzzz\n", + "yyyyyyyyyy\n"}; + return StringRef(LineString[LineNumber - 1]); + } + static unsigned getLineNumberUnittest(clang::tooling::UnifiedPath FilePath, + unsigned Offset) { + static std::vector LineOffsets = {0, 11, 22, 33, 44}; + auto Iter = + std::upper_bound(LineOffsets.begin(), LineOffsets.end(), Offset); + if (Iter == LineOffsets.end()) + return LineOffsets.size(); + return std::distance(LineOffsets.begin(), Iter); + } + static unsigned + getLineBeginOffsetUnittest(clang::tooling::UnifiedPath FilePath, + unsigned LineNumber) { + static std::unordered_map LineOffsets = { + {1, 0}, {2, 11}, {3, 22}, {4, 33}, {5, 44}}; + return LineOffsets[LineNumber]; + } +}; + +TEST_F(ReMigrationTest2, splitReplInOrderToNotCrossLines) { + // Example: + // + // Original repl: + // (ccc\ndddeeeefff\nggg) =>(jjj\nkkk) + // (zz\nyy) =>(xx) + // + // Splitted repls: + // (ccc\n) =>(jjj\nkkk) + // (dddeeeefff\n) => "" + // (ggg) => "" + // (zz\n) => (xx) + // (yy) => "" + + getLineStringHook = this->getLineStringUnittest; + getLineNumberHook = this->getLineNumberUnittest; + getLineBeginOffsetHook = this->getLineBeginOffsetUnittest; + std::vector Repls = {Replacement("file1.cpp", 7, 18, "jjj\nkkk"), + Replacement("file1.cpp", 41, 5, "xx")}; + std::vector Expected = { + Replacement("file1.cpp", 7, 4, "jjj\nkkk"), + Replacement("file1.cpp", 11, 11, ""), Replacement("file1.cpp", 22, 3, ""), + Replacement("file1.cpp", 41, 3, "xx"), + Replacement("file1.cpp", 44, 2, "")}; + auto Result = splitReplInOrderToNotCrossLines(Repls); + std::sort(Result.begin(), Result.end()); + EXPECT_EQ(Expected, Result); +} + +class ReMigrationTest3 : public ::testing::Test { +protected: + void SetUp() override {} + void TearDown() override {} + // clang-format off +/* +aaa bb ccc +aaa bb ccc +aaa bb ccc +aaa bb ccc +aaa bb ccc +aaa bb ccc +aaa bb ccc +aaa bb ccc +aaa bb ccc +aaa bb ccc +*/ + // clang-format on + static StringRef getLineStringUnittest(clang::tooling::UnifiedPath FilePath, + unsigned LineNumber) { + static std::string S = "aaa bb ccc\n"; + return StringRef(S); + } + static unsigned getLineNumberUnittest(clang::tooling::UnifiedPath FilePath, + unsigned Offset) { + static std::vector LineOffsets = {0, 11, 22, 33, 44, + 55, 66, 77, 88, 99}; + auto Iter = + std::upper_bound(LineOffsets.begin(), LineOffsets.end(), Offset); + if (Iter == LineOffsets.end()) + return LineOffsets.size(); + return std::distance(LineOffsets.begin(), Iter); + } + static unsigned + getLineBeginOffsetUnittest(clang::tooling::UnifiedPath FilePath, + unsigned LineNumber) { + static std::unordered_map LineOffsets = { + {1, 0}, {2, 11}, {3, 22}, {4, 33}, {5, 44}, + {6, 55}, {7, 66}, {8, 77}, {9, 88}, {10, 99}}; + return LineOffsets[LineNumber]; + } +}; + +TEST_F(ReMigrationTest3, convertReplcementsLineString) { + // clang-format off + // After appling repls: +/* +aaa zzz ccc +ppp aaa bb ccc +aaa bb ccc +aaa yyy ccc +aaa bb ccc +aaa bb ccq +qqqaa bb ccc +aaa bb ddd + +eee bb ccc +*/ + // clang-format on + getLineStringHook = this->getLineStringUnittest; + getLineNumberHook = this->getLineNumberUnittest; + getLineBeginOffsetHook = this->getLineBeginOffsetUnittest; + std::vector Repls = { + Replacement("file1.cpp", 4, 2, "zzz"), + Replacement("file1.cpp", 64, 3, "q\nqqq"), + Replacement("file1.cpp", 33, 11, "aaa yyy ccc\n"), + Replacement("file1.cpp", 11, 0, "ppp "), + Replacement("file1.cpp", 84, 18, "ddd\neee")}; + std::map Expected = {{1, "aaa zzz ccc\n"}, + {2, "ppp aaa bb ccc\n"}, + {4, "aaa yyy ccc\n"}, + {6, "aaa bb ccq\nqqq"}, + {7, "aa bb ccc\n"}, + {8, "aaa bb ddd\neee"}, + {9, ""}, + {10, " bb ccc\n"}}; + auto Result = convertReplcementsLineString(Repls); + EXPECT_EQ(Expected, Result); +} + +TEST_F(ReMigrationTest3, mergeMapsByLine) { + getLineBeginOffsetHook = this->getLineBeginOffsetUnittest; + getLineStringHook = this->getLineStringUnittest; + std::map MapA = {{1, "zzzzz\n"}, {3, "xxxx1\n"}, + {4, "wwww1\n"}, {5, "vvvvv\n"}, + {7, "ppppp\n"}, {9, "iiijjjkkk\n"}}; + std::map MapB = { + {2, "yyyyy\n"}, {3, "xxxx2\n"}, {4, "wwww2\n"}, {7, ""}, {8, "qqqqq"}}; + UnifiedPath FilePath("test.cu"); + auto Result = mergeMapsByLine(MapA, MapB, FilePath); + std::sort(Result.begin(), Result.end()); + + std::vector Expected = { + Replacement("test.cu", 0, 11, "zzzzz\n"), + Replacement("test.cu", 11, 11, "yyyyy\n"), + Replacement("test.cu", 22, 22, + "<<<<<<<\nxxxx1\nwwww1\n=======\nxxxx2\nwwww2\n>>>>>>>\n"), + Replacement("test.cu", 44, 11, "vvvvv\n"), + Replacement("test.cu", 66, 11, "<<<<<<<\nppppp\n=======\n>>>>>>>\n"), + Replacement("test.cu", 77, 11, "qqqqq"), + Replacement("test.cu", 88, 11, "iiijjjkkk\n"), + }; + + ASSERT_EQ(Expected.size(), Result.size()); + size_t Num = Expected.size(); + auto ExpectedIt = Expected.begin(); + auto ResultIt = Result.begin(); + for (size_t i = 0; i < Num; ++i) { + EXPECT_EQ(ExpectedIt->getFilePath(), ResultIt->getFilePath()); + EXPECT_EQ(ExpectedIt->getOffset(), ResultIt->getOffset()); + EXPECT_EQ(ExpectedIt->getLength(), ResultIt->getLength()); + EXPECT_EQ(ExpectedIt->getReplacementText(), ResultIt->getReplacementText()); + ExpectedIt++; + ResultIt++; + } + + EXPECT_EQ(Expected, Result); +} + +TEST_F(ReMigrationTest1, mergeC1AndC2) { + std::vector Repl_C1 = { + Replacement("file1.cu", 10, 0, "aaa"), + Replacement("file1.cu", 11, 1, "bbb"), + Replacement("file1.cu", 20, 20, "ccc"), + Replacement("file2.cu", 20, 20, "zzz"), + Replacement("file2.cu", 40, 1, "yyy"), + Replacement("file3.cpp", 0, 1, "a"), + Replacement("file4.cpp", 2, 1, "b"), + }; + GitDiffChanges Repl_C2; + Repl_C2.ModifyFileHunks = { + Replacement("file1.dp.cpp", 10, 0, "ddd"), + Replacement("file1.dp.cpp", 10, 2, "eee"), + Replacement("file1.dp.cpp", 40, 2, "fff"), + Replacement("file2.dp.cpp", 21, 3, "xxx"), + Replacement("file2.dp.cpp", 41, 1, "www"), + Replacement("file3.cpp.dp.cpp", 10, 1, "a"), + Replacement("file4.cpp", 12, 1, "b"), + }; + const std::map FileNameMap = { + {"file1.dp.cpp", "file1.cu"}, + {"file2.dp.cpp", "file2.cu"}, + {"file3.cpp.dp.cpp", "file3.cpp"}, + {"file4.cpp", "file4.cpp"}}; + + std::vector Result = mergeC1AndC2(Repl_C1, Repl_C2, FileNameMap); + std::vector Expected = { + Replacement("file1.cu", 10, 0, "aaa"), + Replacement("file1.cu", 10, 0, "ddd"), + Replacement("file1.cu", 10, 2, "eee"), + Replacement("file1.cu", 20, 20, "ccc"), + Replacement("file1.cu", 40, 2, "fff"), + Replacement("file2.cu", 21, 3, "xxx"), + Replacement("file2.cu", 40, 1, "yyy"), + Replacement("file2.cu", 41, 1, "www"), + Replacement("file3.cpp", 0, 1, "a"), + Replacement("file4.cpp", 2, 1, "b"), + Replacement("file3.cpp", 10, 1, "a"), + Replacement("file4.cpp", 12, 1, "b"), + }; + std::sort(Result.begin(), Result.end()); + std::sort(Expected.begin(), Expected.end()); + + ASSERT_EQ(Expected.size(), Result.size()); + size_t Num = Expected.size(); + auto ExpectedIt = Expected.begin(); + auto ResultIt = Result.begin(); + for (size_t i = 0; i < Num; ++i) { + EXPECT_EQ(ExpectedIt->getFilePath(), ResultIt->getFilePath()); + EXPECT_EQ(ExpectedIt->getOffset(), ResultIt->getOffset()); + EXPECT_EQ(ExpectedIt->getLength(), ResultIt->getLength()); + EXPECT_EQ(ExpectedIt->getReplacementText(), ResultIt->getReplacementText()); + ExpectedIt++; + ResultIt++; + } +} + +#if 0 +class ReMigrationTest4 : public ::testing::Test { +protected: + inline static std::vector CUDACodeV1 = {}; + inline static std::vector LineOffsets = {}; + void SetUp() override { + // clang-format off + const std::string CUDACode = R"(#include + +#define CUDA_CHECK(call) \ + do { \ + cudaError_t err = call; \ + if (err != cudaSuccess) { \ + printf("CUDA error in %s at line %d: %s\n", __FILE__, __LINE__, \ + cudaGetErrorString(err)); \ + exit(EXIT_FAILURE); \ + } \ + } while (0) + +void foo() { + float *f; + CUDA_CHECK(cudaMalloc(&f, 100 * sizeof(float))); + float *g; + CUDA_CHECK(cudaMalloc(&g, 100 * sizeof(float))); + cudaMemcpy(f, g, 100 * sizeof(float), cudaMemcpyDeviceToDevice); + cudaDeviceSynchronize(); + cudaFree(f); + cudaFree(g); +} +)"; + // clang-format on + std::istringstream ISS(CUDACode); + std::string Line; + while (std::getline(ISS, Line)) { + if (ISS.eof() && Line.empty()) + break; + Line += '\n'; + LineOffsets.push_back(LineOffsets.empty() ? 0 + : LineOffsets.back() + + CUDACodeV1.back().size()); + CUDACodeV1.push_back(Line); + } + LineOffsets.insert(LineOffsets.begin(), 0); + } + void TearDown() override { CUDACodeV1.clear(); } + static StringRef getLineStringUnittest(clang::tooling::UnifiedPath FilePath, + unsigned LineNumber) { + return StringRef(CUDACodeV1[LineNumber - 1]); + } + static unsigned getLineNumberUnittest(clang::tooling::UnifiedPath FilePath, + unsigned Offset) { + auto Iter = + std::upper_bound(LineOffsets.begin(), LineOffsets.end(), Offset); + if (Iter == LineOffsets.end()) + return LineOffsets.size(); + return std::distance(LineOffsets.begin(), Iter); + } + static unsigned + getLineBeginOffsetUnittest(clang::tooling::UnifiedPath FilePath, + unsigned LineNumber) { + return LineOffsets[LineNumber]; + } +}; + +TEST_F(ReMigrationTest4, reMigrationMerge) { + getLineStringHook = this->getLineStringUnittest; + getLineNumberHook = this->getLineNumberUnittest; + getLineBeginOffsetHook = this->getLineBeginOffsetUnittest; + + std::vector Repl_C1 = { + Replacement("test.cu", 0, 0, + "#include \n#include \n"), + Replacement( + "test.cu", 20, 0, + "/*\nDPCT1009:0: SYCL reports errors using exceptions and does not " + "use error codes. Please replace the \"get_error_string_dummy(...)\" " + "with a real error-handling function.\n*/\n"), + Replacement("test.cu", 186, 11, "dpct::err0"), + Replacement("test.cu", 267, 325, ""), + Replacement("test.cu", 694, 0, " try "), + Replacement( + "test.cu", 695, 0, + "\n dpct::device_ext &dev_ct1 = dpct::get_current_device();\n " + "sycl::queue &q_ct1 = dev_ct1.in_order_queue();"), + Replacement( + "test.cu", 721, 35, + "DPCT_CHECK_ERROR(f = sycl::malloc_device(100, q_ct1))"), + Replacement( + "test.cu", 784, 35, + "DPCT_CHECK_ERROR(g = sycl::malloc_device(100, q_ct1))"), + Replacement("test.cu", 824, 63, + "q_ct1.memcpy(f, g, 100 * sizeof(float))"), + Replacement("test.cu", 891, 23, "dev_ct1.queues_wait_and_throw()"), + Replacement("test.cu", 918, 11, "dpct::dpct_free(f, q_ct1)"), + Replacement("test.cu", 933, 11, "dpct::dpct_free(g, q_ct1)"), + Replacement("test.cu", 947, 0, + "\ncatch (sycl::exception const &exc) {\n std::cerr << " + "exc.what() << \"Exception caught at file:\" << __FILE__ << " + "\", line:\" << __LINE__ << std::endl;\n std::exit(1);\n}")}; + + std::vector Repl_C2 = { + Replacement("test.dp.cpp", 70, 527, "void foo() {\n"), + Replacement("test.dp.cpp", 716, 76, + " f = sycl::malloc_device(100, q_ct1);\n"), + Replacement("test.dp.cpp", 804, 76, + " g = sycl::malloc_device(100, q_ct1);\n")}; + + std::vector Repl_A = { + Replacement("test.cu", 696, 63, ""), + Replacement( + "test.cu", 822, 67, + " float *h;\n CUDA_CHECK(cudaMalloc(&h, 100 * sizeof(float)));\n"), + Replacement("test.cu", 916, 15, ""), + Replacement("test.cu", 946, 0, " cudaFree(h);\n")}; + + std::vector Repl_B = { + Replacement("test.cu", 0, 0, + "#include \n#include \n"), + Replacement( + "test.cu", 20, 0, + "/*\nDPCT1009:0: SYCL reports errors using exceptions and does not " + "use error codes. Please replace the \"get_error_string_dummy(...)\" " + "with a real error-handling function.\n*/\n"), + Replacement("test.cu", 186, 11, "dpct::err0"), + Replacement("test.cu", 267, 325, ""), + Replacement("test.cu", 694, 0, " try "), + Replacement( + "test.cu", 695, 0, + "\n dpct::device_ext &dev_ct1 = dpct::get_current_device();\n " + "sycl::queue &q_ct1 = dev_ct1.in_order_queue();"), + Replacement( + "test.cu", 721, 35, + "DPCT_CHECK_ERROR(g = sycl::malloc_device(100, q_ct1))"), + Replacement( + "test.cu", 784, 35, + "DPCT_CHECK_ERROR(h = sycl::malloc_device(100, q_ct1))"), + Replacement("test.cu", 824, 23, "dev_ct1.queues_wait_and_throw()"), + Replacement("test.cu", 851, 11, "dpct::dpct_free(g, q_ct1)"), + Replacement("test.cu", 866, 11, "dpct::dpct_free(h, q_ct1)"), + Replacement("test.cu", 880, 0, + "\ncatch (sycl::exception const &exc) {\n std::cerr << " + "exc.what() << \"Exception caught at file:\" << __FILE__ << " + "\", line:\" << __LINE__ << std::endl;\n std::exit(1);\n}")}; + + ASSERT_EQ(true, false); // Placeholder for actual test logic +} +#endif