Skip to content

Commit

Permalink
Add Makefile caching helpers for lazy, expensive evaluations
Browse files Browse the repository at this point in the history
Makefiles natively offer two variable expansion modes: immediate and
deferred.  When expanding variables that require invocations of external
programs (such as `llvm-config`) immediate expansion is almost always
preferred, as it will run the external command once, exactly when the
makefile variable is defined.  Deferred mode, on the other hand, will
expand the variable every time it is used, running the external program
again and again.

When the external program is expensive, this cost can slow down the
build significantly, however when the external program requires some
setup (for instance, when it itself is downloaded through other rules in
the Makefile) it cannot always be immediately expanded.  To address
this, we build a caching layer that allows for deferred expansion, but
once it has been expanded once, the variable is replaced with the result
of running the command, and further hits to the same variable will
return the cached value.  (With the slight caveat that an empty result
will cause the external command to be run again in the future).

As an example usecase, this commit converts our relative path
calculation to use a python script and showcases how to cache this
operation.  This will be used for further JLL stdlib work where the
invocation is much more expensive.
  • Loading branch information
staticfloat committed May 4, 2020
1 parent a645d7f commit ad907d5
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 47 deletions.
53 changes: 43 additions & 10 deletions Make.inc
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,28 @@ WITH_GC_DEBUG_ENV := 0
# Prevent picking up $ARCH from the environment variables
ARCH:=

# We need python for things like BB triplet recognition and relative path computation.
# We don't really care about version, generally, so just find something that works:
PYTHON := "$(shell which python 2>/dev/null || which python3 2>/dev/null || which python2 2>/dev/null || echo not found)"
PYTHON_SYSTEM := $(shell $(PYTHON) -c 'from __future__ import print_function; import platform; print(platform.system())')

# If we're running on Cygwin, but using a native-windows Python, we need to use cygpath -w
ifneq ($(and $(filter $(PYTHON_SYSTEM),Windows),$(findstring CYGWIN,$(shell uname))),)
python_cygpath = `cygpath -w $(1)`
else
python_cygpath = $(1)
endif

# Get a relative path easily
define rel_path
$(shell $(PYTHON) $(call python_cygpath,$(JULIAHOME)/contrib/relative_path.py) $(call python_cygpath,$(1)) $(call python_cygpath,$(2)))
endef

# pick up BUILDROOT from O= if it isn't already set (from recursive make)
ifeq ($(BUILDROOT),)
ifeq ("$(origin O)", "command line")
BUILDROOT := $(abspath $O)
BUILDDIR := $(abspath $(BUILDROOT)/$(shell $(JULIAHOME)/contrib/relative_path.sh $(JULIAHOME) $(SRCDIR)))
BUILDDIR := $(abspath $(BUILDROOT)/$(call rel_path,$(JULIAHOME),$(SRCDIR)))
$(info $(shell printf '\033[32;1mBuilding into $(BUILDROOT)\033[0m')) # use printf to expand the escape sequences
else
BUILDROOT:=$(JULIAHOME)
Expand Down Expand Up @@ -277,16 +294,32 @@ private_libdir := $(libdir)/julia
endif
build_private_libdir := $(build_libdir)/julia

# A helper functions for dealing with lazily-evaluated, expensive operations.. Spinning
# up a python process to, for exaxmple, parse a TOML file is expensive, and we must wait
# until the TOML files are on-disk before we can parse them. This means that we cannot
# use `:=` (since we do not want to evaluate these rules now, we want to evaluate them
# when we use them, so we use `=`) however we also do not want to re-evaluate them
# multiple times. So we define a caching mechanism where the rules are still lazily
# evaluated, but we cache the value such that the second time around we don't have to
# re-evaluate them. Usage example:
#
# EXPENSIVE_OPERATION = $(shell prog args...)
# CACHED_RESULT = $(call hit_cache,EXPENSIVE_OPERATION)
#
# The first time you use `$(CACHED_RESULT)`, it will invoke `$(EXPENSIVE_OPERATION)`,
# but after that point, it will not, unless `$(EXPENSIVE_OPERATION)` evaluated to the
# empty string, in which case it will be re-evaluated.
define hit_cache
$(if $(_CACHE-$(1)),,$(eval _CACHE-$(1) := $($(1))))$(_CACHE-$(1))
endef

