Skip to content

Commit f5462a2

Browse files
kinkeJohanEngelen
authored andcommitted
Merge pull request ldc-developers#4430 from JohanEngelen/sema_plugin
Add support for Semantic Analysis plugins
1 parent 65729ed commit f5462a2

14 files changed

+565
-37
lines changed

.github/actions/3-build-cross/action.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,4 @@ runs:
145145
${{ inputs.cmake_flags }}
146146
${{ inputs.with_pgo == 'true' && '-DDFLAGS_LDC=-fprofile-use=../pgo-ldc/merged.profdata' || '' }}
147147
${{ env.CROSS_CMAKE_FLAGS }}
148-
build_targets: ldc2 ldmd2 ldc-build-runtime ldc-profdata ldc-prune-cache timetrace2txt
148+
build_targets: ldc2 ldmd2 ldc-build-runtime ldc-build-plugin ldc-profdata ldc-prune-cache timetrace2txt

.github/actions/5-install/action.yml

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ runs:
2222
else
2323
mkdir -p install/bin
2424
cp build-cross/bin/{ldc2,ldmd2,ldc-build-runtime,ldc-profdata,ldc-prune-cache,timetrace2txt} install/bin/
25+
cp build-cross/bin/ldc-build-plugin install/bin/ || true
2526
cp -R build-cross-libs/lib install/
2627
cp build-cross/lib/{libldc_rt.*,libLTO-ldc.dylib,LLVMgold-ldc.so} install/lib/ || true
2728
mkdir install/etc

CHANGELOG.md

+29
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,34 @@
11
# LDC master
22

