Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ if (CMAKE_SYSTEM_NAME MATCHES "Windows")
endif ()
endif ()

# Export all symbols on Windows to match GCC/Clang behavior on Linux/macOS
# Export all symbols on Windows to match GCC/Clang behavior on Linux/macOS.
# (With MSVC, src/CMakeLists.txt filters Halide.dll's list to stay under the linker's limit.)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)

# Require standard C++17
Expand Down
173 changes: 173 additions & 0 deletions cmake/FilterDllExports.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# Filters a module-definition (.def) export list produced by
# `cmake -E __create_def`, dropping symbols that are not ours to export.
#
# Usage: cmake -DIN=<in.def> -DOUT=<out.def> -P FilterDllExports.cmake
#
# CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS exports every external symbol in Halide's
# object files, including COMDAT instantiations of std:: templates. The list is
# close to link.exe's limit of 65535 exports (LNK1189), so we drop symbols whose
# *enclosing scope* (the outermost namespace/class in the MSVC-decorated name)
# is std, stdext, Concurrency, llvm or clang. Clients instantiate their own
# copies of those templates. A substring match on "@std@@" would be wrong, as
# it also appears in the parameter encoding of Halide functions taking, e.g.,
# std::string.
#
# Whenever a name cannot be confidently reduced, it is kept.
#
# Performance: this runs on every link over ~65k lines, and character-level
# loops in CMake are far too slow. Instead we rely on the fact that a template
# argument list containing no nested template is a regular language. Innermost
# templates are repeatedly collapsed into a plain fragment until the qualified
# name is flat, then the outermost scope is read with a single regex.

if (NOT DEFINED IN OR NOT DEFINED OUT)
message(FATAL_ERROR "Usage: cmake -DIN=<in.def> -DOUT=<out.def> -P FilterDllExports.cmake")
endif ()

set(EXPORT_LIMIT 65535)
set(MAX_PASSES 8)

# -- Building blocks of the mangled-name grammar --------------------------------

# An identifier fragment, e.g. `Halide@`.
set(ident [=[[A-Za-z_][A-Za-z0-9_]*@]=])
# A qualified name: fragments (identifiers or one-digit back-references)
# terminated by `@`, e.g. `Func@Halide@@`. Used after V/U/T/W4 in a type.
set(qual "((${ident}|[0-9])*@)")
# One template argument. Deliberately excludes anything that contains `@` or
# `?` in a way we do not model (function types `6`/`8`, arrays `Y`, `?`...), so
# such templates fail to match and their symbols are kept.
string(CONCAT arg
"(" "[VUT]${qual}" # class/struct/union by name
"|" "W[0-9]${qual}" # enum
"|" [=[\$\$[A-Za-z]]=] # $$Q, $$R, $$T, $$C...
"|" [=[\$0[0-9A-P]*@]=] # non-negative integer value
"|" "_[A-Za-z0-9]" # _N, _J, ...
"|" "[0-579A-SXZ]" # builtins, modifiers, back-references
")"
)

# An innermost template-id `?$name@<args>@`; \1 is the name.
set(template_re [=[\?\$([A-Za-z_][A-Za-z0-9_]*)@]=])
string(APPEND template_re "${arg}*@")

# The outermost scope of a flat qualified name: at least one fragment, then a
# final identifier, terminated by `@@`. \3 is the outermost scope.
set(scope_re "^(${ident}|[0-9])+([A-Za-z_][A-Za-z0-9_]*)@@")
# A qualified name of a single fragment, i.e. an unscoped global.
set(global_re "^[A-Za-z_][A-Za-z0-9_]*@@")

set(dropped_scope_re "^(std|stdext|Concurrency|llvm|clang)$")

# -- Main loop ------------------------------------------------------------------

file(READ "${IN}" content)
string(REPLACE "\r" "" content "${content}")
string(REPLACE "\n" ";" lines "${content}")

set(out "")
set(total 0)
set(dropped 0)
set(kept 0)
set(unparsed 0)

foreach (line IN LISTS lines)
if (NOT line MATCHES "[^ \t]")
continue()
endif ()
if (line MATCHES "^[ \t]*(EXPORTS|LIBRARY)")
string(APPEND out "${line}\n")
continue()
endif ()

string(REGEX REPLACE "^[ \t]+" "" symbol "${line}")
string(REGEX REPLACE "[ \t].*$" "" symbol "${symbol}")
math(EXPR total "${total} + 1")

set(decision KEEP)

