From e59f0f392d8e5e5d1c915c5fa5a55899c09a4ab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C4=8Cert=C3=ADk?= Date: Wed, 4 Jan 2023 22:53:48 -0700 Subject: [PATCH] Initial setup (#1) * Update README * Add a build system * Use Clang * Fix CMake to find clang * Add a C --- .github/workflows/CI.yml | 46 +++++++ CMakeLists.txt | 127 +++++++++++++++++++ README.md | 10 +- build.sh | 9 ++ cmake/Findclang.cmake | 51 ++++++++ environment_unix.yml | 10 ++ examples/test.cpp | 4 + src/CMakeLists.txt | 2 + src/lc.cpp | 260 +++++++++++++++++++++++++++++++++++++++ 9 files changed, 518 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/CI.yml create mode 100644 CMakeLists.txt create mode 100755 build.sh create mode 100644 cmake/Findclang.cmake create mode 100644 environment_unix.yml create mode 100644 examples/test.cpp create mode 100644 src/CMakeLists.txt create mode 100644 src/lc.cpp diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..833a300 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,46 @@ +name: CI + +on: + push: + branches: + - main + tags: + - 'v*' + pull_request: + branches: + - main + + +jobs: + Build: + name: LFortran CI (${{ matrix.python-version }}, ${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: ["ubuntu-latest"] + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - uses: mamba-org/provision-with-micromamba@main + with: + environment-file: environment_unix.yml + + - uses: hendrikmuhs/ccache-action@main + with: + variant: sccache + key: ${{ github.job }}-${{ matrix.os }} + + - name: Build (Linux / macOS) + shell: bash -l {0} + if: contains(matrix.os, 'ubuntu') || contains(matrix.os, 'macos') + run: | + ./build.sh + + - name: Test (Linux / macOS) + shell: bash -l {0} + if: contains(matrix.os, 'ubuntu') || contains(matrix.os, 'macos') + run: | + ./src/lc --ast-dump examples/test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..484cd39 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,127 @@ +cmake_minimum_required(VERSION 3.10 FATAL_ERROR) + +project(lc LANGUAGES C CXX) + +set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) + +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release + CACHE STRING "Build type (Debug, Release)" FORCE) +endif () +if (NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR + CMAKE_BUILD_TYPE STREQUAL "Release")) + message("${CMAKE_BUILD_TYPE}") + message(FATAL_ERROR "CMAKE_BUILD_TYPE must be one of: Debug, Release (current value: '${CMAKE_BUILD_TYPE}')") +endif () + +if (NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17 + CACHE STRING "C++ standard" FORCE) +endif () + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +# LLVM +set(WITH_LLVM no CACHE BOOL "Build with LLVM support") +set(WITH_TARGET_AARCH64 no CACHE BOOL "Enable target AARCH64") +set(WITH_TARGET_X86 no CACHE BOOL "Enable target X86") +set(WITH_TARGET_WASM no CACHE BOOL "Enable target WebAssembly") +if (WITH_LLVM) + set(LFORTRAN_LLVM_COMPONENTS core support mcjit orcjit native asmparser asmprinter) + find_package(LLVM REQUIRED) + message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") + message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") + + # Always enable the native target + if ("${LLVM_NATIVE_ARCH}" STREQUAL "AArch64") + set(WITH_TARGET_AARCH64 yes) + endif() + + if ("${LLVM_NATIVE_ARCH}" STREQUAL "X86") + set(WITH_TARGET_X86 yes) + endif() + + if (WITH_TARGET_AARCH64) + if (NOT ("${LLVM_TARGETS_TO_BUILD}" MATCHES "AArch64")) + message(FATAL_ERROR "The selected LLVM library doesn't have support for AArch64 targets") + endif() + + list(APPEND LFORTRAN_LLVM_COMPONENTS aarch64info aarch64utils aarch64desc aarch64asmparser aarch64codegen aarch64disassembler) + add_definitions("-DHAVE_TARGET_AARCH64=1") + endif() + + if (WITH_TARGET_X86) + if (NOT ("${LLVM_TARGETS_TO_BUILD}" MATCHES "X86")) + message(FATAL_ERROR "The selected LLVM library doesn't have support for X86 targets") + endif() + + list(APPEND LFORTRAN_LLVM_COMPONENTS x86info x86desc x86codegen x86asmparser x86disassembler) + add_definitions("-DHAVE_TARGET_X86=1") + endif() + + if (WITH_TARGET_WASM) + if (NOT ("${LLVM_TARGETS_TO_BUILD}" MATCHES "WebAssembly")) + message(FATAL_ERROR "The selected LLVM library doesn't have support for WebAssembly targets") + endif() + + list(APPEND LFORTRAN_LLVM_COMPONENTS webassemblyasmparser webassemblycodegen webassemblydesc webassemblydisassembler webassemblyinfo) + add_definitions("-DHAVE_TARGET_WASM=1") + endif() + + # Clang dependencies + list(APPEND LFORTRAN_LLVM_COMPONENTS windowsdriver) + + llvm_map_components_to_libnames(llvm_libs ${LFORTRAN_LLVM_COMPONENTS}) + unset(LFORTRAN_LLVM_COMPONENTS) + + add_library(p::llvm INTERFACE IMPORTED) + set_property(TARGET p::llvm PROPERTY INTERFACE_INCLUDE_DIRECTORIES + ${LLVM_INCLUDE_DIRS}) + #set_property(TARGET p::llvm PROPERTY INTERFACE_COMPILE_DEFINITIONS + # ${LLVM_DEFINITIONS}) + #set_property(TARGET p::llvm PROPERTY INTERFACE_COMPILE_OPTIONS + # ${LLVM_DEFINITIONS}) + set_property(TARGET p::llvm PROPERTY INTERFACE_COMPILE_OPTIONS + $<$:${LFORTRAN_CXX_NO_RTTI_FLAG}>) + set_property(TARGET p::llvm PROPERTY INTERFACE_LINK_LIBRARIES + ${llvm_libs}) + if (MSVC) + # LLVM on Windows appends zlib shared library and we must provide + # a path to find it: + get_filename_component(mypath ${ZLIB_LIBRARY} DIRECTORY) + target_link_directories(p::llvm BEFORE INTERFACE ${mypath}) + message(STATUS "ZLIB LIBRARY PATH: ${mypath}") + endif() + set(HAVE_LFORTRAN_LLVM yes) + + find_package(clang 15 REQUIRED) + add_library(p::clang INTERFACE IMPORTED) + set_property(TARGET p::clang PROPERTY INTERFACE_INCLUDE_DIRECTORIES + ${CLANG_INCLUDE_DIRS}) + set_property(TARGET p::clang PROPERTY INTERFACE_LINK_LIBRARIES + ${CLANG_LIBRARIES}) +endif() + +enable_testing() + +message("\n") +message("Configuration results") +message("---------------------") +message("C compiler : ${CMAKE_C_COMPILER}") +message("C++ compiler : ${CMAKE_CXX_COMPILER}") +message("Build type: ${CMAKE_BUILD_TYPE}") +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + message("C compiler flags : ${CMAKE_C_FLAGS_DEBUG}") + message("C++ compiler flags : ${CMAKE_CXX_FLAGS_DEBUG}") +else () + message("C compiler flags : ${CMAKE_C_FLAGS_RELEASE}") + message("C++ compiler flags : ${CMAKE_CXX_FLAGS_RELEASE}") +endif () +message("Installation prefix: ${CMAKE_INSTALL_PREFIX}") +message("WITH_LLVM: ${WITH_LLVM}") +message("WITH_TARGET_AARCH64: ${WITH_TARGET_AARCH64}") +message("WITH_TARGET_X86: ${WITH_TARGET_X86}") +message("WITH_TARGET_WASM: ${WITH_TARGET_WASM}") + + +add_subdirectory(src) diff --git a/README.md b/README.md index f12cac8..dab2cb1 100644 --- a/README.md +++ b/README.md @@ -1 +1,9 @@ -# lc \ No newline at end of file +# lc + +LC is the C frontend to LCompilers. + +# Build + + mamba env create -f environment_unix.yml + ./build.sh + ./src/lc --ast-dump examples/test.cpp diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..57439a1 --- /dev/null +++ b/build.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -ex + +cmake \ + -DCMAKE_BUILD_TYPE=Debug \ + -DWITH_LLVM=yes \ + . +cmake --build . -j16 diff --git a/cmake/Findclang.cmake b/cmake/Findclang.cmake new file mode 100644 index 0000000..49ebcea --- /dev/null +++ b/cmake/Findclang.cmake @@ -0,0 +1,51 @@ +# Copyright (c) 2016 Andrew Kelley +# This file is MIT licensed. +# See http://opensource.org/licenses/MIT + +# CLANG_FOUND +# CLANG_INCLUDE_DIRS +# CLANG_LIBRARIES +# CLANG_LIBDIRS + +find_path(CLANG_INCLUDE_DIRS NAMES clang/Frontend/ASTUnit.h) + +macro(FIND_AND_ADD_CLANG_LIB _libname_) +string(TOUPPER ${_libname_} _prettylibname_) +find_library(CLANG_${_prettylibname_}_LIB NAMES ${_libname_} NAMES_PER_DIR) +if(CLANG_${_prettylibname_}_LIB) + set(CLANG_LIBRARIES ${CLANG_LIBRARIES} ${CLANG_${_prettylibname_}_LIB}) +endif() +endmacro(FIND_AND_ADD_CLANG_LIB) + +FIND_AND_ADD_CLANG_LIB(clangFrontendTool) +FIND_AND_ADD_CLANG_LIB(clangCodeGen) +FIND_AND_ADD_CLANG_LIB(clangFrontend) +FIND_AND_ADD_CLANG_LIB(clangDriver) +FIND_AND_ADD_CLANG_LIB(clangSerialization) +FIND_AND_ADD_CLANG_LIB(clangSema) +FIND_AND_ADD_CLANG_LIB(clangStaticAnalyzerFrontend) +FIND_AND_ADD_CLANG_LIB(clangStaticAnalyzerCheckers) +FIND_AND_ADD_CLANG_LIB(clangStaticAnalyzerCore) +FIND_AND_ADD_CLANG_LIB(clangAnalysis) +FIND_AND_ADD_CLANG_LIB(clangASTMatchers) +FIND_AND_ADD_CLANG_LIB(clangCrossTU) +FIND_AND_ADD_CLANG_LIB(clangAST) +FIND_AND_ADD_CLANG_LIB(clangParse) +FIND_AND_ADD_CLANG_LIB(clangSema) +FIND_AND_ADD_CLANG_LIB(clangBasic) +FIND_AND_ADD_CLANG_LIB(clangEdit) +FIND_AND_ADD_CLANG_LIB(clangLex) +FIND_AND_ADD_CLANG_LIB(clangARCMigrate) +FIND_AND_ADD_CLANG_LIB(clangRewriteFrontend) +FIND_AND_ADD_CLANG_LIB(clangRewrite) +FIND_AND_ADD_CLANG_LIB(clangIndex) +FIND_AND_ADD_CLANG_LIB(clangToolingCore) +FIND_AND_ADD_CLANG_LIB(clangToolingSyntax) +FIND_AND_ADD_CLANG_LIB(clangTooling) +FIND_AND_ADD_CLANG_LIB(clangExtractAPI) +FIND_AND_ADD_CLANG_LIB(clangSupport) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(clang DEFAULT_MSG CLANG_LIBRARIES CLANG_INCLUDE_DIRS) + +mark_as_advanced(CLANG_INCLUDE_DIRS CLANG_LIBRARIES CLANG_LIBDIRS) diff --git a/environment_unix.yml b/environment_unix.yml new file mode 100644 index 0000000..9d1d2a1 --- /dev/null +++ b/environment_unix.yml @@ -0,0 +1,10 @@ +name: lc +channels: + - conda-forge +dependencies: + - cmake + - llvmdev=15.0.6 + - clangdev=15.0.6 + - make + - zlib + - git diff --git a/examples/test.cpp b/examples/test.cpp new file mode 100644 index 0000000..6c393c4 --- /dev/null +++ b/examples/test.cpp @@ -0,0 +1,4 @@ +int f(int x) { + int result = (x / 42); + return result; +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..39cd641 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(lc lc.cpp) +target_link_libraries(lc p::clang p::llvm) diff --git a/src/lc.cpp b/src/lc.cpp new file mode 100644 index 0000000..da54273 --- /dev/null +++ b/src/lc.cpp @@ -0,0 +1,260 @@ +//===--- tools/clang-check/ClangCheck.cpp - Clang check tool --------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This file implements a clang-check tool that runs clang based on the info +// stored in a compilation database. +// +// This tool uses the Clang Tooling infrastructure, see +// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html +// for details on setting it up with LLVM source tree. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/ASTConsumer.h" +#include "clang/CodeGen/ObjectFilePCHContainerOperations.h" +#include "clang/Driver/Options.h" +#include "clang/Frontend/ASTConsumers.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Rewrite/Frontend/FixItRewriter.h" +#include "clang/Rewrite/Frontend/FrontendActions.h" +#include "clang/StaticAnalyzer/Frontend/FrontendActions.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Syntax/BuildTree.h" +#include "clang/Tooling/Syntax/TokenBufferTokenManager.h" +#include "clang/Tooling/Syntax/Tokens.h" +#include "clang/Tooling/Syntax/Tree.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Option/OptTable.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/TargetSelect.h" + +using namespace clang::driver; +using namespace clang::tooling; +using namespace llvm; + +static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); +static cl::extrahelp MoreHelp( + "\tFor example, to run clang-check on all files in a subtree of the\n" + "\tsource tree, use:\n" + "\n" + "\t find path/in/subtree -name '*.cpp'|xargs clang-check\n" + "\n" + "\tor using a specific build path:\n" + "\n" + "\t find path/in/subtree -name '*.cpp'|xargs clang-check -p build/path\n" + "\n" + "\tNote, that path/in/subtree and current directory should follow the\n" + "\trules described above.\n" + "\n" +); + +static cl::OptionCategory ClangCheckCategory("clang-check options"); +static const opt::OptTable &Options = getDriverOptTable(); +static cl::opt + ASTDump("ast-dump", + cl::desc(Options.getOptionHelpText(options::OPT_ast_dump)), + cl::cat(ClangCheckCategory)); +static cl::opt + ASTList("ast-list", + cl::desc(Options.getOptionHelpText(options::OPT_ast_list)), + cl::cat(ClangCheckCategory)); +static cl::opt + ASTPrint("ast-print", + cl::desc(Options.getOptionHelpText(options::OPT_ast_print)), + cl::cat(ClangCheckCategory)); +static cl::opt ASTDumpFilter( + "ast-dump-filter", + cl::desc(Options.getOptionHelpText(options::OPT_ast_dump_filter)), + cl::cat(ClangCheckCategory)); +static cl::opt + Analyze("analyze", + cl::desc(Options.getOptionHelpText(options::OPT_analyze)), + cl::cat(ClangCheckCategory)); +static cl::opt + AnalyzerOutput("analyzer-output-path", + cl::desc(Options.getOptionHelpText(options::OPT_o)), + cl::cat(ClangCheckCategory)); + +static cl::opt + Fixit("fixit", cl::desc(Options.getOptionHelpText(options::OPT_fixit)), + cl::cat(ClangCheckCategory)); +static cl::opt FixWhatYouCan( + "fix-what-you-can", + cl::desc(Options.getOptionHelpText(options::OPT_fix_what_you_can)), + cl::cat(ClangCheckCategory)); + +static cl::opt SyntaxTreeDump("syntax-tree-dump", + cl::desc("dump the syntax tree"), + cl::cat(ClangCheckCategory)); +static cl::opt TokensDump("tokens-dump", + cl::desc("dump the preprocessed tokens"), + cl::cat(ClangCheckCategory)); + +namespace { + +// FIXME: Move FixItRewriteInPlace from lib/Rewrite/Frontend/FrontendActions.cpp +// into a header file and reuse that. +class FixItOptions : public clang::FixItOptions { +public: + FixItOptions() { + FixWhatYouCan = ::FixWhatYouCan; + } + + std::string RewriteFilename(const std::string& filename, int &fd) override { + // We don't need to do permission checking here since clang will diagnose + // any I/O errors itself. + + fd = -1; // No file descriptor for file. + + return filename; + } +}; + +/// Subclasses \c clang::FixItRewriter to not count fixed errors/warnings +/// in the final error counts. +/// +/// This has the side-effect that clang-check -fixit exits with code 0 on +/// successfully fixing all errors. +class FixItRewriter : public clang::FixItRewriter { +public: + FixItRewriter(clang::DiagnosticsEngine& Diags, + clang::SourceManager& SourceMgr, + const clang::LangOptions& LangOpts, + clang::FixItOptions* FixItOpts) + : clang::FixItRewriter(Diags, SourceMgr, LangOpts, FixItOpts) { + } + + bool IncludeInDiagnosticCounts() const override { return false; } +}; + +/// Subclasses \c clang::FixItAction so that we can install the custom +/// \c FixItRewriter. +class ClangCheckFixItAction : public clang::FixItAction { +public: + bool BeginSourceFileAction(clang::CompilerInstance& CI) override { + FixItOpts.reset(new FixItOptions); + Rewriter.reset(new FixItRewriter(CI.getDiagnostics(), CI.getSourceManager(), + CI.getLangOpts(), FixItOpts.get())); + return true; + } +}; + +class DumpSyntaxTree : public clang::ASTFrontendAction { +public: + std::unique_ptr + CreateASTConsumer(clang::CompilerInstance &CI, StringRef InFile) override { + class Consumer : public clang::ASTConsumer { + public: + Consumer(clang::CompilerInstance &CI) : Collector(CI.getPreprocessor()) {} + + void HandleTranslationUnit(clang::ASTContext &AST) override { + clang::syntax::TokenBuffer TB = std::move(Collector).consume(); + if (TokensDump) + llvm::outs() << TB.dumpForTests(); + clang::syntax::TokenBufferTokenManager TBTM(TB, AST.getLangOpts(), + AST.getSourceManager()); + clang::syntax::Arena A; + llvm::outs() + << clang::syntax::buildSyntaxTree(A, TBTM, AST)->dump(TBTM); + } + + private: + clang::syntax::TokenCollector Collector; + }; + return std::make_unique(CI); + } +}; + +class ClangCheckActionFactory { +public: + std::unique_ptr newASTConsumer() { + if (ASTList) + return clang::CreateASTDeclNodeLister(); + if (ASTDump) + return clang::CreateASTDumper(nullptr /*Dump to stdout.*/, ASTDumpFilter, + /*DumpDecls=*/true, + /*Deserialize=*/false, + /*DumpLookups=*/false, + /*DumpDeclTypes=*/false, + clang::ADOF_Default); + if (ASTPrint) + return clang::CreateASTPrinter(nullptr, ASTDumpFilter); + return std::make_unique(); + } +}; + +} // namespace + +int main(int argc, const char **argv) { + llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); + + // Initialize targets for clang module support. +// llvm::InitializeAllTargets(); +// llvm::InitializeAllTargetMCs(); +// llvm::InitializeAllAsmPrinters(); +// llvm::InitializeAllAsmParsers(); + + auto ExpectedParser = + CommonOptionsParser::create(argc, argv, ClangCheckCategory); + if (!ExpectedParser) { + llvm::errs() << ExpectedParser.takeError(); + return 1; + } + CommonOptionsParser &OptionsParser = ExpectedParser.get(); + ClangTool Tool(OptionsParser.getCompilations(), + OptionsParser.getSourcePathList()); + + if (Analyze) { + // Set output path if is provided by user. + // + // As the original -o options have been removed by default via the + // strip-output adjuster, we only need to add the analyzer -o options here + // when it is provided by users. + if (!AnalyzerOutput.empty()) + Tool.appendArgumentsAdjuster( + getInsertArgumentAdjuster(CommandLineArguments{"-o", AnalyzerOutput}, + ArgumentInsertPosition::END)); + + // Running the analyzer requires --analyze. Other modes can work with the + // -fsyntax-only option. + // + // The syntax-only adjuster is installed by default. + // Good: It also strips options that trigger extra output, like -save-temps. + // Bad: We don't want the -fsyntax-only when executing the static analyzer. + // + // To enable the static analyzer, we first strip all -fsyntax-only options + // and then add an --analyze option to the front. + Tool.appendArgumentsAdjuster( + [&](const CommandLineArguments &Args, StringRef /*unused*/) { + CommandLineArguments AdjustedArgs; + for (const std::string &Arg : Args) + if (Arg != "-fsyntax-only") + AdjustedArgs.emplace_back(Arg); + return AdjustedArgs; + }); + Tool.appendArgumentsAdjuster( + getInsertArgumentAdjuster("--analyze", ArgumentInsertPosition::BEGIN)); + } + + ClangCheckActionFactory CheckFactory; + std::unique_ptr FrontendFactory; + + // Choose the correct factory based on the selected mode. + if (Analyze) + FrontendFactory = newFrontendActionFactory(); + else if (Fixit) + FrontendFactory = newFrontendActionFactory(); + else if (SyntaxTreeDump || TokensDump) + FrontendFactory = newFrontendActionFactory(); + else + FrontendFactory = newFrontendActionFactory(&CheckFactory); + + return Tool.run(FrontendFactory.get()); +}