[llvm] r339151 - [RFC] Build LLVM-C.dll on MSVC that exports only the C API

David Bolvansky via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 7 08:54:50 PDT 2018


Author: xbolva00
Date: Tue Aug  7 08:54:50 2018
New Revision: 339151

URL: http://llvm.org/viewvc/llvm-project?rev=339151&view=rev
Log:
[RFC] Build LLVM-C.dll on MSVC that exports only the C API

Summary:
Hello!

This commit adds a LLVM-C target that is always built on MSVC. A big fat warning, this is my first cmake code ever so there is a fair bit of I-have-no-idea-what-I'm-doing going on here. Which is also why I placed it outside of llvm-shlib as I was afraid of breaking things of other people. Secondly llvm-shlib builds a LLVM.so which exports all symbols and then does a thin library that points to it, but on Windows we do not build a LLVM.dll so that would have complicated the code more.

The patch includes a python script that calls dumpbin.exe to get all of the symbols from the built libraries. It then grabs all the symbols starting with LLVM and generates the export file from those. The export file is then used to create the library just like the LLVM-C that is built on darwin.

Improvements that I need help with, to follow up this review.
  - Get cmake to make sure that dumpbin.exe is on the path and wire the full path to the script.
  - Use LLVM-C.dll when building llvm-c-test so we can verify that the symbols are exported.
  - Bundle the LLVM-C.dll with the windows installer.

Why do this?  I'm building a language frontend which is self-hosting, and on windows because of various tooling issues we have a problem of consuming the LLVM*.lib directly on windows. Me and the users of my projects using LLVM would be greatly helped by having LLVM-C.dll built and shipped by the Windows installer. Not only does LLVM takes forever to build, you have to run a extra python script in order to get the final DLL.

Any comments, thoughts or help is greatly appreciated.

Cheers, Jakob.

Patch by: Wallbraker (Jakob Bornecrantz)

Reviewers: compnerd, beanz, hans, smeenai

Reviewed By: beanz

Subscribers: xbolva00, bhelyer, Memnarch, rnk, fedor.sergeev, chapuni, smeenai, john.brawn, deadalnix, llvm-commits, mgorny

Differential Revision: https://reviews.llvm.org/D35077

Added:
    llvm/trunk/tools/llvm-shlib/gen-msvc-exports.py
Modified:
    llvm/trunk/CMakeLists.txt
    llvm/trunk/tools/CMakeLists.txt
    llvm/trunk/tools/llvm-shlib/CMakeLists.txt

Modified: llvm/trunk/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/CMakeLists.txt?rev=339151&r1=339150&r2=339151&view=diff
==============================================================================
--- llvm/trunk/CMakeLists.txt (original)
+++ llvm/trunk/CMakeLists.txt Tue Aug  7 08:54:50 2018
@@ -550,9 +550,13 @@ if(NOT DEFINED LLVM_DYLIB_COMPONENTS)
     "Semicolon-separated list of components to include in libLLVM, or \"all\".")
 endif()
 option(LLVM_LINK_LLVM_DYLIB "Link tools against the libllvm dynamic library" OFF)
-option(LLVM_BUILD_LLVM_C_DYLIB "Build libllvm-c re-export library (Darwin Only)" OFF)
+if(MSVC)
+  option(LLVM_BUILD_LLVM_C_DYLIB "Build LLVM-C.dll (Windows only)" OFF)
+else()
+  option(LLVM_BUILD_LLVM_C_DYLIB "Build libllvm-c re-export library (Darwin only)" OFF)
+endif()
 set(LLVM_BUILD_LLVM_DYLIB_default OFF)
-if(LLVM_LINK_LLVM_DYLIB OR LLVM_BUILD_LLVM_C_DYLIB)
+if(LLVM_LINK_LLVM_DYLIB OR (LLVM_BUILD_LLVM_C_DYLIB AND NOT MSVC))
   set(LLVM_BUILD_LLVM_DYLIB_default ON)
 endif()
 option(LLVM_BUILD_LLVM_DYLIB "Build libllvm dynamic library" ${LLVM_BUILD_LLVM_DYLIB_default})

