Skip to content
Closed
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
46 changes: 37 additions & 9 deletions contrib/optimized/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ LLVM_MERGEFDATA:=$(STAGE0_BINARIES)merge-fdata
LLVM_CXXFILT:=$(STAGE0_TOOLS)llvm-cxxfilt
LLVM_PROFDATA:=$(STAGE0_TOOLS)llvm-profdata
LLVM_OBJCOPY:=$(STAGE0_TOOLS)llvm-objcopy
LLVM_DLLTOOL:=$(STAGE0_TOOLS)llvm-dlltool

ifeq ($(USE_BOLT),1)
# Stop stage 2 after the libraries that BOLT rewrites, so that building the
Expand Down Expand Up @@ -179,6 +180,8 @@ STAGE0_EXTRA_FLAGS:=
# do that, and otherwise the profile constructor is not executed.
STAGE0_POST = find $(STAGE0_BUILD) -name 'libclang_rt.profile-*.a' -exec $(LLVM_OBJCOPY) --rename-section .ctors=.init_array --rename-section .dtors=.fini_array {} +
STAGE1_PLATFORM_FLAGS:=
# Extra flags for the C++ compiled by stages 1 and 2.
PLATFORM_CXXFLAGS:=
# How a stage's own shared libraries are made findable by the tools built
# during it. 1: that stage's build directory.
STAGE_PATH =
Expand All @@ -204,24 +207,49 @@ LINKER:=$(STAGE0_TOOLS)ld.lld$(EXE)
# MSYS2's GCC links winpthread through its spec file, which is what its libstdc++
# expects; clang only does so for -pthread.
LINKER_FLAGS:=-fuse-ld=lld -pthread
# The ELF-only flags do not apply to PE/COFF.
LINK_ONLY_FLAGS:=
# Apply the export map through a COFF definition for lld's MinGW driver.
# Keep the caller's linker flags, but not the driver flags added by this flow.
PLATFORM_FLAGS:="WIN_LD_USE_DEF=1" "LINK_LDFLAGS=$(LDFLAGS)"
# BinaryBuilder's tools need its compiler support libraries (MSYS2's
# libstdc++ lacks some of their imports), plus libLLVM's compression DLLs.
# Place these beside the tools before running any of them. Skip the ELF
# constructor fixup: COFF's profile runtime registers through .CRT$XCU.
STAGE0_EXTRA_TARGETS:=install-zlib install-csl
STAGE0_EXTRA_FLAGS:=USE_BINARYBUILDER_CSL=1
STAGE0_POST = cp $(STAGE0_BUILD)/usr/bin/*.dll $(STAGE0_TOOLS)
# The stages compile against the MSYS2 sysroot, but link against and ship those
# same BinaryBuilder libraries, which BinaryBuilder-built packages also expect at
# run time. Where the sysroot's headers disagree with them, bind to the latter:
# - libstdc++ implements std::call_once differently; see the header.
PLATFORM_CXXFLAGS = -include $(call native_path,$(abspath $(JULIA_ROOT)/contrib/windows/libstdcxx-call-once.h))
# - winpthreads' headers route nanosleep, clock_gettime, the timed waits etc. to
# 64-bit time variants that BinaryBuilder's winpthreads lacks. On x86-64 those
# are the plain functions, so import them under those names instead, from an
# import library that has to precede the sysroot's in every link. It gets a
# directory of its own, and is linked by name: a path would not survive MSYS2's
# argument conversion into LLVM's CMake flags.
ifeq ($(HOST_ARCH),x86_64)
STAGE0_WINPTHREAD_TIME64:=$(STAGE0_BUILD)/usr/lib/winpthread-time64
WINPTHREAD_TIME64_POST = && mkdir -p $(STAGE0_WINPTHREAD_TIME64) && \
$(LLVM_DLLTOOL) -m i386:x86-64 -d $(JULIA_ROOT)/contrib/windows/winpthread-time64.def -l $(STAGE0_WINPTHREAD_TIME64)/libwinpthread-time64.a
LINK_ONLY_FLAGS = -L$(STAGE0_WINPTHREAD_TIME64) -lwinpthread-time64
else
WINPTHREAD_TIME64_POST =
LINK_ONLY_FLAGS =
endif
# Tools built during a stage have to load the libstdc++ they were linked against,
# so stage 0 also puts it in a directory of its own; see STAGE_PATH.
STAGE0_LIBSTDCXX:=$(STAGE0_BUILD)/usr/libstdcxx
STAGE0_POST = cp $(STAGE0_BUILD)/usr/bin/*.dll $(STAGE0_TOOLS) && \
mkdir -p $(STAGE0_LIBSTDCXX) && cp $(STAGE0_BUILD)/usr/bin/libstdc++-6.dll $(STAGE0_LIBSTDCXX) \
$(WINPTHREAD_TIME64_POST)
# Apply the export map through a COFF definition for lld's MinGW driver.
# Keep the caller's linker flags, but not the driver flags added by this flow.
PLATFORM_FLAGS = "WIN_LD_USE_DEF=1" "LINK_LDFLAGS=$(LDFLAGS) $(LINK_ONLY_FLAGS)"
# Those direct linker invocations also miss the profile runtime that
# -fprofile-generate would otherwise have contributed to the link.
STAGE1_PLATFORM_FLAGS = "WIN_LD_EXTRA_LIBS=$(wildcard $(STAGE0_BUILD)/usr/lib/clang/*/lib/windows/libclang_rt.profile-*.a)"
# Let newly built tools such as llvm-tblgen find the stage's zstd/zlib DLLs.
# Append so the sysroot's compiler support libraries retain PATH precedence.
STAGE_PATH = PATH="$$PATH:$(1)/usr/bin"
# Append so that the sysroot's own programs, e.g. cc1, keep finding its DLLs.
# The exception is libstdc++: the sysroot's programs outside its bin directory
# (which is searched first for those inside it) link libstdc++ statically.
STAGE_PATH = PATH="$(STAGE0_LIBSTDCXX):$$PATH:$(1)/usr/bin"
else
LINKER:=$(STAGE0_TOOLS)ld.lld$(EXE)
LINKER_FLAGS:=-fuse-ld=lld
Expand All @@ -241,7 +269,7 @@ TOOLCHAIN_FLAGS = $\
"AR=$(STAGE0_TOOLS)llvm-ar$(EXE)" $\
"RANLIB=$(STAGE0_TOOLS)llvm-ranlib$(EXE)" $\
"CFLAGS+=$(STAGE_CFLAGS)" $\
"CXXFLAGS+=$(STAGE_CXXFLAGS)" $\
"CXXFLAGS+=$(STAGE_CXXFLAGS) $(PLATFORM_CXXFLAGS)" $\
"LDFLAGS+=$(LINKER_FLAGS) $(LINK_ONLY_FLAGS) $(STAGE_LDFLAGS)" $\
$(PLATFORM_FLAGS)

