Skip to content

Commit fe6d993

Browse files
[mlir] Ability to build CAPI dylibs from out of tree projects against installed LLVM.
* Incorporates a reworked version of D106419 (which I have closed but has comments on it). * Extends the standalone example to include a minimal CAPI (for registering its dialect) and a test which, from out of tree, creates an aggregate dylib and links a little sample program against it. This will likely only work today in *static* MLIR builds (until the TypeID fiasco is finally put to bed). It should work on all platforms, though (including Windows - albeit I haven't tried this exact incarnation there). * This is the biggest pre-requisite to being able to build out of tree MLIR Python-based projects from an installed MLIR/LLVM. * I am rather nauseated by the CMake shenanigans I had to endure to get this working. The primary complexity, above and beyond the previous patch is because (with no reason given), it is impossible to export target properties that contain generator expressions... because, of course it isn't. In this case, the primary reason we use generator expressions on the individual embedded libraries is to support arbitrary ordering. Since that need doesn't apply to out of tree (which import everything via FindPackage at the outset), we fall back to a more imperative way of doing the same thing if we detect that the target was imported. Gross, but I don't expect it to need a lot of maintenance. * There should be a relatively straight-forward path from here to rebase libMLIR.so on top of this facility and also make it include the CAPI. Differential Revision: https://reviews.llvm.org/D111504
1 parent abdb82b commit fe6d993

File tree

13 files changed

+339
-30
lines changed

13 files changed

+339
-30
lines changed

mlir/cmake/modules/AddMLIR.cmake

+224-8
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,15 @@ endfunction()
5252
# Don't include this library in libMLIR.so. This option should be used
5353
# for test libraries, executable-specific libraries, or rarely used libraries
5454
# with large dependencies.
55+
# ENABLE_AGGREGATION
56+
# Forces generation of an OBJECT library, exports additional metadata,
57+
# and installs additional object files needed to include this as part of an
58+
# aggregate shared library.
59+
# TODO: Make this the default for all MLIR libraries once all libraries
60+
# are compatible with building an object library.
5561
function(add_mlir_library name)
5662
cmake_parse_arguments(ARG
57-
"SHARED;INSTALL_WITH_TOOLCHAIN;EXCLUDE_FROM_LIBMLIR;DISABLE_INSTALL"
63+
"SHARED;INSTALL_WITH_TOOLCHAIN;EXCLUDE_FROM_LIBMLIR;DISABLE_INSTALL;ENABLE_AGGREGATION"
5864
""
5965
"ADDITIONAL_HEADERS;DEPENDS;LINK_COMPONENTS;LINK_LIBS"
6066
${ARGN})
@@ -90,6 +96,14 @@ function(add_mlir_library name)
9096
${ARG_ADDITIONAL_HEADERS} # It may contain unparsed unknown args.
9197
)
9298
endif()
99+
100+
# Is an object library needed.
101+
set(NEEDS_OBJECT_LIB OFF)
102+
if(ARG_ENABLE_AGGREGATION)
103+
set(NEEDS_OBJECT_LIB ON)
104+
endif()
105+
106+
# Determine type of library.
93107
if(ARG_SHARED)
94108
set(LIBTYPE SHARED)
95109
else()
@@ -100,18 +114,21 @@ function(add_mlir_library name)
100114
else()
101115
set(LIBTYPE STATIC)
102116
endif()
103-
if(NOT XCODE)
104-
# The Xcode generator doesn't handle object libraries correctly.
105-
list(APPEND LIBTYPE OBJECT)
106-
endif()
107117
# Test libraries and such shouldn't be include in libMLIR.so
108118
if(NOT ARG_EXCLUDE_FROM_LIBMLIR)
119+
set(NEEDS_OBJECT_LIB ON)
109120
set_property(GLOBAL APPEND PROPERTY MLIR_STATIC_LIBS ${name})
110121
set_property(GLOBAL APPEND PROPERTY MLIR_LLVM_LINK_COMPONENTS ${ARG_LINK_COMPONENTS})
111122
set_property(GLOBAL APPEND PROPERTY MLIR_LLVM_LINK_COMPONENTS ${LLVM_LINK_COMPONENTS})
112123
endif()
113124
endif()
114125

126+
if(NEEDS_OBJECT_LIB AND NOT XCODE)
127+
# The Xcode generator doesn't handle object libraries correctly.
128+
# We special case xcode when building aggregates.
129+
list(APPEND LIBTYPE OBJECT)
130+
endif()
131+
115132
# MLIR libraries uniformly depend on LLVMSupport. Just specify it once here.
116133
list(APPEND ARG_LINK_COMPONENTS Support)
117134