Modified: llvm/trunk/tools/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/tools/CMakeLists.txt?rev=339151&r1=339150&r2=339151&view=diff
==============================================================================
--- llvm/trunk/tools/CMakeLists.txt (original)
+++ llvm/trunk/tools/CMakeLists.txt Tue Aug  7 08:54:50 2018
@@ -17,7 +17,7 @@ else()
   set(LLVM_TOOL_POLLY_BUILD Off)
 endif()
 
-if(NOT LLVM_BUILD_LLVM_DYLIB )
+if(NOT LLVM_BUILD_LLVM_DYLIB AND NOT LLVM_BUILD_LLVM_C_DYLIB)
   set(LLVM_TOOL_LLVM_SHLIB_BUILD Off)
 endif()
 

Modified: llvm/trunk/tools/llvm-shlib/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/tools/llvm-shlib/CMakeLists.txt?rev=339151&r1=339150&r2=339151&view=diff
==============================================================================
--- llvm/trunk/tools/llvm-shlib/CMakeLists.txt (original)
+++ llvm/trunk/tools/llvm-shlib/CMakeLists.txt Tue Aug  7 08:54:50 2018
@@ -6,77 +6,87 @@ set(SOURCES
   libllvm.cpp
   )
 
-llvm_map_components_to_libnames(LIB_NAMES ${LLVM_DYLIB_COMPONENTS})
-
 if(LLVM_LINK_LLVM_DYLIB AND LLVM_DYLIB_EXPORTED_SYMBOL_FILE)
   message(WARNING "Using LLVM_LINK_LLVM_DYLIB with LLVM_DYLIB_EXPORTED_SYMBOL_FILE may not work. Use at your own risk.")
 endif()
 
-# libLLVM.so should not have any dependencies on any other LLVM
-# shared libraries. When using the "all" pseudo-component,
-# LLVM_AVAILABLE_LIBS is added to the dependencies, which may
-# contain shared libraries (e.g. libLTO).
-#
-# Also exclude libLLVMTableGen for the following reasons:
-#  - it is only used by internal *-tblgen utilities;
-#  - it pollutes the global options space.
-foreach(lib ${LIB_NAMES})
-  get_target_property(t ${lib} TYPE)
-  if("${lib}" STREQUAL "LLVMTableGen")
-  elseif("x${t}" STREQUAL "xSTATIC_LIBRARY")
-    list(APPEND FILTERED_LIB_NAMES ${lib})
-  endif()
-endforeach()
-set(LIB_NAMES ${FILTERED_LIB_NAMES})
-
-if(LLVM_DYLIB_EXPORTED_SYMBOL_FILE)
-  set(LLVM_EXPORTED_SYMBOL_FILE ${LLVM_DYLIB_EXPORTED_SYMBOL_FILE})
-  add_custom_target(libLLVMExports DEPENDS ${LLVM_EXPORTED_SYMBOL_FILE})
-endif()
+if(LLVM_BUILD_LLVM_DYLIB)
+  if(MSVC)
+    message(FATAL_ERROR "Generating libLLVM is not supported on MSVC")
+  endif()
 
-add_llvm_library(LLVM SHARED DISABLE_LLVM_LINK_LLVM_DYLIB SONAME ${SOURCES})
+  llvm_map_components_to_libnames(LIB_NAMES ${LLVM_DYLIB_COMPONENTS})
 
