diff --git a/.cirrus.yml b/.cirrus.yml index 9d9731a04ac..f9d528f0758 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -67,7 +67,7 @@ common_steps_template: &COMMON_STEPS_TEMPLATE excludes+='|^druntime-test-exceptions-debug$' elif [[ "$CI_OS-$CI_ARCH" == "osx-arm64" ]]; then # FIXME: sporadic segfaults/bus errors with enabled optimizations - excludes+='|^core.thread.fiber-shared$' + excludes+='|^core.thread.fiber(-shared)?$' fi ctest -j$PARALLELISM --output-on-failure -E "$excludes" diff --git a/.github/actions/3-build-cross/action.yml b/.github/actions/3-build-cross/action.yml index f583c3213ae..fb1473281da 100644 --- a/.github/actions/3-build-cross/action.yml +++ b/.github/actions/3-build-cross/action.yml @@ -145,4 +145,4 @@ runs: ${{ inputs.cmake_flags }} ${{ inputs.with_pgo == 'true' && '-DDFLAGS_LDC=-fprofile-use=../pgo-ldc/merged.profdata' || '' }} ${{ env.CROSS_CMAKE_FLAGS }} - build_targets: ldc2 ldmd2 ldc-build-runtime ldc-profdata ldc-prune-cache timetrace2txt + build_targets: ldc2 ldmd2 ldc-build-runtime ldc-build-plugin ldc-profdata ldc-prune-cache timetrace2txt diff --git a/.github/actions/5-install/action.yml b/.github/actions/5-install/action.yml index 4a442af58ac..f9388388db1 100644 --- a/.github/actions/5-install/action.yml +++ b/.github/actions/5-install/action.yml @@ -22,6 +22,7 @@ runs: else mkdir -p install/bin cp build-cross/bin/{ldc2,ldmd2,ldc-build-runtime,ldc-profdata,ldc-prune-cache,timetrace2txt} install/bin/ + cp build-cross/bin/ldc-build-plugin install/bin/ || true cp -R build-cross-libs/lib install/ cp build-cross/lib/{libldc_rt.*,libLTO-ldc.dylib,LLVMgold-ldc.so} install/lib/ || true mkdir install/etc diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b6e4e252e8..b727772eeee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,25 @@ # LDC master +#### Big news + +#### Platform support + +#### Bug fixes + +# LDC 1.33.0 (2023-07-23) + #### Big news - Frontend, druntime and Phobos are at version [2.103.1](https://dlang.org/changelog/2.103.0.html), incl. new command-line option `-verror-supplements`. (#4345) +- The `--plugin` commandline option now also accepts semantic analysis plugins. Semantic analysis plugins are recognized by exporting the symbol: `extern(C) void runSemanticAnalysis(Module m)`. The plugin's `runSemanticAnalysis` function is called for each module, after all other semantic analysis steps (also after DCompute SemA), just before object codegen. (#4430) +- New tool `ldc-build-plugin` that helps compiling user plugins. It downloads the correct LDC source version (if it's not already available), and calls LDC with the correct commandline flags to build a plugin. (#4430) - New commandline option `-femit-local-var-lifetime` that enables variable lifetime (scope) annotation to LLVM IR codegen. Lifetime annotation enables stack memory reuse for local variables with non-overlapping scope. (#4395) - C files are now automatically preprocessed using the external C compiler (configurable via `-gcc` or the `CC` environment variable, and `-Xcc` for extra flags). Extra preprocessor flags (e.g., include dirs and manual defines) can be added via new command-line option `-P`. (#4417) - Windows: If `clang-cl.exe` is on `PATH`, it is preferred over Microsoft's `cl.exe` by default (e.g., to avoid printing the C source file name to stderr during preprocessing). - Less pedantic checks for conflicting C(++) function declarations when compiling multiple modules to a single object file ('Error: Function type does not match previously declared function with the same mangled name'). The error now only appears if an object file actually references multiple conflicting functions. (#4420) +- New command-line option `--fcf-protection`, which enables Intel's Control-Flow Enforcement Technology (CET). (#4437) #### Platform support +- Supports LLVM 9.0 - 15.0. #### Bug fixes - Handle potential lambda mangle collisions across separately compiled object files (and the linker then silently picking an arbitrary implementation). Lambdas (and their nested global variables) are now internal to each referencing object file (`static` linkage in C). (#4415) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c3dddeb6d8..1c34a3e7710 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -249,17 +249,6 @@ if(CMAKE_COMPILER_IS_GNUCXX OR (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")) endif() endif() -if(NOT WIN32 AND NOT CYGWIN) - # Unify symbol visibility with LLVM to silence linker warning "direct access in function X to global - # weak symbol Y means the weak symbol cannot be overridden at runtime. This was likely caused by - # different translation units being compiled with different visibility settings." - # See LLVM's cmake/modules/HandleLLVMOptions.cmake. - check_cxx_compiler_flag("-fvisibility-inlines-hidden" SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG) - if (${SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG}) - append("-fvisibility-inlines-hidden" LDC_CXXFLAGS) - endif() -endif() - if(MSVC) # Remove flags here, for exceptions and RTTI. # CL.EXE complains to override flags like "/GR /GR-". @@ -683,10 +672,26 @@ if(LDC_ENABLE_PLUGINS) if(LINKER_ACCEPTS_EXPORT_DYNAMIC_FLAG) set(LDC_LINKERFLAG_LIST "${LDC_LINKERFLAG_LIST};-Wl,--export-dynamic") + else() + message(WARNING "Linker does not accept --export-dynamic, user plugins may give missing symbol errors upon load") endif() endif() endif() message(STATUS "-- Building LDC with plugin support (LDC_ENABLE_PLUGINS): ${LDC_ENABLE_PLUGINS}") +message(STATUS "-- Linking LDC with flags: ${ALTERNATIVE_MALLOC_O};${LDC_LINKERFLAG_LIST}") + +if(NOT WIN32 AND NOT CYGWIN) + # Unify symbol visibility with LLVM to silence linker warning "direct access in function X to global + # weak symbol Y means the weak symbol cannot be overridden at runtime. This was likely caused by + # different translation units being compiled with different visibility settings." + # See LLVM's cmake/modules/HandleLLVMOptions.cmake. + check_cxx_compiler_flag("-fvisibility-inlines-hidden" SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG) + if (LDC_ENABLE_PLUGINS AND NOT APPLE) + # For plugins, we shouldn't apply this flag because it hides the inline methods of e.g. Visitor. On macOS it's OK to add. + elseif (${SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG}) + append("-fvisibility-inlines-hidden" LDC_CXXFLAGS) + endif() +endif() build_d_executable( "${LDC_EXE}" diff --git a/cmake/Modules/BuildDExecutable.cmake b/cmake/Modules/BuildDExecutable.cmake index 32101dafd5c..6318433ea75 100644 --- a/cmake/Modules/BuildDExecutable.cmake +++ b/cmake/Modules/BuildDExecutable.cmake @@ -25,6 +25,7 @@ endmacro() # - DFLAGS_BASE # - LDC_LINK_MANUALLY # - D_LINKER_ARGS +# - LDC_ENABLE_PLUGINS function(build_d_executable target_name output_exe d_src_files compiler_args linker_args extra_compile_deps link_deps compile_separately) set(dflags "${D_COMPILER_FLAGS} ${DFLAGS_BASE} ${compiler_args}") if(UNIX) @@ -40,7 +41,9 @@ function(build_d_executable target_name output_exe d_src_files compiler_args lin # Compile all D modules to a single object. set(object_file ${PROJECT_BINARY_DIR}/obj/${target_name}${CMAKE_CXX_OUTPUT_EXTENSION}) # Default to -linkonce-templates with LDMD host compiler, to speed-up optimization. - if("${D_COMPILER_ID}" STREQUAL "LDMD") + if("${target_name}" STREQUAL "ldc2" AND LDC_ENABLE_PLUGINS) + # For plugin support we need ldc2's symbols to be global, don't use -linkonce-templates. + elseif("${D_COMPILER_ID}" STREQUAL "LDMD") set(dflags -linkonce-templates ${dflags}) endif() add_custom_command( diff --git a/dmd/common/string.d b/dmd/common/string.d index a1614fd907c..cfae1bb6749 100644 --- a/dmd/common/string.d +++ b/dmd/common/string.d @@ -136,8 +136,16 @@ but is guaranteed to follow it. version(Windows) wchar[] toWStringz(const(char)[] narrow, ref SmallBuffer!wchar buffer) nothrow { import core.sys.windows.winnls : CP_ACP, MultiByteToWideChar; + +version (IN_LLVM) +{ + import dmd.root.filename : CodePage; +} +else +{ // assume filenames encoded in system default Windows ANSI code page enum CodePage = CP_ACP; +} if (narrow is null) return null; diff --git a/dmd/root/filename.d b/dmd/root/filename.d index 14cffd265e2..d7842e2463f 100644 --- a/dmd/root/filename.d +++ b/dmd/root/filename.d @@ -44,7 +44,7 @@ version (Windows) version (IN_LLVM) { - private enum CodePage = CP_UTF8; + enum CodePage = CP_UTF8; } else { diff --git a/driver/cl_options_instrumentation.cpp b/driver/cl_options_instrumentation.cpp index 00fe6031d4c..ea00e7d130c 100644 --- a/driver/cl_options_instrumentation.cpp +++ b/driver/cl_options_instrumentation.cpp @@ -84,6 +84,19 @@ llvm::StringRef getXRayInstructionThresholdString() { return thresholdString; } +cl::opt fCFProtection( + "fcf-protection", + cl::desc("Instrument control-flow architecture protection"), cl::ZeroOrMore, + cl::ValueOptional, + cl::values(clEnumValN(CFProtectionType::None, "none", ""), + clEnumValN(CFProtectionType::Branch, "branch", ""), + clEnumValN(CFProtectionType::Return, "return", ""), + clEnumValN(CFProtectionType::Full, "full", ""), + clEnumValN(CFProtectionType::Full, "", + "") // default to "full" if no argument specified + ), + cl::init(CFProtectionType::None)); + void initializeInstrumentationOptionsFromCmdline(const llvm::Triple &triple) { if (ASTPGOInstrGenFile.getNumOccurrences() > 0) { pgoMode = PGO_ASTBasedInstr; @@ -110,6 +123,14 @@ void initializeInstrumentationOptionsFromCmdline(const llvm::Triple &triple) { if (dmdFunctionTrace) global.params.trace = true; + + // fcf-protection is only valid for X86 + if (fCFProtection != CFProtectionType::None && + !(triple.getArch() == llvm::Triple::x86 || + triple.getArch() == llvm::Triple::x86_64)) { + error(Loc(), "option '--fcf-protection' cannot be specified on this target " + "architecture"); + } } } // namespace opts diff --git a/driver/cl_options_instrumentation.h b/driver/cl_options_instrumentation.h index 50a249c09aa..9b06789b341 100644 --- a/driver/cl_options_instrumentation.h +++ b/driver/cl_options_instrumentation.h @@ -29,6 +29,9 @@ extern cl::opt instrumentFunctions; extern cl::opt fXRayInstrument; llvm::StringRef getXRayInstructionThresholdString(); +enum class CFProtectionType { None = 0, Branch = 1, Return = 2, Full = 3 }; +extern cl::opt fCFProtection; + /// This initializes the instrumentation options, and checks the validity of the /// commandline flags. targetTriple should be initialized before calling this. /// It should be called only once. diff --git a/driver/plugins.cpp b/driver/plugins.cpp index 6820da5ea41..713d8bee806 100644 --- a/driver/plugins.cpp +++ b/driver/plugins.cpp @@ -9,6 +9,9 @@ // // Implements functionality related to plugins (`-plugin=...`). // +// Note: plugins can be LLVM-plugins (to be registered with the pass manager) +// or dlang-plugins for semantic analysis. +// //===----------------------------------------------------------------------===// #include "driver/plugins.h" @@ -17,6 +20,7 @@ #include "dmd/errors.h" #include "dmd/globals.h" +#include "dmd/module.h" #include "llvm/Passes/PassBuilder.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/DynamicLibrary.h" @@ -36,63 +40,120 @@ cl::list pluginFiles("plugin", cl::CommaSeparated, cl::desc("Pass plugins to load."), cl::value_desc("dynamic_library.so,lib2.so")); +struct SemaPlugin { + llvm::sys::DynamicLibrary library; + void (*runSemanticAnalysis)(Module *); + + SemaPlugin(const llvm::sys::DynamicLibrary &library, + void (*runSemanticAnalysis)(Module *)) + : library(library), runSemanticAnalysis(runSemanticAnalysis) {} +}; + +llvm::SmallVector sema_plugins; + } // anonymous namespace -#if LDC_LLVM_VER >= 1400 +// Tries to load plugin as SemanticAnalysis. Returns true on 'success', i.e. no +// further attempts needed. +bool loadSemanticAnalysisPlugin(const std::string &filename) { + std::string errorString; + auto library = llvm::sys::DynamicLibrary::getPermanentLibrary( + filename.c_str(), &errorString); + if (!library.isValid()) { + error(Loc(), "Error loading plugin '%s': %s", filename.c_str(), + errorString.c_str()); + return true; // No success, but no need to try loading again as LLVM plugin. + } -namespace { -llvm::SmallVector plugins; + // SemanticAnalysis plugins need to export the `runSemanticAnalysis` function. + void *runSemanticAnalysisFnPtr = + library.getAddressOfSymbol("runSemanticAnalysis"); + + // If the symbol isn't found, this is probably an LLVM plugin. + if (!runSemanticAnalysisFnPtr) + return false; + + sema_plugins.emplace_back( + library, reinterpret_cast(runSemanticAnalysisFnPtr)); + return true; } -/// Loads all plugins for the new pass manager. These plugins will need to be -/// added When building the optimization pipeline. -void loadAllPluginsNewPM() { - for (auto &filename : pluginFiles) { - auto plugin = llvm::PassPlugin::Load(filename); - if (!plugin) { - error(Loc(), "Error loading plugin '%s': %s", filename.c_str(), - llvm::toString(plugin.takeError()).c_str()); - continue; - } - plugins.emplace_back(plugin.get()); + +/// Loads plugin for the legacy pass manager. The static constructor of +/// the plugin should take care of the plugins registering themself with the +/// rest of LDC/LLVM. +void loadLLVMPluginLegacyPM(const std::string &filename) { + std::string errorString; + if (llvm::sys::DynamicLibrary::LoadLibraryPermanently(filename.c_str(), + &errorString)) { + error(Loc(), "Error loading plugin '%s': %s", filename.c_str(), + errorString.c_str()); } } -void registerAllPluginsWithPassBuilder(llvm::PassBuilder &PB) { - for (auto &plugin : plugins) { - plugin.registerPassBuilderCallbacks(PB); + +#if LDC_LLVM_VER >= 1400 + +namespace { +llvm::SmallVector llvm_plugins; + +/// Loads plugin for the new pass manager. The plugin will need to be +/// added explicitly when building the optimization pipeline. +void loadLLVMPluginNewPM(const std::string &filename) { + + auto plugin = llvm::PassPlugin::Load(filename); + if (!plugin) { + error(Loc(), "Error loading plugin '%s': %s", filename.c_str(), + llvm::toString(plugin.takeError()).c_str()); + return; } + llvm_plugins.emplace_back(plugin.get()); } +} // anonymous namespace + #endif // LDC_LLVM_VER >= 1400 -/// Loads all plugins for the legacy pass manaager. The static constructor of -/// each plugin should take care of the plugins registering themself with the -/// rest of LDC/LLVM. -void loadAllPluginsLegacyPM() { +void loadLLVMPlugin(const std::string &filename) { +#if LDC_LLVM_VER >= 1400 + if (opts::isUsingLegacyPassManager()) + loadLLVMPluginLegacyPM(filename); + else + loadLLVMPluginNewPM(filename); +#else + loadLLVMPluginLegacyPM(filename); +#endif +} + +void loadAllPlugins() { for (auto &filename : pluginFiles) { - std::string errorString; - if (llvm::sys::DynamicLibrary::LoadLibraryPermanently(filename.c_str(), - &errorString)) { - error(Loc(), "Error loading plugin '%s': %s", filename.c_str(), - errorString.c_str()); - } + // First attempt to load plugin as SemanticAnalysis plugin. If unsuccesfull, + // load as LLVM plugin. + auto success = loadSemanticAnalysisPlugin(filename); + if (!success) + loadLLVMPlugin(filename); } } +void registerAllPluginsWithPassBuilder(llvm::PassBuilder &PB) { #if LDC_LLVM_VER >= 1400 -void loadAllPlugins() { - if (opts::isUsingLegacyPassManager()) - loadAllPluginsLegacyPM(); - else - loadAllPluginsNewPM(); -} -#else -void loadAllPlugins() { loadAllPluginsLegacyPM(); } -void registerAllPluginsWithPassBuilder(llvm::PassBuilder &) {} + for (auto &plugin : llvm_plugins) { + plugin.registerPassBuilderCallbacks(PB); + } #endif +} + +void runAllSemanticAnalysisPlugins(Module *m) { + for (auto &plugin : sema_plugins) { + assert(plugin.runSemanticAnalysis); + plugin.runSemanticAnalysis(m); + } +} #else // #if LDC_ENABLE_PLUGINS +class Module; + void loadAllPlugins() {} void registerAllPluginsWithPassBuilder(llvm::PassBuilder &) {} +void runAllSemanticAnalysisPlugins(Module *m) {} #endif // LDC_ENABLE_PLUGINS diff --git a/gen/llvmhelpers.cpp b/gen/llvmhelpers.cpp index a7e2c2f8c31..46488f5d903 100644 --- a/gen/llvmhelpers.cpp +++ b/gen/llvmhelpers.cpp @@ -1188,7 +1188,7 @@ LLConstant *DtoConstExpInit(const Loc &loc, Type *targetType, Expression *exp) { val = llvm::ConstantArray::get(at, elements); } - (void)numTotalVals; + (void)numTotalVals; (void) product; // Silence unused variable warning when assert is disabled. assert(product == numTotalVals); return val; } diff --git a/gen/modules.cpp b/gen/modules.cpp index ce32997812a..6ad3f6e9c01 100644 --- a/gen/modules.cpp +++ b/gen/modules.cpp @@ -393,8 +393,27 @@ void registerModuleInfo(Module *m) { emitModuleRefToSection(mangle, moduleInfoSym); } } + +void addModuleFlags(llvm::Module &m) { +#if LDC_LLVM_VER >= 1500 + const auto ModuleMinFlag = llvm::Module::Min; +#else + const auto ModuleMinFlag = llvm::Module::Warning; // Fallback value +#endif + + if (opts::fCFProtection == opts::CFProtectionType::Return || + opts::fCFProtection == opts::CFProtectionType::Full) { + m.addModuleFlag(ModuleMinFlag, "cf-protection-return", 1); + } + + if (opts::fCFProtection == opts::CFProtectionType::Branch || + opts::fCFProtection == opts::CFProtectionType::Full) { + m.addModuleFlag(ModuleMinFlag, "cf-protection-branch", 1); + } } +} // anonymous namespace + void codegenModule(IRState *irs, Module *m) { TimeTraceScope timeScope("Generate IR", m->toChars(), m->loc); @@ -446,6 +465,8 @@ void codegenModule(IRState *irs, Module *m) { addCoverageAnalysisInitializer(m); } + addModuleFlags(irs->module); + gIR = nullptr; irs->dmodule = nullptr; } diff --git a/gen/semantic.d b/gen/semantic.d index 359f75a943c..c818d7b7e09 100644 --- a/gen/semantic.d +++ b/gen/semantic.d @@ -15,13 +15,20 @@ import dmd.dmodule; extern(C++) void dcomputeSemanticAnalysis(Module m); extern(C) int hasComputeAttr(Dsymbol m); +extern(C++) void runAllSemanticAnalysisPlugins(Module m); extern(C++) void extraLDCSpecificSemanticAnalysis(ref Modules modules) { + // First finish DCompute SemA for all modules, before calling plugins. foreach(m; modules[]) { - if (hasComputeAttr(m)) + if (hasComputeAttr(m)) { dcomputeSemanticAnalysis(m); + } + } + + foreach(m; modules[]) + { + runAllSemanticAnalysisPlugins(m); } - } diff --git a/gen/target.cpp b/gen/target.cpp index 154adb3676b..6285c9f1ea6 100644 --- a/gen/target.cpp +++ b/gen/target.cpp @@ -15,6 +15,7 @@ #include "dmd/mtype.h" #include "dmd/target.h" #include "driver/cl_options.h" +#include "driver/cl_options_instrumentation.h" #include "driver/linker.h" #include "gen/abi/abi.h" #include "gen/irstate.h" @@ -309,6 +310,11 @@ Expression *Target::getTargetInfo(const char *name_, const Loc &loc) { Loc(), static_cast(global.params.cplusplus), Type::tint32); } + if (name == "CET") { + auto cet = opts::fCFProtection.getValue(); + return IntegerExp::create(loc, static_cast(cet), Type::tint32); + } + #if LDC_LLVM_SUPPORTED_TARGET_SPIRV || LDC_LLVM_SUPPORTED_TARGET_NVPTX if (name == "dcomputeTargets") { Expressions* exps = createExpressions(); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b8879ecbc43..e6ce8521428 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,6 +1,7 @@ set( LDC2_BIN ${PROJECT_BINARY_DIR}/bin/${LDC_EXE} ) set( LDCPROFDATA_BIN ${PROJECT_BINARY_DIR}/bin/ldc-profdata ) set( LDCPRUNECACHE_BIN ${PROJECT_BINARY_DIR}/bin/${LDCPRUNECACHE_EXE} ) +set( LDCBUILDPLUGIN_BIN ${PROJECT_BINARY_DIR}/bin/${LDC_BUILD_PLUGIN_EXE} ) set( TIMETRACE2TXT_BIN ${PROJECT_BINARY_DIR}/bin/${TIMETRACE2TXT_EXE} ) set( LLVM_TOOLS_DIR ${LLVM_ROOT_DIR}/bin ) set( LDC2_BIN_DIR ${PROJECT_BINARY_DIR}/bin ) diff --git a/tests/codegen/fcf_protection.d b/tests/codegen/fcf_protection.d new file mode 100644 index 00000000000..e5c28cea2ab --- /dev/null +++ b/tests/codegen/fcf_protection.d @@ -0,0 +1,31 @@ +// Test -fcf-protection + +// REQUIRES: target_X86 + +// RUN: %ldc -mtriple=x86_64-linux-gnu -output-ll -of=%t.ll %s -d-version=NOTHING && FileCheck %s --check-prefix=NOTHING < %t.ll + +// RUN: %ldc -mtriple=x86_64-linux-gnu -output-ll -of=%t_branch.ll %s --fcf-protection=branch -d-version=BRANCH && FileCheck %s --check-prefix=BRANCH < %t_branch.ll +// RUN: %ldc -mtriple=x86_64-linux-gnu -output-ll -of=%t_return.ll %s --fcf-protection=return -d-version=RETURN && FileCheck %s --check-prefix=RETURN < %t_return.ll +// RUN: %ldc -mtriple=x86_64-linux-gnu -output-ll -of=%t_full.ll %s --fcf-protection=full -d-version=FULL && FileCheck %s --check-prefix=FULL < %t_full.ll +// RUN: %ldc -mtriple=x86_64-linux-gnu -output-ll -of=%t_noarg.ll %s --fcf-protection -d-version=FULL && FileCheck %s --check-prefix=FULL < %t_noarg.ll + +// NOTHING-NOT: cf-prot +// BRANCH-DAG: "cf-protection-branch", i32 1 +// RETURN-DAG: "cf-protection-return", i32 1 +// FULL-DAG: "cf-protection-branch", i32 1 +// FULL-DAG: "cf-protection-return", i32 1 + +void foo() {} + +version(NOTHING) { + static assert(__traits(getTargetInfo, "CET") == 0); +} +version(BRANCH) { + static assert(__traits(getTargetInfo, "CET") == 1); +} +version(RETURN) { + static assert(__traits(getTargetInfo, "CET") == 2); +} +version(FULL) { + static assert(__traits(getTargetInfo, "CET") == 3); +} diff --git a/tests/lit.site.cfg.in b/tests/lit.site.cfg.in index 44dac1a1c3c..f18b6f4d3d9 100644 --- a/tests/lit.site.cfg.in +++ b/tests/lit.site.cfg.in @@ -17,10 +17,12 @@ OFF = False config.ldc2_bin = "@LDC2_BIN@" config.ldcprofdata_bin = "@LDCPROFDATA_BIN@" config.ldcprunecache_bin = "@LDCPRUNECACHE_BIN@" +config.ldcbuildplugin_bin = "@LDCBUILDPLUGIN_BIN@" config.timetrace2txt_bin = "@TIMETRACE2TXT_BIN@" config.ldc2_bin_dir = "@LDC2_BIN_DIR@" config.ldc2_lib_dir = "@LDC2_LIB_DIR@" config.ldc2_runtime_dir = "@RUNTIME_DIR@" +config.ldc2_source_dir = "@PROJECT_SOURCE_DIR@" config.test_source_root = "@TESTS_IR_DIR@" config.llvm_tools_dir = "@LLVM_TOOLS_DIR@" config.llvm_version = @LDC_LLVM_VER@ @@ -156,6 +158,7 @@ config.substitutions.append( ('%ldc', config.ldc2_bin) ) config.substitutions.append( ('%gnu_make', config.gnu_make_bin) ) config.substitutions.append( ('%profdata', config.ldcprofdata_bin) ) config.substitutions.append( ('%prunecache', config.ldcprunecache_bin) ) +config.substitutions.append( ('%buildplugin', config.ldcbuildplugin_bin + " --ldcSrcDir=" + config.ldc2_source_dir ) ) config.substitutions.append( ('%timetrace2txt', config.timetrace2txt_bin) ) config.substitutions.append( ('%llvm-spirv', os.path.join(config.llvm_tools_dir, 'llvm-spirv')) ) config.substitutions.append( ('%llc', os.path.join(config.llvm_tools_dir, 'llc')) ) diff --git a/tests/plugins/basic_sema_plugin.d b/tests/plugins/basic_sema_plugin.d new file mode 100644 index 00000000000..873c2584df3 --- /dev/null +++ b/tests/plugins/basic_sema_plugin.d @@ -0,0 +1,26 @@ +// REQUIRES: Plugins + +// For some reason this test fails with missing symbol linking issues (or crash) with macOS on Intel x86 (but not for all CI testers...) +// UNSUPPORTED: Darwin && host_X86 + +// RUN: split-file %s %t --leading-lines +// RUN: %buildplugin %t/plugin.d -of=%t/plugin%so --buildDir=%t/build +// RUN: %ldc -wi -c -o- --plugin=%t/plugin%so %t/testcase.d 2>&1 | FileCheck %t/testcase.d + +//--- plugin.d +import dmd.dmodule : Module; +import dmd.errors; +import dmd.location; + +extern(C) void runSemanticAnalysis(Module m) { + if (m.md) { + warning(m.md.loc, "It works!"); + } +} + +//--- testcase.d +// CHECK: testcase.d([[@LINE+1]]): Warning: It works! +module testcase; +int testfunction(int i) { + return i * 2; +} diff --git a/tests/plugins/lit.local.cfg b/tests/plugins/lit.local.cfg index 06c2097c00e..eb5b296e587 100644 --- a/tests/plugins/lit.local.cfg +++ b/tests/plugins/lit.local.cfg @@ -1,8 +1,28 @@ +import lit.formats +import lit.util import os +import sys import platform +import string import re +import subprocess +import glob if (config.plugins_supported): config.available_features.add('Plugins') config.environment['LLVM_CONFIG'] = os.path.join(config.llvm_tools_dir, 'llvm-config') config.environment['LLVM_VERSION'] = str(config.llvm_version) + + # Set feature that tells us that the just-built LDC is ABI compatible with the host D compiler + # For our tets, the required ABI compatibility seems OK since at least LDC 1.30. + # If the compiler is built not by LDC but another compiler, then assume the ABI to be incompatible. + command = [config.ldc2_bin, '--version'] + p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) + text1 = p.stdout.readline() # Ex.: "LDC - the LLVM D compiler (1.33.0-git-716f627)" + text2 = p.stdout.readline() # Ex.: " based on DMD v2.103.1 and LLVM 14.0.0" + text3 = p.stdout.readline() # Ex.: " built with LDC - the LLVM D compiler (1.33.0-beta2)" + host_version = re.compile(' built with LDC.* \(1\.([0-9]+).*\)').match(text3) + if (host_version and int(host_version.group(1)) >= 30): # 30 = LDC 1.30 + config.available_features.add('ABI_compatible_with_host_D') + + diff --git a/tests/plugins/visitor_example.d b/tests/plugins/visitor_example.d new file mode 100644 index 00000000000..1b106444e58 --- /dev/null +++ b/tests/plugins/visitor_example.d @@ -0,0 +1,61 @@ +// REQUIRES: Plugins +// REQUIRES: ABI_compatible_with_host_D + +// For some reason this test fails with missing symbol linking issues (or crash) with macOS on Intel x86. +// UNSUPPORTED: Darwin && host_X86 + +// RUN: split-file %s %t --leading-lines +// RUN: %buildplugin %t/plugin.d -of=%t/plugin%so --buildDir=%t/build +// RUN: %ldc -wi -c -o- --plugin=%t/plugin%so %t/testcase.d 2>&1 | FileCheck %t/testcase.d + +//--- plugin.d +import dmd.dmodule; +import dmd.errors; +import dmd.location; +import dmd.visitor; +import dmd.declaration; +import dmd.dsymbol; + +extern(C++) class MyVisitor : SemanticTimeTransitiveVisitor { + alias visit = SemanticTimeTransitiveVisitor.visit; + + override void visit(VarDeclaration vd) { + if (vd.aliasTuple) { + vd.aliasTuple.foreachVar((s) { + auto vardecl = s.isVarDeclaration(); + if (vardecl && vardecl.type.needsDestruction()) { + warning(vardecl.loc, "It works!"); + } + }); + } + } +} + +extern(C) void runSemanticAnalysis(Module m) { + scope v = new MyVisitor(); + if (!m.members) + return; + m.members.foreachDsymbol((s) { + s.accept(v); + }); +} + +//--- testcase.d +alias AliasSeq(TList...) = TList; + +int i = 0; +struct A { + ~this() { + i *= 2; + } +} + +void main() { + { + // CHECK: testcase.d([[@LINE+1]]): Warning: + AliasSeq!(A, A) params; + i = 1; + } + + assert(i == 4); +} diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 187c95035dd..eb510f3c940 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -60,3 +60,25 @@ build_d_executable( ${COMPILE_D_MODULES_SEPARATELY} ) install(PROGRAMS ${TIMETRACE2TXT_EXE_FULL} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) + +############################################################################# +# Only build ldc-build-plugin tool for platforms where plugins are actually enabled. +if(LDC_ENABLE_PLUGINS) + configure_file(${PROJECT_SOURCE_DIR}/tools/ldc-build-plugin.d.in ${PROJECT_BINARY_DIR}/ldc-build-plugin.d @ONLY) + set(LDC_BUILD_PLUGIN_EXE ldc-build-plugin) + set(LDC_BUILD_PLUGIN_EXE ${LDC_BUILD_PLUGIN_EXE} PARENT_SCOPE) # needed for correctly populating lit.site.cfg.in + set(LDC_BUILD_PLUGIN_EXE_NAME ${PROGRAM_PREFIX}${LDC_BUILD_PLUGIN_EXE}${PROGRAM_SUFFIX}) + set(LDC_BUILD_PLUGIN_EXE_FULL ${PROJECT_BINARY_DIR}/bin/${LDC_BUILD_PLUGIN_EXE_NAME}${CMAKE_EXECUTABLE_SUFFIX}) + build_d_executable( + "${LDC_BUILD_PLUGIN_EXE}" + "${LDC_BUILD_PLUGIN_EXE_FULL}" + "${PROJECT_BINARY_DIR}/ldc-build-plugin.d" + "${DFLAGS_BUILD_TYPE}" + "" + "${PROJECT_SOURCE_DIR}/tools/ldc-build-plugin.d.in" + "" + ${COMPILE_D_MODULES_SEPARATELY} + ) + install(PROGRAMS ${LDC_BUILD_PLUGIN_EXE_FULL} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) +endif() + diff --git a/tools/ldc-build-plugin.d.in b/tools/ldc-build-plugin.d.in new file mode 100644 index 00000000000..84fd25ff357 --- /dev/null +++ b/tools/ldc-build-plugin.d.in @@ -0,0 +1,239 @@ +module ldcBuildRuntime; + +import core.stdc.stdlib : exit; +import std.algorithm; +import std.array; +import std.file; +import std.path; +import std.stdio; + +version (OSX) + version = Darwin; +else version (iOS) + version = Darwin; +else version (TVOS) + version = Darwin; +else version (WatchOS) + version = Darwin; + +struct Config { + string ldcExecutable; + string buildDir; + string ldcSourceDir; + string[] dFlags; + string[] linkerFlags; + bool verbose; + string[] ldcArgs; + string userWorkDir; +} + +version (Windows) enum exeSuffix = ".exe"; +else enum exeSuffix = ""; + +string defaultLdcExecutable; +Config config; + +int main(string[] args) { + enum exeName = "ldc2" ~ exeSuffix; + defaultLdcExecutable = buildPath(thisExePath.dirName, exeName); + config.userWorkDir = getcwd(); + + parseCommandLine(args); + + findLdcExecutable(); + + prepareBuildDir(); + + prepareLdcSource(); + + build(); + + if (config.verbose) + writefln(".: Plugin library built successfully."); + return 0; +} + +void findLdcExecutable() { + if (config.ldcExecutable !is null) { + if (!config.ldcExecutable.exists) { + writefln(".: Error: LDC executable not found: %s", config.ldcExecutable); + exit(1); + } + config.ldcExecutable = config.ldcExecutable.absolutePath; + return; + } + + if (defaultLdcExecutable.exists) { + config.ldcExecutable = defaultLdcExecutable; + return; + } + + writefln(".: Please specify LDC executable via '--ldc='. Aborting.", exeSuffix); + exit(1); +} + +void prepareBuildDir() { + if (config.buildDir is null) + config.buildDir = "ldc-build-plugin.tmp"; + + if (!config.buildDir.exists) { + if (config.verbose) + writefln(".: Creating build directory: %s", config.buildDir); + mkdirRecurse(config.buildDir); + } + + config.buildDir = config.buildDir.absolutePath; +} + +void prepareLdcSource() { + if (config.ldcSourceDir !is null) { + if (!config.ldcSourceDir.exists) { + writefln(".: Error: LDC source directory not found: %s", config.ldcSourceDir); + exit(1); + } + config.ldcSourceDir = config.ldcSourceDir.absolutePath; + return; + } + + const ldcSrc = "ldc-src"; + config.ldcSourceDir = buildPath(config.buildDir, ldcSrc); + if (buildPath(config.ldcSourceDir, "dmd").exists) + return; + + // Download & extract LDC source archive if /ldc-src/dmd doesn't exist yet. + + const wd = WorkingDirScope(config.buildDir); + + auto ldcVersion = "@LDC_VERSION@"; + void removeVersionSuffix(string beginning) { + const suffixIndex = ldcVersion.countUntil(beginning); + if (suffixIndex > 0) + ldcVersion = ldcVersion[0 .. suffixIndex]; + } + removeVersionSuffix("git-"); + removeVersionSuffix("-dirty"); + + import std.format : format; + const localArchiveFile = "ldc-src.zip"; + if (!localArchiveFile.exists) { + const url = "https://github.com/ldc-developers/ldc/releases/download/v%1$s/ldc-%1$s-src.zip".format(ldcVersion); + writefln(".: Downloading LDC source archive: %s", url); + import std.net.curl : download; + download(url, localArchiveFile); + if (getSize(localArchiveFile) < 1_048_576) { + writefln(".: Error: downloaded file is corrupt; has LDC v%s been released?", ldcVersion); + writefln(" You can work around this by manually downloading a src package and moving it to: %s", + buildPath(config.buildDir, localArchiveFile)); + localArchiveFile.remove; + exit(1); + } + } + + extractZipArchive(localArchiveFile, "."); + rename("ldc-%1$s-src".format(ldcVersion), ldcSrc); +} + +void build() { + string[] args = [ + config.ldcExecutable, + "-I" ~ config.ldcSourceDir, + "--d-version=IN_LLVM", + "-J" ~ buildPath(config.ldcSourceDir, "dmd", "res"), + "--shared", + "--defaultlib=", + "--od=" ~ config.buildDir + ]; + + version (Darwin) { + args ~= "-L-Wl,-undefined,dynamic_lookup"; + } + + args ~= config.ldcArgs; + + exec(args); +} + +/*** helpers ***/ + +struct WorkingDirScope { + string originalPath; + this(string path) { originalPath = getcwd(); chdir(path); } + ~this() { chdir(originalPath); } +} + +void exec(string[] command) { + import std.process; + + static string quoteIfNeeded(string arg) { + const r = arg.findAmong(" ;"); + return !r.length ? arg : "'" ~ arg ~ "'"; + } + string flattened = command.map!quoteIfNeeded.join(" "); + if (config.verbose) { + writefln(".: Invoking: %s", flattened); + stdout.flush(); + } + + auto pid = spawnProcess(command, null, std.process.Config.none, config.userWorkDir); + const exitStatus = wait(pid); + + if (exitStatus != 0) { + if (config.verbose) + writeln(".: Error: command failed with status ", exitStatus); + exit(1); + } +} + +void extractZipArchive(string archivePath, string destination) { + import std.zip; + + auto archive = new ZipArchive(std.file.read(archivePath)); + foreach (name, am; archive.directory) { + const destPath = buildNormalizedPath(destination, name); + + const isDir = name.endsWith("/"); + const destDir = isDir ? destPath : destPath.dirName; + mkdirRecurse(destDir); + + if (!isDir) + std.file.write(destPath, archive.expand(am)); + } +} + +void parseCommandLine(string[] args) { + import std.getopt; + + try { + arraySep = ";"; + auto helpInformation = getopt( + args, + std.getopt.config.passThrough, + "ldc", "Path to LDC executable (default: '" ~ defaultLdcExecutable ~ "')", &config.ldcExecutable, + "buildDir", "Path to build directory (default: './ldc-build-plugin.tmp')", &config.buildDir, + "ldcSrcDir", "Path to LDC source directory (if not specified: downloads & extracts source archive into '/ldc-src')", &config.ldcSourceDir, + "dFlags", "Extra LDC flags for the D module (separated by ';')", &config.dFlags, + "verbose|v", "Verbose output (e.g. showing the compile commandline)", &config.verbose, + "linkerFlags", "Extra C linker flags for shared libraries and testrunner executables (separated by ';')", &config.linkerFlags + ); + + // getopt() has removed all consumed args from `args` + // Remaining arguments are interpreted as LDC arguments (e.g. plugin source files and -of=). + config.ldcArgs = args[1 .. $]; + + if (helpInformation.helpWanted) { + defaultGetoptPrinter( + "OVERVIEW: Builds a Semantic Analysis plugin for LDC.\n\n" ~ + "USAGE: ldc-build-plugin [options] sourcefiles... -of=\n\n" ~ + "OPTIONS:\n" ~ + " Unrecognized options are passed through to LDC.", + helpInformation.options + ); + exit(1); + } + } + catch (Exception e) { + writefln("Error processing command line arguments: %s", e.msg); + writeln("Use '--help' for help."); + exit(1); + } +}