# Strip the special-name prefix, leaving the qualified name. Anything
# undecorated (extern "C") is kept as is.
set(body "")
if (symbol MATCHES "^\\?")
if (symbol MATCHES "^\\?\\?_C")
# String literal.
elseif (symbol MATCHES "^\\?\\?\\$")
# Template function: the name is `?$name@...`
string(SUBSTRING "${symbol}" 1 -1 body)
elseif (symbol MATCHES "^\\?\\?_R0\\?A[VU]")
string(SUBSTRING "${symbol}" 8 -1 body)
elseif (symbol MATCHES "^\\?\\?_R4")
string(SUBSTRING "${symbol}" 5 -1 body)
elseif (symbol MATCHES "^\\?\\?_R")
set(decision UNPARSED)
elseif (symbol MATCHES "^\\?\\?__[0-9A-Za-z]")
string(SUBSTRING "${symbol}" 5 -1 body)
elseif (symbol MATCHES "^\\?\\?_[0-9A-Za-z]")
string(SUBSTRING "${symbol}" 4 -1 body)
elseif (symbol MATCHES "^\\?\\?[0-9A-Z]")
string(SUBSTRING "${symbol}" 3 -1 body)
elseif (symbol MATCHES "^\\?\\?")
set(decision UNPARSED)
else ()
string(SUBSTRING "${symbol}" 1 -1 body)
endif ()
endif ()

if (NOT "${body}" STREQUAL "")
foreach (pass RANGE 1 ${MAX_PASSES})
if (NOT body MATCHES "\\?\\$")
break()
endif ()
set(before "${body}")
string(REGEX REPLACE "${template_re}" "\\1@" body "${body}")
if (body STREQUAL before)
break()
endif ()
endforeach ()

if (body MATCHES "${scope_re}")
if ("${CMAKE_MATCH_2}" MATCHES "${dropped_scope_re}")
set(decision DROP)
endif ()
elseif (NOT body MATCHES "${global_re}")
set(decision UNPARSED)
endif ()
endif ()

if (decision STREQUAL DROP)
math(EXPR dropped "${dropped} + 1")
continue()
elseif (decision STREQUAL UNPARSED)
math(EXPR unparsed "${unparsed} + 1")
endif ()
math(EXPR kept "${kept} + 1")
string(APPEND out "${line}\n")
endforeach ()

if (total EQUAL 0)
message(FATAL_ERROR "No exports found in ${IN}")
endif ()

file(WRITE "${OUT}" "${out}")

message(
VERBOSE "Halide.dll exports: ${total} total, ${dropped} filtered out, ${kept} remaining "
"(${unparsed} kept only because their names could not be parsed); limit is ${EXPORT_LIMIT}"
)

if (kept GREATER EXPORT_LIMIT)
message(
FATAL_ERROR "Halide.dll would export ${kept} symbols after filtering, exceeding the "
"linker limit of ${EXPORT_LIMIT} (LNK1189). Reduce the number of exported "
"symbols or extend cmake/FilterDllExports.cmake."
)
endif ()

math(EXPR warn_threshold "${EXPORT_LIMIT} * 95 / 100")
if (kept GREATER warn_threshold)
message(
WARNING "Halide.dll exports ${kept} symbols after filtering, over 95% of the linker "
"limit of ${EXPORT_LIMIT}."
)
endif ()
44 changes: 44 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,50 @@ target_export_script(
GNU_LD "${CMAKE_CURRENT_LIST_DIR}/exported_symbols.ldscript"
)

##
# Trim Halide.dll's export list on MSVC
##

# CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS exports every external symbol in our object
# files, including COMDAT instantiations of std:: templates. That list is close
# to link.exe's limit of 65535 exports (LNK1189), so instead we generate the
# same list ourselves, drop symbols whose enclosing scope isn't ours (see
# cmake/FilterDllExports.cmake), and hand the result to the linker.
#
# `cmake -E __create_def` is an internal CMake command, but it is exactly what
# CMake itself runs for WINDOWS_EXPORT_ALL_SYMBOLS.
if (WIN32 AND BUILD_SHARED_LIBS AND CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
set(Halide_EXPORTS_DIR "${CMAKE_CURRENT_BINARY_DIR}/halide_exports/$<CONFIG>")

set_target_properties(Halide PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS OFF)

file(GENERATE
OUTPUT "${Halide_EXPORTS_DIR}/objects.txt"
CONTENT "$<JOIN:$<TARGET_OBJECTS:Halide>;$<TARGET_OBJECTS:Halide_initmod>,\n>\n"
)

# Export counts are reported at --log-level=VERBOSE; forward the setting.
set(Halide_EXPORTS_LOG_LEVEL "")
if (CMAKE_MESSAGE_LOG_LEVEL)
set(Halide_EXPORTS_LOG_LEVEL "--log-level=${CMAKE_MESSAGE_LOG_LEVEL}")
endif ()

add_custom_command(
TARGET Halide
PRE_LINK
COMMAND
"${CMAKE_COMMAND}" -E __create_def "${Halide_EXPORTS_DIR}/full.def"
"${Halide_EXPORTS_DIR}/objects.txt"
COMMAND
"${CMAKE_COMMAND}" ${Halide_EXPORTS_LOG_LEVEL} "-DIN=${Halide_EXPORTS_DIR}/full.def"
"-DOUT=${Halide_EXPORTS_DIR}/filtered.def" -P
"${Halide_SOURCE_DIR}/cmake/FilterDllExports.cmake"
COMMENT "Generating filtered export list for Halide.dll"
VERBATIM
)
target_link_options(Halide PRIVATE "/DEF:${Halide_EXPORTS_DIR}/filtered.def")
endif ()

##
# Set compiler options for libHalide
##
Expand Down
9 changes: 9 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,12 @@ Halide_feature(WITH_TEST_DEBUGGER "Build debugger-integration tests" ON)
if (WITH_TEST_DEBUGGER)
add_subdirectory(debugger)
endif ()

add_test(
NAME filter_dll_exports
COMMAND
"${CMAKE_COMMAND}" "-DSCRIPT=${Halide_SOURCE_DIR}/cmake/FilterDllExports.cmake"
"-DDIR=${CMAKE_CURRENT_SOURCE_DIR}/filter_dll_exports"
"-DOUT=${CMAKE_CURRENT_BINARY_DIR}/filter_dll_exports.out.def" -P
"${CMAKE_CURRENT_SOURCE_DIR}/filter_dll_exports/run.cmake"
)
27 changes: 27 additions & 0 deletions test/filter_dll_exports/expected.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
EXPORTS
?compile_to_bitcode@Func@Halide@@QAEXABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@ABV?$vector@VArgument@Halide@@V?$allocator@VArgument@Halide@@@std@@@4@0ABVTarget@2@@Z
?name@Func@Halide@@QBEABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ
??0Func@Halide@@QAE@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z
?const_true@Halide@@YA?AVExpr@1@H@Z
??$make_const@H@Internal@Halide@@YA?AVExpr@1@VType@1@H@Z
??0?$IntrusivePtr@VBaseExprNode@Internal@Halide@@@Internal@Halide@@QAE@XZ
??1?$IntrusivePtr@VBaseExprNode@Internal@Halide@@@Internal@Halide@@QAE@XZ
??_7?$_Func_impl_no_alloc@V<lambda_1>@?0??foo@Halide@@YAXXZ@XH@std@@6B@
??_G?$_Func_impl_no_alloc@P6AXH@ZXH@std@@UAEPAXI@Z
??_7Error@Halide@@6B@
??_R0?AVError@Halide@@@8
?global_table@Internal@Halide@@3PAHA DATA
?instances@?$Registry@VFoo@Halide@@@Internal@Halide@@2V?$vector@PAVFoo@Halide@@V?$allocator@PAVFoo@Halide@@@std@@@std@@A DATA
?free_helper@?A0x1234abcd@@YAXXZ
??$foo@V<lambda_1>@?0??bar@Halide@@YAXXZ@@std@@YAXXZ
?vec_size@Halide@@YAIABV?$vector@HV?$allocator@H@std@@@std@@@Z
?callback@Halide@@YAXP6AXABV?$vector@HV?$allocator@H@std@@@std@@@Z@Z
??$apply@P6AXH@Z@Halide@@YAXXZ
??$bar@$00@@Halide@@YAXXZ
??$baz@U?$array@H$01@@std@@@Halide@@YAXXZ
halide_malloc
halide_error
_halide_buffer_get_host
halide_pipeline_finalize DATA
main
?f@@YAXXZ
42 changes: 42 additions & 0 deletions test/filter_dll_exports/input.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
EXPORTS
?compile_to_bitcode@Func@Halide@@QAEXABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@ABV?$vector@VArgument@Halide@@V?$allocator@VArgument@Halide@@@std@@@4@0ABVTarget@2@@Z
?name@Func@Halide@@QBEABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ
??0Func@Halide@@QAE@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z
?const_true@Halide@@YA?AVExpr@1@H@Z
??$make_const@H@Internal@Halide@@YA?AVExpr@1@VType@1@H@Z
??0?$IntrusivePtr@VBaseExprNode@Internal@Halide@@@Internal@Halide@@QAE@XZ
??1?$IntrusivePtr@VBaseExprNode@Internal@Halide@@@Internal@Halide@@QAE@XZ
?_Tidy@?$vector@VFunc@Halide@@V?$allocator@VFunc@Halide@@@std@@@std@@AAEXXZ
??0?$vector@VFunc@Halide@@V?$allocator@VFunc@Halide@@@std@@@std@@QAE@XZ
??1?$vector@VFunc@Halide@@V?$allocator@VFunc@Halide@@@std@@@std@@QAE@XZ
??$_Emplace_reallocate@ABVFunc@Halide@@@?$vector@VFunc@Halide@@V?$allocator@VFunc@Halide@@@std@@@std@@QAEPAVFunc@Halide@@QAV23@ABV23@@Z
??$_Uninitialized_move@PAVFunc@Halide@@PAV12@V?$allocator@VFunc@Halide@@@std@@@std@@YAPAVFunc@Halide@@QAV12@0PAV12@AAV?$allocator@VFunc@Halide@@@0@@Z
??_7?$_Func_impl_no_alloc@V<lambda_1>@?0??foo@Halide@@YAXXZ@XH@std@@6B@
??_G?$_Func_impl_no_alloc@P6AXH@ZXH@std@@UAEPAXI@Z
??1?$map@HHU?$less@H@std@@V?$allocator@U?$pair@$$CBHH@std@@@2@@std@@QAE@XZ
??_7?$_Ref_count_obj2@VFoo@Halide@@@std@@6B@
??_R0?AV?$_Ref_count_obj2@VFoo@Halide@@@std@@@8
??_R4?$_Ref_count_obj2@VFoo@Halide@@@std@@6B@
??_7Error@Halide@@6B@
??_R0?AVError@Halide@@@8
??$_Get_size_of_n@$00@@std@@YAII@Z
?_Id@?$numpunct@D@std@@2V0locale@2@A DATA
?global_table@Internal@Halide@@3PAHA DATA
?instances@?$Registry@VFoo@Halide@@@Internal@Halide@@2V?$vector@PAVFoo@Halide@@V?$allocator@PAVFoo@Halide@@@std@@@std@@A DATA
?_Xlength_error@std@@YAXPBD@Z
?free_helper@?A0x1234abcd@@YAXXZ
??$foo@V<lambda_1>@?0??bar@Halide@@YAXXZ@@std@@YAXXZ
?vec_size@Halide@@YAIABV?$vector@HV?$allocator@H@std@@@std@@@Z
?callback@Halide@@YAXP6AXABV?$vector@HV?$allocator@H@std@@@std@@@Z@Z
??$apply@P6AXH@Z@Halide@@YAXXZ
??$bar@$00@@Halide@@YAXXZ
??$baz@U?$array@H$01@@std@@@Halide@@YAXXZ
??$sz@$$T@std@@YAXXZ
??$sz@_N@std@@YAXXZ
??$sz@W4Kind@Halide@@@std@@YAXXZ
halide_malloc
halide_error
_halide_buffer_get_host
halide_pipeline_finalize DATA
main
?f@@YAXXZ
17 changes: 17 additions & 0 deletions test/filter_dll_exports/run.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Runs cmake/FilterDllExports.cmake on input.def and compares against expected.def.
# Usage: cmake -DSCRIPT=<FilterDllExports.cmake> -DDIR=<this dir> -DOUT=<scratch file> -P run.cmake
execute_process(
COMMAND
"${CMAKE_COMMAND}" --log-level=VERBOSE "-DIN=${DIR}/input.def" "-DOUT=${OUT}" -P "${SCRIPT}"
RESULT_VARIABLE rc
)
if (NOT rc EQUAL 0)
message(FATAL_ERROR "FilterDllExports.cmake failed: ${rc}")
endif ()
execute_process(
COMMAND "${CMAKE_COMMAND}" -E compare_files "${DIR}/expected.def" "${OUT}"
RESULT_VARIABLE rc
)
if (NOT rc EQUAL 0)
message(FATAL_ERROR "FilterDllExports.cmake output differs from expected.def")
endif ()
Loading