# Calculate relative paths to libdir, private_libdir, datarootdir, and sysconfdir
build_libdir_rel := $(shell $(JULIAHOME)/contrib/relative_path.sh $(build_bindir) $(build_libdir))
libdir_rel := $(shell $(JULIAHOME)/contrib/relative_path.sh $(bindir) $(libdir))
build_private_libdir_rel := $(shell $(JULIAHOME)/contrib/relative_path.sh $(build_bindir) $(build_private_libdir))
private_libdir_rel := $(shell $(JULIAHOME)/contrib/relative_path.sh $(bindir) $(private_libdir))
datarootdir_rel := $(shell $(JULIAHOME)/contrib/relative_path.sh $(bindir) $(datarootdir))
libexecdir_rel := $(shell $(JULIAHOME)/contrib/relative_path.sh $(bindir) $(libexecdir))
docdir_rel := $(shell $(JULIAHOME)/contrib/relative_path.sh $(bindir) $(docdir))
sysconfdir_rel := $(shell $(JULIAHOME)/contrib/relative_path.sh $(bindir) $(sysconfdir))
includedir_rel := $(shell $(JULIAHOME)/contrib/relative_path.sh $(bindir) $(includedir))
define cache_rel_path
$(1)_rel_eval = $(call rel_path,$(2),$($(1)))
$(1)_rel = $$(call hit_cache,$(1)_rel_eval)
endef
$(foreach D,libdir private_libdir datarootdir libexecdir docdir sysconfdir includedir,$(eval $(call cache_rel_path,$(D),$(bindir))))
$(foreach D,build_libdir build_private_libdir,$(eval $(call cache_rel_path,$(D),$(build_bindir))))