Expand Down
4 changes: 4 additions & 0 deletions contrib/optimized/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ The flow supports PGO+ThinLTO on macOS and Windows x86-64. On Windows it uses
BinaryBuilder's Clang and lld's MinGW driver with an MSYS2 mingw64 sysroot.
Stage 0 places the matching support DLLs beside the tools; the instrumented
stage supplies Clang's profile runtime to Julia's direct linker invocations.
The stages link against and ship BinaryBuilder's compiler support libraries
rather than the sysroot's, so C++ code is compiled for BinaryBuilder's
`std::call_once` implementation, and winpthreads' 64-bit time functions are
imported under the names its older DLL exports.
The runtime export map is applied through a COFF export definition. A discovery
link identifies exports from the objects and archive members actually used;
the final link exports the matching names and explicitly exported symbols.
Expand Down
22 changes: 17 additions & 5 deletions contrib/optimized/test_makefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@
if p.exists():
sys.exit('configure called on existing directory')
(p / 'deps').mkdir(parents=True)
recipe = 'all julia-deps julia-src-release julia-symlink julia-libccalltest julia-libccalllazyfoo julia-libccalllazybar julia-libllvmcalltest:\n\t@python3 ' + str(root / 'helper.py') + ' build $@ "$(CFLAGS)" "$(LDFLAGS)" "$(JULIA_CPU_TARGET)" "$(USE_BINARYBUILDER_LLVM)" "$(LD)" "$(WIN_LD_USE_DEF)" "$(WIN_LD_EXTRA_LIBS)" "$(LINK_LDFLAGS)"\n'
recipe = 'all julia-deps julia-src-release julia-symlink julia-libccalltest julia-libccalllazyfoo julia-libccalllazybar julia-libllvmcalltest:\n\t@python3 ' + str(root / 'helper.py') + ' build $@ "$(CFLAGS)" "$(LDFLAGS)" "$(JULIA_CPU_TARGET)" "$(USE_BINARYBUILDER_LLVM)" "$(LD)" "$(WIN_LD_USE_DEF)" "$(WIN_LD_EXTRA_LIBS)" "$(LINK_LDFLAGS)" "$(CXXFLAGS)"\n'
(p / 'Makefile').write_text(recipe)
(p / 'deps/Makefile').write_text('%:\n\t@python3 ' + str(root / 'helper.py') + ' install $@ "$(USE_BINARYBUILDER_LLVM)" "$(OS)" "$(USE_BINARYBUILDER_CSL)"\n')
elif args[0] == 'install' and args[3] == 'WINNT':
stage = pathlib.Path.cwd().parent
(stage / 'usr/bin').mkdir(parents=True, exist_ok=True)
(stage / 'usr/tools').mkdir(parents=True, exist_ok=True)
(stage / 'usr/bin/support.dll').write_text('support')
(stage / 'usr/bin/libstdc++-6.dll').write_text('libstdc++')
runtime = stage / 'usr/lib/clang/22/lib/windows'
runtime.mkdir(parents=True, exist_ok=True)
(runtime / 'libclang_rt.profile-x86_64.a').write_text('runtime')
Expand All @@ -57,6 +58,9 @@
elif args[0] == 'fdata':
assert all('.merged.' not in x and pathlib.Path(x).is_file() for x in args[1:]), args
print(''.join(pathlib.Path(x).read_text() for x in args[1:]), end='')
elif args[0] == 'dlltool':
assert args[1:3] == ['-m', 'i386:x86-64'] and args[4].endswith('contrib/windows/winpthread-time64.def'), args
pathlib.Path(args[args.index('-l') + 1]).write_text('implib')
elif args[0] == 'bolt':
if os.getenv('FAIL_BOLT'):
sys.exit(1)
Expand All @@ -74,13 +78,13 @@ def setUp(self):
shutil.copyfile(SOURCE, self.flow / 'Makefile')
(self.root / 'helper.py').write_text(HELPER)
(self.root / 'Makefile').write_text('OS := Linux\nARCH := x86_64\nBINARY := 64\nBUILD_MACHINE := test-triple\nprint-%:\n\t@echo "$*=$($*)"\nconfigure:\n\t@python3 helper.py configure $(O)\n')
for name, mode in [('profdata', 'merge'), ('mergefdata', 'fdata'), ('bolt-tool', 'bolt')]:
for name, mode in [('profdata', 'merge'), ('mergefdata', 'fdata'), ('bolt-tool', 'bolt'), ('dlltool', 'dlltool')]:
p = self.root / name
# profdata already gets 'merge' as the first argument.
prefix = '' if name == 'profdata' else mode + ' '
p.write_text('#!/bin/sh\nexec python3 ' + str(self.root / 'helper.py') + ' ' + prefix + '"$@"\n')
p.chmod(0o755)
self.args = ['LLVM_PROFDATA=' + str(self.root / 'profdata'), 'LLVM_MERGEFDATA=' + str(self.root / 'mergefdata'), 'LLVM_BOLT=' + str(self.root / 'bolt-tool')]
self.args = ['LLVM_PROFDATA=' + str(self.root / 'profdata'), 'LLVM_MERGEFDATA=' + str(self.root / 'mergefdata'), 'LLVM_BOLT=' + str(self.root / 'bolt-tool'), 'LLVM_DLLTOOL=' + str(self.root / 'dlltool')]