@@ -139,8 +156,203 @@ function(add_mlir_library name)
139156
add_custom_target(${name})
140157
endif()
141158
set_target_properties(${name} PROPERTIES FOLDER "MLIR libraries")
159+
160+
# Setup aggregate.
161+
if(ARG_ENABLE_AGGREGATION)
162+
# Compute and store the properties needed to build aggregates.
163+
set(AGGREGATE_OBJECTS)
164+
set(AGGREGATE_OBJECT_LIB)
165+
set(AGGREGATE_DEPS)
166+
if(XCODE)
167+
# XCode has limited support for object libraries. Instead, add dep flags
168+
# that force the entire library to be embedded.
169+
list(APPEND AGGREGATE_DEPS "-force_load" "${name}")
170+
else()
171+
list(APPEND AGGREGATE_OBJECTS "$<TARGET_OBJECTS:obj.${name}>")
172+
list(APPEND AGGREGATE_OBJECT_LIB "obj.${name}")
173+
endif()
174+
175+
# For each declared dependency, transform it into a generator expression
176+
# which excludes it if the ultimate link target is excluding the library.
177+
set(NEW_LINK_LIBRARIES)
178+
get_target_property(CURRENT_LINK_LIBRARIES ${name} LINK_LIBRARIES)
179+
get_mlir_filtered_link_libraries(NEW_LINK_LIBRARIES ${CURRENT_LINK_LIBRARIES})
180+
set_target_properties(${name} PROPERTIES LINK_LIBRARIES "${NEW_LINK_LIBRARIES}")
181+
list(APPEND AGGREGATE_DEPS ${NEW_LINK_LIBRARIES})
182+
set_target_properties(${name} PROPERTIES
183+
EXPORT_PROPERTIES "MLIR_AGGREGATE_OBJECT_LIB_IMPORTED;MLIR_AGGREGATE_DEP_LIBS_IMPORTED"
184+
MLIR_AGGREGATE_OBJECTS "${AGGREGATE_OBJECTS}"
185+
MLIR_AGGREGATE_DEPS "${AGGREGATE_DEPS}"
186+
MLIR_AGGREGATE_OBJECT_LIB_IMPORTED "${AGGREGATE_OBJECT_LIB}"
187+
MLIR_AGGREGATE_DEP_LIBS_IMPORTED "${CURRENT_LINK_LIBRARIES}"
188+
)
189+
190+
# In order for out-of-tree projects to build aggregates of this library,
191+
# we need to install the OBJECT library.
192+
if(NOT ARG_DISABLE_INSTALL)
193+
add_mlir_library_install(obj.${name})
194+
endif()
195+
endif()
142196
endfunction(add_mlir_library)
143197

