Add IREE_COMPILER_BUILD_SHARED_LIBS option. (#12310)

* Balances all of the angels on the head of the pin such that when using
it, trivial changes to the compiler that would usually require a lengthy
re-link happen in <1s.
* Fixes some indirect dependencies that were missing (and enforced with
stricter linking).
* This is the equivalent of how LLVM interprets -DBUILD_SHARED_LIBS=ON,
scoped to just IREE's bundled LLVM, LLVM sub-projects and compiler
sources.
* As with all uses of BUILD_SHARED_LIBS, the support matrix for this
feature is sparse: there are many ways it will not work outside of its
strict case for supporting development.
* iree-run-module is not happy (complaining of duplicate option
registration). Will triage separately.
* This probably only works on Linux right now. It likely can work on
MacOS, but the libIREECompiler.dylib will need to be rigged with
--reexport_library (which is effectively the default for dynamically
loaded ELF). I don't think this will work on Windows without substantial
work.
* I am intending to use this mode to enable rapid reload of dynamically
built plugins, etc.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2c91aac..0c34543 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -94,6 +94,20 @@
 
 option(IREE_BUILD_MICROBENCHMARKS "Builds IREE microbenchmark suites." OFF)
 
+# For development, builds LLVM (and in the future) the whole compiler as
+# individual shared libraries similar to if passing -DBUILD_SHARED_LIBS=ON
+# to a standalone LLVM build. This can dramatically reduce linking time and
+# makes the management of some dependencies more strict.
+# This option is considered experimental and should not be relied on until
+# CI coverage is established.
+option(IREE_COMPILER_BUILD_SHARED_LIBS "Enables BUILD_SHARED_LIBS CMake mode for LLVM and the compiler (this is only suitable for development)" OFF)
+
+# Must be defined as an option (CMake does not do it automatically), even though
+# we override it for different parts of the tree.
+# This option is considered experimental and should not be relied on until
+# CI coverage is established.
+option(BUILD_SHARED_LIBS "Instructs CMake to build libraries as shared if possible" OFF)
+
 #-------------------------------------------------------------------------------
 # Experimental project flags
 #-------------------------------------------------------------------------------
@@ -668,6 +682,12 @@
 if(NOT IREE_BUILD_COMPILER)
   message(STATUS "Not adding LLVM/MLIR because the configuration does not require it")
 else()
+  # Force enable BUILD_SHARED_LIBS for the compiler if instructed.
+  set(_IREE_ORIG_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS})
+  if(IREE_COMPILER_BUILD_SHARED_LIBS)
+    set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE)
+  endif()
+
   # Get the main LLVM deps.
   if(IREE_BUILD_BUNDLED_LLVM)
     iree_llvm_configure_bundled()
@@ -706,6 +726,8 @@
   if(IREE_CLANG_TARGET)
     add_dependencies(iree-test-deps ${IREE_CLANG_TARGET})
   endif()
+
+  set(BUILD_SHARED_LIBS ${_IREE_ORIG_BUILD_SHARED_LIBS} CACHE BOOL "" FORCE)
 endif()
 
 #-------------------------------------------------------------------------------
diff --git a/build_tools/cmake/iree_cc_library.cmake b/build_tools/cmake/iree_cc_library.cmake
index e3d18d1..0392cb7 100644
--- a/build_tools/cmake/iree_cc_library.cmake
+++ b/build_tools/cmake/iree_cc_library.cmake
@@ -113,7 +113,7 @@
 
   if(NOT _RULE_IS_INTERFACE)
     add_library(${_OBJECTS_NAME} OBJECT)
-    if(_RULE_SHARED)
+    if(_RULE_SHARED OR BUILD_SHARED_LIBS)
       add_library(${_NAME} SHARED "$<TARGET_OBJECTS:${_OBJECTS_NAME}>")
       if(_RULE_WINDOWS_DEF_FILE AND WIN32)
         target_sources(${_NAME} PRIVATE "${_RULE_WINDOWS_DEF_FILE}")
@@ -197,6 +197,17 @@
         ${_RULE_DEFINES}
     )
 
+    # If in BUILD_SHARED_LIBS mode, then we need to make sure that visibility
+    # is not hidden. We default to hidden visibility in the main copts so
+    # need to undo it here.
+    # TODO: Switch to the CXX_VISIBILITY_PRESET property and fix the global
+    # hidden setting to follow suit.
+    if(BUILD_SHARED_LIBS AND IREE_SUPPORTS_VISIBILITY_DEFAULT)
+      target_compile_options(${_OBJECTS_NAME} PRIVATE
+        "-fvisibility=default"
+      )
+    endif()
+
     # Add all IREE targets to a folder in the IDE for organization.
     if(_RULE_PUBLIC)
       set_property(TARGET ${_NAME} PROPERTY FOLDER ${IREE_IDE_FOLDER})
@@ -388,9 +399,9 @@
 
 # iree_cc_library_exclude_from_all(target exclude)
 #
-# For a target previously defined in the same package, set the 
+# For a target previously defined in the same package, set the
 # EXCLUDE_FROM_ALL property.
