MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
Mim.cmake
Go to the documentation of this file.
1include(CMakeDependentOption)
2include(GNUInstallDirs)
3
4option(MIM_BUILD_LL_RUNTIME "Compile the ll backend's C runtime wrappers to LLVM IR (requires clang)." ON)
5cmake_dependent_option(
6 MIM_STATIC_PLUGINS
7 "Link the plugins into the binary instead of loading them from shared objects."
8 OFF "NOT EMSCRIPTEN" ON # a wasm build has no dlopen
9)
10# A cross build cannot run the mim it just built.
11set(MIM_NATIVE_MIM "" CACHE FILEPATH "Native mim executable that bootstraps the plugins.")
12# Where a plugin's `.mim` half is staged, so an uninstalled tree can load it.
13set(MIM_PLUGIN_DIR "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}/mim")
14find_program(MIM_CLANG NAMES clang)
15if(MIM_BUILD_LL_RUNTIME AND NOT MIM_CLANG)
16 message(STATUS
17 "MimIR: clang not found; ll backend C runtime wrappers will not be built. "
18 "Set MIM_CLANG or disable MIM_BUILD_LL_RUNTIME to silence this."
19 )
20endif()
21
22# Collects the .mim files that bootstrapping mim_file reads, transitively.
23# A plugin/import directive resolves to <root>/<name>/<name>.mim, mirroring add_mim_plugin's -P arguments.
24# A newly added directive is only picked up on the next configure; editing an existing one is tracked.
25function(mim_transitive_imports mim_file roots out)
26 set(pending "${mim_file}")
27 set(files "")
28
29 while(pending)
30 list(POP_FRONT pending current)
31 file(READ "${current}" contents)
32 string(REGEX REPLACE "/\\*[^*]*\\*+([^/*][^*]*\\*+)*/" "" contents "${contents}")
33 string(REGEX REPLACE "//[^\n]*" "" contents "${contents}")
34 string(REGEX MATCHALL "(plugin|import)[ \t\r\n]+[A-Za-z0-9_]+" directives "${contents}")
35
36 foreach(directive IN LISTS directives)
37 string(REGEX REPLACE "^(plugin|import)[ \t\r\n]+" "" name "${directive}")
38 foreach(root IN LISTS roots)
39 set(candidate "${root}/${name}/${name}.mim")
40 if(EXISTS "${candidate}")
41 if(NOT candidate IN_LIST files)
42 list(APPEND files "${candidate}")
43 list(APPEND pending "${candidate}")
44 endif()
45 break()
46 endif()
47 endforeach()
48 endforeach()
49 endwhile()
50
51 set(${out} "${files}" PARENT_SCOPE)
52endfunction()
53
54## \page add_mim_plugin_cmake add_mim_plugin
55## \brief Registers a new MimIR plugin.
56##
57## Relative to the plugin's `CMakeLists.txt`, there should be a file
58## `<plugin-name>.mim` containing the plugin's annexes.
59##
60## \code{.cmake}
61## add_mim_plugin(<plugin-name>
62## [SOURCES <source>...]
63## [PRIVATE <private-item>...])
64## \endcode
65##
66## `<plugin-name>` may only contain letters, digits, and underscores, and must
67## satisfy MimIR's plugin naming constraints.
68##
69## The command creates two targets:
70## - `mim_internal_<plugin-name>` bootstraps the plugin, generates
71## `<plugin-name>/autogen.h` for the C++ interface used to identify annexes,
72## `<plugin-name>.md` for the documentation, and copies `<plugin-name>.mim`
73## into the build tree.
74## - `mim_<plugin-name>` builds the actual `MODULE` library (`STATIC` with
75## `MIM_STATIC_PLUGINS`) that contains normalizers, phases/stages, and
76## backends. One of the listed source files must export `mim_get_plugin`.
77##
78## `SOURCES` lists the source files that are compiled into the loadable plugin.
79## `PRIVATE` lists additional private link dependencies.
80##
81## The plugin module, its `.mim` file, and the generated `autogen.h` are installed.
82## The export name `mim-targets` must be exported accordingly; see CMake's
83## `install(EXPORT ...)` documentation.
84##
85## Additional target properties can be set afterwards, for example:
86## \code{.cmake}
87## target_include_directories(mim_<plugin-name> <path>...)
88## \endcode
89function(add_mim_plugin)
90 set(PLUGIN ${ARGV0})
91
92 string(LENGTH "${PLUGIN}" PLUGIN_LENGTH)
93 if(NOT PLUGIN MATCHES "^[A-Za-z0-9_]+$" OR PLUGIN_LENGTH GREATER 8)
94 message(FATAL_ERROR
95 "Mim plugin name '${PLUGIN}' must be 1 to 8 letters, digits, or underscores"
96 )
97 endif()
98
99 cmake_parse_arguments(
100 PARSE_ARGV 1 # skip first arg
101 PARSED # prefix of output variables
102 "" # options (none)
103 "" # one-value keywords (none)
104 "SOURCES;PRIVATE" # multi-value keywords
105 )
106
107 set(PLUGIN_MIM ${CMAKE_CURRENT_LIST_DIR}/${PLUGIN}.mim)
108 set(OUT_PLUGIN_MIM ${MIM_PLUGIN_DIR}/${PLUGIN}.mim)
109 set(PLUGIN_MD ${CMAKE_BINARY_DIR}/docs/plug/${PLUGIN}.md)
110 set(AUTOGEN_H ${CMAKE_BINARY_DIR}/include/mim/plug/${PLUGIN}/autogen.h)
111 set(AUTOGEN_PY ${CMAKE_BINARY_DIR}/py/mim/_plugins/${PLUGIN}.py)
112
113 file(
114 MAKE_DIRECTORY
115 ${CMAKE_BINARY_DIR}/docs/plug/
116 ${CMAKE_BINARY_DIR}/include/mim/plug/${PLUGIN}
117 ${CMAKE_BINARY_DIR}/py/mim/_plugins/
118 ${MIM_PLUGIN_DIR}/
119 )
120
121 set(PLUGIN_ROOTS "${CMAKE_SOURCE_DIR}/src/mim/plug" "${CMAKE_CURRENT_LIST_DIR}/..")
122 mim_transitive_imports("${PLUGIN_MIM}" "${PLUGIN_ROOTS}" PLUGIN_IMPORTS)
123
124 if(MIM_NATIVE_MIM)
125 set(BOOTSTRAP_MIM "${MIM_NATIVE_MIM}")
126 set(BOOTSTRAP_DEPENDS "")
127 elseif(MIM_STATIC_PLUGINS)
128 set(BOOTSTRAP_MIM "$<TARGET_FILE:mim_bootstrap>")
129 set(BOOTSTRAP_DEPENDS "mim_bootstrap")
130 else()
131 set(BOOTSTRAP_MIM "$<TARGET_FILE:${MIM_TARGET_NAMESPACE}mim>")
132 set(BOOTSTRAP_DEPENDS "${MIM_TARGET_NAMESPACE}mim")
133 endif()
134
135 add_custom_command(
136 OUTPUT
137 ${AUTOGEN_H}
138 ${AUTOGEN_PY}
139 ${PLUGIN_MD}
140 COMMAND ${BOOTSTRAP_MIM} ${PLUGIN_MIM} -I "${CMAKE_SOURCE_DIR}/src/mim/plug" -I "${CMAKE_CURRENT_LIST_DIR}/.." --bootstrap
141 --output-h ${AUTOGEN_H}
142 --output-md ${PLUGIN_MD}
143 --output-py ${AUTOGEN_PY}
144 MAIN_DEPENDENCY ${PLUGIN_MIM}
145 DEPENDS ${BOOTSTRAP_DEPENDS} ${PLUGIN_IMPORTS}
146 COMMENT "Bootstrapping MimIR plugin '${PLUGIN_MIM}'"
147 VERBATIM
148 )
149 add_custom_command(
150 OUTPUT ${OUT_PLUGIN_MIM}
151 COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PLUGIN_MIM} ${OUT_PLUGIN_MIM}
152 DEPENDS ${PLUGIN_MIM}
153 COMMENT "Copy '${PLUGIN_MIM}' to '${OUT_PLUGIN_MIM}'"
154 VERBATIM
155 )
156
157 add_custom_target(mim_internal_${PLUGIN}
158 DEPENDS
159 ${AUTOGEN_H}
160 ${AUTOGEN_PY}
161 ${PLUGIN_MD}
162 ${OUT_PLUGIN_MIM}
163 )
164
165 if(PLUGIN IN_LIST MIM_PLUGIN_LIST)
166 message(FATAL_ERROR "Mim plugin '${PLUGIN}' is already registered")
167 endif()
168
169 list(APPEND MIM_PLUGIN_LIST "${PLUGIN}")
170 string(APPEND MIM_PLUGIN_LAYOUT "<tab type=\"user\" url=\"@ref ${PLUGIN}\" title=\"${PLUGIN}\"/>")
171
172 set(MIM_PLUGIN_LIST "${MIM_PLUGIN_LIST}" CACHE INTERNAL "MIM_PLUGIN_LIST")
173 set(MIM_PLUGIN_LAYOUT "${MIM_PLUGIN_LAYOUT}" CACHE INTERNAL "MIM_PLUGIN_LAYOUT")
174
175 if(MIM_STATIC_PLUGINS)
176 add_library(mim_${PLUGIN} STATIC)
177 # One binary holds them all, so MIM_PLUGIN_ENTRY names each entry point after its plugin.
178 target_compile_definitions(mim_${PLUGIN} PRIVATE MIM_STATIC_PLUGINS)
179 else()
180 add_library(mim_${PLUGIN} MODULE)
181 endif()
182 add_dependencies(mim_${PLUGIN} mim_internal_${PLUGIN})
183 target_sources(mim_${PLUGIN}
184 PRIVATE
185 ${PARSED_SOURCES}
186 )
187 target_include_directories(mim_${PLUGIN}
188 PUBLIC
189 $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include> # for autogen.h
190 )
191 if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/include")
192 target_include_directories(mim_${PLUGIN}
193 PRIVATE
194 "${CMAKE_CURRENT_LIST_DIR}/include"
195 )
196 endif()
197 target_link_libraries(mim_${PLUGIN}
198 PRIVATE
199 ${PARSED_PRIVATE}
200 ${MIM_TARGET_NAMESPACE}libmim
201 )
202 set_target_properties(mim_${PLUGIN}
203 PROPERTIES
204 CXX_VISIBILITY_PRESET hidden
205 VISIBILITY_INLINES_HIDDEN 1
206 WINDOWS_EXPORT_ALL_SYMBOLS OFF
207 PREFIX "lib" # always use "lib" as prefix regardless of OS/compiler
208 # The genex suppresses the per-config subdir a multi-config generator would append.
209 LIBRARY_OUTPUT_DIRECTORY "${MIM_PLUGIN_DIR}$<0:>"
210 )
211
212 install(
213 TARGETS
214 mim_${PLUGIN}
215 EXPORT mim-targets
216 LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/mim
217 ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/mim
218 RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}/mim
219 INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mim
220 )
221 install(
222 FILES ${OUT_PLUGIN_MIM}
223 DESTINATION ${CMAKE_INSTALL_LIBDIR}/mim
224 )
225 install(
226 FILES ${AUTOGEN_H}
227 DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mim/plug/${PLUGIN}
228 )
229 if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/include")
230 install(
231 DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/include/"
232 DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
233 )
234 endif()
235endfunction()
236
237## \page mim_static_plugin_registry_cmake mim_static_plugin_registry
238## \brief Creates the object library that registers the statically linked plugins.
239##
240## \code{.cmake}
241## mim_static_plugin_registry(<target> <plugin>...)
242## \endcode
243##
244## `MIM_STATIC_PLUGINS` builds have no shared object to `dlopen`, so `<target>` holds a generated static
245## initializer that hands every `<plugin>` to `mim::Driver::add_static_plugin`.
246## It must be an object library: nothing references the registrations, so an archive member would be dropped.
247function(mim_static_plugin_registry TARGET)
248 set(DECLS "")
249 set(CALLS "")
250 foreach(PLUGIN IN LISTS ARGN)
251 string(APPEND DECLS "Plugin MIM_PLUGIN_ENTRY_NAME(${PLUGIN})();\n")
252 string(APPEND CALLS " Driver::add_static_plugin(\"${PLUGIN}\", MIM_PLUGIN_ENTRY_NAME(${PLUGIN}));\n")
253 endforeach()
254
255 set(GENERATED ${CMAKE_BINARY_DIR}/src/mim/${TARGET}.cpp)
256 file(CONFIGURE
257 OUTPUT ${GENERATED}
258 CONTENT "// Generated by mim_static_plugin_registry; see cmake/Mim.cmake.
259#include \"mim/driver.h\"
260
261using namespace mim;
262
263extern \"C\" {
264${DECLS}}
265
266namespace {
267struct Register {
268 Register() {
269${CALLS} }
270} register_;
271} // namespace
272"
273 )
274
275 set(PLUGIN_TARGETS ${ARGN})
276 list(TRANSFORM PLUGIN_TARGETS PREPEND "mim_")
277
278 add_library(${TARGET} OBJECT ${GENERATED})
279 target_compile_definitions(${TARGET} PRIVATE MIM_STATIC_PLUGINS)
280 target_link_libraries(${TARGET} PUBLIC ${MIM_TARGET_NAMESPACE}libmim ${PLUGIN_TARGETS})
281endfunction()
282
283## \page add_mim_runtime_cmake add_mim_runtime
284## \brief Compiles a plugin's C runtime wrappers into a single LLVM IR module.
285##
286## \code{.cmake}
287## add_mim_runtime(<plugin-name>
288## SOURCES <source>...)
289## \endcode
290##
291## All `SOURCES` are compiled with `clang` and merged into a single textual module
292## `<libdir>/mim/rt/<plugin-name>_rt.ll`, next to the plugins. A backend locates
293## that one file via the driver's search paths (see
294## \ref mim::plug::ll::Emitter::load_rt_module) and either embeds it into or links
295## it with its emitted module (see `-X <plugin-name>:rt=embed|extern`). Producing a
296## single module keeps the runtime addressable by one well-known name regardless of
297## how many source files it is split into.
298##
299## A single source is compiled directly to the module; multiple sources are compiled
300## to bitcode and merged with `llvm-link` (which must then be on `PATH`).
301##
302## If `clang` is unavailable (see `MIM_CLANG`) or `MIM_BUILD_LL_RUNTIME` is off,
303## the command is a no-op; the runtime is simply not built.
304function(add_mim_runtime)
305 set(PLUGIN ${ARGV0})
306
307 cmake_parse_arguments(
308 PARSE_ARGV 1 # skip first arg
309 PARSED # prefix of output variables
310 "" # options (none)
311 "" # one-value keywords (none)
312 "SOURCES" # multi-value keywords
313 )
314
315 if(NOT MIM_BUILD_LL_RUNTIME OR NOT MIM_CLANG)
316 return()
317 endif()
318
319 set(RT_DIR ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}/mim/rt)
320 file(MAKE_DIRECTORY ${RT_DIR})
321 set(RT_LL ${RT_DIR}/${PLUGIN}_rt.ll)
322
323 set(RT_SOURCES ${PARSED_SOURCES})
324 list(TRANSFORM RT_SOURCES PREPEND ${CMAKE_CURRENT_LIST_DIR}/)
325
326 list(LENGTH RT_SOURCES N_SOURCES)
327 if(N_SOURCES EQUAL 1)
328 add_custom_command(
329 OUTPUT ${RT_LL}
330 COMMAND ${MIM_CLANG} -S -emit-llvm -O2 ${PARSED_SOURCES} -o ${RT_LL}
331 DEPENDS ${RT_SOURCES}
332 WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
333 COMMENT "Compiling MimIR runtime '${PLUGIN}' to LLVM IR"
334 VERBATIM
335 )
336 else()
337 find_program(MIM_LLVM_LINK NAMES llvm-link)
338 if(NOT MIM_LLVM_LINK)
339 message(FATAL_ERROR
340 "add_mim_runtime(${PLUGIN}) with multiple SOURCES needs llvm-link to merge them"
341 )
342 endif()
343 set(RT_BCS "")
344 foreach(src IN LISTS PARSED_SOURCES)
345 cmake_path(GET src STEM stem)
346 set(bc ${RT_DIR}/${PLUGIN}_${stem}.bc)
347 add_custom_command(
348 OUTPUT ${bc}
349 COMMAND ${MIM_CLANG} -emit-llvm -O2 -c ${src} -o ${bc}
350 DEPENDS ${CMAKE_CURRENT_LIST_DIR}/${src}
351 WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
352 COMMENT "Compiling MimIR runtime source '${stem}' to LLVM bitcode"
353 VERBATIM
354 )
355 list(APPEND RT_BCS ${bc})
356 endforeach()
357 add_custom_command(
358 OUTPUT ${RT_LL}
359 COMMAND ${MIM_LLVM_LINK} -S ${RT_BCS} -o ${RT_LL}
360 DEPENDS ${RT_BCS}
361 COMMENT "Linking MimIR runtime '${PLUGIN}' into a single LLVM IR module"
362 VERBATIM
363 )
364 endif()
365
366 add_custom_target(mim_runtime_${PLUGIN} ALL DEPENDS ${RT_LL})
367 if(TARGET mim_${PLUGIN})
368 add_dependencies(mim_${PLUGIN} mim_runtime_${PLUGIN})
369 endif()
370 install(
371 FILES ${RT_LL}
372 DESTINATION ${CMAKE_INSTALL_LIBDIR}/mim/rt
373 )
374endfunction()