diff --git a/mlir/cmake/modules/AddMLIR.cmake b/mlir/cmake/modules/AddMLIR.cmake --- a/mlir/cmake/modules/AddMLIR.cmake +++ b/mlir/cmake/modules/AddMLIR.cmake @@ -54,7 +54,7 @@ # with large dependencies. function(add_mlir_library name) cmake_parse_arguments(ARG - "SHARED;INSTALL_WITH_TOOLCHAIN;EXCLUDE_FROM_LIBMLIR;DISABLE_INSTALL" + "SHARED;INSTALL_WITH_TOOLCHAIN;EXCLUDE_FROM_LIBMLIR;DISABLE_INSTALL;ENABLE_AGGREGATION" "" "ADDITIONAL_HEADERS;DEPENDS;LINK_COMPONENTS;LINK_LIBS" ${ARGN}) @@ -90,6 +90,14 @@ ${ARG_ADDITIONAL_HEADERS} # It may contain unparsed unknown args. ) endif() + + # Is an object library needed. + set(NEEDS_OBJECT_LIB OFF) + if(ARG_ENABLE_AGGREGATION) + set(NEEDS_OBJECT_LIB ON) + endif() + + # Determine type of library. if(ARG_SHARED) set(LIBTYPE SHARED) else() @@ -100,18 +108,21 @@ else() set(LIBTYPE STATIC) endif() - if(NOT XCODE) - # The Xcode generator doesn't handle object libraries correctly. - list(APPEND LIBTYPE OBJECT) - endif() # Test libraries and such shouldn't be include in libMLIR.so if(NOT ARG_EXCLUDE_FROM_LIBMLIR) + set(NEEDS_OBJECT_LIB ON) set_property(GLOBAL APPEND PROPERTY MLIR_STATIC_LIBS ${name}) set_property(GLOBAL APPEND PROPERTY MLIR_LLVM_LINK_COMPONENTS ${ARG_LINK_COMPONENTS}) set_property(GLOBAL APPEND PROPERTY MLIR_LLVM_LINK_COMPONENTS ${LLVM_LINK_COMPONENTS}) endif() endif() + if(NEEDS_OBJECT_LIB AND NOT XCODE) + # The Xcode generator doesn't handle object libraries correctly. + # We special case xcode when building aggregates. + list(APPEND LIBTYPE OBJECT) + endif() + # MLIR libraries uniformly depend on LLVMSupport. Just specify it once here. list(APPEND ARG_LINK_COMPONENTS Support) @@ -139,8 +150,199 @@ add_custom_target(${name}) endif() set_target_properties(${name} PROPERTIES FOLDER "MLIR libraries") + + # Setup aggregate. + if(ARG_ENABLE_AGGREGATION) + # Compute and store the properties needed to build aggregates. + set(AGGREGATE_OBJECTS) + set(AGGREGATE_OBJECT_LIB) + set(AGGREGATE_DEPS) + if(XCODE) + # XCode has limited support for object libraries. Instead, add dep flags + # that force the entire library to be embedded. + list(APPEND AGGREGATE_DEPS "-force_load" "${name}") + else() + list(APPEND AGGREGATE_OBJECTS "$") + list(APPEND AGGREGATE_OBJECT_LIB "obj.${name}") + endif() + + # For each declared dependency, transform it into a generator expression + # which excludes it if the ultimate link target is excluding the library. + set(NEW_LINK_LIBRARIES) + get_target_property(CURRENT_LINK_LIBRARIES ${name} LINK_LIBRARIES) + get_mlir_filtered_link_libraries(NEW_LINK_LIBRARIES ${CURRENT_LINK_LIBRARIES}) + set_target_properties(${name} PROPERTIES LINK_LIBRARIES "${NEW_LINK_LIBRARIES}") + list(APPEND AGGREGATE_DEPS ${NEW_LINK_LIBRARIES}) + set_target_properties(${name} PROPERTIES + EXPORT_PROPERTIES "MLIR_AGGREGATE_OBJECT_LIB_IMPORTED;MLIR_AGGREGATE_DEP_LIBS_IMPORTED" + MLIR_AGGREGATE_IN_TREE ON + MLIR_AGGREGATE_OBJECTS "${AGGREGATE_OBJECTS}" + MLIR_AGGREGATE_DEPS "${AGGREGATE_DEPS}" + MLIR_AGGREGATE_OBJECT_LIB_IMPORTED "${AGGREGATE_OBJECT_LIB}" + MLIR_AGGREGATE_DEP_LIBS_IMPORTED "${CURRENT_LINK_LIBRARIES}" + ) + + # In order for out-of-tree projects to build aggregates of this library, + # we need to install the OBJECT library. + if(NOT ARG_DISABLE_INSTALL) + add_mlir_library_install(obj.${name}) + endif() + endif() endfunction(add_mlir_library) +# Sets a variable with a transformed list of link libraries such individual +# libraries will be dynamically excluded when evaluated on a final library +# which defines an MLIR_AGGREGATE_EXCLUDE_LIBS which contains any of the +# libraries. Each link library can be a generator expression but must not +# resolve to an arity > 1 (i.e. it can be optional). +function(get_mlir_filtered_link_libraries output) + set(_results) + foreach(linklib ${ARGN}) + # In English, what this expression does: + # For each link library, resolve the property MLIR_AGGREGATE_EXCLUDE_LIBS + # on the context target (i.e. the executable or shared library being linked) + # and, if it is not in that list, emit the library name. Otherwise, empty. + list(APPEND _results + "$<$>>>:${linklib}>" + ) + endforeach() + set(${output} "${_results}" PARENT_SCOPE) +endfunction(get_mlir_filtered_link_libraries) + +# Declares an aggregate library. Such a library is a combination of arbitrary +# regular add_mlir_library() libraries with the special feature that they can +# be configured to statically embed some subset of their dependencies, as is +# typical when creating a .so/.dylib/.dll or a mondo static library. +# +# It is always safe to depend on the aggregate directly in order to compile/link +# against the superset of embedded entities and transitive deps. +# +# Arguments: +# PUBLIC_LIBS: list of dependent libraries to add to the +# INTERFACE_LINK_LIBRARIES property, exporting them to users. This list +# will be transitively filtered to exclude any EMBED_LIBS. +# EMBED_LIBS: list of dependent libraries that should be embedded directly +# into this library. Each of these must be an add_mlir_library() library +# without DISABLE_AGGREGATE. +# +# Note: This is a work in progress and is presently only sufficient for certain +# non nested cases involving the C-API. +function(add_mlir_aggregate name) + cmake_parse_arguments(ARG + "SHARED;STATIC" + "" + "PUBLIC_LIBS;EMBED_LIBS" + ${ARGN}) + set(_libtype) + if(ARG_STATIC) + list(APPEND _libtype STATIC) + endif() + if(ARG_SHARED) + list(APPEND _libtype SHARED) + endif() + set(_debugmsg) + + set(_embed_libs) + set(_objects) + set(_deps) + foreach(lib ${ARG_EMBED_LIBS}) + # Imported targets will not have the MLIR_AGGREGATE_IN_TREE property set. + # In which case, there will also not be the generator based + # MLIR_AGGREGATE_OBJECTS and MLIR_AGGREGATE_DEPS, which are needed in order + # to have order independence in the same project. Instead, we query the + # corresponding string-list _IMPORTED properties and work on those directly. + # This is fine for imported, because by definition, they will be present + # before use. + set(_in_tree_lib ON) + if(TARGET ${lib}) + get_target_property(_has_in_tree_indicator ${lib} MLIR_AGGREGATE_IN_TREE) + if(NOT _has_in_tree_indicator) + set(_in_tree_lib OFF) + endif() + endif() + + if(_in_tree_lib) + # Evaluate the in-tree generator expressions directly (this allows target + # order independence, since these aren't evaluated until the generate + # phase). + # What these expressions do: + # In the context of this aggregate, resolve the list of OBJECTS and DEPS + # that each library advertises and patch it into the whole. + set(_local_objects $>) + set(_local_deps $>) + else() + # It is an imported target, which can only have flat strings populated + # (no generator expressions). + # Rebuild the generator expressions from the imported flat string lists. + get_target_property(_imp_local_object_lib ${lib} MLIR_AGGREGATE_OBJECT_LIB_IMPORTED) + get_target_property(_imp_dep_libs ${lib} MLIR_AGGREGATE_DEP_LIBS_IMPORTED) + set(_local_objects) + if(_imp_local_object_lib) + set(_local_objects "$") + endif() + # We should just be able to do this: + # get_mlir_filtered_link_libraries(_local_deps ${_imp_dep_libs}) + # However, CMake complains about the unqualified use of the one-arg + # $ expression. So we do the same thing but use the + # two-arg form which takes an explicit target. + foreach(_imp_dep_lib ${_imp_dep_libs}) + # In English, what this expression does: + # For each link library, resolve the property MLIR_AGGREGATE_EXCLUDE_LIBS + # on the context target (i.e. the executable or shared library being linked) + # and, if it is not in that list, emit the library name. Otherwise, empty. + list(APPEND _local_deps + "$<$>>>:${_imp_dep_lib}>" + ) + endforeach() + endif() + + list(APPEND _embed_libs ${lib}) + list(APPEND _objects ${_local_objects}) + list(APPEND _deps ${_local_deps}) + + string(APPEND _debugmsg + ": EMBED_LIB ${lib}:\n" + " OBJECTS = ${_local_objects}\n" + " DEPS = ${_local_deps}\n\n") + endforeach() + + # Unfortunately need to compile at least one source file, which is hard + # to guarantee, so just always generate one. We generate one vs using the + # LLVM common dummy.cpp because it works better out of tree. + set(_empty_src "${CMAKE_CURRENT_BINARY_DIR}/${name}__empty.cpp") + file(WRITE "${_empty_src}" "typedef int dummy;") + + add_mlir_library(${name} + ${_libtype} + ${ARG_UNPARSED_ARGUMENTS} + PARTIAL_SOURCES_INTENDED + EXCLUDE_FROM_LIBMLIR + "${_empty_src}" + LINK_LIBS PRIVATE + ${_deps} + ${ARG_PUBLIC_LIBS} + ) + target_sources(${name} PRIVATE ${_objects}) + # TODO: Should be transitive. + set_target_properties(${name} PROPERTIES + MLIR_AGGREGATE_EXCLUDE_LIBS "${_embed_libs}") + if(MSVC) + set_property(TARGET ${name} PROPERTY WINDOWS_EXPORT_ALL_SYMBOLS ON) + endif() + string(APPEND _debugmsg + ": MAIN LIBRARY:\n" + " OBJECTS = ${_objects}\n" + " SOURCES = $>\n" + " DEPS = ${_deps}\n" + " LINK_LIBRARIES = $>\n" + " MLIR_AGGREGATE_EXCLUDE_LIBS = $>\n" + ) + file(GENERATE OUTPUT + "${CMAKE_CURRENT_BINARY_DIR}/${name}.aggregate_debug.txt" + CONTENT "${_debugmsg}" + ) +endfunction(add_mlir_aggregate) + # Adds an MLIR library target for installation. # This is usually done as part of add_mlir_library but is broken out for cases # where non-standard library builds can be installed. @@ -152,7 +354,12 @@ ${export_to_mlirtargets} LIBRARY DESTINATION lib${LLVM_LIBDIR_SUFFIX} ARCHIVE DESTINATION lib${LLVM_LIBDIR_SUFFIX} - RUNTIME DESTINATION bin) + RUNTIME DESTINATION bin + # Note that CMake will create a directory like: + # objects-${CMAKE_BUILD_TYPE}/obj.LibName + # and put object files there. + OBJECTS DESTINATION lib${LLVM_LIBDIR_SUFFIX} + ) if (NOT LLVM_ENABLE_IDE) add_llvm_install_targets(install-${name} @@ -168,9 +375,8 @@ function(add_mlir_public_c_api_library name) add_mlir_library(${name} ${ARGN} - # NOTE: Generates obj.${name} which is used for shared library building. - OBJECT EXCLUDE_FROM_LIBMLIR + ENABLE_AGGREGATION ADDITIONAL_HEADER_DIRS ${MLIR_MAIN_INCLUDE_DIR}/mlir-c ) diff --git a/mlir/cmake/modules/AddMLIRPython.cmake b/mlir/cmake/modules/AddMLIRPython.cmake --- a/mlir/cmake/modules/AddMLIRPython.cmake +++ b/mlir/cmake/modules/AddMLIRPython.cmake @@ -330,9 +330,6 @@ "INSTALL_COMPONENT;INSTALL_DESTINATION;OUTPUT_DIRECTORY;RELATIVE_INSTALL_ROOT" "DECLARED_SOURCES;EMBED_LIBS" ${ARGN}) - # TODO: Upgrade to the aggregate utility in https://reviews.llvm.org/D106419 - # once ready. - # Collect all explicit and transitive embed libs. set(_embed_libs ${ARG_EMBED_LIBS}) _flatten_mlir_python_targets(_all_source_targets ${ARG_DECLARED_SOURCES}) @@ -344,27 +341,13 @@ endforeach() list(REMOVE_DUPLICATES _embed_libs) - foreach(lib ${_embed_libs}) - if(XCODE) - # Xcode doesn't support object libraries, so we have to trick it into - # linking the static libraries instead. - list(APPEND _deps "-force_load" ${lib}) - else() - list(APPEND _objects $) - endif() - # Accumulate transitive deps of each exported lib into _DEPS. - list(APPEND _deps $) - endforeach() - - add_mlir_library(${name} - PARTIAL_SOURCES_INTENDED + # Generate the aggregate .so that everything depends on. + add_mlir_aggregate(${name} SHARED DISABLE_INSTALL - ${_objects} - EXCLUDE_FROM_LIBMLIR - LINK_LIBS - ${_deps} + EMBED_LIBS ${_embed_libs} ) + if(MSVC) set_property(TARGET ${name} PROPERTY WINDOWS_EXPORT_ALL_SYMBOLS ON) endif() diff --git a/mlir/examples/standalone/.gitignore b/mlir/examples/standalone/.gitignore new file mode 100644 --- /dev/null +++ b/mlir/examples/standalone/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/mlir/examples/standalone/include/Standalone-c/Dialects.h b/mlir/examples/standalone/include/Standalone-c/Dialects.h new file mode 100644 --- /dev/null +++ b/mlir/examples/standalone/include/Standalone-c/Dialects.h @@ -0,0 +1,24 @@ +//===- Dialects.h - CAPI for dialects -----------------------------*- C -*-===// +// +// This file is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef STANDALONE_C_DIALECTS_H +#define STANDALONE_C_DIALECTS_H + +#include "mlir-c/Registration.h" + +#ifdef __cplusplus +extern "C" { +#endif + +MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(Standalone, standalone); + +#ifdef __cplusplus +} +#endif + +#endif // STANDALONE_C_DIALECTS_H diff --git a/mlir/examples/standalone/lib/CAPI/CMakeLists.txt b/mlir/examples/standalone/lib/CAPI/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/mlir/examples/standalone/lib/CAPI/CMakeLists.txt @@ -0,0 +1,5 @@ +add_mlir_public_c_api_library(StandaloneCAPI + Dialects.cpp + LINK_LIBS PUBLIC + MLIRStandalone +) diff --git a/mlir/examples/standalone/lib/CAPI/Dialects.cpp b/mlir/examples/standalone/lib/CAPI/Dialects.cpp new file mode 100644 --- /dev/null +++ b/mlir/examples/standalone/lib/CAPI/Dialects.cpp @@ -0,0 +1,15 @@ +//===- Dialects.cpp - CAPI for dialects -----------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "Standalone-c/Dialects.h" + +#include "Standalone/StandaloneDialect.h" +#include "mlir/CAPI/Registration.h" + +MLIR_DEFINE_CAPI_DIALECT_REGISTRATION(Standalone, standalone, + mlir::standalone::StandaloneDialect) diff --git a/mlir/examples/standalone/lib/CMakeLists.txt b/mlir/examples/standalone/lib/CMakeLists.txt --- a/mlir/examples/standalone/lib/CMakeLists.txt +++ b/mlir/examples/standalone/lib/CMakeLists.txt @@ -1 +1,2 @@ +add_subdirectory(CAPI) add_subdirectory(Standalone) diff --git a/mlir/examples/standalone/test/CAPI/CMakeLists.txt b/mlir/examples/standalone/test/CAPI/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/mlir/examples/standalone/test/CAPI/CMakeLists.txt @@ -0,0 +1,18 @@ +# Here we create a single aggregate shared library with the parts of the CAPI +# that we want to bundle together. Then we link a simple C executable +# against it to demonstrate that it does have the fully self contained +# core MLIR library and our own standalone dialect. +add_mlir_aggregate(StandaloneCAPITestLib + SHARED + EMBED_LIBS + MLIRCAPIIR + MLIRCAPIRegistration + StandaloneCAPI +) + +add_llvm_executable(standalone-capi-test + standalone-capi-test.c +) +llvm_update_compile_flags(standalone-capi-test) +target_link_libraries(standalone-capi-test + PRIVATE StandaloneCAPITestLib) diff --git a/mlir/examples/standalone/test/CAPI/lit.local.cfg b/mlir/examples/standalone/test/CAPI/lit.local.cfg new file mode 100644 --- /dev/null +++ b/mlir/examples/standalone/test/CAPI/lit.local.cfg @@ -0,0 +1 @@ +config.suffixes.add('.c') diff --git a/mlir/examples/standalone/test/CAPI/standalone-capi-test.c b/mlir/examples/standalone/test/CAPI/standalone-capi-test.c new file mode 100644 --- /dev/null +++ b/mlir/examples/standalone/test/CAPI/standalone-capi-test.c @@ -0,0 +1,41 @@ +//===- standalone-cap-demo.c - Simple demo of C-API -----------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM +// Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// RUN: standalone-capi-test 2>&1 | FileCheck %s + +#include + +#include "mlir-c/IR.h" +#include "Standalone-c/Dialects.h" + +int main(int argc, char **argv) { + MlirContext ctx = mlirContextCreate(); + // TODO: Create the dialect handles for the builtin dialects and avoid this. + // This adds dozens of MB of binary size over just the standalone dialect. + mlirRegisterAllDialects(ctx); + mlirDialectHandleRegisterDialect(mlirGetDialectHandle__standalone__(), ctx); + + MlirModule module = mlirModuleCreateParse( + ctx, mlirStringRefCreateFromCString("%0 = constant 2 : i32\n" + "%1 = standalone.foo %0 : i32\n")); + if (mlirModuleIsNull(module)) { + printf("ERROR: Could not parse.\n"); + mlirContextDestroy(ctx); + return 1; + } + MlirOperation op = mlirModuleGetOperation(module); + + // CHECK: %[[C:.*]] = constant 2 : i32 + // CHECK: standalone.foo %[[C]] : i32 + mlirOperationDump(op); + + mlirModuleDestroy(module); + mlirContextDestroy(ctx); + return 0; +} diff --git a/mlir/examples/standalone/test/CMakeLists.txt b/mlir/examples/standalone/test/CMakeLists.txt --- a/mlir/examples/standalone/test/CMakeLists.txt +++ b/mlir/examples/standalone/test/CMakeLists.txt @@ -7,6 +7,7 @@ set(STANDALONE_TEST_DEPENDS FileCheck count not + standalone-capi-test standalone-opt standalone-translate ) @@ -18,3 +19,5 @@ set_target_properties(check-standalone PROPERTIES FOLDER "Tests") add_lit_testsuites(STANDALONE ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${STANDALONE_TEST_DEPENDS}) + +add_subdirectory(CAPI) diff --git a/mlir/examples/standalone/test/lit.cfg.py b/mlir/examples/standalone/test/lit.cfg.py --- a/mlir/examples/standalone/test/lit.cfg.py +++ b/mlir/examples/standalone/test/lit.cfg.py @@ -54,6 +54,7 @@ tool_dirs = [config.standalone_tools_dir, config.llvm_tools_dir] tools = [ + 'standalone-capi-test', 'standalone-opt', 'standalone-translate' ]