-# 
+#
 # This is necessary because cc_library targets consist of multiple sub-targets
 # and they all must have the property set.
 function(iree_cc_library_exclude_from_all target exclude_from_all)
@@ -400,6 +411,6 @@
   set(_NAME "${_PACKAGE_NAME}_${target}")
   set(_OBJECTS_NAME ${_NAME}.objects)
 
-  set_target_properties(${_NAME} ${_OBJECTS_NAME} 
+  set_target_properties(${_NAME} ${_OBJECTS_NAME}
     PROPERTIES EXCLUDE_FROM_ALL ${exclude_from_all})
 endfunction()
diff --git a/build_tools/cmake/iree_setup_toolchain.cmake b/build_tools/cmake/iree_setup_toolchain.cmake
index f6f3c49..52d494b 100644
--- a/build_tools/cmake/iree_setup_toolchain.cmake
+++ b/build_tools/cmake/iree_setup_toolchain.cmake
@@ -29,6 +29,12 @@
 endif()
 
 #-------------------------------------------------------------------------------
+# Compiler flag support
+#-------------------------------------------------------------------------------
+
+check_cxx_compiler_flag(-fvisibility=default IREE_SUPPORTS_VISIBILITY_DEFAULT)
+
+#-------------------------------------------------------------------------------
 # Linker setup
 #-------------------------------------------------------------------------------
 
@@ -91,6 +97,13 @@
 if(IREE_ENABLE_ASAN)
   string(APPEND CMAKE_CXX_FLAGS " -fsanitize=address")
   string(APPEND CMAKE_C_FLAGS " -fsanitize=address")
+  # If doing any kind of shared library builds, then we have to link against
+  # the shared libasan, and the user will be responsible for adding the
+  # appropriate path to LD_LIBRARY_PATH (or else binaries will fail to launch).
+  if(BUILD_SHARED_LIBS OR IREE_COMPILER_BUILD_SHARED_LIBS)
+    string(APPEND CMAKE_EXE_LINKER_FLAGS " -shared-libasan")
+    string(APPEND CMAKE_SHARED_LINKER_FLAGS " -shared-libasan")
+  endif()
 endif()
 if(IREE_ENABLE_MSAN)
   string(APPEND CMAKE_CXX_FLAGS " -fsanitize=memory")
diff --git a/compiler/src/CMakeLists.txt b/compiler/src/CMakeLists.txt
index d6a8419..02571bc 100644
--- a/compiler/src/CMakeLists.txt
+++ b/compiler/src/CMakeLists.txt
@@ -17,4 +17,13 @@
 # which provides common includes and copts for the tree.
 set(IREE_IMPLICIT_DEFS_CC_DEPS iree::compiler::src::defs)
 
+# Force enable BUILD_SHARED_LIBS for the compiler if instructed.
+set(_IREE_ORIG_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS})
+if(IREE_COMPILER_BUILD_SHARED_LIBS)
+  set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE)
+endif()
+
 add_subdirectory(iree/compiler)