-list(REMOVE_DUPLICATES LIB_NAMES)
-if(("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") OR (MINGW) OR (HAIKU)
-   OR ("${CMAKE_SYSTEM_NAME}" STREQUAL "FreeBSD")
-   OR ("${CMAKE_SYSTEM_NAME}" STREQUAL "OpenBSD")
-   OR ("${CMAKE_SYSTEM_NAME}" STREQUAL "Fuchsia")
-   OR ("${CMAKE_SYSTEM_NAME}" STREQUAL "DragonFly")
-   OR ("${CMAKE_SYSTEM_NAME}" STREQUAL "SunOS")) # FIXME: It should be "GNU ld for elf"
-  configure_file(
-  ${CMAKE_CURRENT_SOURCE_DIR}/simple_version_script.map.in
-  ${LLVM_LIBRARY_DIR}/tools/llvm-shlib/simple_version_script.map)
-
-  # GNU ld doesn't resolve symbols in the version script.
-  set(LIB_NAMES -Wl,--whole-archive ${LIB_NAMES} -Wl,--no-whole-archive)
-  if (NOT LLVM_LINKER_IS_SOLARISLD)
-    # Solaris ld does not accept global: *; so there is no way to version *all* global symbols
-    set(LIB_NAMES -Wl,--version-script,${LLVM_LIBRARY_DIR}/tools/llvm-shlib/simple_version_script.map ${LIB_NAMES})
+  # libLLVM.so should not have any dependencies on any other LLVM
+  # shared libraries. When using the "all" pseudo-component,
+  # LLVM_AVAILABLE_LIBS is added to the dependencies, which may
+  # contain shared libraries (e.g. libLTO).
+  #
+  # Also exclude libLLVMTableGen for the following reasons:
+  #  - it is only used by internal *-tblgen utilities;
+  #  - it pollutes the global options space.
+  foreach(lib ${LIB_NAMES})
+    get_target_property(t ${lib} TYPE)
+    if("${lib}" STREQUAL "LLVMTableGen")
+    elseif("x${t}" STREQUAL "xSTATIC_LIBRARY")
+      list(APPEND FILTERED_LIB_NAMES ${lib})
+    endif()
+  endforeach()
+  set(LIB_NAMES ${FILTERED_LIB_NAMES})
+
+  if(LLVM_DYLIB_EXPORTED_SYMBOL_FILE)
+    set(LLVM_EXPORTED_SYMBOL_FILE ${LLVM_DYLIB_EXPORTED_SYMBOL_FILE})
+    add_custom_target(libLLVMExports DEPENDS ${LLVM_EXPORTED_SYMBOL_FILE})
   endif()
-elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin")
-  set(LIB_NAMES -Wl,-all_load ${LIB_NAMES})
-endif()
 
-target_link_libraries(LLVM PRIVATE ${LIB_NAMES})
+  add_llvm_library(LLVM SHARED DISABLE_LLVM_LINK_LLVM_DYLIB SONAME ${SOURCES})
 
-if (APPLE)
-  set_property(TARGET LLVM APPEND_STRING PROPERTY
-              LINK_FLAGS
-              " -compatibility_version 1 -current_version ${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.${LLVM_VERSION_PATCH}")
-endif()
+  list(REMOVE_DUPLICATES LIB_NAMES)
+  if(("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") OR (MINGW) OR (HAIKU)
+     OR ("${CMAKE_SYSTEM_NAME}" STREQUAL "FreeBSD")
+     OR ("${CMAKE_SYSTEM_NAME}" STREQUAL "OpenBSD")
+     OR ("${CMAKE_SYSTEM_NAME}" STREQUAL "Fuchsia")
+     OR ("${CMAKE_SYSTEM_NAME}" STREQUAL "DragonFly")
+     OR ("${CMAKE_SYSTEM_NAME}" STREQUAL "SunOS")) # FIXME: It should be "GNU ld for elf"
+    configure_file(
+    ${CMAKE_CURRENT_SOURCE_DIR}/simple_version_script.map.in
+    ${LLVM_LIBRARY_DIR}/tools/llvm-shlib/simple_version_script.map)
+
+    # GNU ld doesn't resolve symbols in the version script.
+    set(LIB_NAMES -Wl,--whole-archive ${LIB_NAMES} -Wl,--no-whole-archive)
+    if (NOT LLVM_LINKER_IS_SOLARISLD)
+      # Solaris ld does not accept global: *; so there is no way to version *all* global symbols
+      set(LIB_NAMES -Wl,--version-script,${LLVM_LIBRARY_DIR}/tools/llvm-shlib/simple_version_script.map ${LIB_NAMES})
+    endif()
+  elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin")
+    set(LIB_NAMES -Wl,-all_load ${LIB_NAMES})
+  endif()
 