198+
# Sets a variable with a transformed list of link libraries such individual
199+
# libraries will be dynamically excluded when evaluated on a final library
200+
# which defines an MLIR_AGGREGATE_EXCLUDE_LIBS which contains any of the
201+
# libraries. Each link library can be a generator expression but must not
202+
# resolve to an arity > 1 (i.e. it can be optional).
203+
function(get_mlir_filtered_link_libraries output)
204+
set(_results)
205+
foreach(linklib ${ARGN})
206+
# In English, what this expression does:
207+
# For each link library, resolve the property MLIR_AGGREGATE_EXCLUDE_LIBS
208+
# on the context target (i.e. the executable or shared library being linked)
209+
# and, if it is not in that list, emit the library name. Otherwise, empty.
210+
list(APPEND _results
211+
"$<$<NOT:$<IN_LIST:${linklib},$<GENEX_EVAL:$<TARGET_PROPERTY:MLIR_AGGREGATE_EXCLUDE_LIBS>>>>:${linklib}>"
212+
)
213+
endforeach()
214+
set(${output} "${_results}" PARENT_SCOPE)
215+
endfunction(get_mlir_filtered_link_libraries)
216+
217+
# Declares an aggregate library. Such a library is a combination of arbitrary
218+
# regular add_mlir_library() libraries with the special feature that they can
219+
# be configured to statically embed some subset of their dependencies, as is
220+
# typical when creating a .so/.dylib/.dll or a mondo static library.
221+
#
222+
# It is always safe to depend on the aggregate directly in order to compile/link
223+
# against the superset of embedded entities and transitive deps.
224+
#
225+
# Arguments:
226+
# PUBLIC_LIBS: list of dependent libraries to add to the
227+
# INTERFACE_LINK_LIBRARIES property, exporting them to users. This list
228+
# will be transitively filtered to exclude any EMBED_LIBS.
229+
# EMBED_LIBS: list of dependent libraries that should be embedded directly
230+
# into this library. Each of these must be an add_mlir_library() library
231+
# without DISABLE_AGGREGATE.
232+
#
233+
# Note: This is a work in progress and is presently only sufficient for certain
234+
# non nested cases involving the C-API.
235+
function(add_mlir_aggregate name)
236+
cmake_parse_arguments(ARG
237+
"SHARED;STATIC"
238+
""
239+
"PUBLIC_LIBS;EMBED_LIBS"
240+
${ARGN})
241+
set(_libtype)
242+
if(ARG_STATIC)
243+
list(APPEND _libtype STATIC)
244+
endif()
245+
if(ARG_SHARED)
246+
list(APPEND _libtype SHARED)
247+
endif()
248+
set(_debugmsg)
249+
250+
set(_embed_libs)
251+
set(_objects)
252+
set(_deps)
253+
foreach(lib ${ARG_EMBED_LIBS})
254+
# We have to handle imported vs in-tree differently:
255+
# in-tree: To support arbitrary ordering, the generator expressions get
256+
# set on the dependent target when it is constructed and then just
257+
# eval'd here. This means we can build an aggregate from targets that
258+
# may not yet be defined, which is typical for in-tree.
259+
# imported: Exported properties do not support generator expressions, so
260+
# we imperatively query and manage the expansion here. This is fine
261+
# because imported targets will always be found/configured first and
262+
# do not need to support arbitrary ordering. If CMake every supports
263+
# exporting generator expressions, then this can be simplified.
264+
set(_is_imported OFF)
265+
if(TARGET ${lib})
266+
get_target_property(_is_imported ${lib} IMPORTED)
267+
endif()
268+
269+
if(NOT _is_imported)
270+
# Evaluate the in-tree generator expressions directly (this allows target
271+
# order independence, since these aren't evaluated until the generate
272+
# phase).
273+
# What these expressions do:
274+
# In the context of this aggregate, resolve the list of OBJECTS and DEPS
275+
# that each library advertises and patch it into the whole.
276+
set(_local_objects $<TARGET_GENEX_EVAL:${name},$<TARGET_PROPERTY:${lib},MLIR_AGGREGATE_OBJECTS>>)
277+
set(_local_deps $<TARGET_GENEX_EVAL:${name},$<TARGET_PROPERTY:${lib},MLIR_AGGREGATE_DEPS>>)
278+
else()
279+
# It is an imported target, which can only have flat strings populated
280+
# (no generator expressions).
281+
# Rebuild the generator expressions from the imported flat string lists.
282+
get_property(_has_object_lib_prop TARGET ${lib} PROPERTY MLIR_AGGREGATE_OBJECT_LIB_IMPORTED SET)
283+
get_property(_has_dep_libs_prop TARGET ${lib} PROPERTY MLIR_AGGREGATE_DEP_LIBS_IMPORTED SET)
284+
if(NOT _has_object_lib_prop OR NOT _has_dep_libs_prop)
285+
message(SEND_ERROR "Cannot create an aggregate out of imported ${lib}: It is missing properties indicating that it was built for aggregation")
286+
endif()
287+
get_target_property(_imp_local_object_lib ${lib} MLIR_AGGREGATE_OBJECT_LIB_IMPORTED)
288+
get_target_property(_imp_dep_libs ${lib} MLIR_AGGREGATE_DEP_LIBS_IMPORTED)
289+
set(_local_objects)
290+
if(_imp_local_object_lib)
291+
set(_local_objects "$<TARGET_OBJECTS:${_imp_local_object_lib}>")
292+
endif()
293+
# We should just be able to do this:
294+
# get_mlir_filtered_link_libraries(_local_deps ${_imp_dep_libs})
295+
# However, CMake complains about the unqualified use of the one-arg
296+
# $<TARGET_PROPERTY> expression. So we do the same thing but use the
297+
# two-arg form which takes an explicit target.
298+
foreach(_imp_dep_lib ${_imp_dep_libs})
299+
# In English, what this expression does:
300+
# For each link library, resolve the property MLIR_AGGREGATE_EXCLUDE_LIBS
301+
# on the context target (i.e. the executable or shared library being linked)
302+
# and, if it is not in that list, emit the library name. Otherwise, empty.
303+
list(APPEND _local_deps
304+
"$<$<NOT:$<IN_LIST:${_imp_dep_lib},$<GENEX_EVAL:$<TARGET_PROPERTY:${name},MLIR_AGGREGATE_EXCLUDE_LIBS>>>>:${_imp_dep_lib}>"
305+
)
306+
endforeach()
307+
endif()
308+
309+
list(APPEND _embed_libs ${lib})
310+
list(APPEND _objects ${_local_objects})
311+
list(APPEND _deps ${_local_deps})
312+
313+
string(APPEND _debugmsg
314+
": EMBED_LIB ${lib}:\n"
315+
" OBJECTS = ${_local_objects}\n"
316+
" DEPS = ${_local_deps}\n\n")
317+
endforeach()
318+
319+
# Unfortunately need to compile at least one source file, which is hard
320+
# to guarantee, so just always generate one. We generate one vs using the
321+
# LLVM common dummy.cpp because it works better out of tree.
322+
set(_empty_src "${CMAKE_CURRENT_BINARY_DIR}/${name}__empty.cpp")
323+
file(WRITE "${_empty_src}" "typedef int dummy;")
324+
325+
add_mlir_library(${name}
326+
${_libtype}
327+
${ARG_UNPARSED_ARGUMENTS}
328+
PARTIAL_SOURCES_INTENDED
329+
EXCLUDE_FROM_LIBMLIR
330+
"${_empty_src}"
331+
LINK_LIBS PRIVATE
332+
${_deps}
333+
${ARG_PUBLIC_LIBS}
334+
)
335+
target_sources(${name} PRIVATE ${_objects})
336+
# TODO: Should be transitive.
337+
set_target_properties(${name} PROPERTIES
338+
MLIR_AGGREGATE_EXCLUDE_LIBS "${_embed_libs}")
339+
if(MSVC)
340+
set_property(TARGET ${name} PROPERTY WINDOWS_EXPORT_ALL_SYMBOLS ON)
341+
endif()
342+
string(APPEND _debugmsg
343+
": MAIN LIBRARY:\n"
344+
" OBJECTS = ${_objects}\n"
345+
" SOURCES = $<TARGET_GENEX_EVAL:${name},$<TARGET_PROPERTY:${name},SOURCES>>\n"
346+
" DEPS = ${_deps}\n"
347+
" LINK_LIBRARIES = $<TARGET_GENEX_EVAL:${name},$<TARGET_PROPERTY:${name},LINK_LIBRARIES>>\n"
348+
" MLIR_AGGREGATE_EXCLUDE_LIBS = $<TARGET_GENEX_EVAL:${name},$<TARGET_PROPERTY:${name},MLIR_AGGREGATE_EXCLUDE_LIBS>>\n"
349+
)
350+
file(GENERATE OUTPUT
351+
"${CMAKE_CURRENT_BINARY_DIR}/${name}.aggregate_debug.txt"
352+
CONTENT "${_debugmsg}"
353+
)
354+
endfunction(add_mlir_aggregate)
355+
144356
# Adds an MLIR library target for installation.
145357
# This is usually done as part of add_mlir_library but is broken out for cases
146358
# where non-standard library builds can be installed.
@@ -152,7 +364,12 @@ function(add_mlir_library_install name)
152364
${export_to_mlirtargets}
153365
LIBRARY DESTINATION lib${LLVM_LIBDIR_SUFFIX}
154366
ARCHIVE DESTINATION lib${LLVM_LIBDIR_SUFFIX}
155-
RUNTIME DESTINATION bin)
367+
RUNTIME DESTINATION bin
368+
# Note that CMake will create a directory like:
369+
# objects-${CMAKE_BUILD_TYPE}/obj.LibName
370+
# and put object files there.
371+
OBJECTS DESTINATION lib${LLVM_LIBDIR_SUFFIX}
372+
)
156373