def make(self, *args, ok=True, env=None):
p = subprocess.run(['make', '--no-print-directory', '-s', '-j4', '-C', str(self.flow), *self.args, *args], text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env)
Expand Down Expand Up @@ -116,6 +120,7 @@ def test_matrix(self):
self.assertEqual('-flto=thin' in a[2], lto == '1')
self.assertEqual(a[4], 'generic;haswell')
self.assertEqual(a[5], '0')
self.assertNotIn('-include', a[10])
self.assertTrue(all(a[2] == '1' for d, a in events if a[0] == 'install'))
self.assertEqual(any(a[:2] == ['install', 'install-BOLT'] for d, a in events), bolt == '1')
if bolt == '1':
Expand Down Expand Up @@ -148,16 +153,23 @@ def test_windows_stages(self):
self.assertFalse(any(a[1] == 'install-BOLT' for a in installs))
stage0 = self.flow / 'toolchain'
self.assertEqual((stage0 / 'usr/tools/support.dll').read_text(), 'support')
self.assertEqual([p.name for p in (stage0 / 'usr/libstdcxx').iterdir()], ['libstdc++-6.dll'])
self.assertEqual((stage0 / 'usr/lib/winpthread-time64/libwinpthread-time64.a').read_text(), 'implib')
time64 = '-L' + str(stage0 / 'usr/lib/winpthread-time64') + ' -lwinpthread-time64'
for directory, args in events:
if args[0] != 'build':
continue
self.assertEqual(args[6], str(stage0 / 'usr/tools/ld.lld.exe'))
self.assertEqual(args[7], '1')
self.assertEqual(args[9], '--no-insert-timestamp')
# Both kinds of link take the time64 import library, but compiles do not.
self.assertEqual(args[9], '--no-insert-timestamp ' + time64)
self.assertIn(time64, args[3])
self.assertNotIn('winpthread-time64', args[2] + args[10])
self.assertIn('-pthread', args[3])
self.assertNotIn('--undefined-version', args[3])
self.assertNotIn('--emit-relocs', args[3])
self.assertEqual(args[-1]['PATH'], env['PATH'] + ':' + str(self.flow / directory / 'usr/bin'))
self.assertEqual(args[-1]['PATH'], ':'.join([str(stage0 / 'usr/libstdcxx'), env['PATH'], str(self.flow / directory / 'usr/bin')]))
self.assertIn('-include C:' + str(self.root / 'contrib/windows/libstdcxx-call-once.h'), args[10])
if directory == 'pgo-instrumented.build':
self.assertEqual(args[8], str(stage0 / 'usr/lib/clang/22/lib/windows/libclang_rt.profile-x86_64.a'))
self.assertNotIn('-flto', args[2])
Expand Down
20 changes: 20 additions & 0 deletions contrib/windows/libstdcxx-call-once.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Make libstdc++'s headers compile std::call_once for the libstdc++ that Julia
// ships on Windows, BinaryBuilder's (from CompilerSupportLibraries_jll), when
// the headers themselves come from a differently configured toolchain such as
// MSYS2's. Pass it with -include, so that it runs before any other header.
//
// BinaryBuilder's libstdc++ is configured without thread-local storage, and
// implements std::call_once through a global functor and mutex
// (std::__once_functor, std::__get_once_mutex(), ...). MSYS2's is configured
// with TLS, and its headers instead call accessors for thread-local state
// (std::__get_once_callable() and std::__get_once_call() since GCC 16), which
// BinaryBuilder's libstdc++ does not export. Nor does MSYS2's libstdc++ export
// the former, so shipping it instead would break the BinaryBuilder-built C++
// libraries that packages load. _GLIBCXX_HAVE_TLS only selects between these
// two implementations in <mutex>.
#if defined(__cplusplus) && defined(__has_include)
#if __has_include(<bits/c++config.h>)
#include <bits/c++config.h>
#undef _GLIBCXX_HAVE_TLS
#endif
#endif
17 changes: 17 additions & 0 deletions contrib/windows/winpthread-time64.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
; Import the 64-bit time variants that newer winpthreads headers call under the
; names older winpthreads DLLs export. Only valid where time_t is 64 bits wide,
; i.e. x86-64. See contrib/optimized/Makefile.
LIBRARY "libwinpthread-1.dll"
EXPORTS
clock_getres64 == clock_getres
clock_gettime64 == clock_gettime
clock_nanosleep64 == clock_nanosleep
clock_settime64 == clock_settime
nanosleep64 == nanosleep
pthread_cond_timedwait64 == pthread_cond_timedwait
pthread_cond_timedwait64_relative_np == pthread_cond_timedwait_relative_np
pthread_delay64_np == pthread_delay_np
pthread_mutex_timedlock64 == pthread_mutex_timedlock
pthread_rwlock_timedrdlock64 == pthread_rwlock_timedrdlock
pthread_rwlock_timedwrlock64 == pthread_rwlock_timedwrlock
sem_timedwait64 == sem_timedwait
Loading