3+
#### Big news
4+
- 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)
5+
- 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)
6+
- New tool `ldc-build-plugin` that helps compiling user plugins. It downloads the correct LDC source version (if its not already available), and calls LDC with the correct commandline flags to build a plugin. (#4430)
7+
- 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)
8+
- 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)
9+
- 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).
10+
- 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)
11+
- New command-line option `--fcf-protection`, which enables Intel's Control-Flow Enforcement Technology (CET). (#4437)
12+
13+
#### Platform support
14+
15+
#### Bug fixes
16+
- 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)
17+
18+
# LDC 1.32.2 (2023-05-12)
19+
20+
#### Big news
21+
- New command-line option `--fwarn-stack-size=<threshold>` with LLVM 13+. (#4378)
22+
- New command-line option `--fsplit-stack` for incremental stack allocations, see https://llvm.org/docs/SegmentedStacks.html. (#4379)
23+
- New UDA `ldc.attributes.noSplitStack` disables it on a per-function basis. (#4382)
24+
- New command-line option `--indent` for the `timetrace2txt` tool. (#4391)
25+
26+
#### Bug fixes
27+
- Fix potentially huge compile slowdowns with `-g` and LLVM 15+. (#4354, #4393)
28+
- Treat *all* LLVM warnings as regular warnings (e.g., errors with `-w`). Requires LLVM 13+. (#4384)
29+
30+
# LDC 1.32.1 (2023-04-17)
31+
332
#### Big news
433
- The prebuilt Linux packages are now generated on a Ubuntu 20.04 box, so the min required `glibc` version has been raised from 2.26 to 2.31. (#4367)
534

CMakeLists.txt

+39-25
Original file line numberDiff line numberDiff line change
@@ -246,17 +246,6 @@ if(CMAKE_COMPILER_IS_GNUCXX OR (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang"))
246246
endif()
247247
endif()
248248

249-
if(NOT WIN32 AND NOT CYGWIN)
250-
# Unify symbol visibility with LLVM to silence linker warning "direct access in function X to global
251-
# weak symbol Y means the weak symbol cannot be overridden at runtime. This was likely caused by
252-
# different translation units being compiled with different visibility settings."
253-
# See LLVM's cmake/modules/HandleLLVMOptions.cmake.
254-
check_cxx_compiler_flag("-fvisibility-inlines-hidden" SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG)
255-
if (${SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG})
256-
append("-fvisibility-inlines-hidden" LDC_CXXFLAGS)
257-
endif()
258-
endif()
259-
260249
if(MSVC)
261250
# Remove flags here, for exceptions and RTTI.
262251
# CL.EXE complains to override flags like "/GR /GR-".
@@ -632,6 +621,22 @@ if(LDC_WITH_LLD)
632621
endif()
633622
endif()
634623

624+
if(NOT DEFINED LDC_LINK_MANUALLY)
625+
if(MSVC)
626+
# Use the D host compiler for linking D executables.
627+
set(LDC_LINK_MANUALLY OFF)
628+
else()
629+
# On Unix-like systems, default to having CMake link the D executables via the C++ compiler.
630+
# (Using the D compiler needs -Xcc and -gcc support, see file BuildDExecutable.cmake.)
631+
set(LDC_LINK_MANUALLY ON)
632+
endif()
633+
endif()
634+
if(LDC_LINK_MANUALLY AND NOT DEFINED D_LINKER_ARGS)
635+
include(ExtractDMDSystemLinker)
636+
message(STATUS "Host D compiler linker program: ${D_LINKER_COMMAND}")
637+
message(STATUS "Host D compiler linker flags: ${D_LINKER_ARGS}")
638+
endif()
639+
635640
# Plugin support
636641
if(UNIX)
637642
set(LDC_ENABLE_PLUGINS_DEFAULT ON)
@@ -643,7 +648,16 @@ if(LDC_ENABLE_PLUGINS)
643648
add_definitions(-DLDC_ENABLE_PLUGINS)
644649

645650
if(APPLE)
646-
# No extra link flags needed.
651+
# Need to disable dead_strip with LDC host compilers.
652+
if("${D_COMPILER_ID}" STREQUAL "LDMD")
653+
if(LDC_LINK_MANUALLY)
654+
# suboptimal - applies to all D executables (incl. ldmd2, ldc-build-runtime, ldc-prune-cache)
655+
list(REMOVE_ITEM D_LINKER_ARGS "-Wl,-dead_strip")
656+
else()
657+
# just for ldc2 (and ldc2-unittest)
658+
append("-disable-linker-strip-dead" DFLAGS_LDC)
659+
endif()
660+
endif()
647661
elseif(UNIX)
648662
# For plugin support, we need to link with --export-dynamic on Unix.
649663
# Make sure the linker supports --export-dynamic (on Solaris it is not supported and also not needed).
@@ -654,26 +668,26 @@ if(LDC_ENABLE_PLUGINS)
654668

655669
if(LINKER_ACCEPTS_EXPORT_DYNAMIC_FLAG)
656670
set(LDC_LINKERFLAG_LIST "${LDC_LINKERFLAG_LIST};-Wl,--export-dynamic")
671+
else()
672+
message(WARNING "Linker does not accept --export-dynamic, user plugins may give missing symbol errors upon load")
657673
endif()
658674
endif()
659675
endif()
660676
message(STATUS "-- Building LDC with plugin support (LDC_ENABLE_PLUGINS): ${LDC_ENABLE_PLUGINS}")
677+
message(STATUS "-- Linking LDC with flags: ${ALTERNATIVE_MALLOC_O};${LDC_LINKERFLAG_LIST}")
661678

662-
if(NOT DEFINED LDC_LINK_MANUALLY)
663-
if(MSVC)
664-
# Use the D host compiler for linking D executables.
665-
set(LDC_LINK_MANUALLY OFF)
666-
else()
667-
# On Unix-like systems, default to having CMake link the D executables via the C++ compiler.
668-
# (Using the D compiler needs -Xcc and -gcc support, see file BuildDExecutable.cmake.)
669-
set(LDC_LINK_MANUALLY ON)
679+
if(NOT WIN32 AND NOT CYGWIN)
680+
# Unify symbol visibility with LLVM to silence linker warning "direct access in function X to global
681+
# weak symbol Y means the weak symbol cannot be overridden at runtime. This was likely caused by
682+
# different translation units being compiled with different visibility settings."
683+
# See LLVM's cmake/modules/HandleLLVMOptions.cmake.
684+
check_cxx_compiler_flag("-fvisibility-inlines-hidden" SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG)
685+
if (LDC_ENABLE_PLUGINS AND NOT APPLE)
686+
# 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.
687+
elseif (${SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG})
688+
append("-fvisibility-inlines-hidden" LDC_CXXFLAGS)
670689
endif()
671690
endif()
672-
if(LDC_LINK_MANUALLY AND NOT DEFINED D_LINKER_ARGS)
673-
include(ExtractDMDSystemLinker)
674-
message(STATUS "Host D compiler linker program: ${D_LINKER_COMMAND}")
675-
message(STATUS "Host D compiler linker flags: ${D_LINKER_ARGS}")
676-
endif()
677691

678692
build_d_executable(
679693
"${LDC_EXE}"

cmake/Modules/BuildDExecutable.cmake

+4-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ endmacro()
2525
# - DFLAGS_BASE
2626
# - LDC_LINK_MANUALLY
2727
# - D_LINKER_ARGS
28+
# - LDC_ENABLE_PLUGINS
2829
function(build_d_executable target_name output_exe d_src_files compiler_args linker_args extra_compile_deps link_deps compile_separately)
2930
set(dflags "${D_COMPILER_FLAGS} ${DFLAGS_BASE} ${compiler_args}")
3031
if(UNIX)
@@ -40,7 +41,9 @@ function(build_d_executable target_name output_exe d_src_files compiler_args lin
4041
# Compile all D modules to a single object.
4142
set(object_file ${PROJECT_BINARY_DIR}/obj/${target_name}${CMAKE_CXX_OUTPUT_EXTENSION})
4243
# Default to -linkonce-templates with LDMD host compiler, to speed-up optimization.
43-
if("${D_COMPILER_ID}" STREQUAL "LDMD")
44+
if("${target_name}" STREQUAL "ldc2" AND LDC_ENABLE_PLUGINS)
45+
# For plugin support we need ldc2's symbols to be global, don't use -linkonce-templates.
46+
elseif("${D_COMPILER_ID}" STREQUAL "LDMD")
4447
set(dflags -linkonce-templates ${dflags})
4548
endif()
4649
add_custom_command(

driver/plugins.cpp

+110-8
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
//
1010
// Implements functionality related to plugins (`-plugin=...`).
1111
//
12+
// Note: plugins can be LLVM-plugins (to be registered with the pass manager)
13+
// or dlang-plugins for semantic analysis.
14+
//
1215
//===----------------------------------------------------------------------===//
1316

1417
#include "driver/plugins.h"
@@ -17,6 +20,8 @@
1720

1821
#include "dmd/errors.h"
1922
#include "dmd/globals.h"
23+
#include "dmd/module.h"
24+
#include "llvm/Passes/PassBuilder.h"
2025
#include "llvm/Support/CommandLine.h"
2126
#include "llvm/Support/DynamicLibrary.h"
2227

@@ -27,23 +32,120 @@ cl::list<std::string>
2732
pluginFiles("plugin", cl::CommaSeparated, cl::desc("Plugins to load."),
2833
cl::value_desc("dynamic_library.so,lib2.so"));
2934

35+
struct SemaPlugin {
36+
llvm::sys::DynamicLibrary library;
37+
void (*runSemanticAnalysis)(Module *);
38+
39+
SemaPlugin(const llvm::sys::DynamicLibrary &library,
40+
void (*runSemanticAnalysis)(Module *))
41+
: library(library), runSemanticAnalysis(runSemanticAnalysis) {}
42+
};
43+
44+
llvm::SmallVector<SemaPlugin, 1> sema_plugins;
45+
46+
} // anonymous namespace
47+
48+
// Tries to load plugin as SemanticAnalysis. Returns true on 'success', i.e. no
49+
// further attempts needed.
50+
bool loadSemanticAnalysisPlugin(const std::string &filename) {
51+
std::string errorString;
52+
auto library = llvm::sys::DynamicLibrary::getPermanentLibrary(
53+
filename.c_str(), &errorString);
54+
if (!library.isValid()) {
55+
error(Loc(), "Error loading plugin '%s': %s", filename.c_str(),
56+
errorString.c_str());
57+
return true; // No success, but no need to try loading again as LLVM plugin.
58+
}
59+
60+
// SemanticAnalysis plugins need to export the `runSemanticAnalysis` function.
61+
void *runSemanticAnalysisFnPtr =
62+
library.getAddressOfSymbol("runSemanticAnalysis");
63+
64+
// If the symbol isn't found, this is probably an LLVM plugin.
65+
if (!runSemanticAnalysisFnPtr)
66+
return false;
67+
68+
sema_plugins.emplace_back(
69+
library, reinterpret_cast<void (*)(Module *)>(runSemanticAnalysisFnPtr));
70+
return true;
71+
}
72+
73+
/// Loads plugin for the legacy pass manager. The static constructor of
74+
/// the plugin should take care of the plugins registering themself with the
75+
/// rest of LDC/LLVM.
76+
void loadLLVMPluginLegacyPM(const std::string &filename) {
77+
std::string errorString;
78+
if (llvm::sys::DynamicLibrary::LoadLibraryPermanently(filename.c_str(),
79+
&errorString)) {
80+
error(Loc(), "Error loading plugin '%s': %s", filename.c_str(),
81+
errorString.c_str());
82+
}
83+
}
84+
85+
#if LDC_LLVM_VER >= 1400
86+
87+
namespace {
88+
llvm::SmallVector<llvm::PassPlugin, 1> llvm_plugins;
89+
90+
/// Loads plugin for the new pass manager. The plugin will need to be
91+
/// added explicitly when building the optimization pipeline.
92+
void loadLLVMPluginNewPM(const std::string &filename) {
93+
94+
auto plugin = llvm::PassPlugin::Load(filename);
95+
if (!plugin) {
96+
error(Loc(), "Error loading plugin '%s': %s", filename.c_str(),
97+
llvm::toString(plugin.takeError()).c_str());
98+
return;
99+
}
100+
llvm_plugins.emplace_back(plugin.get());
101+
}
102+
30103
} // anonymous namespace
31104

32-
/// Loads all plugins. The static constructor of each plugin should take care of
33-
/// the plugins registering themself with the rest of LDC/LLVM.
105+
#endif // LDC_LLVM_VER >= 1400
106+
107+
void loadLLVMPlugin(const std::string &filename) {
108+
#if LDC_LLVM_VER >= 1400
109+
if (opts::isUsingLegacyPassManager())
110+
loadLLVMPluginLegacyPM(filename);
111+
else
112+
loadLLVMPluginNewPM(filename);
113+
#else
114+
loadLLVMPluginLegacyPM(filename);
115+
#endif
116+
}
117+
34118
void loadAllPlugins() {
35119
for (auto &filename : pluginFiles) {
36-
std::string errorString;
37-
if (llvm::sys::DynamicLibrary::LoadLibraryPermanently(filename.c_str(),
38-
&errorString)) {
39-
error(Loc(), "Error loading plugin '%s': %s", filename.c_str(),
40-
errorString.c_str());
41-
}
120+
// First attempt to load plugin as SemanticAnalysis plugin. If unsuccesfull,
121+
// load as LLVM plugin.
122+
auto success = loadSemanticAnalysisPlugin(filename);
123+
if (!success)
124+
loadLLVMPlugin(filename);
125+
}
126+
}
127+
128+
void registerAllPluginsWithPassBuilder(llvm::PassBuilder &PB) {
129+
#if LDC_LLVM_VER >= 1400
130+
for (auto &plugin : llvm_plugins) {
131+
plugin.registerPassBuilderCallbacks(PB);
132+
}
133+
#endif
134+
}
135+
136+
void runAllSemanticAnalysisPlugins(Module *m) {
137+
for (auto &plugin : sema_plugins) {
138+
assert(plugin.runSemanticAnalysis);
139+
plugin.runSemanticAnalysis(m);
42140
}
43141
}
44142

45143
#else // #if LDC_ENABLE_PLUGINS
46144

145+
class Module;
146+
47147
void loadAllPlugins() {}
148+
void registerAllPluginsWithPassBuilder(llvm::PassBuilder &) {}
149+
void runAllSemanticAnalysisPlugins(Module *m) {}
48150

49151
#endif // LDC_ENABLE_PLUGINS

gen/semantic.d

+9-2
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,20 @@ import dmd.dmodule;
1515

1616
extern(C++) void dcomputeSemanticAnalysis(Module m);
1717
extern(C) int hasComputeAttr(Dsymbol m);
18+
extern(C++) void runAllSemanticAnalysisPlugins(Module m);
1819

1920
extern(C++) void extraLDCSpecificSemanticAnalysis(ref Modules modules)
2021
{
22+
// First finish DCompute SemA for all modules, before calling plugins.
2123
foreach(m; modules[])
2224
{
23-
if (hasComputeAttr(m))
25+
if (hasComputeAttr(m)) {
2426
dcomputeSemanticAnalysis(m);
27+
}
28+
}
29+
30+
foreach(m; modules[])
31+
{
32+
runAllSemanticAnalysisPlugins(m);
2533
}
26-
2734
}

tests/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
set( LDC2_BIN ${PROJECT_BINARY_DIR}/bin/${LDC_EXE} )
22
set( LDCPROFDATA_BIN ${PROJECT_BINARY_DIR}/bin/ldc-profdata )
33
set( LDCPRUNECACHE_BIN ${PROJECT_BINARY_DIR}/bin/${LDCPRUNECACHE_EXE} )
4+
set( LDCBUILDPLUGIN_BIN ${PROJECT_BINARY_DIR}/bin/${LDC_BUILD_PLUGIN_EXE} )
45
set( TIMETRACE2TXT_BIN ${PROJECT_BINARY_DIR}/bin/${TIMETRACE2TXT_EXE} )
56
set( LLVM_TOOLS_DIR ${LLVM_ROOT_DIR}/bin )
67
set( LDC2_BIN_DIR ${PROJECT_BINARY_DIR}/bin )

tests/lit.site.cfg.in

+3
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ OFF = False
1717
config.ldc2_bin = "@LDC2_BIN@"
1818
config.ldcprofdata_bin = "@LDCPROFDATA_BIN@"
1919
config.ldcprunecache_bin = "@LDCPRUNECACHE_BIN@"
20+
config.ldcbuildplugin_bin = "@LDCBUILDPLUGIN_BIN@"
2021
config.timetrace2txt_bin = "@TIMETRACE2TXT_BIN@"
2122
config.ldc2_bin_dir = "@LDC2_BIN_DIR@"
2223
config.ldc2_lib_dir = "@LDC2_LIB_DIR@"
2324
config.ldc2_runtime_dir = "@RUNTIME_DIR@"
25+
config.ldc2_source_dir = "@PROJECT_SOURCE_DIR@"
2426
config.test_source_root = "@TESTS_IR_DIR@"
2527
config.llvm_tools_dir = "@LLVM_TOOLS_DIR@"
2628
config.llvm_version = @LDC_LLVM_VER@
@@ -156,6 +158,7 @@ config.substitutions.append( ('%ldc', config.ldc2_bin) )
156158
config.substitutions.append( ('%gnu_make', config.gnu_make_bin) )
157159
config.substitutions.append( ('%profdata', config.ldcprofdata_bin) )
158160
config.substitutions.append( ('%prunecache', config.ldcprunecache_bin) )
161+
config.substitutions.append( ('%buildplugin', config.ldcbuildplugin_bin + " --ldcSrcDir=" + config.ldc2_source_dir ) )
159162
config.substitutions.append( ('%timetrace2txt', config.timetrace2txt_bin) )
160163
config.substitutions.append( ('%llvm-spirv', os.path.join(config.llvm_tools_dir, 'llvm-spirv')) )
161164
config.substitutions.append( ('%runtimedir', config.ldc2_runtime_dir ) )

tests/plugins/basic_sema_plugin.d

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// REQUIRES: Plugins
2+
3+
// For some reason this test fails with missing symbol linking issues (or crash) with macOS on Intel x86 (but not for all CI testers...)
4+
// UNSUPPORTED: Darwin && host_X86
5+
6+
// RUN: split-file %s %t --leading-lines
7+
// RUN: %buildplugin %t/plugin.d -of=%t/plugin%so --buildDir=%t/build
8+
// RUN: %ldc -wi -c -o- --plugin=%t/plugin%so %t/testcase.d 2>&1 | FileCheck %t/testcase.d
9+
10+
//--- plugin.d
11+
import dmd.dmodule : Module;
12+
import dmd.errors;
13+
import dmd.location;
14+
15+
extern(C) void runSemanticAnalysis(Module m) {
16+
if (m.md) {
17+
warning(m.md.loc, "It works!");
18+
}
19+
}
20+
21+
//--- testcase.d
22+
// CHECK: testcase.d([[@LINE+1]]): Warning: It works!
23+
module testcase;
24+
int testfunction(int i) {
25+
return i * 2;
26+
}

0 commit comments

Comments
 (0)