Skip to content

Commit ef7789a

Browse files
authored
Merge pull request #4430 from JohanEngelen/sema_plugin
Add support for Semantic Analysis plugins
2 parents 234d859 + 64655c2 commit ef7789a

File tree

14 files changed

+502
-51
lines changed

14 files changed

+502
-51
lines changed

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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
#### Big news
44
- 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)
57
- 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)
68
- 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)
79
- 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).

CMakeLists.txt

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -249,17 +249,6 @@ if(CMAKE_COMPILER_IS_GNUCXX OR (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang"))
249249
endif()
250250
endif()
251251

252-
if(NOT WIN32 AND NOT CYGWIN)
253-
# Unify symbol visibility with LLVM to silence linker warning "direct access in function X to global
254-
# weak symbol Y means the weak symbol cannot be overridden at runtime. This was likely caused by
255-
# different translation units being compiled with different visibility settings."
256-
# See LLVM's cmake/modules/HandleLLVMOptions.cmake.
257-
check_cxx_compiler_flag("-fvisibility-inlines-hidden" SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG)
258-
if (${SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG})
259-
append("-fvisibility-inlines-hidden" LDC_CXXFLAGS)
260-
endif()
261-
endif()
262-
263252
if(MSVC)
264253
# Remove flags here, for exceptions and RTTI.
265254
# CL.EXE complains to override flags like "/GR /GR-".
@@ -683,10 +672,26 @@ if(LDC_ENABLE_PLUGINS)
683672

684673
if(LINKER_ACCEPTS_EXPORT_DYNAMIC_FLAG)
685674
set(LDC_LINKERFLAG_LIST "${LDC_LINKERFLAG_LIST};-Wl,--export-dynamic")
675+
else()
676+
message(WARNING "Linker does not accept --export-dynamic, user plugins may give missing symbol errors upon load")
686677
endif()
687678
endif()
688679
endif()
689680
message(STATUS "-- Building LDC with plugin support (LDC_ENABLE_PLUGINS): ${LDC_ENABLE_PLUGINS}")
681+
message(STATUS "-- Linking LDC with flags: ${ALTERNATIVE_MALLOC_O};${LDC_LINKERFLAG_LIST}")
682+
683+
if(NOT WIN32 AND NOT CYGWIN)
684+
# Unify symbol visibility with LLVM to silence linker warning "direct access in function X to global
685+
# weak symbol Y means the weak symbol cannot be overridden at runtime. This was likely caused by
686+
# different translation units being compiled with different visibility settings."
687+
# See LLVM's cmake/modules/HandleLLVMOptions.cmake.
688+
check_cxx_compiler_flag("-fvisibility-inlines-hidden" SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG)
689+
if (LDC_ENABLE_PLUGINS AND NOT APPLE)
690+
# 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.
691+
elseif (${SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG})
692+
append("-fvisibility-inlines-hidden" LDC_CXXFLAGS)
693+
endif()
694+
endif()
690695

691696
build_d_executable(
692697
"${LDC_EXE}"

cmake/Modules/BuildDExecutable.cmake

Lines changed: 4 additions & 1 deletion
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

Lines changed: 97 additions & 36 deletions
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,7 @@
1720

1821
#include "dmd/errors.h"
1922
#include "dmd/globals.h"
23+
#include "dmd/module.h"
2024
#include "llvm/Passes/PassBuilder.h"
2125
#include "llvm/Support/CommandLine.h"
2226
#include "llvm/Support/DynamicLibrary.h"
@@ -36,63 +40,120 @@ cl::list<std::string> pluginFiles("plugin", cl::CommaSeparated,
3640
cl::desc("Pass plugins to load."),
3741
cl::value_desc("dynamic_library.so,lib2.so"));
3842

43+
struct SemaPlugin {
44+
llvm::sys::DynamicLibrary library;
45+
void (*runSemanticAnalysis)(Module *);
46+
47+
SemaPlugin(const llvm::sys::DynamicLibrary &library,
48+
void (*runSemanticAnalysis)(Module *))
49+
: library(library), runSemanticAnalysis(runSemanticAnalysis) {}
50+
};
51+
52+
llvm::SmallVector<SemaPlugin, 1> sema_plugins;
53+
3954
} // anonymous namespace
4055

41-
#if LDC_LLVM_VER >= 1400
56+
// Tries to load plugin as SemanticAnalysis. Returns true on 'success', i.e. no
57+
// further attempts needed.
58+
bool loadSemanticAnalysisPlugin(const std::string &filename) {
59+
std::string errorString;
60+
auto library = llvm::sys::DynamicLibrary::getPermanentLibrary(
61+
filename.c_str(), &errorString);
62+
if (!library.isValid()) {
63+
error(Loc(), "Error loading plugin '%s': %s", filename.c_str(),
64+
errorString.c_str());
65+
return true; // No success, but no need to try loading again as LLVM plugin.
66+
}
4267

43-
namespace {
44-
llvm::SmallVector<llvm::PassPlugin, 1> plugins;
68+
// SemanticAnalysis plugins need to export the `runSemanticAnalysis` function.
69+
void *runSemanticAnalysisFnPtr =
70+
library.getAddressOfSymbol("runSemanticAnalysis");
71+
72+
// If the symbol isn't found, this is probably an LLVM plugin.
73+
if (!runSemanticAnalysisFnPtr)
74+
return false;
75+
76+
sema_plugins.emplace_back(
77+
library, reinterpret_cast<void (*)(Module *)>(runSemanticAnalysisFnPtr));
78+
return true;
4579
}
46-
/// Loads all plugins for the new pass manager. These plugins will need to be
47-
/// added When building the optimization pipeline.
48-
void loadAllPluginsNewPM() {
49-
for (auto &filename : pluginFiles) {
50-
auto plugin = llvm::PassPlugin::Load(filename);
51-
if (!plugin) {
52-
error(Loc(), "Error loading plugin '%s': %s", filename.c_str(),
53-
llvm::toString(plugin.takeError()).c_str());
54-
continue;
55-
}
56-
plugins.emplace_back(plugin.get());
80+
81+
/// Loads plugin for the legacy pass manager. The static constructor of
82+
/// the plugin should take care of the plugins registering themself with the
83+
/// rest of LDC/LLVM.
84+
void loadLLVMPluginLegacyPM(const std::string &filename) {
85+
std::string errorString;
86+
if (llvm::sys::DynamicLibrary::LoadLibraryPermanently(filename.c_str(),
87+
&errorString)) {
88+
error(Loc(), "Error loading plugin '%s': %s", filename.c_str(),
89+
errorString.c_str());
5790
}
5891
}
59-
void registerAllPluginsWithPassBuilder(llvm::PassBuilder &PB) {
60-
for (auto &plugin : plugins) {
61-
plugin.registerPassBuilderCallbacks(PB);
92+
93+
#if LDC_LLVM_VER >= 1400
94+
95+
namespace {
96+
llvm::SmallVector<llvm::PassPlugin, 1> llvm_plugins;
97+
98+
/// Loads plugin for the new pass manager. The plugin will need to be
99+
/// added explicitly when building the optimization pipeline.
100+
void loadLLVMPluginNewPM(const std::string &filename) {
101+
102+
auto plugin = llvm::PassPlugin::Load(filename);
103+
if (!plugin) {
104+
error(Loc(), "Error loading plugin '%s': %s", filename.c_str(),
105+
llvm::toString(plugin.takeError()).c_str());
106+
return;
62107
}
108+
llvm_plugins.emplace_back(plugin.get());
63109
}
64110

111+
} // anonymous namespace
112+
65113
#endif // LDC_LLVM_VER >= 1400
66114

67-
/// Loads all plugins for the legacy pass manaager. The static constructor of
68-
/// each plugin should take care of the plugins registering themself with the
69-
/// rest of LDC/LLVM.
70-
void loadAllPluginsLegacyPM() {
115+
void loadLLVMPlugin(const std::string &filename) {
116+
#if LDC_LLVM_VER >= 1400
117+
if (opts::isUsingLegacyPassManager())
118+
loadLLVMPluginLegacyPM(filename);
119+
else
120+
loadLLVMPluginNewPM(filename);
121+
#else
122+
loadLLVMPluginLegacyPM(filename);
123+
#endif
124+
}
125+
126+
void loadAllPlugins() {
71127
for (auto &filename : pluginFiles) {
72-
std::string errorString;
73-
if (llvm::sys::DynamicLibrary::LoadLibraryPermanently(filename.c_str(),
74-
&errorString)) {
75-
error(Loc(), "Error loading plugin '%s': %s", filename.c_str(),
76-
errorString.c_str());
77-
}
128+
// First attempt to load plugin as SemanticAnalysis plugin. If unsuccesfull,
129+
// load as LLVM plugin.
130+
auto success = loadSemanticAnalysisPlugin(filename);
131+
if (!success)
132+
loadLLVMPlugin(filename);
78133
}
79134
}
80135

136+
void registerAllPluginsWithPassBuilder(llvm::PassBuilder &PB) {
81137
#if LDC_LLVM_VER >= 1400
82-
void loadAllPlugins() {
83-
if (opts::isUsingLegacyPassManager())
84-
loadAllPluginsLegacyPM();
85-
else
86-
loadAllPluginsNewPM();
87-
}
88-
#else
89-
void loadAllPlugins() { loadAllPluginsLegacyPM(); }
90-
void registerAllPluginsWithPassBuilder(llvm::PassBuilder &) {}
138+
for (auto &plugin : llvm_plugins) {
139+
plugin.registerPassBuilderCallbacks(PB);
140+
}
91141
#endif
142+
}
143+
144+
void runAllSemanticAnalysisPlugins(Module *m) {
145+
for (auto &plugin : sema_plugins) {
146+
assert(plugin.runSemanticAnalysis);
147+
plugin.runSemanticAnalysis(m);
148+
}
149+
}
92150

93151
#else // #if LDC_ENABLE_PLUGINS
94152

153+
class Module;
154+
95155
void loadAllPlugins() {}
96156
void registerAllPluginsWithPassBuilder(llvm::PassBuilder &) {}
157+
void runAllSemanticAnalysisPlugins(Module *m) {}
97158

98159
#endif // LDC_ENABLE_PLUGINS

gen/semantic.d

Lines changed: 9 additions & 2 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 3 additions & 0 deletions
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( ('%llc', os.path.join(config.llvm_tools_dir, 'llc')) )

tests/plugins/basic_sema_plugin.d

Lines changed: 26 additions & 0 deletions
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)