-if(TARGET libLLVMExports)
-  add_dependencies(LLVM libLLVMExports)
-endif()
+  target_link_libraries(LLVM PRIVATE ${LIB_NAMES})
 
-if(LLVM_BUILD_LLVM_C_DYLIB)
-  # To get the export list for a single llvm library:
-  # nm ${LIB_PATH} | awk "/T _LLVM/ { print $3 }" | sort -u | sed -e "s/^_//g" > ${LIB_PATH}.exports
+  if (APPLE)
+    set_property(TARGET LLVM APPEND_STRING PROPERTY
+                LINK_FLAGS
+                " -compatibility_version 1 -current_version ${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.${LLVM_VERSION_PATCH}")
+  endif()
 
+  if(TARGET libLLVMExports)
+    add_dependencies(LLVM libLLVMExports)
+  endif()
+endif()
+
+if(LLVM_BUILD_LLVM_C_DYLIB AND NOT MSVC)
   if(NOT APPLE)
     message(FATAL_ERROR "Generating libLLVM-c is only supported on Darwin")
   endif()
 
+  if(NOT LLVM_BUILD_LLVM_DYLIB)
+    message(FATAL_ERROR "Generating libLLVM-c requires LLVM_BUILD_LLVM_C_DYLIB on Darwin")
+  endif()
+
+  # To get the export list for a single llvm library:
+  # nm ${LIB_PATH} | awk "/T _LLVM/ { print $3 }" | sort -u | sed -e "s/^_//g" > ${LIB_PATH}.exports
+
   set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_BINARY_DIR}/libllvm-c.exports)
 
   set(LIB_DIR ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/lib${LLVM_LIBDIR_SUFFIX})
@@ -104,3 +114,40 @@ if(LLVM_BUILD_LLVM_C_DYLIB)
               " -compatibility_version 1 -current_version ${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.${LLVM_VERSION_PATCH} -Wl,-reexport_library ${LIB_PATH}")
 endif()
 
+if(MSVC)
+  # Build the LLVM-C.dll library that exports the C API.
+
+  set(LLVM_LINK_COMPONENTS
+    ${LLVM_DYLIB_COMPONENTS}
+    )
+
+  llvm_map_components_to_libnames(LIB_NAMES ${LLVM_DYLIB_COMPONENTS})
+  list(REMOVE_DUPLICATES LIB_NAMES)
+
+  # The python script needs to know whether symbols are prefixed with underscores or not.
+  if(LLVM_HOST_TRIPLE STREQUAL "i686-pc-win32")
+    set(GEN_UNDERSCORE "--underscore")
+  else()
+    set(GEN_UNDERSCORE "")
+  endif()
+
+  # Get the full name to the libs so the python script understands them.
+  foreach(lib ${LIB_NAMES})
+    list(APPEND FULL_LIB_NAMES ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/lib/${lib}.lib)
+  endforeach()
+
+  # Generate the exports file dynamically.
+  set(GEN_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/gen-msvc-exports.py)
+
+  set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/libllvm-c.exports)
+
+  add_custom_command(OUTPUT ${LLVM_EXPORTED_SYMBOL_FILE}
+    COMMAND ${PYTHON_EXECUTABLE} ${GEN_SCRIPT} ${FULL_LIB_NAMES} ${GEN_UNDERSCORE} --nm ${LLVM_TOOLS_BINARY_DIR}/llvm-nm -o ${LLVM_EXPORTED_SYMBOL_FILE}
+    DEPENDS ${LIB_NAMES} llvm-nm
+    COMMENT "Generating export list for LLVM-C"
+    VERBATIM )
+
+  # Finally link the target.
+  add_llvm_library(LLVM-C SHARED ${SOURCES} DEPENDS intrinsics_gen)
+
+endif()

