From 598e83191f9814066ba701f16ca19197de8ab801 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 30 Nov 2024 17:12:11 +0300 Subject: [PATCH] [0.0.23] Upgrade LLVM to 19.1.4 Added base implementation of CodeEvaluator class which able to evaluate constexpr code. Small refactoring in CodeAnalyzer. Fixed possible bug with id0.hpp file when it contents could be corrupted. Used better approach from LLVM with virtual files. --- .github/workflows/build.yml | 6 +- ...CollectConstexprVariableEvalResultAction.h | 20 ++ LLVM/include/RG3/LLVM/CodeEvaluator.h | 42 +++ .../RG3/LLVM/CompilerInstanceFactory.h | 23 ++ .../CollectConstexprVariableEvalResult.h | 22 ++ ...llectConstexprVariableEvalResultAction.cpp | 15 + LLVM/source/CodeAnalyzer.cpp | 292 +----------------- LLVM/source/CodeEvaluator.cpp | 93 ++++++ LLVM/source/CompilerInstanceFactory.cpp | 289 +++++++++++++++++ .../CollectConstexprVariableEvalResult.cpp | 70 +++++ Tests/Unit/source/Tests_CodeEvaluator.cpp | 57 ++++ 11 files changed, 642 insertions(+), 287 deletions(-) create mode 100644 LLVM/include/RG3/LLVM/Actions/CollectConstexprVariableEvalResultAction.h create mode 100644 LLVM/include/RG3/LLVM/CodeEvaluator.h create mode 100644 LLVM/include/RG3/LLVM/CompilerInstanceFactory.h create mode 100644 LLVM/include/RG3/LLVM/Consumers/CollectConstexprVariableEvalResult.h create mode 100644 LLVM/source/Actions/CollectConstexprVariableEvalResultAction.cpp create mode 100644 LLVM/source/CodeEvaluator.cpp create mode 100644 LLVM/source/CompilerInstanceFactory.cpp create mode 100644 LLVM/source/Consumers/CollectConstexprVariableEvalResult.cpp create mode 100644 Tests/Unit/source/Tests_CodeEvaluator.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 24616df..fdae71f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: cxx: "cl", boost_toolset: msvc, cmake_generator: "Visual Studio 17 2022", - llvm_tag: "llvmorg-18.1.8", + llvm_tag: "llvmorg-19.1.4", python_version: "3.10", os_version: "2022", artifact_id: "RG3_Windows" @@ -35,7 +35,7 @@ jobs: cxx: "g++-13", boost_toolset: gcc, cmake_generator: "Ninja", - llvm_tag: "llvmorg-18.1.8", + llvm_tag: "llvmorg-19.1.4", python_version: "3.10", os_version: "24.04", artifact_id: "RG3_Linux" @@ -48,7 +48,7 @@ jobs: cxx: "clang++", boost_toolset: gcc, cmake_generator: "Ninja", - llvm_tag: "llvmorg-18.1.8", + llvm_tag: "llvmorg-19.1.4", python_version: "3.10", os_version: "13", artifact_id: "RG3_macOS" diff --git a/LLVM/include/RG3/LLVM/Actions/CollectConstexprVariableEvalResultAction.h b/LLVM/include/RG3/LLVM/Actions/CollectConstexprVariableEvalResultAction.h new file mode 100644 index 0000000..53a3091 --- /dev/null +++ b/LLVM/include/RG3/LLVM/Actions/CollectConstexprVariableEvalResultAction.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include +#include +#include + + + +namespace rg3::llvm::actions +{ + struct CollectConstexprVariableEvalResultAction : public clang::ASTFrontendAction + { + std::unordered_set aExpectedVariables {}; + std::unordered_map* pEvaluatedVariables { nullptr }; + + std::unique_ptr CreateASTConsumer(clang::CompilerInstance& /*compilerInstance*/, clang::StringRef /*file*/) override; + }; +} \ No newline at end of file diff --git a/LLVM/include/RG3/LLVM/CodeEvaluator.h b/LLVM/include/RG3/LLVM/CodeEvaluator.h new file mode 100644 index 0000000..b7a5277 --- /dev/null +++ b/LLVM/include/RG3/LLVM/CodeEvaluator.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace rg3::llvm +{ + using VariableValue = std::variant; + + struct CodeEvaluateResult + { + AnalyzerResult::CompilerIssuesVector vIssues; + std::unordered_map mOutputs; + + explicit operator bool() const noexcept; + }; + + class CodeEvaluator + { + public: + CodeEvaluator(); + CodeEvaluator(CompilerConfig compilerConfig); + + void setCompilerEnvironment(const CompilerEnvironment& env); + CompilerConfig& getCompilerConfig(); + + CodeEvaluateResult evaluateCode(const std::string& sCode, const std::vector& aCaptureOutputVariables); + + private: + std::optional m_env; + CompilerConfig m_compilerConfig; + std::string m_sSourceCode {}; + }; +} \ No newline at end of file diff --git a/LLVM/include/RG3/LLVM/CompilerInstanceFactory.h b/LLVM/include/RG3/LLVM/CompilerInstanceFactory.h new file mode 100644 index 0000000..8171498 --- /dev/null +++ b/LLVM/include/RG3/LLVM/CompilerInstanceFactory.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include +#include + +#include +#include +#include + + +namespace rg3::llvm +{ + struct CompilerInstanceFactory + { + static void makeInstance( + clang::CompilerInstance* pOutInstance, + const std::variant& sInput, + const CompilerConfig& sCompilerConfig, + const CompilerEnvironment* pCompilerEnv = nullptr); + }; +} \ No newline at end of file diff --git a/LLVM/include/RG3/LLVM/Consumers/CollectConstexprVariableEvalResult.h b/LLVM/include/RG3/LLVM/Consumers/CollectConstexprVariableEvalResult.h new file mode 100644 index 0000000..52c183f --- /dev/null +++ b/LLVM/include/RG3/LLVM/Consumers/CollectConstexprVariableEvalResult.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include +#include +#include + + +namespace rg3::llvm::consumers +{ + struct CollectConstexprVariableEvalResult : public clang::ASTConsumer + { + std::unordered_set aExpectedVariables {}; + std::unordered_map* pEvaluatedVariables { nullptr }; + + public: + CollectConstexprVariableEvalResult(); + + void HandleTranslationUnit(clang::ASTContext& ctx) override; + }; +} \ No newline at end of file diff --git a/LLVM/source/Actions/CollectConstexprVariableEvalResultAction.cpp b/LLVM/source/Actions/CollectConstexprVariableEvalResultAction.cpp new file mode 100644 index 0000000..0face2f --- /dev/null +++ b/LLVM/source/Actions/CollectConstexprVariableEvalResultAction.cpp @@ -0,0 +1,15 @@ +#include +#include + + +namespace rg3::llvm::actions +{ + std::unique_ptr CollectConstexprVariableEvalResultAction::CreateASTConsumer(clang::CompilerInstance&, clang::StringRef) + { + auto pConsumer = std::make_unique(); + pConsumer->aExpectedVariables = aExpectedVariables; + pConsumer->pEvaluatedVariables = pEvaluatedVariables; + + return std::move(pConsumer); + } +} \ No newline at end of file diff --git a/LLVM/source/CodeAnalyzer.cpp b/LLVM/source/CodeAnalyzer.cpp index 403962b..1d2daa6 100644 --- a/LLVM/source/CodeAnalyzer.cpp +++ b/LLVM/source/CodeAnalyzer.cpp @@ -1,81 +1,17 @@ #include #include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include #include -#include /// Auto-generated by CMake - #include #include namespace rg3::llvm { - struct Visitor - { - clang::FrontendOptions& compilerOptions; - - void operator()(const std::filesystem::path& path) - { - std::string absolutePath = std::filesystem::absolute(path).string(); - - compilerOptions.Inputs.push_back( - clang::FrontendInputFile( - absolutePath, - clang::InputKind( - clang::Language::CXX, - clang::InputKind::Format::Source, - false, // NOT preprocessed - clang::InputKind::HeaderUnitKind::HeaderUnit_User, - true // is Header = true - ), - false // IsSystem = false - ) - ); - } - - void operator()(const std::string& buffer) - { - compilerOptions.Inputs.push_back( - clang::FrontendInputFile( - ::llvm::MemoryBufferRef( - ::llvm::StringRef(buffer.data()), - ::llvm::StringRef("id0.hpp") - ), - clang::InputKind( - clang::Language::CXX, - clang::InputKind::Format::Source, - false, // NOT preprocessed - clang::InputKind::HeaderUnitKind::HeaderUnit_User, - true // is Header = true - ), - false // IsSystem = false - ) - ); - } - }; - AnalyzerResult::operator bool() const { return std::count_if( @@ -145,8 +81,9 @@ namespace rg3::llvm AnalyzerResult CodeAnalyzer::analyze() { AnalyzerResult result; - const CompilerEnvironment* pCompilerEnv = nullptr; - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + CompilerEnvironment* pCompilerEnv = nullptr; + // Run platform env detector if (!m_env.has_value()) { @@ -163,228 +100,15 @@ namespace rg3::llvm } pCompilerEnv = &m_env.value(); + clang::CompilerInstance compilerInstance {}; + CompilerInstanceFactory::makeInstance(&compilerInstance, m_source, m_compilerConfig, pCompilerEnv); - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - clang::CompilerInstance compilerInstance; - compilerInstance.createDiagnostics(); - - // Create custom diagnostics consumer and pass it into CompilerInstance + // Add diagnostics consumer { auto errorCollector = std::make_unique(result); compilerInstance.getDiagnostics().setClient(errorCollector.release(), false); } - // Set up FileManager and SourceManager - compilerInstance.createFileManager(); - compilerInstance.createSourceManager(compilerInstance.getFileManager()); - - std::vector vProxyArgs = m_compilerConfig.vCompilerArgs; -#ifdef _WIN32 - vProxyArgs.emplace_back("-fms-extensions"); - vProxyArgs.emplace_back("-fdelayed-template-parsing"); - vProxyArgs.emplace_back("-fms-compatibility-version=19"); -#endif - - if (auto it = std::find(vProxyArgs.begin(), vProxyArgs.end(), "-x"); it != vProxyArgs.end()) - { - // need to remove this iter and next - auto next = std::next(it); - - if (next != std::end(vProxyArgs)) - { - // remove next - vProxyArgs.erase(next); - } - - // and remove this - vProxyArgs.erase(it); - } - - // We will append "-x c++-header" option always - vProxyArgs.emplace_back("-x"); - vProxyArgs.emplace_back("c++-header"); - - std::vector vCompilerArgs; - vCompilerArgs.resize(vProxyArgs.size()); - - for (size_t i = 0; i < vProxyArgs.size(); i++) - { - vCompilerArgs[i] = vProxyArgs[i].c_str(); - } - - // Set up CompilerInvocation - auto invocation = std::make_shared(); - clang::CompilerInvocation::CreateFromArgs( - *invocation, - ::llvm::ArrayRef(vCompilerArgs.data(), vCompilerArgs.size()), - compilerInstance.getDiagnostics() - ); - - // Use C++20 - clang::LangStandard::Kind langKind; - auto& langOptions = invocation->getLangOpts(); - - langOptions.CPlusPlus = 1; - - switch (m_compilerConfig.cppStandard) - { - case CxxStandard::CC_11: - langOptions.CPlusPlus11 = 1; - langKind = clang::LangStandard::Kind::lang_cxx11; - break; - case CxxStandard::CC_14: - langOptions.CPlusPlus14 = 1; - langKind = clang::LangStandard::Kind::lang_cxx14; - break; - case CxxStandard::CC_17: - langOptions.CPlusPlus17 = 1; - langKind = clang::LangStandard::Kind::lang_cxx17; - break; - case CxxStandard::CC_20: - langOptions.CPlusPlus20 = 1; - langKind = clang::LangStandard::Kind::lang_cxx20; - break; - case CxxStandard::CC_23: - langOptions.CPlusPlus23 = 1; - langKind = clang::LangStandard::Kind::lang_cxx23; - break; - case CxxStandard::CC_26: - langOptions.CPlusPlus26 = 1; - langKind = clang::LangStandard::Kind::lang_cxx26; - break; - default: - langOptions.CPlusPlus11 = 1; - langKind = clang::LangStandard::Kind::lang_cxx11; - break; - } - - langOptions.LangStd = langKind; - langOptions.IsHeaderFile = true; // NOTE: Maybe we should use flag here? - -#ifdef _WIN32 - compilerInstance.getPreprocessorOpts().addMacroDef("_MSC_VER=1932"); - compilerInstance.getPreprocessorOpts().addMacroDef("_MSC_FULL_VER=193231329"); - compilerInstance.getPreprocessorOpts().addMacroDef("_MSC_EXTENSIONS"); - - /** - * Workaround: it's workaround for MSVC 2022 with yvals_core.h which send static assert when Clang version less than 17.x.x - * Example : static assertion failed: error STL1000: Unexpected compiler version, expected Clang 17.0.0 or newer. at C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.40.33807\include\yvals_core.h:898 - * - * This macro block static assertion and should help us, but this part of code MUST be removed after RG3 migrates to latest LLVM & Clang - */ - compilerInstance.getPreprocessorOpts().addMacroDef("_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH=1"); -#endif - -#ifdef __APPLE__ - // This should be enough? - compilerInstance.getPreprocessorOpts().addMacroDef("__GCC_HAVE_DWARF2_CFI_ASM=1"); -#endif - - std::shared_ptr targetOpts = nullptr; - - // Setup triple -#if !defined(__APPLE__) - if (pCompilerEnv->triple.empty()) - { - // Use default triple - targetOpts = std::make_shared(); - targetOpts->Triple = ::llvm::sys::getDefaultTargetTriple(); - clang::TargetInfo* targetInfo = clang::TargetInfo::CreateTargetInfo(compilerInstance.getDiagnostics(), targetOpts); - compilerInstance.setTarget(targetInfo); - - auto triple = targetInfo->getTriple(); - - std::vector vIncs; - clang::LangOptions::setLangDefaults(langOptions, clang::Language::CXX, triple, vIncs, langKind); - } - else - { - targetOpts = std::make_shared(); - targetOpts->Triple = ::llvm::Triple::normalize(pCompilerEnv->triple); - clang::TargetInfo* targetInfo = clang::TargetInfo::CreateTargetInfo(compilerInstance.getDiagnostics(), targetOpts); - compilerInstance.setTarget(targetInfo); - - auto triple = targetInfo->getTriple(); - - std::vector vIncs; - clang::LangOptions::setLangDefaults(langOptions, clang::Language::CXX, triple, vIncs, langKind); - } -#else - // On Apple we should use default triple instead of detect it at runtime - ::llvm::Triple triple(::llvm::sys::getDefaultTargetTriple()); - compilerInstance.getTargetOpts().Triple = triple.str(); - compilerInstance.setTarget(clang::TargetInfo::CreateTargetInfo(compilerInstance.getDiagnostics(), std::make_shared(compilerInstance.getTargetOpts()))); -#endif - - compilerInstance.setInvocation(invocation); - - // Set up FrontendOptions - clang::FrontendOptions &opts = compilerInstance.getFrontendOpts(); - opts.ProgramAction = clang::frontend::ParseSyntaxOnly; - opts.SkipFunctionBodies = static_cast(m_compilerConfig.bSkipFunctionBodies); - - opts.Inputs.clear(); - - // Prepare compiler instance - { - Visitor v { opts }; - std::visit(v, m_source); - } - - // Set macros - clang::PreprocessorOptions& preprocessorOptions = compilerInstance.getPreprocessorOpts(); - for (const auto& compilerDef : m_compilerConfig.vCompilerDefs) - { - preprocessorOptions.addMacroDef(compilerDef); - } - - // Add builtins - preprocessorOptions.addMacroDef("__RG3__=1"); - preprocessorOptions.addMacroDef("__RG3_COMMIT__=\"" RG3_BUILD_HASH "\""); - preprocessorOptions.addMacroDef("__RG3_BUILD_DATE__=\"" __DATE__ "\""); - -#ifdef __APPLE__ - // For apple only. They cares about GNUC? Idk & I don't care - preprocessorOptions.addMacroDef("__GNUC__=4"); -#endif - - // Setup header dirs source - clang::HeaderSearchOptions& headerSearchOptions = compilerInstance.getHeaderSearchOpts(); - { - for (const auto& sysInc : pCompilerEnv->config.vSystemIncludes) - { - const auto absolutePath = std::filesystem::absolute(sysInc.sFsLocation); - - clang::frontend::IncludeDirGroup group = clang::frontend::IncludeDirGroup::Angled; - - if (sysInc.eKind == IncludeKind::IK_SYSROOT) - { - // ignore sysroot here - continue; - } - - if (sysInc.eKind == IncludeKind::IK_SYSTEM) - { - group = clang::frontend::IncludeDirGroup::System; - } - - if (sysInc.eKind == IncludeKind::IK_C_SYSTEM) - { - group = clang::frontend::IncludeDirGroup::ExternCSystem; - } - - headerSearchOptions.AddPath(absolutePath.string(), group, false, true); - } - - for (const auto& incInfo : m_compilerConfig.vIncludes) - { - // Convert path to absolute - const auto absolutePath = std::filesystem::absolute(incInfo.sFsLocation); - headerSearchOptions.AddPath(absolutePath.string(), clang::frontend::IncludeDirGroup::Angled, false, true); - } - } - // Run actions { rg3::llvm::actions::ExtractTypesFromTUAction findTypesAction { result.vFoundTypes, m_compilerConfig }; diff --git a/LLVM/source/CodeEvaluator.cpp b/LLVM/source/CodeEvaluator.cpp new file mode 100644 index 0000000..0d04f44 --- /dev/null +++ b/LLVM/source/CodeEvaluator.cpp @@ -0,0 +1,93 @@ +#include +#include + +#include +#include + +#include + +#include +#include + +#include +#include + + +namespace rg3::llvm +{ + CodeEvaluateResult::operator bool() const noexcept + { + return std::count_if( + vIssues.begin(), + vIssues.end(), + [](const AnalyzerResult::CompilerIssue& issue) -> bool { + return issue.kind != AnalyzerResult::CompilerIssue::IssueKind::IK_INFO && + issue.kind != AnalyzerResult::CompilerIssue::IssueKind::IK_NONE; + }) == 0; + } + + CodeEvaluator::CodeEvaluator() = default; + + CodeEvaluator::CodeEvaluator(rg3::llvm::CompilerConfig compilerConfig) + : m_compilerConfig(std::move(compilerConfig)) + { + } + + void CodeEvaluator::setCompilerEnvironment(const rg3::llvm::CompilerEnvironment& env) + { + m_env = env; + } + + CompilerConfig& CodeEvaluator::getCompilerConfig() + { + return m_compilerConfig; + } + + CodeEvaluateResult CodeEvaluator::evaluateCode(const std::string& sCode, const std::vector& aCaptureOutputVariables) + { + CodeEvaluateResult sResult {}; + AnalyzerResult sTempResult {}; + CompilerEnvironment* pCompilerEnv = nullptr; + + m_sSourceCode = sCode; + + // Run platform env detector + if (!m_env.has_value()) + { + const auto compilerEnvironment = CompilerConfigDetector::detectSystemCompilerEnvironment(); + if (auto pEnvFailure = std::get_if(&compilerEnvironment)) + { + // Fatal error + sResult.vIssues.emplace_back(AnalyzerResult::CompilerIssue { AnalyzerResult::CompilerIssue::IssueKind::IK_ERROR, m_sSourceCode, 0, 0, pEnvFailure->message }); + return sResult; + } + + // Override env + m_env = *std::get_if(&compilerEnvironment); + } + + pCompilerEnv = &m_env.value(); + clang::CompilerInstance compilerInstance {}; + CompilerInstanceFactory::makeInstance(&compilerInstance, m_sSourceCode, m_compilerConfig, pCompilerEnv); + + // Add diagnostics consumer + { + auto errorCollector = std::make_unique(sTempResult); + compilerInstance.getDiagnostics().setClient(errorCollector.release(), false); + } + + // Run actions + { + rg3::llvm::actions::CollectConstexprVariableEvalResultAction collectConstexprVariableEvalResultAction {}; + collectConstexprVariableEvalResultAction.aExpectedVariables = std::unordered_set { aCaptureOutputVariables.begin(), aCaptureOutputVariables.end() }; + collectConstexprVariableEvalResultAction.pEvaluatedVariables = &sResult.mOutputs; + + compilerInstance.ExecuteAction(collectConstexprVariableEvalResultAction); + } + + // Copy result + std::copy(sTempResult.vIssues.begin(), sTempResult.vIssues.end(), std::back_inserter(sResult.vIssues)); + + return sResult; + } +} \ No newline at end of file diff --git a/LLVM/source/CompilerInstanceFactory.cpp b/LLVM/source/CompilerInstanceFactory.cpp new file mode 100644 index 0000000..e1f0989 --- /dev/null +++ b/LLVM/source/CompilerInstanceFactory.cpp @@ -0,0 +1,289 @@ +#include +#include /// Auto-generated by CMake + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +namespace rg3::llvm +{ + struct Visitor + { + clang::FrontendOptions& compilerOptions; + clang::CompilerInstance* pCompilerInstance { nullptr }; + + void operator()(const std::filesystem::path& path) + { + std::string absolutePath = std::filesystem::absolute(path).string(); + + compilerOptions.Inputs.push_back( + clang::FrontendInputFile( + absolutePath, + clang::InputKind( + clang::Language::CXX, + clang::InputKind::Format::Source, + false, // NOT preprocessed + clang::InputKind::HeaderUnitKind::HeaderUnit_User, + true // is Header = true + ), + false // IsSystem = false + ) + ); + } + + void operator()(const std::string& buffer) + { + std::string sanitizedBuffer; + std::remove_copy_if( + buffer.begin(), buffer.end(), + std::back_inserter(sanitizedBuffer), + [](char c) { return c == '\0'; } + ); + + auto pMemBuffer = ::llvm::MemoryBuffer::getMemBufferCopy(sanitizedBuffer, "id0.hpp"); + pCompilerInstance->getPreprocessorOpts().addRemappedFile("id0.hpp", pMemBuffer.release()); + + compilerOptions.Inputs.push_back(clang::FrontendInputFile("id0.hpp", clang::Language::CXX)); + } + }; + + void CompilerInstanceFactory::makeInstance(clang::CompilerInstance* pOutInstance, + const std::variant& sInput, + const rg3::llvm::CompilerConfig& sCompilerConfig, + const rg3::llvm::CompilerEnvironment* pCompilerEnv) + { + pOutInstance->createDiagnostics(); + + // Set up FileManager and SourceManager + pOutInstance->createFileManager(); + pOutInstance->createSourceManager(pOutInstance->getFileManager()); + + std::vector vProxyArgs = sCompilerConfig.vCompilerArgs; +#ifdef _WIN32 + vProxyArgs.emplace_back("-fms-extensions"); + vProxyArgs.emplace_back("-fdelayed-template-parsing"); + vProxyArgs.emplace_back("-fms-compatibility-version=19"); +#endif + + if (auto it = std::find(vProxyArgs.begin(), vProxyArgs.end(), "-x"); it != vProxyArgs.end()) + { + // need to remove this iter and next + auto next = std::next(it); + + if (next != std::end(vProxyArgs)) + { + // remove next + vProxyArgs.erase(next); + } + + // and remove this + vProxyArgs.erase(it); + } + + // We will append "-x c++-header" option always + vProxyArgs.emplace_back("-x"); + vProxyArgs.emplace_back("c++-header"); + + std::vector vCompilerArgs; + vCompilerArgs.resize(vProxyArgs.size()); + + for (size_t i = 0; i < vProxyArgs.size(); i++) + { + vCompilerArgs[i] = vProxyArgs[i].c_str(); + } + + // Set up CompilerInvocation + auto invocation = std::make_shared(); + clang::CompilerInvocation::CreateFromArgs( + *invocation, + ::llvm::ArrayRef(vCompilerArgs.data(), vCompilerArgs.size()), + pOutInstance->getDiagnostics() + ); + + // Use C++20 + clang::LangStandard::Kind langKind; + auto& langOptions = invocation->getLangOpts(); + + langOptions.CPlusPlus = 1; + + switch (sCompilerConfig.cppStandard) + { + case CxxStandard::CC_11: + langOptions.CPlusPlus11 = 1; + langKind = clang::LangStandard::Kind::lang_cxx11; + break; + case CxxStandard::CC_14: + langOptions.CPlusPlus14 = 1; + langKind = clang::LangStandard::Kind::lang_cxx14; + break; + case CxxStandard::CC_17: + langOptions.CPlusPlus17 = 1; + langKind = clang::LangStandard::Kind::lang_cxx17; + break; + case CxxStandard::CC_20: + langOptions.CPlusPlus20 = 1; + langKind = clang::LangStandard::Kind::lang_cxx20; + break; + case CxxStandard::CC_23: + langOptions.CPlusPlus23 = 1; + langKind = clang::LangStandard::Kind::lang_cxx23; + break; + case CxxStandard::CC_26: + langOptions.CPlusPlus26 = 1; + langKind = clang::LangStandard::Kind::lang_cxx26; + break; + default: + langOptions.CPlusPlus11 = 1; + langKind = clang::LangStandard::Kind::lang_cxx11; + break; + } + + langOptions.LangStd = langKind; + langOptions.IsHeaderFile = true; // NOTE: Maybe we should use flag here? + +#ifdef _WIN32 + pOutInstance->getPreprocessorOpts().addMacroDef("_MSC_VER=1932"); + pOutInstance->getPreprocessorOpts().addMacroDef("_MSC_FULL_VER=193231329"); + pOutInstance->getPreprocessorOpts().addMacroDef("_MSC_EXTENSIONS"); + + /** + * Workaround: it's workaround for MSVC 2022 with yvals_core.h which send static assert when Clang version less than 17.x.x + * Example : static assertion failed: error STL1000: Unexpected compiler version, expected Clang 17.0.0 or newer. at C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.40.33807\include\yvals_core.h:898 + * + * This macro block static assertion and should help us, but this part of code MUST be removed after RG3 migrates to latest LLVM & Clang + */ + pOutInstance->getPreprocessorOpts().addMacroDef("_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH=1"); +#endif + +#ifdef __APPLE__ + // This should be enough? + pOutInstance->getPreprocessorOpts().addMacroDef("__GCC_HAVE_DWARF2_CFI_ASM=1"); +#endif + + std::shared_ptr targetOpts = nullptr; + + // Setup triple +#if !defined(__APPLE__) + if (pCompilerEnv->triple.empty()) + { + // Use default triple + targetOpts = std::make_shared(); + targetOpts->Triple = ::llvm::sys::getDefaultTargetTriple(); + clang::TargetInfo* targetInfo = clang::TargetInfo::CreateTargetInfo(pOutInstance->getDiagnostics(), targetOpts); + pOutInstance->setTarget(targetInfo); + + auto triple = targetInfo->getTriple(); + + std::vector vIncs; + clang::LangOptions::setLangDefaults(langOptions, clang::Language::CXX, triple, vIncs, langKind); + } + else + { + targetOpts = std::make_shared(); + targetOpts->Triple = ::llvm::Triple::normalize(pCompilerEnv->triple); + clang::TargetInfo* targetInfo = clang::TargetInfo::CreateTargetInfo(pOutInstance->getDiagnostics(), targetOpts); + pOutInstance->setTarget(targetInfo); + + auto triple = targetInfo->getTriple(); + + std::vector vIncs; + clang::LangOptions::setLangDefaults(langOptions, clang::Language::CXX, triple, vIncs, langKind); + } +#else + // On Apple we should use default triple instead of detect it at runtime + ::llvm::Triple triple(::llvm::sys::getDefaultTargetTriple()); + pOutInstance->getTargetOpts().Triple = triple.str(); + pOutInstance->setTarget(clang::TargetInfo::CreateTargetInfo(pOutInstance->getDiagnostics(), std::make_shared(pOutInstance->getTargetOpts()))); +#endif + + pOutInstance->setInvocation(invocation); + + // Set up FrontendOptions + clang::FrontendOptions &opts = pOutInstance->getFrontendOpts(); + opts.ProgramAction = clang::frontend::ParseSyntaxOnly; + opts.SkipFunctionBodies = static_cast(sCompilerConfig.bSkipFunctionBodies); + + opts.Inputs.clear(); + + // Prepare compiler instance + { + Visitor v { opts, pOutInstance }; + std::visit(v, sInput); + } + + // Set macros + clang::PreprocessorOptions& preprocessorOptions = pOutInstance->getPreprocessorOpts(); + for (const auto& compilerDef : sCompilerConfig.vCompilerDefs) + { + preprocessorOptions.addMacroDef(compilerDef); + } + + // Add builtins + preprocessorOptions.addMacroDef("__RG3__=1"); + preprocessorOptions.addMacroDef("__RG3_COMMIT__=\"" RG3_BUILD_HASH "\""); + preprocessorOptions.addMacroDef("__RG3_BUILD_DATE__=\"" __DATE__ "\""); + +#ifdef __APPLE__ + // For apple only. They cares about GNUC? Idk & I don't care + preprocessorOptions.addMacroDef("__GNUC__=4"); +#endif + + // Setup header dirs source + clang::HeaderSearchOptions& headerSearchOptions = pOutInstance->getHeaderSearchOpts(); + { + for (const auto& sysInc : pCompilerEnv->config.vSystemIncludes) + { + const auto absolutePath = std::filesystem::absolute(sysInc.sFsLocation); + + clang::frontend::IncludeDirGroup group = clang::frontend::IncludeDirGroup::Angled; + + if (sysInc.eKind == IncludeKind::IK_SYSROOT) + { + // ignore sysroot here + continue; + } + + if (sysInc.eKind == IncludeKind::IK_SYSTEM) + { + group = clang::frontend::IncludeDirGroup::System; + } + + if (sysInc.eKind == IncludeKind::IK_C_SYSTEM) + { + group = clang::frontend::IncludeDirGroup::ExternCSystem; + } + + headerSearchOptions.AddPath(absolutePath.string(), group, false, true); + } + + for (const auto& incInfo : sCompilerConfig.vIncludes) + { + // Convert path to absolute + const auto absolutePath = std::filesystem::absolute(incInfo.sFsLocation); + headerSearchOptions.AddPath(absolutePath.string(), clang::frontend::IncludeDirGroup::Angled, false, true); + } + } + + // small self check + assert(pOutInstance->hasDiagnostics() && "Diagnostics not set up!"); + assert(pOutInstance->hasTarget() && "Target not set up!"); + assert(pOutInstance->hasFileManager() && "FileManager not set up!"); + } +} \ No newline at end of file diff --git a/LLVM/source/Consumers/CollectConstexprVariableEvalResult.cpp b/LLVM/source/Consumers/CollectConstexprVariableEvalResult.cpp new file mode 100644 index 0000000..8334b88 --- /dev/null +++ b/LLVM/source/Consumers/CollectConstexprVariableEvalResult.cpp @@ -0,0 +1,70 @@ +#include + +#include +#include + + +namespace rg3::llvm::consumers +{ + class ConstexprVisitor : public clang::RecursiveASTVisitor { + private: + std::unordered_set m_aExpectedVariables {}; + std::unordered_map* m_pEvaluatedVariables { nullptr }; + clang::ASTContext& m_sContext; + + public: + explicit ConstexprVisitor(clang::ASTContext& context, std::unordered_map* pEvaluatedValues, const std::unordered_set& aExpectedVariables) + : m_sContext(context), m_aExpectedVariables(aExpectedVariables), m_pEvaluatedVariables(pEvaluatedValues) + { + } + + bool VisitVarDecl(clang::VarDecl* pVarDecl) + { + std::string sName = pVarDecl->getNameAsString(); + + if (pVarDecl->isConstexpr() && m_aExpectedVariables.contains(sName)) + { + auto* pEvaluated = pVarDecl->getEvaluatedValue(); + if (pEvaluated) + { + if (pVarDecl->getType()->isBooleanType()) + { + (*m_pEvaluatedVariables)[sName] = pEvaluated->getInt().getBoolValue(); + } + else if (pVarDecl->getType()->isSignedIntegerType()) + { + (*m_pEvaluatedVariables)[sName] = pEvaluated->getInt().getExtValue(); + } + else if (pVarDecl->getType()->isUnsignedIntegerType()) + { + (*m_pEvaluatedVariables)[sName] = pEvaluated->getInt().getZExtValue(); + } + else if (pVarDecl->getType()->isFloatingType()) + { + (*m_pEvaluatedVariables)[sName] = pEvaluated->getFloat().convertToFloat(); + } + else if (pVarDecl->getType()->isDoubleType()) + { + (*m_pEvaluatedVariables)[sName] = pEvaluated->getFloat().convertToDouble(); + } + else if (pVarDecl->getType()->isPointerType() && pVarDecl->getType()->getPointeeType()->isCharType()) + { + if (auto pStrValue = ::llvm::dyn_cast(pEvaluated->getLValueBase().get())) + { + (*m_pEvaluatedVariables)[sName] = pStrValue->getString().str(); + } + } + } + } + return true; // Continue traversal + } + }; + + CollectConstexprVariableEvalResult::CollectConstexprVariableEvalResult() = default; + + void CollectConstexprVariableEvalResult::HandleTranslationUnit(clang::ASTContext& ctx) + { + ConstexprVisitor visitor { ctx, pEvaluatedVariables, aExpectedVariables }; + visitor.TraverseDecl(ctx.getTranslationUnitDecl()); + } +} \ No newline at end of file diff --git a/Tests/Unit/source/Tests_CodeEvaluator.cpp b/Tests/Unit/source/Tests_CodeEvaluator.cpp new file mode 100644 index 0000000..a273e46 --- /dev/null +++ b/Tests/Unit/source/Tests_CodeEvaluator.cpp @@ -0,0 +1,57 @@ +#include + +#include +#include + + +class Tests_CodeEvaluator : public ::testing::Test +{ + protected: + void SetUp() override + { + g_Eval = std::make_unique(); + g_Eval->getCompilerConfig().cppStandard = rg3::llvm::CxxStandard::CC_20; + g_Eval->getCompilerConfig().bSkipFunctionBodies = true; + } + + void TearDown() override + { + g_Eval = nullptr; + } + + protected: + std::unique_ptr g_Eval { nullptr }; +}; + + +TEST_F(Tests_CodeEvaluator, SimpleUsage) +{ + auto res = g_Eval->evaluateCode("static constexpr int aResult = 32 * 2;", { "aResult" }); + ASSERT_TRUE(res.vIssues.empty()) << "No issues expected to be here"; + ASSERT_EQ(res.mOutputs.size(), 1) << "Expected to have 1 output"; + ASSERT_TRUE(res.mOutputs.contains("aResult")) << "aResult should be here"; + ASSERT_TRUE(std::holds_alternative(res.mOutputs["aResult"])); + ASSERT_EQ(std::get(res.mOutputs["aResult"]), 64) << "Must be 64!"; +} + +TEST_F(Tests_CodeEvaluator, ClassInheritanceConstexprTest) +{ + auto res = g_Eval->evaluateCode(R"( +#include + +struct MyCoolBaseClass {}; + +struct MyDniweClass {}; +struct MyBuddyClass : MyCoolBaseClass {}; + +constexpr bool g_bDniweInherited = std::is_base_of_v; +constexpr bool g_bBuddyInherited = std::is_base_of_v; +)", { "g_bDniweInherited", "g_bBuddyInherited" }); + + ASSERT_TRUE(res.vIssues.empty()) << "No issues expected to be here"; + ASSERT_EQ(res.mOutputs.size(), 2) << "Expected to have 2 outputs"; + ASSERT_TRUE(res.mOutputs.contains("g_bDniweInherited")) << "g_bDniweInherited should be here"; + ASSERT_TRUE(res.mOutputs.contains("g_bBuddyInherited")) << "g_bBuddyInherited should be here"; + ASSERT_TRUE(std::get(res.mOutputs["g_bBuddyInherited"])) << "Buddy should be ok!"; + ASSERT_FALSE(std::get(res.mOutputs["g_bDniweInherited"])) << "Dniwe should be bad!"; +} \ No newline at end of file