INSTALL_F := $(JULIAHOME)/contrib/install.sh 644
INSTALL_M := $(JULIAHOME)/contrib/install.sh 755
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ $(BUILDROOT)/doc/_build/html/en/index.html: $(shell find $(BUILDROOT)/base $(BUI

julia-symlink: julia-ui-$(JULIA_BUILD_MODE)
ifeq ($(OS),WINNT)
@echo '@"%~dp0"\'"$(shell $(JULIAHOME)/contrib/relative_path.sh "$(BUILDROOT)" "$(JULIA_EXECUTABLE)" | tr / '\\')" '%*' > $(BUILDROOT)/julia.bat
@echo '@"%~dp0"\'"$$(echo $(call rel_path,$(BUILDROOT),$(JULIA_EXECUTABLE)) | tr / '\\')" '%*' > $(BUILDROOT)/julia.bat
chmod a+x $(BUILDROOT)/julia.bat
else
ifndef JULIA_VAGRANT_BUILD
@ln -sf "$(shell $(JULIAHOME)/contrib/relative_path.sh "$(BUILDROOT)" "$(JULIA_EXECUTABLE)")" $(BUILDROOT)/julia
@ln -sf $(call rel_path,$(BUILDROOT),$(JULIA_EXECUTABLE)) $(BUILDROOT)/julia
endif
endif

Expand Down
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ Multi-threading changes

Build system changes
--------------------
* The build system now contains a pure-make caching system for expanding expensive operations at the latest
possible moment, while still expanding it only once. ([#35626])


New library functions
Expand Down
2 changes: 1 addition & 1 deletion contrib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Installation
|[ julia-config.jl ](https://github.com/JuliaLang/julia/blob/master/contrib/julia-config.jl) | Determines build parameters required by an embedded Julia |
|[ julia.desktop ](https://github.com/JuliaLang/julia/blob/master/contrib/julia.desktop) | GNOME desktop config file |
|[ mac/ ](https://github.com/JuliaLang/julia/blob/master/contrib/mac/) | Mac install files |
|[ relative_path.sh ](https://github.com/JuliaLang/julia/blob/master/contrib/relative_path.sh) | Convert absolute path into relative path script |
|[ relative_path.py ](https://github.com/JuliaLang/julia/blob/master/contrib/relative_path.py) | Convert absolute paths into relative paths |
|[ repackage_system_suitesparse4.make ](https://github.com/JuliaLang/julia/blob/master/contrib/repackage_system_suitesparse4.make) | Links shared libraries from static-library for suitesparse4 |
|[ stringreplace.c ](https://github.com/JuliaLang/julia/blob/master/contrib/stringreplace.c) | Replace strings to hardcoded paths in binaries during `make install` |
|[ travis_fastfail.sh ](https://github.com/JuliaLang/julia/blob/master/contrib/travis_fastfail.sh ) | Checks for queued build tests in Travis |
Expand Down
10 changes: 10 additions & 0 deletions contrib/relative_path.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import sys, os
if len(sys.argv) != 3:
sys.stderr.write("\nrelative_path.py - incomplete arguments: %s\n"%(sys.argv))
sys.exit(1)

# We always use `/` as the path separator, no matter what OS we're running on, since our
# shells and whatnot during the build are all POSIX shells/cygwin. We rely on the build
# system itself to canonicalize to `\` when it needs to, and deal with the shell escaping
# and whatnot at the latest possible moment.
sys.stdout.write(os.path.relpath(sys.argv[2], sys.argv[1]).replace(os.path.sep, '/'))
31 changes: 0 additions & 31 deletions contrib/relative_path.sh

This file was deleted.

4 changes: 2 additions & 2 deletions deps/llvm.mk
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ LLVM_CMAKE += -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_HOST_TRIPL
ifeq ($(USE_POLLY_ACC),1)
LLVM_CMAKE += -DPOLLY_ENABLE_GPGPU_CODEGEN=ON
endif
LLVM_CMAKE += -DLLVM_TOOLS_INSTALL_DIR=$(shell $(JULIAHOME)/contrib/relative_path.sh $(build_prefix) $(build_depsbindir))
LLVM_CMAKE += -DLLVM_UTILS_INSTALL_DIR=$(shell $(JULIAHOME)/contrib/relative_path.sh $(build_prefix) $(build_depsbindir))
LLVM_CMAKE += -DLLVM_TOOLS_INSTALL_DIR=$(call rel_path,$(build_prefix),$(build_depsbindir))
LLVM_CMAKE += -DLLVM_UTILS_INSTALL_DIR=$(call rel_path,$(build_prefix),$(build_depsbindir))
LLVM_CMAKE += -DLLVM_INCLUDE_UTILS=ON -DLLVM_INSTALL_UTILS=ON
LLVM_CMAKE += -DLLVM_BINDINGS_LIST="" -DLLVM_INCLUDE_DOCS=Off -DLLVM_ENABLE_TERMINFO=Off -DHAVE_HISTEDIT_H=Off -DHAVE_LIBEDIT=Off
ifeq ($(LLVM_ASSERTIONS), 1)
Expand Down
2 changes: 1 addition & 1 deletion sysimage.mk
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ BASE_SRCS := $(sort $(shell find $(JULIAHOME)/base -name \*.jl -and -not -name s
$(shell find $(BUILDROOT)/base -name \*.jl -and -not -name sysimg.jl))
STDLIB_SRCS := $(JULIAHOME)/base/sysimg.jl $(shell find $(build_datarootdir)/julia/stdlib/$(VERSDIR)/*/src -name \*.jl) \
$(build_prefix)/manifest/Pkg
RELBUILDROOT := $(shell $(JULIAHOME)/contrib/relative_path.sh "$(JULIAHOME)/base" "$(BUILDROOT)/base/")
RELBUILDROOT := $(call rel_path,$(JULIAHOME)/base,$(BUILDROOT)/base)/ # <-- make sure this always has a trailing slash

$(build_private_libdir)/corecompiler.ji: $(COMPILER_SRCS)
@$(call PRINT_JULIA, cd $(JULIAHOME)/base && \
Expand Down

0 comments on commit ad907d5

Please sign in to comment.