157374
if (NOT LLVM_ENABLE_IDE)
158375
add_llvm_install_targets(install-${name}
@@ -168,9 +385,8 @@ endfunction()
168385
function(add_mlir_public_c_api_library name)
169386
add_mlir_library(${name}
170387
${ARGN}
171-
# NOTE: Generates obj.${name} which is used for shared library building.
172-
OBJECT
173388
EXCLUDE_FROM_LIBMLIR
389+
ENABLE_AGGREGATION
174390
ADDITIONAL_HEADER_DIRS
175391
${MLIR_MAIN_INCLUDE_DIR}/mlir-c
176392
)

mlir/cmake/modules/AddMLIRPython.cmake

+4-21
Original file line numberDiff line numberDiff line change
@@ -330,9 +330,6 @@ function(add_mlir_python_common_capi_library name)
330330
"INSTALL_COMPONENT;INSTALL_DESTINATION;OUTPUT_DIRECTORY;RELATIVE_INSTALL_ROOT"
331331
"DECLARED_SOURCES;EMBED_LIBS"
332332
${ARGN})
333-
# TODO: Upgrade to the aggregate utility in https://reviews.llvm.org/D106419
334-
# once ready.
335-
336333
# Collect all explicit and transitive embed libs.
337334
set(_embed_libs ${ARG_EMBED_LIBS})
338335
_flatten_mlir_python_targets(_all_source_targets ${ARG_DECLARED_SOURCES})
@@ -344,27 +341,13 @@ function(add_mlir_python_common_capi_library name)
344341
endforeach()
345342
list(REMOVE_DUPLICATES _embed_libs)
346343

