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