+
+# Reset BUILD_SHARED_LIBS.
+set(BUILD_SHARED_LIBS ${_IREE_ORIG_BUILD_SHARED_LIBS} CACHE BOOL "" FORCE)
diff --git a/compiler/src/iree/compiler/API/python/CMakeLists.txt b/compiler/src/iree/compiler/API/python/CMakeLists.txt
index 1667aea..7d8221f 100644
--- a/compiler/src/iree/compiler/API/python/CMakeLists.txt
+++ b/compiler/src/iree/compiler/API/python/CMakeLists.txt
@@ -135,8 +135,7 @@
   )
   target_link_libraries(${target}
     iree_compiler_bindings_c_headers
-    iree_compiler_API2_SharedImpl
-  )
+    iree_compiler_API2_Impl)
   set_target_properties(${target}
     PROPERTIES
       OUTPUT_NAME "${ARG_OUTPUT_NAME}"
diff --git a/compiler/src/iree/compiler/API2/CMakeLists.txt b/compiler/src/iree/compiler/API2/CMakeLists.txt
index 7a03c31..751ffe5 100644
--- a/compiler/src/iree/compiler/API2/CMakeLists.txt
+++ b/compiler/src/iree/compiler/API2/CMakeLists.txt
@@ -109,8 +109,12 @@
 
 # The bazel side uses a "Impl" library alias to generically represent
 # static or shared. Provide a corresponding one here.
+# When in BUILD_SHARED_LIBS mode, we do CMake level linking against the
+# non-shared library, since strict layering is enforced there. The mondo
+# shared library can still be generated and used dynamically but is not
+# generally depended on from a normal C++ link.
 
-if(IREE_LINK_COMPILER_SHARED_LIBRARY)
+if(IREE_LINK_COMPILER_SHARED_LIBRARY AND NOT BUILD_SHARED_LIBS)
 iree_cc_library(
   NAME
     Impl
@@ -124,4 +128,4 @@
   DEPS
     ::StaticImpl
 )
-endif()  # IREE_LINK_COMPILER_SHARED_LIBRARY
+endif()
diff --git a/compiler/src/iree/compiler/API2/Internal/BUILD b/compiler/src/iree/compiler/API2/Internal/BUILD
index 9d2dc51..d4ca338 100644
--- a/compiler/src/iree/compiler/API2/Internal/BUILD
+++ b/compiler/src/iree/compiler/API2/Internal/BUILD
@@ -25,6 +25,7 @@
         "//compiler/bindings/c:headers",
         "//compiler/src/iree/compiler/ConstEval",
         "//compiler/src/iree/compiler/Dialect/VM/Target:init_targets",
+        "//compiler/src/iree/compiler/Dialect/VM/Target/Bytecode",
         "//compiler/src/iree/compiler/Dialect/VM/Target/C",
         "//compiler/src/iree/compiler/Pipelines",
         "//compiler/src/iree/compiler/Tools:init_llvmir_translations",
diff --git a/compiler/src/iree/compiler/API2/Internal/CMakeLists.txt b/compiler/src/iree/compiler/API2/Internal/CMakeLists.txt
index e926eb0..5e03e14 100644
--- a/compiler/src/iree/compiler/API2/Internal/CMakeLists.txt
+++ b/compiler/src/iree/compiler/API2/Internal/CMakeLists.txt
@@ -24,6 +24,7 @@
     MLIRParser
     MLIRSupport
     iree::compiler::ConstEval
+    iree::compiler::Dialect::VM::Target::Bytecode
     iree::compiler::Dialect::VM::Target::C
     iree::compiler::Dialect::VM::Target::init_targets
     iree::compiler::Pipelines
diff --git a/compiler/src/iree/compiler/API2/test/BUILD b/compiler/src/iree/compiler/API2/test/BUILD
index 9d736a2..d7669e2 100644
--- a/compiler/src/iree/compiler/API2/test/BUILD
+++ b/compiler/src/iree/compiler/API2/test/BUILD
@@ -20,5 +20,6 @@
         "//compiler/src/iree/compiler/API2:Impl",
         "//compiler/src/iree/compiler/API2:MLIRInteropHeaders",
         "//runtime/src/iree/base",
+        "@llvm-project//mlir:CAPIIRHeaders",
     ],
 )
diff --git a/compiler/src/iree/compiler/API2/test/CMakeLists.txt b/compiler/src/iree/compiler/API2/test/CMakeLists.txt
index 47c07c3..7676f10 100644
--- a/compiler/src/iree/compiler/API2/test/CMakeLists.txt
+++ b/compiler/src/iree/compiler/API2/test/CMakeLists.txt
@@ -16,6 +16,7 @@
   SRCS
     "api-test-main.c"
   DEPS
+    IREELLVMIncludeSetup
     iree::base
     iree::compiler::API2::Impl
     iree::compiler::API2::MLIRInteropHeaders
diff --git a/compiler/src/iree/compiler/Dialect/HAL/Target/LLVM/CMakeLists.txt b/compiler/src/iree/compiler/Dialect/HAL/Target/LLVM/CMakeLists.txt
index 673e634..4d5528c 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/Target/LLVM/CMakeLists.txt
+++ b/compiler/src/iree/compiler/Dialect/HAL/Target/LLVM/CMakeLists.txt
@@ -143,19 +143,23 @@
 
 add_library(IREELLVMCPUTargetDeps INTERFACE)
 
-function(_add_optional_llvm_dep dep)
-  if(TARGET ${dep})
-    target_link_libraries(IREELLVMCPUTargetDeps INTERFACE ${dep})
+function(_add_optional_llvm_target_deps target)
+  # Key off of the CodeGen target and then include the rest.
+  if(TARGET "LLVM${target}CodeGen")
+    target_link_libraries(IREELLVMCPUTargetDeps INTERFACE
+      "LLVM${target}AsmParser"
+      "LLVM${target}CodeGen"
+      "LLVM${target}Desc"
+      "LLVM${target}Info")
   endif()
 endfunction()
 
-_add_optional_llvm_dep(LLVMAArch64AsmParser)
-_add_optional_llvm_dep(LLVMAArch64CodeGen)
-_add_optional_llvm_dep(LLVMARMAsmParser)
-_add_optional_llvm_dep(LLVMARMCodeGen)
-_add_optional_llvm_dep(LLVMRISCVAsmParser)
-_add_optional_llvm_dep(LLVMRISCVCodeGen)
-_add_optional_llvm_dep(LLVMWebAssemblyAsmParser)
-_add_optional_llvm_dep(LLVMWebAssemblyCodeGen)
-_add_optional_llvm_dep(LLVMX86AsmParser)
-_add_optional_llvm_dep(LLVMX86CodeGen)
+# This is just the CPU backends that we hard-link to. Single-target backends
+# like CUDA and ROCM bring their dependencies explicitly.
+# TODO: More archaeology and tieing this to explicit top-level configuration
+# of backends.
+_add_optional_llvm_target_deps(AArch64)
+_add_optional_llvm_target_deps(ARM)
+_add_optional_llvm_target_deps(RISCV)
+_add_optional_llvm_target_deps(WebAssembly)
+_add_optional_llvm_target_deps(X86)