347-
foreach(lib ${_embed_libs})
348-
if(XCODE)
349-
# Xcode doesn't support object libraries, so we have to trick it into
350-
# linking the static libraries instead.
351-
list(APPEND _deps "-force_load" ${lib})
352-
else()
353-
list(APPEND _objects $<TARGET_OBJECTS:obj.${lib}>)
354-
endif()
355-
# Accumulate transitive deps of each exported lib into _DEPS.
356-
list(APPEND _deps $<TARGET_PROPERTY:${lib},LINK_LIBRARIES>)
357-
endforeach()
358-
359-
add_mlir_library(${name}
360-
PARTIAL_SOURCES_INTENDED
344+
# Generate the aggregate .so that everything depends on.
345+
add_mlir_aggregate(${name}
361346
SHARED
362347
DISABLE_INSTALL
363-
${_objects}
364-
EXCLUDE_FROM_LIBMLIR
365-
LINK_LIBS
366-
${_deps}
348+
EMBED_LIBS ${_embed_libs}
367349
)
350+
368351
if(MSVC)
369352
set_property(TARGET ${name} PROPERTY WINDOWS_EXPORT_ALL_SYMBOLS ON)
370353
endif()

mlir/examples/standalone/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//===- Dialects.h - CAPI for dialects -----------------------------*- C -*-===//
2+
//
3+
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef STANDALONE_C_DIALECTS_H
10+
#define STANDALONE_C_DIALECTS_H
11+
12+
#include "mlir-c/Registration.h"
13+
14+
#ifdef __cplusplus
15+
extern "C" {
16+
#endif
17+
18+
MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(Standalone, standalone);
19+
20+
#ifdef __cplusplus
21+
}
22+
#endif
23+
24+
#endif // STANDALONE_C_DIALECTS_H
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
add_mlir_public_c_api_library(StandaloneCAPI
2+
Dialects.cpp
3+
LINK_LIBS PUBLIC
4+
MLIRStandalone
5+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//===- Dialects.cpp - CAPI for dialects -----------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "Standalone-c/Dialects.h"
10+
11+
#include "Standalone/StandaloneDialect.h"
12+
#include "mlir/CAPI/Registration.h"
13+
14+
MLIR_DEFINE_CAPI_DIALECT_REGISTRATION(Standalone, standalone,
15+
mlir::standalone::StandaloneDialect)
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
add_subdirectory(CAPI)
12
add_subdirectory(Standalone)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Here we create a single aggregate shared library with the parts of the CAPI
2+
# that we want to bundle together. Then we link a simple C executable
3+
# against it to demonstrate that it does have the fully self contained
4+
# core MLIR library and our own standalone dialect.
5+
add_mlir_aggregate(StandaloneCAPITestLib
6+
SHARED
7+
EMBED_LIBS
8+
MLIRCAPIIR
9+
MLIRCAPIRegistration
10+
StandaloneCAPI
11+
)
12+
13+
add_llvm_executable(standalone-capi-test
14+
standalone-capi-test.c
15+
)
16+
llvm_update_compile_flags(standalone-capi-test)
17+
target_link_libraries(standalone-capi-test
18+
PRIVATE StandaloneCAPITestLib)

0 commit comments

Comments
 (0)