Added: llvm/trunk/tools/llvm-shlib/gen-msvc-exports.py
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/tools/llvm-shlib/gen-msvc-exports.py?rev=339151&view=auto
==============================================================================
--- llvm/trunk/tools/llvm-shlib/gen-msvc-exports.py (added)
+++ llvm/trunk/tools/llvm-shlib/gen-msvc-exports.py Tue Aug  7 08:54:50 2018
@@ -0,0 +1,106 @@
+#===- gen-msvc-exports.py - Generate C API export file -------*- python -*--===#
+#
+#                     The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+#===------------------------------------------------------------------------===#
+#
+# Generate an export file from a list of given LIB files. This only exports symbols
+# that start with LLVM, so it only exports the LLVM C API.
+#
+# To have CMake run this, set LLVM_BUILD_LLVM_C_DYLIB to on while
+# building on Windows.
+#
+# To run manually, build LLVM with Visual Studio, use a Command prompt
+# to navigate to the directory with the .lib files (Debug\lib etc). Then run
+#     python C:\Path\To\gen-msvc-exports.py --nm ..\bin\llvm-nm.exe LLVM*.lib
+#
+# If you're generating a 32 bit DLL, use the `--underscore` flag.
+# If you want to use a different `llvm-nm` executable, pass the path
+# with the `--nm` flag.
+#
+# You can use the --output flag to set the name of the export file.
+#
+#===------------------------------------------------------------------------===#
+from tempfile import mkstemp
+from contextlib import contextmanager
+from subprocess import check_call
+import argparse
+import os
+import re
+
+
+_UNDERSCORE_REGEX = {
+    False: re.compile(r"^\w+\s+T\s+(LLVM.*)$"),
+    True:  re.compile(r"^\w+\s+T\s+_(LLVM.*)$")
+}
+
+
+ at contextmanager
+def removing(path):
+    try:
+        yield path
+    finally:
+        os.unlink(path)
+
+
+def touch_tempfile(*args, **kwargs):
+    fd, name = mkstemp(*args, **kwargs)
+    os.close(fd)
+    return name
+
+
+def gen_llvm_c_export(output, underscore, libs, nm):
+    """Generate the export file for the LLVM-C DLL.
+
+    Run `nm` for each lib in `libs`, and output an export file
+    to `output`. If `underscore` is true, symbols will
+    be assumed to be prefixed with an underscore.
+    """
+    with removing(touch_tempfile(prefix='dumpout', suffix='.txt')) as dumpout:
+
+        # Get the right regex.
+        p = _UNDERSCORE_REGEX[underscore]
+
+        with open(output, 'w+t') as output_f:
+
+            # For each lib get the LLVM* functions it exports.
+            for lib in libs:
+                # Call dumpbin.
+                with open(dumpout, 'w+t') as dumpout_f:
+                    check_call([nm, '-g', lib], stdout=dumpout_f)
+
+                # Get the matching lines.
+                with open(dumpout) as dumpbin:
+                    for line in dumpbin:
+                        m = p.match(line)
+                        if m is not None:
+                            output_f.write(m.group(1) + '\n')
+
+
+def main():
+    parser = argparse.ArgumentParser('gen-msvc-exports')
+
+    parser.add_argument(
+        '-o', '--output', help='output filename', default='LLVM-C.exports'
+    )
+    parser.add_argument('-u', '--underscore',
+        help='labels are prefixed with an underscore (use for 32 bit DLLs)',
+        action='store_true'
+    )
+    parser.add_argument(
+        '--nm', help='path to the llvm-nm executable', default='llvm-nm'
+    )
+    parser.add_argument(
+        'libs', metavar='LIBS', nargs='+', help='list of libraries to generate export from'
+    )
+
+    ns = parser.parse_args()
+
+    gen_llvm_c_export(ns.output, ns.underscore, ns.libs, ns.nm)
+
+
+if __name__ == '__main__':
+    main()




More information about the llvm-commits mailing list