From 90934ff7306efdaa66a272d472c6bcdb94448d34 Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Sat, 19 Nov 2022 18:56:42 -0300 Subject: [PATCH 001/387] Initialize padding in datatype layout (#47640) We use that memory for hashing it, so we need to ensure it is set to zero. --- src/datatype.c | 2 ++ src/julia.h | 1 + 2 files changed, 3 insertions(+) diff --git a/src/datatype.c b/src/datatype.c index 0dcae8a6dec983..9d22685473e075 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -219,6 +219,7 @@ static jl_datatype_layout_t *jl_get_layout(uint32_t sz, flddesc->alignment = alignment; flddesc->haspadding = haspadding; flddesc->fielddesc_type = fielddesc_type; + flddesc->padding = 0; flddesc->npointers = npointers; flddesc->first_ptr = (npointers > 0 ? pointers[0] : -1); @@ -815,6 +816,7 @@ JL_DLLEXPORT jl_datatype_t * jl_new_foreign_type(jl_sym_t *name, layout->haspadding = 1; layout->npointers = haspointers; layout->fielddesc_type = 3; + layout->padding = 0; jl_fielddescdyn_t * desc = (jl_fielddescdyn_t *) ((char *)layout + sizeof(*layout)); desc->markfunc = markfunc; diff --git a/src/julia.h b/src/julia.h index cc850757730922..8e17fdc1edf175 100644 --- a/src/julia.h +++ b/src/julia.h @@ -523,6 +523,7 @@ typedef struct { uint16_t alignment; // strictest alignment over all fields uint16_t haspadding : 1; // has internal undefined bytes uint16_t fielddesc_type : 2; // 0 -> 8, 1 -> 16, 2 -> 32, 3 -> foreign type + uint16_t padding : 13; // union { // jl_fielddesc8_t field8[nfields]; // jl_fielddesc16_t field16[nfields]; From bba41d41319aa898373784438bd38873eab1da41 Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Sun, 20 Nov 2022 14:45:20 -0500 Subject: [PATCH 002/387] Turn on Intel jitevents by default on Linux (#47586) --- Make.inc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Make.inc b/Make.inc index a6c17ccd801389..40caeffb9d313e 100644 --- a/Make.inc +++ b/Make.inc @@ -77,7 +77,7 @@ JULIA_THREADS := 1 # Set to 1 to enable profiling with OProfile USE_OPROFILE_JITEVENTS ?= 0 -# USE_PERF_JITEVENTS defined below since default is OS specific +# USE_PERF_JITEVENTS, and USE_INTEL_JITEVENTS defined below since default is OS specific # assume we don't have LIBSSP support in our compiler, will enable later if likely true HAVE_SSP := 0 @@ -442,8 +442,10 @@ endif # Set to 1 to enable profiling with perf ifeq ("$(OS)", "Linux") USE_PERF_JITEVENTS ?= 1 +USE_INTEL_JITEVENTS ?= 1 else USE_PERF_JITEVENTS ?= 0 +USE_INTEL_JITEVENTS ?= 0 endif JULIACODEGEN := LLVM From 67b8ac020833a14d98e8a147b8aae5caf2288a41 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Sun, 20 Nov 2022 17:24:52 -0500 Subject: [PATCH 003/387] build: separate stdlib manifests by version (#47596) We install these by version, so it helps a lot if they reflect that in the manifest name. Also some other mild cleanup. --- Make.inc | 17 ---------------- Makefile | 38 ++++++++++++++++++++++++----------- cli/Makefile | 3 +-- deps/tools/common.mk | 15 +++++++++----- deps/tools/git-external.mk | 3 ++- deps/tools/stdlib-external.mk | 9 +++++++-- deps/tools/uninstallers.mk | 1 + stdlib/Makefile | 16 +++++++++------ sysimage.mk | 4 ++-- 9 files changed, 59 insertions(+), 47 deletions(-) diff --git a/Make.inc b/Make.inc index 40caeffb9d313e..24f5964e9dcc0a 100644 --- a/Make.inc +++ b/Make.inc @@ -135,23 +135,6 @@ endif export BUILDROOT unexport O -# Make sure the user didn't try to specify a path that will confuse the shell / make -METACHARACTERS := ][?*{}() $$%:;&|!\#,\\`\": -ifneq (,$(findstring ',$(value BUILDROOT))) -$(error cowardly refusing to build into directory with a single-quote in the path) -endif -ifneq (,$(findstring ',$(value JULIAHOME))) -$(error cowardly refusing to build from source directory with a single-quote in the path) -endif -ifneq (,$(shell echo '$(value BUILDROOT)' | grep '[$(METACHARACTERS)]')) -$(error cowardly refusing to build into directory with a shell-metacharacter in the path\ - (got: $(value BUILDROOT))) -endif -ifneq (,$(shell echo '$(value JULIAHOME)' | grep '[$(METACHARACTERS)]')) -$(error cowardly refusing to build from source directory with a shell-metacharacter in the path\ - (got: $(value JULIAHOME))) -endif - # we include twice to pickup user definitions better # include from JULIAHOME first so that BUILDROOT can override MAYBE_HOST := diff --git a/Makefile b/Makefile index 15a8cd1c855f91..3f81263b26ab2e 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,23 @@ include $(JULIAHOME)/Make.inc # import LLVM_SHARED_LIB_NAME include $(JULIAHOME)/deps/llvm-ver.make +# Make sure the user didn't try to build in a path that will confuse the shell or make +METACHARACTERS := [][?*{}() $$%:;&|!\#,\\`\":]\|/\./\|/\.\./ +ifneq (,$(findstring ',$(value BUILDROOT))) +$(error cowardly refusing to build into directory with a single-quote in the path) +endif +ifneq (,$(findstring ',$(value JULIAHOME))) +$(error cowardly refusing to build from source directory with a single-quote in the path) +endif +ifneq (,$(shell echo '$(value BUILDROOT)/' | grep '$(METACHARACTERS)')) +$(error cowardly refusing to build into directory with a shell-metacharacter in the path\ + (got: $(value BUILDROOT))) +endif +ifneq (,$(shell echo '$(value JULIAHOME)/' | grep '$(METACHARACTERS)')) +$(error cowardly refusing to build from source directory with a shell-metacharacter in the path\ + (got: $(value JULIAHOME))) +endif + VERSDIR := v`cut -d. -f1-2 < $(JULIAHOME)/VERSION` default: $(JULIA_BUILD_MODE) # contains either "debug" or "release" @@ -13,7 +30,7 @@ DIRS := $(sort $(build_bindir) $(build_depsbindir) $(build_libdir) $(build_priva ifneq ($(BUILDROOT),$(JULIAHOME)) BUILDDIRS := $(BUILDROOT) $(addprefix $(BUILDROOT)/,base src src/flisp src/support src/clangsa cli doc deps stdlib test test/clangsa test/embedding test/llvmpasses) BUILDDIRMAKE := $(addsuffix /Makefile,$(BUILDDIRS)) $(BUILDROOT)/sysimage.mk -DIRS := $(DIRS) $(BUILDDIRS) +DIRS += $(BUILDDIRS) $(BUILDDIRMAKE): | $(BUILDDIRS) @# add Makefiles to the build directories for convenience (pointing back to the source location of each) @echo '# -- This file is automatically generated in julia/Makefile -- #' > $@ @@ -44,10 +61,6 @@ $(foreach link,base $(JULIAHOME)/test,$(eval $(call symlink_target,$(link),$$(bu julia_flisp.boot.inc.phony: julia-deps @$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT)/src julia_flisp.boot.inc.phony -# Build the HTML docs (skipped if already exists, notably in tarballs) -$(BUILDROOT)/doc/_build/html/en/index.html: $(shell find $(BUILDROOT)/base $(BUILDROOT)/doc \( -path $(BUILDROOT)/doc/_build -o -path $(BUILDROOT)/doc/deps -o -name *_constants.jl -o -name *_h.jl -o -name version_git.jl \) -prune -o -type f -print) - @$(MAKE) docs - julia-symlink: julia-cli-$(JULIA_BUILD_MODE) ifeq ($(OS),WINNT) echo '@"%~dp0/'"$$(echo '$(call rel_path,$(BUILDROOT),$(JULIA_EXECUTABLE))')"'" %*' | tr / '\\' > $(BUILDROOT)/julia.bat @@ -236,7 +249,7 @@ define stringreplace endef -install: $(build_depsbindir)/stringreplace $(BUILDROOT)/doc/_build/html/en/index.html +install: $(build_depsbindir)/stringreplace docs @$(MAKE) $(QUIET_MAKE) $(JULIA_BUILD_MODE) @for subdir in $(bindir) $(datarootdir)/julia/stdlib/$(VERSDIR) $(docdir) $(man1dir) $(includedir)/julia $(libdir) $(private_libdir) $(sysconfdir) $(libexecdir); do \ mkdir -p $(DESTDIR)$$subdir; \ @@ -484,7 +497,7 @@ app: darwinframework: $(MAKE) -C $(JULIAHOME)/contrib/mac/framework -light-source-dist.tmp: $(BUILDROOT)/doc/_build/html/en/index.html +light-source-dist.tmp: docs ifneq ($(BUILDROOT),$(JULIAHOME)) $(error make light-source-dist does not work in out-of-tree builds) endif @@ -561,12 +574,13 @@ distcleanall: cleanall @-$(MAKE) -C $(BUILDROOT)/deps distcleanall @-$(MAKE) -C $(BUILDROOT)/doc cleanall -.PHONY: default debug release check-whitespace release-candidate \ +.FORCE: +.PHONY: .FORCE default debug release check-whitespace release-candidate \ julia-debug julia-release julia-stdlib julia-deps julia-deps-libs \ julia-cli-release julia-cli-debug julia-src-release julia-src-debug \ julia-symlink julia-base julia-sysimg julia-sysimg-ji julia-sysimg-release julia-sysimg-debug \ - test testall testall1 test test-* test-revise-* \ - clean distcleanall cleanall clean-* \ + test testall testall1 test \ + clean distcleanall cleanall $(CLEAN_TARGETS) \ run-julia run-julia-debug run-julia-release run \ install binary-dist light-source-dist.tmp light-source-dist \ dist full-source-dist source-dist @@ -583,12 +597,12 @@ testall: check-whitespace $(JULIA_BUILD_MODE) testall1: check-whitespace $(JULIA_BUILD_MODE) @env JULIA_CPU_THREADS=1 $(MAKE) $(QUIET_MAKE) -C $(BUILDROOT)/test all JULIA_BUILD_MODE=$(JULIA_BUILD_MODE) -test-%: check-whitespace $(JULIA_BUILD_MODE) +test-%: check-whitespace $(JULIA_BUILD_MODE) .FORCE @([ $$(( $$(date +%s) - $$(date -r $(build_private_libdir)/sys.$(SHLIB_EXT) +%s) )) -le 100 ] && \ printf '\033[93m HINT The system image was recently rebuilt. Are you aware of the test-revise-* targets? See CONTRIBUTING.md. \033[0m\n') || true @$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT)/test $* JULIA_BUILD_MODE=$(JULIA_BUILD_MODE) -test-revise-%: +test-revise-%: .FORCE @$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT)/test revise-$* JULIA_BUILD_MODE=$(JULIA_BUILD_MODE) # download target for some hardcoded windows dependencies diff --git a/cli/Makefile b/cli/Makefile index 58c1f82f48662b..dfe7b594ee46eb 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -70,8 +70,7 @@ dump-trampolines: $(SRCDIR)/trampolines/trampolines_$(ARCH).S $(CC) $(SHIPFLAGS) $(LOADER_CFLAGS) $< -S | sed -E 's/ ((%%)|;) /\n/g' | sed -E 's/.global/\n.global/g' DIRS = $(build_bindir) $(build_libdir) -$(DIRS): - @mkdir -p $@ +$(foreach dir,$(DIRS),$(eval $(call dir_target,$(dir)))) ifeq ($(OS),WINNT) $(BUILDDIR)/julia_res.o: $(JULIAHOME)/contrib/windows/julia.rc $(JULIAHOME)/VERSION diff --git a/deps/tools/common.mk b/deps/tools/common.mk index b09786682b941f..c19886114c14e2 100644 --- a/deps/tools/common.mk +++ b/deps/tools/common.mk @@ -107,8 +107,8 @@ endif DIRS := $(sort $(build_bindir) $(build_depsbindir) $(build_libdir) $(build_includedir) $(build_sysconfdir) $(build_datarootdir) $(build_staging) $(build_prefix)/manifest) $(foreach dir,$(DIRS),$(eval $(call dir_target,$(dir)))) - $(build_prefix): | $(DIRS) + $(eval $(call dir_target,$(SRCCACHE))) @@ -174,6 +174,7 @@ $$(build_prefix)/manifest/$(strip $1): $$(build_staging)/$2.tar | $(build_prefix $(UNTAR) $$< -C $$(build_prefix) $6 echo '$$(UNINSTALL_$(strip $1))' > $$@ +.PHONY: $(addsuffix -$(strip $1),stage install distclean uninstall reinstall) endef define staged-uninstaller @@ -192,14 +193,18 @@ endef define symlink_install # (target-name, rel-from, abs-to) clean-$1: uninstall-$1 install-$1: $$(build_prefix)/manifest/$1 -reinstall-$1: install-$1 +reinstall-$1: + +$$(MAKE) uninstall-$1 + +$$(MAKE) stage-$1 + +$$(MAKE) install-$1 +.PHONY: $(addsuffix -$1,clean install reinstall) UNINSTALL_$(strip $1) := $2 symlink-uninstaller $3 -$$(build_prefix)/manifest/$1: $$(BUILDDIR)/$2/build-compiled | $3 $$(build_prefix)/manifest +$$(build_prefix)/manifest/$1: $$(BUILDDIR)/$2/build-compiled | $$(abspath $$(dir $3/$1)) $$(abspath $$(dir $$(build_prefix)/manifest/$1)) -+[ ! \( -e $3/$1 -o -h $3/$1 \) ] || $$(MAKE) uninstall-$1 ifeq ($$(BUILD_OS), WINNT) - cmd //C mklink //J $$(call mingw_to_dos,$3/$1,cd $3 &&) $$(call mingw_to_dos,$$(BUILDDIR)/$2,) + cmd //C mklink //J $$(call mingw_to_dos,$3/$1,cd $3/$(dir $1) &&) $$(call mingw_to_dos,$$(BUILDDIR)/$2,) else ifneq (,$$(findstring CYGWIN,$$(BUILD_OS))) cmd /C mklink /J $$(call cygpath_w,$3/$1) $$(call cygpath_w,$$(BUILDDIR)/$2) else ifdef JULIA_VAGRANT_BUILD @@ -213,7 +218,7 @@ endef define symlink-uninstaller uninstall-$1: ifeq ($$(BUILD_OS), WINNT) - -cmd //C rmdir $$(call mingw_to_dos,$3/$1,cd $3 &&) + -cmd //C rmdir $$(call mingw_to_dos,$3/$1,cd $3/$(dir $1) &&) else rm -rf $3/$1 endif diff --git a/deps/tools/git-external.mk b/deps/tools/git-external.mk index 65b40b87ee9376..cf1610ac1bf5d7 100644 --- a/deps/tools/git-external.mk +++ b/deps/tools/git-external.mk @@ -68,11 +68,12 @@ $5/$$($2_SRC_DIR)/source-extracted: $$($2_SRC_FILE) $(TAR) -C $$(dir $$@) --strip-components 1 -xf $$< echo 1 > $$@ -checksum-$(1): $$($2_SRC_FILE) +checksum-$1: $$($2_SRC_FILE) $$(JLCHECKSUM) $$< endif # DEPS_GIT $$(build_prefix)/manifest/$1: $$(SRCDIR)/$1.version # make the manifest stale if the version file is touched (causing re-install for compliant targets) distclean-$1: rm -rf $5/$$($2_SRC_DIR) $$($2_SRC_FILE) $$(BUILDDIR)/$$($2_SRC_DIR) +.PHONY: $(addsuffix -$1,checksum distclean) endef diff --git a/deps/tools/stdlib-external.mk b/deps/tools/stdlib-external.mk index 60f50b56ee2e0f..0a99111605a45c 100644 --- a/deps/tools/stdlib-external.mk +++ b/deps/tools/stdlib-external.mk @@ -16,12 +16,17 @@ $$(eval $$(call git-external,$1,$2,,,$$(BUILDDIR))) $$(BUILDDIR)/$$($2_SRC_DIR)/build-compiled: $$(BUILDDIR)/$$($2_SRC_DIR)/source-extracted @# no build steps echo 1 > $$@ -$$(eval $$(call symlink_install,$1,$$$$($2_SRC_DIR),$$$$(build_datarootdir)/julia/stdlib/$$$$(VERSDIR))) +$$(eval $$(call symlink_install,$$$$(VERSDIR)/$1,$$$$($2_SRC_DIR),$$$$(build_datarootdir)/julia/stdlib)) clean-$1: -rm -f $$(BUILDDIR)/$$($2_SRC_DIR)/build-compiled get-$1: $$($2_SRC_FILE) extract-$1: $$(BUILDDIR)/$$($2_SRC_DIR)/source-extracted configure-$1: extract-$1 compile-$1: $$(BUILDDIR)/$$($2_SRC_DIR)/build-compiled - +install-$1: install-$$(VERSDIR)/$1 +uninstall-$1: uninstall-$$(VERSDIR)/$1 +reinstall-$1: reinstall-$$(VERSDIR)/$1 +version-check-$1: version-check-$$(VERSDIR)/$1 +clean-$1: clean-$$(VERSDIR)/$1 +.PHONY: $(addsuffix -$1,get extract configure compile install uninstall reinstall clean) endef diff --git a/deps/tools/uninstallers.mk b/deps/tools/uninstallers.mk index 48387914643db0..0051786ed1d0a9 100644 --- a/deps/tools/uninstallers.mk +++ b/deps/tools/uninstallers.mk @@ -17,6 +17,7 @@ else uninstall-$1: @echo "skipping uninstall: $1 not installed" endif +.PHONY: uninstall-$1 endef $(foreach dep,$(DEP_LIBS_STAGED_ALL),$(eval $(call define-uninstaller,$(dep)))) diff --git a/stdlib/Makefile b/stdlib/Makefile index 7957520c31ea3b..427bf7fe29ec7a 100644 --- a/stdlib/Makefile +++ b/stdlib/Makefile @@ -14,9 +14,8 @@ include $(JULIAHOME)/deps/*.version VERSDIR := v$(shell cut -d. -f1-2 < $(JULIAHOME)/VERSION) - -$(build_datarootdir)/julia/stdlib/$(VERSDIR): - mkdir -p $@ +DIRS := $(build_datarootdir)/julia/stdlib/$(VERSDIR) $(build_prefix)/manifest/$(VERSDIR) +$(foreach dir,$(DIRS),$(eval $(call dir_target,$(dir)))) JLLS = DSFMT GMP CURL LIBGIT2 LLVM LIBSSH2 LIBUV MBEDTLS MPFR NGHTTP2 \ BLASTRAMPOLINE OPENBLAS OPENLIBM P7ZIP PCRE LIBSUITESPARSE ZLIB \ @@ -43,14 +42,19 @@ $(foreach jll,$(JLLS),$(eval $(call download-artifacts-toml,$(jll)))) STDLIBS = Artifacts Base64 CRC32c Dates Distributed FileWatching \ Future InteractiveUtils LazyArtifacts Libdl LibGit2 LinearAlgebra Logging \ - Markdown Mmap Printf Profile Random REPL Serialization SHA \ - SharedArrays Sockets SparseArrays SuiteSparse Test TOML Unicode UUIDs \ + Markdown Mmap Printf Profile Random REPL Serialization \ + SharedArrays Sockets Test TOML Unicode UUIDs \ $(JLL_NAMES) STDLIBS_EXT = Pkg Statistics LibCURL Downloads ArgTools Tar NetworkOptions SuiteSparse SparseArrays SHA $(foreach module, $(STDLIBS_EXT), $(eval $(call stdlib-external,$(module),$(shell echo $(module) | tr a-z A-Z)))) +ifneq ($(filter $(STDLIBS),$(STDLIBS_EXT)),) +$(error ERROR duplicated STDLIBS in list) +endif + + # Generate symlinks to all stdlibs at usr/share/julia/stdlib/vX.Y/ $(foreach module, $(STDLIBS), $(eval $(call symlink_target,$$(JULIAHOME)/stdlib/$(module),$$(build_datarootdir)/julia/stdlib/$$(VERSDIR),$(module)))) @@ -68,5 +72,5 @@ clean: $(addprefix clean-, $(STDLIBS_EXT)) $(CLEAN_TARGETS) extstdlibclean distclean: $(addprefix distclean-, $(STDLIBS_EXT)) clean checksumall: $(addprefix checksum-, $(STDLIBS_EXT)) -DEP_LIBS_STAGED_ALL := $(STDLIBS_EXT) +DEP_LIBS_STAGED_ALL := $(addprefix $(VERSDIR)/,$(STDLIBS_EXT)) include $(JULIAHOME)/deps/tools/uninstallers.mk diff --git a/sysimage.mk b/sysimage.mk index 2d154672d81306..b426a74454b1d7 100644 --- a/sysimage.mk +++ b/sysimage.mk @@ -10,7 +10,7 @@ sysimg-bc: $(build_private_libdir)/sys-bc.a sysimg-release: $(build_private_libdir)/sys.$(SHLIB_EXT) sysimg-debug: $(build_private_libdir)/sys-debug.$(SHLIB_EXT) -VERSDIR := v`cut -d. -f1-2 < $(JULIAHOME)/VERSION` +VERSDIR := v$(shell cut -d. -f1-2 < $(JULIAHOME)/VERSION) $(build_private_libdir)/%.$(SHLIB_EXT): $(build_private_libdir)/%-o.a @$(call PRINT_LINK, $(CXX) $(LDFLAGS) -shared $(fPIC) -L$(build_private_libdir) -L$(build_libdir) -L$(build_shlibdir) -o $@ \ @@ -54,7 +54,7 @@ COMPILER_SRCS += $(shell find $(JULIAHOME)/base/compiler -name \*.jl) BASE_SRCS := $(sort $(shell find $(JULIAHOME)/base -name \*.jl -and -not -name sysimg.jl) \ $(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 + $(wildcard $(build_prefix)/manifest/$(VERSDIR)/*) RELBUILDROOT := $(call rel_path,$(JULIAHOME)/base,$(BUILDROOT)/base)/ # <-- make sure this always has a trailing slash $(build_private_libdir)/corecompiler.ji: $(COMPILER_SRCS) From c9eccfc1e863bcbf7ef3be55403c5bcd60a1494e Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sun, 20 Nov 2022 23:16:40 -0500 Subject: [PATCH 004/387] Algorithmic improvements for LateLowerGC pass (#47634) On a particularly pathological case I'm looking at (~100k calls, each a safepoint, similar number of allocations), I'm seeing LateLowerGC pass take the majority of the middle end time (around 80s out of 100s). This PR goes through and improves some of the algorithmics and data structure choices and gives a roughly 10x improvement on this particular pathological case (down to about 7.6s on my machine). That still represents about 1/3 of the total middle end time, which I'm not happy about, but perhaps that is reasonable given how pathological my particular test case is. For comparison, register allocation (which needs to solve a somewhat similar problem) on this IR takes about 20s. --- src/llvm-late-gc-lowering.cpp | 257 ++++++++++++++++++--------------- test/llvmpasses/refinements.ll | 3 +- 2 files changed, 139 insertions(+), 121 deletions(-) diff --git a/src/llvm-late-gc-lowering.cpp b/src/llvm-late-gc-lowering.cpp index eaba9c7b10d98f..08376426b855dd 100644 --- a/src/llvm-late-gc-lowering.cpp +++ b/src/llvm-late-gc-lowering.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -21,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -225,15 +227,20 @@ using namespace llvm; simply sink the alloca into the GCFrame. */ +// 4096 bits == 64 words (64 bit words). Larger bit numbers are faster and doing something +// substantially smaller here doesn't actually save much memory because of malloc overhead. +// Too large is bad also though - 4096 was found to be a reasonable middle ground. +using LargeSparseBitVector = SparseBitVector<4096>; + struct BBState { // Uses in this BB // These do not get updated after local analysis - BitVector Defs; - BitVector PhiOuts; - BitVector UpExposedUses; + LargeSparseBitVector Defs; + LargeSparseBitVector PhiOuts; + LargeSparseBitVector UpExposedUses; // These get updated during dataflow - BitVector LiveIn; - BitVector LiveOut; + LargeSparseBitVector LiveIn; + LargeSparseBitVector LiveOut; std::vector Safepoints; int TopmostSafepoint = -1; bool HasSafepoint = false; @@ -257,9 +264,9 @@ struct State { std::map ReversePtrNumbering; // Neighbors in the coloring interference graph. I.e. for each value, the // indices of other values that are used simultaneously at some safe point. - std::vector> Neighbors; + std::vector Neighbors; // The result of the local analysis - std::map BBStates; + std::map BBStates; // Refinement map. If all of the values are rooted // (-1 means an externally rooted value and -2 means a globally/permanently rooted value), @@ -289,7 +296,7 @@ struct State { std::vector ReturnsTwice; // The set of values live at a particular safepoint - std::vector LiveSets; + std::vector< LargeSparseBitVector > LiveSets; // Those values that - if live out from our parent basic block - are live // at this safepoint. std::vector> LiveIfLiveOut; @@ -333,7 +340,7 @@ struct LateLowerGCFrame: private JuliaPassContext { CallInst *pgcstack; void MaybeNoteDef(State &S, BBState &BBS, Value *Def, const std::vector &SafepointsSoFar, SmallVector &&RefinedPtr = SmallVector()); - void NoteUse(State &S, BBState &BBS, Value *V, BitVector &Uses); + void NoteUse(State &S, BBState &BBS, Value *V, LargeSparseBitVector &Uses); void NoteUse(State &S, BBState &BBS, Value *V) { NoteUse(S, BBS, V, BBS.UpExposedUses); } @@ -363,7 +370,7 @@ struct LateLowerGCFrame: private JuliaPassContext { void NoteUseChain(State &S, BBState &BBS, User *TheUser); SmallVector GetPHIRefinements(PHINode *phi, State &S); void FixUpRefinements(ArrayRef PHINumbers, State &S); - void RefineLiveSet(BitVector &LS, State &S, const std::vector &CalleeRoots); + void RefineLiveSet(LargeSparseBitVector &LS, State &S, const std::vector &CalleeRoots); Value *EmitTagPtr(IRBuilder<> &builder, Type *T, Value *V); Value *EmitLoadTag(IRBuilder<> &builder, Value *V); }; @@ -996,11 +1003,17 @@ std::vector LateLowerGCFrame::NumberAll(State &S, Value *V) { static void MaybeResize(BBState &BBS, unsigned Idx) { + /* if (BBS.Defs.size() <= Idx) { BBS.Defs.resize(Idx + 1); BBS.UpExposedUses.resize(Idx + 1); BBS.PhiOuts.resize(Idx + 1); } + */ +} + +static bool HasBitSet(const LargeSparseBitVector &BV, unsigned Bit) { + return BV.test(Bit); } static bool HasBitSet(const BitVector &BV, unsigned Bit) { @@ -1010,9 +1023,9 @@ static bool HasBitSet(const BitVector &BV, unsigned Bit) { static void NoteDef(State &S, BBState &BBS, int Num, const std::vector &SafepointsSoFar) { assert(Num >= 0); MaybeResize(BBS, Num); - assert(BBS.Defs[Num] == 0 && "SSA Violation or misnumbering?"); - BBS.Defs[Num] = 1; - BBS.UpExposedUses[Num] = 0; + assert(!BBS.Defs.test(Num) && "SSA Violation or misnumbering?"); + BBS.Defs.set(Num); + BBS.UpExposedUses.reset(Num); // This value could potentially be live at any following safe point // if it ends up live out, so add it to the LiveIfLiveOut lists for all // following safepoints. @@ -1056,7 +1069,7 @@ static int NoteSafepoint(State &S, BBState &BBS, CallInst *CI, std::vector return Number; } -void LateLowerGCFrame::NoteUse(State &S, BBState &BBS, Value *V, BitVector &Uses) { +void LateLowerGCFrame::NoteUse(State &S, BBState &BBS, Value *V, LargeSparseBitVector &Uses) { // Short circuit to avoid having to deal with vectors of constants, etc. if (isa(V)) return; @@ -1066,7 +1079,7 @@ void LateLowerGCFrame::NoteUse(State &S, BBState &BBS, Value *V, BitVector &Uses if (Num < 0) return; MaybeResize(BBS, Num); - Uses[Num] = 1; + Uses.set(Num); } } else { std::vector Nums = NumberAll(S, V); @@ -1074,7 +1087,7 @@ void LateLowerGCFrame::NoteUse(State &S, BBState &BBS, Value *V, BitVector &Uses if (Num < 0) continue; MaybeResize(BBS, Num); - Uses[Num] = 1; + Uses.set(Num); } } } @@ -1107,31 +1120,44 @@ void RecursivelyVisit(callback f, Value *V) { } } -static void dumpBitVectorValues(State &S, BitVector &BV) { +static void dumpBitVectorValues(State &S, LargeSparseBitVector &BV, ModuleSlotTracker &MST) { bool first = true; - for (int Idx = BV.find_first(); Idx >= 0; Idx = BV.find_next(Idx)) { + for (auto Idx : BV) { if (!first) dbgs() << ", "; first = false; - S.ReversePtrNumbering[Idx]->printAsOperand(dbgs()); + S.ReversePtrNumbering[Idx]->printAsOperand(dbgs(), false, MST); } } +static void dumpBBState(const BasicBlock &BB, State &S, ModuleSlotTracker &MST) +{ + dbgs() << "Liveness analysis for BB " << BB.getName(); + dbgs() << "\n\tDefs: "; + dumpBitVectorValues(S, S.BBStates[&BB].Defs, MST); + dbgs() << "\n\tPhiOuts: "; + dumpBitVectorValues(S, S.BBStates[&BB].PhiOuts, MST); + dbgs() << "\n\tUpExposedUses: "; + dumpBitVectorValues(S, S.BBStates[&BB].UpExposedUses, MST); + dbgs() << "\n\tLiveIn: "; + dumpBitVectorValues(S, S.BBStates[&BB].LiveIn, MST); + dbgs() << "\n\tLiveOut: "; + dumpBitVectorValues(S, S.BBStates[&BB].LiveOut, MST); + dbgs() << "\n"; +} + +JL_USED_FUNC static void dumpBBState(const BasicBlock &BB, State &S) +{ + ModuleSlotTracker MST(BB.getParent()->getParent()); + dumpBBState(BB, S, MST); +} + + /* Debugging utility to dump liveness information */ JL_USED_FUNC static void dumpLivenessState(Function &F, State &S) { + ModuleSlotTracker MST(F.getParent()); for (auto &BB : F) { - dbgs() << "Liveness analysis for BB " << BB.getName(); - dbgs() << "\n\tDefs: "; - dumpBitVectorValues(S, S.BBStates[&BB].Defs); - dbgs() << "\n\tPhiOuts: "; - dumpBitVectorValues(S, S.BBStates[&BB].PhiOuts); - dbgs() << "\n\tUpExposedUses: "; - dumpBitVectorValues(S, S.BBStates[&BB].UpExposedUses); - dbgs() << "\n\tLiveIn: "; - dumpBitVectorValues(S, S.BBStates[&BB].LiveIn); - dbgs() << "\n\tLiveOut: "; - dumpBitVectorValues(S, S.BBStates[&BB].LiveOut); - dbgs() << "\n"; + return dumpBBState(BB, S, MST); } } @@ -1444,6 +1470,8 @@ void LateLowerGCFrame::FixUpRefinements(ArrayRef PHINumbers, State &S) // This should have been handled by the first loop above. assert(j != 0 && j <= RefinedPtr.size()); RefinedPtr.resize(j); + } else { + S.Refinements.erase(Num); } visited.reset(); } @@ -1898,14 +1926,11 @@ void LateLowerGCFrame::MaybeTrackStore(State &S, StoreInst *I) { */ void LateLowerGCFrame::ComputeLiveness(State &S) { bool Converged = false; - /* Liveness is a reverse problem, so RPOT is a good way to - * perform this iteration. - */ - ReversePostOrderTraversal RPOT(S.F); - BitVector NewLive; + /* Liveness is a reverse problem, so post-order is a good way to perform this iteration. */ + LargeSparseBitVector NewLive; while (!Converged) { bool AnyChanged = false; - for (BasicBlock *BB : RPOT) { + for (BasicBlock *BB : post_order(S.F)) { // This could all be done more efficiently, by only updating what // changed - Let's get it working first though. BBState &BBS = S.BBStates[BB]; @@ -1915,14 +1940,13 @@ void LateLowerGCFrame::ComputeLiveness(State &S) { } if (NewLive != BBS.LiveOut) { AnyChanged = true; - BBS.LiveOut = NewLive; - MaybeResize(BBS, BBS.LiveOut.size() - 1); + BBS.LiveOut = NewLive; } - NewLive.reset(BBS.Defs); + NewLive.intersectWithComplement(BBS.Defs); NewLive |= BBS.UpExposedUses; if (NewLive != BBS.LiveIn) { AnyChanged = true; - std::swap(BBS.LiveIn, NewLive); + std::swap(BBS.LiveIn, NewLive); } } Converged = !AnyChanged; @@ -1935,8 +1959,8 @@ JL_USED_FUNC static void dumpSafepointsForBBName(Function &F, State &S, const ch for (auto it : S.SafepointNumbering) { if (it.first->getParent()->getName() == BBName) { dbgs() << "Live at " << *it.first << "\n"; - BitVector &LS = S.LiveSets[it.second]; - for (int Idx = LS.find_first(); Idx >= 0; Idx = LS.find_next(Idx)) { + LargeSparseBitVector &LS = S.LiveSets[it.second]; + for (auto Idx : LS) { dbgs() << "\t"; S.ReversePtrNumbering[Idx]->printAsOperand(dbgs()); dbgs() << "\n"; @@ -1945,63 +1969,70 @@ JL_USED_FUNC static void dumpSafepointsForBBName(Function &F, State &S, const ch } } -void LateLowerGCFrame::RefineLiveSet(BitVector &LS, State &S, const std::vector &CalleeRoots) +static bool IsIndirectlyRooted(const State &S, LargeSparseBitVector &Visited, LargeSparseBitVector &IndirectlyRootedLS, const LargeSparseBitVector &LS, int RefPtr) { + if (HasBitSet(IndirectlyRootedLS, RefPtr)) + return true; + if (HasBitSet(Visited, RefPtr)) + return false; + const auto it = S.Refinements.find(RefPtr); + if (it == S.Refinements.end()) { + Visited.set(RefPtr); + return false; + } + const auto &RefinedPtr = it->second; + assert(!RefinedPtr.empty()); + bool rooted = true; + for (auto NRefPtr: RefinedPtr) { + if (NRefPtr < 0 || IsIndirectlyRooted(S, Visited, IndirectlyRootedLS, LS, NRefPtr)) { + continue; + } + // Not indirectly rooted, but in LS - can be used to establish a root + if (HasBitSet(LS, NRefPtr)) + continue; + rooted = false; + break; + } + if (rooted) + IndirectlyRootedLS.set(RefPtr); + Visited.set(RefPtr); + return rooted; +} + +void LateLowerGCFrame::RefineLiveSet(LargeSparseBitVector &LS, State &S, const std::vector &CalleeRoots) { - BitVector FullLS(S.MaxPtrNumber + 1, false); - FullLS |= LS; - // First expand the live set according to the refinement map - // so that we can see all the values that are effectively live. + // It is possible that a value is not directly rooted by the refinements in the live set, but rather + // indirectly by following the edges of the refinement graph to all the values that root it. + // For example, suppose we have: + // LS: 1 4 5 + // Refinements: 1 -> {2,3} + // 2 -> 4 + // 3 -> 5 + // Even though {2,3} is not in the LiveSet, we can still refine, because we can follow the edges to + // the roots {4, 5} which are in the live set. The two bit vectors here cache the lookup for efficiency. + LargeSparseBitVector Visited; + LargeSparseBitVector IndirectlyRootedLS; for (auto Num: CalleeRoots) { // For callee rooted values, they are all kept alive at the safepoint. // Make sure they are marked (even though they probably are already) // so that other values can be refined to them. - FullLS[Num] = 1; + IndirectlyRootedLS.set(Num); + // Now unmark all values that are rooted by the callee after + // refining other values to them. + LS.reset(Num); } - bool changed; - do { - changed = false; - for (auto &kv: S.Refinements) { - int Num = kv.first; - if (Num < 0 || HasBitSet(FullLS, Num) || kv.second.empty()) - continue; - bool live = true; - for (auto &refine: kv.second) { - if (refine < 0 || HasBitSet(FullLS, refine)) - continue; - live = false; - break; - } - if (live) { - changed = true; - FullLS[Num] = 1; - } - } - } while (changed); + // Now remove all values from the LiveSet that's kept alive by other objects // This loop only mutate `LS` which isn't read from in the loop body so // a single pass is enough. - for (int Idx = LS.find_first(); Idx >= 0; Idx = LS.find_next(Idx)) { - if (!S.Refinements.count(Idx)) - continue; - const auto &RefinedPtr = S.Refinements[Idx]; - if (RefinedPtr.empty()) - continue; - bool rooted = true; - for (auto RefPtr: RefinedPtr) { - if (RefPtr < 0 || HasBitSet(FullLS, RefPtr)) - continue; - rooted = false; - break; - } + auto it = LS.begin(); + while (it != LS.end()) { + int Idx = *it; + bool rooted = IsIndirectlyRooted(S, Visited, IndirectlyRootedLS, LS, Idx); + ++it; if (rooted) { - LS[Idx] = 0; + LS.reset(Idx); } } - for (auto Num: CalleeRoots) { - // Now unmark all values that are rooted by the callee after - // refining other values to them. - LS[Num] = 0; - } } void LateLowerGCFrame::ComputeLiveSets(State &S) { @@ -2012,13 +2043,13 @@ void LateLowerGCFrame::ComputeLiveSets(State &S) { Instruction *Safepoint = it.first; BasicBlock *BB = Safepoint->getParent(); BBState &BBS = S.BBStates[BB]; - BitVector LiveAcross = BBS.LiveIn; + LargeSparseBitVector LiveAcross = BBS.LiveIn; LiveAcross &= BBS.LiveOut; - BitVector &LS = S.LiveSets[idx]; + LargeSparseBitVector &LS = S.LiveSets[idx]; LS |= LiveAcross; for (int Live : S.LiveIfLiveOut[idx]) { if (HasBitSet(BBS.LiveOut, Live)) - LS[Live] = 1; + LS.set(Live); } RefineLiveSet(LS, S, S.CalleeRoots[idx]); // If the function has GC preserves, figure out whether we need to @@ -2042,30 +2073,18 @@ void LateLowerGCFrame::ComputeLiveSets(State &S) { if (OutsideRange) continue; for (unsigned Num : it2.second) { - if (Num >= LS.size()) - LS.resize(Num + 1); - LS[Num] = 1; + LS.set(Num); } } } } // Compute the interference graph - for (int i = 0; i <= S.MaxPtrNumber; ++i) { - SetVector Neighbors; - BitVector NeighborBits(S.MaxPtrNumber); - for (auto it : S.SafepointNumbering) { - const BitVector &LS = S.LiveSets[it.second]; - if ((unsigned)i >= LS.size() || !LS[i]) - continue; - NeighborBits |= LS; - } - for (int Idx = NeighborBits.find_first(); Idx >= 0; Idx = NeighborBits.find_next(Idx)) { - // We explicitly let i be a neighbor of itself, to distinguish - // between being the only value live at a safepoint, vs not - // being live at any safepoint. - Neighbors.insert(Idx); + S.Neighbors.resize(S.MaxPtrNumber+1); + for (auto it : S.SafepointNumbering) { + const LargeSparseBitVector &LS = S.LiveSets[it.second]; + for (int idx : LS) { + S.Neighbors[idx] |= LS; } - S.Neighbors.push_back(Neighbors); } } @@ -2081,8 +2100,8 @@ struct PEOIterator { }; std::vector Elements; std::vector> Levels; - const std::vector> &Neighbors; - PEOIterator(const std::vector> &Neighbors) : Neighbors(Neighbors) { + const std::vector &Neighbors; + PEOIterator(const std::vector &Neighbors) : Neighbors(Neighbors) { // Initialize State std::vector FirstLevel; for (unsigned i = 0; i < Neighbors.size(); ++i) { @@ -2151,8 +2170,8 @@ std::vector LateLowerGCFrame::ColorRoots(const State &S) { to returns_twice */ for (auto it : S.ReturnsTwice) { int Num = S.SafepointNumbering.at(it); - const BitVector &LS = S.LiveSets[Num]; - for (int Idx = LS.find_first(); Idx >= 0; Idx = LS.find_next(Idx)) { + const LargeSparseBitVector &LS = S.LiveSets[Num]; + for (int Idx : LS) { if (Colors[Idx] == -1) Colors[Idx] = PreAssignedColors++; } @@ -2531,7 +2550,7 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S, bool *CFGModified) { return ChangesMade; } -static void AddInPredLiveOuts(BasicBlock *BB, BitVector &LiveIn, State &S) +static void AddInPredLiveOuts(BasicBlock *BB, LargeSparseBitVector &LiveIn, State &S) { bool First = true; std::set Visited; @@ -2542,7 +2561,7 @@ static void AddInPredLiveOuts(BasicBlock *BB, BitVector &LiveIn, State &S) WorkList.pop_back(); // Nothing is live at function entry if (BB == &S.F->getEntryBlock()) { - LiveIn.reset(); + LiveIn.clear(); return; } for (BasicBlock *Pred : predecessors(BB)) { @@ -2592,13 +2611,13 @@ void LateLowerGCFrame::PlaceGCFrameStores(State &S, unsigned MinColorRoot, if (!BBS.HasSafepoint) { continue; } - BitVector LiveIn; + LargeSparseBitVector LiveIn; AddInPredLiveOuts(&BB, LiveIn, S); - const BitVector *LastLive = &LiveIn; + const LargeSparseBitVector *LastLive = &LiveIn; for(auto rit = BBS.Safepoints.rbegin(); rit != BBS.Safepoints.rend(); ++rit ) { - const BitVector &NowLive = S.LiveSets[*rit]; - for (int Idx = NowLive.find_first(); Idx >= 0; Idx = NowLive.find_next(Idx)) { + const LargeSparseBitVector &NowLive = S.LiveSets[*rit]; + for (int Idx : NowLive) { if (!HasBitSet(*LastLive, Idx)) { PlaceGCFrameStore(S, Idx, MinColorRoot, Colors, GCFrame, S.ReverseSafepointNumbering[*rit]); diff --git a/test/llvmpasses/refinements.ll b/test/llvmpasses/refinements.ll index cb2dea816c56b7..6c92bab06e3573 100644 --- a/test/llvmpasses/refinements.ll +++ b/test/llvmpasses/refinements.ll @@ -7,6 +7,7 @@ declare {}*** @julia.get_pgcstack() declare void @jl_safepoint() declare void @one_arg_boxed({} addrspace(10)*) declare {} addrspace(10)* @ijl_box_int64(i64) +declare {} addrspace(10)* @allocate_some_value() define void @argument_refinement({} addrspace(10)* %a) { ; CHECK-LABEL: @argument_refinement @@ -54,8 +55,6 @@ define void @heap_refinement2(i64 %a) { ret void } -declare {} addrspace(10)* @allocate_some_value() - ; Check that the way we compute rooting is compatible with refinements define void @issue22770() { ; CHECK-LABEL: @issue22770 From d18fd479c4252c0fb21e22144bd209bd1b120fdb Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Mon, 21 Nov 2022 09:34:31 -0300 Subject: [PATCH 005/387] Make DemoteFloat16 a conditional pass (#43327) * add TargetMachine check * Add initial float16 multiversioning stuff * make check more robust and remove x86 check * move check to inside the pass * C++ is hard * Comment out the ckeck because it won't work inside the pass * whitespace in the comment * Change the logic not to depend on a TM * Add preliminary support for x86 test * Cosmetic changes --- src/llvm-demote-float16.cpp | 38 +++++++++++++++++++++++++++++++++++- src/llvm-multiversioning.cpp | 10 ++++++++++ src/processor.h | 2 ++ src/processor_arm.cpp | 9 ++++++++- 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/llvm-demote-float16.cpp b/src/llvm-demote-float16.cpp index 51535e7cb1f9f3..57ec30ca57947f 100644 --- a/src/llvm-demote-float16.cpp +++ b/src/llvm-demote-float16.cpp @@ -25,6 +25,8 @@ #include #include #include +#include "julia.h" +#include "jitlayers.h" #define DEBUG_TYPE "demote_float16" @@ -43,13 +45,47 @@ INST_STATISTIC(FRem); INST_STATISTIC(FCmp); #undef INST_STATISTIC +extern JuliaOJIT *jl_ExecutionEngine; + +Optional always_have_fp16() { +#if defined(_CPU_X86_) || defined(_CPU_X86_64_) + // x86 doesn't support fp16 + // TODO: update for sapphire rapids when it comes out + return false; +#else + return {}; +#endif +} + namespace { +bool have_fp16(Function &caller) { + auto unconditional = always_have_fp16(); + if (unconditional.hasValue()) + return unconditional.getValue(); + + Attribute FSAttr = caller.getFnAttribute("target-features"); + StringRef FS = + FSAttr.isValid() ? FSAttr.getValueAsString() : jl_ExecutionEngine->getTargetFeatureString(); +#if defined(_CPU_AARCH64_) + if (FS.find("+fp16fml") != llvm::StringRef::npos || FS.find("+fullfp16") != llvm::StringRef::npos){ + return true; + } +#else + if (FS.find("+avx512fp16") != llvm::StringRef::npos){ + return true; + } +#endif + return false; +} + static bool demoteFloat16(Function &F) { + if (have_fp16(F)) + return false; + auto &ctx = F.getContext(); auto T_float32 = Type::getFloatTy(ctx); - SmallVector erase; for (auto &BB : F) { for (auto &I : BB) { diff --git a/src/llvm-multiversioning.cpp b/src/llvm-multiversioning.cpp index 7e0bed6276a2d4..f2fdb6f4fd1c86 100644 --- a/src/llvm-multiversioning.cpp +++ b/src/llvm-multiversioning.cpp @@ -45,6 +45,8 @@ using namespace llvm; extern Optional always_have_fma(Function&); +extern Optional always_have_fp16(); + namespace { constexpr uint32_t clone_mask = JL_TARGET_CLONE_LOOP | JL_TARGET_CLONE_SIMD | JL_TARGET_CLONE_MATH | JL_TARGET_CLONE_CPU; @@ -480,6 +482,14 @@ uint32_t CloneCtx::collect_func_info(Function &F) flag |= JL_TARGET_CLONE_MATH; } } + if(!always_have_fp16().hasValue()){ + for (size_t i = 0; i < I.getNumOperands(); i++) { + if(I.getOperand(i)->getType()->isHalfTy()){ + flag |= JL_TARGET_CLONE_FLOAT16; + } + // Check for BFloat16 when they are added to julia can be done here + } + } if (has_veccall && (flag & JL_TARGET_CLONE_SIMD) && (flag & JL_TARGET_CLONE_MATH)) { return flag; } diff --git a/src/processor.h b/src/processor.h index f3b571cf9b9374..4b9071fb4f663f 100644 --- a/src/processor.h +++ b/src/processor.h @@ -112,6 +112,8 @@ enum { JL_TARGET_MINSIZE = 1 << 7, // Clone when the function queries CPU features JL_TARGET_CLONE_CPU = 1 << 8, + // Clone when the function uses fp16 + JL_TARGET_CLONE_FLOAT16 = 1 << 9, }; #define JL_FEATURE_DEF_NAME(name, bit, llvmver, str) JL_FEATURE_DEF(name, bit, llvmver) diff --git a/src/processor_arm.cpp b/src/processor_arm.cpp index ea8dddf629d62a..eaa950662d0dea 100644 --- a/src/processor_arm.cpp +++ b/src/processor_arm.cpp @@ -1602,12 +1602,19 @@ static void ensure_jit_target(bool imaging) auto &t = jit_targets[i]; if (t.en.flags & JL_TARGET_CLONE_ALL) continue; + auto &features0 = jit_targets[t.base].en.features; // Always clone when code checks CPU features t.en.flags |= JL_TARGET_CLONE_CPU; + static constexpr uint32_t clone_fp16[] = {Feature::fp16fml,Feature::fullfp16}; + for (auto fe: clone_fp16) { + if (!test_nbit(features0, fe) && test_nbit(t.en.features, fe)) { + t.en.flags |= JL_TARGET_CLONE_FLOAT16; + break; + } + } // The most useful one in general... t.en.flags |= JL_TARGET_CLONE_LOOP; #ifdef _CPU_ARM_ - auto &features0 = jit_targets[t.base].en.features; static constexpr uint32_t clone_math[] = {Feature::vfp3, Feature::vfp4, Feature::neon}; for (auto fe: clone_math) { if (!test_nbit(features0, fe) && test_nbit(t.en.features, fe)) { From c5fe17b821b8af32ada7694bf874cb6eb1793d77 Mon Sep 17 00:00:00 2001 From: Petr Vana Date: Mon, 21 Nov 2022 15:45:21 +0100 Subject: [PATCH 006/387] Doc: The default sorting alg. is stable from 1.9 (#47579) * Update doc/src/base/sort.md * Update docs: The default sorting alg. is stable * Compat 1.9 for QuickSort to be stable * Specify the default algorithm * Use example from InlineStrings.jl * Change example to jldoctest * Remove "*appear* to be stable." as slightly misleading. Co-authored-by: Lilith Orion Hafner --- doc/src/base/sort.md | 80 ++++++++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 33 deletions(-) diff --git a/doc/src/base/sort.md b/doc/src/base/sort.md index 9f00381ab892c8..e93d9716b14871 100644 --- a/doc/src/base/sort.md +++ b/doc/src/base/sort.md @@ -141,53 +141,67 @@ There are currently four sorting algorithms available in base Julia: * [`PartialQuickSort(k)`](@ref) * [`MergeSort`](@ref) -`InsertionSort` is an O(n^2) stable sorting algorithm. It is efficient for very small `n`, and -is used internally by `QuickSort`. +`InsertionSort` is an O(n²) stable sorting algorithm. It is efficient for very small `n`, +and is used internally by `QuickSort`. -`QuickSort` is an O(n log n) sorting algorithm which is in-place, very fast, but not stable – -i.e. elements which are considered equal will not remain in the same order in which they originally -appeared in the array to be sorted. `QuickSort` is the default algorithm for numeric values, including -integers and floats. +`QuickSort` is a very fast sorting algorithm with an average-case time complexity of +O(n log n). `QuickSort` is stable, i.e., elements considered equal will remain in the same +order. Notice that O(n²) is worst-case complexity, but it gets vanishingly unlikely as the +pivot selection is randomized. -`PartialQuickSort(k)` is similar to `QuickSort`, but the output array is only sorted up to index -`k` if `k` is an integer, or in the range of `k` if `k` is an `OrdinalRange`. For example: +`PartialQuickSort(k::OrdinalRange)` is similar to `QuickSort`, but the output array is only +sorted in the range of `k`. For example: -```julia -x = rand(1:500, 100) -k = 50 -k2 = 50:100 -s = sort(x; alg=QuickSort) -ps = sort(x; alg=PartialQuickSort(k)) -qs = sort(x; alg=PartialQuickSort(k2)) -map(issorted, (s, ps, qs)) # => (true, false, false) -map(x->issorted(x[1:k]), (s, ps, qs)) # => (true, true, false) -map(x->issorted(x[k2]), (s, ps, qs)) # => (true, false, true) -s[1:k] == ps[1:k] # => true -s[k2] == qs[k2] # => true +```jldoctest +julia> x = rand(1:500, 100); + +julia> k = 50:100; + +julia> s1 = sort(x; alg=QuickSort); + +julia> s2 = sort(x; alg=PartialQuickSort(k)); + +julia> map(issorted, (s1, s2)) +(true, false) + +julia> map(x->issorted(x[k]), (s1, s2)) +(true, true) + +julia> s1[k] == s2[k] +true ``` +!!! compat "Julia 1.9" + The `QuickSort` and `PartialQuickSort` algorithms are stable since Julia 1.9. + `MergeSort` is an O(n log n) stable sorting algorithm but is not in-place – it requires a temporary array of half the size of the input array – and is typically not quite as fast as `QuickSort`. It is the default algorithm for non-numeric data. -The default sorting algorithms are chosen on the basis that they are fast and stable, or *appear* -to be so. For numeric types indeed, `QuickSort` is selected as it is faster and indistinguishable -in this case from a stable sort (unless the array records its mutations in some way). The stability -property comes at a non-negligible cost, so if you don't need it, you may want to explicitly specify -your preferred algorithm, e.g. `sort!(v, alg=QuickSort)`. +The default sorting algorithms are chosen on the basis that they are fast and stable. +Usually, `QuickSort` is selected, but `InsertionSort` is preferred for small data. +You can also explicitly specify your preferred algorithm, e.g. +`sort!(v, alg=PartialQuickSort(10:20))`. -The mechanism by which Julia picks default sorting algorithms is implemented via the `Base.Sort.defalg` -function. It allows a particular algorithm to be registered as the default in all sorting functions -for specific arrays. For example, here are the two default methods from [`sort.jl`](https://github.com/JuliaLang/julia/blob/master/base/sort.jl): +The mechanism by which Julia picks default sorting algorithms is implemented via the +`Base.Sort.defalg` function. It allows a particular algorithm to be registered as the +default in all sorting functions for specific arrays. For example, here is the default +method from [`sort.jl`](https://github.com/JuliaLang/julia/blob/master/base/sort.jl): + +```julia +defalg(v::AbstractArray) = DEFAULT_STABLE +``` +You may change the default behavior for specific types by defining new methods for `defalg`. +For example, [InlineStrings.jl](https://github.com/JuliaStrings/InlineStrings.jl/blob/v1.3.2/src/InlineStrings.jl#L903) +defines the following method: ```julia -defalg(v::AbstractArray) = MergeSort -defalg(v::AbstractArray{<:Number}) = QuickSort +Base.Sort.defalg(::AbstractArray{<:Union{SmallInlineStrings, Missing}}) = InlineStringSort ``` -As for numeric arrays, choosing a non-stable default algorithm for array types for which the notion -of a stable sort is meaningless (i.e. when two values comparing equal can not be distinguished) -may make sense. +!!! compat "Julia 1.9" + The default sorting algorithm (returned by `Base.Sort.defalg`) is guaranteed + to be stable since Julia 1.9. Previous versions had unstable edge cases when sorting numeric arrays. ## Alternate orderings From ab262e73104994e05c149d5b21f1ba71139b6fca Mon Sep 17 00:00:00 2001 From: N5N3 <2642243996@qq.com> Date: Tue, 22 Nov 2022 02:27:33 +0800 Subject: [PATCH 007/387] Unroll `foldl/r` at julia level for small `NamedTuple` (#47109) And make `Tuple` dispatched similarly. This commit also renames `_reverse`, as it has a non-related 2-arg version in `Base`. --- base/reduce.jl | 11 ++++++++--- base/tuple.jl | 2 -- test/namedtuple.jl | 11 +++++++++++ test/reduce.jl | 18 ++++++++++++++++-- 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/base/reduce.jl b/base/reduce.jl index 0bcf5f8ca5923a..467a0e55639076 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -64,6 +64,11 @@ function _foldl_impl(op::OP, init, itr) where {OP} return v end +function _foldl_impl(op, init, itr::Union{Tuple,NamedTuple}) + length(itr) <= 32 && return afoldl(op, init, itr...) + @invoke _foldl_impl(op, init, itr::Any) +end + struct _InitialValue end """ @@ -196,11 +201,11 @@ foldl(op, itr; kw...) = mapfoldl(identity, op, itr; kw...) function mapfoldr_impl(f, op, nt, itr) op′, itr′ = _xfadjoint(BottomRF(FlipArgs(op)), Generator(f, itr)) - return foldl_impl(op′, nt, _reverse(itr′)) + return foldl_impl(op′, nt, _reverse_iter(itr′)) end -_reverse(itr) = Iterators.reverse(itr) -_reverse(itr::Tuple) = reverse(itr) #33235 +_reverse_iter(itr) = Iterators.reverse(itr) +_reverse_iter(itr::Union{Tuple,NamedTuple}) = length(itr) <= 32 ? reverse(itr) : Iterators.reverse(itr) #33235 struct FlipArgs{F} f::F diff --git a/base/tuple.jl b/base/tuple.jl index 689645b35fcbbc..d59c9239217e33 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -326,8 +326,6 @@ function map(f, t1::Any32, t2::Any32, ts::Any32...) (A...,) end -_foldl_impl(op, init, itr::Tuple) = afoldl(op, init, itr...) - # type-stable padding fill_to_length(t::NTuple{N,Any}, val, ::Val{N}) where {N} = t fill_to_length(t::Tuple{}, val, ::Val{1}) = (val,) diff --git a/test/namedtuple.jl b/test/namedtuple.jl index 6c41ab788b7331..82efed0a080df1 100644 --- a/test/namedtuple.jl +++ b/test/namedtuple.jl @@ -338,3 +338,14 @@ end # issue #44086 @test NamedTuple{(:x, :y, :z), Tuple{Int8, Int16, Int32}}((z=1, x=2, y=3)) === (x = Int8(2), y = Int16(3), z = Int32(1)) + +@testset "mapfoldl" begin + A1 = (;a=1, b=2, c=3, d=4) + A2 = (;a=-1, b=-2, c=-3, d=-4) + @test (((1=>2)=>3)=>4) == foldl(=>, A1) == + mapfoldl(identity, =>, A1) == mapfoldl(abs, =>, A2) + @test mapfoldl(abs, =>, A2, init=-10) == ((((-10=>1)=>2)=>3)=>4) + @test mapfoldl(abs, =>, (;), init=-10) == -10 + @test mapfoldl(abs, Pair{Any,Any}, NamedTuple(Symbol(:x,i) => i for i in 1:30)) == mapfoldl(abs, Pair{Any,Any}, [1:30;]) + @test_throws "reducing over an empty collection" mapfoldl(abs, =>, (;)) +end diff --git a/test/reduce.jl b/test/reduce.jl index 84d93b12913e41..4c05b179edcff8 100644 --- a/test/reduce.jl +++ b/test/reduce.jl @@ -33,8 +33,12 @@ using .Main.OffsetArrays @test Base.mapfoldr(abs2, -, 2:5) == -14 @test Base.mapfoldr(abs2, -, 2:5; init=10) == -4 -@test @inferred(mapfoldr(x -> x + 1, (x, y) -> (x, y...), (1, 2.0, '3'); - init = ())) == (2, 3.0, '4') +for t in Any[(1, 2.0, '3'), (;a = 1, b = 2.0, c = '3')] + @test @inferred(mapfoldr(x -> x + 1, (x, y) -> (x, y...), t; + init = ())) == (2, 3.0, '4') + @test @inferred(mapfoldl(x -> x + 1, (x, y) -> (x..., y), t; + init = ())) == (2, 3.0, '4') +end @test foldr((x, y) -> ('⟨' * x * '|' * y * '⟩'), "λ 🐨.α") == "⟨λ|⟨ |⟨🐨|⟨.|α⟩⟩⟩⟩" # issue #31780 let x = rand(10) @@ -691,3 +695,13 @@ end @test @inferred(prod(b)) == prod(collect(b)) @test @inferred(minimum(a)) == minimum(collect(a)) end + +function fold_alloc(a) + sum(a) + foldr(+, a) + max(@allocated(sum(a)), @allocated(foldr(+, a))) +end +let a = NamedTuple(Symbol(:x,i) => i for i in 1:33), + b = (a...,) + @test fold_alloc(a) == fold_alloc(b) == 0 +end From 23160a109dd9dff845ef8670b7545ab6ad23ae18 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Tue, 22 Nov 2022 04:43:36 +0900 Subject: [PATCH 008/387] correct throw `ArgumentError` from `lbt_find_backing_library` (#47651) --- stdlib/LinearAlgebra/src/lbt.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/LinearAlgebra/src/lbt.jl b/stdlib/LinearAlgebra/src/lbt.jl index f8fbd7f526ccbc..3099eb3b765cee 100644 --- a/stdlib/LinearAlgebra/src/lbt.jl +++ b/stdlib/LinearAlgebra/src/lbt.jl @@ -240,7 +240,7 @@ If the given `symbol_name` is not contained within the list of exported symbols, function lbt_find_backing_library(symbol_name, interface::Symbol; config::LBTConfig = lbt_get_config()) if interface ∉ (:ilp64, :lp64) - throw(Argument("Invalid interface specification: '$(interface)'")) + throw(ArgumentError("Invalid interface specification: '$(interface)'")) end symbol_idx = findfirst(s -> s == symbol_name, config.exported_symbols) if symbol_idx === nothing From 1e2ba337f0fcaf7b14b801cdc598484fb3cf9ade Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Mon, 21 Nov 2022 15:28:29 -0500 Subject: [PATCH 009/387] Cleanup: Use `issingletontype` consistently (#47618) Rather than reaching for `isdefined(x, :instance)`. They are currently equivalent, but I was playing with an extension that would have made them not be. I don't currently have plans to finish that particular PR, but this cleanup seems good regardless to specify exactly what is meant rather than reaching for the implementation. --- base/compiler/abstractinterpretation.jl | 2 +- base/compiler/inferenceresult.jl | 4 ++-- base/compiler/tfuncs.jl | 6 +++--- base/compiler/typelattice.jl | 2 +- base/compiler/typeutils.jl | 2 +- base/compiler/utilities.jl | 4 ++-- base/summarysize.jl | 2 +- stdlib/Serialization/src/Serialization.jl | 2 +- test/compiler/contextual.jl | 2 +- test/core.jl | 4 ++-- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index a79da806775b89..55866d4a637136 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1671,7 +1671,7 @@ end @inline function egal_condition(c::Const, @nospecialize(xt), max_union_splitting::Int) thentype = c elsetype = widenslotwrapper(xt) - if elsetype isa Type && isdefined(typeof(c.val), :instance) # can only widen a if it is a singleton + if elsetype isa Type && issingletontype(typeof(c.val)) # can only widen a if it is a singleton elsetype = typesubtract(elsetype, typeof(c.val), max_union_splitting) end return ConditionalTypes(thentype, elsetype) diff --git a/base/compiler/inferenceresult.jl b/base/compiler/inferenceresult.jl index d276745ee0052e..792495cb6dc0db 100644 --- a/base/compiler/inferenceresult.jl +++ b/base/compiler/inferenceresult.jl @@ -151,7 +151,7 @@ function most_general_argtypes(method::Union{Method, Nothing}, @nospecialize(spe end for i in 1:length(vargtype_elements) atyp = vargtype_elements[i] - if isa(atyp, DataType) && isdefined(atyp, :instance) + if issingletontype(atyp) # replace singleton types with their equivalent Const object vargtype_elements[i] = Const(atyp.instance) elseif isconstType(atyp) @@ -180,7 +180,7 @@ function most_general_argtypes(method::Union{Method, Nothing}, @nospecialize(spe tail_index -= 1 end atyp = unwraptv(atyp) - if isa(atyp, DataType) && isdefined(atyp, :instance) + if issingletontype(atyp) # replace singleton types with their equivalent Const object atyp = Const(atyp.instance) elseif isconstType(atyp) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 2937a164c91026..50c2f1e15a0898 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -253,8 +253,8 @@ function egal_tfunc(::ConstsLattice, @nospecialize(x), @nospecialize(y)) return Const(x.val === y.val) elseif !hasintersect(widenconst(x), widenconst(y)) return Const(false) - elseif (isa(x, Const) && y === typeof(x.val) && isdefined(y, :instance)) || - (isa(y, Const) && x === typeof(y.val) && isdefined(x, :instance)) + elseif (isa(x, Const) && y === typeof(x.val) && issingletontype(x)) || + (isa(y, Const) && x === typeof(y.val) && issingletontype(y)) return Const(true) end return Bool @@ -1753,7 +1753,7 @@ function tuple_tfunc(@specialize(lattice::AbstractLattice), argtypes::Vector{Any end typ = Tuple{params...} # replace a singleton type with its equivalent Const object - isdefined(typ, :instance) && return Const(typ.instance) + issingletontype(typ) && return Const(typ.instance) return anyinfo ? PartialStruct(typ, argtypes) : typ end tuple_tfunc(argtypes::Vector{Any}) = tuple_tfunc(fallback_lattice, argtypes) diff --git a/base/compiler/typelattice.jl b/base/compiler/typelattice.jl index f10363f91de1a4..71675647e5aabf 100644 --- a/base/compiler/typelattice.jl +++ b/base/compiler/typelattice.jl @@ -493,7 +493,7 @@ function ⊑(lattice::ConstsLattice, @nospecialize(a), @nospecialize(b)) # most conservative option. return isa(b, Type) && isa(a.val, b) elseif isa(b, Const) - if isa(a, DataType) && isdefined(a, :instance) + if issingletontype(a) return a.instance === b.val end return false diff --git a/base/compiler/typeutils.jl b/base/compiler/typeutils.jl index d2794a64b0f566..8207153e822b03 100644 --- a/base/compiler/typeutils.jl +++ b/base/compiler/typeutils.jl @@ -55,7 +55,7 @@ function has_nontrivial_const_info(lattice::ConstsLattice, @nospecialize t) isa(t, PartialTypeVar) && return true if isa(t, Const) val = t.val - return !isdefined(typeof(val), :instance) && !(isa(val, Type) && hasuniquerep(val)) + return !issingletontype(typeof(val)) && !(isa(val, Type) && hasuniquerep(val)) end return has_nontrivial_const_info(widenlattice(lattice), t) end diff --git a/base/compiler/utilities.jl b/base/compiler/utilities.jl index 243572fefcc85f..2915870ae2ea58 100644 --- a/base/compiler/utilities.jl +++ b/base/compiler/utilities.jl @@ -296,7 +296,7 @@ function singleton_type(@nospecialize(ft)) return ft.val elseif isconstType(ft) return ft.parameters[1] - elseif ft isa DataType && isdefined(ft, :instance) + elseif issingletontype(ft) return ft.instance end return nothing @@ -304,7 +304,7 @@ end function maybe_singleton_const(@nospecialize(t)) if isa(t, DataType) - if isdefined(t, :instance) + if issingletontype(t) return Const(t.instance) elseif isconstType(t) return Const(t.parameters[1]) diff --git a/base/summarysize.jl b/base/summarysize.jl index b20eaa710a2fbf..9bbae187cab123 100644 --- a/base/summarysize.jl +++ b/base/summarysize.jl @@ -77,7 +77,7 @@ end (ss::SummarySize)(@nospecialize obj) = _summarysize(ss, obj) # define the general case separately to make sure it is not specialized for every type @noinline function _summarysize(ss::SummarySize, @nospecialize obj) - isdefined(typeof(obj), :instance) && return 0 + issingletontype(typeof(obj)) && return 0 # NOTE: this attempts to discover multiple copies of the same immutable value, # and so is somewhat approximate. key = ccall(:jl_value_ptr, Ptr{Cvoid}, (Any,), obj) diff --git a/stdlib/Serialization/src/Serialization.jl b/stdlib/Serialization/src/Serialization.jl index a3a57be9e90b80..ce9220f7864a7d 100644 --- a/stdlib/Serialization/src/Serialization.jl +++ b/stdlib/Serialization/src/Serialization.jl @@ -1323,7 +1323,7 @@ function deserialize_typename(s::AbstractSerializer, number) tn.max_methods = maxm if has_instance ty = ty::DataType - if !isdefined(ty, :instance) + if !Base.issingletontype(ty) singleton = ccall(:jl_new_struct, Any, (Any, Any...), ty) # use setfield! directly to avoid `fieldtype` lowering expecting to see a Singleton object already on ty ccall(:jl_set_nth_field, Cvoid, (Any, Csize_t, Any), ty, Base.fieldindex(DataType, :instance)-1, singleton) diff --git a/test/compiler/contextual.jl b/test/compiler/contextual.jl index b2f51b20475633..7e6ebe8b620799 100644 --- a/test/compiler/contextual.jl +++ b/test/compiler/contextual.jl @@ -70,7 +70,7 @@ module MiniCassette end function overdub_generator(self, c, f, args) - if !isdefined(f, :instance) + if !Base.issingletontype(f) return :(return f(args...)) end diff --git a/test/core.jl b/test/core.jl index 801058a0b87eb1..ec292262db6a0a 100644 --- a/test/core.jl +++ b/test/core.jl @@ -4870,8 +4870,8 @@ end let a = Val{Val{TypeVar(:_, Int)}}, b = Val{Val{x} where x<:Int} - @test !isdefined(a, :instance) - @test isdefined(b, :instance) + @test !Base.issingletontype(a) + @test Base.issingletontype(b) @test Base.isconcretetype(b) end From 59965205ccbdffb4e25e1b60f651ca9df79230a4 Mon Sep 17 00:00:00 2001 From: Simon Byrne Date: Mon, 21 Nov 2022 13:31:33 -0800 Subject: [PATCH 010/387] update MPFR (#47659) checksums updated via approach described in #47174. --- deps/checksums/mpfr | 68 ++++++++++++++++---------------- deps/mpfr.version | 6 +-- stdlib/MPFR_jll/Project.toml | 2 +- stdlib/MPFR_jll/test/runtests.jl | 2 +- 4 files changed, 37 insertions(+), 41 deletions(-) diff --git a/deps/checksums/mpfr b/deps/checksums/mpfr index d00b0133d36ea6..99c02301251d8a 100644 --- a/deps/checksums/mpfr +++ b/deps/checksums/mpfr @@ -1,34 +1,34 @@ -MPFR.v4.1.1+3.aarch64-apple-darwin.tar.gz/md5/cd774c829cb5d5f9908ef84966af75f0 -MPFR.v4.1.1+3.aarch64-apple-darwin.tar.gz/sha512/c20ba17da62facb9bd60dea6fd400a075027c1bb5ebb5c7d0e333dc348b72f17e1de3edca24dd74178ae98c86591d2c817a69e866fd2a8f6b10ee097091f8ffd -MPFR.v4.1.1+3.aarch64-linux-gnu.tar.gz/md5/b99df82089eb79447b8f17eed56c87eb -MPFR.v4.1.1+3.aarch64-linux-gnu.tar.gz/sha512/9935bda1d37a7947808c887e10762fc71307027c698a7b871cc02ae87c2f41cffee0400f453ae9940899bba515f104ea7a81610801919e2c74bdb67703756d7a -MPFR.v4.1.1+3.aarch64-linux-musl.tar.gz/md5/e41f04255e53a24f66c75a40c0d17279 -MPFR.v4.1.1+3.aarch64-linux-musl.tar.gz/sha512/56d08924f8ec0e2f48b8d052d4687b14230737e045ba2c70325271c07987212671254902229189e7ae6cabc80cd88613e442aec0a1ab6e191d4844a86cf9b2b0 -MPFR.v4.1.1+3.armv6l-linux-gnueabihf.tar.gz/md5/c20bb7471bffff3bcd6b2db75ec9dda6 -MPFR.v4.1.1+3.armv6l-linux-gnueabihf.tar.gz/sha512/33ba51c8f0a2412d99c747128d4c813e8b31872cc50e4b9edb133645aa1b993b84e174ffc63c61861e41527400ae22fc7cfb5983feaab3cd31ab1f7e412e8e91 -MPFR.v4.1.1+3.armv6l-linux-musleabihf.tar.gz/md5/acbf8b461679b65a72bb3c7973ac6d8a -MPFR.v4.1.1+3.armv6l-linux-musleabihf.tar.gz/sha512/2cac950fa45c09a316e71583c541b3cb9e556ac771807f2482c0051b43141eefb803974e4f9f57113e911992a5d2510ef783b9970b8eef000869d61b10a3ad8f -MPFR.v4.1.1+3.armv7l-linux-gnueabihf.tar.gz/md5/5fce89c30bb9e59b97cbe061b27b1b15 -MPFR.v4.1.1+3.armv7l-linux-gnueabihf.tar.gz/sha512/e18267b2cbc7860c7121211ab8b081bf065b8b35831368df23b51b03a980f5083e505bafbc0351c6e8e7dd6d7d94c592c36b840e577a738116c83f2e93e2054c -MPFR.v4.1.1+3.armv7l-linux-musleabihf.tar.gz/md5/39a5e85cdcb8752b67aa4e4b6a756ae6 -MPFR.v4.1.1+3.armv7l-linux-musleabihf.tar.gz/sha512/a4ef907c80959c372f1b733457a9240e9a90879cd2eace95dc916f4234a430d8c1a7eb6bf7879be8fb016b59fea96bee47933c7f51f553e0351ab0ac578cc46b -MPFR.v4.1.1+3.i686-linux-gnu.tar.gz/md5/542abb1baf47807320049d484aa1ad5b -MPFR.v4.1.1+3.i686-linux-gnu.tar.gz/sha512/dc0fe265d3b89658d75bdedf53b3ee23250d7314d70d9d3ccbafe4172d0493da89620e39c48e60f5f7e56daf60226c4a7a814df5b213e4df71d6c29edab82012 -MPFR.v4.1.1+3.i686-linux-musl.tar.gz/md5/a32e2396d9410d4308141d1cbf9eb761 -MPFR.v4.1.1+3.i686-linux-musl.tar.gz/sha512/533981ce319d06bc4569a094f82d00f80e01e1336b52d95b79ac0dcc225bb08ce3593f261ab5b7c450e5596016b5ef906177eb96fc0e321ba95d54b5f1f9ce2e -MPFR.v4.1.1+3.i686-w64-mingw32.tar.gz/md5/20255e7daea1ea2b0f4edf7425545687 -MPFR.v4.1.1+3.i686-w64-mingw32.tar.gz/sha512/69f96bcc85ee53ca7ea0cc46cb719e9ee4dfdddd604e744bcf9668ae9217f00a9a039d2f9a065734038da716f4699f3d21cfcd2c56e209ddd57a1761f5005782 -MPFR.v4.1.1+3.powerpc64le-linux-gnu.tar.gz/md5/b791927fce9e496624b4edd38fd84b28 -MPFR.v4.1.1+3.powerpc64le-linux-gnu.tar.gz/sha512/b65e9fe22d0b7816203e000b3135ed9cf10345ad490ec15c792f14126a60ad362593567d9bb24d91b6602c3a9134a087d25a75f1719bfbd3f2ebaf2af32409e4 -MPFR.v4.1.1+3.x86_64-apple-darwin.tar.gz/md5/a17d9b178bc7c8f3705067464892d3e1 -MPFR.v4.1.1+3.x86_64-apple-darwin.tar.gz/sha512/e1a5c93212779ff9b66e7169cd33e47645d8256ea29ef4fb8f2bb98f9f7b2da38b7e11194e5be4386b9f16ce452a654b714f9bc62a214b93a05cb3e7cc9bcb1c -MPFR.v4.1.1+3.x86_64-linux-gnu.tar.gz/md5/bba0619a653df1ef6d780991d5afd161 -MPFR.v4.1.1+3.x86_64-linux-gnu.tar.gz/sha512/0a07cb559cb406c07ca9d209e2db6f31ea78c4e311e996dd47d670900d35ef305961d1c10aea04b63cf149d129f41d994e8a410ca06a2eb93e6c23443a3aff10 -MPFR.v4.1.1+3.x86_64-linux-musl.tar.gz/md5/e48473dc33f5da91649e1f96f39f7c9f -MPFR.v4.1.1+3.x86_64-linux-musl.tar.gz/sha512/f0df45dce81051283d7663c1457a805559810df921c215ec9e1a7415fe5f6ab398f2ae2215ed71916a48aa955b986f3f1050df41390b1f8fbb33c7cdb85ff716 -MPFR.v4.1.1+3.x86_64-unknown-freebsd.tar.gz/md5/5e667fc1528a594658792696e36dc8b7 -MPFR.v4.1.1+3.x86_64-unknown-freebsd.tar.gz/sha512/2e6bf53e01d2bd99a2cdba057b59aaa827d08e049083172abc5c2d71b280307c5a6439ea2d68b8d306787255ee23e429ef68ac8f9c7ffb846e0ec32f59cc43c0 -MPFR.v4.1.1+3.x86_64-w64-mingw32.tar.gz/md5/c4a704a8b1ca6a37824f6e6c17991f27 -MPFR.v4.1.1+3.x86_64-w64-mingw32.tar.gz/sha512/4b9c7af4d8ec6780fd88fa6f5284b909eb9ed1d81efac5cf525f60ac32ccf7bc1ad970bde42f273f9c9ced9e12c3c6a21dd9f8a67510c06919285ae9e85f0e2a -mpfr-4.1.0.tar.bz2/md5/44b892bc5a45bafb4294d134e13aad1d -mpfr-4.1.0.tar.bz2/sha512/410208ee0d48474c1c10d3d4a59decd2dfa187064183b09358ec4c4666e34d74383128436b404123b831e585d81a9176b24c7ced9d913967c5fce35d4040a0b4 +MPFR.v4.1.1+4.aarch64-apple-darwin.tar.gz/md5/07c92f3104cf508189292287719e77df +MPFR.v4.1.1+4.aarch64-apple-darwin.tar.gz/sha512/75f828f39091abcb8c8742ba7ea2bea2affb1644848a4272ec39081d6ad1399f027c3371f922d424c5d8bc72b78b408ce78f53a3c7b784140b2002f140684665 +MPFR.v4.1.1+4.aarch64-linux-gnu.tar.gz/md5/a6f60de83c161fa401c5a49c283ee94a +MPFR.v4.1.1+4.aarch64-linux-gnu.tar.gz/sha512/1c3f52d0f3c9005f2290a7a632458486972f768a9772a55ec59438f5010441768e1351a1a23e4a0b1f341b038324ceea0032b1efc0a0ad017aacbf70cde2cafb +MPFR.v4.1.1+4.aarch64-linux-musl.tar.gz/md5/8e6bc4cf8b94bdbd08ec7428d29f75b7 +MPFR.v4.1.1+4.aarch64-linux-musl.tar.gz/sha512/08489b81aa665bb2eb62c6c804c1c041c90587a0df6004a10017a3490c4ad049511dcca29cf38dbaada44fbf783b2bd1a788797dc16f128adce77bef4ec9a4a3 +MPFR.v4.1.1+4.armv6l-linux-gnueabihf.tar.gz/md5/f6f7f3f264e7b48ee9a518f21a7249f5 +MPFR.v4.1.1+4.armv6l-linux-gnueabihf.tar.gz/sha512/3b45907a1de70fcddf5a0fb90ce45d5dabf09f11b92e1174e2779a79b0991c75b1c9037981b0cd999f32cebfc358d311377af71130222a5b81dbb43c0a9ebe76 +MPFR.v4.1.1+4.armv6l-linux-musleabihf.tar.gz/md5/07304ab9676c39c56aad073f2825fd1d +MPFR.v4.1.1+4.armv6l-linux-musleabihf.tar.gz/sha512/3c7a872aab1baa4d1966cbf42cc09799944d319441f41df560632f5e4d9af9c71de25c714faab223aa1cf4e5ae09ff68c514d073711b07758e033cd492bf7eb7 +MPFR.v4.1.1+4.armv7l-linux-gnueabihf.tar.gz/md5/261482058f90306858833156bb332281 +MPFR.v4.1.1+4.armv7l-linux-gnueabihf.tar.gz/sha512/c0acb7f476a736360763e269fb7b309b9f8843d19a9931694bb01efe77e6fe4f12c969d9ae0e16c16cb14cd9a0d67ff91fa02ba141c3f2f7b908170cac216800 +MPFR.v4.1.1+4.armv7l-linux-musleabihf.tar.gz/md5/c61c6d04f3d4147b76480867e90d2262 +MPFR.v4.1.1+4.armv7l-linux-musleabihf.tar.gz/sha512/3e6cc63c7404899de3d4e4da208c40e363f427ce1bd4f0c1d5d04711870239240a8b98e4d152f6d78128e4430f703ab0debe6c35e6cd8ef80aa4a605105d619f +MPFR.v4.1.1+4.i686-linux-gnu.tar.gz/md5/0dff053d5488f220f94a56beae0bf4a4 +MPFR.v4.1.1+4.i686-linux-gnu.tar.gz/sha512/26c5c4b91998f5bcffcf5a873c451acab376efd25e13671ec5cb4f1316d1866cf7fc841f7aff17a339326ed1730b720be8ab39349ff5cee0619891925b4eb79e +MPFR.v4.1.1+4.i686-linux-musl.tar.gz/md5/2edb5f985db6b39115f13bd05d623677 +MPFR.v4.1.1+4.i686-linux-musl.tar.gz/sha512/207f346be68458aeadc803d0466eba428b63c7ee9c654b06c00ae4a7e2bbd01ab3644f1db1ef9870730937a37e658956bdc2fdcab70d4619e149574a48a7191d +MPFR.v4.1.1+4.i686-w64-mingw32.tar.gz/md5/7228b731bfb530c48d5afe7c5f51cccc +MPFR.v4.1.1+4.i686-w64-mingw32.tar.gz/sha512/faac80db43d5c252c8d7f90a56b832b6a8bd7543465dadc57dfc8590c6eb54e49c96d6b337b4caeeba73917440be512d115b54485de73b6194f67d67e3d11dce +MPFR.v4.1.1+4.powerpc64le-linux-gnu.tar.gz/md5/27e01308e698ddd83a68cd0fdbea318b +MPFR.v4.1.1+4.powerpc64le-linux-gnu.tar.gz/sha512/48718cff4df3e16c50d7ed47fc0a693699919b9033fd31084e125d8a7abb68cecfcf6e1b34be83f4b6ada9d168a01fc653b4e33e1b5021b3143e603b560a8225 +MPFR.v4.1.1+4.x86_64-apple-darwin.tar.gz/md5/a91682cb62bd6c7f8acb36a33585867a +MPFR.v4.1.1+4.x86_64-apple-darwin.tar.gz/sha512/82d2ff90e1a8a358f2fab643dfc3ead84edc8fabcf956b7479c0a0b1005430187a5315951e1b160e843776233cb2d655b5a27cfd37691cfed42f9b89f824e525 +MPFR.v4.1.1+4.x86_64-linux-gnu.tar.gz/md5/d3a3c97177e554685882f7b9f3eb0ee8 +MPFR.v4.1.1+4.x86_64-linux-gnu.tar.gz/sha512/c7af9df8c12ea3d3f784a048aae7c630f07515b509d9d0a3e0003b9697a3370112c3507a39b442d80a5671df95c2fa6a20b446443ac4cb0d48f3108e21e0d755 +MPFR.v4.1.1+4.x86_64-linux-musl.tar.gz/md5/bda6453ee85bf43348c41ebfd4accc94 +MPFR.v4.1.1+4.x86_64-linux-musl.tar.gz/sha512/0e85dd4361a67c7fe91bf9fffaad0eddfc93d578b0452e662628124d1e7589502221f20919d442875c731f57678c87b30ccfa1e9a00a77a6b42740dce96fd410 +MPFR.v4.1.1+4.x86_64-unknown-freebsd.tar.gz/md5/b2e40a50e486991660c30985a0ee6214 +MPFR.v4.1.1+4.x86_64-unknown-freebsd.tar.gz/sha512/bfc3010b2c94384ca2050b41e08ca26b22c813c1f38b274074854430a736f0f45530ee0df36030cfa479950848d8623c4e9b07fc8de4f6fbfda31a98abc9a4c6 +MPFR.v4.1.1+4.x86_64-w64-mingw32.tar.gz/md5/1b87833f68846d342dbdf283f3d39170 +MPFR.v4.1.1+4.x86_64-w64-mingw32.tar.gz/sha512/5c85a5664b4106eae733be0a85e8ab645b93dd78983cab8741cc13451ea429cb432a783f5a3b2a815db9376eb8bf83a6649247ef028d6a7f5dab9e519a9005b4 +mpfr-4.1.1.tar.bz2/md5/48eea07f8bb60dd9bbec1ec37a749f24 +mpfr-4.1.1.tar.bz2/sha512/f0efefbfc4dec367cdab6299272062508ec80d53daa779fe05954cd626983277039a10d9d072ae686584f6ce75014ef2136e3f095128fa21fc994f7c6f33d674 diff --git a/deps/mpfr.version b/deps/mpfr.version index 63fed0f8504f4c..5e9e119e3e3a1e 100644 --- a/deps/mpfr.version +++ b/deps/mpfr.version @@ -2,8 +2,4 @@ MPFR_JLL_NAME := MPFR ## source build -MPFR_VER := 4.1.0 - -# Note: jll use a different version `4.1.1+1` ("stdlib/MPFR_jll/Project.toml") -# See notes in build_tarballs.jl -# https://github.com/JuliaPackaging/Yggdrasil/blob/3c877e18dd9bb9b2e79415e00f661a7e37b2aea9/M/MPFR/build_tarballs.jl#L40-L42 +MPFR_VER := 4.1.1 diff --git a/stdlib/MPFR_jll/Project.toml b/stdlib/MPFR_jll/Project.toml index e3b994a94e98e0..560c25a5564017 100644 --- a/stdlib/MPFR_jll/Project.toml +++ b/stdlib/MPFR_jll/Project.toml @@ -1,6 +1,6 @@ name = "MPFR_jll" uuid = "3a97d323-0669-5f0c-9066-3539efd106a3" -version = "4.1.1+3" +version = "4.1.1+4" [deps] GMP_jll = "781609d7-10c4-51f6-84f2-b8444358ff6d" diff --git a/stdlib/MPFR_jll/test/runtests.jl b/stdlib/MPFR_jll/test/runtests.jl index 68bb6d3ec40e42..31c4ed07025512 100644 --- a/stdlib/MPFR_jll/test/runtests.jl +++ b/stdlib/MPFR_jll/test/runtests.jl @@ -4,5 +4,5 @@ using Test, Libdl, MPFR_jll @testset "MPFR_jll" begin vn = VersionNumber(unsafe_string(ccall((:mpfr_get_version,libmpfr), Cstring, ()))) - @test vn == v"4.1.0" + @test vn == v"4.1.1" end From 3966d5c19510a6ab4905eec67168cac7baf9f899 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Tue, 22 Nov 2022 07:44:17 +0900 Subject: [PATCH 011/387] minor NFC cleanups on slot2ssa (#47652) --- base/compiler/ssair/slot2ssa.jl | 96 +++++++++++++++++---------------- 1 file changed, 50 insertions(+), 46 deletions(-) diff --git a/base/compiler/ssair/slot2ssa.jl b/base/compiler/ssair/slot2ssa.jl index bdf87f929a31a1..dcd776b1cebdcf 100644 --- a/base/compiler/ssair/slot2ssa.jl +++ b/base/compiler/ssair/slot2ssa.jl @@ -1,5 +1,7 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +const UnoptSlot = Union{SlotNumber, TypedSlot} + mutable struct SlotInfo defs::Vector{Int} uses::Vector{Int} @@ -15,24 +17,24 @@ function scan_entry!(result::Vector{SlotInfo}, idx::Int, @nospecialize(stmt)) push!(result[slot_id(stmt.slot)].defs, idx) return elseif isexpr(stmt, :(=)) - if isa(stmt.args[1], SlotNumber) - push!(result[slot_id(stmt.args[1])].defs, idx) + arg1 = stmt.args[1] + if isa(arg1, SlotNumber) + push!(result[slot_id(arg1)].defs, idx) end stmt = stmt.args[2] end - if isa(stmt, Union{SlotNumber, TypedSlot}) + if isa(stmt, UnoptSlot) push!(result[slot_id(stmt)].uses, idx) return end for op in userefs(stmt) val = op[] - if isa(val, Union{SlotNumber, TypedSlot}) + if isa(val, UnoptSlot) push!(result[slot_id(val)].uses, idx) end end end - function scan_slot_def_use(nargs::Int, ci::CodeInfo, code::Vector{Any}) nslots = length(ci.slotflags) result = SlotInfo[SlotInfo() for i = 1:nslots] @@ -62,13 +64,12 @@ function renumber_ssa!(@nospecialize(stmt), ssanums::Vector{SSAValue}, new_ssa:: return ssamap(val->renumber_ssa(val, ssanums, new_ssa), stmt) end -function make_ssa!(ci::CodeInfo, code::Vector{Any}, idx, slot, @nospecialize(typ)) - (idx == 0) && return Argument(slot) +function make_ssa!(ci::CodeInfo, code::Vector{Any}, idx::Int, @nospecialize(typ)) stmt = code[idx] @assert isexpr(stmt, :(=)) code[idx] = stmt.args[2] (ci.ssavaluetypes::Vector{Any})[idx] = typ - idx + return SSAValue(idx) end function new_to_regular(@nospecialize(stmt), new_offset::Int) @@ -82,7 +83,7 @@ function new_to_regular(@nospecialize(stmt), new_offset::Int) return urs[] end -function fixup_slot!(ir::IRCode, ci::CodeInfo, idx::Int, slot::Int, @nospecialize(stmt::Union{SlotNumber, TypedSlot}), @nospecialize(ssa)) +function fixup_slot!(ir::IRCode, ci::CodeInfo, idx::Int, slot::Int, stmt::UnoptSlot, @nospecialize(ssa)) # We don't really have the information here to get rid of these. # We'll do so later if ssa === UNDEF_TOKEN @@ -103,34 +104,34 @@ function fixup_slot!(ir::IRCode, ci::CodeInfo, idx::Int, slot::Int, @nospecializ @assert false # unreachable end -function fixemup!(cond, rename, ir::IRCode, ci::CodeInfo, idx::Int, @nospecialize(stmt)) - if isa(stmt, Union{SlotNumber, TypedSlot}) && cond(stmt) - return fixup_slot!(ir, ci, idx, slot_id(stmt), stmt, rename(stmt)) +function fixemup!(@specialize(slot_filter), @specialize(rename_slot), ir::IRCode, ci::CodeInfo, idx::Int, @nospecialize(stmt)) + if isa(stmt, UnoptSlot) && slot_filter(stmt) + return fixup_slot!(ir, ci, idx, slot_id(stmt), stmt, rename_slot(stmt)) end if isexpr(stmt, :(=)) - stmt.args[2] = fixemup!(cond, rename, ir, ci, idx, stmt.args[2]) + stmt.args[2] = fixemup!(slot_filter, rename_slot, ir, ci, idx, stmt.args[2]) return stmt end if isa(stmt, PhiNode) for i = 1:length(stmt.edges) isassigned(stmt.values, i) || continue val = stmt.values[i] - isa(val, Union{SlotNumber, TypedSlot}) || continue - cond(val) || continue + isa(val, UnoptSlot) || continue + slot_filter(val) || continue bb_idx = block_for_inst(ir.cfg, Int(stmt.edges[i])) from_bb_terminator = last(ir.cfg.blocks[bb_idx].stmts) - stmt.values[i] = fixup_slot!(ir, ci, from_bb_terminator, slot_id(val), val, rename(val)) + stmt.values[i] = fixup_slot!(ir, ci, from_bb_terminator, slot_id(val), val, rename_slot(val)) end return stmt end if isexpr(stmt, :isdefined) val = stmt.args[1] - if isa(val, Union{SlotNumber, TypedSlot}) + if isa(val, UnoptSlot) slot = slot_id(val) if (ci.slotflags[slot] & SLOT_USEDUNDEF) == 0 return true else - ssa = rename(val) + ssa = rename_slot(val) if ssa === UNDEF_TOKEN return false elseif !isa(ssa, SSAValue) && !isa(ssa, NewSSAValue) @@ -145,8 +146,8 @@ function fixemup!(cond, rename, ir::IRCode, ci::CodeInfo, idx::Int, @nospecializ urs = userefs(stmt) for op in urs val = op[] - if isa(val, Union{SlotNumber, TypedSlot}) && cond(val) - x = fixup_slot!(ir, ci, idx, slot_id(val), val, rename(val)) + if isa(val, UnoptSlot) && slot_filter(val) + x = fixup_slot!(ir, ci, idx, slot_id(val), val, rename_slot(val)) # We inserted an undef error node. Delete subsequent statement # to avoid confusing the optimizer if x === UNDEF_TOKEN @@ -171,12 +172,12 @@ end function fixup_uses!(ir::IRCode, ci::CodeInfo, code::Vector{Any}, uses::Vector{Int}, slot::Int, @nospecialize(ssa)) for use in uses - code[use] = fixemup!(stmt->slot_id(stmt)==slot, stmt->ssa, ir, ci, use, code[use]) + code[use] = fixemup!(x::UnoptSlot->slot_id(x)==slot, stmt::UnoptSlot->ssa, ir, ci, use, code[use]) end end function rename_uses!(ir::IRCode, ci::CodeInfo, idx::Int, @nospecialize(stmt), renames::Vector{Any}) - return fixemup!(stmt->true, stmt->renames[slot_id(stmt)], ir, ci, idx, stmt) + return fixemup!(stmt::UnoptSlot->true, stmt::UnoptSlot->renames[slot_id(stmt)], ir, ci, idx, stmt) end function strip_trailing_junk!(ci::CodeInfo, code::Vector{Any}, info::Vector{CallInfo}) @@ -655,7 +656,7 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, domtree::DomTree, else val = code[slot.defs[]].args[2] typ = typ_for_val(val, ci, ir.sptypes, slot.defs[], slottypes) - ssaval = SSAValue(make_ssa!(ci, code, slot.defs[], idx, typ)) + ssaval = make_ssa!(ci, code, slot.defs[], typ) fixup_uses!(ir, ci, code, slot.uses, idx, ssaval) end continue @@ -784,30 +785,33 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, domtree::DomTree, end code[idx] = stmt # Record a store - if isexpr(stmt, :(=)) && isa(stmt.args[1], SlotNumber) - id = slot_id(stmt.args[1]) - val = stmt.args[2] - typ = typ_for_val(val, ci, ir.sptypes, idx, slottypes) - # Having UNDEF_TOKEN appear on the RHS is possible if we're on a dead branch. - # Do something reasonable here, by marking the LHS as undef as well. - if val !== UNDEF_TOKEN - incoming_vals[id] = SSAValue(make_ssa!(ci, code, idx, id, typ)::Int) - else - code[idx] = nothing - incoming_vals[id] = UNDEF_TOKEN - end - enter_block = item - while haskey(exc_handlers, enter_block) - (; enter_block, leave_block) = exc_handlers[enter_block] - cidx = findfirst((; slot)::NewPhiCNode->slot_id(slot)==id, new_phic_nodes[leave_block]) - if cidx !== nothing - node = UpsilonNode(incoming_vals[id]) - if incoming_vals[id] === UNDEF_TOKEN - node = UpsilonNode() - typ = MaybeUndef(Union{}) + if isexpr(stmt, :(=)) + arg1 = stmt.args[1] + if isa(arg1, SlotNumber) + id = slot_id(arg1) + val = stmt.args[2] + typ = typ_for_val(val, ci, ir.sptypes, idx, slottypes) + # Having UNDEF_TOKEN appear on the RHS is possible if we're on a dead branch. + # Do something reasonable here, by marking the LHS as undef as well. + if val !== UNDEF_TOKEN + incoming_vals[id] = make_ssa!(ci, code, idx, typ) + else + code[idx] = nothing + incoming_vals[id] = UNDEF_TOKEN + end + enter_block = item + while haskey(exc_handlers, enter_block) + (; enter_block, leave_block) = exc_handlers[enter_block] + cidx = findfirst((; slot)::NewPhiCNode->slot_id(slot)==id, new_phic_nodes[leave_block]) + if cidx !== nothing + node = UpsilonNode(incoming_vals[id]) + if incoming_vals[id] === UNDEF_TOKEN + node = UpsilonNode() + typ = MaybeUndef(Union{}) + end + push!(new_phic_nodes[leave_block][cidx].node.values, + NewSSAValue(insert_node!(ir, idx, NewInstruction(node, typ), true).id - length(ir.stmts))) end - push!(new_phic_nodes[leave_block][cidx].node.values, - NewSSAValue(insert_node!(ir, idx, NewInstruction(node, typ), true).id - length(ir.stmts))) end end end From 4fa07cd0d07c0e2882a505c08a992f146d885ad4 Mon Sep 17 00:00:00 2001 From: Michael Abbott <32575566+mcabbott@users.noreply.github.com> Date: Mon, 21 Nov 2022 20:25:15 -0500 Subject: [PATCH 012/387] Add compat note for `sortperm(x; dims)` (#47657) --- base/sort.jl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/base/sort.jl b/base/sort.jl index b4227e6fb5d3ee..e995a64a9f76f7 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -1100,6 +1100,9 @@ ascending order. See also [`sortperm!`](@ref), [`partialsortperm`](@ref), [`invperm`](@ref), [`indexin`](@ref). To sort slices of an array, refer to [`sortslices`](@ref). +!!! compat "Julia 1.9" + The method accepting `dims` requires at least Julia 1.9. + # Examples ```jldoctest julia> v = [3, 1, 2]; @@ -1163,6 +1166,9 @@ end Like [`sortperm`](@ref), but accepts a preallocated index vector or array `ix` with the same `axes` as `A`. If `initialized` is `false` (the default), `ix` is initialized to contain the values `LinearIndices(A)`. +!!! compat "Julia 1.9" + The method accepting `dims` requires at least Julia 1.9. + # Examples ```jldoctest julia> v = [3, 1, 2]; p = zeros(Int, 3); From 7262534ff650dac0a03d995712ead228f8225bfc Mon Sep 17 00:00:00 2001 From: st-- Date: Tue, 22 Nov 2022 12:28:02 +0200 Subject: [PATCH 013/387] Fix mapreduce_first docstring error (#42832) The docstring of Base.mapreduce_first referred `reduce_first(op, f, x)`, but `reduce_first` is a 2-argument function, and it should refer to `mapreduce_first(f, op, x)` instead. --- base/reduce.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/base/reduce.jl b/base/reduce.jl index 467a0e55639076..ae2671a2e746aa 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -393,7 +393,7 @@ reduce_empty_iter(op, itr, ::EltypeUnknown) = throw(ArgumentError(""" The value to be returned when calling [`reduce`](@ref), [`foldl`](@ref`) or [`foldr`](@ref) with reduction `op` over an iterator which contains a single element -`x`. This value may also used to initialise the recursion, so that `reduce(op, [x, y])` +`x`. This value may also be used to initialise the recursion, so that `reduce(op, [x, y])` may call `op(reduce_first(op, x), y)`. The default is `x` for most types. The main purpose is to ensure type stability, so @@ -416,8 +416,8 @@ reduce_first(::typeof(mul_prod), x::SmallUnsigned) = UInt(x) The value to be returned when calling [`mapreduce`](@ref), [`mapfoldl`](@ref`) or [`mapfoldr`](@ref) with map `f` and reduction `op` over an iterator which contains a -single element `x`. This value may also used to initialise the recursion, so that -`mapreduce(f, op, [x, y])` may call `op(reduce_first(op, f, x), f(y))`. +single element `x`. This value may also be used to initialise the recursion, so that +`mapreduce(f, op, [x, y])` may call `op(mapreduce_first(f, op, x), f(y))`. The default is `reduce_first(op, f(x))`. """ From 95165bbc69a45e19df3859717ffdec1da93d9b82 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Tue, 22 Nov 2022 18:48:33 -0500 Subject: [PATCH 014/387] Add an inference option to assume no new bindings being added (#47674) This is currently intended for use with external AbstractInterpreters that want to be able to constant fold `:isdefined` away. We always support the `true` case, since we don't support removing bindings, but the `false` case is currently not foldable. In the future, we might partition bindings by world age, in which case we would likely get this for free, but for now, just add this as a hook for external AbstractInterpreters to use. --- base/compiler/abstractinterpretation.jl | 7 +++++++ base/compiler/types.jl | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 55866d4a637136..0fb85c4d00d1a2 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2385,10 +2385,14 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp elseif isa(sym, Symbol) if isdefined(sv.mod, sym) t = Const(true) + elseif sv.params.assume_bindings_static + t = Const(false) end elseif isa(sym, GlobalRef) if isdefined(sym.mod, sym.name) t = Const(true) + elseif sv.params.assume_bindings_static + t = Const(false) end elseif isexpr(sym, :static_parameter) n = sym.args[1]::Int @@ -2499,6 +2503,9 @@ function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, fram end elseif isdefined_globalref(g) nothrow = true + elseif isa(frame, InferenceState) && frame.params.assume_bindings_static + consistent = inaccessiblememonly = ALWAYS_TRUE + rt = Union{} end merge_effects!(interp, frame, Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly)) return rt diff --git a/base/compiler/types.jl b/base/compiler/types.jl index 52d431455447bf..ff0d4f62e4968b 100644 --- a/base/compiler/types.jl +++ b/base/compiler/types.jl @@ -147,6 +147,10 @@ struct InferenceParams # tuple contains more than this many elements MAX_TUPLE_SPLAT::Int + # Assume that no new bindings will be added, i.e. a non-existing binding + # at inference time can be assumed to always error. + assume_bindings_static::Bool + function InferenceParams(; ipo_constant_propagation::Bool = true, aggressive_constant_propagation::Bool = false, @@ -156,6 +160,7 @@ struct InferenceParams apply_union_enum::Int = 8, tupletype_depth::Int = 3, tuple_splat::Int = 32, + assume_bindings_static::Bool = false, ) return new( ipo_constant_propagation, @@ -166,6 +171,7 @@ struct InferenceParams apply_union_enum, tupletype_depth, tuple_splat, + assume_bindings_static ) end end From 3200219b1f7e2681ece9e4b99bda48586fab8a93 Mon Sep 17 00:00:00 2001 From: Petr Vana Date: Wed, 23 Nov 2022 05:02:38 +0100 Subject: [PATCH 015/387] build: add get-lld target (#47589) Fixes `make -C deps getall` --- deps/llvm.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/deps/llvm.mk b/deps/llvm.mk index 5d297b6c369bfb..c13551ee331efc 100644 --- a/deps/llvm.mk +++ b/deps/llvm.mk @@ -314,4 +314,5 @@ $(eval $(call bb-install,llvm-tools,LLVM_TOOLS,false,true)) endif # USE_BINARYBUILDER_LLVM +get-lld: get-llvm install-lld install-clang install-llvm-tools: install-llvm From 27ebaa7fd5854ae76cf68b273fafed3fe9fe4a19 Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Wed, 23 Nov 2022 16:26:11 -0300 Subject: [PATCH 016/387] Print the detailed type on heap snapshot (#47503) Fixes https://github.com/JuliaLang/julia/issues/47502 --- .gitignore | 2 +- src/gc-heap-snapshot.cpp | 48 +++++++++++++++++++++++----------------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index 2780210c41a9bd..836a35781cd6fa 100644 --- a/.gitignore +++ b/.gitignore @@ -33,7 +33,7 @@ .DS_Store .idea/* .vscode/* - +*.heapsnapshot # Buildkite: Ignore the entire .buildkite directory /.buildkite diff --git a/src/gc-heap-snapshot.cpp b/src/gc-heap-snapshot.cpp index 001f2ea74d092d..ac2a0469364525 100644 --- a/src/gc-heap-snapshot.cpp +++ b/src/gc-heap-snapshot.cpp @@ -124,7 +124,7 @@ HeapSnapshot *g_snapshot = nullptr; extern jl_mutex_t heapsnapshot_lock; void serialize_heap_snapshot(ios_t *stream, HeapSnapshot &snapshot, char all_one); -static inline void _record_gc_edge(const char *node_type, const char *edge_type, +static inline void _record_gc_edge(const char *edge_type, jl_value_t *a, jl_value_t *b, size_t name_or_index) JL_NOTSAFEPOINT; void _record_gc_just_edge(const char *edge_type, Node &from_node, size_t to_idx, size_t name_or_idx) JL_NOTSAFEPOINT; void _add_internal_root(HeapSnapshot *snapshot); @@ -185,45 +185,56 @@ size_t record_node_to_gc_snapshot(jl_value_t *a) JL_NOTSAFEPOINT // Insert a new Node size_t self_size = 0; - std::string type_name; StringRef name = ""; StringRef node_type = "object"; jl_datatype_t *type = (jl_datatype_t*)jl_typeof(a); if (jl_is_string(a)) { - node_type = "string"; + node_type = "String"; name = jl_string_data(a); self_size = jl_string_len(a); } else if (jl_is_symbol(a)) { - node_type = "symbol"; + node_type = "jl_sym_t"; name = jl_symbol_name((jl_sym_t*)a); self_size = name.size(); } else if (jl_is_simplevector(a)) { - node_type = "array"; + node_type = "jl_svec_t"; name = "SimpleVector"; self_size = sizeof(jl_svec_t) + sizeof(void*) * jl_svec_len(a); } else if (jl_is_module(a)) { + node_type = "jl_module_t"; name = jl_symbol_name_(((_jl_module_t*)a)->name); self_size = sizeof(jl_module_t); } else if (jl_is_task(a)) { + node_type = "jl_task_t"; name = "Task"; self_size = sizeof(jl_task_t); } else if (jl_is_datatype(a)) { - type_name = string("Type{") + string(jl_symbol_name_(((_jl_datatype_t*)a)->name->name)) + string("}"); - name = StringRef(type_name); - self_size = sizeof(jl_task_t); + ios_need_close = 1; + ios_mem(&str_, 0); + JL_STREAM* str = (JL_STREAM*)&str_; + jl_static_show(str, a); + name = StringRef((const char*)str_.buf, str_.size); + node_type = "jl_datatype_t"; + self_size = sizeof(jl_datatype_t); + } + else if (jl_is_array(a)){ + ios_need_close = 1; + ios_mem(&str_, 0); + JL_STREAM* str = (JL_STREAM*)&str_; + jl_static_show(str, (jl_value_t*)type); + name = StringRef((const char*)str_.buf, str_.size); + node_type = "jl_array_t"; + self_size = sizeof(jl_array_t); } else { - self_size = jl_is_array_type(type) - ? sizeof(jl_array_t) - : (size_t)jl_datatype_size(type); - + self_size = (size_t)jl_datatype_size(type); // print full type into ios buffer and get StringRef to it. // The ios is cleaned up below. ios_need_close = 1; @@ -371,13 +382,13 @@ void _gc_heap_snapshot_record_frame_to_frame_edge(jl_gcframe_t *from, jl_gcframe void _gc_heap_snapshot_record_array_edge(jl_value_t *from, jl_value_t *to, size_t index) JL_NOTSAFEPOINT { - _record_gc_edge("array", "element", from, to, index); + _record_gc_edge("element", from, to, index); } void _gc_heap_snapshot_record_object_edge(jl_value_t *from, jl_value_t *to, void *slot) JL_NOTSAFEPOINT { string path = _fieldpath_for_slot(from, slot); - _record_gc_edge("object", "property", from, to, + _record_gc_edge("property", from, to, g_snapshot->names.find_or_create_string_id(path)); } @@ -395,7 +406,6 @@ void _gc_heap_snapshot_record_module_to_binding(jl_module_t* module, jl_binding_ auto &from_node = g_snapshot->nodes[from_node_idx]; auto &to_node = g_snapshot->nodes[to_node_idx]; - from_node.type = g_snapshot->node_types.find_or_create_string_id("object"); _record_gc_just_edge("property", from_node, to_node_idx, g_snapshot->names.find_or_create_string_id("")); if (value_idx) _record_gc_just_edge("internal", to_node, value_idx, g_snapshot->names.find_or_create_string_id("value")); @@ -405,7 +415,7 @@ void _gc_heap_snapshot_record_module_to_binding(jl_module_t* module, jl_binding_ void _gc_heap_snapshot_record_internal_array_edge(jl_value_t *from, jl_value_t *to) JL_NOTSAFEPOINT { - _record_gc_edge("object", "internal", from, to, + _record_gc_edge("internal", from, to, g_snapshot->names.find_or_create_string_id("")); } @@ -432,19 +442,17 @@ void _gc_heap_snapshot_record_hidden_edge(jl_value_t *from, void* to, size_t byt } auto to_node_idx = record_pointer_to_gc_snapshot(to, bytes, alloc_kind); auto &from_node = g_snapshot->nodes[from_node_idx]; - from_node.type = g_snapshot->node_types.find_or_create_string_id("native"); _record_gc_just_edge("hidden", from_node, to_node_idx, name_or_idx); } -static inline void _record_gc_edge(const char *node_type, const char *edge_type, - jl_value_t *a, jl_value_t *b, size_t name_or_idx) JL_NOTSAFEPOINT +static inline void _record_gc_edge(const char *edge_type, jl_value_t *a, + jl_value_t *b, size_t name_or_idx) JL_NOTSAFEPOINT { auto from_node_idx = record_node_to_gc_snapshot(a); auto to_node_idx = record_node_to_gc_snapshot(b); auto &from_node = g_snapshot->nodes[from_node_idx]; - from_node.type = g_snapshot->node_types.find_or_create_string_id(node_type); _record_gc_just_edge(edge_type, from_node, to_node_idx, name_or_idx); } From 113efb6e0aa27879cb423ab323c0159911e4c5e7 Mon Sep 17 00:00:00 2001 From: pchintalapudi <34727397+pchintalapudi@users.noreply.github.com> Date: Wed, 23 Nov 2022 17:11:39 -0500 Subject: [PATCH 017/387] Remove typeinfer lock altogether (#46825) * Remove typeinfer lock altogether * Don't remove the typeinf lock functions * Track reentrancy in current task state * Fix up some git status * Initialize task variables * Promise that jl_typeinf_func is rooted somewhere --- base/compiler/typeinfer.jl | 4 +--- base/loading.jl | 2 +- doc/src/devdocs/locks.md | 1 + src/aotcompile.cpp | 3 +++ src/dump.c | 15 ++++++++++++-- src/gf.c | 42 ++++++++++++++++---------------------- src/jitlayers.cpp | 30 +++++++++++++++++---------- src/julia.h | 4 ++++ src/julia_internal.h | 2 +- src/task.c | 4 ++++ 10 files changed, 65 insertions(+), 42 deletions(-) diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 87d8870ce33945..1a13cc051944e2 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -407,9 +407,7 @@ function cache_result!(interp::AbstractInterpreter, result::InferenceResult) if track_newly_inferred[] m = linfo.def if isa(m, Method) && m.module != Core - ccall(:jl_typeinf_lock_begin, Cvoid, ()) - push!(newly_inferred, linfo) - ccall(:jl_typeinf_lock_end, Cvoid, ()) + ccall(:jl_push_newly_inferred, Cvoid, (Any,), linfo) end end end diff --git a/base/loading.jl b/base/loading.jl index 1a933b274b7de8..a5df7c24408ae2 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -1662,6 +1662,7 @@ function include_package_for_output(pkg::PkgId, input::String, depot_path::Vecto task_local_storage()[:SOURCE_PATH] = source end + ccall(:jl_set_newly_inferred, Cvoid, (Any,), Core.Compiler.newly_inferred) Core.Compiler.track_newly_inferred.x = true try Base.include(Base.__toplevel__, input) @@ -1672,7 +1673,6 @@ function include_package_for_output(pkg::PkgId, input::String, depot_path::Vecto finally Core.Compiler.track_newly_inferred.x = false end - ccall(:jl_set_newly_inferred, Cvoid, (Any,), Core.Compiler.newly_inferred) end const PRECOMPILE_TRACE_COMPILE = Ref{String}() diff --git a/doc/src/devdocs/locks.md b/doc/src/devdocs/locks.md index f2ddc26fb954de..9b2d992d8f5bb4 100644 --- a/doc/src/devdocs/locks.md +++ b/doc/src/devdocs/locks.md @@ -42,6 +42,7 @@ The following is a leaf lock (level 2), and only acquires level 1 locks (safepoi > * typecache > * Module->lock > * JLDebuginfoPlugin::PluginMutex +> * newly_inferred_mutex The following is a level 3 lock, which can only acquire level 1 or level 2 locks internally: diff --git a/src/aotcompile.cpp b/src/aotcompile.cpp index 2714bc664eb575..83e1c6d150430e 100644 --- a/src/aotcompile.cpp +++ b/src/aotcompile.cpp @@ -274,6 +274,8 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm jl_code_info_t *src = NULL; JL_GC_PUSH1(&src); JL_LOCK(&jl_codegen_lock); + auto ct = jl_current_task; + ct->reentrant_codegen++; orc::ThreadSafeContext ctx; orc::ThreadSafeModule backing; if (!llvmmod) { @@ -425,6 +427,7 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm if (ctx.getContext()) { jl_ExecutionEngine->releaseContext(std::move(ctx)); } + ct->reentrant_codegen--; JL_UNLOCK(&jl_codegen_lock); // Might GC return (void*)data; } diff --git a/src/dump.c b/src/dump.c index 2a32d40e7a2a39..96c875c4ec7f59 100644 --- a/src/dump.c +++ b/src/dump.c @@ -158,6 +158,8 @@ static htable_t external_mis; // Inference tracks newly-inferred MethodInstances during precompilation // and registers them by calling jl_set_newly_inferred static jl_array_t *newly_inferred JL_GLOBALLY_ROOTED; +// Mutex for newly_inferred +static jl_mutex_t newly_inferred_mutex; // New roots to add to Methods. These can't be added until after // recaching is complete, so we have to hold on to them separately @@ -2894,14 +2896,23 @@ JL_DLLEXPORT void jl_init_restored_modules(jl_array_t *init_order) // --- entry points --- -// Register all newly-inferred MethodInstances -// This gets called as the final step of Base.include_package_for_output +// Register array of newly-inferred MethodInstances +// This gets called as the first step of Base.include_package_for_output JL_DLLEXPORT void jl_set_newly_inferred(jl_value_t* _newly_inferred) { assert(_newly_inferred == NULL || jl_is_array(_newly_inferred)); newly_inferred = (jl_array_t*) _newly_inferred; } +JL_DLLEXPORT void jl_push_newly_inferred(jl_value_t* linfo) +{ + JL_LOCK(&newly_inferred_mutex); + size_t end = jl_array_len(newly_inferred); + jl_array_grow_end(newly_inferred, 1); + jl_arrayset(newly_inferred, linfo, end); + JL_UNLOCK(&newly_inferred_mutex); +} + // Serialize the modules in `worklist` to file `fname` JL_DLLEXPORT int jl_save_incremental(const char *fname, jl_array_t *worklist) { diff --git a/src/gf.c b/src/gf.c index 191a9321ba8057..0bce672ca729cc 100644 --- a/src/gf.c +++ b/src/gf.c @@ -279,8 +279,8 @@ jl_code_info_t *jl_type_infer(jl_method_instance_t *mi, size_t world, int force) JL_TIMING(INFERENCE); if (jl_typeinf_func == NULL) return NULL; - static int in_inference; - if (in_inference > 2) + jl_task_t *ct = jl_current_task; + if (ct->reentrant_inference > 2) return NULL; jl_code_info_t *src = NULL; @@ -300,7 +300,6 @@ jl_code_info_t *jl_type_infer(jl_method_instance_t *mi, size_t world, int force) jl_printf(JL_STDERR, "\n"); } #endif - jl_task_t *ct = jl_current_task; int last_errno = errno; #ifdef _OS_WINDOWS_ DWORD last_error = GetLastError(); @@ -308,7 +307,7 @@ jl_code_info_t *jl_type_infer(jl_method_instance_t *mi, size_t world, int force) size_t last_age = ct->world_age; ct->world_age = jl_typeinf_world; mi->inInference = 1; - in_inference++; + ct->reentrant_inference++; JL_TRY { src = (jl_code_info_t*)jl_apply(fargs, 3); } @@ -329,7 +328,7 @@ jl_code_info_t *jl_type_infer(jl_method_instance_t *mi, size_t world, int force) src = NULL; } ct->world_age = last_age; - in_inference--; + ct->reentrant_inference--; mi->inInference = 0; #ifdef _OS_WINDOWS_ SetLastError(last_error); @@ -544,7 +543,7 @@ static int reset_mt_caches(jl_methtable_t *mt, void *env) } -jl_function_t *jl_typeinf_func = NULL; +jl_function_t *jl_typeinf_func JL_GLOBALLY_ROOTED = NULL; JL_DLLEXPORT size_t jl_typeinf_world = 1; JL_DLLEXPORT void jl_set_typeinf_func(jl_value_t *f) @@ -3416,44 +3415,39 @@ int jl_has_concrete_subtype(jl_value_t *typ) return ((jl_datatype_t*)typ)->has_concrete_subtype; } -// TODO: separate the codegen and typeinf locks -// currently using a coarser lock seems like -// the best way to avoid acquisition priority -// ordering violations -//static jl_mutex_t typeinf_lock; #define typeinf_lock jl_codegen_lock -static jl_mutex_t inference_timing_mutex; -static uint64_t inference_start_time = 0; -static uint8_t inference_is_measuring_compile_time = 0; - JL_DLLEXPORT void jl_typeinf_timing_begin(void) { if (jl_atomic_load_relaxed(&jl_measure_compile_time_enabled)) { - JL_LOCK_NOGC(&inference_timing_mutex); - if (inference_is_measuring_compile_time++ == 0) { - inference_start_time = jl_hrtime(); - } - JL_UNLOCK_NOGC(&inference_timing_mutex); + jl_task_t *ct = jl_current_task; + if (ct->inference_start_time == 0 && ct->reentrant_inference == 1) + ct->inference_start_time = jl_hrtime(); } } JL_DLLEXPORT void jl_typeinf_timing_end(void) { - JL_LOCK_NOGC(&inference_timing_mutex); - if (--inference_is_measuring_compile_time == 0) { - jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - inference_start_time)); + jl_task_t *ct = jl_current_task; + if (ct->inference_start_time != 0 && ct->reentrant_inference == 1) { + jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - ct->inference_start_time)); + ct->inference_start_time = 0; } - JL_UNLOCK_NOGC(&inference_timing_mutex); } JL_DLLEXPORT void jl_typeinf_lock_begin(void) { JL_LOCK(&typeinf_lock); + //Although this is claiming to be a typeinfer lock, it is actually + //affecting the codegen lock count, not type inference's inferencing count + jl_task_t *ct = jl_current_task; + ct->reentrant_codegen++; } JL_DLLEXPORT void jl_typeinf_lock_end(void) { + jl_task_t *ct = jl_current_task; + ct->reentrant_codegen--; JL_UNLOCK(&typeinf_lock); } diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index e612c39ca97d22..b6a30d3380b27e 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -295,7 +295,8 @@ const char *jl_generate_ccallable(LLVMOrcThreadSafeModuleRef llvmmod, void *sysi extern "C" JL_DLLEXPORT int jl_compile_extern_c_impl(LLVMOrcThreadSafeModuleRef llvmmod, void *p, void *sysimg, jl_value_t *declrt, jl_value_t *sigt) { - JL_LOCK(&jl_codegen_lock); + auto ct = jl_current_task; + ct->reentrant_codegen++; uint64_t compiler_start_time = 0; uint8_t measure_compile_time_enabled = jl_atomic_load_relaxed(&jl_measure_compile_time_enabled); if (measure_compile_time_enabled) @@ -311,6 +312,7 @@ int jl_compile_extern_c_impl(LLVMOrcThreadSafeModuleRef llvmmod, void *p, void * backing = jl_create_llvm_module("cextern", pparams ? pparams->tsctx : ctx, pparams ? pparams->imaging : imaging_default()); into = &backing; } + JL_LOCK(&jl_codegen_lock); jl_codegen_params_t params(into->getContext()); if (pparams == NULL) pparams = ¶ms; @@ -330,12 +332,12 @@ int jl_compile_extern_c_impl(LLVMOrcThreadSafeModuleRef llvmmod, void *p, void * if (success && llvmmod == NULL) jl_ExecutionEngine->addModule(std::move(*into)); } - if (jl_codegen_lock.count == 1 && measure_compile_time_enabled) + JL_UNLOCK(&jl_codegen_lock); + if (!--ct->reentrant_codegen && measure_compile_time_enabled) jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); if (ctx.getContext()) { jl_ExecutionEngine->releaseContext(std::move(ctx)); } - JL_UNLOCK(&jl_codegen_lock); return success; } @@ -386,7 +388,8 @@ void jl_extern_c_impl(jl_value_t *declrt, jl_tupletype_t *sigt) extern "C" JL_DLLEXPORT jl_code_instance_t *jl_generate_fptr_impl(jl_method_instance_t *mi JL_PROPAGATES_ROOT, size_t world) { - JL_LOCK(&jl_codegen_lock); // also disables finalizers, to prevent any unexpected recursion + auto ct = jl_current_task; + ct->reentrant_codegen++; uint64_t compiler_start_time = 0; uint8_t measure_compile_time_enabled = jl_atomic_load_relaxed(&jl_measure_compile_time_enabled); bool is_recompile = false; @@ -395,6 +398,7 @@ jl_code_instance_t *jl_generate_fptr_impl(jl_method_instance_t *mi JL_PROPAGATES // if we don't have any decls already, try to generate it now jl_code_info_t *src = NULL; JL_GC_PUSH1(&src); + JL_LOCK(&jl_codegen_lock); // also disables finalizers, to prevent any unexpected recursion jl_value_t *ci = jl_rettype_inferred(mi, world, world); jl_code_instance_t *codeinst = (ci == jl_nothing ? NULL : (jl_code_instance_t*)ci); if (codeinst) { @@ -437,13 +441,13 @@ jl_code_instance_t *jl_generate_fptr_impl(jl_method_instance_t *mi JL_PROPAGATES else { codeinst = NULL; } - if (jl_codegen_lock.count == 1 && measure_compile_time_enabled) { + JL_UNLOCK(&jl_codegen_lock); + if (!--ct->reentrant_codegen && measure_compile_time_enabled) { uint64_t t_comp = jl_hrtime() - compiler_start_time; if (is_recompile) jl_atomic_fetch_add_relaxed(&jl_cumulative_recompile_time, t_comp); jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, t_comp); } - JL_UNLOCK(&jl_codegen_lock); JL_GC_POP(); return codeinst; } @@ -454,11 +458,13 @@ void jl_generate_fptr_for_unspecialized_impl(jl_code_instance_t *unspec) if (jl_atomic_load_relaxed(&unspec->invoke) != NULL) { return; } - JL_LOCK(&jl_codegen_lock); + auto ct = jl_current_task; + ct->reentrant_codegen++; uint64_t compiler_start_time = 0; uint8_t measure_compile_time_enabled = jl_atomic_load_relaxed(&jl_measure_compile_time_enabled); if (measure_compile_time_enabled) compiler_start_time = jl_hrtime(); + JL_LOCK(&jl_codegen_lock); if (jl_atomic_load_relaxed(&unspec->invoke) == NULL) { jl_code_info_t *src = NULL; JL_GC_PUSH1(&src); @@ -486,9 +492,9 @@ void jl_generate_fptr_for_unspecialized_impl(jl_code_instance_t *unspec) } JL_GC_POP(); } - if (jl_codegen_lock.count == 1 && measure_compile_time_enabled) - jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); JL_UNLOCK(&jl_codegen_lock); // Might GC + if (!--ct->reentrant_codegen && measure_compile_time_enabled) + jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); } @@ -508,11 +514,13 @@ jl_value_t *jl_dump_method_asm_impl(jl_method_instance_t *mi, size_t world, // normally we prevent native code from being generated for these functions, // (using sentinel value `1` instead) // so create an exception here so we can print pretty our lies - JL_LOCK(&jl_codegen_lock); // also disables finalizers, to prevent any unexpected recursion + auto ct = jl_current_task; + ct->reentrant_codegen++; uint64_t compiler_start_time = 0; uint8_t measure_compile_time_enabled = jl_atomic_load_relaxed(&jl_measure_compile_time_enabled); if (measure_compile_time_enabled) compiler_start_time = jl_hrtime(); + JL_LOCK(&jl_codegen_lock); // also disables finalizers, to prevent any unexpected recursion specfptr = (uintptr_t)jl_atomic_load_relaxed(&codeinst->specptr.fptr); if (specfptr == 0) { jl_code_info_t *src = jl_type_infer(mi, world, 0); @@ -536,7 +544,7 @@ jl_value_t *jl_dump_method_asm_impl(jl_method_instance_t *mi, size_t world, } JL_GC_POP(); } - if (measure_compile_time_enabled) + if (!--ct->reentrant_codegen && measure_compile_time_enabled) jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); JL_UNLOCK(&jl_codegen_lock); } diff --git a/src/julia.h b/src/julia.h index 8e17fdc1edf175..1ec6fe2bd39bf5 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1768,6 +1768,7 @@ JL_DLLEXPORT void jl_save_system_image(const char *fname); JL_DLLEXPORT void jl_restore_system_image(const char *fname); JL_DLLEXPORT void jl_restore_system_image_data(const char *buf, size_t len); JL_DLLEXPORT void jl_set_newly_inferred(jl_value_t *newly_inferred); +JL_DLLEXPORT void jl_push_newly_inferred(jl_value_t *linfo); JL_DLLEXPORT int jl_save_incremental(const char *fname, jl_array_t *worklist); JL_DLLEXPORT jl_value_t *jl_restore_incremental(const char *fname, jl_array_t *depmods); JL_DLLEXPORT jl_value_t *jl_restore_incremental_from_buf(const char *buf, size_t sz, jl_array_t *depmods); @@ -1938,6 +1939,9 @@ typedef struct _jl_task_t { jl_ucontext_t ctx; void *stkbuf; // malloc'd memory (either copybuf or stack) size_t bufsz; // actual sizeof stkbuf + uint64_t inference_start_time; // time when inference started + unsigned int reentrant_inference; // How many times we've reentered inference + unsigned int reentrant_codegen; // How many times we've reentered codegen unsigned int copy_stack:31; // sizeof stack for copybuf unsigned int started:1; } jl_task_t; diff --git a/src/julia_internal.h b/src/julia_internal.h index 94c0f0dc0a6205..f1929892df5513 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -292,7 +292,7 @@ void print_func_loc(JL_STREAM *s, jl_method_t *m); extern jl_array_t *_jl_debug_method_invalidation JL_GLOBALLY_ROOTED; extern JL_DLLEXPORT size_t jl_page_size; -extern jl_function_t *jl_typeinf_func; +extern jl_function_t *jl_typeinf_func JL_GLOBALLY_ROOTED; extern JL_DLLEXPORT size_t jl_typeinf_world; extern _Atomic(jl_typemap_entry_t*) call_cache[N_CALL_CACHE] JL_GLOBALLY_ROOTED; extern jl_array_t *jl_all_methods JL_GLOBALLY_ROOTED; diff --git a/src/task.c b/src/task.c index 1f7bf027f032c7..81b90a832e2dd7 100644 --- a/src/task.c +++ b/src/task.c @@ -938,6 +938,8 @@ JL_DLLEXPORT jl_task_t *jl_new_task(jl_function_t *start, jl_value_t *completion t->threadpoolid = ct->threadpoolid; t->ptls = NULL; t->world_age = ct->world_age; + t->reentrant_codegen = 0; + t->reentrant_inference = 0; #ifdef COPY_STACKS if (!t->copy_stack) { @@ -1523,6 +1525,8 @@ jl_task_t *jl_init_root_task(jl_ptls_t ptls, void *stack_lo, void *stack_hi) ct->sticky = 1; ct->ptls = ptls; ct->world_age = 1; // OK to run Julia code on this task + ct->reentrant_codegen = 0; + ct->reentrant_inference = 0; ptls->root_task = ct; jl_atomic_store_relaxed(&ptls->current_task, ct); JL_GC_PROMISE_ROOTED(ct); From 25b27468ea515e54de1b7aa6a521eb333415c4b4 Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Thu, 24 Nov 2022 10:33:32 +0100 Subject: [PATCH 018/387] LinearAlgebra: Speed up the trace function (#47585) --- stdlib/LinearAlgebra/src/adjtrans.jl | 3 +++ stdlib/LinearAlgebra/src/bidiag.jl | 2 ++ stdlib/LinearAlgebra/src/dense.jl | 2 +- stdlib/LinearAlgebra/src/symmetric.jl | 1 + stdlib/LinearAlgebra/src/triangular.jl | 5 +++++ stdlib/LinearAlgebra/src/tridiag.jl | 4 ++++ stdlib/LinearAlgebra/test/adjtrans.jl | 7 +++++++ stdlib/LinearAlgebra/test/bidiag.jl | 11 +++++++++++ stdlib/LinearAlgebra/test/triangular.jl | 3 +++ stdlib/LinearAlgebra/test/tridiag.jl | 7 +++++++ 10 files changed, 44 insertions(+), 1 deletion(-) diff --git a/stdlib/LinearAlgebra/src/adjtrans.jl b/stdlib/LinearAlgebra/src/adjtrans.jl index ef815b3ad708b4..058b1992f66258 100644 --- a/stdlib/LinearAlgebra/src/adjtrans.jl +++ b/stdlib/LinearAlgebra/src/adjtrans.jl @@ -414,6 +414,9 @@ switch_dim12(B::AbstractArray) = PermutedDimsArray(B, (2, 1, ntuple(Base.Fix1(+, (-)(A::Adjoint) = Adjoint( -A.parent) (-)(A::Transpose) = Transpose(-A.parent) +tr(A::Adjoint) = adjoint(tr(parent(A))) +tr(A::Transpose) = transpose(tr(parent(A))) + ## multiplication * function _dot_nonrecursive(u, v) diff --git a/stdlib/LinearAlgebra/src/bidiag.jl b/stdlib/LinearAlgebra/src/bidiag.jl index a452fe43987d4e..218fd67a1b9d22 100644 --- a/stdlib/LinearAlgebra/src/bidiag.jl +++ b/stdlib/LinearAlgebra/src/bidiag.jl @@ -208,6 +208,8 @@ convert(::Type{T}, m::AbstractMatrix) where {T<:Bidiagonal} = m isa T ? m : T(m) similar(B::Bidiagonal, ::Type{T}) where {T} = Bidiagonal(similar(B.dv, T), similar(B.ev, T), B.uplo) similar(B::Bidiagonal, ::Type{T}, dims::Union{Dims{1},Dims{2}}) where {T} = zeros(T, dims...) +tr(B::Bidiagonal) = sum(B.dv) + function kron(A::Diagonal, B::Bidiagonal) # `_droplast!` is only guaranteed to work with `Vector` kdv = _makevector(kron(diag(A), B.dv)) diff --git a/stdlib/LinearAlgebra/src/dense.jl b/stdlib/LinearAlgebra/src/dense.jl index 12a77d7a662d92..7f5e44382f5c5d 100644 --- a/stdlib/LinearAlgebra/src/dense.jl +++ b/stdlib/LinearAlgebra/src/dense.jl @@ -344,7 +344,7 @@ diagm(m::Integer, n::Integer, v::AbstractVector) = diagm(m, n, 0 => v) function tr(A::Matrix{T}) where T n = checksquare(A) t = zero(T) - for i=1:n + @inbounds @simd for i in 1:n t += A[i,i] end t diff --git a/stdlib/LinearAlgebra/src/symmetric.jl b/stdlib/LinearAlgebra/src/symmetric.jl index 24da88ad20e87a..038188139aa300 100644 --- a/stdlib/LinearAlgebra/src/symmetric.jl +++ b/stdlib/LinearAlgebra/src/symmetric.jl @@ -363,6 +363,7 @@ Base.copy(A::Adjoint{<:Any,<:Symmetric}) = Base.copy(A::Transpose{<:Any,<:Hermitian}) = Hermitian(copy(transpose(A.parent.data)), ifelse(A.parent.uplo == 'U', :L, :U)) +tr(A::Symmetric) = tr(A.data) # to avoid AbstractMatrix fallback (incl. allocations) tr(A::Hermitian) = real(tr(A.data)) Base.conj(A::HermOrSym) = typeof(A)(conj(A.data), A.uplo) diff --git a/stdlib/LinearAlgebra/src/triangular.jl b/stdlib/LinearAlgebra/src/triangular.jl index 08793402204826..bd21471deeedd8 100644 --- a/stdlib/LinearAlgebra/src/triangular.jl +++ b/stdlib/LinearAlgebra/src/triangular.jl @@ -428,6 +428,11 @@ function -(A::UnitUpperTriangular) UpperTriangular(Anew) end +tr(A::LowerTriangular) = tr(A.data) +tr(A::UnitLowerTriangular) = size(A, 1) * oneunit(eltype(A)) +tr(A::UpperTriangular) = tr(A.data) +tr(A::UnitUpperTriangular) = size(A, 1) * oneunit(eltype(A)) + # copy and scale function copyto!(A::T, B::T) where T<:Union{UpperTriangular,UnitUpperTriangular} n = size(B,1) diff --git a/stdlib/LinearAlgebra/src/tridiag.jl b/stdlib/LinearAlgebra/src/tridiag.jl index 649ab12ab50349..e43e9e699e3a99 100644 --- a/stdlib/LinearAlgebra/src/tridiag.jl +++ b/stdlib/LinearAlgebra/src/tridiag.jl @@ -172,6 +172,8 @@ Base.copy(S::Adjoint{<:Any,<:SymTridiagonal}) = SymTridiagonal(map(x -> copy.(ad ishermitian(S::SymTridiagonal) = isreal(S.dv) && isreal(_evview(S)) issymmetric(S::SymTridiagonal) = true +tr(S::SymTridiagonal) = sum(S.dv) + function diag(M::SymTridiagonal{T}, n::Integer=0) where T<:Number # every branch call similar(..., ::Int) to make sure the # same vector type is returned independent of n @@ -747,6 +749,8 @@ function triu!(M::Tridiagonal{T}, k::Integer=0) where T return M end +tr(M::Tridiagonal) = sum(M.d) + ################### # Generic methods # ################### diff --git a/stdlib/LinearAlgebra/test/adjtrans.jl b/stdlib/LinearAlgebra/test/adjtrans.jl index e96ea28531d379..7479057d9f0279 100644 --- a/stdlib/LinearAlgebra/test/adjtrans.jl +++ b/stdlib/LinearAlgebra/test/adjtrans.jl @@ -636,4 +636,11 @@ end @test mapreduce(string, *, [1 2; 3 4]') == mapreduce(string, *, copy([1 2; 3 4]')) == "1234" end +@testset "trace" begin + for T in (Float64, ComplexF64), t in (adjoint, transpose) + A = randn(T, 10, 10) + @test tr(t(A)) == tr(copy(t(A))) == t(tr(A)) + end +end + end # module TestAdjointTranspose diff --git a/stdlib/LinearAlgebra/test/bidiag.jl b/stdlib/LinearAlgebra/test/bidiag.jl index 22c070be13cb52..9866fce047dd17 100644 --- a/stdlib/LinearAlgebra/test/bidiag.jl +++ b/stdlib/LinearAlgebra/test/bidiag.jl @@ -218,6 +218,17 @@ Random.seed!(1) end end + @testset "trace" begin + for uplo in (:U, :L) + B = Bidiagonal(dv, ev, uplo) + if relty <: Integer + @test tr(B) == tr(Matrix(B)) + else + @test tr(B) ≈ tr(Matrix(B)) rtol=2eps(relty) + end + end + end + Tfull = Array(T) @testset "Linear solves" begin if relty <: AbstractFloat diff --git a/stdlib/LinearAlgebra/test/triangular.jl b/stdlib/LinearAlgebra/test/triangular.jl index 4475dde1e543bc..8c9f6494205a67 100644 --- a/stdlib/LinearAlgebra/test/triangular.jl +++ b/stdlib/LinearAlgebra/test/triangular.jl @@ -169,6 +169,9 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo # diag @test diag(A1) == diag(Matrix(A1)) + # tr + @test tr(A1)::elty1 == tr(Matrix(A1)) + # real @test real(A1) == real(Matrix(A1)) @test imag(A1) == imag(Matrix(A1)) diff --git a/stdlib/LinearAlgebra/test/tridiag.jl b/stdlib/LinearAlgebra/test/tridiag.jl index 0fcd8744142be3..590870d4dad0a6 100644 --- a/stdlib/LinearAlgebra/test/tridiag.jl +++ b/stdlib/LinearAlgebra/test/tridiag.jl @@ -263,6 +263,13 @@ end @test (@inferred diag(GA))::typeof(GenericArray(d)) == GenericArray(d) @test (@inferred diag(GA, -1))::typeof(GenericArray(d)) == GenericArray(dl) end + @testset "trace" begin + if real(elty) <: Integer + @test tr(A) == tr(fA) + else + @test tr(A) ≈ tr(fA) rtol=2eps(real(elty)) + end + end @testset "Idempotent tests" begin for func in (conj, transpose, adjoint) @test func(func(A)) == A From 726bbd7afda4373e10b8ab1eac9dfb53c81c8755 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Thu, 24 Nov 2022 12:30:00 +0100 Subject: [PATCH 019/387] Fix regression in generic_bitcast with Union{} arguments. (#47605) --- src/intrinsics.cpp | 7 ++++++- test/compiler/codegen.jl | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/intrinsics.cpp b/src/intrinsics.cpp index 7893a376645087..38d923cb5a99e0 100644 --- a/src/intrinsics.cpp +++ b/src/intrinsics.cpp @@ -1134,7 +1134,12 @@ static jl_cgval_t emit_intrinsic(jl_codectx_t &ctx, intrinsic f, jl_value_t **ar jl_cgval_t *argv = (jl_cgval_t*)alloca(sizeof(jl_cgval_t) * nargs); for (size_t i = 0; i < nargs; ++i) { - argv[i] = emit_expr(ctx, args[i + 1]); + jl_cgval_t arg = emit_expr(ctx, args[i + 1]); + if (arg.typ == jl_bottom_type) { + // intrinsics generally don't handle buttom values, so bail out early + return jl_cgval_t(); + } + argv[i] = arg; } // this forces everything to use runtime-intrinsics (e.g. for testing) diff --git a/test/compiler/codegen.jl b/test/compiler/codegen.jl index 4bc7eb8f6d8565..11cbd21b793a11 100644 --- a/test/compiler/codegen.jl +++ b/test/compiler/codegen.jl @@ -785,3 +785,8 @@ f_isa_type(@nospecialize(x)) = isa(x, Type) # Issue #47247 f47247(a::Ref{Int}, b::Nothing) = setfield!(a, :x, b) @test_throws TypeError f47247(Ref(5), nothing) + +@testset "regression in generic_bitcast: should support Union{} values" begin + f(x) = Core.bitcast(UInt64, x) + @test occursin("llvm.trap", get_llvm(f, Tuple{Union{}})) +end From d0a211a9209d25b1297693c562fc3a679204a0c6 Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Thu, 24 Nov 2022 07:42:49 -0800 Subject: [PATCH 020/387] Filesystem: `rm(; recursive=true)` should ignore `UV_EACCES` (#47668) The command-line program `rm` has no problem deleting an empty directory that we do not have listing permissions on, so we should follow suit. Example: ``` mktempdir() do dir mkpath("$(dir)/foo") chmod("$(dir)/foo", 0o200) rm(dir; recursive=true) end ``` --- base/file.jl | 2 +- test/file.jl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/base/file.jl b/base/file.jl index d57b17354eb1fe..b761e1d65ccb5e 100644 --- a/base/file.jl +++ b/base/file.jl @@ -294,7 +294,7 @@ function rm(path::AbstractString; force::Bool=false, recursive::Bool=false) rm(joinpath(path, p), force=force, recursive=true) end catch err - if !(force && isa(err, IOError) && err.code==Base.UV_EACCES) + if !(isa(err, IOError) && err.code==Base.UV_EACCES) rethrow(err) end end diff --git a/test/file.jl b/test/file.jl index c0cdc0a8eacd59..7ca49fe3a065bb 100644 --- a/test/file.jl +++ b/test/file.jl @@ -1520,11 +1520,11 @@ if !Sys.iswindows() chmod(joinpath(d, "empty_outer", "empty_inner"), 0o333) # Test that an empty directory, even when we can't read its contents, is deletable - rm(joinpath(d, "empty_outer"); recursive=true, force=true) + rm(joinpath(d, "empty_outer"); recursive=true) @test !isdir(joinpath(d, "empty_outer")) # But a non-empty directory is not - @test_throws Base.IOError rm(joinpath(d, "nonempty"); recursive=true, force=true) + @test_throws Base.IOError rm(joinpath(d, "nonempty"); recursive=true) chmod(joinpath(d, "nonempty"), 0o777) rm(joinpath(d, "nonempty"); recursive=true, force=true) @test !isdir(joinpath(d, "nonempty")) From 039d8fda7740aa9fa78ae0024900ef66b3979032 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Fri, 25 Nov 2022 18:11:50 +0900 Subject: [PATCH 021/387] effects: add `:removable` convenient setting for `Base.@assume_effects` (#47700) This setting is similar to `:foldable` but for dead call elimination. --- base/expr.jl | 14 +++++++++++++- test/compiler/effects.jl | 8 ++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/base/expr.jl b/base/expr.jl index 376ed43ba52f00..769b1faa0d24db 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -436,6 +436,7 @@ The following `setting`s are supported. - `:notaskstate` - `:inaccessiblememonly` - `:foldable` +- `:removable` - `:total` # Extended help @@ -596,7 +597,6 @@ global state or mutable memory pointed to by its arguments. This setting is a convenient shortcut for the set of effects that the compiler requires to be guaranteed to constant fold a call at compile time. It is currently equivalent to the following `setting`s: - - `:consistent` - `:effect_free` - `:terminates_globally` @@ -607,6 +607,16 @@ currently equivalent to the following `setting`s: however, that by the `:consistent`-cy requirements, any such annotated call must consistently throw given the same argument values. +--- +## `:removable` + +This setting is a convenient shortcut for the set of effects that the compiler +requires to be guaranteed to delete a call whose result is unused at compile time. +It is currently equivalent to the following `setting`s: +- `:effect_free` +- `:nothrow` +- `:terminates_globally` + --- ## `:total` @@ -666,6 +676,8 @@ macro assume_effects(args...) inaccessiblememonly = val elseif setting === :foldable consistent = effect_free = terminates_globally = val + elseif setting === :removable + effect_free = nothrow = terminates_globally = val elseif setting === :total consistent = effect_free = nothrow = terminates_globally = notaskstate = inaccessiblememonly = val else diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl index 1a5043f49ddba6..54ce22421dc47d 100644 --- a/test/compiler/effects.jl +++ b/test/compiler/effects.jl @@ -63,6 +63,14 @@ Base.@assume_effects :foldable concrete_eval( concrete_eval(getindex, ___CONST_DICT___, :a) end +# :removable override +Base.@assume_effects :removable removable_call( + f, args...; kwargs...) = f(args...; kwargs...) +@test fully_eliminated() do + @noinline removable_call(getindex, ___CONST_DICT___, :a) + nothing +end + # terminates_globally override # https://github.com/JuliaLang/julia/issues/41694 Base.@assume_effects :terminates_globally function issue41694(x) From 9d25932dbad468d8432c9fc197b03035e13dbbda Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Fri, 25 Nov 2022 18:14:36 +0900 Subject: [PATCH 022/387] sort out argument order of `maybe_erase_unused!` (#47701) --- base/compiler/ssair/ir.jl | 38 +++++++++++++++-------------------- base/compiler/ssair/passes.jl | 10 ++++++--- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index 9e337ab94f8d9b..0f86945b15b88a 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -562,7 +562,6 @@ end insert_node!(ir::IRCode, pos::Int, newinst::NewInstruction, attach_after::Bool=false) = insert_node!(ir, SSAValue(pos), newinst, attach_after) - mutable struct IncrementalCompact ir::IRCode result::InstructionStream @@ -1606,20 +1605,18 @@ function iterate_compact(compact::IncrementalCompact) return Pair{Int,Int}(compact.idx-1, old_result_idx) end -function maybe_erase_unused!( - extra_worklist::Vector{Int}, compact::IncrementalCompact, idx::Int, in_worklist::Bool, - callback = null_dce_callback) - - inst = idx <= length(compact.result) ? compact.result[idx] : - compact.new_new_nodes.stmts[idx - length(compact.result)] +maybe_erase_unused!(compact::IncrementalCompact, idx::Int, in_worklist::Bool, extra_worklist::Vector{Int}) = + maybe_erase_unused!(null_dce_callback, compact, idx, in_worklist, extra_worklist) +function maybe_erase_unused!(callback::Function, compact::IncrementalCompact, idx::Int, + in_worklist::Bool, extra_worklist::Vector{Int}) + nresult = length(compact.result) + inst = idx ≤ nresult ? compact.result[idx] : compact.new_new_nodes.stmts[idx-nresult] stmt = inst[:inst] stmt === nothing && return false - if inst[:type] === Bottom - effect_free = false - else - effect_free = inst[:flag] & IR_FLAG_EFFECT_FREE != 0 - end - function kill_ssa_value(val::SSAValue) + inst[:type] === Bottom && return false + effect_free = (inst[:flag] & IR_FLAG_EFFECT_FREE) ≠ 0 + effect_free || return false + foreachssa(stmt) do val::SSAValue if compact.used_ssas[val.id] == 1 if val.id < idx || in_worklist push!(extra_worklist, val.id) @@ -1628,12 +1625,8 @@ function maybe_erase_unused!( compact.used_ssas[val.id] -= 1 callback(val) end - if effect_free - foreachssa(kill_ssa_value, stmt) - inst[:inst] = nothing - return true - end - return false + inst[:inst] = nothing + return true end struct FixedNode @@ -1722,16 +1715,17 @@ function just_fixup!(compact::IncrementalCompact, new_new_nodes_offset::Union{In end end -function simple_dce!(compact::IncrementalCompact, callback = null_dce_callback) +simple_dce!(compact::IncrementalCompact) = simple_dce!(null_dce_callback, compact) +function simple_dce!(callback::Function, compact::IncrementalCompact) # Perform simple DCE for unused values @assert isempty(compact.new_new_used_ssas) # just_fixup! wasn't run? extra_worklist = Int[] for (idx, nused) in Iterators.enumerate(compact.used_ssas) nused == 0 || continue - maybe_erase_unused!(extra_worklist, compact, idx, false, callback) + maybe_erase_unused!(callback, compact, idx, false, extra_worklist) end while !isempty(extra_worklist) - maybe_erase_unused!(extra_worklist, compact, pop!(extra_worklist), true, callback) + maybe_erase_unused!(callback, compact, pop!(extra_worklist), true, extra_worklist) end end diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index 115dd6153dde3b..4b5ee76c28bacc 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -1053,7 +1053,9 @@ function sroa_pass!(ir::IRCode, inlining::Union{Nothing, InliningState} = nothin # but before the DCE) for our predicate within `sroa_mutables!`, but we also # try an extra effort using a callback so that reference counts are updated used_ssas = copy(compact.used_ssas) - simple_dce!(compact, (x::SSAValue) -> used_ssas[x.id] -= 1) + simple_dce!(compact) do x::SSAValue + used_ssas[x.id] -= 1 + end ir = complete(compact) sroa_mutables!(ir, defuses, used_ssas, lazydomtree, inlining) return ir @@ -1485,9 +1487,11 @@ end function adce_erase!(phi_uses::Vector{Int}, extra_worklist::Vector{Int}, compact::IncrementalCompact, idx::Int, in_worklist::Bool) # return whether this made a change if isa(compact.result[idx][:inst], PhiNode) - return maybe_erase_unused!(extra_worklist, compact, idx, in_worklist, val::SSAValue -> phi_uses[val.id] -= 1) + return maybe_erase_unused!(compact, idx, in_worklist, extra_worklist) do val::SSAValue + phi_uses[val.id] -= 1 + end else - return maybe_erase_unused!(extra_worklist, compact, idx, in_worklist) + return maybe_erase_unused!(compact, idx, in_worklist, extra_worklist) end end From 04214ece63e472fada63f2ff961b5d10d925669a Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Fri, 25 Nov 2022 11:49:03 +0100 Subject: [PATCH 023/387] add string literal continuation syntax to docs for `"` (#47690) --- base/docs/basedocs.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index 36e6d5ab398a12..033d0fcb0ec5ef 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -3020,7 +3020,7 @@ QuoteNode """ " -`"` Is used to delimit string literals. +`"` Is used to delimit string literals. A trailing `\\` can be used to continue a string literal on the next line. # Examples @@ -3030,6 +3030,10 @@ julia> "Hello World!" julia> "Hello World!\\n" "Hello World!\\n" + +julia> "Hello \\ + World" +"Hello World" ``` See also [`\"""`](@ref \"\"\"). From 02aa0b08665c5d5ff34ec344c21ba17c0f8d6a07 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Sat, 26 Nov 2022 06:47:30 +0600 Subject: [PATCH 024/387] Fix overflow in pow5 (#47511) Fixup for #46764 --- base/ryu/utils.jl | 2 +- test/ryu.jl | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/base/ryu/utils.jl b/base/ryu/utils.jl index e87d245aa4ee8f..4fe0b7d397d074 100644 --- a/base/ryu/utils.jl +++ b/base/ryu/utils.jl @@ -64,7 +64,7 @@ lengthforindex(idx) = div(((Int64(16 * idx) * 1292913986) >> 32) + 1 + 16 + 8, 9 Return `true` if `5^p` is a divisor of `x`. """ -pow5(x, p) = x % (5^p) == 0 +pow5(x, p) = x % (UInt64(5)^p) == 0 """ Ryu.pow2(x, p) diff --git a/test/ryu.jl b/test/ryu.jl index cf60e4867e2362..0b10bd7e49ba5d 100644 --- a/test/ryu.jl +++ b/test/ryu.jl @@ -52,6 +52,11 @@ end @test "2.305843009213694e40" == Ryu.writeshortest(Core.bitcast(Float64, 0x4850F0CF064DD592)) end +@testset "pow5 overflow (#47464)" begin + @test "4.6458339e+63" == Ryu.writeexp(4.645833859177319e63, 7) + @test "4.190673780e+40" == Ryu.writeexp(4.190673779576499e40, 9) +end + @testset "OutputLength" begin @test "1.0" == Ryu.writeshortest(1.0) # already tested in Basic @test "1.2" == Ryu.writeshortest(1.2) From 88a0627003c45ddac304b7be933c93caae8ae6b3 Mon Sep 17 00:00:00 2001 From: pchintalapudi <34727397+pchintalapudi@users.noreply.github.com> Date: Sat, 26 Nov 2022 04:58:10 -0500 Subject: [PATCH 025/387] Fix and simplify inference timing logic (#47711) * Fix and simplify inference timing logic * Reduce task struct size --- src/gf.c | 14 ++++++++------ src/julia.h | 4 ++-- src/task.c | 2 ++ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/gf.c b/src/gf.c index 0bce672ca729cc..0e98f2a140d4a1 100644 --- a/src/gf.c +++ b/src/gf.c @@ -3419,18 +3419,20 @@ int jl_has_concrete_subtype(jl_value_t *typ) JL_DLLEXPORT void jl_typeinf_timing_begin(void) { - if (jl_atomic_load_relaxed(&jl_measure_compile_time_enabled)) { - jl_task_t *ct = jl_current_task; - if (ct->inference_start_time == 0 && ct->reentrant_inference == 1) - ct->inference_start_time = jl_hrtime(); + jl_task_t *ct = jl_current_task; + if (ct->reentrant_inference == 1) { + ct->inference_start_time = jl_hrtime(); } } JL_DLLEXPORT void jl_typeinf_timing_end(void) { jl_task_t *ct = jl_current_task; - if (ct->inference_start_time != 0 && ct->reentrant_inference == 1) { - jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - ct->inference_start_time)); + if (ct->reentrant_inference == 1) { + if (jl_atomic_load_relaxed(&jl_measure_compile_time_enabled)) { + uint64_t inftime = jl_hrtime() - ct->inference_start_time; + jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, inftime); + } ct->inference_start_time = 0; } } diff --git a/src/julia.h b/src/julia.h index 1ec6fe2bd39bf5..981e6a0ee8232e 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1940,8 +1940,8 @@ typedef struct _jl_task_t { void *stkbuf; // malloc'd memory (either copybuf or stack) size_t bufsz; // actual sizeof stkbuf uint64_t inference_start_time; // time when inference started - unsigned int reentrant_inference; // How many times we've reentered inference - unsigned int reentrant_codegen; // How many times we've reentered codegen + uint16_t reentrant_inference; // How many times we've reentered inference + uint16_t reentrant_codegen; // How many times we've reentered codegen unsigned int copy_stack:31; // sizeof stack for copybuf unsigned int started:1; } jl_task_t; diff --git a/src/task.c b/src/task.c index 81b90a832e2dd7..a5ebc1ce26005d 100644 --- a/src/task.c +++ b/src/task.c @@ -940,6 +940,7 @@ JL_DLLEXPORT jl_task_t *jl_new_task(jl_function_t *start, jl_value_t *completion t->world_age = ct->world_age; t->reentrant_codegen = 0; t->reentrant_inference = 0; + t->inference_start_time = 0; #ifdef COPY_STACKS if (!t->copy_stack) { @@ -1527,6 +1528,7 @@ jl_task_t *jl_init_root_task(jl_ptls_t ptls, void *stack_lo, void *stack_hi) ct->world_age = 1; // OK to run Julia code on this task ct->reentrant_codegen = 0; ct->reentrant_inference = 0; + ct->inference_start_time = 0; ptls->root_task = ct; jl_atomic_store_relaxed(&ptls->current_task, ct); JL_GC_PROMISE_ROOTED(ct); From 60668c547083ea4a7b6edc4deb99efc2e62755d1 Mon Sep 17 00:00:00 2001 From: pchintalapudi <34727397+pchintalapudi@users.noreply.github.com> Date: Sat, 26 Nov 2022 20:35:55 -0500 Subject: [PATCH 026/387] Add loose compilation time test (#47716) --- test/misc.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/misc.jl b/test/misc.jl index 78b7c4175e11ba..ee7f59bf673595 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -354,6 +354,10 @@ end after_comp, after_recomp = Base.cumulative_compile_time_ns() # no need to turn timing off, @time will do that @test after_comp >= before_comp; +# should be approximately 60,000,000 ns, we definitely shouldn't exceed 100x that value +# failing this probably means an uninitialized variable somewhere +@test after_comp - before_comp < 6_000_000_000; + end # redirect_stdout macro capture_stdout(ex) From 5495b8d67a66720559cfd8c13ebb315a80e4e579 Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Sun, 27 Nov 2022 18:21:46 -0500 Subject: [PATCH 027/387] Fix GCExt test (#47699) * Add test/gcext to out-of-tree * Disable gcext test that uses jl_gc_internal_obj_base_ptr --- Makefile | 2 +- src/julia_gcext.h | 2 ++ test/gcext/LocalTest.jl | 20 ++++++++++---------- test/gcext/Makefile | 2 +- test/gcext/gcext-test.jl | 11 +++++++---- test/gcext/gcext.c | 1 + 6 files changed, 22 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index 3f81263b26ab2e..ad9dcac6bbb7df 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ all: debug release # sort is used to remove potential duplicates DIRS := $(sort $(build_bindir) $(build_depsbindir) $(build_libdir) $(build_private_libdir) $(build_libexecdir) $(build_includedir) $(build_includedir)/julia $(build_sysconfdir)/julia $(build_datarootdir)/julia $(build_datarootdir)/julia/stdlib $(build_man1dir)) ifneq ($(BUILDROOT),$(JULIAHOME)) -BUILDDIRS := $(BUILDROOT) $(addprefix $(BUILDROOT)/,base src src/flisp src/support src/clangsa cli doc deps stdlib test test/clangsa test/embedding test/llvmpasses) +BUILDDIRS := $(BUILDROOT) $(addprefix $(BUILDROOT)/,base src src/flisp src/support src/clangsa cli doc deps stdlib test test/clangsa test/embedding test/gcext test/llvmpasses) BUILDDIRMAKE := $(addsuffix /Makefile,$(BUILDDIRS)) $(BUILDROOT)/sysimage.mk DIRS += $(BUILDDIRS) $(BUILDDIRMAKE): | $(BUILDDIRS) diff --git a/src/julia_gcext.h b/src/julia_gcext.h index 65231984747714..669e80d069fa40 100644 --- a/src/julia_gcext.h +++ b/src/julia_gcext.h @@ -120,6 +120,8 @@ JL_DLLEXPORT int jl_gc_conservative_gc_support_enabled(void); // external allocations may not all be valid objects and that for those, // the user *must* validate that they have a proper type, i.e. that // jl_typeof(obj) is an actual type object. +// +// NOTE: Only valid to call from within a GC context. JL_DLLEXPORT jl_value_t *jl_gc_internal_obj_base_ptr(void *p); // Return a non-null pointer to the start of the stack area if the task diff --git a/test/gcext/LocalTest.jl b/test/gcext/LocalTest.jl index f73b4b47e80237..e2ee94e7653218 100644 --- a/test/gcext/LocalTest.jl +++ b/test/gcext/LocalTest.jl @@ -54,13 +54,13 @@ function set_aux_root(n :: Int, x :: String) return ccall(:set_aux_root, Nothing, (UInt, String), n, x) end -function internal_obj_scan(p :: Any) - if ccall(:internal_obj_scan, Cint, (Any,), p) == 0 - global internal_obj_scan_failures += 1 - end -end +# function internal_obj_scan(p :: Any) +# if ccall(:internal_obj_scan, Cint, (Any,), p) == 0 +# global internal_obj_scan_failures += 1 +# end +# end -global internal_obj_scan_failures = 0 +# global internal_obj_scan_failures = 0 for i in 0:1000 set_aux_root(i, string(i)) @@ -70,12 +70,12 @@ function test() local stack = make() for i in 1:100000 push(stack, string(i, base=2)) - internal_obj_scan(top(stack)) + # internal_obj_scan(top(stack)) end for i in 1:1000 local stack2 = make() - internal_obj_scan(stack2) - internal_obj_scan(blob(stack2)) + # internal_obj_scan(stack2) + # internal_obj_scan(blob(stack2)) while !empty(stack) push(stack2, pop(stack)) end @@ -98,5 +98,5 @@ end print(gc_counter_full(), " full collections.\n") print(gc_counter_inc(), " partial collections.\n") print(num_obj_sweeps(), " object sweeps.\n") -print(internal_obj_scan_failures, " internal object scan failures.\n") +# print(internal_obj_scan_failures, " internal object scan failures.\n") print(corrupted_roots, " corrupted auxiliary roots.\n") diff --git a/test/gcext/Makefile b/test/gcext/Makefile index 7cb602572e3c55..b3314d1f9b32b8 100644 --- a/test/gcext/Makefile +++ b/test/gcext/Makefile @@ -41,7 +41,7 @@ $(BIN)/gcext-debug$(EXE): $(SRCDIR)/gcext.c ifneq ($(abspath $(BIN)),$(abspath $(SRCDIR))) # for demonstration purposes, our demo code is also installed # in $BIN, although this would likely not be typical -$(BIN)/LocalModule.jl: $(SRCDIR)/LocalModule.jl +$(BIN)/LocalTest.jl: $(SRCDIR)/LocalTest.jl cp $< $@ endif diff --git a/test/gcext/gcext-test.jl b/test/gcext/gcext-test.jl index e6f3e3663ff0e9..0dc9bbadd92b52 100644 --- a/test/gcext/gcext-test.jl +++ b/test/gcext/gcext-test.jl @@ -31,12 +31,15 @@ end errlines = fetch(err_task) lines = fetch(out_task) @test length(errlines) == 0 - @test length(lines) == 6 + # @test length(lines) == 6 + @test length(lines) == 5 @test checknum(lines[2], r"([0-9]+) full collections", n -> n >= 10) @test checknum(lines[3], r"([0-9]+) partial collections", n -> n > 0) @test checknum(lines[4], r"([0-9]+) object sweeps", n -> n > 0) - @test checknum(lines[5], r"([0-9]+) internal object scan failures", - n -> n == 0) - @test checknum(lines[6], r"([0-9]+) corrupted auxiliary roots", + # @test checknum(lines[5], r"([0-9]+) internal object scan failures", + # n -> n == 0) + # @test checknum(lines[6], r"([0-9]+) corrupted auxiliary roots", + # n -> n == 0) + @test checknum(lines[5], r"([0-9]+) corrupted auxiliary roots", n -> n == 0) end diff --git a/test/gcext/gcext.c b/test/gcext/gcext.c index 7f2986d8f1f577..842d6004ab9656 100644 --- a/test/gcext/gcext.c +++ b/test/gcext/gcext.c @@ -307,6 +307,7 @@ static size_t gc_alloc_size(jl_value_t *val) int internal_obj_scan(jl_value_t *val) { + // FIXME: `jl_gc_internal_obj_base_ptr` is not allowed to be called from outside GC if (jl_gc_internal_obj_base_ptr(val) == val) { size_t size = gc_alloc_size(val); char *addr = (char *)val; From 42ac4b10814d123f953e6eb6fdd6d24edbdf12a9 Mon Sep 17 00:00:00 2001 From: t-bltg Date: Mon, 28 Nov 2022 07:18:13 +0100 Subject: [PATCH 028/387] `Test`: fix `allowed_undefineds` typo (#47572) --- stdlib/Test/src/Test.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index e58e154d4d61a3..ae3a9a57c84a3c 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -1904,8 +1904,8 @@ be suppressed by supplying a collection of `GlobalRef`s for which the warning can be skipped. For example, setting ``` -allow_undefineds = Set([GlobalRef(Base, :active_repl), - GlobalRef(Base, :active_repl_backend)]) +allowed_undefineds = Set([GlobalRef(Base, :active_repl), + GlobalRef(Base, :active_repl_backend)]) ``` would suppress warnings about `Base.active_repl` and From f9662b8c4e1fd97ed59bff1fcbd13889967d98e3 Mon Sep 17 00:00:00 2001 From: Eugene Toder Date: Mon, 28 Nov 2022 01:24:11 -0500 Subject: [PATCH 029/387] Remove dead code in Base.retry (#47718) y === nothing is always false inside of while y !== nothing loop. --- base/error.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/base/error.jl b/base/error.jl index 07f66aa5cf6d27..4e9be0e172d61d 100644 --- a/base/error.jl +++ b/base/error.jl @@ -295,7 +295,6 @@ function retry(f; delays=ExponentialBackOff(), check=nothing) try return f(args...; kwargs...) catch e - y === nothing && rethrow() if check !== nothing result = check(state, e) state, retry_or_not = length(result) == 2 ? result : (state, result) From b34fe1d6955cca6be6dbc2706d2c251f01b25cb1 Mon Sep 17 00:00:00 2001 From: Jakob Nybo Nissen Date: Mon, 28 Nov 2022 07:26:07 +0100 Subject: [PATCH 030/387] Allow crc32c hashing of SubString{String} (#47693) --- base/util.jl | 4 +++- stdlib/CRC32c/src/CRC32c.jl | 2 +- stdlib/CRC32c/test/runtests.jl | 10 ++++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/base/util.jl b/base/util.jl index cef62587be05fe..3ae75a7f58e281 100644 --- a/base/util.jl +++ b/base/util.jl @@ -469,7 +469,9 @@ _crc32c(a::NTuple{<:Any, UInt8}, crc::UInt32=0x00000000) = _crc32c(a::Union{Array{UInt8},FastContiguousSubArray{UInt8,N,<:Array{UInt8}} where N}, crc::UInt32=0x00000000) = unsafe_crc32c(a, length(a) % Csize_t, crc) -_crc32c(s::String, crc::UInt32=0x00000000) = unsafe_crc32c(s, sizeof(s) % Csize_t, crc) +function _crc32c(s::Union{String, SubString{String}}, crc::UInt32=0x00000000) + unsafe_crc32c(s, sizeof(s) % Csize_t, crc) +end function _crc32c(io::IO, nb::Integer, crc::UInt32=0x00000000) nb < 0 && throw(ArgumentError("number of bytes to checksum must be ≥ 0, got $nb")) diff --git a/stdlib/CRC32c/src/CRC32c.jl b/stdlib/CRC32c/src/CRC32c.jl index 42a5f468a88866..35d2d4cb339d69 100644 --- a/stdlib/CRC32c/src/CRC32c.jl +++ b/stdlib/CRC32c/src/CRC32c.jl @@ -36,7 +36,7 @@ function crc32c end crc32c(a::Union{Array{UInt8},FastContiguousSubArray{UInt8,N,<:Array{UInt8}} where N}, crc::UInt32=0x00000000) = Base._crc32c(a, crc) -crc32c(s::String, crc::UInt32=0x00000000) = Base._crc32c(s, crc) +crc32c(s::Union{String, SubString{String}}, crc::UInt32=0x00000000) = Base._crc32c(s, crc) """ crc32c(io::IO, [nb::Integer,] crc::UInt32=0x00000000) diff --git a/stdlib/CRC32c/test/runtests.jl b/stdlib/CRC32c/test/runtests.jl index b385880850abcb..e9e933ee2451cc 100644 --- a/stdlib/CRC32c/test/runtests.jl +++ b/stdlib/CRC32c/test/runtests.jl @@ -6,7 +6,9 @@ using CRC32c function test_crc32c(crc32c) # CRC32c checksum (test data generated from @andrewcooke's CRC.jl package) for (n,crc) in [(0,0x00000000),(1,0xa016d052),(2,0x03f89f52),(3,0xf130f21e),(4,0x29308cf4),(5,0x53518fab),(6,0x4f4dfbab),(7,0xbd3a64dc),(8,0x46891f81),(9,0x5a14b9f9),(10,0xb219db69),(11,0xd232a91f),(12,0x51a15563),(13,0x9f92de41),(14,0x4d8ae017),(15,0xc8b74611),(16,0xa0de6714),(17,0x672c992a),(18,0xe8206eb6),(19,0xc52fd285),(20,0x327b0397),(21,0x318263dd),(22,0x08485ccd),(23,0xea44d29e),(24,0xf6c0cb13),(25,0x3969bba2),(26,0x6a8810ec),(27,0x75b3d0df),(28,0x82d535b1),(29,0xbdf7fc12),(30,0x1f836b7d),(31,0xd29f33af),(32,0x8e4acb3e),(33,0x1cbee2d1),(34,0xb25f7132),(35,0xb0fa484c),(36,0xb9d262b4),(37,0x3207fe27),(38,0xa024d7ac),(39,0x49a2e7c5),(40,0x0e2c157f),(41,0x25f7427f),(42,0x368c6adc),(43,0x75efd4a5),(44,0xa84c5c31),(45,0x0fc817b2),(46,0x8d99a881),(47,0x5cc3c078),(48,0x9983d5e2),(49,0x9267c2db),(50,0xc96d4745),(51,0x058d8df3),(52,0x453f9cf3),(53,0xb714ade1),(54,0x55d3c2bc),(55,0x495710d0),(56,0x3bddf494),(57,0x4f2577d0),(58,0xdae0f604),(59,0x3c57c632),(60,0xfe39bbb0),(61,0x6f5d1d41),(62,0x7d996665),(63,0x68c738dc),(64,0x8dfea7ae)] - @test crc32c(UInt8[1:n;]) == crc == crc32c(String(UInt8[1:n;])) + s = String(UInt8[1:n;]) + ss = SubString(String(UInt8[0:(n+1);]), 2:(n+1)) + @test crc32c(UInt8[1:n;]) == crc == crc32c(s) == crc32c(ss) end # test that crc parameter is equivalent to checksum of concatenated data, @@ -48,7 +50,11 @@ unsafe_crc32c_sw(a, n, crc) = ccall(:jl_crc32c_sw, UInt32, (UInt32, Ptr{UInt8}, Csize_t), crc, a, n) crc32c_sw(a::Union{Array{UInt8},Base.FastContiguousSubArray{UInt8,N,<:Array{UInt8}} where N}, crc::UInt32=0x00000000) = unsafe_crc32c_sw(a, length(a), crc) -crc32c_sw(s::String, crc::UInt32=0x00000000) = unsafe_crc32c_sw(s, sizeof(s), crc) + +function crc32c_sw(s::Union{String, SubString{String}}, crc::UInt32=0x00000000) + unsafe_crc32c_sw(s, sizeof(s), crc) +end + function crc32c_sw(io::IO, nb::Integer, crc::UInt32=0x00000000) nb < 0 && throw(ArgumentError("number of bytes to checksum must be ≥ 0")) buf = Vector{UInt8}(undef, min(nb, 24576)) From 99be295ebd59da2fd4ccd07e18d9a5c3f691b011 Mon Sep 17 00:00:00 2001 From: Adrian Hill Date: Mon, 28 Nov 2022 07:28:45 +0100 Subject: [PATCH 031/387] Fix length of Markdown header underlines (#47708) --- stdlib/Markdown/src/render/terminal/render.jl | 2 +- stdlib/Markdown/test/runtests.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/Markdown/src/render/terminal/render.jl b/stdlib/Markdown/src/render/terminal/render.jl index 3fd274aee2a2e4..f3764bf1e84431 100644 --- a/stdlib/Markdown/src/render/terminal/render.jl +++ b/stdlib/Markdown/src/render/terminal/render.jl @@ -88,7 +88,7 @@ function _term_header(io::IO, md, char, columns) if line_no > 1 line_width = max(line_width, div(columns, 3)) end - char != ' ' && print(io, '\n', ' '^(margin), char^line_width) + char != ' ' && print(io, '\n', ' '^(margin), char^(line_width-margin)) end end diff --git a/stdlib/Markdown/test/runtests.jl b/stdlib/Markdown/test/runtests.jl index dfe80430a00d6e..b85d7604378ead 100644 --- a/stdlib/Markdown/test/runtests.jl +++ b/stdlib/Markdown/test/runtests.jl @@ -376,7 +376,7 @@ table = md""" # mime output let out = @test sprint(show, "text/plain", book) == - " Title\n ≡≡≡≡≡≡≡\n\n Some discussion\n\n │ A quote\n\n Section important\n ===================\n\n Some bolded\n\n • list1\n\n • list2" + " Title\n ≡≡≡≡≡\n\n Some discussion\n\n │ A quote\n\n Section important\n =================\n\n Some bolded\n\n • list1\n\n • list2" @test sprint(show, "text/markdown", book) == """ # Title From 7514bcf0bda547012f19a071daa132c3e8e97613 Mon Sep 17 00:00:00 2001 From: Rashid Rafeek Date: Mon, 28 Nov 2022 12:14:05 +0530 Subject: [PATCH 032/387] Fix REPL keybinding CTRL-Q for stdlib methods (#47637) --- stdlib/REPL/src/REPL.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index 708a4f895573a0..4c83cdf33508d0 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -1248,7 +1248,7 @@ function setup_interface( @goto writeback end try - InteractiveUtils.edit(linfos[n][1], linfos[n][2]) + InteractiveUtils.edit(Base.fixup_stdlib_path(linfos[n][1]), linfos[n][2]) catch ex ex isa ProcessFailedException || ex isa Base.IOError || ex isa SystemError || rethrow() @info "edit failed" _exception=ex From 20d13dad451988860eb61c49b9f86cb8d8da1671 Mon Sep 17 00:00:00 2001 From: "C. Brenhin Keller" Date: Mon, 28 Nov 2022 01:45:13 -0500 Subject: [PATCH 033/387] Reuse `du2` of `Tridiagonal` matrix for pivoting in `lu!` if extant (#47564) --- stdlib/LinearAlgebra/src/lu.jl | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/stdlib/LinearAlgebra/src/lu.jl b/stdlib/LinearAlgebra/src/lu.jl index 95fbe6344e88fe..df4154b00e9ac6 100644 --- a/stdlib/LinearAlgebra/src/lu.jl +++ b/stdlib/LinearAlgebra/src/lu.jl @@ -527,7 +527,14 @@ function lu!(A::Tridiagonal{T,V}, pivot::Union{RowMaximum,NoPivot} = RowMaximum( if dl === du throw(ArgumentError("off-diagonals of `A` must not alias")) end - du2 = fill!(similar(d, max(0, n-2)), 0)::V + # Check if Tridiagonal matrix already has du2 for pivoting + has_du2_defined = isdefined(A, :du2) && length(A.du2) == max(0, n-2) + if has_du2_defined + du2 = A.du2::V + else + du2 = similar(d, max(0, n-2))::V + end + fill!(du2, 0) @inbounds begin for i = 1:n @@ -582,7 +589,7 @@ function lu!(A::Tridiagonal{T,V}, pivot::Union{RowMaximum,NoPivot} = RowMaximum( end end end - B = Tridiagonal{T,V}(dl, d, du, du2) + B = has_du2_defined ? A : Tridiagonal{T,V}(dl, d, du, du2) check && checknonsingular(info, pivot) return LU{T,Tridiagonal{T,V},typeof(ipiv)}(B, ipiv, convert(BlasInt, info)) end From ea23403f1106333e311bfa621292b9e2942dbdf9 Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Mon, 28 Nov 2022 07:48:17 +0100 Subject: [PATCH 034/387] Return early in `axp[b]y` if alpha and beta is zero, fixes #47531. (#47557) --- stdlib/LinearAlgebra/src/generic.jl | 7 +++-- stdlib/LinearAlgebra/test/generic.jl | 46 ++++++++++++++++------------ 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/stdlib/LinearAlgebra/src/generic.jl b/stdlib/LinearAlgebra/src/generic.jl index 64bfd33aa30bab..4759f352035f61 100644 --- a/stdlib/LinearAlgebra/src/generic.jl +++ b/stdlib/LinearAlgebra/src/generic.jl @@ -1444,10 +1444,11 @@ function axpy!(α, x::AbstractArray, y::AbstractArray) if n != length(y) throw(DimensionMismatch("x has length $n, but y has length $(length(y))")) end + iszero(α) && return y for (IY, IX) in zip(eachindex(y), eachindex(x)) @inbounds y[IY] += x[IX]*α end - y + return y end function axpy!(α, x::AbstractArray, rx::AbstractArray{<:Integer}, y::AbstractArray, ry::AbstractArray{<:Integer}) @@ -1458,10 +1459,11 @@ function axpy!(α, x::AbstractArray, rx::AbstractArray{<:Integer}, y::AbstractAr elseif !checkindex(Bool, eachindex(IndexLinear(), y), ry) throw(BoundsError(y, ry)) end + iszero(α) && return y for (IY, IX) in zip(eachindex(ry), eachindex(rx)) @inbounds y[ry[IY]] += x[rx[IX]]*α end - y + return y end """ @@ -1487,6 +1489,7 @@ function axpby!(α, x::AbstractArray, β, y::AbstractArray) if length(x) != length(y) throw(DimensionMismatch("x has length $(length(x)), but y has length $(length(y))")) end + iszero(α) && isone(β) && return y for (IX, IY) in zip(eachindex(x), eachindex(y)) @inbounds y[IY] = x[IX]*α + y[IY]*β end diff --git a/stdlib/LinearAlgebra/test/generic.jl b/stdlib/LinearAlgebra/test/generic.jl index a4682f32d07aa0..a95827867cd18f 100644 --- a/stdlib/LinearAlgebra/test/generic.jl +++ b/stdlib/LinearAlgebra/test/generic.jl @@ -117,12 +117,12 @@ end x = ['a','b','c','d','e'] y = ['a','b','c','d','e'] α, β = 'f', 'g' - @test_throws DimensionMismatch LinearAlgebra.axpy!(α,x,['g']) - @test_throws DimensionMismatch LinearAlgebra.axpby!(α,x,β,['g']) - @test_throws BoundsError LinearAlgebra.axpy!(α,x,Vector(-1:5),y,Vector(1:7)) - @test_throws BoundsError LinearAlgebra.axpy!(α,x,Vector(1:7),y,Vector(-1:5)) - @test_throws BoundsError LinearAlgebra.axpy!(α,x,Vector(1:7),y,Vector(1:7)) - @test_throws DimensionMismatch LinearAlgebra.axpy!(α,x,Vector(1:3),y,Vector(1:5)) + @test_throws DimensionMismatch axpy!(α, x, ['g']) + @test_throws DimensionMismatch axpby!(α, x, β, ['g']) + @test_throws BoundsError axpy!(α, x, Vector(-1:5), y, Vector(1:7)) + @test_throws BoundsError axpy!(α, x, Vector(1:7), y, Vector(-1:5)) + @test_throws BoundsError axpy!(α, x, Vector(1:7), y, Vector(1:7)) + @test_throws DimensionMismatch axpy!(α, x, Vector(1:3), y, Vector(1:5)) end @test !issymmetric(fill(1,5,3)) @@ -302,44 +302,50 @@ end end end -@testset "LinearAlgebra.axp(b)y! for element type without commutative multiplication" begin +@testset "axp(b)y! for element type without commutative multiplication" begin α = [1 2; 3 4] β = [5 6; 7 8] x = fill([ 9 10; 11 12], 3) y = fill([13 14; 15 16], 3) - axpy = LinearAlgebra.axpy!(α, x, deepcopy(y)) - axpby = LinearAlgebra.axpby!(α, x, β, deepcopy(y)) + axpy = axpy!(α, x, deepcopy(y)) + axpby = axpby!(α, x, β, deepcopy(y)) @test axpy == x .* [α] .+ y @test axpy != [α] .* x .+ y @test axpby == x .* [α] .+ y .* [β] @test axpby != [α] .* x .+ [β] .* y + axpy = axpy!(zero(α), x, deepcopy(y)) + axpby = axpby!(zero(α), x, one(β), deepcopy(y)) + @test axpy == y + @test axpy == y + @test axpby == y + @test axpby == y end -@testset "LinearAlgebra.axpy! for x and y of different dimensions" begin +@testset "axpy! for x and y of different dimensions" begin α = 5 x = 2:5 y = fill(1, 2, 4) rx = [1 4] ry = [2 8] - @test LinearAlgebra.axpy!(α, x, rx, y, ry) == [1 1 1 1; 11 1 1 26] + @test axpy!(α, x, rx, y, ry) == [1 1 1 1; 11 1 1 26] end -@testset "LinearAlgebra.axp(b)y! for non strides input" begin +@testset "axp(b)y! for non strides input" begin a = rand(5, 5) - @test LinearAlgebra.axpby!(1, Hermitian(a), 1, zeros(size(a))) == Hermitian(a) - @test LinearAlgebra.axpby!(1, 1.:5, 1, zeros(5)) == 1.:5 - @test LinearAlgebra.axpy!(1, Hermitian(a), zeros(size(a))) == Hermitian(a) - @test LinearAlgebra.axpy!(1, 1.:5, zeros(5)) == 1.:5 + @test axpby!(1, Hermitian(a), 1, zeros(size(a))) == Hermitian(a) + @test axpby!(1, 1.:5, 1, zeros(5)) == 1.:5 + @test axpy!(1, Hermitian(a), zeros(size(a))) == Hermitian(a) + @test axpy!(1, 1.:5, zeros(5)) == 1.:5 end @testset "LinearAlgebra.axp(b)y! for stride-vector like input" begin for T in (Float32, Float64, ComplexF32, ComplexF64) a = rand(T, 5, 5) - @test LinearAlgebra.axpby!(1, view(a, :, 1:5), 1, zeros(T, size(a))) == a - @test LinearAlgebra.axpy!(1, view(a, :, 1:5), zeros(T, size(a))) == a + @test axpby!(1, view(a, :, 1:5), 1, zeros(T, size(a))) == a + @test axpy!(1, view(a, :, 1:5), zeros(T, size(a))) == a b = view(a, 25:-2:1) - @test LinearAlgebra.axpby!(1, b, 1, zeros(T, size(b))) == b - @test LinearAlgebra.axpy!(1, b, zeros(T, size(b))) == b + @test axpby!(1, b, 1, zeros(T, size(b))) == b + @test axpy!(1, b, zeros(T, size(b))) == b end end From 902e8a7c2f7ba45aa35b8f5de4c2840a306a1958 Mon Sep 17 00:00:00 2001 From: Hendrik Ranocha Date: Mon, 28 Nov 2022 12:17:27 +0100 Subject: [PATCH 035/387] fix 5-arg `mul!` for vectors of vectors (#47665) Co-authored-by: N5N3 <2642243996@qq.com> --- stdlib/LinearAlgebra/src/matmul.jl | 2 +- stdlib/LinearAlgebra/test/matmul.jl | 52 +++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/stdlib/LinearAlgebra/src/matmul.jl b/stdlib/LinearAlgebra/src/matmul.jl index 3e034ce87ede06..6d00b950525e68 100644 --- a/stdlib/LinearAlgebra/src/matmul.jl +++ b/stdlib/LinearAlgebra/src/matmul.jl @@ -807,7 +807,7 @@ function generic_matvecmul!(C::AbstractVector{R}, tA, A::AbstractVecOrMat, B::Ab end for k = 1:mB aoffs = (k-1)*Astride - b = _add(B[k], false) + b = _add(B[k]) for i = 1:mA C[i] += A[aoffs + i] * b end diff --git a/stdlib/LinearAlgebra/test/matmul.jl b/stdlib/LinearAlgebra/test/matmul.jl index cf0295ce552b5b..0150c4c2efdc82 100644 --- a/stdlib/LinearAlgebra/test/matmul.jl +++ b/stdlib/LinearAlgebra/test/matmul.jl @@ -156,6 +156,58 @@ end end end +@testset "generic_matvecmul for vectors of vectors" begin + @testset "matrix of scalars" begin + u = [[1, 2], [3, 4]] + A = [1 2; 3 4] + v = [[0, 0], [0, 0]] + Au = [[7, 10], [15, 22]] + @test A * u == Au + mul!(v, A, u) + @test v == Au + mul!(v, A, u, 2, -1) + @test v == Au + end + + @testset "matrix of matrices" begin + u = [[1, 2], [3, 4]] + A = Matrix{Matrix{Int}}(undef, 2, 2) + A[1, 1] = [1 2; 3 4] + A[1, 2] = [5 6; 7 8] + A[2, 1] = [9 10; 11 12] + A[2, 2] = [13 14; 15 16] + v = [[0, 0], [0, 0]] + Au = [[44, 64], [124, 144]] + @test A * u == Au + mul!(v, A, u) + @test v == Au + mul!(v, A, u, 2, -1) + @test v == Au + end +end + +@testset "generic_matmatmul for matrices of vectors" begin + B = Matrix{Vector{Int}}(undef, 2, 2) + B[1, 1] = [1, 2] + B[2, 1] = [3, 4] + B[1, 2] = [5, 6] + B[2, 2] = [7, 8] + A = [1 2; 3 4] + C = Matrix{Vector{Int}}(undef, 2, 2) + AB = Matrix{Vector{Int}}(undef, 2, 2) + AB[1, 1] = [7, 10] + AB[2, 1] = [15, 22] + AB[1, 2] = [19, 22] + AB[2, 2] = [43, 50] + @test A * B == AB + mul!(C, A, B) + @test C == AB + mul!(C, A, B, 2, -1) + @test C == AB + LinearAlgebra._generic_matmatmul!(C, 'N', 'N', A, B, LinearAlgebra.MulAddMul(2, -1)) + @test C == AB +end + @testset "fallbacks & such for BlasFloats" begin AA = rand(Float64, 6, 6) BB = rand(Float64, 6, 6) From c8ea33dfb2aa27af092ae45f2d83049a99fc6fa5 Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Mon, 28 Nov 2022 21:50:17 +0400 Subject: [PATCH 036/387] Add link to alloc profiler issue in Profile docs (#47728) --- doc/src/manual/profile.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/manual/profile.md b/doc/src/manual/profile.md index 5b18f57a186be6..b2c9d722f57e6c 100644 --- a/doc/src/manual/profile.md +++ b/doc/src/manual/profile.md @@ -362,7 +362,7 @@ Passing `sample_rate=1.0` will make it record everything (which is slow); `Profile.Allocs.UnknownType`. You can read more about the missing types and the plan to improve this, here: - . + [issue #43688](https://github.com/JuliaLang/julia/issues/43688). ## External Profiling From 3f9409c8b4e9c82d567f1dd3ac9980fad1872a3c Mon Sep 17 00:00:00 2001 From: apaz Date: Mon, 28 Nov 2022 14:33:45 -0600 Subject: [PATCH 037/387] Fix nth_methtable tparam of -1 when n==0 (#47666) Fixes #47625 --- src/method.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/method.c b/src/method.c index f8fe34f7cffd63..587ffd65e1cd86 100644 --- a/src/method.c +++ b/src/method.c @@ -911,7 +911,7 @@ static jl_methtable_t *nth_methtable(jl_value_t *a JL_PROPAGATES_ROOT, int n) JL if (mt != NULL) return mt; } - if (jl_is_tuple_type(a)) { + else if (jl_is_tuple_type(a)) { if (jl_nparams(a) >= n) return nth_methtable(jl_tparam(a, n - 1), 0); } From 1badb9d770fabd50a42fe92129200d01d5224924 Mon Sep 17 00:00:00 2001 From: Allen Hill Date: Mon, 28 Nov 2022 17:36:07 -0500 Subject: [PATCH 038/387] doc: add example of unwrapping `Some(nothing)` in `something` docstring (#47682) --- base/some.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/base/some.jl b/base/some.jl index e09a926ab931b5..08cb3c1648ba1a 100644 --- a/base/some.jl +++ b/base/some.jl @@ -85,6 +85,9 @@ julia> something(nothing, 1) julia> something(Some(1), nothing) 1 +julia> something(Some(nothing), 2) === nothing +true + julia> something(missing, nothing) missing From 2642c4efc3886c6dcac160539ffc01633c0ff0cc Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Tue, 29 Nov 2022 08:12:37 +0900 Subject: [PATCH 039/387] allow `Base._which` to take overlay method table (#47722) --- base/reflection.jl | 11 +++++++++-- stdlib/InteractiveUtils/src/codeview.jl | 2 +- test/compiler/contextual.jl | 2 +- test/compiler/inference.jl | 2 +- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/base/reflection.jl b/base/reflection.jl index de0296447be581..1dfc085de59259 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1484,8 +1484,15 @@ end print_statement_costs(args...; kwargs...) = print_statement_costs(stdout, args...; kwargs...) -function _which(@nospecialize(tt::Type), world=get_world_counter()) - match, _ = Core.Compiler._findsup(tt, nothing, world) +function _which(@nospecialize(tt::Type); + method_table::Union{Nothing,Core.MethodTable}=nothing, + world::UInt=get_world_counter()) + if method_table === nothing + table = Core.Compiler.InternalMethodTable(world) + else + table = Core.Compiler.OverlayMethodTable(world, method_table) + end + match, = Core.Compiler.findsup(tt, table) if match === nothing error("no unique matching method found for the specified argument types") end diff --git a/stdlib/InteractiveUtils/src/codeview.jl b/stdlib/InteractiveUtils/src/codeview.jl index ce7e76ae4cfd79..76666813853528 100644 --- a/stdlib/InteractiveUtils/src/codeview.jl +++ b/stdlib/InteractiveUtils/src/codeview.jl @@ -179,7 +179,7 @@ function _dump_function(@nospecialize(f), @nospecialize(t), native::Bool, wrappe # get the MethodInstance for the method match if !isa(f, Core.OpaqueClosure) world = Base.get_world_counter() - match = Base._which(signature_type(f, t), world) + match = Base._which(signature_type(f, t); world) linfo = Core.Compiler.specialize_method(match) # TODO: use jl_is_cacheable_sig instead of isdispatchtuple isdispatchtuple(linfo.specTypes) || (warning = GENERIC_SIG_WARNING) diff --git a/test/compiler/contextual.jl b/test/compiler/contextual.jl index 7e6ebe8b620799..75cbcf2de37fc0 100644 --- a/test/compiler/contextual.jl +++ b/test/compiler/contextual.jl @@ -75,7 +75,7 @@ module MiniCassette end tt = Tuple{f, args...} - match = Base._which(tt, typemax(UInt)) + match = Base._which(tt; world=typemax(UInt)) mi = Core.Compiler.specialize_method(match) # Unsupported in this mini-cassette @assert !mi.def.isva diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 529e8a90dec3a0..d440b0097ac535 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -1251,7 +1251,7 @@ function get_linfo(@nospecialize(f), @nospecialize(t)) throw(ArgumentError("argument is not a generic function")) end # get the MethodInstance for the method match - match = Base._which(Base.signature_type(f, t), Base.get_world_counter()) + match = Base._which(Base.signature_type(f, t)) precompile(match.spec_types) return Core.Compiler.specialize_method(match) end From 865007d701341526bc5442fcb2409a82a82a7ccd Mon Sep 17 00:00:00 2001 From: Logan Kilpatrick <23kilpatrick23@gmail.com> Date: Mon, 28 Nov 2022 16:05:51 -0800 Subject: [PATCH 040/387] Add section headings to the Doc's home page & links (#47646) * Add sections heading to the Doc's home & links Co-authored-by: Viral B. Shah Co-authored-by: Fredrik Ekre --- doc/src/index.md | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/doc/src/index.md b/doc/src/index.md index a1915395151bc7..bb758d14b4cf2f 100644 --- a/doc/src/index.md +++ b/doc/src/index.md @@ -32,7 +32,19 @@ Markdown.parse(""" """) ``` -### [Introduction](@id man-introduction) +## [Important Links](@id man-important-links) + +Below is a non-exhasutive list of links that will be useful as you learn and use the Julia programming language. + +- [Julia Homepage](https://julialang.org) +- [Download Julia](https://julialang.org/downloads/) +- [Discussion forum](https://discourse.julialang.org) +- [Julia YouTube](https://www.youtube.com/user/JuliaLanguage) +- [Find Julia Packages](https://julialang.org/packages/) +- [Learning Resources](https://julialang.org/learning/) +- [Read and write blogs on Julia](https://forem.julialang.org) + +## [Introduction](@id man-introduction) Scientific computing has traditionally required the highest performance, yet domain experts have largely moved to slower dynamic languages for daily work. We believe there are many good reasons @@ -46,7 +58,9 @@ with performance comparable to traditional statically-typed languages. Because Julia's compiler is different from the interpreters used for languages like Python or R, you may find that Julia's performance is unintuitive at first. If you find that something is slow, we highly recommend reading through the [Performance Tips](@ref man-performance-tips) section before trying anything -else. Once you understand how Julia works, it's easy to write code that's nearly as fast as C. +else. Once you understand how Julia works, it is easy to write code that is nearly as fast as C. + +## [Julia Compared to Other Languages](@id man-julia-compared-other-languages) Julia features optional typing, multiple dispatch, and good performance, achieved using type inference and [just-in-time (JIT) compilation](https://en.wikipedia.org/wiki/Just-in-time_compilation) (and @@ -70,14 +84,16 @@ The most significant departures of Julia from typical dynamic languages are: * Automatic generation of efficient, specialized code for different argument types * Good performance, approaching that of statically-compiled languages like C -Although one sometimes speaks of dynamic languages as being "typeless", they are definitely not: -every object, whether primitive or user-defined, has a type. The lack of type declarations in +Although one sometimes speaks of dynamic languages as being "typeless", they are definitely not. +Every object, whether primitive or user-defined, has a type. The lack of type declarations in most dynamic languages, however, means that one cannot instruct the compiler about the types of values, and often cannot explicitly talk about types at all. In static languages, on the other hand, while one can -- and usually must -- annotate types for the compiler, types exist only at compile time and cannot be manipulated or expressed at run time. In Julia, types are themselves run-time objects, and can also be used to convey information to the compiler. +### [What Makes Julia, Julia?](@id man-what-makes-julia) + While the casual programmer need not explicitly use types or multiple dispatch, they are the core unifying features of Julia: functions are defined on different combinations of argument types, and applied by dispatching to the most specific matching definition. This model is a good fit @@ -93,6 +109,8 @@ languages. For large scale numerical problems, speed always has been, continues always will be crucial: the amount of data being processed has easily kept pace with Moore's Law over the past decades. +### [Advantages of Julia](@id man-advantages-of-julia) + Julia aims to create an unprecedented combination of ease-of-use, power, and efficiency in a single language. In addition to the above, some advantages of Julia over comparable systems include: From f6e911aad7eaa0e703a20f0481265d339b2a3625 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Tue, 29 Nov 2022 13:18:07 +0900 Subject: [PATCH 041/387] make `ml_matches` return `nothing` when there are too many matches (#44478) Continuation from . Previously `ml_matches` (and its dependent utilities like `_methods_by_ftype`) returned `false` for cases when there are too many matching method, but its consumer like `findall(::Type, ::InternalMethodTable)` usually handles such case as `missing`. This commit does a refactor so that they all use the more consistent value `nothing` for representing that case. --- base/compiler/abstractinterpretation.jl | 4 ++-- base/compiler/bootstrap.jl | 3 ++- base/compiler/methodtable.jl | 20 +++++++++----------- base/reflection.jl | 2 +- src/dump.c | 4 ++-- src/gf.c | 16 ++++++++-------- stdlib/REPL/src/REPLCompletions.jl | 4 ++-- test/ambiguous.jl | 2 ++ test/compiler/datastructures.jl | 4 ++-- test/compiler/validation.jl | 2 +- 10 files changed, 31 insertions(+), 30 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 0fb85c4d00d1a2..d95692599f5f00 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -294,7 +294,7 @@ function find_matching_methods(argtypes::Vector{Any}, @nospecialize(atype), meth mt === nothing && return FailedMethodMatch("Could not identify method table for call") mt = mt::Core.MethodTable result = findall(sig_n, method_table; limit = max_methods) - if result === missing + if result === nothing return FailedMethodMatch("For one of the union split cases, too many methods matched") end (; matches, overlayed) = result @@ -333,7 +333,7 @@ function find_matching_methods(argtypes::Vector{Any}, @nospecialize(atype), meth end mt = mt::Core.MethodTable result = findall(atype, method_table; limit = max_methods) - if result === missing + if result === nothing # this means too many methods matched # (assume this will always be true, so we don't compute / update valid age in this case) return FailedMethodMatch("Too many methods matched") diff --git a/base/compiler/bootstrap.jl b/base/compiler/bootstrap.jl index 4b79cd57f9d11d..77b36cb9c7f71b 100644 --- a/base/compiler/bootstrap.jl +++ b/base/compiler/bootstrap.jl @@ -36,8 +36,9 @@ let interp = NativeInterpreter() else tt = Tuple{typeof(f), Vararg{Any}} end - for m in _methods_by_ftype(tt, 10, typemax(UInt)) + for m in _methods_by_ftype(tt, 10, typemax(UInt))::Vector # remove any TypeVars from the intersection + m = m::MethodMatch typ = Any[m.spec_types.parameters...] for i = 1:length(typ) typ[i] = unwraptv(typ[i]) diff --git a/base/compiler/methodtable.jl b/base/compiler/methodtable.jl index 93ea00da4986e7..c3def0879f2ed1 100644 --- a/base/compiler/methodtable.jl +++ b/base/compiler/methodtable.jl @@ -57,30 +57,30 @@ Overlays another method table view with an additional local fast path cache that can respond to repeated, identical queries faster than the original method table. """ struct CachedMethodTable{T} <: MethodTableView - cache::IdDict{MethodMatchKey, Union{Missing,MethodMatchResult}} + cache::IdDict{MethodMatchKey, Union{Nothing,MethodMatchResult}} table::T end -CachedMethodTable(table::T) where T = CachedMethodTable{T}(IdDict{MethodMatchKey, Union{Missing,MethodMatchResult}}(), table) +CachedMethodTable(table::T) where T = CachedMethodTable{T}(IdDict{MethodMatchKey, Union{Nothing,MethodMatchResult}}(), table) """ findall(sig::Type, view::MethodTableView; limit::Int=-1) -> - MethodMatchResult(matches::MethodLookupResult, overlayed::Bool) or missing + MethodMatchResult(matches::MethodLookupResult, overlayed::Bool) or nothing Find all methods in the given method table `view` that are applicable to the given signature `sig`. If no applicable methods are found, an empty result is returned. -If the number of applicable methods exceeded the specified `limit`, `missing` is returned. +If the number of applicable methods exceeded the specified `limit`, `nothing` is returned. Note that the default setting `limit=-1` does not limit the number of applicable methods. `overlayed` indicates if any of the matching methods comes from an overlayed method table. """ function findall(@nospecialize(sig::Type), table::InternalMethodTable; limit::Int=-1) result = _findall(sig, nothing, table.world, limit) - result === missing && return missing + result === nothing && return nothing return MethodMatchResult(result, false) end function findall(@nospecialize(sig::Type), table::OverlayMethodTable; limit::Int=-1) result = _findall(sig, table.mt, table.world, limit) - result === missing && return missing + result === nothing && return nothing nr = length(result) if nr ≥ 1 && result[nr].fully_covers # no need to fall back to the internal method table @@ -88,7 +88,7 @@ function findall(@nospecialize(sig::Type), table::OverlayMethodTable; limit::Int end # fall back to the internal method table fallback_result = _findall(sig, nothing, table.world, limit) - fallback_result === missing && return missing + fallback_result === nothing && return nothing # merge the fallback match results with the internal method table return MethodMatchResult( MethodLookupResult( @@ -105,10 +105,8 @@ function _findall(@nospecialize(sig::Type), mt::Union{Nothing,Core.MethodTable}, _max_val = RefValue{UInt}(typemax(UInt)) _ambig = RefValue{Int32}(0) ms = _methods_by_ftype(sig, mt, limit, world, false, _min_val, _max_val, _ambig) - if ms === false - return missing - end - return MethodLookupResult(ms::Vector{Any}, WorldRange(_min_val[], _max_val[]), _ambig[] != 0) + isa(ms, Vector) || return nothing + return MethodLookupResult(ms, WorldRange(_min_val[], _max_val[]), _ambig[] != 0) end function findall(@nospecialize(sig::Type), table::CachedMethodTable; limit::Int=-1) diff --git a/base/reflection.jl b/base/reflection.jl index 1dfc085de59259..e78634808bd334 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -950,7 +950,7 @@ function _methods_by_ftype(@nospecialize(t), mt::Union{Core.MethodTable, Nothing return _methods_by_ftype(t, mt, lim, world, false, RefValue{UInt}(typemin(UInt)), RefValue{UInt}(typemax(UInt)), Ptr{Int32}(C_NULL)) end function _methods_by_ftype(@nospecialize(t), mt::Union{Core.MethodTable, Nothing}, lim::Int, world::UInt, ambig::Bool, min::Ref{UInt}, max::Ref{UInt}, has_ambig::Ref{Int32}) - return ccall(:jl_matching_methods, Any, (Any, Any, Cint, Cint, UInt, Ptr{UInt}, Ptr{UInt}, Ptr{Int32}), t, mt, lim, ambig, world, min, max, has_ambig)::Union{Array{Any,1}, Bool} + return ccall(:jl_matching_methods, Any, (Any, Any, Cint, Cint, UInt, Ptr{UInt}, Ptr{UInt}, Ptr{Int32}), t, mt, lim, ambig, world, min, max, has_ambig)::Union{Vector{Any},Nothing} end # high-level, more convenient method lookup functions diff --git a/src/dump.c b/src/dump.c index 96c875c4ec7f59..125f3faca36877 100644 --- a/src/dump.c +++ b/src/dump.c @@ -1398,7 +1398,7 @@ static void jl_collect_edges(jl_array_t *edges, jl_array_t *ext_targets) int ambig = 0; matches = jl_matching_methods((jl_tupletype_t*)sig, jl_nothing, -1, 0, world, &min_valid, &max_valid, &ambig); - if (matches == jl_false) { + if (matches == jl_nothing) { callee_ids = NULL; // invalid break; } @@ -2408,7 +2408,7 @@ static jl_array_t *jl_verify_edges(jl_array_t *targets) // TODO: possibly need to included ambiguities too (for the optimizer correctness)? matches = jl_matching_methods((jl_tupletype_t*)sig, jl_nothing, -1, 0, world, &min_valid, &max_valid, &ambig); - if (matches == jl_false) { + if (matches == jl_nothing) { valid = 0; } else { diff --git a/src/gf.c b/src/gf.c index 0e98f2a140d4a1..3be1457afe2d66 100644 --- a/src/gf.c +++ b/src/gf.c @@ -1102,7 +1102,7 @@ static jl_method_instance_t *cache_method( size_t max_valid2 = ~(size_t)0; temp = ml_matches(mt, compilationsig, MAX_UNSPECIALIZED_CONFLICTS, 1, 1, world, 0, &min_valid2, &max_valid2, NULL); int guards = 0; - if (temp == jl_false) { + if (temp == jl_nothing) { cache_with_orig = 1; } else { @@ -2304,7 +2304,7 @@ jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types JL_PROPAGATES *min_valid = min_valid2; if (*max_valid > max_valid2) *max_valid = max_valid2; - if (matches == jl_false || jl_array_len(matches) != 1 || ambig) + if (matches == jl_nothing || jl_array_len(matches) != 1 || ambig) return NULL; JL_GC_PUSH1(&matches); jl_method_match_t *match = (jl_method_match_t*)jl_array_ptr_ref(matches, 0); @@ -2716,7 +2716,7 @@ static jl_method_match_t *_gf_invoke_lookup(jl_value_t *types JL_PROPAGATES_ROOT if (mt == jl_nothing) mt = NULL; jl_value_t *matches = ml_matches((jl_methtable_t*)mt, (jl_tupletype_t*)types, 1, 0, 0, world, 1, min_valid, max_valid, NULL); - if (matches == jl_false || jl_array_len(matches) != 1) + if (matches == jl_nothing || jl_array_len(matches) != 1) return NULL; jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(matches, 0); return matc; @@ -3016,14 +3016,14 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, } if (!jl_typemap_intersection_visitor(jl_atomic_load_relaxed(&mt->defs), 0, &env.match)) { JL_GC_POP(); - return jl_false; + return jl_nothing; } } else { // else: scan everything if (!jl_foreach_reachable_mtable(ml_mtable_visitor, &env.match)) { JL_GC_POP(); - return jl_false; + return jl_nothing; } } *min_valid = env.min_valid; @@ -3105,7 +3105,7 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, } else if (lim == 1) { JL_GC_POP(); - return jl_false; + return jl_nothing; } } else { @@ -3249,7 +3249,7 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, ndisjoint += 1; if (ndisjoint > lim) { JL_GC_POP(); - return jl_false; + return jl_nothing; } } } @@ -3396,7 +3396,7 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, *ambig = has_ambiguity; JL_GC_POP(); if (lim >= 0 && len > lim) - return jl_false; + return jl_nothing; return env.t; } diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index 2ce7a6151b351b..34ce7ad9928fb6 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -656,10 +656,10 @@ function complete_methods!(out::Vector{Completion}, @nospecialize(funct), args_e t_in = Tuple{funct, args_ex...} m = Base._methods_by_ftype(t_in, nothing, max_method_completions, Base.get_world_counter(), #=ambig=# true, Ref(typemin(UInt)), Ref(typemax(UInt)), Ptr{Int32}(C_NULL)) - if m === false + if !isa(m, Vector) push!(out, TextCompletion(sprint(Base.show_signature_function, funct) * "( too many methods, use SHIFT-TAB to show )")) + return end - m isa Vector || return for match in m # TODO: if kwargs_ex, filter out methods without kwargs? push!(out, MethodCompletion(match.spec_types, match.method)) diff --git a/test/ambiguous.jl b/test/ambiguous.jl index 369dbc73942727..bd58c9bb627ffd 100644 --- a/test/ambiguous.jl +++ b/test/ambiguous.jl @@ -350,6 +350,7 @@ f35983(::Type, ::Type) = 2 @test first(Base.methods(f35983, (Any, Any))).sig == Tuple{typeof(f35983), Type, Type} let ambig = Ref{Int32}(0) ms = Base._methods_by_ftype(Tuple{typeof(f35983), Type, Type}, nothing, -1, typemax(UInt), true, Ref{UInt}(typemin(UInt)), Ref{UInt}(typemax(UInt)), ambig) + @test ms isa Vector @test length(ms) == 1 @test ambig[] == 0 end @@ -358,6 +359,7 @@ f35983(::Type{Int16}, ::Any) = 3 @test length(Base.methods(f35983, (Type, Type))) == 1 let ambig = Ref{Int32}(0) ms = Base._methods_by_ftype(Tuple{typeof(f35983), Type, Type}, nothing, -1, typemax(UInt), true, Ref{UInt}(typemin(UInt)), Ref{UInt}(typemax(UInt)), ambig) + @test ms isa Vector @test length(ms) == 2 @test ambig[] == 1 end diff --git a/test/compiler/datastructures.jl b/test/compiler/datastructures.jl index c16d968328d189..a25a884373ab43 100644 --- a/test/compiler/datastructures.jl +++ b/test/compiler/datastructures.jl @@ -8,8 +8,8 @@ using Test sig = Tuple{typeof(*), Any, Any} result1 = Core.Compiler.findall(sig, table; limit=-1) result2 = Core.Compiler.findall(sig, table; limit=Core.Compiler.get_max_methods(*, @__MODULE__, interp)) - @test result1 !== Core.Compiler.missing && !Core.Compiler.isempty(result1.matches) - @test result2 === Core.Compiler.missing + @test result1 !== nothing && !Core.Compiler.isempty(result1.matches) + @test result2 === nothing end @testset "BitSetBoundedMinPrioritySet" begin diff --git a/test/compiler/validation.jl b/test/compiler/validation.jl index ffa79ed1c823d4..c25aae71ab1577 100644 --- a/test/compiler/validation.jl +++ b/test/compiler/validation.jl @@ -20,7 +20,7 @@ end msig = Tuple{typeof(f22938),Int,Int,Int,Int} world = Base.get_world_counter() -match = Base._methods_by_ftype(msig, -1, world)[] +match = only(Base._methods_by_ftype(msig, -1, world)) mi = Core.Compiler.specialize_method(match) c0 = Core.Compiler.retrieve_code_info(mi) From 4fd26ba1352e5f4ffe20b1724926228a0a6fdd41 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Tue, 29 Nov 2022 13:19:25 +0900 Subject: [PATCH 042/387] reflection: support additional call syntaxes for `@invoke[latest]` (#47705) Like `@invoke (xs::Xs)[i::I] = v::V` and `@invokelatest x.f = v`. Co-Authored-By: Jameson Nash --- base/reflection.jl | 113 +++++++++++++++++++++++++++++++++++++++------ test/misc.jl | 103 +++++++++++++++++++++++++++++++++-------- 2 files changed, 181 insertions(+), 35 deletions(-) diff --git a/base/reflection.jl b/base/reflection.jl index e78634808bd334..3f5383239e29ed 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1885,6 +1885,12 @@ When an argument's type annotation is omitted, it's replaced with `Core.Typeof` To invoke a method where an argument is untyped or explicitly typed as `Any`, annotate the argument with `::Any`. +It also supports the following syntax: +- `@invoke (x::X).f` expands to `invoke(getproperty, Tuple{X,Symbol}, x, :f)` +- `@invoke (x::X).f = v::V` expands to `invoke(setproperty!, Tuple{X,Symbol,V}, x, :f, v)` +- `@invoke (xs::Xs)[i::I]` expands to `invoke(getindex, Tuple{Xs,I}, xs, i)` +- `@invoke (xs::Xs)[i::I] = v::V` expands to `invoke(setindex!, Tuple{Xs,V,I}, xs, v, i)` + # Examples ```jldoctest @@ -1893,6 +1899,18 @@ julia> @macroexpand @invoke f(x::T, y) julia> @invoke 420::Integer % Unsigned 0x00000000000001a4 + +julia> @macroexpand @invoke (x::X).f +:(Core.invoke(Base.getproperty, Tuple{X, Core.Typeof(:f)}, x, :f)) + +julia> @macroexpand @invoke (x::X).f = v::V +:(Core.invoke(Base.setproperty!, Tuple{X, Core.Typeof(:f), V}, x, :f, v)) + +julia> @macroexpand @invoke (xs::Xs)[i::I] +:(Core.invoke(Base.getindex, Tuple{Xs, I}, xs, i)) + +julia> @macroexpand @invoke (xs::Xs)[i::I] = v::V +:(Core.invoke(Base.setindex!, Tuple{Xs, V, I}, xs, v, i)) ``` !!! compat "Julia 1.7" @@ -1900,9 +1918,13 @@ julia> @invoke 420::Integer % Unsigned !!! compat "Julia 1.9" This macro is exported as of Julia 1.9. + +!!! compat "Julia 1.10" + The additional syntax is supported as of Julia 1.10. """ macro invoke(ex) - f, args, kwargs = destructure_callex(ex) + topmod = Core.Compiler._topmod(__module__) # well, except, do not get it via CC but define it locally + f, args, kwargs = destructure_callex(topmod, ex) types = Expr(:curly, :Tuple) out = Expr(:call, GlobalRef(Core, :invoke)) isempty(kwargs) || push!(out.args, Expr(:parameters, kwargs...)) @@ -1927,29 +1949,90 @@ Provides a convenient way to call [`Base.invokelatest`](@ref). `@invokelatest f(args...; kwargs...)` will simply be expanded into `Base.invokelatest(f, args...; kwargs...)`. +It also supports the following syntax: +- `@invokelatest x.f` expands to `Base.invokelatest(getproperty, x, :f)` +- `@invokelatest x.f = v` expands to `Base.invokelatest(setproperty!, x, :f, v)` +- `@invokelatest xs[i]` expands to `invoke(getindex, xs, i)` +- `@invokelatest xs[i] = v` expands to `invoke(setindex!, xs, v, i)` + +```jldoctest +julia> @macroexpand @invokelatest f(x; kw=kwv) +:(Base.invokelatest(f, x; kw = kwv)) + +julia> @macroexpand @invokelatest x.f +:(Base.invokelatest(Base.getproperty, x, :f)) + +julia> @macroexpand @invokelatest x.f = v +:(Base.invokelatest(Base.setproperty!, x, :f, v)) + +julia> @macroexpand @invokelatest xs[i] +:(Base.invokelatest(Base.getindex, xs, i)) + +julia> @macroexpand @invokelatest xs[i] = v +:(Base.invokelatest(Base.setindex!, xs, v, i)) +``` + !!! compat "Julia 1.7" This macro requires Julia 1.7 or later. + +!!! compat "Julia 1.10" + The additional syntax is supported as of Julia 1.10. """ macro invokelatest(ex) - f, args, kwargs = destructure_callex(ex) - return esc(:($(GlobalRef(@__MODULE__, :invokelatest))($(f), $(args...); $(kwargs...)))) + topmod = Core.Compiler._topmod(__module__) # well, except, do not get it via CC but define it locally + f, args, kwargs = destructure_callex(topmod, ex) + out = Expr(:call, GlobalRef(Base, :invokelatest)) + isempty(kwargs) || push!(out.args, Expr(:parameters, kwargs...)) + push!(out.args, f) + append!(out.args, args) + return esc(out) end -function destructure_callex(ex) - isexpr(ex, :call) || throw(ArgumentError("a call expression f(args...; kwargs...) should be given")) +function destructure_callex(topmod::Module, @nospecialize(ex)) + function flatten(xs) + out = Any[] + for x in xs + if isexpr(x, :tuple) + append!(out, x.args) + else + push!(out, x) + end + end + return out + end - f = first(ex.args) - args = [] - kwargs = [] - for x in ex.args[2:end] - if isexpr(x, :parameters) - append!(kwargs, x.args) - elseif isexpr(x, :kw) - push!(kwargs, x) + kwargs = Any[] + if isexpr(ex, :call) # `f(args...)` + f = first(ex.args) + args = Any[] + for x in ex.args[2:end] + if isexpr(x, :parameters) + append!(kwargs, x.args) + elseif isexpr(x, :kw) + push!(kwargs, x) + else + push!(args, x) + end + end + elseif isexpr(ex, :.) # `x.f` + f = GlobalRef(topmod, :getproperty) + args = flatten(ex.args) + elseif isexpr(ex, :ref) # `x[i]` + f = GlobalRef(topmod, :getindex) + args = flatten(ex.args) + elseif isexpr(ex, :(=)) # `x.f = v` or `x[i] = v` + lhs, rhs = ex.args + if isexpr(lhs, :.) + f = GlobalRef(topmod, :setproperty!) + args = flatten(Any[lhs.args..., rhs]) + elseif isexpr(lhs, :ref) + f = GlobalRef(topmod, :setindex!) + args = flatten(Any[lhs.args[1], rhs, lhs.args[2]]) else - push!(args, x) + throw(ArgumentError("expected a `setproperty!` expression `x.f = v` or `setindex!` expression `x[i] = v`")) end + else + throw(ArgumentError("expected a `:call` expression `f(args...; kwargs...)`")) end - return f, args, kwargs end diff --git a/test/misc.jl b/test/misc.jl index ee7f59bf673595..8a4b2749788955 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -906,38 +906,87 @@ end module atinvokelatest f(x) = 1 g(x, y; z=0) = x * y + z +mutable struct X; x; end +Base.getproperty(::X, ::Any) = error("overload me") +Base.setproperty!(::X, ::Any, ::Any) = error("overload me") +struct Xs + xs::Vector{Any} end - -let foo() = begin - @eval atinvokelatest.f(x::Int) = 3 - return Base.@invokelatest atinvokelatest.f(0) - end - @test foo() == 3 +Base.getindex(::Xs, ::Any) = error("overload me") +Base.setindex!(::Xs, ::Any, ::Any) = error("overload me") end -let foo() = begin +let call_test() = begin @eval atinvokelatest.f(x::Int) = 3 - return Base.@invokelatest atinvokelatest.f(0) + return @invokelatest atinvokelatest.f(0) end - @test foo() == 3 + @test call_test() == 3 - bar() = begin + call_with_kws_test() = begin @eval atinvokelatest.g(x::Int, y::Int; z=3) = z - return Base.@invokelatest atinvokelatest.g(2, 3; z=1) + return @invokelatest atinvokelatest.g(2, 3; z=1) + end + @test call_with_kws_test() == 1 + + getproperty_test() = begin + @eval Base.getproperty(x::atinvokelatest.X, f::Symbol) = getfield(x, f) + x = atinvokelatest.X(nothing) + return @invokelatest x.x + end + @test isnothing(getproperty_test()) + + setproperty!_test() = begin + @eval Base.setproperty!(x::atinvokelatest.X, f::Symbol, @nospecialize(v)) = setfield!(x, f, v) + x = atinvokelatest.X(nothing) + @invokelatest x.x = 1 + return x end - @test bar() == 1 + x = setproperty!_test() + @test getfield(x, :x) == 1 + + getindex_test() = begin + @eval Base.getindex(xs::atinvokelatest.Xs, idx::Int) = xs.xs[idx] + xs = atinvokelatest.Xs(Any[nothing]) + return @invokelatest xs[1] + end + @test isnothing(getindex_test()) + + setindex!_test() = begin + @eval function Base.setindex!(xs::atinvokelatest.Xs, @nospecialize(v), idx::Int) + xs.xs[idx] = v + end + xs = atinvokelatest.Xs(Any[nothing]) + @invokelatest xs[1] = 1 + return xs + end + xs = setindex!_test() + @test xs.xs[1] == 1 end +abstract type InvokeX end +Base.getproperty(::InvokeX, ::Symbol) = error("overload InvokeX") +Base.setproperty!(::InvokeX, ::Symbol, @nospecialize(v::Any)) = error("overload InvokeX") +mutable struct InvokeX2 <: InvokeX; x; end +Base.getproperty(x::InvokeX2, f::Symbol) = getfield(x, f) +Base.setproperty!(x::InvokeX2, f::Symbol, @nospecialize(v::Any)) = setfield!(x, f, v) + +abstract type InvokeXs end +Base.getindex(::InvokeXs, ::Int) = error("overload InvokeXs") +Base.setindex!(::InvokeXs, @nospecialize(v::Any), ::Int) = error("overload InvokeXs") +struct InvokeXs2 <: InvokeXs + xs::Vector{Any} +end +Base.getindex(xs::InvokeXs2, idx::Int) = xs.xs[idx] +Base.setindex!(xs::InvokeXs2, @nospecialize(v::Any), idx::Int) = xs.xs[idx] = v + @testset "@invoke macro" begin # test against `invoke` doc example - let - f(x::Real) = x^2 + let f(x::Real) = x^2 f(x::Integer) = 1 + @invoke f(x::Real) @test f(2) == 5 end - let - f1(::Integer) = Integer + let f1(::Integer) = Integer f1(::Real) = Real; f2(x::Real) = _f2(x) _f2(::Integer) = Integer @@ -949,8 +998,7 @@ end end # when argment's type annotation is omitted, it should be specified as `Core.Typeof(x)` - let - f(_) = Any + let f(_) = Any f(x::Integer) = Integer @test f(1) === Integer @test @invoke(f(1::Any)) === Any @@ -963,13 +1011,28 @@ end end # handle keyword arguments correctly - let - f(a; kw1 = nothing, kw2 = nothing) = a + max(kw1, kw2) + let f(a; kw1 = nothing, kw2 = nothing) = a + max(kw1, kw2) f(::Integer; kwargs...) = error("don't call me") @test_throws Exception f(1; kw1 = 1, kw2 = 2) @test 3 == @invoke f(1::Any; kw1 = 1, kw2 = 2) end + + # additional syntax test + let x = InvokeX2(nothing) + @test_throws "overload InvokeX" @invoke (x::InvokeX).x + @test isnothing(@invoke x.x) + @test_throws "overload InvokeX" @invoke (x::InvokeX).x = 42 + @invoke x.x = 42 + @test 42 == x.x + + xs = InvokeXs2(Any[nothing]) + @test_throws "overload InvokeXs" @invoke (xs::InvokeXs)[1] + @test isnothing(@invoke xs[1]) + @test_throws "overload InvokeXs" @invoke (xs::InvokeXs)[1] = 42 + @invoke xs[1] = 42 + @test 42 == xs.xs[1] + end end # Endian tests From 4d5fd91463ee59e759e627b3d6fc2ef7c6cdb22c Mon Sep 17 00:00:00 2001 From: st-- Date: Tue, 29 Nov 2022 09:13:43 +0200 Subject: [PATCH 043/387] improve @(s)printf docstrings (#47583) --- stdlib/Printf/src/Printf.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/stdlib/Printf/src/Printf.jl b/stdlib/Printf/src/Printf.jl index 9f14961aa2acf4..27c5605f8d83b3 100644 --- a/stdlib/Printf/src/Printf.jl +++ b/stdlib/Printf/src/Printf.jl @@ -911,6 +911,10 @@ Use shorter of decimal or scientific 1.23 1.23e+07 For a systematic specification of the format, see [here](https://www.cplusplus.com/reference/cstdio/printf/). See also [`@sprintf`](@ref) to get the result as a `String` instead of it being printed. +If you need to use a programmatically generated format string, use +`print(Printf.format(Printf.Format(format_string), args...))` instead. +See [`Printf.format`](@ref) for more details. + # Caveats `Inf` and `NaN` are printed consistently as `Inf` and `NaN` for flags `%a`, `%A`, `%e`, `%E`, `%f`, `%F`, `%g`, and `%G`. Furthermore, if a floating point number is @@ -951,6 +955,10 @@ end Return [`@printf`](@ref) formatted output as string. +If you need to use a programmatically generated format string, use +`Printf.format(Printf.Format(format_string), args...)` instead. +See [`Printf.format`](@ref) for more details. + # Examples ```jldoctest julia> @sprintf "this is a %s %15.1f" "test" 34.567 From edc8241acf912fc009419ccc37ff8e6b3d7807c6 Mon Sep 17 00:00:00 2001 From: Dilum Aluthge Date: Tue, 29 Nov 2022 02:38:07 -0500 Subject: [PATCH 044/387] Revert "improve @(s)printf docstrings (#47583)" (#47734) This reverts commit 4d5fd91463ee59e759e627b3d6fc2ef7c6cdb22c. --- stdlib/Printf/src/Printf.jl | 8 -------- 1 file changed, 8 deletions(-) diff --git a/stdlib/Printf/src/Printf.jl b/stdlib/Printf/src/Printf.jl index 27c5605f8d83b3..9f14961aa2acf4 100644 --- a/stdlib/Printf/src/Printf.jl +++ b/stdlib/Printf/src/Printf.jl @@ -911,10 +911,6 @@ Use shorter of decimal or scientific 1.23 1.23e+07 For a systematic specification of the format, see [here](https://www.cplusplus.com/reference/cstdio/printf/). See also [`@sprintf`](@ref) to get the result as a `String` instead of it being printed. -If you need to use a programmatically generated format string, use -`print(Printf.format(Printf.Format(format_string), args...))` instead. -See [`Printf.format`](@ref) for more details. - # Caveats `Inf` and `NaN` are printed consistently as `Inf` and `NaN` for flags `%a`, `%A`, `%e`, `%E`, `%f`, `%F`, `%g`, and `%G`. Furthermore, if a floating point number is @@ -955,10 +951,6 @@ end Return [`@printf`](@ref) formatted output as string. -If you need to use a programmatically generated format string, use -`Printf.format(Printf.Format(format_string), args...)` instead. -See [`Printf.format`](@ref) for more details. - # Examples ```jldoctest julia> @sprintf "this is a %s %15.1f" "test" 34.567 From 54a9c2fcc6460c2d7b650df3506215fcc34c2ae2 Mon Sep 17 00:00:00 2001 From: Oscar Smith Date: Tue, 29 Nov 2022 11:36:50 -0500 Subject: [PATCH 045/387] improve inlining cost analysis for invoke (#47671) --- base/compiler/optimize.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index 1bbba8e0cada12..f7cc2ae0268681 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -737,7 +737,7 @@ function statement_cost(ex::Expr, line::Int, src::Union{CodeInfo, IRCode}, sptyp end return T_IFUNC_COST[iidx] end - if isa(f, Builtin) + if isa(f, Builtin) && f !== invoke # The efficiency of operations like a[i] and s.b # depend strongly on whether the result can be # inferred, so check the type of ex From cbfdb3facd0f2ece4088f43ef97533e9e0921081 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 7 Feb 2022 05:19:24 -0600 Subject: [PATCH 046/387] Replace the `.ji` serialization with sysimage format This unifies two serializers, `dump.c` (used for packages) and `staticdata.c` (used for system images). It adopts the `staticdata` strategy, adding support for external linkage, uniquing of MethodInstances & types, method extensions, external specializations, and invalidation. This lays the groundwork for native code caching as done with system images. Co-authored-by: Valentin Churavy Co-authored-by: Jameson Nash Co-authored-by: Tim Holy --- base/compiler/typeinfer.jl | 8 +- base/loading.jl | 83 +- deps/llvm.mk | 2 +- src/Makefile | 7 +- src/aotcompile.cpp | 27 +- src/clangsa/GCChecker.cpp | 2 +- src/codegen-stubs.c | 4 +- src/codegen.cpp | 14 +- src/datatype.c | 2 +- src/dlload.c | 2 +- src/dump.c | 3577 --------------------------------- src/gc.c | 25 +- src/gf.c | 2 +- src/init.c | 6 +- src/ircode.c | 132 ++ src/jitlayers.h | 1 - src/jl_exported_funcs.inc | 7 +- src/julia.expmap | 1 + src/julia.h | 19 +- src/julia_internal.h | 62 +- src/llvm-multiversioning.cpp | 55 +- src/method.c | 2 +- src/module.c | 25 +- src/precompile.c | 118 +- src/processor.cpp | 9 +- src/processor.h | 1 + src/processor_arm.cpp | 23 + src/processor_fallback.cpp | 25 + src/processor_x86.cpp | 22 + src/rtutils.c | 6 + src/staticdata.c | 2317 +++++++++++++++------ src/staticdata_utils.c | 1279 ++++++++++++ src/subtype.c | 4 +- src/support/arraylist.h | 2 +- src/support/rle.h | 9 +- src/threading.c | 2 + stdlib/LLD_jll/src/LLD_jll.jl | 1 - stdlib/Profile/src/Allocs.jl | 6 +- test/precompile.jl | 29 +- 39 files changed, 3485 insertions(+), 4433 deletions(-) delete mode 100644 src/dump.c create mode 100644 src/staticdata_utils.c diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 1a13cc051944e2..81d0f06608a316 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -1,8 +1,8 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -# Tracking of newly-inferred MethodInstances during precompilation +# Tracking of newly-inferred CodeInstances during precompilation const track_newly_inferred = RefValue{Bool}(false) -const newly_inferred = MethodInstance[] +const newly_inferred = CodeInstance[] # build (and start inferring) the inference frame for the top-level MethodInstance function typeinf(interp::AbstractInterpreter, result::InferenceResult, cache::Symbol) @@ -403,11 +403,11 @@ function cache_result!(interp::AbstractInterpreter, result::InferenceResult) # TODO: also don't store inferred code if we've previously decided to interpret this function if !already_inferred inferred_result = transform_result_for_cache(interp, linfo, valid_worlds, result) - code_cache(interp)[linfo] = CodeInstance(result, inferred_result, valid_worlds) + code_cache(interp)[linfo] = ci = CodeInstance(result, inferred_result, valid_worlds) if track_newly_inferred[] m = linfo.def if isa(m, Method) && m.module != Core - ccall(:jl_push_newly_inferred, Cvoid, (Any,), linfo) + ccall(:jl_push_newly_inferred, Cvoid, (Any,), ci) end end end diff --git a/base/loading.jl b/base/loading.jl index a5df7c24408ae2..1e168d8a29e62b 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -898,7 +898,7 @@ function _include_from_serialized(pkg::PkgId, path::String, depmods::Vector{Any} end @debug "Loading cache file $path for $pkg" - sv = ccall(:jl_restore_incremental, Any, (Cstring, Any), path, depmods) + sv = ccall(:jl_restore_incremental, Any, (Cstring, Any, Cint), path, depmods, false) if isa(sv, Exception) return sv end @@ -973,7 +973,7 @@ function run_package_callbacks(modkey::PkgId) end # loads a precompile cache file, after checking stale_cachefile tests -function _tryrequire_from_serialized(modkey::PkgId, build_id::UInt64) +function _tryrequire_from_serialized(modkey::PkgId, build_id::UInt128) assert_havelock(require_lock) loaded = nothing if root_module_exists(modkey) @@ -1021,7 +1021,7 @@ function _tryrequire_from_serialized(modkey::PkgId, path::String, sourcepath::St for i in 1:length(depmods) dep = depmods[i] dep isa Module && continue - _, depkey, depbuild_id = dep::Tuple{String, PkgId, UInt64} + _, depkey, depbuild_id = dep::Tuple{String, PkgId, UInt128} @assert root_module_exists(depkey) dep = root_module(depkey) depmods[i] = dep @@ -1052,7 +1052,7 @@ function _tryrequire_from_serialized(pkg::PkgId, path::String) local depmodnames io = open(path, "r") try - isvalid_cache_header(io) || return ArgumentError("Invalid header in cache file $path.") + iszero(isvalid_cache_header(io)) && return ArgumentError("Invalid header in cache file $path.") depmodnames = parse_cache_header(io)[3] isvalid_file_crc(io) || return ArgumentError("Invalid checksum in cache file $path.") finally @@ -1074,7 +1074,7 @@ end # returns `nothing` if require found a precompile cache for this sourcepath, but couldn't load it # returns the set of modules restored if the cache load succeeded -@constprop :none function _require_search_from_serialized(pkg::PkgId, sourcepath::String, build_id::UInt64) +@constprop :none function _require_search_from_serialized(pkg::PkgId, sourcepath::String, build_id::UInt128) assert_havelock(require_lock) paths = find_all_in_cache_path(pkg) for path_to_try in paths::Vector{String} @@ -1087,7 +1087,7 @@ end for i in 1:length(staledeps) dep = staledeps[i] dep isa Module && continue - modpath, modkey, modbuild_id = dep::Tuple{String, PkgId, UInt64} + modpath, modkey, modbuild_id = dep::Tuple{String, PkgId, UInt128} modpaths = find_all_in_cache_path(modkey) modfound = false for modpath_to_try in modpaths::Vector{String} @@ -1101,7 +1101,7 @@ end break end if !modfound - @debug "Rejecting cache file $path_to_try because required dependency $modkey with build ID $modbuild_id is missing from the cache." + @debug "Rejecting cache file $path_to_try because required dependency $modkey with build ID $(UUID(modbuild_id)) is missing from the cache." staledeps = true break end @@ -1153,7 +1153,7 @@ const package_callbacks = Any[] const include_callbacks = Any[] # used to optionally track dependencies when requiring a module: -const _concrete_dependencies = Pair{PkgId,UInt64}[] # these dependency versions are "set in stone", and the process should try to avoid invalidating them +const _concrete_dependencies = Pair{PkgId,UInt128}[] # these dependency versions are "set in stone", and the process should try to avoid invalidating them const _require_dependencies = Any[] # a list of (mod, path, mtime) tuples that are the file dependencies of the module currently being precompiled const _track_dependencies = Ref(false) # set this to true to track the list of file dependencies function _include_dependency(mod::Module, _path::AbstractString) @@ -1406,7 +1406,7 @@ function _require(pkg::PkgId, env=nothing) # attempt to load the module file via the precompile cache locations if JLOptions().use_compiled_modules != 0 - m = _require_search_from_serialized(pkg, path, UInt64(0)) + m = _require_search_from_serialized(pkg, path, UInt128(0)) if m isa Module return m end @@ -1416,7 +1416,7 @@ function _require(pkg::PkgId, env=nothing) # but it was not handled by the precompile loader, complain for (concrete_pkg, concrete_build_id) in _concrete_dependencies if pkg == concrete_pkg - @warn """Module $(pkg.name) with build ID $concrete_build_id is missing from the cache. + @warn """Module $(pkg.name) with build ID $((UUID(concrete_build_id))) is missing from the cache. This may mean $pkg does not support precompilation but is imported by a module that does.""" if JLOptions().incremental != 0 # during incremental precompilation, this should be fail-fast @@ -1785,9 +1785,13 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in close(tmpio) p = create_expr_cache(pkg, path, tmppath, concrete_deps, internal_stderr, internal_stdout) if success(p) - # append checksum to the end of the .ji file: - open(tmppath, "a+") do f - write(f, _crc32c(seekstart(f))) + # append extra crc to the end of the .ji file: + open(tmppath, "r+") do f + if iszero(isvalid_cache_header(f)) + error("Invalid header for $pkg in new cache file $(repr(tmppath)).") + end + seekstart(f) + write(f, _crc32c(f)) end # inherit permission from the source file (and make them writable) chmod(tmppath, filemode(path) & 0o777 | 0o200) @@ -1807,7 +1811,7 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in end end - # this is atomic according to POSIX: + # this is atomic according to POSIX (not Win32): rename(tmppath, cachefile; force=true) return cachefile end @@ -1817,13 +1821,16 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in if p.exitcode == 125 return PrecompilableError() else - error("Failed to precompile $pkg to $tmppath.") + error("Failed to precompile $pkg to $(repr(tmppath)).") end end -module_build_id(m::Module) = ccall(:jl_module_build_id, UInt64, (Any,), m) +function module_build_id(m::Module) + hi, lo = ccall(:jl_module_build_id, NTuple{2,UInt64}, (Any,), m) + return (UInt128(hi) << 64) | lo +end -isvalid_cache_header(f::IOStream) = (0 != ccall(:jl_read_verify_header, Cint, (Ptr{Cvoid},), f.ios)) +isvalid_cache_header(f::IOStream) = ccall(:jl_read_verify_header, UInt64, (Ptr{Cvoid},), f.ios) # returns checksum id or zero isvalid_file_crc(f::IOStream) = (_crc32c(seekstart(f), filesize(f) - 4) == read(f, UInt32)) struct CacheHeaderIncludes @@ -1897,13 +1904,14 @@ function parse_cache_header(f::IO) totbytes -= 8 @assert totbytes == 0 "header of cache file appears to be corrupt (totbytes == $(totbytes))" # read the list of modules that are required to be present during loading - required_modules = Vector{Pair{PkgId, UInt64}}() + required_modules = Vector{Pair{PkgId, UInt128}}() while true n = read(f, Int32) n == 0 && break sym = String(read(f, n)) # module name uuid = UUID((read(f, UInt64), read(f, UInt64))) # pkg UUID - build_id = read(f, UInt64) # build id + build_id = UInt128(read(f, UInt64)) << 64 + build_id |= read(f, UInt64) push!(required_modules, PkgId(uuid, sym) => build_id) end return modules, (includes, requires), required_modules, srctextpos, prefs, prefs_hash @@ -1912,17 +1920,17 @@ end function parse_cache_header(cachefile::String; srcfiles_only::Bool=false) io = open(cachefile, "r") try - !isvalid_cache_header(io) && throw(ArgumentError("Invalid header in cache file $cachefile.")) + iszero(isvalid_cache_header(io)) && throw(ArgumentError("Invalid header in cache file $cachefile.")) ret = parse_cache_header(io) srcfiles_only || return ret - modules, (includes, requires), required_modules, srctextpos, prefs, prefs_hash = ret + _, (includes, _), _, srctextpos, _... = ret srcfiles = srctext_files(io, srctextpos) delidx = Int[] for (i, chi) in enumerate(includes) chi.filename ∈ srcfiles || push!(delidx, i) end deleteat!(includes, delidx) - return modules, (includes, requires), required_modules, srctextpos, prefs, prefs_hash + return ret finally close(io) end @@ -1930,11 +1938,11 @@ end -preferences_hash(f::IO) = parse_cache_header(f)[end] +preferences_hash(f::IO) = parse_cache_header(f)[6] function preferences_hash(cachefile::String) io = open(cachefile, "r") try - if !isvalid_cache_header(io) + if iszero(isvalid_cache_header(io)) throw(ArgumentError("Invalid header in cache file $cachefile.")) end return preferences_hash(io) @@ -1945,14 +1953,14 @@ end function cache_dependencies(f::IO) - defs, (includes, requires), modules, srctextpos, prefs, prefs_hash = parse_cache_header(f) + _, (includes, _), modules, _... = parse_cache_header(f) return modules, map(chi -> (chi.filename, chi.mtime), includes) # return just filename and mtime end function cache_dependencies(cachefile::String) io = open(cachefile, "r") try - !isvalid_cache_header(io) && throw(ArgumentError("Invalid header in cache file $cachefile.")) + iszero(isvalid_cache_header(io)) && throw(ArgumentError("Invalid header in cache file $cachefile.")) return cache_dependencies(io) finally close(io) @@ -1960,7 +1968,7 @@ function cache_dependencies(cachefile::String) end function read_dependency_src(io::IO, filename::AbstractString) - modules, (includes, requires), required_modules, srctextpos, prefs, prefs_hash = parse_cache_header(io) + srctextpos = parse_cache_header(io)[4] srctextpos == 0 && error("no source-text stored in cache file") seek(io, srctextpos) return _read_dependency_src(io, filename) @@ -1983,7 +1991,7 @@ end function read_dependency_src(cachefile::String, filename::AbstractString) io = open(cachefile, "r") try - !isvalid_cache_header(io) && throw(ArgumentError("Invalid header in cache file $cachefile.")) + iszero(isvalid_cache_header(io)) && throw(ArgumentError("Invalid header in cache file $cachefile.")) return read_dependency_src(io, filename) finally close(io) @@ -2173,12 +2181,13 @@ get_compiletime_preferences(::Nothing) = String[] # returns true if it "cachefile.ji" is stale relative to "modpath.jl" and build_id for modkey # otherwise returns the list of dependencies to also check @constprop :none function stale_cachefile(modpath::String, cachefile::String; ignore_loaded::Bool = false) - return stale_cachefile(PkgId(""), UInt64(0), modpath, cachefile; ignore_loaded) + return stale_cachefile(PkgId(""), UInt128(0), modpath, cachefile; ignore_loaded) end -@constprop :none function stale_cachefile(modkey::PkgId, build_id::UInt64, modpath::String, cachefile::String; ignore_loaded::Bool = false) +@constprop :none function stale_cachefile(modkey::PkgId, build_id::UInt128, modpath::String, cachefile::String; ignore_loaded::Bool = false) io = open(cachefile, "r") try - if !isvalid_cache_header(io) + checksum = isvalid_cache_header(io) + if iszero(checksum) @debug "Rejecting cache file $cachefile due to it containing an invalid cache header" return true # invalid cache file end @@ -2191,9 +2200,12 @@ end @debug "Rejecting cache file $cachefile for $modkey since it is for $id instead" return true end - if build_id != UInt64(0) && id.second != build_id - @debug "Ignoring cache file $cachefile for $modkey since it is does not provide desired build_id" - return true + if build_id != UInt128(0) + id_build = (UInt128(checksum) << 64) | id.second + if id_build != build_id + @debug "Ignoring cache file $cachefile for $modkey ($((UUID(id_build)))) since it is does not provide desired build_id ($((UUID(build_id))))" + return true + end end id = id.first modules = Dict{PkgId, UInt64}(modules) @@ -2233,11 +2245,12 @@ end for (req_key, req_build_id) in _concrete_dependencies build_id = get(modules, req_key, UInt64(0)) if build_id !== UInt64(0) + build_id |= UInt128(checksum) << 64 if build_id === req_build_id skip_timecheck = true break end - @debug "Rejecting cache file $cachefile because it provides the wrong build_id (got $build_id) for $req_key (want $req_build_id)" + @debug "Rejecting cache file $cachefile because it provides the wrong build_id (got $((UUID(build_id)))) for $req_key (want $(UUID(req_build_id)))" return true # cachefile doesn't provide the required version of the dependency end end diff --git a/deps/llvm.mk b/deps/llvm.mk index c13551ee331efc..78d037ec126d09 100644 --- a/deps/llvm.mk +++ b/deps/llvm.mk @@ -308,8 +308,8 @@ LLVM_TOOLS_JLL_TAGS := -llvm_version+$(LLVM_VER_MAJ) endif $(eval $(call bb-install,llvm,LLVM,false,true)) -$(eval $(call bb-install,clang,CLANG,false,true)) $(eval $(call bb-install,lld,LLD,false,true)) +$(eval $(call bb-install,clang,CLANG,false,true)) $(eval $(call bb-install,llvm-tools,LLVM_TOOLS,false,true)) endif # USE_BINARYBUILDER_LLVM diff --git a/src/Makefile b/src/Makefile index 886a0a546ff3ac..8ac15083a73e29 100644 --- a/src/Makefile +++ b/src/Makefile @@ -42,7 +42,7 @@ endif SRCS := \ jltypes gf typemap smallintset ast builtins module interpreter symbol \ - dlload sys init task array dump staticdata toplevel jl_uv datatype \ + dlload sys init task array staticdata toplevel jl_uv datatype \ simplevector runtime_intrinsics precompile jloptions \ threading partr stackwalk gc gc-debug gc-pages gc-stacks gc-alloc-profiler method \ jlapi signal-handling safepoint timing subtype rtutils gc-heap-snapshot \ @@ -291,7 +291,6 @@ $(BUILDDIR)/codegen.o $(BUILDDIR)/codegen.dbg.obj: $(addprefix $(SRCDIR)/,\ $(BUILDDIR)/datatype.o $(BUILDDIR)/datatype.dbg.obj: $(SRCDIR)/support/htable.h $(SRCDIR)/support/htable.inc $(BUILDDIR)/debuginfo.o $(BUILDDIR)/debuginfo.dbg.obj: $(addprefix $(SRCDIR)/,debuginfo.h processor.h jitlayers.h debug-registry.h) $(BUILDDIR)/disasm.o $(BUILDDIR)/disasm.dbg.obj: $(SRCDIR)/debuginfo.h $(SRCDIR)/processor.h -$(BUILDDIR)/dump.o $(BUILDDIR)/dump.dbg.obj: $(addprefix $(SRCDIR)/,common_symbols1.inc common_symbols2.inc builtin_proto.h serialize.h) $(BUILDDIR)/gc-debug.o $(BUILDDIR)/gc-debug.dbg.obj: $(SRCDIR)/gc.h $(BUILDDIR)/gc-pages.o $(BUILDDIR)/gc-pages.dbg.obj: $(SRCDIR)/gc.h $(BUILDDIR)/gc.o $(BUILDDIR)/gc.dbg.obj: $(SRCDIR)/gc.h $(SRCDIR)/gc-heap-snapshot.h $(SRCDIR)/gc-alloc-profiler.h @@ -317,7 +316,7 @@ $(BUILDDIR)/llvm-remove-addrspaces.o $(BUILDDIR)/llvm-remove-addrspaces.dbg.obj: $(BUILDDIR)/llvm-ptls.o $(BUILDDIR)/llvm-ptls.dbg.obj: $(SRCDIR)/codegen_shared.h $(BUILDDIR)/processor.o $(BUILDDIR)/processor.dbg.obj: $(addprefix $(SRCDIR)/,processor_*.cpp processor.h features_*.h) $(BUILDDIR)/signal-handling.o $(BUILDDIR)/signal-handling.dbg.obj: $(addprefix $(SRCDIR)/,signals-*.c) -$(BUILDDIR)/staticdata.o $(BUILDDIR)/staticdata.dbg.obj: $(SRCDIR)/processor.h $(SRCDIR)/builtin_proto.h +$(BUILDDIR)/staticdata.o $(BUILDDIR)/staticdata.dbg.obj: $(SRCDIR)/staticdata_utils.c $(SRCDIR)/processor.h $(SRCDIR)/builtin_proto.h $(BUILDDIR)/toplevel.o $(BUILDDIR)/toplevel.dbg.obj: $(SRCDIR)/builtin_proto.h $(BUILDDIR)/ircode.o $(BUILDDIR)/ircode.dbg.obj: $(SRCDIR)/serialize.h $(BUILDDIR)/pipeline.o $(BUILDDIR)/pipeline.dbg.obj: $(SRCDIR)/passes.h $(SRCDIR)/jitlayers.h @@ -453,7 +452,7 @@ SA_EXCEPTIONS-jloptions.c := -Xanalyzer -analyzer-config -Xana SA_EXCEPTIONS-subtype.c := -Xanalyzer -analyzer-config -Xanalyzer silence-checkers="core.uninitialized.Assign;core.UndefinedBinaryOperatorResult" SA_EXCEPTIONS-codegen.c := -Xanalyzer -analyzer-config -Xanalyzer silence-checkers="core" # these need to be annotated (and possibly fixed) -SKIP_IMPLICIT_ATOMICS := dump.c module.c staticdata.c codegen.cpp +SKIP_IMPLICIT_ATOMICS := module.c staticdata.c codegen.cpp # these need to be annotated (and possibly fixed) SKIP_GC_CHECK := codegen.cpp rtutils.c diff --git a/src/aotcompile.cpp b/src/aotcompile.cpp index 83e1c6d150430e..26ba66fa967371 100644 --- a/src/aotcompile.cpp +++ b/src/aotcompile.cpp @@ -92,7 +92,7 @@ typedef struct { std::vector jl_sysimg_fvars; std::vector jl_sysimg_gvars; std::map> jl_fvar_map; - std::map jl_value_to_llvm; // uses 1-based indexing + std::vector jl_value_to_llvm; } jl_native_code_desc_t; extern "C" JL_DLLEXPORT @@ -110,17 +110,12 @@ void jl_get_function_id_impl(void *native_code, jl_code_instance_t *codeinst, } extern "C" JL_DLLEXPORT -int32_t jl_get_llvm_gv_impl(void *native_code, jl_value_t *p) +void jl_get_llvm_gvs_impl(void *native_code, arraylist_t *gvs) { - // map a jl_value_t memory location to a GlobalVariable + // map a memory location (jl_value_t or jl_binding_t) to a GlobalVariable jl_native_code_desc_t *data = (jl_native_code_desc_t*)native_code; - if (data) { - auto it = data->jl_value_to_llvm.find(p); - if (it != data->jl_value_to_llvm.end()) { - return it->second; - } - } - return 0; + arraylist_grow(gvs, data->jl_value_to_llvm.size()); + memcpy(gvs->items, data->jl_value_to_llvm.data(), gvs->len * sizeof(void*)); } extern "C" JL_DLLEXPORT @@ -148,7 +143,6 @@ static void emit_offset_table(Module &mod, const std::vector &vars { // Emit a global variable with all the variable addresses. // The cloning pass will convert them into offsets. - assert(!vars.empty()); size_t nvars = vars.size(); std::vector addrs(nvars); for (size_t i = 0; i < nvars; i++) { @@ -258,9 +252,9 @@ static void jl_ci_cache_lookup(const jl_cgparams_t &cgparams, jl_method_instance // this builds the object file portion of the sysimage files for fast startup, and can // also be used be extern consumers like GPUCompiler.jl to obtain a module containing // all reachable & inferrrable functions. The `policy` flag switches between the default -// mode `0`, the extern mode `1`, and imaging mode `2`. +// mode `0`, the extern mode `1`. extern "C" JL_DLLEXPORT -void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvmmod, const jl_cgparams_t *cgparams, int _policy) +void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvmmod, const jl_cgparams_t *cgparams, int _policy, int _imaging_mode) { ++CreateNativeCalls; CreateNativeMax.updateMax(jl_array_len(methods)); @@ -268,7 +262,7 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm cgparams = &jl_default_cgparams; jl_native_code_desc_t *data = new jl_native_code_desc_t; CompilationPolicy policy = (CompilationPolicy) _policy; - bool imaging = imaging_default() || policy == CompilationPolicy::ImagingMode; + bool imaging = imaging_default() || _imaging_mode == 1; jl_workqueue_t emitted; jl_method_instance_t *mi = NULL; jl_code_info_t *src = NULL; @@ -342,10 +336,11 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm // process the globals array, before jl_merge_module destroys them std::vector gvars; + data->jl_value_to_llvm.resize(params.globals.size()); for (auto &global : params.globals) { + data->jl_value_to_llvm.at(gvars.size()) = global.first; gvars.push_back(std::string(global.second->getName())); - data->jl_value_to_llvm[global.first] = gvars.size(); } CreateNativeMethods += emitted.size(); @@ -575,7 +570,7 @@ void jl_dump_native_impl(void *native_code, Type *T_psize = T_size->getPointerTo(); // add metadata information - if (imaging_default()) { + if (imaging_default() || jl_options.outputo) { emit_offset_table(*dataM, data->jl_sysimg_gvars, "jl_sysimg_gvars", T_psize); emit_offset_table(*dataM, data->jl_sysimg_fvars, "jl_sysimg_fvars", T_psize); diff --git a/src/clangsa/GCChecker.cpp b/src/clangsa/GCChecker.cpp index 34821d6bac9cb5..513e6db606eb89 100644 --- a/src/clangsa/GCChecker.cpp +++ b/src/clangsa/GCChecker.cpp @@ -1332,7 +1332,7 @@ bool GCChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { } else if (name == "JL_GC_PUSH1" || name == "JL_GC_PUSH2" || name == "JL_GC_PUSH3" || name == "JL_GC_PUSH4" || name == "JL_GC_PUSH5" || name == "JL_GC_PUSH6" || - name == "JL_GC_PUSH7") { + name == "JL_GC_PUSH7" || name == "JL_GC_PUSH8") { ProgramStateRef State = C.getState(); // Transform slots to roots, transform values to rooted unsigned NumArgs = CE->getNumArgs(); diff --git a/src/codegen-stubs.c b/src/codegen-stubs.c index 1f209f36291a2f..01324e349f08f6 100644 --- a/src/codegen-stubs.c +++ b/src/codegen-stubs.c @@ -13,7 +13,7 @@ JL_DLLEXPORT void jl_dump_native_fallback(void *native_code, const char *bc_fname, const char *unopt_bc_fname, const char *obj_fname, const char *asm_fname, const char *sysimg_data, size_t sysimg_len) UNAVAILABLE -JL_DLLEXPORT int32_t jl_get_llvm_gv_fallback(void *native_code, jl_value_t *p) UNAVAILABLE +JL_DLLEXPORT void jl_get_llvm_gvs_fallback(void *native_code, arraylist_t *gvs) UNAVAILABLE JL_DLLEXPORT void jl_extern_c_fallback(jl_function_t *f, jl_value_t *rt, jl_value_t *argt, char *name) UNAVAILABLE JL_DLLEXPORT jl_value_t *jl_dump_method_asm_fallback(jl_method_instance_t *linfo, size_t world, @@ -66,7 +66,7 @@ JL_DLLEXPORT size_t jl_jit_total_bytes_fallback(void) return 0; } -JL_DLLEXPORT void *jl_create_native_fallback(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvmctxt, const jl_cgparams_t *cgparams, int _policy) UNAVAILABLE +JL_DLLEXPORT void *jl_create_native_fallback(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvmmod, const jl_cgparams_t *cgparams, int _policy, int _imaging_mode) UNAVAILABLE JL_DLLEXPORT void jl_dump_compiles_fallback(void *s) { diff --git a/src/codegen.cpp b/src/codegen.cpp index f02815df37e733..ce8f8f4adf48c7 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2222,7 +2222,8 @@ static void visitLine(jl_codectx_t &ctx, uint64_t *ptr, Value *addend, const cha static void coverageVisitLine(jl_codectx_t &ctx, StringRef filename, int line) { - assert(!ctx.emission_context.imaging); + if (ctx.emission_context.imaging) + return; // TODO if (filename == "" || filename == "none" || filename == "no file" || filename == "" || line < 0) return; visitLine(ctx, jl_coverage_data_pointer(filename, line), ConstantInt::get(getInt64Ty(ctx.builder.getContext()), 1), "lcnt"); @@ -2232,7 +2233,8 @@ static void coverageVisitLine(jl_codectx_t &ctx, StringRef filename, int line) static void mallocVisitLine(jl_codectx_t &ctx, StringRef filename, int line, Value *sync) { - assert(!ctx.emission_context.imaging); + if (ctx.emission_context.imaging) + return; // TODO if (filename == "" || filename == "none" || filename == "no file" || filename == "" || line < 0) return; Value *addend = sync @@ -4020,6 +4022,8 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, const std::string name; StringRef protoname; bool need_to_emit = true; + // TODO: We should check if the code is available externally + // and then emit a trampoline. if (ctx.use_cache) { // optimization: emit the correct name immediately, if we know it // TODO: use `emitted` map here too to try to consolidate names? @@ -6785,7 +6789,7 @@ static jl_llvm_functions_t }(); std::string wrapName; - raw_string_ostream(wrapName) << "jfptr_" << unadorned_name << "_" << globalUniqueGeneratedNames++; + raw_string_ostream(wrapName) << "jfptr_" << unadorned_name << "_" << globalUniqueGeneratedNames++; declarations.functionObject = wrapName; (void)gen_invoke_wrapper(lam, jlrettype, returninfo, retarg, declarations.functionObject, M, ctx.emission_context); // TODO: add attributes: maybe_mark_argument_dereferenceable(Arg, argType) @@ -8260,6 +8264,10 @@ void jl_compile_workqueue( StringRef preal_decl = ""; bool preal_specsig = false; auto invoke = jl_atomic_load_relaxed(&codeinst->invoke); + // TODO: available_extern + // We need to emit a trampoline that loads the target address in an extern_module from a GV + // Right now we will unecessarily emit a function we have already compiled in a native module + // again in a calling module. if (params.cache && invoke != NULL) { auto fptr = jl_atomic_load_relaxed(&codeinst->specptr.fptr); if (invoke == jl_fptr_args_addr) { diff --git a/src/datatype.c b/src/datatype.c index 9d22685473e075..b225ff3bd4fe24 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -72,7 +72,7 @@ JL_DLLEXPORT jl_typename_t *jl_new_typename_in(jl_sym_t *name, jl_module_t *modu jl_atomic_store_relaxed(&tn->cache, jl_emptysvec); jl_atomic_store_relaxed(&tn->linearcache, jl_emptysvec); tn->names = NULL; - tn->hash = bitmix(bitmix(module ? module->build_id : 0, name->hash), 0xa1ada1da); + tn->hash = bitmix(bitmix(module ? module->build_id.lo : 0, name->hash), 0xa1ada1da); tn->_reserved = 0; tn->abstract = abstract; tn->mutabl = mutabl; diff --git a/src/dlload.c b/src/dlload.c index 57310c18b0e464..dd5d75da31a34e 100644 --- a/src/dlload.c +++ b/src/dlload.c @@ -73,7 +73,7 @@ const char *jl_crtdll_name = CRTDLL_BASENAME ".dll"; #define JL_RTLD(flags, FLAG) (flags & JL_RTLD_ ## FLAG ? RTLD_ ## FLAG : 0) #ifdef _OS_WINDOWS_ -static void win32_formatmessage(DWORD code, char *reason, int len) JL_NOTSAFEPOINT +void win32_formatmessage(DWORD code, char *reason, int len) JL_NOTSAFEPOINT { DWORD res; LPWSTR errmsg; diff --git a/src/dump.c b/src/dump.c deleted file mode 100644 index 125f3faca36877..00000000000000 --- a/src/dump.c +++ /dev/null @@ -1,3577 +0,0 @@ -// This file is a part of Julia. License is MIT: https://julialang.org/license - -/* - saving and restoring precompiled modules (.ji files) -*/ -#include -#include - -#include "julia.h" -#include "julia_internal.h" -#include "julia_gcext.h" -#include "builtin_proto.h" -#include "serialize.h" - -#ifndef _OS_WINDOWS_ -#include -#endif - -#include "valgrind.h" -#include "julia_assert.h" - -#ifdef __cplusplus -extern "C" { -#endif - -// This file, together with ircode.c, allows (de)serialization between -// modules and *.ji cache files. `jl_save_incremental` gets called as the final step -// during package precompilation, and `_jl_restore_incremental` by `using SomePkg` -// whenever `SomePkg` has not yet been loaded. - -// Types, methods, and method instances form a graph that may have cycles, so -// serialization has to break these cycles. This is handled via "backreferences," -// referring to already (de)serialized items by an index. It is critial to ensure -// that the indexes of these backreferences align precisely during serialization -// and deserialization, to ensure that these integer indexes mean the same thing -// under both circumstances. Consequently, if you are modifying this file, be -// careful to match the sequence, if necessary reserving space for something that will -// be updated later. - -// It is also necessary to save & restore references to externally-defined -// objects, e.g., for package methods that call methods defined in Base or -// elsewhere. Consequently during deserialization there's a distinction between -// "reference" types, methods, and method instances (essentially like a -// GlobalRef), and "recached" version that refer to the actual entity in the -// running session. As a concrete example, types have a module in which they are -// defined, but once defined those types can be used by any dependent package. -// We don't store the full type definition again in that dependent package, we -// just encode a reference to that type. In the running session, such references -// are merely pointers to the type-cache, but the specific address is obviously -// not likely to be reproducible across sessions (it will differ between the -// session in which you precompile and the session in which you're using the -// package). Hence, during serialization we recode them as "verbose" references -// (that follow Julia syntax to allow them to be reconstructed), but on -// deserialization we have to replace those verbose references with the -// appropriate pointer in the user's running session. We complete -// deserialization before beginning the process of recaching, because we need -// the backreferences during deserialization and the actual objects during -// recaching. - -// Finally, because our backedge graph is not bidirectional, special handling is -// required to identify backedges from external methods that call internal methods. -// These get set aside and restored at the end of deserialization. - -// In broad terms, the major steps in serialization are: -// - starting from a "worklist" of modules, write the header. This stores things -// like the Julia build this was precompiled for, the package dependencies, -// the list of include files, file modification times, etc. -// - gather the collection of items to be written to this precompile file. This -// includes accessible from the module's binding table (if they are owned by a -// worklist module), but also includes things like methods added to external -// functions, instances of external methods that were newly type-inferred -// while precompiling a worklist module, and backedges of callees that were -// called by methods in this package. By and large, these latter items are not -// referenced by the module(s) in the package, and so these have to be -// extracted by traversing the entire system searching for things that do link -// back to a module in the worklist. -// - serialize all the items. The first time we encounter an item, we serialized -// it, and on future references (pointers) to that item we replace them with -// with a backreference. `jl_serialize_*` functions handle this work. -// - write source text for the files that defined the package. This is primarily -// to support Revise.jl. - -// Deserialization is the mirror image of serialization, but in some ways is -// trickier: -// - we have to merge items into the running session (recaching as described -// above) and handle cases like having two dependent packages caching the same -// MethodInstance of a dependency -// - we have to check for invalidation---the user might have loaded other -// packages that define methods that supersede some of the dispatches chosen -// when the package was precompiled, or this package might define methods that -// supersede dispatches for previously-loaded packages. These two -// possibilities are checked during backedge and method insertion, -// respectively. -// Both of these mean that deserialization requires one to look up a lot of -// things in the running session; for example, for invalidation checks we have -// to do type-intersection between signatures used for MethodInstances and the -// current session's full MethodTable. In practice, such steps dominate package -// loading time (it has very little to do with I/O or deserialization -// performance). Paradoxically, sometimes storing more code in a package can -// lead to faster performance: references to things in the same .ji file can be -// precomputed, but external references have to be looked up. You can see this -// effect in the benchmarks for #43990, where storing external MethodInstances -// and CodeInstances (more code than was stored previously) actually decreased -// load times for many packages. - -// Note that one should prioritize deserialization performance over serialization performance, -// since deserialization may be performed much more often than serialization. -// Certain items are preprocessed during serialization to save work when they are -// later deserialized. - - -// TODO: put WeakRefs on the weak_refs list during deserialization -// TODO: handle finalizers - -// type => tag hash for a few core types (e.g., Expr, PhiNode, etc) -static htable_t ser_tag; -// tag => type mapping, the reverse of ser_tag -static jl_value_t *deser_tag[256]; -// hash of some common symbols, encoded as CommonSym_tag plus 1 byte -static htable_t common_symbol_tag; -static jl_value_t *deser_symbols[256]; - -// table of all objects that have been deserialized, indexed by pos -// (the order in the serializer stream). the low -// bit is reserved for flagging certain entries and pos is -// left shift by 1 -static htable_t backref_table; // pos = backref_table[obj] -static int backref_table_numel; -static arraylist_t backref_list; // obj = backref_list[pos] - -// set of all CodeInstances yet to be (in)validated -static htable_t new_code_instance_validate; - -// list of (jl_value_t **loc, size_t pos) entries -// for anything that was flagged by the deserializer for later -// type-rewriting of some sort. pos is the index in backref_list. -static arraylist_t flagref_list; -// ref => value hash for looking up the "real" entity from -// the deserialized ref. Used for entities that must be unique, -// like types, methods, and method instances -static htable_t uniquing_table; - -// list of (size_t pos, itemkey) entries -// for the serializer to mark values in need of rework -// during deserialization later -// This includes items that need rehashing (IdDict, TypeMapLevels) -// and modules. -static arraylist_t reinit_list; - -// list of modules being serialized -// This is not quite globally rooted, but we take care to only -// ever assigned rooted values here. -static jl_array_t *serializer_worklist JL_GLOBALLY_ROOTED; -// The set of external MethodInstances we want to serialize -// (methods owned by other modules that were first inferred for a -// module currently being serialized) -static htable_t external_mis; -// Inference tracks newly-inferred MethodInstances during precompilation -// and registers them by calling jl_set_newly_inferred -static jl_array_t *newly_inferred JL_GLOBALLY_ROOTED; -// Mutex for newly_inferred -static jl_mutex_t newly_inferred_mutex; - -// New roots to add to Methods. These can't be added until after -// recaching is complete, so we have to hold on to them separately -// Stored as method => (worklist_key, newroots) -// The worklist_key is the uuid of the module that triggered addition -// of `newroots`. This is needed because CodeInstances reference -// their roots by "index", and we use a bipartite index -// (module_uuid, integer_index) to make indexes "relocatable" -// (meaning that users can load modules in different orders and -// so the absolute integer index of a root is not reproducible). -// See the "root blocks" section of method.c for more detail. -static htable_t queued_method_roots; - -// inverse of backedges graph (caller=>callees hash) -jl_array_t *edges_map JL_GLOBALLY_ROOTED; // rooted for the duration of our uses of this - -// list of requested ccallable signatures -static arraylist_t ccallable_list; - -typedef struct { - ios_t *s; - jl_ptls_t ptls; - jl_array_t *loaded_modules_array; -} jl_serializer_state; - -static jl_value_t *jl_idtable_type = NULL; -static jl_typename_t *jl_idtable_typename = NULL; -static jl_value_t *jl_bigint_type = NULL; -static int gmp_limb_size = 0; - -static void write_float64(ios_t *s, double x) JL_NOTSAFEPOINT -{ - write_uint64(s, *((uint64_t*)&x)); -} - -void *jl_lookup_ser_tag(jl_value_t *v) -{ - return ptrhash_get(&ser_tag, v); -} - -void *jl_lookup_common_symbol(jl_value_t *v) -{ - return ptrhash_get(&common_symbol_tag, v); -} - -jl_value_t *jl_deser_tag(uint8_t tag) -{ - return deser_tag[tag]; -} - -jl_value_t *jl_deser_symbol(uint8_t tag) -{ - return deser_symbols[tag]; -} - -uint64_t jl_worklist_key(jl_array_t *worklist) -{ - assert(jl_is_array(worklist)); - size_t len = jl_array_len(worklist); - if (len > 0) { - jl_module_t *topmod = (jl_module_t*)jl_array_ptr_ref(worklist, len-1); - assert(jl_is_module(topmod)); - return topmod->build_id; - } - return 0; -} - -// --- serialize --- - -#define jl_serialize_value(s, v) jl_serialize_value_((s), (jl_value_t*)(v), 0) -static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v, int as_literal) JL_GC_DISABLED; - -static void jl_serialize_cnull(jl_serializer_state *s, jl_value_t *t) -{ - backref_table_numel++; - write_uint8(s->s, TAG_CNULL); - jl_serialize_value(s, t); -} - -static int module_in_worklist(jl_module_t *mod) JL_NOTSAFEPOINT -{ - int i, l = jl_array_len(serializer_worklist); - for (i = 0; i < l; i++) { - jl_module_t *workmod = (jl_module_t*)jl_array_ptr_ref(serializer_worklist, i); - if (jl_is_module(workmod) && jl_is_submodule(mod, workmod)) - return 1; - } - return 0; -} - -static int method_instance_in_queue(jl_method_instance_t *mi) -{ - return ptrhash_get(&external_mis, mi) != HT_NOTFOUND; -} - -// compute whether a type references something internal to worklist -// and thus could not have existed before deserialize -// and thus does not need delayed unique-ing -static int type_in_worklist(jl_datatype_t *dt) JL_NOTSAFEPOINT -{ - if (module_in_worklist(dt->name->module)) - return 1; - int i, l = jl_svec_len(dt->parameters); - for (i = 0; i < l; i++) { - jl_value_t *p = jl_unwrap_unionall(jl_tparam(dt, i)); - // TODO: what about Union and TypeVar?? - if (type_in_worklist((jl_datatype_t*)(jl_is_datatype(p) ? p : jl_typeof(p)))) - return 1; - } - return 0; -} - -static int type_recursively_external(jl_datatype_t *dt); - -static int type_parameter_recursively_external(jl_value_t *p0) JL_NOTSAFEPOINT -{ - if (!jl_is_concrete_type(p0)) - return 0; - jl_datatype_t *p = (jl_datatype_t*)p0; - //while (jl_is_unionall(p)) { - // if (!type_parameter_recursively_external(((jl_unionall_t*)p)->var->lb)) - // return 0; - // if (!type_parameter_recursively_external(((jl_unionall_t*)p)->var->ub)) - // return 0; - // p = (jl_datatype_t*)((jl_unionall_t*)p)->body; - //} - if (module_in_worklist(p->name->module)) - return 0; - if (p->name->wrapper != (jl_value_t*)p0) { - if (!type_recursively_external(p)) - return 0; - } - return 1; -} - -// returns true if all of the parameters are tag 6 or 7 -static int type_recursively_external(jl_datatype_t *dt) JL_NOTSAFEPOINT -{ - if (!dt->isconcretetype) - return 0; - if (jl_svec_len(dt->parameters) == 0) - return 1; - - int i, l = jl_svec_len(dt->parameters); - for (i = 0; i < l; i++) { - if (!type_parameter_recursively_external(jl_tparam(dt, i))) - return 0; - } - return 1; -} - -static void mark_backedges_in_worklist(jl_method_instance_t *mi, htable_t *visited, int found) -{ - int oldfound = (char*)ptrhash_get(visited, mi) - (char*)HT_NOTFOUND; - if (oldfound < 3) - return; // not in-progress - ptrhash_put(visited, mi, (void*)((char*)HT_NOTFOUND + 1 + found)); -#ifndef NDEBUG - jl_module_t *mod = mi->def.module; - if (jl_is_method(mod)) - mod = ((jl_method_t*)mod)->module; - assert(jl_is_module(mod)); - assert(!mi->precompiled && !module_in_worklist(mod)); - assert(mi->backedges); -#endif - size_t i = 0, n = jl_array_len(mi->backedges); - while (i < n) { - jl_method_instance_t *be; - i = get_next_edge(mi->backedges, i, NULL, &be); - mark_backedges_in_worklist(be, visited, found); - } -} - -// When we infer external method instances, ensure they link back to the -// package. Otherwise they might be, e.g., for external macros -static int has_backedge_to_worklist(jl_method_instance_t *mi, htable_t *visited, int depth) -{ - jl_module_t *mod = mi->def.module; - if (jl_is_method(mod)) - mod = ((jl_method_t*)mod)->module; - assert(jl_is_module(mod)); - if (mi->precompiled || module_in_worklist(mod)) { - return 1; - } - if (!mi->backedges) { - return 0; - } - void **bp = ptrhash_bp(visited, mi); - // HT_NOTFOUND: not yet analyzed - // HT_NOTFOUND + 1: no link back - // HT_NOTFOUND + 2: does link back - // HT_NOTFOUND + 3 + depth: in-progress - int found = (char*)*bp - (char*)HT_NOTFOUND; - if (found) - return found - 1; - *bp = (void*)((char*)HT_NOTFOUND + 3 + depth); // preliminarily mark as in-progress - size_t i = 0, n = jl_array_len(mi->backedges); - int cycle = 0; - while (i < n) { - jl_method_instance_t *be; - i = get_next_edge(mi->backedges, i, NULL, &be); - int child_found = has_backedge_to_worklist(be, visited, depth + 1); - if (child_found == 1) { - found = 1; - break; - } - else if (child_found >= 2 && child_found - 2 < cycle) { - // record the cycle will resolve at depth "cycle" - cycle = child_found - 2; - assert(cycle); - } - } - if (!found && cycle && cycle != depth) - return cycle + 2; - bp = ptrhash_bp(visited, mi); // re-acquire since rehashing might change the location - *bp = (void*)((char*)HT_NOTFOUND + 1 + found); - if (cycle) { - // If we are the top of the current cycle, now mark all other parts of - // our cycle by re-walking the backedges graph and marking all WIP - // items as found. - // Be careful to only re-walk as far as we had originally scanned above. - // Or if we found a backedge, also mark all of the other parts of the - // cycle as also having an backedge. - n = i; - i = 0; - while (i < n) { - jl_method_instance_t *be; - i = get_next_edge(mi->backedges, i, NULL, &be); - mark_backedges_in_worklist(be, visited, found); - } - } - return found; -} - -// given the list of MethodInstances that were inferred during the -// build, select those that are external and have at least one -// relocatable CodeInstance and are inferred to be called from the worklist -// or explicitly added by a precompile statement. -// Also prepares external_mis for method_instance_in_queue queries. -static jl_array_t *queue_external_mis(jl_array_t *list) -{ - if (list == NULL) - return NULL; - size_t i, n = 0; - htable_t visited; - assert(jl_is_array(list)); - size_t n0 = jl_array_len(list); - htable_new(&visited, n0); - for (i = 0; i < n0; i++) { - jl_method_instance_t *mi = (jl_method_instance_t*)jl_array_ptr_ref(list, i); - assert(jl_is_method_instance(mi)); - if (jl_is_method(mi->def.value)) { - jl_method_t *m = mi->def.method; - if (!module_in_worklist(m->module)) { - jl_code_instance_t *ci = mi->cache; - while (ci) { - if (ci->max_world == ~(size_t)0 && ci->relocatability && ci->inferred) - break; - ci = jl_atomic_load_relaxed(&ci->next); - } - if (ci && ptrhash_get(&external_mis, mi) == HT_NOTFOUND) { - int found = has_backedge_to_worklist(mi, &visited, 1); - assert(found == 0 || found == 1); - if (found == 1) { - ptrhash_put(&external_mis, mi, ci); - n++; - } - } - } - } - } - htable_free(&visited); - if (n == 0) - return NULL; - jl_array_t *mi_list = jl_alloc_vec_any(n); - n = 0; - for (size_t i = 0; i < external_mis.size; i += 2) { - void *ci = external_mis.table[i+1]; - if (ci != HT_NOTFOUND) { - jl_array_ptr_set(mi_list, n++, (jl_value_t*)ci); - } - } - assert(n == jl_array_len(mi_list)); - return mi_list; -} - -static void jl_serialize_datatype(jl_serializer_state *s, jl_datatype_t *dt) JL_GC_DISABLED -{ - int tag = 0; - int internal = module_in_worklist(dt->name->module); - if (!internal && jl_unwrap_unionall(dt->name->wrapper) == (jl_value_t*)dt) { - tag = 6; // external primary type - } - else if (jl_is_tuple_type(dt) ? !dt->isconcretetype : dt->hasfreetypevars) { - tag = 0; // normal struct - } - else if (internal) { - if (jl_unwrap_unionall(dt->name->wrapper) == (jl_value_t*)dt) // comes up often since functions create types - tag = 5; // internal, and not in the typename cache - else - tag = 10; // anything else that's internal (just may need recaching) - } - else if (type_recursively_external(dt)) { - tag = 7; // external type that can be immediately recreated (with apply_type) - } - else if (type_in_worklist(dt)) { - tag = 11; // external, but definitely new (still needs caching, but not full unique-ing) - } - else { - // this is eligible for (and possibly requires) unique-ing later, - // so flag this in the backref table as special - uintptr_t *bp = (uintptr_t*)ptrhash_bp(&backref_table, dt); - assert(*bp != (uintptr_t)HT_NOTFOUND); - *bp |= 1; - tag = 12; - } - - write_uint8(s->s, TAG_DATATYPE); - write_uint8(s->s, tag); - if (tag == 6 || tag == 7) { - // for tag==6, copy its typevars in case there are references to them elsewhere - jl_serialize_value(s, dt->name); - jl_serialize_value(s, dt->parameters); - return; - } - - int has_instance = (dt->instance != NULL); - int has_layout = (dt->layout != NULL); - write_uint8(s->s, has_layout | (has_instance << 1)); - write_uint8(s->s, dt->hasfreetypevars - | (dt->isconcretetype << 1) - | (dt->isdispatchtuple << 2) - | (dt->isbitstype << 3) - | (dt->zeroinit << 4) - | (dt->has_concrete_subtype << 5) - | (dt->cached_by_hash << 6) - | (dt->isprimitivetype << 7)); - write_int32(s->s, dt->hash); - - if (has_layout) { - uint8_t layout = 0; - if (dt->layout == ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_array_type))->layout) { - layout = 1; - } - else if (dt->layout == jl_nothing_type->layout) { - layout = 2; - } - else if (dt->layout == ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_pointer_type))->layout) { - layout = 3; - } - write_uint8(s->s, layout); - if (layout == 0) { - uint32_t nf = dt->layout->nfields; - uint32_t np = dt->layout->npointers; - size_t fieldsize = jl_fielddesc_size(dt->layout->fielddesc_type); - ios_write(s->s, (const char*)dt->layout, sizeof(*dt->layout)); - size_t fldsize = nf * fieldsize; - if (dt->layout->first_ptr != -1) - fldsize += np << dt->layout->fielddesc_type; - ios_write(s->s, (const char*)(dt->layout + 1), fldsize); - } - } - - if (has_instance) - jl_serialize_value(s, dt->instance); - jl_serialize_value(s, dt->name); - jl_serialize_value(s, dt->parameters); - jl_serialize_value(s, dt->super); - jl_serialize_value(s, dt->types); -} - -static void jl_serialize_module(jl_serializer_state *s, jl_module_t *m) -{ - write_uint8(s->s, TAG_MODULE); - jl_serialize_value(s, m->name); - size_t i; - if (!module_in_worklist(m)) { - if (m == m->parent) { - // top-level module - write_int8(s->s, 2); - int j = 0; - for (i = 0; i < jl_array_len(s->loaded_modules_array); i++) { - jl_module_t *mi = (jl_module_t*)jl_array_ptr_ref(s->loaded_modules_array, i); - if (!module_in_worklist(mi)) { - if (m == mi) { - write_int32(s->s, j); - return; - } - j++; - } - } - assert(0 && "top level module not found in modules array"); - } - else { - write_int8(s->s, 1); - jl_serialize_value(s, m->parent); - } - return; - } - write_int8(s->s, 0); - jl_serialize_value(s, m->parent); - void **table = m->bindings.table; - for (i = 0; i < m->bindings.size; i += 2) { - if (table[i+1] != HT_NOTFOUND) { - jl_serialize_value(s, (jl_value_t*)table[i]); - jl_binding_t *b = (jl_binding_t*)table[i+1]; - jl_serialize_value(s, b->name); - jl_value_t *e = jl_atomic_load_relaxed(&b->value); - if (!b->constp && e && jl_is_cpointer(e) && jl_unbox_voidpointer(e) != (void*)-1 && jl_unbox_voidpointer(e) != NULL) - // reset Ptr fields to C_NULL (but keep MAP_FAILED / INVALID_HANDLE) - jl_serialize_cnull(s, jl_typeof(e)); - else - jl_serialize_value(s, e); - jl_serialize_value(s, jl_atomic_load_relaxed(&b->globalref)); - jl_serialize_value(s, b->owner); - jl_serialize_value(s, jl_atomic_load_relaxed(&b->ty)); - write_int8(s->s, (b->deprecated<<3) | (b->constp<<2) | (b->exportp<<1) | (b->imported)); - } - } - jl_serialize_value(s, NULL); - write_int32(s->s, m->usings.len); - for(i=0; i < m->usings.len; i++) { - jl_serialize_value(s, (jl_value_t*)m->usings.items[i]); - } - write_uint8(s->s, m->istopmod); - write_uint64(s->s, m->uuid.hi); - write_uint64(s->s, m->uuid.lo); - write_uint64(s->s, m->build_id); - write_int32(s->s, m->counter); - write_int32(s->s, m->nospecialize); - write_uint8(s->s, m->optlevel); - write_uint8(s->s, m->compile); - write_uint8(s->s, m->infer); - write_uint8(s->s, m->max_methods); -} - -static int jl_serialize_generic(jl_serializer_state *s, jl_value_t *v) JL_GC_DISABLED -{ - if (v == NULL) { - write_uint8(s->s, TAG_NULL); - return 1; - } - - void *tag = ptrhash_get(&ser_tag, v); - if (tag != HT_NOTFOUND) { - uint8_t t8 = (intptr_t)tag; - if (t8 <= LAST_TAG) - write_uint8(s->s, 0); - write_uint8(s->s, t8); - return 1; - } - - if (jl_is_symbol(v)) { - void *idx = ptrhash_get(&common_symbol_tag, v); - if (idx != HT_NOTFOUND) { - write_uint8(s->s, TAG_COMMONSYM); - write_uint8(s->s, (uint8_t)(size_t)idx); - return 1; - } - } - else if (v == (jl_value_t*)jl_core_module) { - write_uint8(s->s, TAG_CORE); - return 1; - } - else if (v == (jl_value_t*)jl_base_module) { - write_uint8(s->s, TAG_BASE); - return 1; - } - - if (jl_typeis(v, jl_string_type) && jl_string_len(v) == 0) { - jl_serialize_value(s, jl_an_empty_string); - return 1; - } - else if (!jl_is_uint8(v)) { - void **bp = ptrhash_bp(&backref_table, v); - if (*bp != HT_NOTFOUND) { - uintptr_t pos = (char*)*bp - (char*)HT_NOTFOUND - 1; - if (pos < 65536) { - write_uint8(s->s, TAG_SHORT_BACKREF); - write_uint16(s->s, pos); - } - else { - write_uint8(s->s, TAG_BACKREF); - write_int32(s->s, pos); - } - return 1; - } - intptr_t pos = backref_table_numel++; - if (((jl_datatype_t*)(jl_typeof(v)))->name == jl_idtable_typename) { - // will need to rehash this, later (after types are fully constructed) - arraylist_push(&reinit_list, (void*)pos); - arraylist_push(&reinit_list, (void*)1); - } - if (jl_is_module(v)) { - jl_module_t *m = (jl_module_t*)v; - if (module_in_worklist(m) && !module_in_worklist(m->parent)) { - // will need to reinsert this into parent bindings, later (in case of any errors during reinsert) - arraylist_push(&reinit_list, (void*)pos); - arraylist_push(&reinit_list, (void*)2); - } - } - // TypeMapLevels need to be rehashed - if (jl_is_mtable(v)) { - arraylist_push(&reinit_list, (void*)pos); - arraylist_push(&reinit_list, (void*)3); - } - pos <<= 1; - ptrhash_put(&backref_table, v, (char*)HT_NOTFOUND + pos + 1); - } - - return 0; -} - -static void jl_serialize_code_instance(jl_serializer_state *s, jl_code_instance_t *codeinst, - int skip_partial_opaque, int force) JL_GC_DISABLED -{ - if (!force && jl_serialize_generic(s, (jl_value_t*)codeinst)) { - return; - } - assert(codeinst != NULL); // handle by jl_serialize_generic, but this makes clang-sa happy - - int validate = 0; - if (codeinst->max_world == ~(size_t)0 && codeinst->inferred) - // TODO: also check if this object is part of the codeinst cache and in edges_map - validate = 1; // can check on deserialize if this cache entry is still valid - int flags = validate << 0; - if (codeinst->invoke == jl_fptr_const_return) - flags |= 1 << 2; - if (codeinst->precompile) - flags |= 1 << 3; - - // CodeInstances with PartialOpaque return type are currently not allowed - // to be cached. We skip them in serialization here, forcing them to - // be re-infered on reload. - int write_ret_type = validate || codeinst->min_world == 0; - if (write_ret_type && codeinst->rettype_const && - jl_typeis(codeinst->rettype_const, jl_partial_opaque_type)) { - if (skip_partial_opaque) { - jl_serialize_code_instance(s, codeinst->next, skip_partial_opaque, 0); - return; - } - else { - jl_error("Cannot serialize CodeInstance with PartialOpaque rettype"); - } - } - - write_uint8(s->s, TAG_CODE_INSTANCE); - write_uint8(s->s, flags); - write_uint32(s->s, codeinst->ipo_purity_bits); - write_uint32(s->s, jl_atomic_load_relaxed(&codeinst->purity_bits)); - jl_serialize_value(s, (jl_value_t*)codeinst->def); - if (write_ret_type) { - jl_serialize_value(s, jl_atomic_load_relaxed(&codeinst->inferred)); - jl_serialize_value(s, codeinst->rettype_const); - jl_serialize_value(s, codeinst->rettype); - jl_serialize_value(s, codeinst->argescapes); - } - else { - // skip storing useless data - jl_serialize_value(s, NULL); - jl_serialize_value(s, NULL); - jl_serialize_value(s, jl_any_type); - jl_serialize_value(s, jl_nothing); - } - write_uint8(s->s, codeinst->relocatability); - jl_serialize_code_instance(s, codeinst->next, skip_partial_opaque, 0); -} - -enum METHOD_SERIALIZATION_MODE { - METHOD_INTERNAL = 1, - METHOD_EXTERNAL_MT = 2, - METHOD_HAS_NEW_ROOTS = 4, -}; - -static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v, int as_literal) JL_GC_DISABLED -{ - if (jl_serialize_generic(s, v)) { - return; - } - - size_t i; - if (jl_is_svec(v)) { - size_t l = jl_svec_len(v); - if (l <= 255) { - write_uint8(s->s, TAG_SVEC); - write_uint8(s->s, (uint8_t)l); - } - else { - write_uint8(s->s, TAG_LONG_SVEC); - write_int32(s->s, l); - } - for (i = 0; i < l; i++) { - jl_serialize_value(s, jl_svecref(v, i)); - } - } - else if (jl_is_symbol(v)) { - size_t l = strlen(jl_symbol_name((jl_sym_t*)v)); - if (l <= 255) { - write_uint8(s->s, TAG_SYMBOL); - write_uint8(s->s, (uint8_t)l); - } - else { - write_uint8(s->s, TAG_LONG_SYMBOL); - write_int32(s->s, l); - } - ios_write(s->s, jl_symbol_name((jl_sym_t*)v), l); - } - else if (jl_is_array(v)) { - jl_array_t *ar = (jl_array_t*)v; - jl_value_t *et = jl_tparam0(jl_typeof(ar)); - int isunion = jl_is_uniontype(et); - if (ar->flags.ndims == 1 && ar->elsize <= 0x1f) { - write_uint8(s->s, TAG_ARRAY1D); - write_uint8(s->s, (ar->flags.ptrarray << 7) | (ar->flags.hasptr << 6) | (isunion << 5) | (ar->elsize & 0x1f)); - } - else { - write_uint8(s->s, TAG_ARRAY); - write_uint16(s->s, ar->flags.ndims); - write_uint16(s->s, (ar->flags.ptrarray << 15) | (ar->flags.hasptr << 14) | (isunion << 13) | (ar->elsize & 0x1fff)); - } - for (i = 0; i < ar->flags.ndims; i++) - jl_serialize_value(s, jl_box_long(jl_array_dim(ar,i))); - jl_serialize_value(s, jl_typeof(ar)); - size_t l = jl_array_len(ar); - if (ar->flags.ptrarray) { - for (i = 0; i < l; i++) { - jl_value_t *e = jl_array_ptr_ref(v, i); - if (e && jl_is_cpointer(e) && jl_unbox_voidpointer(e) != (void*)-1 && jl_unbox_voidpointer(e) != NULL) - // reset Ptr elements to C_NULL (but keep MAP_FAILED / INVALID_HANDLE) - jl_serialize_cnull(s, jl_typeof(e)); - else - jl_serialize_value(s, e); - } - } - else if (ar->flags.hasptr) { - const char *data = (const char*)jl_array_data(ar); - uint16_t elsz = ar->elsize; - size_t j, np = ((jl_datatype_t*)et)->layout->npointers; - for (i = 0; i < l; i++) { - const char *start = data; - for (j = 0; j < np; j++) { - uint32_t ptr = jl_ptr_offset((jl_datatype_t*)et, j); - const jl_value_t *const *fld = &((const jl_value_t *const *)data)[ptr]; - if ((const char*)fld != start) - ios_write(s->s, start, (const char*)fld - start); - JL_GC_PROMISE_ROOTED(*fld); - jl_serialize_value(s, *fld); - start = (const char*)&fld[1]; - } - data += elsz; - if (data != start) - ios_write(s->s, start, data - start); - } - } - else if (jl_is_cpointer_type(et)) { - // reset Ptr elements to C_NULL - const void **data = (const void**)jl_array_data(ar); - for (i = 0; i < l; i++) { - const void *e = data[i]; - if (e != (void*)-1) - e = NULL; - ios_write(s->s, (const char*)&e, sizeof(e)); - } - } - else { - ios_write(s->s, (char*)jl_array_data(ar), l * ar->elsize); - if (jl_array_isbitsunion(ar)) - ios_write(s->s, jl_array_typetagdata(ar), l); - } - } - else if (jl_is_datatype(v)) { - jl_serialize_datatype(s, (jl_datatype_t*)v); - } - else if (jl_is_unionall(v)) { - write_uint8(s->s, TAG_UNIONALL); - jl_datatype_t *d = (jl_datatype_t*)jl_unwrap_unionall(v); - if (jl_is_datatype(d) && d->name->wrapper == v && - !module_in_worklist(d->name->module)) { - write_uint8(s->s, 1); - jl_serialize_value(s, d->name->module); - jl_serialize_value(s, d->name->name); - } - else { - write_uint8(s->s, 0); - jl_serialize_value(s, ((jl_unionall_t*)v)->var); - jl_serialize_value(s, ((jl_unionall_t*)v)->body); - } - } - else if (jl_is_typevar(v)) { - write_uint8(s->s, TAG_TVAR); - jl_serialize_value(s, ((jl_tvar_t*)v)->name); - jl_serialize_value(s, ((jl_tvar_t*)v)->lb); - jl_serialize_value(s, ((jl_tvar_t*)v)->ub); - } - else if (jl_is_method(v)) { - write_uint8(s->s, TAG_METHOD); - jl_method_t *m = (jl_method_t*)v; - uint64_t key = 0; - int serialization_mode = 0, nwithkey = 0; - if (m->is_for_opaque_closure || module_in_worklist(m->module)) - serialization_mode |= METHOD_INTERNAL; - if (!(serialization_mode & METHOD_INTERNAL)) { - key = jl_worklist_key(serializer_worklist); - nwithkey = nroots_with_key(m, key); - if (nwithkey > 0) - serialization_mode |= METHOD_HAS_NEW_ROOTS; - } - if (!(serialization_mode & METHOD_INTERNAL)) { - // flag this in the backref table as special - uintptr_t *bp = (uintptr_t*)ptrhash_bp(&backref_table, v); - assert(*bp != (uintptr_t)HT_NOTFOUND); - *bp |= 1; - } - jl_serialize_value(s, (jl_value_t*)m->sig); - jl_serialize_value(s, (jl_value_t*)m->module); - if (m->external_mt != NULL) { - assert(jl_typeis(m->external_mt, jl_methtable_type)); - jl_methtable_t *mt = (jl_methtable_t*)m->external_mt; - if (!module_in_worklist(mt->module)) { - serialization_mode |= METHOD_EXTERNAL_MT; - } - } - write_uint8(s->s, serialization_mode); - if (serialization_mode & METHOD_EXTERNAL_MT) { - // We reference this method table by module and binding - jl_methtable_t *mt = (jl_methtable_t*)m->external_mt; - jl_serialize_value(s, mt->module); - jl_serialize_value(s, mt->name); - } - else { - jl_serialize_value(s, (jl_value_t*)m->external_mt); - } - if (!(serialization_mode & METHOD_INTERNAL)) { - if (serialization_mode & METHOD_HAS_NEW_ROOTS) { - // Serialize the roots that belong to key - write_uint64(s->s, key); - write_int32(s->s, nwithkey); - rle_iter_state rootiter = rle_iter_init(0); - uint64_t *rletable = NULL; - size_t nblocks2 = 0, nroots = jl_array_len(m->roots); - if (m->root_blocks) { - rletable = (uint64_t*)jl_array_data(m->root_blocks); - nblocks2 = jl_array_len(m->root_blocks); - } - // this visits every item, if it becomes a bottleneck we could hop blocks - while (rle_iter_increment(&rootiter, nroots, rletable, nblocks2)) - if (rootiter.key == key) - jl_serialize_value(s, jl_array_ptr_ref(m->roots, rootiter.i)); - } - return; - } - jl_serialize_value(s, m->specializations); - jl_serialize_value(s, jl_atomic_load_relaxed(&m->speckeyset)); - jl_serialize_value(s, (jl_value_t*)m->name); - jl_serialize_value(s, (jl_value_t*)m->file); - write_int32(s->s, m->line); - write_int32(s->s, m->called); - write_int32(s->s, m->nargs); - write_int32(s->s, m->nospecialize); - write_int32(s->s, m->nkw); - write_int8(s->s, m->isva); - write_int8(s->s, m->pure); - write_int8(s->s, m->is_for_opaque_closure); - write_int8(s->s, m->constprop); - write_uint8(s->s, m->purity.bits); - jl_serialize_value(s, (jl_value_t*)m->slot_syms); - jl_serialize_value(s, (jl_value_t*)m->roots); - jl_serialize_value(s, (jl_value_t*)m->root_blocks); - write_int32(s->s, m->nroots_sysimg); - jl_serialize_value(s, (jl_value_t*)m->ccallable); - jl_serialize_value(s, (jl_value_t*)m->source); - jl_serialize_value(s, (jl_value_t*)m->unspecialized); - jl_serialize_value(s, (jl_value_t*)m->generator); - jl_serialize_value(s, (jl_value_t*)m->invokes); - jl_serialize_value(s, (jl_value_t*)m->recursion_relation); - } - else if (jl_is_method_instance(v)) { - jl_method_instance_t *mi = (jl_method_instance_t*)v; - if (jl_is_method(mi->def.value) && mi->def.method->is_for_opaque_closure) { - jl_error("unimplemented: serialization of MethodInstances for OpaqueClosure"); - } - write_uint8(s->s, TAG_METHOD_INSTANCE); - int internal = 0; - if (!jl_is_method(mi->def.method)) - internal = 1; - else if (module_in_worklist(mi->def.method->module)) - internal = 2; - write_uint8(s->s, internal); - if (!internal) { - // also flag this in the backref table as special - uintptr_t *bp = (uintptr_t*)ptrhash_bp(&backref_table, v); - assert(*bp != (uintptr_t)HT_NOTFOUND); - *bp |= 1; - } - if (internal == 1) - jl_serialize_value(s, (jl_value_t*)mi->uninferred); - jl_serialize_value(s, (jl_value_t*)mi->specTypes); - jl_serialize_value(s, mi->def.value); - if (!internal) - return; - jl_serialize_value(s, (jl_value_t*)mi->sparam_vals); - jl_array_t *backedges = mi->backedges; - if (backedges) { - // filter backedges to only contain pointers - // to items that we will actually store (internal >= 2) - size_t ins = 0, i = 0, l = jl_array_len(backedges); - jl_value_t **b_edges = (jl_value_t**)jl_array_data(backedges); - jl_value_t *invokeTypes; - jl_method_instance_t *backedge; - while (i < l) { - i = get_next_edge(backedges, i, &invokeTypes, &backedge); - if (module_in_worklist(backedge->def.method->module) || method_instance_in_queue(backedge)) { - if (invokeTypes) - b_edges[ins++] = invokeTypes; - b_edges[ins++] = (jl_value_t*)backedge; - } - } - if (ins != l) - jl_array_del_end(backedges, l - ins); - if (ins == 0) - backedges = NULL; - } - jl_serialize_value(s, (jl_value_t*)backedges); - jl_serialize_value(s, (jl_value_t*)NULL); //callbacks - jl_serialize_code_instance(s, mi->cache, 1, 0); - } - else if (jl_is_code_instance(v)) { - jl_serialize_code_instance(s, (jl_code_instance_t*)v, 0, 1); - } - else if (jl_typeis(v, jl_module_type)) { - jl_serialize_module(s, (jl_module_t*)v); - } - else if (jl_typeis(v, jl_task_type)) { - jl_error("Task cannot be serialized"); - } - else if (jl_typeis(v, jl_opaque_closure_type)) { - jl_error("Live opaque closures cannot be serialized"); - } - else if (jl_typeis(v, jl_string_type)) { - write_uint8(s->s, TAG_STRING); - write_int32(s->s, jl_string_len(v)); - ios_write(s->s, jl_string_data(v), jl_string_len(v)); - } - else if (jl_typeis(v, jl_int64_type)) { - void *data = jl_data_ptr(v); - if (*(int64_t*)data >= INT16_MIN && *(int64_t*)data <= INT16_MAX) { - write_uint8(s->s, TAG_SHORTER_INT64); - write_uint16(s->s, (uint16_t)*(int64_t*)data); - } - else if (*(int64_t*)data >= S32_MIN && *(int64_t*)data <= S32_MAX) { - write_uint8(s->s, TAG_SHORT_INT64); - write_int32(s->s, (int32_t)*(int64_t*)data); - } - else { - write_uint8(s->s, TAG_INT64); - write_uint64(s->s, *(int64_t*)data); - } - } - else if (jl_typeis(v, jl_int32_type)) { - void *data = jl_data_ptr(v); - if (*(int32_t*)data >= INT16_MIN && *(int32_t*)data <= INT16_MAX) { - write_uint8(s->s, TAG_SHORT_INT32); - write_uint16(s->s, (uint16_t)*(int32_t*)data); - } - else { - write_uint8(s->s, TAG_INT32); - write_int32(s->s, *(int32_t*)data); - } - } - else if (jl_typeis(v, jl_uint8_type)) { - write_uint8(s->s, TAG_UINT8); - write_int8(s->s, *(int8_t*)jl_data_ptr(v)); - } - else if (jl_is_cpointer(v) && jl_unbox_voidpointer(v) == NULL) { - write_uint8(s->s, TAG_CNULL); - jl_serialize_value(s, jl_typeof(v)); - return; - } - else if (jl_bigint_type && jl_typeis(v, jl_bigint_type)) { - write_uint8(s->s, TAG_SHORT_GENERAL); - write_uint8(s->s, jl_datatype_size(jl_bigint_type)); - jl_serialize_value(s, jl_bigint_type); - jl_value_t *sizefield = jl_get_nth_field(v, 1); - jl_serialize_value(s, sizefield); - void *data = jl_unbox_voidpointer(jl_get_nth_field(v, 2)); - int32_t sz = jl_unbox_int32(sizefield); - size_t nb = (sz == 0 ? 1 : (sz < 0 ? -sz : sz)) * gmp_limb_size; - ios_write(s->s, (char*)data, nb); - } - else { - jl_datatype_t *t = (jl_datatype_t*)jl_typeof(v); - if (v == t->instance) { - if (!type_in_worklist(t)) { - // also flag this in the backref table as special - // if it might not be unique (is external) - uintptr_t *bp = (uintptr_t*)ptrhash_bp(&backref_table, v); - assert(*bp != (uintptr_t)HT_NOTFOUND); - *bp |= 1; - } - write_uint8(s->s, TAG_SINGLETON); - jl_serialize_value(s, t); - return; - } - assert(!t->instance && "detected singleton construction corruption"); - - if (t == jl_typename_type) { - void *bttag = ptrhash_get(&ser_tag, ((jl_typename_t*)t)->wrapper); - if (bttag != HT_NOTFOUND) { - write_uint8(s->s, TAG_BITYPENAME); - write_uint8(s->s, (uint8_t)(intptr_t)bttag); - return; - } - } - size_t tsz = jl_datatype_size(t); - if (tsz <= 255) { - write_uint8(s->s, TAG_SHORT_GENERAL); - write_uint8(s->s, tsz); - } - else { - write_uint8(s->s, TAG_GENERAL); - write_int32(s->s, tsz); - } - jl_serialize_value(s, t); - if (t == jl_typename_type) { - jl_typename_t *tn = (jl_typename_t*)v; - int internal = module_in_worklist(tn->module); - write_uint8(s->s, internal); - jl_serialize_value(s, tn->module); - jl_serialize_value(s, tn->name); - if (internal) { - jl_serialize_value(s, tn->names); - jl_serialize_value(s, tn->wrapper); - jl_serialize_value(s, tn->mt); - ios_write(s->s, (char*)&tn->hash, sizeof(tn->hash)); - write_uint8(s->s, tn->abstract | (tn->mutabl << 1) | (tn->mayinlinealloc << 2)); - write_uint8(s->s, tn->max_methods); - if (!tn->abstract) - write_uint16(s->s, tn->n_uninitialized); - size_t nb = tn->atomicfields ? (jl_svec_len(tn->names) + 31) / 32 * sizeof(uint32_t) : 0; - write_int32(s->s, nb); - if (nb) - ios_write(s->s, (char*)tn->atomicfields, nb); - nb = tn->constfields ? (jl_svec_len(tn->names) + 31) / 32 * sizeof(uint32_t) : 0; - write_int32(s->s, nb); - if (nb) - ios_write(s->s, (char*)tn->constfields, nb); - } - return; - } - - if (jl_is_foreign_type(t)) { - jl_error("Cannot serialize instances of foreign datatypes"); - } - - char *data = (char*)jl_data_ptr(v); - size_t i, j, np = t->layout->npointers; - uint32_t nf = t->layout->nfields; - char *last = data; - for (i = 0, j = 0; i < nf+1; i++) { - char *ptr = data + (i < nf ? jl_field_offset(t, i) : jl_datatype_size(t)); - if (j < np) { - char *prevptr = (char*)&((jl_value_t**)data)[jl_ptr_offset(t, j)]; - while (ptr > prevptr) { - // previous field contained pointers; write them and their interleaved data - if (prevptr > last) - ios_write(s->s, last, prevptr - last); - jl_value_t *e = *(jl_value_t**)prevptr; - JL_GC_PROMISE_ROOTED(e); - if (t->name->mutabl && e && jl_field_isptr(t, i - 1) && jl_is_cpointer(e) && - jl_unbox_voidpointer(e) != (void*)-1 && jl_unbox_voidpointer(e) != NULL) - // reset Ptr fields to C_NULL (but keep MAP_FAILED / INVALID_HANDLE) - jl_serialize_cnull(s, jl_typeof(e)); - else - jl_serialize_value(s, e); - last = prevptr + sizeof(jl_value_t*); - j++; - if (j < np) - prevptr = (char*)&((jl_value_t**)data)[jl_ptr_offset(t, j)]; - else - break; - } - } - if (i == nf) - break; - if (t->name->mutabl && jl_is_cpointer_type(jl_field_type(t, i)) && *(void**)ptr != (void*)-1) { - if (ptr > last) - ios_write(s->s, last, ptr - last); - char *n = NULL; - ios_write(s->s, (char*)&n, sizeof(n)); - last = ptr + sizeof(n); - } - } - char *ptr = data + jl_datatype_size(t); - if (ptr > last) - ios_write(s->s, last, ptr - last); - } -} - - -// Create the forward-edge map (caller => callees) -// the intent of these functions is to invert the backedges tree -// for anything that points to a method not part of the worklist -// -// from MethodTables -static void jl_collect_missing_backedges(jl_methtable_t *mt) -{ - jl_array_t *backedges = mt->backedges; - if (backedges) { - size_t i, l = jl_array_len(backedges); - for (i = 1; i < l; i += 2) { - jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(backedges, i); - jl_value_t *missing_callee = jl_array_ptr_ref(backedges, i - 1); // signature of abstract callee - jl_array_t *edges = (jl_array_t*)jl_eqtable_get(edges_map, (jl_value_t*)caller, NULL); - if (edges == NULL) { - edges = jl_alloc_vec_any(0); - JL_GC_PUSH1(&edges); - edges_map = jl_eqtable_put(edges_map, (jl_value_t*)caller, (jl_value_t*)edges, NULL); - JL_GC_POP(); - } - jl_array_ptr_1d_push(edges, NULL); - jl_array_ptr_1d_push(edges, missing_callee); - } - } -} - - -// from MethodInstances -static void collect_backedges(jl_method_instance_t *callee, int internal) JL_GC_DISABLED -{ - jl_array_t *backedges = callee->backedges; - if (backedges) { - size_t i = 0, l = jl_array_len(backedges); - while (i < l) { - jl_value_t *invokeTypes; - jl_method_instance_t *caller; - i = get_next_edge(backedges, i, &invokeTypes, &caller); - jl_array_t *edges = (jl_array_t*)jl_eqtable_get(edges_map, (jl_value_t*)caller, NULL); - if (edges == NULL) { - edges = jl_alloc_vec_any(0); - JL_GC_PUSH1(&edges); - edges_map = jl_eqtable_put(edges_map, (jl_value_t*)caller, (jl_value_t*)edges, NULL); - JL_GC_POP(); - } - jl_array_ptr_1d_push(edges, invokeTypes); - jl_array_ptr_1d_push(edges, (jl_value_t*)callee); - } - } -} - - -// For functions owned by modules not on the worklist, call this on each method. -// - if the method is owned by a worklist module, add it to the list of things to be -// fully serialized -// - Collect all backedges (may be needed later when we invert this list). -static int jl_collect_methcache_from_mod(jl_typemap_entry_t *ml, void *closure) JL_GC_DISABLED -{ - jl_array_t *s = (jl_array_t*)closure; - jl_method_t *m = ml->func.method; - if (s && module_in_worklist(m->module)) { - jl_array_ptr_1d_push(s, (jl_value_t*)m); - jl_array_ptr_1d_push(s, (jl_value_t*)ml->simplesig); - } - jl_svec_t *specializations = m->specializations; - size_t i, l = jl_svec_len(specializations); - for (i = 0; i < l; i++) { - jl_method_instance_t *callee = (jl_method_instance_t*)jl_svecref(specializations, i); - if ((jl_value_t*)callee != jl_nothing) - collect_backedges(callee, !s); - } - return 1; -} - -static void jl_collect_methtable_from_mod(jl_array_t *s, jl_methtable_t *mt) JL_GC_DISABLED -{ - jl_typemap_visitor(mt->defs, jl_collect_methcache_from_mod, (void*)s); -} - -// Collect methods of external functions defined by modules in the worklist -// "extext" = "extending external" -// Also collect relevant backedges -static void jl_collect_extext_methods_from_mod(jl_array_t *s, jl_module_t *m) JL_GC_DISABLED -{ - if (s && module_in_worklist(m)) - s = NULL; // do not collect any methods - size_t i; - void **table = m->bindings.table; - for (i = 1; i < m->bindings.size; i += 2) { - if (table[i] != HT_NOTFOUND) { - jl_binding_t *b = (jl_binding_t*)table[i]; - if (b->owner == m && b->value && b->constp) { - jl_value_t *bv = jl_unwrap_unionall(b->value); - if (jl_is_datatype(bv)) { - jl_typename_t *tn = ((jl_datatype_t*)bv)->name; - if (tn->module == m && tn->name == b->name && tn->wrapper == b->value) { - jl_methtable_t *mt = tn->mt; - if (mt != NULL && - (jl_value_t*)mt != jl_nothing && - (mt != jl_type_type_mt && mt != jl_nonfunction_mt)) { - assert(mt->module == tn->module); - jl_collect_methtable_from_mod(s, mt); - if (s) - jl_collect_missing_backedges(mt); - } - } - } - else if (jl_is_module(b->value)) { - jl_module_t *child = (jl_module_t*)b->value; - if (child != m && child->parent == m && child->name == b->name) { - // this is the original/primary binding for the submodule - jl_collect_extext_methods_from_mod(s, (jl_module_t*)b->value); - } - } - else if (jl_is_mtable(b->value)) { - jl_methtable_t *mt = (jl_methtable_t*)b->value; - if (mt->module == m && mt->name == b->name) { - // this is probably an external method table, so let's assume so - // as there is no way to precisely distinguish them, - // and the rest of this serializer does not bother - // to handle any method tables specially - jl_collect_methtable_from_mod(s, (jl_methtable_t*)bv); - } - } - } - } - } -} - -static void jl_record_edges(jl_method_instance_t *caller, arraylist_t *wq, jl_array_t *edges) JL_GC_DISABLED -{ - jl_array_t *callees = (jl_array_t*)jl_eqtable_pop(edges_map, (jl_value_t*)caller, NULL, NULL); - if (callees != NULL) { - jl_array_ptr_1d_push(edges, (jl_value_t*)caller); - jl_array_ptr_1d_push(edges, (jl_value_t*)callees); - size_t i, l = jl_array_len(callees); - for (i = 1; i < l; i += 2) { - jl_method_instance_t *c = (jl_method_instance_t*)jl_array_ptr_ref(callees, i); - if (c && jl_is_method_instance(c)) { - arraylist_push(wq, c); - } - } - } -} - - -// Extract `edges` and `ext_targets` from `edges_map` -// `edges` = [caller1, targets_indexes1, ...], the list of methods and their edges -// `ext_targets` is [invokesig1, callee1, matches1, ...], the edges for each target -static void jl_collect_edges(jl_array_t *edges, jl_array_t *ext_targets) -{ - size_t world = jl_atomic_load_acquire(&jl_world_counter); - arraylist_t wq; - arraylist_new(&wq, 0); - void **table = (void**)jl_array_data(edges_map); // edges is caller => callees - size_t table_size = jl_array_len(edges_map); - for (size_t i = 0; i < table_size; i += 2) { - assert(table == jl_array_data(edges_map) && table_size == jl_array_len(edges_map) && - "edges_map changed during iteration"); - jl_method_instance_t *caller = (jl_method_instance_t*)table[i]; - jl_array_t *callees = (jl_array_t*)table[i + 1]; - if (callees == NULL) - continue; - assert(jl_is_method_instance(caller) && jl_is_method(caller->def.method)); - if (module_in_worklist(caller->def.method->module) || - method_instance_in_queue(caller)) { - jl_record_edges(caller, &wq, edges); - } - } - while (wq.len) { - jl_method_instance_t *caller = (jl_method_instance_t*)arraylist_pop(&wq); - jl_record_edges(caller, &wq, edges); - } - arraylist_free(&wq); - edges_map = NULL; - htable_t edges_map2; - htable_new(&edges_map2, 0); - htable_t edges_ids; - size_t l = jl_array_len(edges); - htable_new(&edges_ids, l); - for (size_t i = 0; i < l / 2; i++) { - jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, i * 2); - void *target = (void*)((char*)HT_NOTFOUND + i + 1); - ptrhash_put(&edges_ids, (void*)caller, target); - } - // process target list to turn it into a memoized validity table - // and compute the old methods list, ready for serialization - jl_value_t *matches = NULL; - jl_array_t *callee_ids = NULL; - JL_GC_PUSH2(&matches, &callee_ids); - for (size_t i = 0; i < l; i += 2) { - jl_array_t *callees = (jl_array_t*)jl_array_ptr_ref(edges, i + 1); - size_t l = jl_array_len(callees); - callee_ids = jl_alloc_array_1d(jl_array_int32_type, l + 1); - int32_t *idxs = (int32_t*)jl_array_data(callee_ids); - idxs[0] = 0; - size_t nt = 0; - for (size_t j = 0; j < l; j += 2) { - jl_value_t *invokeTypes = jl_array_ptr_ref(callees, j); - jl_value_t *callee = jl_array_ptr_ref(callees, j + 1); - assert(callee && "unsupported edge"); - - if (jl_is_method_instance(callee)) { - jl_methtable_t *mt = jl_method_get_table(((jl_method_instance_t*)callee)->def.method); - if (module_in_worklist(mt->module)) - continue; - } - - // (nullptr, c) => call - // (invokeTypes, c) => invoke - // (nullptr, invokeTypes) => missing call - // (invokeTypes, nullptr) => missing invoke (unused--inferred as Any) - void *target = ptrhash_get(&edges_map2, invokeTypes ? (void*)invokeTypes : (void*)callee); - if (target == HT_NOTFOUND) { - size_t min_valid = 0; - size_t max_valid = ~(size_t)0; - if (invokeTypes) { - jl_methtable_t *mt = jl_method_get_table(((jl_method_instance_t*)callee)->def.method); - if ((jl_value_t*)mt == jl_nothing) { - callee_ids = NULL; // invalid - break; - } - else { - matches = jl_gf_invoke_lookup_worlds(invokeTypes, (jl_value_t*)mt, world, &min_valid, &max_valid); - if (matches == jl_nothing) { - callee_ids = NULL; // invalid - break; - } - matches = (jl_value_t*)((jl_method_match_t*)matches)->method; - } - } - else { - jl_value_t *sig; - if (jl_is_method_instance(callee)) - sig = ((jl_method_instance_t*)callee)->specTypes; - else - sig = callee; - int ambig = 0; - matches = jl_matching_methods((jl_tupletype_t*)sig, jl_nothing, - -1, 0, world, &min_valid, &max_valid, &ambig); - if (matches == jl_nothing) { - callee_ids = NULL; // invalid - break; - } - size_t k; - for (k = 0; k < jl_array_len(matches); k++) { - jl_method_match_t *match = (jl_method_match_t *)jl_array_ptr_ref(matches, k); - jl_array_ptr_set(matches, k, match->method); - } - } - jl_array_ptr_1d_push(ext_targets, invokeTypes); - jl_array_ptr_1d_push(ext_targets, callee); - jl_array_ptr_1d_push(ext_targets, matches); - target = (void*)((char*)HT_NOTFOUND + jl_array_len(ext_targets) / 3); - ptrhash_put(&edges_map2, (void*)callee, target); - } - idxs[++nt] = (char*)target - (char*)HT_NOTFOUND - 1; - } - jl_array_ptr_set(edges, i + 1, callee_ids); // swap callees for ids - if (!callee_ids) - continue; - idxs[0] = nt; - // record place of every method in edges - // add method edges to the callee_ids list - for (size_t j = 0; j < l; j += 2) { - jl_value_t *callee = jl_array_ptr_ref(callees, j + 1); - if (callee && jl_is_method_instance(callee)) { - void *target = ptrhash_get(&edges_ids, (void*)callee); - if (target != HT_NOTFOUND) { - idxs[++nt] = (char*)target - (char*)HT_NOTFOUND - 1; - } - } - } - jl_array_del_end(callee_ids, l - nt); - } - JL_GC_POP(); - htable_free(&edges_map2); -} - -// serialize information about all loaded modules -static void write_mod_list(ios_t *s, jl_array_t *a) -{ - size_t i; - size_t len = jl_array_len(a); - for (i = 0; i < len; i++) { - jl_module_t *m = (jl_module_t*)jl_array_ptr_ref(a, i); - assert(jl_is_module(m)); - if (!module_in_worklist(m)) { - const char *modname = jl_symbol_name(m->name); - size_t l = strlen(modname); - write_int32(s, l); - ios_write(s, modname, l); - write_uint64(s, m->uuid.hi); - write_uint64(s, m->uuid.lo); - write_uint64(s, m->build_id); - } - } - write_int32(s, 0); -} - -// "magic" string and version header of .ji file -static const int JI_FORMAT_VERSION = 11; -static const char JI_MAGIC[] = "\373jli\r\n\032\n"; // based on PNG signature -static const uint16_t BOM = 0xFEFF; // byte-order marker -static void write_header(ios_t *s) -{ - ios_write(s, JI_MAGIC, strlen(JI_MAGIC)); - write_uint16(s, JI_FORMAT_VERSION); - ios_write(s, (char *) &BOM, 2); - write_uint8(s, sizeof(void*)); - ios_write(s, JL_BUILD_UNAME, strlen(JL_BUILD_UNAME)+1); - ios_write(s, JL_BUILD_ARCH, strlen(JL_BUILD_ARCH)+1); - ios_write(s, JULIA_VERSION_STRING, strlen(JULIA_VERSION_STRING)+1); - const char *branch = jl_git_branch(), *commit = jl_git_commit(); - ios_write(s, branch, strlen(branch)+1); - ios_write(s, commit, strlen(commit)+1); -} - -// serialize information about the result of deserializing this file -static void write_work_list(ios_t *s) -{ - int i, l = jl_array_len(serializer_worklist); - for (i = 0; i < l; i++) { - jl_module_t *workmod = (jl_module_t*)jl_array_ptr_ref(serializer_worklist, i); - if (workmod->parent == jl_main_module || workmod->parent == workmod) { - size_t l = strlen(jl_symbol_name(workmod->name)); - write_int32(s, l); - ios_write(s, jl_symbol_name(workmod->name), l); - write_uint64(s, workmod->uuid.hi); - write_uint64(s, workmod->uuid.lo); - write_uint64(s, workmod->build_id); - } - } - write_int32(s, 0); -} - -static void write_module_path(ios_t *s, jl_module_t *depmod) JL_NOTSAFEPOINT -{ - if (depmod->parent == jl_main_module || depmod->parent == depmod) - return; - const char *mname = jl_symbol_name(depmod->name); - size_t slen = strlen(mname); - write_module_path(s, depmod->parent); - write_int32(s, slen); - ios_write(s, mname, slen); -} - -// Cache file header -// Serialize the global Base._require_dependencies array of pathnames that -// are include dependencies. Also write Preferences and return -// the location of the srctext "pointer" in the header index. -static int64_t write_dependency_list(ios_t *s, jl_array_t **udepsp) -{ - int64_t initial_pos = 0; - int64_t pos = 0; - static jl_array_t *deps = NULL; - if (!deps) - deps = (jl_array_t*)jl_get_global(jl_base_module, jl_symbol("_require_dependencies")); - - // unique(deps) to eliminate duplicates while preserving order: - // we preserve order so that the topmost included .jl file comes first - static jl_value_t *unique_func = NULL; - if (!unique_func) - unique_func = jl_get_global(jl_base_module, jl_symbol("unique")); - jl_value_t *uniqargs[2] = {unique_func, (jl_value_t*)deps}; - jl_task_t *ct = jl_current_task; - size_t last_age = ct->world_age; - ct->world_age = jl_atomic_load_acquire(&jl_world_counter); - jl_array_t *udeps = (*udepsp = deps && unique_func ? (jl_array_t*)jl_apply(uniqargs, 2) : NULL); - ct->world_age = last_age; - - // write a placeholder for total size so that we can quickly seek past all of the - // dependencies if we don't need them - initial_pos = ios_pos(s); - write_uint64(s, 0); - if (udeps) { - size_t i, l = jl_array_len(udeps); - for (i = 0; i < l; i++) { - jl_value_t *deptuple = jl_array_ptr_ref(udeps, i); - jl_value_t *dep = jl_fieldref(deptuple, 1); // file abspath - size_t slen = jl_string_len(dep); - write_int32(s, slen); - ios_write(s, jl_string_data(dep), slen); - write_float64(s, jl_unbox_float64(jl_fieldref(deptuple, 2))); // mtime - jl_module_t *depmod = (jl_module_t*)jl_fieldref(deptuple, 0); // evaluating module - jl_module_t *depmod_top = depmod; - while (depmod_top->parent != jl_main_module && depmod_top->parent != depmod_top) - depmod_top = depmod_top->parent; - unsigned provides = 0; - size_t j, lj = jl_array_len(serializer_worklist); - for (j = 0; j < lj; j++) { - jl_module_t *workmod = (jl_module_t*)jl_array_ptr_ref(serializer_worklist, j); - if (workmod->parent == jl_main_module || workmod->parent == workmod) { - ++provides; - if (workmod == depmod_top) { - write_int32(s, provides); - write_module_path(s, depmod); - break; - } - } - } - write_int32(s, 0); - } - write_int32(s, 0); // terminator, for ease of reading - - // Calculate Preferences hash for current package. - jl_value_t *prefs_hash = NULL; - jl_value_t *prefs_list = NULL; - JL_GC_PUSH1(&prefs_list); - if (jl_base_module) { - // Toplevel module is the module we're currently compiling, use it to get our preferences hash - jl_value_t * toplevel = (jl_value_t*)jl_get_global(jl_base_module, jl_symbol("__toplevel__")); - jl_value_t * prefs_hash_func = jl_get_global(jl_base_module, jl_symbol("get_preferences_hash")); - jl_value_t * get_compiletime_prefs_func = jl_get_global(jl_base_module, jl_symbol("get_compiletime_preferences")); - - if (toplevel && prefs_hash_func && get_compiletime_prefs_func) { - // Temporary invoke in newest world age - size_t last_age = ct->world_age; - ct->world_age = jl_atomic_load_acquire(&jl_world_counter); - - // call get_compiletime_prefs(__toplevel__) - jl_value_t *args[3] = {get_compiletime_prefs_func, (jl_value_t*)toplevel, NULL}; - prefs_list = (jl_value_t*)jl_apply(args, 2); - - // Call get_preferences_hash(__toplevel__, prefs_list) - args[0] = prefs_hash_func; - args[2] = prefs_list; - prefs_hash = (jl_value_t*)jl_apply(args, 3); - - // Reset world age to normal - ct->world_age = last_age; - } - } - - // If we successfully got the preferences, write it out, otherwise write `0` for this `.ji` file. - if (prefs_hash != NULL && prefs_list != NULL) { - size_t i, l = jl_array_len(prefs_list); - for (i = 0; i < l; i++) { - jl_value_t *pref_name = jl_array_ptr_ref(prefs_list, i); - size_t slen = jl_string_len(pref_name); - write_int32(s, slen); - ios_write(s, jl_string_data(pref_name), slen); - } - write_int32(s, 0); // terminator - write_uint64(s, jl_unbox_uint64(prefs_hash)); - } else { - // This is an error path, but let's at least generate a valid `.ji` file. - // We declare an empty list of preference names, followed by a zero-hash. - // The zero-hash is not what would be generated for an empty set of preferences, - // and so this `.ji` file will be invalidated by a future non-erroring pass - // through this function. - write_int32(s, 0); - write_uint64(s, 0); - } - JL_GC_POP(); // for prefs_list - - // write a dummy file position to indicate the beginning of the source-text - pos = ios_pos(s); - ios_seek(s, initial_pos); - write_uint64(s, pos - initial_pos); - ios_seek(s, pos); - write_uint64(s, 0); - } - return pos; -} - -// --- deserialize --- - -static jl_value_t *jl_deserialize_value(jl_serializer_state *s, jl_value_t **loc) JL_GC_DISABLED; - -static jl_value_t *jl_deserialize_datatype(jl_serializer_state *s, int pos, jl_value_t **loc) JL_GC_DISABLED -{ - assert(pos == backref_list.len - 1 && "nothing should have been deserialized since assigning pos"); - int tag = read_uint8(s->s); - if (tag == 6 || tag == 7) { - jl_typename_t *name = (jl_typename_t*)jl_deserialize_value(s, NULL); - jl_value_t *dtv = name->wrapper; - jl_svec_t *parameters = (jl_svec_t*)jl_deserialize_value(s, NULL); - dtv = jl_apply_type(dtv, jl_svec_data(parameters), jl_svec_len(parameters)); - backref_list.items[pos] = dtv; - return dtv; - } - if (!(tag == 0 || tag == 5 || tag == 10 || tag == 11 || tag == 12)) { - assert(0 && "corrupt deserialization state"); - abort(); - } - jl_datatype_t *dt = jl_new_uninitialized_datatype(); - backref_list.items[pos] = dt; - if (loc != NULL && loc != HT_NOTFOUND) - *loc = (jl_value_t*)dt; - uint8_t flags = read_uint8(s->s); - uint8_t memflags = read_uint8(s->s); - int has_layout = flags & 1; - int has_instance = (flags >> 1) & 1; - dt->hasfreetypevars = memflags & 1; - dt->isconcretetype = (memflags >> 1) & 1; - dt->isdispatchtuple = (memflags >> 2) & 1; - dt->isbitstype = (memflags >> 3) & 1; - dt->zeroinit = (memflags >> 4) & 1; - dt->has_concrete_subtype = (memflags >> 5) & 1; - dt->cached_by_hash = (memflags >> 6) & 1; - dt->isprimitivetype = (memflags >> 7) & 1; - dt->hash = read_int32(s->s); - - if (has_layout) { - uint8_t layout = read_uint8(s->s); - if (layout == 1) { - dt->layout = ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_array_type))->layout; - } - else if (layout == 2) { - dt->layout = jl_nothing_type->layout; - } - else if (layout == 3) { - dt->layout = ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_pointer_type))->layout; - } - else { - assert(layout == 0); - jl_datatype_layout_t buffer; - ios_readall(s->s, (char*)&buffer, sizeof(buffer)); - uint32_t nf = buffer.nfields; - uint32_t np = buffer.npointers; - uint8_t fielddesc_type = buffer.fielddesc_type; - size_t fielddesc_size = nf > 0 ? jl_fielddesc_size(fielddesc_type) : 0; - size_t fldsize = nf * fielddesc_size; - if (buffer.first_ptr != -1) - fldsize += np << fielddesc_type; - jl_datatype_layout_t *layout = (jl_datatype_layout_t*)jl_gc_perm_alloc( - sizeof(jl_datatype_layout_t) + fldsize, - 0, 4, 0); - *layout = buffer; - ios_readall(s->s, (char*)(layout + 1), fldsize); - dt->layout = layout; - } - } - - if (tag == 10 || tag == 11 || tag == 12) { - assert(pos > 0); - arraylist_push(&flagref_list, loc == HT_NOTFOUND ? NULL : loc); - arraylist_push(&flagref_list, (void*)(uintptr_t)pos); - ptrhash_put(&uniquing_table, dt, NULL); - } - - if (has_instance) { - assert(dt->isconcretetype && "there shouldn't be an instance on an abstract type"); - dt->instance = jl_deserialize_value(s, &dt->instance); - jl_gc_wb(dt, dt->instance); - } - dt->name = (jl_typename_t*)jl_deserialize_value(s, (jl_value_t**)&dt->name); - jl_gc_wb(dt, dt->name); - dt->parameters = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&dt->parameters); - jl_gc_wb(dt, dt->parameters); - dt->super = (jl_datatype_t*)jl_deserialize_value(s, (jl_value_t**)&dt->super); - jl_gc_wb(dt, dt->super); - dt->types = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&dt->types); - if (dt->types) jl_gc_wb(dt, dt->types); - - return (jl_value_t*)dt; -} - -static jl_value_t *jl_deserialize_value_svec(jl_serializer_state *s, uint8_t tag, jl_value_t **loc) JL_GC_DISABLED -{ - size_t i, len; - if (tag == TAG_SVEC) - len = read_uint8(s->s); - else - len = read_int32(s->s); - jl_svec_t *sv = jl_alloc_svec(len); - if (loc != NULL) - *loc = (jl_value_t*)sv; - arraylist_push(&backref_list, (jl_value_t*)sv); - jl_value_t **data = jl_svec_data(sv); - for (i = 0; i < len; i++) { - data[i] = jl_deserialize_value(s, &data[i]); - } - return (jl_value_t*)sv; -} - -static jl_value_t *jl_deserialize_value_symbol(jl_serializer_state *s, uint8_t tag) JL_GC_DISABLED -{ - size_t len; - if (tag == TAG_SYMBOL) - len = read_uint8(s->s); - else - len = read_int32(s->s); - char *name = (char*)(len >= 256 ? malloc_s(len + 1) : alloca(len + 1)); - ios_readall(s->s, name, len); - name[len] = '\0'; - jl_value_t *sym = (jl_value_t*)jl_symbol(name); - if (len >= 256) - free(name); - arraylist_push(&backref_list, sym); - return sym; -} - -static jl_value_t *jl_deserialize_value_array(jl_serializer_state *s, uint8_t tag) JL_GC_DISABLED -{ - int16_t i, ndims; - int isptr, isunion, hasptr, elsize; - if (tag == TAG_ARRAY1D) { - ndims = 1; - elsize = read_uint8(s->s); - isptr = (elsize >> 7) & 1; - hasptr = (elsize >> 6) & 1; - isunion = (elsize >> 5) & 1; - elsize = elsize & 0x1f; - } - else { - ndims = read_uint16(s->s); - elsize = read_uint16(s->s); - isptr = (elsize >> 15) & 1; - hasptr = (elsize >> 14) & 1; - isunion = (elsize >> 13) & 1; - elsize = elsize & 0x1fff; - } - uintptr_t pos = backref_list.len; - arraylist_push(&backref_list, NULL); - size_t *dims = (size_t*)alloca(ndims * sizeof(size_t)); - for (i = 0; i < ndims; i++) { - dims[i] = jl_unbox_long(jl_deserialize_value(s, NULL)); - } - jl_array_t *a = jl_new_array_for_deserialization( - (jl_value_t*)NULL, ndims, dims, !isptr, hasptr, isunion, elsize); - backref_list.items[pos] = a; - jl_value_t *aty = jl_deserialize_value(s, &jl_astaggedvalue(a)->type); - jl_set_typeof(a, aty); - if (a->flags.ptrarray) { - jl_value_t **data = (jl_value_t**)jl_array_data(a); - size_t i, numel = jl_array_len(a); - for (i = 0; i < numel; i++) { - data[i] = jl_deserialize_value(s, &data[i]); - //if (data[i]) // not needed because `a` is new (gc is disabled) - // jl_gc_wb(a, data[i]); - } - assert(jl_astaggedvalue(a)->bits.gc == GC_CLEAN); // gc is disabled - } - else if (a->flags.hasptr) { - size_t i, numel = jl_array_len(a); - char *data = (char*)jl_array_data(a); - uint16_t elsz = a->elsize; - jl_datatype_t *et = (jl_datatype_t*)jl_tparam0(jl_typeof(a)); - size_t j, np = et->layout->npointers; - for (i = 0; i < numel; i++) { - char *start = data; - for (j = 0; j < np; j++) { - uint32_t ptr = jl_ptr_offset(et, j); - jl_value_t **fld = &((jl_value_t**)data)[ptr]; - if ((char*)fld != start) - ios_readall(s->s, start, (const char*)fld - start); - *fld = jl_deserialize_value(s, fld); - //if (*fld) // not needed because `a` is new (gc is disabled) - // jl_gc_wb(a, *fld); - start = (char*)&fld[1]; - } - data += elsz; - if (data != start) - ios_readall(s->s, start, data - start); - } - assert(jl_astaggedvalue(a)->bits.gc == GC_CLEAN); // gc is disabled - } - else { - size_t extra = jl_array_isbitsunion(a) ? jl_array_len(a) : 0; - size_t tot = jl_array_len(a) * a->elsize + extra; - ios_readall(s->s, (char*)jl_array_data(a), tot); - } - return (jl_value_t*)a; -} - -static jl_value_t *jl_deserialize_value_method(jl_serializer_state *s, jl_value_t **loc) JL_GC_DISABLED -{ - jl_method_t *m = - (jl_method_t*)jl_gc_alloc(s->ptls, sizeof(jl_method_t), - jl_method_type); - memset(m, 0, sizeof(jl_method_t)); - uintptr_t pos = backref_list.len; - arraylist_push(&backref_list, m); - m->sig = (jl_value_t*)jl_deserialize_value(s, (jl_value_t**)&m->sig); - jl_gc_wb(m, m->sig); - m->module = (jl_module_t*)jl_deserialize_value(s, (jl_value_t**)&m->module); - jl_gc_wb(m, m->module); - int serialization_mode = read_uint8(s->s); - if (serialization_mode & METHOD_EXTERNAL_MT) { - jl_module_t *mt_mod = (jl_module_t*)jl_deserialize_value(s, NULL); - jl_sym_t *mt_name = (jl_sym_t*)jl_deserialize_value(s, NULL); - m->external_mt = jl_get_global(mt_mod, mt_name); - jl_gc_wb(m, m->external_mt); - assert(jl_typeis(m->external_mt, jl_methtable_type)); - } - else { - m->external_mt = jl_deserialize_value(s, &m->external_mt); - jl_gc_wb(m, m->external_mt); - } - if (!(serialization_mode & METHOD_INTERNAL)) { - assert(loc != NULL && loc != HT_NOTFOUND); - arraylist_push(&flagref_list, loc); - arraylist_push(&flagref_list, (void*)pos); - if (serialization_mode & METHOD_HAS_NEW_ROOTS) { - uint64_t key = read_uint64(s->s); - int i, nnew = read_int32(s->s); - jl_array_t *newroots = jl_alloc_vec_any(nnew); - jl_value_t **data = (jl_value_t**)jl_array_data(newroots); - for (i = 0; i < nnew; i++) - data[i] = jl_deserialize_value(s, &(data[i])); - // Storing the new roots in `m->roots` risks losing them due to recaching - // (which replaces pointers to `m` with ones to the "live" method). - // Put them in separate storage so we can find them later. - assert(ptrhash_get(&queued_method_roots, m) == HT_NOTFOUND); - // In storing the key, on 32-bit platforms we need two slots. Might as well do this for all platforms. - jl_svec_t *qmrval = jl_alloc_svec_uninit(3); // GC is disabled - jl_svec_data(qmrval)[0] = (jl_value_t*)(uintptr_t)(key & ((((uint64_t)1) << 32) - 1)); // lo bits - jl_svec_data(qmrval)[1] = (jl_value_t*)(uintptr_t)((key >> 32) & ((((uint64_t)1) << 32) - 1)); // hi bits - jl_svec_data(qmrval)[2] = (jl_value_t*)newroots; - ptrhash_put(&queued_method_roots, m, qmrval); - } - return (jl_value_t*)m; - } - m->specializations = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&m->specializations); - jl_gc_wb(m, m->specializations); - jl_array_t *speckeyset = (jl_array_t*)jl_deserialize_value(s, (jl_value_t**)&m->speckeyset); - jl_atomic_store_relaxed(&m->speckeyset, speckeyset); - jl_gc_wb(m, speckeyset); - m->name = (jl_sym_t*)jl_deserialize_value(s, NULL); - jl_gc_wb(m, m->name); - m->file = (jl_sym_t*)jl_deserialize_value(s, NULL); - m->line = read_int32(s->s); - m->primary_world = jl_atomic_load_acquire(&jl_world_counter); - m->deleted_world = ~(size_t)0; - m->called = read_int32(s->s); - m->nargs = read_int32(s->s); - m->nospecialize = read_int32(s->s); - m->nkw = read_int32(s->s); - m->isva = read_int8(s->s); - m->pure = read_int8(s->s); - m->is_for_opaque_closure = read_int8(s->s); - m->constprop = read_int8(s->s); - m->purity.bits = read_uint8(s->s); - m->slot_syms = jl_deserialize_value(s, (jl_value_t**)&m->slot_syms); - jl_gc_wb(m, m->slot_syms); - m->roots = (jl_array_t*)jl_deserialize_value(s, (jl_value_t**)&m->roots); - if (m->roots) - jl_gc_wb(m, m->roots); - m->root_blocks = (jl_array_t*)jl_deserialize_value(s, (jl_value_t**)&m->root_blocks); - if (m->root_blocks) - jl_gc_wb(m, m->root_blocks); - m->nroots_sysimg = read_int32(s->s); - m->ccallable = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&m->ccallable); - if (m->ccallable) { - jl_gc_wb(m, m->ccallable); - arraylist_push(&ccallable_list, m->ccallable); - } - m->source = jl_deserialize_value(s, &m->source); - if (m->source) - jl_gc_wb(m, m->source); - m->unspecialized = (jl_method_instance_t*)jl_deserialize_value(s, (jl_value_t**)&m->unspecialized); - if (m->unspecialized) - jl_gc_wb(m, m->unspecialized); - m->generator = jl_deserialize_value(s, (jl_value_t**)&m->generator); - if (m->generator) - jl_gc_wb(m, m->generator); - m->invokes = jl_deserialize_value(s, (jl_value_t**)&m->invokes); - jl_gc_wb(m, m->invokes); - m->recursion_relation = jl_deserialize_value(s, (jl_value_t**)&m->recursion_relation); - if (m->recursion_relation) - jl_gc_wb(m, m->recursion_relation); - JL_MUTEX_INIT(&m->writelock); - return (jl_value_t*)m; -} - -static jl_value_t *jl_deserialize_value_method_instance(jl_serializer_state *s, jl_value_t **loc) JL_GC_DISABLED -{ - jl_method_instance_t *mi = - (jl_method_instance_t*)jl_gc_alloc(s->ptls, sizeof(jl_method_instance_t), - jl_method_instance_type); - memset(mi, 0, sizeof(jl_method_instance_t)); - uintptr_t pos = backref_list.len; - arraylist_push(&backref_list, mi); - int internal = read_uint8(s->s); - if (internal == 1) { - mi->uninferred = jl_deserialize_value(s, &mi->uninferred); - jl_gc_wb(mi, mi->uninferred); - } - mi->specTypes = (jl_value_t*)jl_deserialize_value(s, (jl_value_t**)&mi->specTypes); - jl_gc_wb(mi, mi->specTypes); - mi->def.value = jl_deserialize_value(s, &mi->def.value); - jl_gc_wb(mi, mi->def.value); - - if (!internal) { - assert(loc != NULL && loc != HT_NOTFOUND); - arraylist_push(&flagref_list, loc); - arraylist_push(&flagref_list, (void*)pos); - return (jl_value_t*)mi; - } - - mi->sparam_vals = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&mi->sparam_vals); - jl_gc_wb(mi, mi->sparam_vals); - mi->backedges = (jl_array_t*)jl_deserialize_value(s, (jl_value_t**)&mi->backedges); - if (mi->backedges) - jl_gc_wb(mi, mi->backedges); - mi->callbacks = (jl_array_t*)jl_deserialize_value(s, (jl_value_t**)&mi->callbacks); - if (mi->callbacks) - jl_gc_wb(mi, mi->callbacks); - mi->cache = (jl_code_instance_t*)jl_deserialize_value(s, (jl_value_t**)&mi->cache); - if (mi->cache) - jl_gc_wb(mi, mi->cache); - return (jl_value_t*)mi; -} - -static jl_value_t *jl_deserialize_value_code_instance(jl_serializer_state *s, jl_value_t **loc) JL_GC_DISABLED -{ - jl_code_instance_t *codeinst = - (jl_code_instance_t*)jl_gc_alloc(s->ptls, sizeof(jl_code_instance_t), jl_code_instance_type); - memset(codeinst, 0, sizeof(jl_code_instance_t)); - arraylist_push(&backref_list, codeinst); - int flags = read_uint8(s->s); - int validate = (flags >> 0) & 3; - int constret = (flags >> 2) & 1; - codeinst->ipo_purity_bits = read_uint32(s->s); - jl_atomic_store_relaxed(&codeinst->purity_bits, read_uint32(s->s)); - codeinst->def = (jl_method_instance_t*)jl_deserialize_value(s, (jl_value_t**)&codeinst->def); - jl_gc_wb(codeinst, codeinst->def); - jl_value_t *inferred = jl_deserialize_value(s, NULL); - jl_atomic_store_release(&codeinst->inferred, inferred); - jl_gc_wb(codeinst, inferred); - codeinst->rettype_const = jl_deserialize_value(s, &codeinst->rettype_const); - if (codeinst->rettype_const) - jl_gc_wb(codeinst, codeinst->rettype_const); - codeinst->rettype = jl_deserialize_value(s, &codeinst->rettype); - jl_gc_wb(codeinst, codeinst->rettype); - codeinst->argescapes = jl_deserialize_value(s, &codeinst->argescapes); - jl_gc_wb(codeinst, codeinst->argescapes); - if (constret) - codeinst->invoke = jl_fptr_const_return; - if ((flags >> 3) & 1) - codeinst->precompile = 1; - codeinst->relocatability = read_uint8(s->s); - assert(codeinst->relocatability <= 1); - codeinst->next = (jl_code_instance_t*)jl_deserialize_value(s, (jl_value_t**)&codeinst->next); - jl_gc_wb(codeinst, codeinst->next); - if (validate) { - codeinst->min_world = jl_atomic_load_acquire(&jl_world_counter); - ptrhash_put(&new_code_instance_validate, codeinst, (void*)(~(uintptr_t)HT_NOTFOUND)); // "HT_FOUND" - } - return (jl_value_t*)codeinst; -} - -static jl_value_t *jl_deserialize_value_module(jl_serializer_state *s) JL_GC_DISABLED -{ - uintptr_t pos = backref_list.len; - arraylist_push(&backref_list, NULL); - jl_sym_t *mname = (jl_sym_t*)jl_deserialize_value(s, NULL); - int ref_only = read_uint8(s->s); - if (ref_only) { - jl_value_t *m_ref; - if (ref_only == 1) - m_ref = jl_get_global((jl_module_t*)jl_deserialize_value(s, NULL), mname); - else - m_ref = jl_array_ptr_ref(s->loaded_modules_array, read_int32(s->s)); - backref_list.items[pos] = m_ref; - return m_ref; - } - jl_module_t *m = jl_new_module(mname); - backref_list.items[pos] = m; - m->parent = (jl_module_t*)jl_deserialize_value(s, (jl_value_t**)&m->parent); - jl_gc_wb(m, m->parent); - - while (1) { - jl_sym_t *asname = (jl_sym_t*)jl_deserialize_value(s, NULL); - if (asname == NULL) - break; - jl_binding_t *b = jl_get_binding_wr(m, asname, 1); - b->name = (jl_sym_t*)jl_deserialize_value(s, (jl_value_t**)&b->name); - jl_value_t *bvalue = jl_deserialize_value(s, (jl_value_t**)&b->value); - *(jl_value_t**)&b->value = bvalue; - if (bvalue != NULL) jl_gc_wb(m, bvalue); - jl_value_t *bglobalref = jl_deserialize_value(s, (jl_value_t**)&b->globalref); - *(jl_value_t**)&b->globalref = bglobalref; - if (bglobalref != NULL) jl_gc_wb(m, bglobalref); - b->owner = (jl_module_t*)jl_deserialize_value(s, (jl_value_t**)&b->owner); - if (b->owner != NULL) jl_gc_wb(m, b->owner); - jl_value_t *bty = jl_deserialize_value(s, (jl_value_t**)&b->ty); - *(jl_value_t**)&b->ty = bty; - int8_t flags = read_int8(s->s); - b->deprecated = (flags>>3) & 1; - b->constp = (flags>>2) & 1; - b->exportp = (flags>>1) & 1; - b->imported = (flags) & 1; - } - size_t i = m->usings.len; - size_t ni = read_int32(s->s); - arraylist_grow(&m->usings, ni); - ni += i; - while (i < ni) { - m->usings.items[i] = jl_deserialize_value(s, (jl_value_t**)&m->usings.items[i]); - i++; - } - m->istopmod = read_uint8(s->s); - m->uuid.hi = read_uint64(s->s); - m->uuid.lo = read_uint64(s->s); - m->build_id = read_uint64(s->s); - m->counter = read_int32(s->s); - m->nospecialize = read_int32(s->s); - m->optlevel = read_int8(s->s); - m->compile = read_int8(s->s); - m->infer = read_int8(s->s); - m->max_methods = read_int8(s->s); - m->primary_world = jl_atomic_load_acquire(&jl_world_counter); - return (jl_value_t*)m; -} - -static jl_value_t *jl_deserialize_value_singleton(jl_serializer_state *s, jl_value_t **loc) JL_GC_DISABLED -{ - jl_value_t *v = (jl_value_t*)jl_gc_alloc(s->ptls, 0, NULL); - uintptr_t pos = backref_list.len; - arraylist_push(&backref_list, (void*)v); - // TODO: optimize the case where the value can easily be obtained - // from an external module (tag == 6) as dt->instance - assert(loc != HT_NOTFOUND); - // if loc == NULL, then the caller can't provide the address where the instance will be - // stored. this happens if a field might store a 0-size value, but the field itself is - // not 0 size, e.g. `::Union{Int,Nothing}` - if (loc != NULL) { - arraylist_push(&flagref_list, loc); - arraylist_push(&flagref_list, (void*)pos); - } - jl_datatype_t *dt = (jl_datatype_t*)jl_deserialize_value(s, (jl_value_t**)HT_NOTFOUND); // no loc, since if dt is replaced, then dt->instance would be also - jl_set_typeof(v, dt); - if (dt->instance == NULL) - return v; - return dt->instance; -} - -static void jl_deserialize_struct(jl_serializer_state *s, jl_value_t *v) JL_GC_DISABLED -{ - jl_datatype_t *dt = (jl_datatype_t*)jl_typeof(v); - char *data = (char*)jl_data_ptr(v); - size_t i, np = dt->layout->npointers; - char *start = data; - for (i = 0; i < np; i++) { - uint32_t ptr = jl_ptr_offset(dt, i); - jl_value_t **fld = &((jl_value_t**)data)[ptr]; - if ((char*)fld != start) - ios_readall(s->s, start, (const char*)fld - start); - *fld = jl_deserialize_value(s, fld); - //if (*fld)// a is new (gc is disabled) - // jl_gc_wb(a, *fld); - start = (char*)&fld[1]; - } - data += jl_datatype_size(dt); - if (data != start) - ios_readall(s->s, start, data - start); - if (dt == jl_typemap_entry_type) { - jl_typemap_entry_t *entry = (jl_typemap_entry_t*)v; - if (entry->max_world == ~(size_t)0) { - if (entry->min_world > 1) { - // update world validity to reflect current state of the counter - entry->min_world = jl_atomic_load_acquire(&jl_world_counter); - } - } - else { - // garbage entry - delete it :( - entry->min_world = 1; - entry->max_world = 0; - } - } else if (dt == jl_globalref_type) { - jl_globalref_t *r = (jl_globalref_t*)v; - jl_binding_t *b = jl_get_binding_if_bound(r->mod, r->name); - r->bnd_cache = b && b->value ? b : NULL; - } -} - -static jl_value_t *jl_deserialize_value_any(jl_serializer_state *s, uint8_t tag, jl_value_t **loc) JL_GC_DISABLED -{ - int32_t sz = (tag == TAG_SHORT_GENERAL ? read_uint8(s->s) : read_int32(s->s)); - jl_value_t *v = jl_gc_alloc(s->ptls, sz, NULL); - jl_set_typeof(v, (void*)(intptr_t)0x50); - uintptr_t pos = backref_list.len; - arraylist_push(&backref_list, v); - jl_datatype_t *dt = (jl_datatype_t*)jl_deserialize_value(s, &jl_astaggedvalue(v)->type); - assert(sz != 0 || loc); - if (dt == jl_typename_type) { - int internal = read_uint8(s->s); - jl_typename_t *tn; - if (internal) { - tn = (jl_typename_t*)jl_gc_alloc( - s->ptls, sizeof(jl_typename_t), jl_typename_type); - memset(tn, 0, sizeof(jl_typename_t)); - tn->cache = jl_emptysvec; // the cache is refilled later (tag 5) - tn->linearcache = jl_emptysvec; // the cache is refilled later (tag 5) - backref_list.items[pos] = tn; - } - jl_module_t *m = (jl_module_t*)jl_deserialize_value(s, NULL); - jl_sym_t *sym = (jl_sym_t*)jl_deserialize_value(s, NULL); - if (internal) { - tn->module = m; - tn->name = sym; - tn->names = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&tn->names); - jl_gc_wb(tn, tn->names); - tn->wrapper = jl_deserialize_value(s, &tn->wrapper); - jl_gc_wb(tn, tn->wrapper); - tn->Typeofwrapper = NULL; - tn->mt = (jl_methtable_t*)jl_deserialize_value(s, (jl_value_t**)&tn->mt); - jl_gc_wb(tn, tn->mt); - ios_read(s->s, (char*)&tn->hash, sizeof(tn->hash)); - int8_t flags = read_int8(s->s); - tn->_reserved = 0; - tn->abstract = flags & 1; - tn->mutabl = (flags>>1) & 1; - tn->mayinlinealloc = (flags>>2) & 1; - tn->max_methods = read_uint8(s->s); - if (tn->abstract) - tn->n_uninitialized = 0; - else - tn->n_uninitialized = read_uint16(s->s); - size_t nfields = read_int32(s->s); - if (nfields) { - tn->atomicfields = (uint32_t*)malloc(nfields); - ios_read(s->s, (char*)tn->atomicfields, nfields); - } - nfields = read_int32(s->s); - if (nfields) { - tn->constfields = (uint32_t*)malloc(nfields); - ios_read(s->s, (char*)tn->constfields, nfields); - } - } - else { - jl_datatype_t *dt = (jl_datatype_t*)jl_unwrap_unionall(jl_get_global(m, sym)); - assert(jl_is_datatype(dt)); - tn = dt->name; - backref_list.items[pos] = tn; - } - return (jl_value_t*)tn; - } - jl_set_typeof(v, dt); - if ((jl_value_t*)dt == jl_bigint_type) { - jl_value_t *sizefield = jl_deserialize_value(s, NULL); - int32_t sz = jl_unbox_int32(sizefield); - int32_t nw = (sz == 0 ? 1 : (sz < 0 ? -sz : sz)); - size_t nb = nw * gmp_limb_size; - void *buf = jl_gc_counted_malloc(nb); - if (buf == NULL) - jl_throw(jl_memory_exception); - ios_readall(s->s, (char*)buf, nb); - jl_set_nth_field(v, 0, jl_box_int32(nw)); - jl_set_nth_field(v, 1, sizefield); - jl_set_nth_field(v, 2, jl_box_voidpointer(buf)); - } - else { - jl_deserialize_struct(s, v); - } - return v; -} - -static jl_value_t *jl_deserialize_value(jl_serializer_state *s, jl_value_t **loc) JL_GC_DISABLED -{ - assert(!ios_eof(s->s)); - jl_value_t *v; - size_t n; - uintptr_t pos; - uint8_t tag = read_uint8(s->s); - if (tag > LAST_TAG) - return deser_tag[tag]; - switch (tag) { - case TAG_NULL: return NULL; - case 0: - tag = read_uint8(s->s); - return deser_tag[tag]; - case TAG_BACKREF: JL_FALLTHROUGH; case TAG_SHORT_BACKREF: ; - uintptr_t offs = (tag == TAG_BACKREF) ? read_int32(s->s) : read_uint16(s->s); - int isflagref = 0; - isflagref = !!(offs & 1); - offs >>= 1; - // assert(offs >= 0); // offs is unsigned so this is always true - assert(offs < backref_list.len); - jl_value_t *bp = (jl_value_t*)backref_list.items[offs]; - assert(bp); - if (isflagref && loc != HT_NOTFOUND) { - if (loc != NULL) { - // as in jl_deserialize_value_singleton, the caller won't have a place to - // store this reference given a field type like Union{Int,Nothing} - arraylist_push(&flagref_list, loc); - arraylist_push(&flagref_list, (void*)(uintptr_t)-1); - } - } - return (jl_value_t*)bp; - case TAG_SVEC: JL_FALLTHROUGH; case TAG_LONG_SVEC: - return jl_deserialize_value_svec(s, tag, loc); - case TAG_COMMONSYM: - return deser_symbols[read_uint8(s->s)]; - case TAG_SYMBOL: JL_FALLTHROUGH; case TAG_LONG_SYMBOL: - return jl_deserialize_value_symbol(s, tag); - case TAG_ARRAY: JL_FALLTHROUGH; case TAG_ARRAY1D: - return jl_deserialize_value_array(s, tag); - case TAG_UNIONALL: - pos = backref_list.len; - arraylist_push(&backref_list, NULL); - if (read_uint8(s->s)) { - jl_module_t *m = (jl_module_t*)jl_deserialize_value(s, NULL); - jl_sym_t *sym = (jl_sym_t*)jl_deserialize_value(s, NULL); - jl_value_t *v = jl_get_global(m, sym); - assert(jl_is_unionall(v)); - backref_list.items[pos] = v; - return v; - } - v = jl_gc_alloc(s->ptls, sizeof(jl_unionall_t), jl_unionall_type); - backref_list.items[pos] = v; - ((jl_unionall_t*)v)->var = (jl_tvar_t*)jl_deserialize_value(s, (jl_value_t**)&((jl_unionall_t*)v)->var); - jl_gc_wb(v, ((jl_unionall_t*)v)->var); - ((jl_unionall_t*)v)->body = jl_deserialize_value(s, &((jl_unionall_t*)v)->body); - jl_gc_wb(v, ((jl_unionall_t*)v)->body); - return v; - case TAG_TVAR: - v = jl_gc_alloc(s->ptls, sizeof(jl_tvar_t), jl_tvar_type); - jl_tvar_t *tv = (jl_tvar_t*)v; - arraylist_push(&backref_list, tv); - tv->name = (jl_sym_t*)jl_deserialize_value(s, NULL); - jl_gc_wb(tv, tv->name); - tv->lb = jl_deserialize_value(s, &tv->lb); - jl_gc_wb(tv, tv->lb); - tv->ub = jl_deserialize_value(s, &tv->ub); - jl_gc_wb(tv, tv->ub); - return (jl_value_t*)tv; - case TAG_METHOD: - return jl_deserialize_value_method(s, loc); - case TAG_METHOD_INSTANCE: - return jl_deserialize_value_method_instance(s, loc); - case TAG_CODE_INSTANCE: - return jl_deserialize_value_code_instance(s, loc); - case TAG_MODULE: - return jl_deserialize_value_module(s); - case TAG_SHORTER_INT64: - v = jl_box_int64((int16_t)read_uint16(s->s)); - arraylist_push(&backref_list, v); - return v; - case TAG_SHORT_INT64: - v = jl_box_int64(read_int32(s->s)); - arraylist_push(&backref_list, v); - return v; - case TAG_INT64: - v = jl_box_int64((int64_t)read_uint64(s->s)); - arraylist_push(&backref_list, v); - return v; - case TAG_SHORT_INT32: - v = jl_box_int32((int16_t)read_uint16(s->s)); - arraylist_push(&backref_list, v); - return v; - case TAG_INT32: - v = jl_box_int32(read_int32(s->s)); - arraylist_push(&backref_list, v); - return v; - case TAG_UINT8: - return jl_box_uint8(read_uint8(s->s)); - case TAG_SINGLETON: - return jl_deserialize_value_singleton(s, loc); - case TAG_CORE: - return (jl_value_t*)jl_core_module; - case TAG_BASE: - return (jl_value_t*)jl_base_module; - case TAG_CNULL: - v = jl_gc_alloc(s->ptls, sizeof(void*), NULL); - jl_set_typeof(v, (void*)(intptr_t)0x50); - *(void**)v = NULL; - uintptr_t pos = backref_list.len; - arraylist_push(&backref_list, v); - jl_set_typeof(v, jl_deserialize_value(s, &jl_astaggedvalue(v)->type)); - return v; - case TAG_BITYPENAME: - v = deser_tag[read_uint8(s->s)]; - return (jl_value_t*)((jl_datatype_t*)jl_unwrap_unionall(v))->name; - case TAG_STRING: - n = read_int32(s->s); - v = jl_alloc_string(n); - arraylist_push(&backref_list, v); - ios_readall(s->s, jl_string_data(v), n); - return v; - case TAG_DATATYPE: - pos = backref_list.len; - arraylist_push(&backref_list, NULL); - return jl_deserialize_datatype(s, pos, loc); - default: - assert(tag == TAG_GENERAL || tag == TAG_SHORT_GENERAL); - return jl_deserialize_value_any(s, tag, loc); - } -} - -// Add methods to external (non-worklist-owned) functions -static void jl_insert_methods(jl_array_t *list) -{ - size_t i, l = jl_array_len(list); - for (i = 0; i < l; i += 2) { - jl_method_t *meth = (jl_method_t*)jl_array_ptr_ref(list, i); - assert(jl_is_method(meth)); - assert(!meth->is_for_opaque_closure); - jl_tupletype_t *simpletype = (jl_tupletype_t*)jl_array_ptr_ref(list, i + 1); - jl_methtable_t *mt = jl_method_get_table(meth); - assert((jl_value_t*)mt != jl_nothing); - jl_method_table_insert(mt, meth, simpletype); - } -} - -int remove_code_instance_from_validation(jl_code_instance_t *codeinst) -{ - return ptrhash_remove(&new_code_instance_validate, codeinst); -} - -// verify that these edges intersect with the same methods as before -static jl_array_t *jl_verify_edges(jl_array_t *targets) -{ - size_t world = jl_atomic_load_acquire(&jl_world_counter); - size_t i, l = jl_array_len(targets) / 3; - jl_array_t *valids = jl_alloc_array_1d(jl_array_uint8_type, l); - memset(jl_array_data(valids), 1, l); - jl_value_t *loctag = NULL; - jl_value_t *matches = NULL; - JL_GC_PUSH3(&valids, &matches, &loctag); - for (i = 0; i < l; i++) { - jl_value_t *invokesig = jl_array_ptr_ref(targets, i * 3); - jl_value_t *callee = jl_array_ptr_ref(targets, i * 3 + 1); - jl_value_t *expected = jl_array_ptr_ref(targets, i * 3 + 2); - int valid = 1; - size_t min_valid = 0; - size_t max_valid = ~(size_t)0; - if (invokesig) { - assert(callee && "unsupported edge"); - jl_methtable_t *mt = jl_method_get_table(((jl_method_instance_t*)callee)->def.method); - if ((jl_value_t*)mt == jl_nothing) { - valid = 0; - } - else { - matches = jl_gf_invoke_lookup_worlds(invokesig, (jl_value_t*)mt, world, &min_valid, &max_valid); - if (matches == jl_nothing) { - valid = 0; - } - else { - matches = (jl_value_t*)((jl_method_match_t*)matches)->method; - if (matches != expected) { - valid = 0; - } - } - } - } - else { - jl_value_t *sig; - if (jl_is_method_instance(callee)) - sig = ((jl_method_instance_t*)callee)->specTypes; - else - sig = callee; - assert(jl_is_array(expected)); - int ambig = 0; - // TODO: possibly need to included ambiguities too (for the optimizer correctness)? - matches = jl_matching_methods((jl_tupletype_t*)sig, jl_nothing, - -1, 0, world, &min_valid, &max_valid, &ambig); - if (matches == jl_nothing) { - valid = 0; - } - else { - // setdiff!(matches, expected) - size_t j, k, ins = 0; - if (jl_array_len(matches) != jl_array_len(expected)) { - valid = 0; - } - for (k = 0; k < jl_array_len(matches); k++) { - jl_method_t *match = ((jl_method_match_t*)jl_array_ptr_ref(matches, k))->method; - size_t l = jl_array_len(expected); - for (j = 0; j < l; j++) - if (match == (jl_method_t*)jl_array_ptr_ref(expected, j)) - break; - if (j == l) { - // intersection has a new method or a method was - // deleted--this is now probably no good, just invalidate - // everything about it now - valid = 0; - if (!_jl_debug_method_invalidation) - break; - jl_array_ptr_set(matches, ins++, match); - } - } - if (!valid && _jl_debug_method_invalidation) - jl_array_del_end((jl_array_t*)matches, jl_array_len(matches) - ins); - } - } - jl_array_uint8_set(valids, i, valid); - if (!valid && _jl_debug_method_invalidation) { - jl_array_ptr_1d_push(_jl_debug_method_invalidation, invokesig ? (jl_value_t*)invokesig : callee); - loctag = jl_cstr_to_string("insert_backedges_callee"); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); - loctag = jl_box_int32((int32_t)i); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, matches); - } - //jl_static_show((JL_STREAM*)ios_stderr, (jl_value_t*)invokesig); - //jl_static_show((JL_STREAM*)ios_stderr, (jl_value_t*)callee); - //ios_puts(valid ? "valid\n" : "INVALID\n", ios_stderr); - } - JL_GC_POP(); - return valids; -} - -// Combine all edges relevant to a method into the visited table -void jl_verify_methods(jl_array_t *edges, jl_array_t *valids, htable_t *visited) -{ - jl_value_t *loctag = NULL; - JL_GC_PUSH1(&loctag); - size_t i, l = jl_array_len(edges) / 2; - htable_new(visited, l); - for (i = 0; i < l; i++) { - jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, 2 * i); - assert(jl_is_method_instance(caller) && jl_is_method(caller->def.method)); - jl_array_t *callee_ids = (jl_array_t*)jl_array_ptr_ref(edges, 2 * i + 1); - assert(jl_typeis((jl_value_t*)callee_ids, jl_array_int32_type)); - int valid = 1; - if (callee_ids == NULL) { - // serializing the edges had failed - valid = 0; - } - else { - int32_t *idxs = (int32_t*)jl_array_data(callee_ids); - size_t j; - for (j = 0; valid && j < idxs[0]; j++) { - int32_t idx = idxs[j + 1]; - valid = jl_array_uint8_ref(valids, idx); - if (!valid && _jl_debug_method_invalidation) { - jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)caller); - loctag = jl_cstr_to_string("verify_methods"); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); - loctag = jl_box_int32((int32_t)idx); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); - } - } - } - ptrhash_put(visited, caller, (void*)(((char*)HT_NOTFOUND) + valid + 1)); - //jl_static_show((JL_STREAM*)ios_stderr, (jl_value_t*)caller); - //ios_puts(valid ? "valid\n" : "INVALID\n", ios_stderr); - // HT_NOTFOUND: valid (no invalid edges) - // HT_NOTFOUND + 1: invalid - // HT_NOTFOUND + 2: need to scan - // HT_NOTFOUND + 3 + depth: in-progress - } - JL_GC_POP(); -} - - -// Propagate the result of cycle-resolution to all edges (recursively) -static int mark_edges_in_worklist(jl_array_t *edges, int idx, jl_method_instance_t *cycle, htable_t *visited, int found) -{ - jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, idx * 2); - int oldfound = (char*)ptrhash_get(visited, caller) - (char*)HT_NOTFOUND; - if (oldfound < 3) - return 0; // not in-progress - if (!found) { - ptrhash_remove(visited, (void*)caller); - } - else { - ptrhash_put(visited, (void*)caller, (void*)((char*)HT_NOTFOUND + 1 + found)); - } - jl_array_t *callee_ids = (jl_array_t*)jl_array_ptr_ref(edges, idx * 2 + 1); - assert(jl_typeis((jl_value_t*)callee_ids, jl_array_int32_type)); - int32_t *idxs = (int32_t*)jl_array_data(callee_ids); - size_t i, badidx = 0, n = jl_array_len(callee_ids); - for (i = idxs[0] + 1; i < n; i++) { - if (mark_edges_in_worklist(edges, idxs[i], cycle, visited, found) && badidx == 0) - badidx = i - idxs[0]; - } - if (_jl_debug_method_invalidation) { - jl_value_t *loctag = NULL; - JL_GC_PUSH1(&loctag); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)caller); - loctag = jl_cstr_to_string("verify_methods"); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); - jl_method_instance_t *callee = cycle; - if (badidx--) - callee = (jl_method_instance_t*)jl_array_ptr_ref(edges, 2 * badidx); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)callee); - JL_GC_POP(); - } - return 1; -} - - -// Visit the entire call graph, starting from edges[idx] to determine if that method is valid -static int jl_verify_graph_edge(jl_array_t *edges, int idx, htable_t *visited, int depth) -{ - jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, idx * 2); - assert(jl_is_method_instance(caller) && jl_is_method(caller->def.method)); - int found = (char*)ptrhash_get(visited, (void*)caller) - (char*)HT_NOTFOUND; - if (found == 0) - return 1; // valid - if (found == 1) - return 0; // invalid - if (found != 2) - return found - 1; // depth - found = 0; - ptrhash_put(visited, (void*)caller, (void*)((char*)HT_NOTFOUND + 3 + depth)); // change 2 to in-progress at depth - jl_array_t *callee_ids = (jl_array_t*)jl_array_ptr_ref(edges, idx * 2 + 1); - assert(jl_typeis((jl_value_t*)callee_ids, jl_array_int32_type)); - int32_t *idxs = (int32_t*)jl_array_data(callee_ids); - int cycle = 0; - size_t i, n = jl_array_len(callee_ids); - for (i = idxs[0] + 1; i < n; i++) { - int32_t idx = idxs[i]; - int child_found = jl_verify_graph_edge(edges, idx, visited, depth + 1); - if (child_found == 0) { - found = 1; - if (_jl_debug_method_invalidation) { - jl_value_t *loctag = NULL; - JL_GC_PUSH1(&loctag); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)caller); - loctag = jl_cstr_to_string("verify_methods"); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, jl_array_ptr_ref(edges, idx * 2)); - JL_GC_POP(); - } - break; - } - else if (child_found >= 2 && child_found - 2 < cycle) { - // record the cycle will resolve at depth "cycle" - cycle = child_found - 2; - assert(cycle); - } - } - if (!found) { - if (cycle && cycle != depth) - return cycle + 2; - ptrhash_remove(visited, (void*)caller); - } - else { // found invalid - ptrhash_put(visited, (void*)caller, (void*)((char*)HT_NOTFOUND + 1 + found)); - } - if (cycle) { - // If we are the top of the current cycle, now mark all other parts of - // our cycle by re-walking the backedges graph and marking all WIP - // items as found. - // Be careful to only re-walk as far as we had originally scanned above. - // Or if we found a backedge, also mark all of the other parts of the - // cycle as also having an backedge. - n = i; - for (i = idxs[0] + 1; i < n; i++) { - mark_edges_in_worklist(edges, idxs[i], caller, visited, found); - } - } - return found ? 0 : 1; -} - -// Visit all entries in edges, verify if they are valid -static jl_array_t *jl_verify_graph(jl_array_t *edges, htable_t *visited) -{ - size_t i, n = jl_array_len(edges) / 2; - jl_array_t *valids = jl_alloc_array_1d(jl_array_uint8_type, n); - JL_GC_PUSH1(&valids); - int8_t *valids_data = (int8_t*)jl_array_data(valids); - for (i = 0; i < n; i++) { - valids_data[i] = jl_verify_graph_edge(edges, i, visited, 1); - } - JL_GC_POP(); - return valids; -} - -// Restore backedges to external targets -// `edges` = [caller1, targets_indexes1, ...], the list of worklist-owned methods calling external methods. -// `ext_targets` is [invokesig1, callee1, matches1, ...], the global set of non-worklist callees of worklist-owned methods. -static void jl_insert_backedges(jl_array_t *edges, jl_array_t *ext_targets, jl_array_t *mi_list) -{ - // determine which CodeInstance objects are still valid in our image - size_t world = jl_atomic_load_acquire(&jl_world_counter); - jl_array_t *valids = jl_verify_edges(ext_targets); - JL_GC_PUSH1(&valids); - htable_t visited; - htable_new(&visited, 0); - jl_verify_methods(edges, valids, &visited); - valids = jl_verify_graph(edges, &visited); - size_t i, l = jl_array_len(edges) / 2; - - // next build a map from external_mis to their CodeInstance for insertion - if (mi_list == NULL) { - htable_reset(&visited, 0); - } - else { - size_t i, l = jl_array_len(mi_list); - htable_reset(&visited, l); - for (i = 0; i < l; i++) { - jl_code_instance_t *ci = (jl_code_instance_t*)jl_array_ptr_ref(mi_list, i); - ptrhash_put(&visited, (void*)ci->def, (void*)ci); - } - } - - // next disable any invalid codes, so we do not try to enable them - for (i = 0; i < l; i++) { - jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, 2 * i); - assert(jl_is_method_instance(caller) && jl_is_method(caller->def.method)); - int valid = jl_array_uint8_ref(valids, i); - if (valid) - continue; - void *ci = ptrhash_get(&visited, (void*)caller); - if (ci != HT_NOTFOUND) { - assert(jl_is_code_instance(ci)); - remove_code_instance_from_validation((jl_code_instance_t*)ci); // mark it as handled - } - else { - jl_code_instance_t *codeinst = caller->cache; - while (codeinst) { - remove_code_instance_from_validation(codeinst); // should be left invalid - codeinst = jl_atomic_load_relaxed(&codeinst->next); - } - } - } - - // finally enable any applicable new codes - for (i = 0; i < l; i++) { - jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, 2 * i); - int valid = jl_array_uint8_ref(valids, i); - if (!valid) - continue; - // if this callee is still valid, add all the backedges - jl_array_t *callee_ids = (jl_array_t*)jl_array_ptr_ref(edges, 2 * i + 1); - int32_t *idxs = (int32_t*)jl_array_data(callee_ids); - for (size_t j = 0; j < idxs[0]; j++) { - int32_t idx = idxs[j + 1]; - jl_value_t *invokesig = jl_array_ptr_ref(ext_targets, idx * 3); - jl_value_t *callee = jl_array_ptr_ref(ext_targets, idx * 3 + 1); - if (callee && jl_is_method_instance(callee)) { - jl_method_instance_add_backedge((jl_method_instance_t*)callee, invokesig, caller); - } - else { - jl_value_t *sig = callee == NULL ? invokesig : callee; - jl_methtable_t *mt = jl_method_table_for(sig); - // FIXME: rarely, `callee` has an unexpected `Union` signature, - // see https://github.com/JuliaLang/julia/pull/43990#issuecomment-1030329344 - // Fix the issue and turn this back into an `assert((jl_value_t*)mt != jl_nothing)` - // This workaround exposes us to (rare) 265-violations. - if ((jl_value_t*)mt != jl_nothing) - jl_method_table_add_backedge(mt, sig, (jl_value_t*)caller); - } - } - // then enable it - void *ci = ptrhash_get(&visited, (void*)caller); - if (ci != HT_NOTFOUND) { - // have some new external code to use - assert(jl_is_code_instance(ci)); - jl_code_instance_t *codeinst = (jl_code_instance_t*)ci; - remove_code_instance_from_validation(codeinst); // mark it as handled - assert(codeinst->min_world >= world && codeinst->inferred); - codeinst->max_world = ~(size_t)0; - if (jl_rettype_inferred(caller, world, ~(size_t)0) == jl_nothing) { - jl_mi_cache_insert(caller, codeinst); - } - } - else { - jl_code_instance_t *codeinst = caller->cache; - while (codeinst) { - if (remove_code_instance_from_validation(codeinst)) { // mark it as handled - assert(codeinst->min_world >= world && codeinst->inferred); - codeinst->max_world = ~(size_t)0; - } - codeinst = jl_atomic_load_relaxed(&codeinst->next); - } - } - } - - htable_free(&visited); - JL_GC_POP(); -} - -static void validate_new_code_instances(void) -{ - size_t world = jl_atomic_load_acquire(&jl_world_counter); - size_t i; - for (i = 0; i < new_code_instance_validate.size; i += 2) { - if (new_code_instance_validate.table[i+1] != HT_NOTFOUND) { - jl_code_instance_t *ci = (jl_code_instance_t*)new_code_instance_validate.table[i]; - JL_GC_PROMISE_ROOTED(ci); // TODO: this needs a root (or restructuring to avoid it) - assert(ci->min_world >= world && ci->inferred); - ci->max_world = ~(size_t)0; - jl_method_instance_t *caller = ci->def; - if (jl_rettype_inferred(caller, world, ~(size_t)0) == jl_nothing) { - jl_mi_cache_insert(caller, ci); - } - //jl_static_show((JL_STREAM*)ios_stderr, (jl_value_t*)caller); - //ios_puts("FREE\n", ios_stderr); - } - } -} - -static jl_value_t *read_verify_mod_list(ios_t *s, jl_array_t *mod_list) -{ - if (!jl_main_module->build_id) { - return jl_get_exceptionf(jl_errorexception_type, - "Main module uuid state is invalid for module deserialization."); - } - size_t i, l = jl_array_len(mod_list); - for (i = 0; ; i++) { - size_t len = read_int32(s); - if (len == 0 && i == l) - return NULL; // success - if (len == 0 || i == l) - return jl_get_exceptionf(jl_errorexception_type, "Wrong number of entries in module list."); - char *name = (char*)alloca(len + 1); - ios_readall(s, name, len); - name[len] = '\0'; - jl_uuid_t uuid; - uuid.hi = read_uint64(s); - uuid.lo = read_uint64(s); - uint64_t build_id = read_uint64(s); - jl_sym_t *sym = _jl_symbol(name, len); - jl_module_t *m = (jl_module_t*)jl_array_ptr_ref(mod_list, i); - if (!m || !jl_is_module(m) || m->uuid.hi != uuid.hi || m->uuid.lo != uuid.lo || m->name != sym || m->build_id != build_id) { - return jl_get_exceptionf(jl_errorexception_type, - "Invalid input in module list: expected %s.", name); - } - } -} - -static int readstr_verify(ios_t *s, const char *str) -{ - size_t i, len = strlen(str); - for (i = 0; i < len; ++i) - if ((char)read_uint8(s) != str[i]) - return 0; - return 1; -} - -JL_DLLEXPORT int jl_read_verify_header(ios_t *s) -{ - uint16_t bom; - return (readstr_verify(s, JI_MAGIC) && - read_uint16(s) == JI_FORMAT_VERSION && - ios_read(s, (char *) &bom, 2) == 2 && bom == BOM && - read_uint8(s) == sizeof(void*) && - readstr_verify(s, JL_BUILD_UNAME) && !read_uint8(s) && - readstr_verify(s, JL_BUILD_ARCH) && !read_uint8(s) && - readstr_verify(s, JULIA_VERSION_STRING) && !read_uint8(s) && - readstr_verify(s, jl_git_branch()) && !read_uint8(s) && - readstr_verify(s, jl_git_commit()) && !read_uint8(s)); -} - -static void jl_finalize_serializer(jl_serializer_state *s) -{ - size_t i, l; - // save module initialization order - if (jl_module_init_order != NULL) { - l = jl_array_len(jl_module_init_order); - for (i = 0; i < l; i++) { - // verify that all these modules were saved - assert(ptrhash_get(&backref_table, jl_array_ptr_ref(jl_module_init_order, i)) != HT_NOTFOUND); - } - } - jl_serialize_value(s, jl_module_init_order); - - // record list of reinitialization functions - l = reinit_list.len; - for (i = 0; i < l; i += 2) { - write_int32(s->s, (int)((uintptr_t) reinit_list.items[i])); - write_int32(s->s, (int)((uintptr_t) reinit_list.items[i+1])); - } - write_int32(s->s, -1); -} - -static void jl_reinit_item(jl_value_t *v, int how, arraylist_t *tracee_list) -{ - JL_TRY { - switch (how) { - case 1: { // rehash IdDict - jl_array_t **a = (jl_array_t**)v; - // Assume *a don't need a write barrier - *a = jl_idtable_rehash(*a, jl_array_len(*a)); - jl_gc_wb(v, *a); - break; - } - case 2: { // reinsert module v into parent (const) - jl_module_t *mod = (jl_module_t*)v; - if (mod->parent == mod) // top level modules handled by loader - break; - jl_binding_t *b = jl_get_binding_wr(mod->parent, mod->name, 1); // this can throw - jl_declare_constant(b); // this can also throw - if (b->value != NULL) { - if (!jl_is_module(b->value)) { - jl_errorf("Invalid redefinition of constant %s.", - jl_symbol_name(mod->name)); // this also throws - } - if (jl_generating_output() && jl_options.incremental) { - jl_errorf("Cannot replace module %s during incremental precompile.", jl_symbol_name(mod->name)); - } - jl_printf(JL_STDERR, "WARNING: replacing module %s.\n", jl_symbol_name(mod->name)); - } - b->value = v; - jl_gc_wb_binding(b, v); - break; - } - case 3: { // rehash MethodTable - jl_methtable_t *mt = (jl_methtable_t*)v; - if (tracee_list) - arraylist_push(tracee_list, mt); - break; - } - default: - assert(0 && "corrupt deserialization state"); - abort(); - } - } - JL_CATCH { - jl_printf((JL_STREAM*)STDERR_FILENO, "WARNING: error while reinitializing value "); - jl_static_show((JL_STREAM*)STDERR_FILENO, v); - jl_printf((JL_STREAM*)STDERR_FILENO, ":\n"); - jl_static_show((JL_STREAM*)STDERR_FILENO, jl_current_exception()); - jl_printf((JL_STREAM*)STDERR_FILENO, "\n"); - jlbacktrace(); // written to STDERR_FILENO - } -} - -static jl_array_t *jl_finalize_deserializer(jl_serializer_state *s, arraylist_t *tracee_list) -{ - jl_array_t *init_order = (jl_array_t*)jl_deserialize_value(s, NULL); - - // run reinitialization functions - int pos = read_int32(s->s); - while (pos != -1) { - jl_reinit_item((jl_value_t*)backref_list.items[pos], read_int32(s->s), tracee_list); - pos = read_int32(s->s); - } - return init_order; -} - -JL_DLLEXPORT void jl_init_restored_modules(jl_array_t *init_order) -{ - int i, l = jl_array_len(init_order); - for (i = 0; i < l; i++) { - jl_value_t *mod = jl_array_ptr_ref(init_order, i); - if (!jl_generating_output() || jl_options.incremental) { - jl_module_run_initializer((jl_module_t*)mod); - } - else { - if (jl_module_init_order == NULL) - jl_module_init_order = jl_alloc_vec_any(0); - jl_array_ptr_1d_push(jl_module_init_order, mod); - } - } -} - - -// --- entry points --- - -// Register array of newly-inferred MethodInstances -// This gets called as the first step of Base.include_package_for_output -JL_DLLEXPORT void jl_set_newly_inferred(jl_value_t* _newly_inferred) -{ - assert(_newly_inferred == NULL || jl_is_array(_newly_inferred)); - newly_inferred = (jl_array_t*) _newly_inferred; -} - -JL_DLLEXPORT void jl_push_newly_inferred(jl_value_t* linfo) -{ - JL_LOCK(&newly_inferred_mutex); - size_t end = jl_array_len(newly_inferred); - jl_array_grow_end(newly_inferred, 1); - jl_arrayset(newly_inferred, linfo, end); - JL_UNLOCK(&newly_inferred_mutex); -} - -// Serialize the modules in `worklist` to file `fname` -JL_DLLEXPORT int jl_save_incremental(const char *fname, jl_array_t *worklist) -{ - JL_TIMING(SAVE_MODULE); - jl_task_t *ct = jl_current_task; - ios_t f; - if (ios_file(&f, fname, 1, 1, 1, 1) == NULL) { - jl_printf(JL_STDERR, "Cannot open cache file \"%s\" for writing.\n", fname); - return 1; - } - - jl_array_t *mod_array = NULL, *udeps = NULL; - jl_array_t *extext_methods = NULL, *mi_list = NULL; - jl_array_t *ext_targets = NULL, *edges = NULL; - JL_GC_PUSH7(&mod_array, &udeps, &extext_methods, &mi_list, &ext_targets, &edges, &edges_map); - - mod_array = jl_get_loaded_modules(); // __toplevel__ modules loaded in this session (from Base.loaded_modules_array) - assert(jl_precompile_toplevel_module == NULL); - jl_precompile_toplevel_module = (jl_module_t*)jl_array_ptr_ref(worklist, jl_array_len(worklist)-1); - - serializer_worklist = worklist; - write_header(&f); - // write description of contents (name, uuid, buildid) - write_work_list(&f); - // Determine unique (module, abspath, mtime) dependencies for the files defining modules in the worklist - // (see Base._require_dependencies). These get stored in `udeps` and written to the ji-file header. - // Also write Preferences. - int64_t srctextpos = write_dependency_list(&f, &udeps); // srctextpos: position of srctext entry in header index (update later) - // write description of requirements for loading (modules that must be pre-loaded if initialization is to succeed) - // this can return errors during deserialize, - // best to keep it early (before any actual initialization) - write_mod_list(&f, mod_array); - - arraylist_new(&reinit_list, 0); - htable_new(&backref_table, 5000); - htable_new(&external_mis, newly_inferred ? jl_array_len(newly_inferred) : 0); - ptrhash_put(&backref_table, jl_main_module, (char*)HT_NOTFOUND + 1); - backref_table_numel = 1; - jl_idtable_type = jl_base_module ? jl_get_global(jl_base_module, jl_symbol("IdDict")) : NULL; - jl_idtable_typename = jl_base_module ? ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_idtable_type))->name : NULL; - jl_bigint_type = jl_base_module ? jl_get_global(jl_base_module, jl_symbol("BigInt")) : NULL; - if (jl_bigint_type) { - gmp_limb_size = jl_unbox_long(jl_get_global((jl_module_t*)jl_get_global(jl_base_module, jl_symbol("GMP")), - jl_symbol("BITS_PER_LIMB"))) / 8; - } - - jl_gc_enable_finalizers(ct, 0); // make sure we don't run any Julia code concurrently after this point - - // Save the inferred code from newly inferred, external methods - mi_list = queue_external_mis(newly_inferred); - - edges_map = jl_alloc_vec_any(0); - extext_methods = jl_alloc_vec_any(0); // [method1, simplesig1, ...], worklist-owned "extending external" methods added to functions owned by modules outside the worklist - size_t i, len = jl_array_len(mod_array); - for (i = 0; i < len; i++) { - jl_module_t *m = (jl_module_t*)jl_array_ptr_ref(mod_array, i); - assert(jl_is_module(m)); - if (m->parent == m) // some toplevel modules (really just Base) aren't actually - jl_collect_extext_methods_from_mod(extext_methods, m); - } - jl_collect_methtable_from_mod(extext_methods, jl_type_type_mt); - jl_collect_missing_backedges(jl_type_type_mt); - jl_collect_methtable_from_mod(extext_methods, jl_nonfunction_mt); - jl_collect_missing_backedges(jl_nonfunction_mt); - // jl_collect_extext_methods_from_mod and jl_collect_missing_backedges also accumulate data in edges_map. - // Process this to extract `edges` and `ext_targets`. - ext_targets = jl_alloc_vec_any(0); // [invokesig1, callee1, matches1, ...] non-worklist callees of worklist-owned methods - // ordinary dispatch: invokesig=NULL, callee is MethodInstance - // `invoke` dispatch: invokesig is signature, callee is MethodInstance - // abstract call: callee is signature - edges = jl_alloc_vec_any(0); // [caller1, ext_targets_indexes1, ...] for worklist-owned methods calling external methods - jl_collect_edges(edges, ext_targets); - - jl_serializer_state s = { - &f, - jl_current_task->ptls, - mod_array - }; - jl_serialize_value(&s, worklist); // serialize module-owned items (those accessible from the bindings table) - jl_serialize_value(&s, extext_methods); // serialize new worklist-owned methods for external functions - - // The next three allow us to restore code instances, if still valid - jl_serialize_value(&s, mi_list); - jl_serialize_value(&s, edges); - jl_serialize_value(&s, ext_targets); - jl_finalize_serializer(&s); - serializer_worklist = NULL; - - htable_free(&backref_table); - htable_free(&external_mis); - arraylist_free(&reinit_list); - - jl_gc_enable_finalizers(ct, 1); // make sure we don't run any Julia code concurrently before this point - - // Write the source-text for the dependent files - if (udeps) { - // Go back and update the source-text position to point to the current position - int64_t posfile = ios_pos(&f); - ios_seek(&f, srctextpos); - write_uint64(&f, posfile); - ios_seek_end(&f); - // Each source-text file is written as - // int32: length of abspath - // char*: abspath - // uint64: length of src text - // char*: src text - // At the end we write int32(0) as a terminal sentinel. - len = jl_array_len(udeps); - ios_t srctext; - for (i = 0; i < len; i++) { - jl_value_t *deptuple = jl_array_ptr_ref(udeps, i); - jl_value_t *depmod = jl_fieldref(deptuple, 0); // module - // Dependencies declared with `include_dependency` are excluded - // because these may not be Julia code (and could be huge) - if (depmod != (jl_value_t*)jl_main_module) { - jl_value_t *dep = jl_fieldref(deptuple, 1); // file abspath - const char *depstr = jl_string_data(dep); - if (!depstr[0]) - continue; - ios_t *srctp = ios_file(&srctext, depstr, 1, 0, 0, 0); - if (!srctp) { - jl_printf(JL_STDERR, "WARNING: could not cache source text for \"%s\".\n", - jl_string_data(dep)); - continue; - } - size_t slen = jl_string_len(dep); - write_int32(&f, slen); - ios_write(&f, depstr, slen); - posfile = ios_pos(&f); - write_uint64(&f, 0); // placeholder for length of this file in bytes - uint64_t filelen = (uint64_t) ios_copyall(&f, &srctext); - ios_close(&srctext); - ios_seek(&f, posfile); - write_uint64(&f, filelen); - ios_seek_end(&f); - } - } - } - write_int32(&f, 0); // mark the end of the source text - ios_close(&f); - JL_GC_POP(); - jl_precompile_toplevel_module = NULL; - - return 0; -} - -#ifndef JL_NDEBUG -// skip the performance optimizations of jl_types_equal and just use subtyping directly -// one of these types is invalid - that's why we're doing the recache type operation -static int jl_invalid_types_equal(jl_datatype_t *a, jl_datatype_t *b) -{ - return jl_subtype((jl_value_t*)a, (jl_value_t*)b) && jl_subtype((jl_value_t*)b, (jl_value_t*)a); -} -STATIC_INLINE jl_value_t *verify_type(jl_value_t *v) JL_NOTSAFEPOINT -{ - assert(v && jl_typeof(v) && jl_typeof(jl_typeof(v)) == (jl_value_t*)jl_datatype_type); - return v; -} -#endif - - -static jl_datatype_t *recache_datatype(jl_datatype_t *dt) JL_GC_DISABLED; - -static jl_value_t *recache_type(jl_value_t *p) JL_GC_DISABLED -{ - if (jl_is_datatype(p)) { - jl_datatype_t *pdt = (jl_datatype_t*)p; - if (ptrhash_get(&uniquing_table, p) != HT_NOTFOUND) { - p = (jl_value_t*)recache_datatype(pdt); - } - else { - jl_svec_t *tt = pdt->parameters; - // ensure all type parameters are recached - size_t i, l = jl_svec_len(tt); - for (i = 0; i < l; i++) - jl_svecset(tt, i, recache_type(jl_svecref(tt, i))); - ptrhash_put(&uniquing_table, p, p); // ensures this algorithm isn't too exponential - } - } - else if (jl_is_typevar(p)) { - jl_tvar_t *ptv = (jl_tvar_t*)p; - ptv->lb = recache_type(ptv->lb); - ptv->ub = recache_type(ptv->ub); - } - else if (jl_is_uniontype(p)) { - jl_uniontype_t *pu = (jl_uniontype_t*)p; - pu->a = recache_type(pu->a); - pu->b = recache_type(pu->b); - } - else if (jl_is_unionall(p)) { - jl_unionall_t *pa = (jl_unionall_t*)p; - pa->var = (jl_tvar_t*)recache_type((jl_value_t*)pa->var); - pa->body = recache_type(pa->body); - } - else { - jl_datatype_t *pt = (jl_datatype_t*)jl_typeof(p); - jl_datatype_t *cachep = recache_datatype(pt); - if (cachep->instance) - p = cachep->instance; - else if (pt != cachep) - jl_set_typeof(p, cachep); - } - return p; -} - -// Extract pre-existing datatypes from cache, and insert new types into cache -// insertions also update uniquing_table -static jl_datatype_t *recache_datatype(jl_datatype_t *dt) JL_GC_DISABLED -{ - jl_datatype_t *t; // the type after unique'ing - assert(verify_type((jl_value_t*)dt)); - t = (jl_datatype_t*)ptrhash_get(&uniquing_table, dt); - if (t == HT_NOTFOUND) - return dt; - if (t != NULL) - return t; - - jl_svec_t *tt = dt->parameters; - // recache all type parameters - size_t i, l = jl_svec_len(tt); - for (i = 0; i < l; i++) - jl_svecset(tt, i, recache_type(jl_svecref(tt, i))); - - // then recache the type itself - if (jl_svec_len(tt) == 0) { // jl_cache_type doesn't work if length(parameters) == 0 - t = dt; - } - else { - t = jl_lookup_cache_type_(dt); - if (t == NULL) { - jl_cache_type_(dt); - t = dt; - } - assert(t->hash == dt->hash); - assert(jl_invalid_types_equal(t, dt)); - } - ptrhash_put(&uniquing_table, dt, t); - return t; -} - -// Recache everything from flagref_list except methods and method instances -// Cleans out any handled items so that anything left in flagref_list still needs future processing -static void jl_recache_types(void) JL_GC_DISABLED -{ - size_t i; - // first rewrite all the unique'd objects - for (i = 0; i < flagref_list.len; i += 2) { - jl_value_t **loc = (jl_value_t**)flagref_list.items[i + 0]; - int offs = (int)(intptr_t)flagref_list.items[i + 1]; - jl_value_t *o = loc ? *loc : (jl_value_t*)backref_list.items[offs]; - if (!jl_is_method(o) && !jl_is_method_instance(o)) { - jl_datatype_t *dt; - jl_value_t *v; - if (jl_is_datatype(o)) { - dt = (jl_datatype_t*)o; - v = dt->instance; - } - else { - dt = (jl_datatype_t*)jl_typeof(o); - v = o; - } - jl_datatype_t *t = recache_datatype(dt); // get or create cached type (also updates uniquing_table) - if ((jl_value_t*)dt == o && t != dt) { - assert(!type_in_worklist(dt)); - if (loc) - *loc = (jl_value_t*)t; - if (offs > 0) - backref_list.items[offs] = t; - } - if (v == o && t->instance != v) { - assert(t->instance); - assert(loc); - *loc = t->instance; - if (offs > 0) - backref_list.items[offs] = t->instance; - } - } - } - // invalidate the old datatypes to help catch errors - for (i = 0; i < uniquing_table.size; i += 2) { - jl_datatype_t *o = (jl_datatype_t*)uniquing_table.table[i]; // deserialized ref - jl_datatype_t *t = (jl_datatype_t*)uniquing_table.table[i + 1]; // the real type - if (o != t) { - assert(t != NULL && jl_is_datatype(o)); - if (t->instance != o->instance) - jl_set_typeof(o->instance, (void*)(intptr_t)0x20); - jl_set_typeof(o, (void*)(intptr_t)0x10); - } - } - // then do a cleanup pass to drop these from future iterations of flagref_list - i = 0; - while (i < flagref_list.len) { - jl_value_t **loc = (jl_value_t**)flagref_list.items[i + 0]; - int offs = (int)(intptr_t)flagref_list.items[i + 1]; - jl_value_t *o = loc ? *loc : (jl_value_t*)backref_list.items[offs]; - if (jl_is_method(o) || jl_is_method_instance(o)) { - i += 2; - } - else { - // delete this item from the flagref list, so it won't be re-encountered later - flagref_list.len -= 2; - if (i >= flagref_list.len) - break; - flagref_list.items[i + 0] = flagref_list.items[flagref_list.len + 0]; // move end-of-list here (executes a `reverse()`) - flagref_list.items[i + 1] = flagref_list.items[flagref_list.len + 1]; - } - } -} - -// look up a method from a previously deserialized dependent module -static jl_method_t *jl_lookup_method(jl_methtable_t *mt, jl_datatype_t *sig, size_t world) -{ - if (world < jl_main_module->primary_world) - world = jl_main_module->primary_world; - struct jl_typemap_assoc search = {(jl_value_t*)sig, world, NULL, 0, ~(size_t)0}; - jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(mt->defs, &search, /*offs*/0, /*subtype*/0); - return (jl_method_t*)entry->func.value; -} - -static jl_method_t *jl_recache_method(jl_method_t *m) -{ - assert(!m->is_for_opaque_closure); - assert(jl_is_method(m)); - jl_datatype_t *sig = (jl_datatype_t*)m->sig; - jl_methtable_t *mt = jl_method_get_table(m); - assert((jl_value_t*)mt != jl_nothing); - jl_set_typeof(m, (void*)(intptr_t)0x30); // invalidate the old value to help catch errors - return jl_lookup_method(mt, sig, m->module->primary_world); -} - -static jl_value_t *jl_recache_other_(jl_value_t *o); - -static jl_method_instance_t *jl_recache_method_instance(jl_method_instance_t *mi) -{ - jl_method_t *m = mi->def.method; - m = (jl_method_t*)jl_recache_other_((jl_value_t*)m); - assert(jl_is_method(m)); - jl_datatype_t *argtypes = (jl_datatype_t*)mi->specTypes; - jl_set_typeof(mi, (void*)(intptr_t)0x40); // invalidate the old value to help catch errors - jl_svec_t *env = jl_emptysvec; - jl_value_t *ti = jl_type_intersection_env((jl_value_t*)argtypes, (jl_value_t*)m->sig, &env); - //assert(ti != jl_bottom_type); (void)ti; - if (ti == jl_bottom_type) - env = jl_emptysvec; // the intersection may fail now if the type system had made an incorrect subtype env in the past - jl_method_instance_t *_new = jl_specializations_get_linfo(m, (jl_value_t*)argtypes, env); - return _new; -} - -static jl_value_t *jl_recache_other_(jl_value_t *o) -{ - jl_value_t *newo = (jl_value_t*)ptrhash_get(&uniquing_table, o); - if (newo != HT_NOTFOUND) - return newo; - if (jl_is_method(o)) { - // lookup the real Method based on the placeholder sig - newo = (jl_value_t*)jl_recache_method((jl_method_t*)o); - ptrhash_put(&uniquing_table, newo, newo); - } - else if (jl_is_method_instance(o)) { - // lookup the real MethodInstance based on the placeholder specTypes - newo = (jl_value_t*)jl_recache_method_instance((jl_method_instance_t*)o); - } - else { - abort(); - } - ptrhash_put(&uniquing_table, o, newo); - return newo; -} - -static void jl_recache_other(void) -{ - size_t i = 0; - while (i < flagref_list.len) { - jl_value_t **loc = (jl_value_t**)flagref_list.items[i + 0]; - int offs = (int)(intptr_t)flagref_list.items[i + 1]; - jl_value_t *o = loc ? *loc : (jl_value_t*)backref_list.items[offs]; - i += 2; - jl_value_t *newo = jl_recache_other_(o); - if (loc) - *loc = newo; - if (offs > 0) - backref_list.items[offs] = newo; - } - flagref_list.len = 0; -} - -// Wait to copy roots until recaching is done -// This is because recaching requires that all pointers to methods and methodinstances -// stay at their source location as recorded by flagref_list. Once recaching is complete, -// they can be safely copied over. -static void jl_copy_roots(void) -{ - size_t i, j, l; - for (i = 0; i < queued_method_roots.size; i+=2) { - jl_method_t *m = (jl_method_t*)queued_method_roots.table[i]; - m = (jl_method_t*)ptrhash_get(&uniquing_table, m); - jl_svec_t *keyroots = (jl_svec_t*)queued_method_roots.table[i+1]; - if (keyroots != HT_NOTFOUND) { - uint64_t key = (uint64_t)(uintptr_t)jl_svec_ref(keyroots, 0) | ((uint64_t)(uintptr_t)jl_svec_ref(keyroots, 1) << 32); - jl_array_t *roots = (jl_array_t*)jl_svec_ref(keyroots, 2); - assert(jl_is_array(roots)); - l = jl_array_len(roots); - for (j = 0; j < l; j++) { - jl_value_t *r = jl_array_ptr_ref(roots, j); - jl_value_t *newr = (jl_value_t*)ptrhash_get(&uniquing_table, r); - if (newr != HT_NOTFOUND) { - jl_array_ptr_set(roots, j, newr); - } - } - jl_append_method_roots(m, key, roots); - } - } -} - -static int trace_method(jl_typemap_entry_t *entry, void *closure) -{ - jl_call_tracer(jl_newmeth_tracer, (jl_value_t*)entry->func.method); - return 1; -} - -// Restore module(s) from a cache file f -static jl_value_t *_jl_restore_incremental(ios_t *f, jl_array_t *mod_array) -{ - JL_TIMING(LOAD_MODULE); - jl_task_t *ct = jl_current_task; - if (ios_eof(f) || !jl_read_verify_header(f)) { - ios_close(f); - return jl_get_exceptionf(jl_errorexception_type, - "Precompile file header verification checks failed."); - } - { // skip past the mod list - size_t len; - while ((len = read_int32(f))) - ios_skip(f, len + 3 * sizeof(uint64_t)); - } - { // skip past the dependency list - size_t deplen = read_uint64(f); - ios_skip(f, deplen); - } - - jl_bigint_type = jl_base_module ? jl_get_global(jl_base_module, jl_symbol("BigInt")) : NULL; - if (jl_bigint_type) { - gmp_limb_size = jl_unbox_long(jl_get_global((jl_module_t*)jl_get_global(jl_base_module, jl_symbol("GMP")), - jl_symbol("BITS_PER_LIMB"))) / 8; - } - - // verify that the system state is valid - jl_value_t *verify_fail = read_verify_mod_list(f, mod_array); - if (verify_fail) { - ios_close(f); - return verify_fail; - } - - // prepare to deserialize - int en = jl_gc_enable(0); - jl_gc_enable_finalizers(ct, 0); - jl_atomic_fetch_add(&jl_world_counter, 1); // reserve a world age for the deserialization - - arraylist_new(&backref_list, 4000); - arraylist_push(&backref_list, jl_main_module); - arraylist_new(&flagref_list, 0); - htable_new(&queued_method_roots, 0); - htable_new(&new_code_instance_validate, 0); - arraylist_new(&ccallable_list, 0); - htable_new(&uniquing_table, 0); - - jl_serializer_state s = { - f, - ct->ptls, - mod_array - }; - jl_array_t *restored = (jl_array_t*)jl_deserialize_value(&s, (jl_value_t**)&restored); - serializer_worklist = restored; - assert(jl_typeis((jl_value_t*)restored, jl_array_any_type)); - - // See explanation in jl_save_incremental for variables of the same names - jl_value_t *extext_methods = jl_deserialize_value(&s, &extext_methods); - jl_value_t *mi_list = jl_deserialize_value(&s, &mi_list); // reload MIs stored by queue_external_mis - jl_value_t *edges = jl_deserialize_value(&s, &edges); - jl_value_t *ext_targets = jl_deserialize_value(&s, &ext_targets); - - arraylist_t *tracee_list = NULL; - if (jl_newmeth_tracer) // debugging - tracee_list = arraylist_new((arraylist_t*)malloc_s(sizeof(arraylist_t)), 0); - - // at this point, the AST is fully reconstructed, but still completely disconnected - // now all of the interconnects will be created - jl_recache_types(); // make all of the types identities correct - jl_insert_methods((jl_array_t*)extext_methods); // hook up extension methods for external generic functions (needs to be after recache types) - jl_recache_other(); // make all of the other objects identities correct (needs to be after insert methods) - jl_copy_roots(); // copying new roots of external methods (must wait until recaching is complete) - htable_free(&uniquing_table); - jl_array_t *init_order = jl_finalize_deserializer(&s, tracee_list); // done with f and s (needs to be after recache) - if (init_order == NULL) - init_order = (jl_array_t*)jl_an_empty_vec_any; - assert(jl_typeis((jl_value_t*)init_order, jl_array_any_type)); - - JL_GC_PUSH5(&init_order, &restored, &edges, &ext_targets, &mi_list); - jl_gc_enable(en); // subtyping can allocate a lot, not valid before recache-other - - jl_insert_backedges((jl_array_t*)edges, (jl_array_t*)ext_targets, (jl_array_t*)mi_list); // restore external backedges (needs to be last) - // check new CodeInstances and validate any that lack external backedges - validate_new_code_instances(); - - serializer_worklist = NULL; - htable_free(&new_code_instance_validate); - arraylist_free(&flagref_list); - arraylist_free(&backref_list); - htable_free(&queued_method_roots); - ios_close(f); - - jl_gc_enable_finalizers(ct, 1); // make sure we don't run any Julia code concurrently before this point - if (tracee_list) { - jl_methtable_t *mt; - while ((mt = (jl_methtable_t*)arraylist_pop(tracee_list)) != NULL) { - JL_GC_PROMISE_ROOTED(mt); - jl_typemap_visitor(mt->defs, trace_method, NULL); - } - arraylist_free(tracee_list); - free(tracee_list); - } - for (int i = 0; i < ccallable_list.len; i++) { - jl_svec_t *item = (jl_svec_t*)ccallable_list.items[i]; - JL_GC_PROMISE_ROOTED(item); - int success = jl_compile_extern_c(NULL, NULL, NULL, jl_svecref(item, 0), jl_svecref(item, 1)); - if (!success) - jl_safe_printf("@ccallable was already defined for this method name\n"); - } - arraylist_free(&ccallable_list); - jl_value_t *ret = (jl_value_t*)jl_svec(2, restored, init_order); - JL_GC_POP(); - - return (jl_value_t*)ret; -} - -JL_DLLEXPORT jl_value_t *jl_restore_incremental_from_buf(const char *buf, size_t sz, jl_array_t *mod_array) -{ - ios_t f; - ios_static_buffer(&f, (char*)buf, sz); - return _jl_restore_incremental(&f, mod_array); -} - -JL_DLLEXPORT jl_value_t *jl_restore_incremental(const char *fname, jl_array_t *mod_array) -{ - ios_t f; - if (ios_file(&f, fname, 1, 0, 0, 0) == NULL) { - return jl_get_exceptionf(jl_errorexception_type, - "Cache file \"%s\" not found.\n", fname); - } - return _jl_restore_incremental(&f, mod_array); -} - -// --- init --- - -void jl_init_serializer(void) -{ - jl_task_t *ct = jl_current_task; - htable_new(&ser_tag, 0); - htable_new(&common_symbol_tag, 0); - htable_new(&backref_table, 0); - - void *vals[] = { jl_emptysvec, jl_emptytuple, jl_false, jl_true, jl_nothing, jl_any_type, - jl_call_sym, jl_invoke_sym, jl_invoke_modify_sym, jl_goto_ifnot_sym, jl_return_sym, jl_symbol("tuple"), - jl_an_empty_string, jl_an_empty_vec_any, - - // empirical list of very common symbols - #include "common_symbols1.inc" - - jl_box_int32(0), jl_box_int32(1), jl_box_int32(2), - jl_box_int32(3), jl_box_int32(4), jl_box_int32(5), - jl_box_int32(6), jl_box_int32(7), jl_box_int32(8), - jl_box_int32(9), jl_box_int32(10), jl_box_int32(11), - jl_box_int32(12), jl_box_int32(13), jl_box_int32(14), - jl_box_int32(15), jl_box_int32(16), jl_box_int32(17), - jl_box_int32(18), jl_box_int32(19), jl_box_int32(20), - - jl_box_int64(0), jl_box_int64(1), jl_box_int64(2), - jl_box_int64(3), jl_box_int64(4), jl_box_int64(5), - jl_box_int64(6), jl_box_int64(7), jl_box_int64(8), - jl_box_int64(9), jl_box_int64(10), jl_box_int64(11), - jl_box_int64(12), jl_box_int64(13), jl_box_int64(14), - jl_box_int64(15), jl_box_int64(16), jl_box_int64(17), - jl_box_int64(18), jl_box_int64(19), jl_box_int64(20), - - jl_bool_type, jl_linenumbernode_type, jl_pinode_type, - jl_upsilonnode_type, jl_type_type, jl_bottom_type, jl_ref_type, - jl_pointer_type, jl_abstractarray_type, jl_nothing_type, - jl_vararg_type, - jl_densearray_type, jl_function_type, jl_typename_type, - jl_builtin_type, jl_task_type, jl_uniontype_type, - jl_array_any_type, jl_intrinsic_type, - jl_abstractslot_type, jl_methtable_type, jl_typemap_level_type, - jl_voidpointer_type, jl_newvarnode_type, jl_abstractstring_type, - jl_array_symbol_type, jl_anytuple_type, jl_tparam0(jl_anytuple_type), - jl_emptytuple_type, jl_array_uint8_type, jl_code_info_type, - jl_typeofbottom_type, jl_typeofbottom_type->super, - jl_namedtuple_type, jl_array_int32_type, - jl_typedslot_type, jl_uint32_type, jl_uint64_type, - jl_type_type_mt, jl_nonfunction_mt, - jl_opaque_closure_type, - - ct->ptls->root_task, - - NULL }; - - // more common symbols, less common than those above. will get 2-byte encodings. - void *common_symbols[] = { - #include "common_symbols2.inc" - NULL - }; - - deser_tag[TAG_SYMBOL] = (jl_value_t*)jl_symbol_type; - deser_tag[TAG_SSAVALUE] = (jl_value_t*)jl_ssavalue_type; - deser_tag[TAG_DATATYPE] = (jl_value_t*)jl_datatype_type; - deser_tag[TAG_SLOTNUMBER] = (jl_value_t*)jl_slotnumber_type; - deser_tag[TAG_SVEC] = (jl_value_t*)jl_simplevector_type; - deser_tag[TAG_ARRAY] = (jl_value_t*)jl_array_type; - deser_tag[TAG_EXPR] = (jl_value_t*)jl_expr_type; - deser_tag[TAG_PHINODE] = (jl_value_t*)jl_phinode_type; - deser_tag[TAG_PHICNODE] = (jl_value_t*)jl_phicnode_type; - deser_tag[TAG_STRING] = (jl_value_t*)jl_string_type; - deser_tag[TAG_MODULE] = (jl_value_t*)jl_module_type; - deser_tag[TAG_TVAR] = (jl_value_t*)jl_tvar_type; - deser_tag[TAG_METHOD_INSTANCE] = (jl_value_t*)jl_method_instance_type; - deser_tag[TAG_METHOD] = (jl_value_t*)jl_method_type; - deser_tag[TAG_CODE_INSTANCE] = (jl_value_t*)jl_code_instance_type; - deser_tag[TAG_GLOBALREF] = (jl_value_t*)jl_globalref_type; - deser_tag[TAG_INT32] = (jl_value_t*)jl_int32_type; - deser_tag[TAG_INT64] = (jl_value_t*)jl_int64_type; - deser_tag[TAG_UINT8] = (jl_value_t*)jl_uint8_type; - deser_tag[TAG_LINEINFO] = (jl_value_t*)jl_lineinfonode_type; - deser_tag[TAG_UNIONALL] = (jl_value_t*)jl_unionall_type; - deser_tag[TAG_GOTONODE] = (jl_value_t*)jl_gotonode_type; - deser_tag[TAG_QUOTENODE] = (jl_value_t*)jl_quotenode_type; - deser_tag[TAG_GOTOIFNOT] = (jl_value_t*)jl_gotoifnot_type; - deser_tag[TAG_RETURNNODE] = (jl_value_t*)jl_returnnode_type; - deser_tag[TAG_ARGUMENT] = (jl_value_t*)jl_argument_type; - - intptr_t i = 0; - while (vals[i] != NULL) { - deser_tag[LAST_TAG+1+i] = (jl_value_t*)vals[i]; - i += 1; - } - assert(LAST_TAG+1+i < 256); - - for (i = 2; i < 256; i++) { - if (deser_tag[i]) - ptrhash_put(&ser_tag, deser_tag[i], (void*)i); - } - - i = 2; - while (common_symbols[i-2] != NULL) { - ptrhash_put(&common_symbol_tag, common_symbols[i-2], (void*)i); - deser_symbols[i] = (jl_value_t*)common_symbols[i-2]; - i += 1; - } - assert(i <= 256); -} - -#ifdef __cplusplus -} -#endif diff --git a/src/gc.c b/src/gc.c index 212a4b4d691a4b..0fa2077f4edaf7 100644 --- a/src/gc.c +++ b/src/gc.c @@ -173,6 +173,11 @@ jl_gc_num_t gc_num = {0}; static size_t last_long_collect_interval; int gc_n_threads; jl_ptls_t* gc_all_tls_states; +const uint64_t _jl_buff_tag[3] = {0x4eadc0004eadc000ull, 0x4eadc0004eadc000ull, 0x4eadc0004eadc000ull}; // aka 0xHEADER00 +JL_DLLEXPORT uintptr_t jl_get_buff_tag(void) +{ + return jl_buff_tag; +} pagetable_t memory_map; @@ -1759,14 +1764,6 @@ JL_DLLEXPORT void jl_gc_queue_binding(jl_binding_t *bnd) static void *volatile gc_findval; // for usage from gdb, for finding the gc-root for a value #endif -static void *sysimg_base; -static void *sysimg_end; -void jl_gc_set_permalloc_region(void *start, void *end) -{ - sysimg_base = start; - sysimg_end = end; -} - // Handle the case where the stack is only partially copied. STATIC_INLINE uintptr_t gc_get_stack_addr(void *_addr, uintptr_t offset, @@ -2551,7 +2548,7 @@ module_binding: { jl_binding_t *b = *begin; if (b == (jl_binding_t*)HT_NOTFOUND) continue; - if ((void*)b >= sysimg_base && (void*)b < sysimg_end) { + if (jl_object_in_image((jl_value_t*)b)) { jl_taggedvalue_t *buf = jl_astaggedvalue(b); uintptr_t tag = buf->header; uint8_t bits; @@ -2676,7 +2673,7 @@ mark: { jl_datatype_t *vt = (jl_datatype_t*)tag; int foreign_alloc = 0; int update_meta = __likely(!meta_updated && !gc_verifying); - if (update_meta && (void*)o >= sysimg_base && (void*)o < sysimg_end) { + if (update_meta && jl_object_in_image(new_obj)) { foreign_alloc = 1; update_meta = 0; } @@ -3025,6 +3022,8 @@ static void mark_roots(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp) } if (_jl_debug_method_invalidation != NULL) gc_mark_queue_obj(gc_cache, sp, _jl_debug_method_invalidation); + if (jl_build_ids != NULL) + gc_mark_queue_obj(gc_cache, sp, jl_build_ids); // constants gc_mark_queue_obj(gc_cache, sp, jl_emptytuple_type); @@ -4089,8 +4088,6 @@ JL_DLLEXPORT jl_value_t *jl_gc_alloc_3w(void) JL_DLLEXPORT int jl_gc_enable_conservative_gc_support(void) { - static_assert(jl_buff_tag % GC_PAGE_SZ == 0, - "jl_buff_tag must be a multiple of GC_PAGE_SZ"); if (jl_is_initialized()) { int result = jl_atomic_fetch_or(&support_conservative_marking, 1); if (!result) { @@ -4197,8 +4194,8 @@ JL_DLLEXPORT jl_value_t *jl_gc_internal_obj_base_ptr(void *p) valid_object: // We have to treat objects with type `jl_buff_tag` differently, // as they must not be passed to the usual marking functions. - // Note that jl_buff_tag is a multiple of GC_PAGE_SZ, thus it - // cannot be a type reference. + // Note that jl_buff_tag is real pointer into libjulia, + // thus it cannot be a type reference. if ((cell->header & ~(uintptr_t) 3) == jl_buff_tag) return NULL; return jl_valueof(cell); diff --git a/src/gf.c b/src/gf.c index 3be1457afe2d66..77cb7d236168db 100644 --- a/src/gf.c +++ b/src/gf.c @@ -459,7 +459,7 @@ static int get_method_unspec_list(jl_typemap_entry_t *def, void *closure) return 1; } -static int foreach_mtable_in_module( +int foreach_mtable_in_module( jl_module_t *m, int (*visit)(jl_methtable_t *mt, void *env), void *env) diff --git a/src/init.c b/src/init.c index 926aa050629260..89f4153ff15384 100644 --- a/src/init.c +++ b/src/init.c @@ -783,6 +783,10 @@ JL_DLLEXPORT void julia_init(JL_IMAGE_SEARCH rel) jl_install_default_signal_handlers(); jl_gc_init(); + + arraylist_new(&jl_linkage_blobs, 0); + arraylist_new(&jl_image_relocs, 0); + jl_ptls_t ptls = jl_init_threadtls(0); #pragma GCC diagnostic push #if defined(_COMPILER_GCC_) && __GNUC__ >= 12 @@ -808,7 +812,7 @@ static NOINLINE void _finish_julia_init(JL_IMAGE_SEARCH rel, jl_ptls_t ptls, jl_ jl_restore_system_image(jl_options.image_file); } else { jl_init_types(); - jl_global_roots_table = jl_alloc_vec_any(16); + jl_global_roots_table = jl_alloc_vec_any(0); jl_init_codegen(); } diff --git a/src/ircode.c b/src/ircode.c index 1c857051217d03..9f71d8e8dd28cc 100644 --- a/src/ircode.c +++ b/src/ircode.c @@ -29,6 +29,34 @@ typedef struct { uint8_t relocatability; } jl_ircode_state; +// type => tag hash for a few core types (e.g., Expr, PhiNode, etc) +static htable_t ser_tag; +// tag => type mapping, the reverse of ser_tag +static jl_value_t *deser_tag[256]; +// hash of some common symbols, encoded as CommonSym_tag plus 1 byte +static htable_t common_symbol_tag; +static jl_value_t *deser_symbols[256]; + +void *jl_lookup_ser_tag(jl_value_t *v) +{ + return ptrhash_get(&ser_tag, v); +} + +void *jl_lookup_common_symbol(jl_value_t *v) +{ + return ptrhash_get(&common_symbol_tag, v); +} + +jl_value_t *jl_deser_tag(uint8_t tag) +{ + return deser_tag[tag]; +} + +jl_value_t *jl_deser_symbol(uint8_t tag) +{ + return deser_symbols[tag]; +} + // --- encoding --- #define jl_encode_value(s, v) jl_encode_value_((s), (jl_value_t*)(v), 0) @@ -1020,6 +1048,110 @@ JL_DLLEXPORT jl_value_t *jl_uncompress_argname_n(jl_value_t *syms, size_t i) return jl_nothing; } +void jl_init_serializer(void) +{ + jl_task_t *ct = jl_current_task; + htable_new(&ser_tag, 0); + htable_new(&common_symbol_tag, 0); + + void *vals[] = { jl_emptysvec, jl_emptytuple, jl_false, jl_true, jl_nothing, jl_any_type, + jl_call_sym, jl_invoke_sym, jl_invoke_modify_sym, jl_goto_ifnot_sym, jl_return_sym, jl_symbol("tuple"), + jl_an_empty_string, jl_an_empty_vec_any, + + // empirical list of very common symbols + #include "common_symbols1.inc" + + jl_box_int32(0), jl_box_int32(1), jl_box_int32(2), + jl_box_int32(3), jl_box_int32(4), jl_box_int32(5), + jl_box_int32(6), jl_box_int32(7), jl_box_int32(8), + jl_box_int32(9), jl_box_int32(10), jl_box_int32(11), + jl_box_int32(12), jl_box_int32(13), jl_box_int32(14), + jl_box_int32(15), jl_box_int32(16), jl_box_int32(17), + jl_box_int32(18), jl_box_int32(19), jl_box_int32(20), + + jl_box_int64(0), jl_box_int64(1), jl_box_int64(2), + jl_box_int64(3), jl_box_int64(4), jl_box_int64(5), + jl_box_int64(6), jl_box_int64(7), jl_box_int64(8), + jl_box_int64(9), jl_box_int64(10), jl_box_int64(11), + jl_box_int64(12), jl_box_int64(13), jl_box_int64(14), + jl_box_int64(15), jl_box_int64(16), jl_box_int64(17), + jl_box_int64(18), jl_box_int64(19), jl_box_int64(20), + + jl_bool_type, jl_linenumbernode_type, jl_pinode_type, + jl_upsilonnode_type, jl_type_type, jl_bottom_type, jl_ref_type, + jl_pointer_type, jl_abstractarray_type, jl_nothing_type, + jl_vararg_type, + jl_densearray_type, jl_function_type, jl_typename_type, + jl_builtin_type, jl_task_type, jl_uniontype_type, + jl_array_any_type, jl_intrinsic_type, + jl_abstractslot_type, jl_methtable_type, jl_typemap_level_type, + jl_voidpointer_type, jl_newvarnode_type, jl_abstractstring_type, + jl_array_symbol_type, jl_anytuple_type, jl_tparam0(jl_anytuple_type), + jl_emptytuple_type, jl_array_uint8_type, jl_code_info_type, + jl_typeofbottom_type, jl_typeofbottom_type->super, + jl_namedtuple_type, jl_array_int32_type, + jl_typedslot_type, jl_uint32_type, jl_uint64_type, + jl_type_type_mt, jl_nonfunction_mt, + jl_opaque_closure_type, + + ct->ptls->root_task, + + NULL }; + + // more common symbols, less common than those above. will get 2-byte encodings. + void *common_symbols[] = { + #include "common_symbols2.inc" + NULL + }; + + deser_tag[TAG_SYMBOL] = (jl_value_t*)jl_symbol_type; + deser_tag[TAG_SSAVALUE] = (jl_value_t*)jl_ssavalue_type; + deser_tag[TAG_DATATYPE] = (jl_value_t*)jl_datatype_type; + deser_tag[TAG_SLOTNUMBER] = (jl_value_t*)jl_slotnumber_type; + deser_tag[TAG_SVEC] = (jl_value_t*)jl_simplevector_type; + deser_tag[TAG_ARRAY] = (jl_value_t*)jl_array_type; + deser_tag[TAG_EXPR] = (jl_value_t*)jl_expr_type; + deser_tag[TAG_PHINODE] = (jl_value_t*)jl_phinode_type; + deser_tag[TAG_PHICNODE] = (jl_value_t*)jl_phicnode_type; + deser_tag[TAG_STRING] = (jl_value_t*)jl_string_type; + deser_tag[TAG_MODULE] = (jl_value_t*)jl_module_type; + deser_tag[TAG_TVAR] = (jl_value_t*)jl_tvar_type; + deser_tag[TAG_METHOD_INSTANCE] = (jl_value_t*)jl_method_instance_type; + deser_tag[TAG_METHOD] = (jl_value_t*)jl_method_type; + deser_tag[TAG_CODE_INSTANCE] = (jl_value_t*)jl_code_instance_type; + deser_tag[TAG_GLOBALREF] = (jl_value_t*)jl_globalref_type; + deser_tag[TAG_INT32] = (jl_value_t*)jl_int32_type; + deser_tag[TAG_INT64] = (jl_value_t*)jl_int64_type; + deser_tag[TAG_UINT8] = (jl_value_t*)jl_uint8_type; + deser_tag[TAG_LINEINFO] = (jl_value_t*)jl_lineinfonode_type; + deser_tag[TAG_UNIONALL] = (jl_value_t*)jl_unionall_type; + deser_tag[TAG_GOTONODE] = (jl_value_t*)jl_gotonode_type; + deser_tag[TAG_QUOTENODE] = (jl_value_t*)jl_quotenode_type; + deser_tag[TAG_GOTOIFNOT] = (jl_value_t*)jl_gotoifnot_type; + deser_tag[TAG_RETURNNODE] = (jl_value_t*)jl_returnnode_type; + deser_tag[TAG_ARGUMENT] = (jl_value_t*)jl_argument_type; + + intptr_t i = 0; + while (vals[i] != NULL) { + deser_tag[LAST_TAG+1+i] = (jl_value_t*)vals[i]; + i += 1; + } + assert(LAST_TAG+1+i < 256); + + for (i = 2; i < 256; i++) { + if (deser_tag[i]) + ptrhash_put(&ser_tag, deser_tag[i], (void*)i); + } + + i = 2; + while (common_symbols[i-2] != NULL) { + ptrhash_put(&common_symbol_tag, common_symbols[i-2], (void*)i); + deser_symbols[i] = (jl_value_t*)common_symbols[i-2]; + i += 1; + } + assert(i <= 256); +} + #ifdef __cplusplus } #endif diff --git a/src/jitlayers.h b/src/jitlayers.h index ba38abff0d6f46..77ac5d64bb46d2 100644 --- a/src/jitlayers.h +++ b/src/jitlayers.h @@ -220,7 +220,6 @@ jl_llvm_functions_t jl_emit_codeinst( enum CompilationPolicy { Default = 0, Extern = 1, - ImagingMode = 2 }; typedef std::map> jl_workqueue_t; diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index 714998b650e284..f97c9894238591 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -400,7 +400,7 @@ XX(jl_resolve_globals_in_ir) \ XX(jl_restore_excstack) \ XX(jl_restore_incremental) \ - XX(jl_restore_incremental_from_buf) \ + XX(jl_restore_package_image_from_file) \ XX(jl_restore_system_image) \ XX(jl_restore_system_image_data) \ XX(jl_rethrow) \ @@ -408,8 +408,6 @@ XX(jl_rettype_inferred) \ XX(jl_running_on_valgrind) \ XX(jl_safe_printf) \ - XX(jl_save_incremental) \ - XX(jl_save_system_image) \ XX(jl_SC_CLK_TCK) \ XX(jl_set_ARGS) \ XX(jl_set_const) \ @@ -520,6 +518,7 @@ XX(jl_vexceptionf) \ XX(jl_vprintf) \ XX(jl_wakeup_thread) \ + XX(jl_write_compiler_output) \ XX(jl_yield) \ #define JL_RUNTIME_EXPORTED_FUNCS_WIN(XX) \ @@ -535,7 +534,7 @@ YY(jl_get_llvm_module) \ YY(jl_get_LLVM_VERSION) \ YY(jl_dump_native) \ - YY(jl_get_llvm_gv) \ + YY(jl_get_llvm_gvs) \ YY(jl_dump_function_asm) \ YY(jl_LLVMCreateDisasm) \ YY(jl_LLVMDisasmInstruction) \ diff --git a/src/julia.expmap b/src/julia.expmap index 41299aa808572a..35cc5eac48b6ac 100644 --- a/src/julia.expmap +++ b/src/julia.expmap @@ -5,6 +5,7 @@ asprintf; bitvector_*; ios_*; + arraylist_grow; small_arraylist_grow; jl_*; ijl_*; diff --git a/src/julia.h b/src/julia.h index 981e6a0ee8232e..769a2923e20fe4 100644 --- a/src/julia.h +++ b/src/julia.h @@ -315,7 +315,7 @@ typedef struct _jl_method_t { jl_array_t *roots; // pointers in generated code (shared to reduce memory), or null // Identify roots by module-of-origin. We only track the module for roots added during incremental compilation. // May be NULL if no external roots have been added, otherwise it's a Vector{UInt64} - jl_array_t *root_blocks; // RLE (build_id, offset) pairs (even/odd indexing) + jl_array_t *root_blocks; // RLE (build_id.lo, offset) pairs (even/odd indexing) int32_t nroots_sysimg; // # of roots stored in the system image jl_svec_t *ccallable; // svec(rettype, sig) if a ccallable entry point is requested for this @@ -592,7 +592,7 @@ typedef struct _jl_module_t { // hidden fields: htable_t bindings; arraylist_t usings; // modules with all bindings potentially imported - uint64_t build_id; + jl_uuid_t build_id; jl_uuid_t uuid; size_t primary_world; _Atomic(uint32_t) counter; @@ -841,6 +841,7 @@ extern void JL_GC_PUSH3(void *, void *, void *) JL_NOTSAFEPOINT; extern void JL_GC_PUSH4(void *, void *, void *, void *) JL_NOTSAFEPOINT; extern void JL_GC_PUSH5(void *, void *, void *, void *, void *) JL_NOTSAFEPOINT; extern void JL_GC_PUSH7(void *, void *, void *, void *, void *, void *, void *) JL_NOTSAFEPOINT; +extern void JL_GC_PUSH8(void *, void *, void *, void *, void *, void *, void *, void *) JL_NOTSAFEPOINT; extern void _JL_GC_PUSHARGS(jl_value_t **, size_t) JL_NOTSAFEPOINT; // This is necessary, because otherwise the analyzer considers this undefined // behavior and terminates the exploration @@ -880,6 +881,9 @@ extern void JL_GC_POP() JL_NOTSAFEPOINT; #define JL_GC_PUSH7(arg1, arg2, arg3, arg4, arg5, arg6, arg7) \ void *__gc_stkf[] = {(void*)JL_GC_ENCODE_PUSH(7), jl_pgcstack, arg1, arg2, arg3, arg4, arg5, arg6, arg7}; \ jl_pgcstack = (jl_gcframe_t*)__gc_stkf; +#define JL_GC_PUSH8(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) \ + void *__gc_stkf[] = {(void*)JL_GC_ENCODE_PUSH(8), jl_pgcstack, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8}; \ + jl_pgcstack = (jl_gcframe_t*)__gc_stkf; #define JL_GC_PUSHARGS(rts_var,n) \ @@ -1763,15 +1767,14 @@ JL_DLLEXPORT jl_gcframe_t **jl_adopt_thread(void); JL_DLLEXPORT int jl_deserialize_verify_header(ios_t *s); JL_DLLEXPORT void jl_preload_sysimg_so(const char *fname); JL_DLLEXPORT void jl_set_sysimg_so(void *handle); -JL_DLLEXPORT ios_t *jl_create_system_image(void *); -JL_DLLEXPORT void jl_save_system_image(const char *fname); +JL_DLLEXPORT ios_t *jl_create_system_image(void *, jl_array_t *worklist); JL_DLLEXPORT void jl_restore_system_image(const char *fname); JL_DLLEXPORT void jl_restore_system_image_data(const char *buf, size_t len); +JL_DLLEXPORT jl_value_t *jl_restore_incremental(const char *fname, jl_array_t *depmods, int complete); + JL_DLLEXPORT void jl_set_newly_inferred(jl_value_t *newly_inferred); -JL_DLLEXPORT void jl_push_newly_inferred(jl_value_t *linfo); -JL_DLLEXPORT int jl_save_incremental(const char *fname, jl_array_t *worklist); -JL_DLLEXPORT jl_value_t *jl_restore_incremental(const char *fname, jl_array_t *depmods); -JL_DLLEXPORT jl_value_t *jl_restore_incremental_from_buf(const char *buf, size_t sz, jl_array_t *depmods); +JL_DLLEXPORT void jl_push_newly_inferred(jl_value_t *ci); +JL_DLLEXPORT void jl_write_compiler_output(void); // parsing JL_DLLEXPORT jl_value_t *jl_parse_all(const char *text, size_t text_len, diff --git a/src/julia_internal.h b/src/julia_internal.h index f1929892df5513..6ddfa5d92072c9 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -290,6 +290,9 @@ extern tracer_cb jl_newmeth_tracer; void jl_call_tracer(tracer_cb callback, jl_value_t *tracee); void print_func_loc(JL_STREAM *s, jl_method_t *m); extern jl_array_t *_jl_debug_method_invalidation JL_GLOBALLY_ROOTED; +extern arraylist_t jl_linkage_blobs; // external linkage: sysimg/pkgimages +extern jl_array_t *jl_build_ids JL_GLOBALLY_ROOTED; // external linkage: corresponding build_ids +extern arraylist_t jl_image_relocs; // external linkage: sysimg/pkgimages extern JL_DLLEXPORT size_t jl_page_size; extern jl_function_t *jl_typeinf_func JL_GLOBALLY_ROOTED; @@ -460,9 +463,12 @@ JL_DLLEXPORT jl_value_t *jl_gc_alloc(jl_ptls_t ptls, size_t sz, void *ty); # define jl_gc_alloc(ptls, sz, ty) jl_gc_alloc_(ptls, sz, ty) #endif -// jl_buff_tag must be a multiple of GC_PAGE_SZ so that it can't be -// confused for an actual type reference. -#define jl_buff_tag ((uintptr_t)0x4eadc000) +// jl_buff_tag must be an actual pointer here, so it cannot be confused for an actual type reference. +// defined as uint64_t[3] so that we can get the right alignment of this and a "type tag" on it +const extern uint64_t _jl_buff_tag[3]; +#define jl_buff_tag ((uintptr_t)LLT_ALIGN((uintptr_t)&_jl_buff_tag[1],16)) +JL_DLLEXPORT uintptr_t jl_get_buff_tag(void); + typedef void jl_gc_tracked_buffer_t; // For the benefit of the static analyzer STATIC_INLINE jl_gc_tracked_buffer_t *jl_gc_alloc_buf(jl_ptls_t ptls, size_t sz) { @@ -608,9 +614,9 @@ void push_edge(jl_array_t *list, jl_value_t *invokesig, jl_method_instance_t *ca JL_DLLEXPORT void jl_add_method_root(jl_method_t *m, jl_module_t *mod, jl_value_t* root); void jl_append_method_roots(jl_method_t *m, uint64_t modid, jl_array_t* roots); -int get_root_reference(rle_reference *rr, jl_method_t *m, size_t i); -jl_value_t *lookup_root(jl_method_t *m, uint64_t key, int index); -int nroots_with_key(jl_method_t *m, uint64_t key); +int get_root_reference(rle_reference *rr, jl_method_t *m, size_t i) JL_NOTSAFEPOINT; +jl_value_t *lookup_root(jl_method_t *m, uint64_t key, int index) JL_NOTSAFEPOINT; +int nroots_with_key(jl_method_t *m, uint64_t key) JL_NOTSAFEPOINT; int jl_valid_type_param(jl_value_t *v); @@ -690,6 +696,7 @@ jl_expr_t *jl_exprn(jl_sym_t *head, size_t n); jl_function_t *jl_new_generic_function(jl_sym_t *name, jl_module_t *module); jl_function_t *jl_new_generic_function_with_supertype(jl_sym_t *name, jl_module_t *module, jl_datatype_t *st); int jl_foreach_reachable_mtable(int (*visit)(jl_methtable_t *mt, void *env), void *env); +int foreach_mtable_in_module(jl_module_t *m, int (*visit)(jl_methtable_t *mt, void *env), void *env); void jl_init_main_module(void); JL_DLLEXPORT int jl_is_submodule(jl_module_t *child, jl_module_t *parent) JL_NOTSAFEPOINT; jl_array_t *jl_get_loaded_modules(void); @@ -900,7 +907,7 @@ typedef DWORD jl_pgcstack_key_t; #else typedef jl_gcframe_t ***(*jl_pgcstack_key_t)(void) JL_NOTSAFEPOINT; #endif -JL_DLLEXPORT void jl_pgcstack_getkey(jl_get_pgcstack_func **f, jl_pgcstack_key_t *k); +JL_DLLEXPORT void jl_pgcstack_getkey(jl_get_pgcstack_func **f, jl_pgcstack_key_t *k) JL_NOTSAFEPOINT; #if !defined(_OS_WINDOWS_) && !defined(__APPLE__) && !defined(JL_DISABLE_LIBUNWIND) extern pthread_mutex_t in_signal_lock; @@ -918,7 +925,38 @@ static inline void jl_set_gc_and_wait(void) jl_atomic_store_release(&ct->ptls->gc_state, state); } #endif -void jl_gc_set_permalloc_region(void *start, void *end); + +// Query if a Julia object is if a permalloc region (due to part of a sys- pkg-image) +STATIC_INLINE size_t n_linkage_blobs(void) JL_NOTSAFEPOINT +{ + if (!jl_build_ids) + return 0; + assert(jl_is_array(jl_build_ids)); + return jl_array_len(jl_build_ids); +} + +// TODO: Makes this a binary search +STATIC_INLINE size_t external_blob_index(jl_value_t *v) JL_NOTSAFEPOINT { + size_t i, nblobs = n_linkage_blobs(); + assert(jl_linkage_blobs.len == 2*nblobs); + for (i = 0; i < nblobs; i++) { + uintptr_t left = (uintptr_t)jl_linkage_blobs.items[2*i]; + uintptr_t right = (uintptr_t)jl_linkage_blobs.items[2*i + 1]; + if (left < (uintptr_t)v && (uintptr_t)v <= right) { + // the last object may be a singleton (v is shifted by a type tag, so we use exclusive bounds here) + break; + } + } + return i; +} + +STATIC_INLINE uint8_t jl_object_in_image(jl_value_t* v) JL_NOTSAFEPOINT { + size_t blob = external_blob_index(v); + if (blob == n_linkage_blobs()) { + return 0; + } + return 1; +} typedef struct { LLVMOrcThreadSafeModuleRef TSM; @@ -932,11 +970,11 @@ JL_DLLEXPORT jl_value_t *jl_dump_fptr_asm(uint64_t fptr, char raw_mc, const char JL_DLLEXPORT jl_value_t *jl_dump_function_ir(jl_llvmf_dump_t *dump, char strip_ir_metadata, char dump_module, const char *debuginfo); JL_DLLEXPORT jl_value_t *jl_dump_function_asm(jl_llvmf_dump_t *dump, char raw_mc, const char* asm_variant, const char *debuginfo, char binary); -void *jl_create_native(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvmmod, const jl_cgparams_t *cgparams, int policy); +void *jl_create_native(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvmmod, const jl_cgparams_t *cgparams, int policy, int imaging_mode); void jl_dump_native(void *native_code, const char *bc_fname, const char *unopt_bc_fname, const char *obj_fname, const char *asm_fname, const char *sysimg_data, size_t sysimg_len); -int32_t jl_get_llvm_gv(void *native_code, jl_value_t *p) JL_NOTSAFEPOINT; +void jl_get_llvm_gvs(void *native_code, arraylist_t *gvs); JL_DLLEXPORT void jl_get_function_id(void *native_code, jl_code_instance_t *ncode, int32_t *func_idx, int32_t *specfunc_idx); @@ -1223,6 +1261,7 @@ extern void *jl_ntdll_handle; extern void *jl_kernel32_handle; extern void *jl_crtdll_handle; extern void *jl_winsock_handle; +void win32_formatmessage(DWORD code, char *reason, int len) JL_NOTSAFEPOINT; #endif JL_DLLEXPORT void *jl_get_library_(const char *f_lib, int throw_err); @@ -1563,7 +1602,6 @@ void jl_register_fptrs(uint64_t sysimage_base, const struct _jl_sysimg_fptrs_t * jl_method_instance_t **linfos, size_t n); void jl_write_coverage_data(const char*); void jl_write_malloc_log(void); -void jl_write_compiler_output(void); #if jl_has_builtin(__builtin_unreachable) || defined(_COMPILER_GCC_) || defined(_COMPILER_INTEL_) # define jl_unreachable() __builtin_unreachable() @@ -1616,6 +1654,8 @@ JL_DLLEXPORT uint16_t julia__truncdfhf2(double param) JL_NOTSAFEPOINT; //JL_DLLEXPORT uint16_t julia__floatunsihf(uint32_t n) JL_NOTSAFEPOINT; //JL_DLLEXPORT uint16_t julia__floatundihf(uint64_t n) JL_NOTSAFEPOINT; +JL_DLLEXPORT uint32_t jl_crc32c(uint32_t crc, const char *buf, size_t len); + #ifdef __cplusplus } #endif diff --git a/src/llvm-multiversioning.cpp b/src/llvm-multiversioning.cpp index f2fdb6f4fd1c86..815ebfe7ed1011 100644 --- a/src/llvm-multiversioning.cpp +++ b/src/llvm-multiversioning.cpp @@ -306,23 +306,31 @@ static inline std::vector consume_gv(Module &M, const char *name, bool allow // Strip them from the Module so that it's easier to handle the uses. GlobalVariable *gv = M.getGlobalVariable(name); assert(gv && gv->hasInitializer()); - auto *ary = cast(gv->getInitializer()); - unsigned nele = ary->getNumOperands(); + ArrayType *Ty = cast(gv->getInitializer()->getType()); + unsigned nele = Ty->getArrayNumElements(); std::vector res(nele); - unsigned i = 0; - while (i < nele) { - llvm::Value *val = ary->getOperand(i)->stripPointerCasts(); - if (allow_bad_fvars && (!isa(val) || (isa(val) && cast(val)->isDeclaration()))) { - // Shouldn't happen in regular use, but can happen in bugpoint. - nele--; - continue; + ConstantArray *ary = nullptr; + if (gv->getInitializer()->isNullValue()) { + for (unsigned i = 0; i < nele; ++i) + res[i] = cast(Constant::getNullValue(Ty->getArrayElementType())); + } + else { + ary = cast(gv->getInitializer()); + unsigned i = 0; + while (i < nele) { + llvm::Value *val = ary->getOperand(i)->stripPointerCasts(); + if (allow_bad_fvars && (!isa(val) || (isa(val) && cast(val)->isDeclaration()))) { + // Shouldn't happen in regular use, but can happen in bugpoint. + nele--; + continue; + } + res[i++] = cast(val); } - res[i++] = cast(val); + res.resize(nele); } - res.resize(nele); assert(gv->use_empty()); gv->eraseFromParent(); - if (ary->use_empty()) + if (ary && ary->use_empty()) ary->destroyConstant(); return res; } @@ -935,17 +943,24 @@ Constant *CloneCtx::emit_offset_table(const std::vector &vars, StringRef nam { auto T_int32 = Type::getInt32Ty(M.getContext()); auto T_size = getSizeTy(M.getContext()); - assert(!vars.empty()); - add_comdat(GlobalAlias::create(T_size, 0, GlobalVariable::ExternalLinkage, - name + "_base", - ConstantExpr::getBitCast(vars[0], T_size->getPointerTo()), &M)); - auto vbase = ConstantExpr::getPtrToInt(vars[0], T_size); uint32_t nvars = vars.size(); + Constant *base = nullptr; + if (nvars > 0) { + base = ConstantExpr::getBitCast(vars[0], T_size->getPointerTo()); + add_comdat(GlobalAlias::create(T_size, 0, GlobalVariable::ExternalLinkage, + name + "_base", + base, &M)); + } else { + base = ConstantExpr::getNullValue(T_size->getPointerTo()); + } + auto vbase = ConstantExpr::getPtrToInt(base, T_size); std::vector offsets(nvars + 1); offsets[0] = ConstantInt::get(T_int32, nvars); - offsets[1] = ConstantInt::get(T_int32, 0); - for (uint32_t i = 1; i < nvars; i++) - offsets[i + 1] = get_ptrdiff32(vars[i], vbase); + if (nvars > 0) { + offsets[1] = ConstantInt::get(T_int32, 0); + for (uint32_t i = 1; i < nvars; i++) + offsets[i + 1] = get_ptrdiff32(vars[i], vbase); + } ArrayType *vars_type = ArrayType::get(T_int32, nvars + 1); add_comdat(new GlobalVariable(M, vars_type, true, GlobalVariable::ExternalLinkage, diff --git a/src/method.c b/src/method.c index 587ffd65e1cd86..d0485f239824b5 100644 --- a/src/method.c +++ b/src/method.c @@ -1188,7 +1188,7 @@ JL_DLLEXPORT void jl_add_method_root(jl_method_t *m, jl_module_t *mod, jl_value_ uint64_t modid = 0; if (mod) { assert(jl_is_module(mod)); - modid = mod->build_id; + modid = mod->build_id.lo; } assert(jl_is_method(m)); prepare_method_for_roots(m, modid); diff --git a/src/module.c b/src/module.c index 0dc5e20d18b89e..605bcd3c2b7737 100644 --- a/src/module.c +++ b/src/module.c @@ -23,9 +23,10 @@ JL_DLLEXPORT jl_module_t *jl_new_module_(jl_sym_t *name, uint8_t default_names) m->istopmod = 0; m->uuid = uuid_zero; static unsigned int mcounter; // simple counter backup, in case hrtime is not incrementing - m->build_id = jl_hrtime() + (++mcounter); - if (!m->build_id) - m->build_id++; // build id 0 is invalid + m->build_id.lo = jl_hrtime() + (++mcounter); + if (!m->build_id.lo) + m->build_id.lo++; // build id 0 is invalid + m->build_id.hi = ~(uint64_t)0; m->primary_world = 0; m->counter = 1; m->nospecialize = 0; @@ -936,7 +937,7 @@ JL_DLLEXPORT jl_value_t *jl_module_names(jl_module_t *m, int all, int imported) JL_DLLEXPORT jl_sym_t *jl_module_name(jl_module_t *m) { return m->name; } JL_DLLEXPORT jl_module_t *jl_module_parent(jl_module_t *m) { return m->parent; } -JL_DLLEXPORT uint64_t jl_module_build_id(jl_module_t *m) { return m->build_id; } +JL_DLLEXPORT jl_uuid_t jl_module_build_id(jl_module_t *m) { return m->build_id; } JL_DLLEXPORT jl_uuid_t jl_module_uuid(jl_module_t* m) { return m->uuid; } // TODO: make this part of the module constructor and read-only? @@ -972,6 +973,22 @@ JL_DLLEXPORT void jl_clear_implicit_imports(jl_module_t *m) JL_UNLOCK(&m->lock); } +JL_DLLEXPORT void jl_init_restored_modules(jl_array_t *init_order) +{ + int i, l = jl_array_len(init_order); + for (i = 0; i < l; i++) { + jl_value_t *mod = jl_array_ptr_ref(init_order, i); + if (!jl_generating_output() || jl_options.incremental) { + jl_module_run_initializer((jl_module_t*)mod); + } + else { + if (jl_module_init_order == NULL) + jl_module_init_order = jl_alloc_vec_any(0); + jl_array_ptr_1d_push(jl_module_init_order, mod); + } + } +} + #ifdef __cplusplus } #endif diff --git a/src/precompile.c b/src/precompile.c index d5d8416c1097b9..9c9c79b154a324 100644 --- a/src/precompile.c +++ b/src/precompile.c @@ -21,17 +21,14 @@ JL_DLLEXPORT int jl_generating_output(void) } static void *jl_precompile(int all); +static void *jl_precompile_worklist(jl_array_t *worklist); -void jl_write_compiler_output(void) +JL_DLLEXPORT void jl_write_compiler_output(void) { if (!jl_generating_output()) { return; } - void *native_code = NULL; - if (!jl_options.incremental) - native_code = jl_precompile(jl_options.compile_enabled == JL_OPTIONS_COMPILE_ALL); - if (!jl_module_init_order) { jl_printf(JL_STDERR, "WARNING: --output requested, but no modules defined during run\n"); return; @@ -60,46 +57,51 @@ void jl_write_compiler_output(void) } } + assert(jl_precompile_toplevel_module == NULL); + void *native_code = NULL; + if (jl_options.outputo || jl_options.outputbc || jl_options.outputunoptbc || jl_options.outputasm) { + if (jl_options.incremental) + jl_precompile_toplevel_module = (jl_module_t*)jl_array_ptr_ref(worklist, jl_array_len(worklist)-1); + native_code = jl_options.incremental ? jl_precompile_worklist(worklist) : jl_precompile(jl_options.compile_enabled == JL_OPTIONS_COMPILE_ALL); + if (jl_options.incremental) + jl_precompile_toplevel_module = NULL; + } + if (jl_options.incremental) { - if (jl_options.outputji) - if (jl_save_incremental(jl_options.outputji, worklist)) - jl_exit(1); if (jl_options.outputbc || jl_options.outputunoptbc) jl_printf(JL_STDERR, "WARNING: incremental output to a .bc file is not implemented\n"); - if (jl_options.outputo) - jl_printf(JL_STDERR, "WARNING: incremental output to a .o file is not implemented\n"); if (jl_options.outputasm) jl_printf(JL_STDERR, "WARNING: incremental output to a .s file is not implemented\n"); + if (jl_options.outputo) { + jl_printf(JL_STDERR, "WARNING: incremental output to a .o file is not implemented\n"); + } } - else { - ios_t *s = NULL; - if (jl_options.outputo || jl_options.outputbc || jl_options.outputunoptbc || jl_options.outputasm) - s = jl_create_system_image(native_code); - if (jl_options.outputji) { - if (s == NULL) { - jl_save_system_image(jl_options.outputji); - } - else { - ios_t f; - if (ios_file(&f, jl_options.outputji, 1, 1, 1, 1) == NULL) - jl_errorf("cannot open system image file \"%s\" for writing", jl_options.outputji); - ios_write(&f, (const char*)s->buf, (size_t)s->size); - ios_close(&f); - } - } + ios_t *s = jl_create_system_image(native_code, jl_options.incremental ? worklist : NULL); - if (jl_options.outputo || jl_options.outputbc || jl_options.outputunoptbc || jl_options.outputasm) { - assert(s); - jl_dump_native(native_code, - jl_options.outputbc, - jl_options.outputunoptbc, - jl_options.outputo, - jl_options.outputasm, - (const char*)s->buf, (size_t)s->size); - jl_postoutput_hook(); - } + if (jl_options.outputji) { + ios_t f; + if (ios_file(&f, jl_options.outputji, 1, 1, 1, 1) == NULL) + jl_errorf("cannot open system image file \"%s\" for writing", jl_options.outputji); + ios_write(&f, (const char*)s->buf, (size_t)s->size); + ios_close(&f); } + + if (native_code) { + jl_dump_native(native_code, + jl_options.outputbc, + jl_options.outputunoptbc, + jl_options.outputo, + jl_options.outputasm, + (const char*)s->buf, (size_t)s->size); + jl_postoutput_hook(); + } + + if (s) { + ios_close(s); + free(s); + } + for (size_t i = 0; i < jl_current_modules.size; i += 2) { if (jl_current_modules.table[i + 1] != HT_NOTFOUND) { jl_printf(JL_STDERR, "\nWARNING: detected unclosed module: "); @@ -340,16 +342,11 @@ static int precompile_enq_all_specializations_(jl_methtable_t *mt, void *env) return jl_typemap_visitor(jl_atomic_load_relaxed(&mt->defs), precompile_enq_all_specializations__, env); } -static void *jl_precompile(int all) +static void *jl_precompile_(jl_array_t *m) { - // array of MethodInstances and ccallable aliases to include in the output - jl_array_t *m = jl_alloc_vec_any(0); jl_array_t *m2 = NULL; jl_method_instance_t *mi = NULL; - JL_GC_PUSH3(&m, &m2, &mi); - if (all) - jl_compile_all_defs(m); - jl_foreach_reachable_mtable(precompile_enq_all_specializations_, m); + JL_GC_PUSH2(&m2, &mi); m2 = jl_alloc_vec_any(0); for (size_t i = 0; i < jl_array_len(m); i++) { jl_value_t *item = jl_array_ptr_ref(m, i); @@ -368,8 +365,39 @@ static void *jl_precompile(int all) jl_array_ptr_1d_push(m2, item); } } - m = NULL; - void *native_code = jl_create_native(m2, NULL, NULL, 0); + void *native_code = jl_create_native(m2, NULL, NULL, 0, 1); + JL_GC_POP(); + return native_code; +} + +static void *jl_precompile(int all) +{ + // array of MethodInstances and ccallable aliases to include in the output + jl_array_t *m = jl_alloc_vec_any(0); + JL_GC_PUSH1(&m); + if (all) + jl_compile_all_defs(m); + jl_foreach_reachable_mtable(precompile_enq_all_specializations_, m); + void *native_code = jl_precompile_(m); + JL_GC_POP(); + return native_code; +} + +static void *jl_precompile_worklist(jl_array_t *worklist) +{ + if (!worklist) + return NULL; + // this "found" array will contain function + // type signatures that were inferred but haven't been compiled + jl_array_t *m = jl_alloc_vec_any(0); + JL_GC_PUSH1(&m); + size_t i, nw = jl_array_len(worklist); + for (i = 0; i < nw; i++) { + jl_module_t *mod = (jl_module_t*)jl_array_ptr_ref(worklist, i); + assert(jl_is_module(mod)); + foreach_mtable_in_module(mod, precompile_enq_all_specializations_, m); + } + void *native_code = jl_precompile_(m); JL_GC_POP(); return native_code; } diff --git a/src/processor.cpp b/src/processor.cpp index b9dfc2b7f0b4e6..df114b4d802575 100644 --- a/src/processor.cpp +++ b/src/processor.cpp @@ -627,10 +627,14 @@ static inline jl_sysimg_fptrs_t parse_sysimg(void *hdl, F &&callback) // .data base char *data_base; - jl_dlsym(hdl, "jl_sysimg_gvars_base", (void**)&data_base, 1); + if (!jl_dlsym(hdl, "jl_sysimg_gvars_base", (void**)&data_base, 0)) { + data_base = NULL; + } // .text base char *text_base; - jl_dlsym(hdl, "jl_sysimg_fvars_base", (void**)&text_base, 1); + if (!jl_dlsym(hdl, "jl_sysimg_fvars_base", (void**)&text_base, 0)) { + text_base = NULL; + } res.base = text_base; int32_t *offsets; @@ -713,6 +717,7 @@ static inline jl_sysimg_fptrs_t parse_sysimg(void *hdl, F &&callback) if (reloc_idx == idx) { found = true; auto slot = (const void**)(data_base + reloc_slots[reloc_i * 2 + 1]); + assert(slot); *slot = offset + res.base; } else if (reloc_idx > idx) { diff --git a/src/processor.h b/src/processor.h index 4b9071fb4f663f..ac00f8874141bd 100644 --- a/src/processor.h +++ b/src/processor.h @@ -166,6 +166,7 @@ typedef struct _jl_sysimg_fptrs_t { * Return the data about the function pointers selected. */ jl_sysimg_fptrs_t jl_init_processor_sysimg(void *hdl); +jl_sysimg_fptrs_t jl_init_processor_pkgimg(void *hdl); // Return the name of the host CPU as a julia string. JL_DLLEXPORT jl_value_t *jl_get_cpu_name(void); diff --git a/src/processor_arm.cpp b/src/processor_arm.cpp index eaa950662d0dea..f7a112993e3e54 100644 --- a/src/processor_arm.cpp +++ b/src/processor_arm.cpp @@ -1586,6 +1586,20 @@ static uint32_t sysimg_init_cb(const void *id) return match.best_idx; } +static uint32_t pkgimg_init_cb(const void *id) +{ + TargetData target = jit_targets.front(); + auto pkgimg = deserialize_target_data((const uint8_t*)id); + for (auto &t: pkgimg) { + if (auto nname = normalize_cpu_name(t.name)) { + t.name = nname; + } + } + auto match = match_sysimg_targets(pkgimg, target, max_vector_size); + + return match.best_idx; +} + static void ensure_jit_target(bool imaging) { auto &cmdline = get_cmdline_targets(); @@ -1795,6 +1809,15 @@ jl_sysimg_fptrs_t jl_init_processor_sysimg(void *hdl) return parse_sysimg(hdl, sysimg_init_cb); } +jl_sysimg_fptrs_t jl_init_processor_pkgimg(void *hdl) +{ + if (jit_targets.empty()) + jl_error("JIT targets not initialized"); + if (jit_targets.size() > 1) + jl_error("Expected only one JIT target"); + return parse_sysimg(hdl, pkgimg_init_cb); +} + std::pair> jl_get_llvm_target(bool imaging, uint32_t &flags) { ensure_jit_target(imaging); diff --git a/src/processor_fallback.cpp b/src/processor_fallback.cpp index 1f314eb460f0f2..3160bd0ba67506 100644 --- a/src/processor_fallback.cpp +++ b/src/processor_fallback.cpp @@ -51,6 +51,22 @@ static uint32_t sysimg_init_cb(const void *id) return best_idx; } +static uint32_t pkgimg_init_cb(const void *id) +{ + TargetData<1> target = jit_targets.front(); + // Find the last name match or use the default one. + uint32_t best_idx = 0; + auto pkgimg = deserialize_target_data<1>((const uint8_t*)id); + for (uint32_t i = 0; i < pkgimg.size(); i++) { + auto &imgt = pkgimg[i]; + if (imgt.name == target.name) { + best_idx = i; + } + } + + return best_idx; +} + static void ensure_jit_target(bool imaging) { auto &cmdline = get_cmdline_targets(); @@ -103,6 +119,15 @@ jl_sysimg_fptrs_t jl_init_processor_sysimg(void *hdl) return parse_sysimg(hdl, sysimg_init_cb); } +jl_sysimg_fptrs_t jl_init_processor_pkgimg(void *hdl) +{ + if (jit_targets.empty()) + jl_error("JIT targets not initialized"); + if (jit_targets.size() > 1) + jl_error("Expected only one JIT target"); + return parse_sysimg(hdl, pkgimg_init_cb); +} + std::pair> jl_get_llvm_target(bool imaging, uint32_t &flags) { ensure_jit_target(imaging); diff --git a/src/processor_x86.cpp b/src/processor_x86.cpp index 77ee5afaf5e853..b73838a55777e6 100644 --- a/src/processor_x86.cpp +++ b/src/processor_x86.cpp @@ -878,6 +878,19 @@ static uint32_t sysimg_init_cb(const void *id) return match.best_idx; } +static uint32_t pkgimg_init_cb(const void *id) +{ + TargetData target = jit_targets.front(); + auto pkgimg = deserialize_target_data((const uint8_t*)id); + for (auto &t: pkgimg) { + if (auto nname = normalize_cpu_name(t.name)) { + t.name = nname; + } + } + auto match = match_sysimg_targets(pkgimg, target, max_vector_size); + return match.best_idx; +} + static void ensure_jit_target(bool imaging) { auto &cmdline = get_cmdline_targets(); @@ -1018,6 +1031,15 @@ jl_sysimg_fptrs_t jl_init_processor_sysimg(void *hdl) return parse_sysimg(hdl, sysimg_init_cb); } +jl_sysimg_fptrs_t jl_init_processor_pkgimg(void *hdl) +{ + if (jit_targets.empty()) + jl_error("JIT targets not initialized"); + if (jit_targets.size() > 1) + jl_error("Expected only one JIT target"); + return parse_sysimg(hdl, pkgimg_init_cb); +} + extern "C" JL_DLLEXPORT std::pair> jl_get_llvm_target(bool imaging, uint32_t &flags) { ensure_jit_target(imaging); diff --git a/src/rtutils.c b/src/rtutils.c index 497b348f871d54..f34303b9aeea53 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -708,6 +708,12 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt n += jl_static_show_x(out, (jl_value_t*)vt, depth); n += jl_printf(out, ">"); } + else if (vt == (jl_datatype_t*)jl_buff_tag) { + n += jl_printf(out, "", (void*)v); + } + else if (vt == (jl_datatype_t*)(uintptr_t)(0xbabababababababaull & ~15)) { + n += jl_printf(out, "", (void*)v); + } // These need to be special cased because they // exist only by pointer identity in early startup else if (v == (jl_value_t*)jl_simplevector_type) { diff --git a/src/staticdata.c b/src/staticdata.c index ff958b0d3c30f2..e1f0f86aa68fc3 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -4,33 +4,24 @@ /* saving and restoring system images - This performs serialization and deserialization of in-memory data. The dump.c file is similar, but has less complete coverage: - dump.c has no knowledge of native code (and simply discards it), whereas this supports native code caching in .o files. - Duplication is avoided by elevating the .o-serialized versions of global variables and native-compiled functions to become - the authoritative source for such entities in the system image, with references to these objects appropriately inserted into - the (de)serialized version of Julia's internal data. This makes deserialization simple and fast: we only need to deal with - pointer relocation, registering with the garbage collector, and making note of special internal types. During serialization, - we also need to pay special attention to things like builtin functions, C-implemented types (those in jltypes.c), the metadata - for documentation, optimal layouts, integration with native system image generation, and preparing other preprocessing - directives. - - dump.c has capabilities missing from this serializer, most notably the ability to handle external references. This is not needed - for system images as they are self-contained. However, it would be needed to support incremental compilation of packages. + This performs serialization and deserialization of system and package images. It creates and saves a compact binary + blob, making deserialization "simple" and fast: we "only" need to deal with uniquing, pointer relocation, + method root insertion, registering with the garbage collector, making note of special internal types, and + backedges/invalidation. Special objects include things like builtin functions, C-implemented types (those in jltypes.c), + the metadata for documentation, optimal layouts, integration with native system image generation, and preparing other + preprocessing directives. During serialization, the flow has several steps: - - step 1 inserts relevant items into `backref_table`, an `obj` => `id::Int` mapping. `id` is assigned by - order of insertion. This is effectively a recursive traversal, singling out items like pointers and symbols - that need restoration when the system image is loaded. This stage is implemented by `jl_serialize_value` - and its callees; while it would be simplest to use recursion, this risks stack overflow, so recursion is mimicked + - step 1 inserts relevant items into `serialization_order`, an `obj` => `id::Int` mapping. `id` is assigned by + order of insertion. This stage is implemented by `jl_queue_for_serialization` and its callees; + while it would be simplest to use recursion, this risks stack overflow, so recursion is mimicked using a work-queue managed by `jl_serialize_reachable`. - It's worth emphasizing that despite the name `jl_serialize_value`, the only goal of this stage is to - insert objects into `backref_table`. The entire system gets inserted, either directly or indirectly via - fields of other objects. Objects requiring pointer relocation or gc registration must be inserted directly. - In later stages, such objects get referenced by their `id`. + It's worth emphasizing that the only goal of this stage is to insert objects into `serialization_order`. + In later stages, such objects get written in order of `id`. - - step 2 (the biggest of four steps) takes all items in `backref_table` and actually serializes them ordered + - step 2 (the biggest of four steps) takes all items in `serialization_order` and actually serializes them ordered by `id`. The system is serialized into several distinct streams (see `jl_serializer_state`), a "main stream" (the `s` field) as well as parallel streams for writing specific categories of additional internal data (e.g., global data invisible to codegen, as well as deserialization "touch-up" tables, see below). These different streams @@ -47,14 +38,36 @@ one of the corresponding categorical list, then `index = t << RELOC_TAG_OFFSET + i`. The simplest source for the details of this encoding can be found in the pair of functions `get_reloc_for_item` and `get_item_for_reloc`. + `uniquing` also holds the serialized location of external DataTypes, MethodInstances, and singletons + in the serialized blob (i.e., new-at-the-time-of-serialization specializations). + Most of step 2 is handled by `jl_write_values`, followed by special handling of the dedicated parallel streams. - step 3 combines the different sections (fields of `jl_serializer_state`) into one - - step 4 writes the values of the hard-coded tagged items and `reinit_list`/`ccallable_list` - -The tables written to the serializer stream make deserialization fairly straightforward. Much of the "real work" is -done by `get_item_for_reloc`. + - step 4 writes the values of the hard-coded tagged items and `ccallable_list` + +Much of the "real work" during deserialization is done by `get_item_for_reloc`. But a few items require specific +attention: +- uniquing: during deserialization, the target item (an "external" type or MethodInstance) must be checked against + the running system to see whether such an object already exists (i.e., whether some other previously-loaded package + or workload has created such types/MethodInstances previously) or whether it needs to be created de-novo. + In either case, all references at `location` must be updated to the one in the running system. + `new_dt_objs` is a hash set of newly allocated datatype-reachable objects +- method root insertion: when new specializations generate new roots, these roots must be inserted into + method root tables +- backedges & invalidation: external edges have to be checked against the running system and any invalidations executed. + +Encoding of a pointer: +- in the location of the pointer, we initially write zero padding +- for both relocs_list and gctags_list, we write loc/backrefid (for gctags_list this is handled by the caller of write_gctaggedfield, + for relocs_list it's handled by write_pointerfield) +- when writing to disk, both call get_reloc_for_item, and its return value (subject to modification by gc bits) + ends up being written into the data stream (s->s), and the data stream's position written to s->relocs + +External links: +- location holds the offset +- loc/0 in relocs_list */ #include @@ -75,6 +88,8 @@ done by `get_item_for_reloc`. #include "valgrind.h" #include "julia_assert.h" +#include "staticdata_utils.c" + #ifdef __cplusplus extern "C" { #endif @@ -272,23 +287,27 @@ static uintptr_t nsym_tag; // array of definitions for the predefined tagged object types // (reverse of symbol_table) static arraylist_t deser_sym; - -// table of all objects that are serialized -static htable_t backref_table; -static int backref_table_numel; -static arraylist_t layout_table; // cache of `position(s)` for each `id` in `backref_table` +// Predefined tags that do not have special handling in `externally_linked` +static htable_t external_objects; + +static htable_t serialization_order; // to break cycles, mark all objects that are serialized +static htable_t unique_ready; // as we serialize types, we need to know if all reachable objects are also already serialized. This tracks whether `immediate` has been set for all of them. +static htable_t nullptrs; +static htable_t bindings; // because they are not first-class objects +// FIFO queue for objects to be serialized. Anything requiring fixup upon deserialization +// must be "toplevel" in this queue. For types, parameters and field types must appear +// before the "wrapper" type so they can be properly recached against the running system. +static arraylist_t serialization_queue; +static arraylist_t layout_table; // cache of `position(s)` for each `id` in `serialization_order` static arraylist_t object_worklist; // used to mimic recursion by jl_serialize_reachable -// Both `reinit_list` and `ccallable_list` are lists of (size_t pos, code) entries -// for the serializer to mark values in need of rework during deserialization -// codes: -// 1: typename (reinit_list) -// 2: module (reinit_list) -// 3: method (ccallable_list) -static arraylist_t reinit_list; - -// @ccallable entry points to install -static arraylist_t ccallable_list; +// Permanent list of void* (begin, end+1) pairs of system/package images we've loaded previously +// togther with their module build_ids (used for external linkage) +// jl_linkage_blobs.items[2i:2i+1] correspond to jl_build_ids[i] (0-offset indexing) +// TODO: Keep this sorted so that we can use binary-search +arraylist_t jl_linkage_blobs; +arraylist_t jl_image_relocs; +jl_array_t *jl_build_ids JL_GLOBALLY_ROOTED = NULL; // hash of definitions for predefined function pointers static htable_t fptr_to_id; @@ -297,7 +316,12 @@ void *native_functions; // opaque jl_native_code_desc_t blob used for fetching // table of struct field addresses to rewrite during saving static htable_t field_replace; -static htable_t layout_cache; +typedef struct { + uint64_t base; + uintptr_t *gvars_base; + int32_t *gvars_offsets; + jl_sysimg_fptrs_t fptrs; +} jl_image_t; // array of definitions for the predefined function pointers // (reverse of fptr_to_id) @@ -326,26 +350,42 @@ typedef struct { ios_t *fptr_record; // serialized array mapping fptrid => spos arraylist_t relocs_list; // a list of (location, target) pairs, see description at top arraylist_t gctags_list; // " + arraylist_t uniquing_types; // a list of locations that reference types that must be de-duplicated + arraylist_t uniquing_objs; // a list of locations that reference non-types that must be de-duplicated + arraylist_t fixup_types; // a list of locations of types requiring (re)caching + arraylist_t fixup_objs; // a list of locations of objects requiring (re)caching + arraylist_t ccallable_list; // @ccallable entry points to install + // record of build_ids for all external linkages, in order of serialization for the current sysimg/pkgimg + // conceptually, the base pointer for the jth externally-linked item is determined from + // i = findfirst(==(link_ids[j]), jl_build_ids) + // blob_base = jl_linkage_blobs.items[2i] # 0-offset indexing + // We need separate lists since they are intermingled at creation but split when written. + jl_array_t *link_ids_relocs; + jl_array_t *link_ids_gctags; + jl_array_t *link_ids_gvars; jl_ptls_t ptls; + htable_t callers_with_edges; + jl_image_t *image; + int8_t incremental; } jl_serializer_state; static jl_value_t *jl_idtable_type = NULL; static jl_typename_t *jl_idtable_typename = NULL; static jl_value_t *jl_bigint_type = NULL; static int gmp_limb_size = 0; - static jl_sym_t *jl_docmeta_sym = NULL; // Tags of category `t` are located at offsets `t << RELOC_TAG_OFFSET` // Consequently there is room for 2^RELOC_TAG_OFFSET pointers, etc enum RefTags { - DataRef, // mutable data - ConstDataRef, // constant data (e.g., layouts) - TagRef, // items serialized via their tags - SymbolRef, // symbols - BindingRef, // module bindings - FunctionRef, // generic functions - BuiltinFunctionRef // builtin functions + DataRef, // mutable data + ConstDataRef, // constant data (e.g., layouts) + TagRef, // items serialized via their tags + SymbolRef, // symbols + BindingRef, // module bindings + FunctionRef, // generic functions + BuiltinFunctionRef, // builtin functions + ExternalLinkage // items defined externally (used when serializing packages) }; // calling conventions for internal entry points. @@ -384,17 +424,29 @@ static void write_reloc_t(ios_t *s, uintptr_t reloc_id) JL_NOTSAFEPOINT } } -// --- Static Compile --- +static int jl_is_binding(uintptr_t v) JL_NOTSAFEPOINT +{ + return jl_typeis(v, (jl_datatype_t*)jl_buff_tag); +} + +// Reporting to PkgCacheInspector +typedef struct { + size_t sysdata; + size_t isbitsdata; + size_t symboldata; + size_t tagslist; + size_t reloclist; + size_t gvarlist; + size_t fptrlist; +} pkgcachesizes; +// --- Static Compile --- static void *jl_sysimg_handle = NULL; -static uint64_t sysimage_base = 0; -static uintptr_t *sysimg_gvars_base = NULL; -static const int32_t *sysimg_gvars_offsets = NULL; -static jl_sysimg_fptrs_t sysimg_fptrs; +static jl_image_t sysimage; -static inline uintptr_t *sysimg_gvars(uintptr_t *base, size_t idx) +static inline uintptr_t *sysimg_gvars(uintptr_t *base, int32_t *offsets, size_t idx) { - return base + sysimg_gvars_offsets[idx] / sizeof(base[0]); + return base + offsets[idx] / sizeof(base[0]); } JL_DLLEXPORT int jl_running_on_valgrind(void) @@ -407,10 +459,10 @@ static void jl_load_sysimg_so(void) int imaging_mode = jl_generating_output() && !jl_options.incremental; // in --build mode only use sysimg data, not precompiled native code if (!imaging_mode && jl_options.use_sysimage_native_code==JL_OPTIONS_USE_SYSIMAGE_NATIVE_CODE_YES) { - jl_dlsym(jl_sysimg_handle, "jl_sysimg_gvars_base", (void **)&sysimg_gvars_base, 1); - jl_dlsym(jl_sysimg_handle, "jl_sysimg_gvars_offsets", (void **)&sysimg_gvars_offsets, 1); - sysimg_gvars_offsets += 1; - assert(sysimg_fptrs.base); + jl_dlsym(jl_sysimg_handle, "jl_sysimg_gvars_base", (void **)&sysimage.gvars_base, 1); + jl_dlsym(jl_sysimg_handle, "jl_sysimg_gvars_offsets", (void **)&sysimage.gvars_offsets, 1); + sysimage.gvars_offsets += 1; + assert(sysimage.fptrs.base); void *pgcstack_func_slot; jl_dlsym(jl_sysimg_handle, "jl_pgcstack_func_slot", &pgcstack_func_slot, 1); @@ -423,19 +475,19 @@ static void jl_load_sysimg_so(void) *tls_offset_idx = (uintptr_t)(jl_tls_offset == -1 ? 0 : jl_tls_offset); #ifdef _OS_WINDOWS_ - sysimage_base = (intptr_t)jl_sysimg_handle; + sysimage.base = (intptr_t)jl_sysimg_handle; #else Dl_info dlinfo; - if (dladdr((void*)sysimg_gvars_base, &dlinfo) != 0) { - sysimage_base = (intptr_t)dlinfo.dli_fbase; + if (dladdr((void*)sysimage.gvars_base, &dlinfo) != 0) { + sysimage.base = (intptr_t)dlinfo.dli_fbase; } else { - sysimage_base = 0; + sysimage.base = 0; } #endif } else { - memset(&sysimg_fptrs, 0, sizeof(sysimg_fptrs)); + memset(&sysimage.fptrs, 0, sizeof(sysimage.fptrs)); } const char *sysimg_data; jl_dlsym(jl_sysimg_handle, "jl_system_image_data", (void **)&sysimg_data, 1); @@ -447,6 +499,94 @@ static void jl_load_sysimg_so(void) // --- serializer --- +#define NBOX_C 1024 + +static int jl_needs_serialization(jl_serializer_state *s, jl_value_t *v) +{ + // ignore items that are given a special relocation representation + if (s->incremental && jl_object_in_image(v)) + return 0; + + if (v == NULL || jl_is_symbol(v) || v == jl_nothing) { + return 0; + } + else if (jl_typeis(v, jl_int64_type)) { + int64_t i64 = *(int64_t*)v + NBOX_C / 2; + if ((uint64_t)i64 < NBOX_C) + return 0; + } + else if (jl_typeis(v, jl_int32_type)) { + int32_t i32 = *(int32_t*)v + NBOX_C / 2; + if ((uint32_t)i32 < NBOX_C) + return 0; + } + else if (jl_typeis(v, jl_uint8_type)) { + return 0; + } + else if (jl_typeis(v, jl_task_type)) { + return 0; + } + + return 1; +} + + +static int caching_tag(jl_value_t *v) JL_NOTSAFEPOINT +{ + if (jl_is_method_instance(v)) { + jl_method_instance_t *mi = (jl_method_instance_t*)v; + jl_value_t *m = mi->def.value; + if (jl_is_method(m) && jl_object_in_image(m)) + return 1 + type_in_worklist(mi->specTypes); + } + if (jl_is_datatype(v)) { + jl_datatype_t *dt = (jl_datatype_t*)v; + if (jl_is_tuple_type(dt) ? !dt->isconcretetype : dt->hasfreetypevars) + return 0; // aka !is_cacheable from jltypes.c + if (jl_object_in_image((jl_value_t*)dt->name)) + return 1 + type_in_worklist(v); + } + jl_value_t *dtv = jl_typeof(v); + if (jl_is_datatype_singleton((jl_datatype_t*)dtv)) { + return 1 - type_in_worklist(dtv); // these are already recached in the datatype in the image + } + return 0; +} + +static int needs_recaching(jl_value_t *v) JL_NOTSAFEPOINT +{ + return caching_tag(v) == 2; +} + +static int needs_uniquing(jl_value_t *v) JL_NOTSAFEPOINT +{ + assert(!jl_object_in_image(v)); + return caching_tag(v) == 1; +} + +static void record_field_change(jl_value_t **addr, jl_value_t *newval) JL_NOTSAFEPOINT +{ + ptrhash_put(&field_replace, (void*)addr, newval); +} + +static jl_value_t *get_replaceable_field(jl_value_t **addr, int mutabl) JL_GC_DISABLED +{ + jl_value_t *fld = (jl_value_t*)ptrhash_get(&field_replace, addr); + if (fld == HT_NOTFOUND) { + fld = *addr; + if (mutabl && fld && jl_is_cpointer_type(jl_typeof(fld)) && jl_unbox_voidpointer(fld) != NULL && jl_unbox_voidpointer(fld) != (void*)(uintptr_t)-1) { + void **nullval = ptrhash_bp(&nullptrs, (void*)jl_typeof(fld)); + if (*nullval == HT_NOTFOUND) { + void *C_NULL = NULL; + *nullval = (void*)jl_new_bits(jl_typeof(fld), &C_NULL); + } + fld = (jl_value_t*)*nullval; + } + return fld; + } + return fld; +} + static uintptr_t jl_fptr_id(void *fptr) { void **pbp = ptrhash_bp(&fptr_to_id, fptr); @@ -456,113 +596,126 @@ static uintptr_t jl_fptr_id(void *fptr) return *(uintptr_t*)pbp; } -#define jl_serialize_value(s, v) jl_serialize_value_(s,(jl_value_t*)(v),1) -static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v, int recursive); +// `jl_queue_for_serialization` adds items to `serialization_order` +#define jl_queue_for_serialization(s, v) jl_queue_for_serialization_((s), (jl_value_t*)(v), 1, 0) +static void jl_queue_for_serialization_(jl_serializer_state *s, jl_value_t *v, int recursive, int immediate); -static void jl_serialize_module(jl_serializer_state *s, jl_module_t *m) +static void jl_queue_module_for_serialization(jl_serializer_state *s, jl_module_t *m) { - jl_serialize_value(s, m->name); - jl_serialize_value(s, m->parent); + jl_queue_for_serialization(s, m->name); + jl_queue_for_serialization(s, m->parent); size_t i; void **table = m->bindings.table; for (i = 0; i < m->bindings.size; i += 2) { if (table[i+1] != HT_NOTFOUND) { - jl_serialize_value(s, (jl_value_t*)table[i]); + jl_queue_for_serialization(s, (jl_value_t*)table[i]); jl_binding_t *b = (jl_binding_t*)table[i+1]; - jl_serialize_value(s, b->name); + ptrhash_put(&bindings, b, (void*)(uintptr_t)-1); + jl_queue_for_serialization(s, b->name); + jl_value_t *value; if (jl_docmeta_sym && b->name == jl_docmeta_sym && jl_options.strip_metadata) - jl_serialize_value(s, jl_nothing); + value = jl_nothing; else - jl_serialize_value(s, jl_atomic_load_relaxed(&b->value)); - jl_serialize_value(s, jl_atomic_load_relaxed(&b->globalref)); - jl_serialize_value(s, b->owner); - jl_serialize_value(s, jl_atomic_load_relaxed(&b->ty)); + value = get_replaceable_field((jl_value_t**)&b->value, !b->constp); + jl_queue_for_serialization(s, value); + jl_queue_for_serialization(s, jl_atomic_load_relaxed(&b->globalref)); + jl_queue_for_serialization(s, b->owner); + jl_queue_for_serialization(s, jl_atomic_load_relaxed(&b->ty)); } } for (i = 0; i < m->usings.len; i++) { - jl_serialize_value(s, (jl_value_t*)m->usings.items[i]); + jl_queue_for_serialization(s, (jl_value_t*)m->usings.items[i]); } } -static jl_value_t *get_replaceable_field(jl_value_t **addr) +// Anything that requires uniquing or fixing during deserialization needs to be "toplevel" +// in serialization (i.e., have its own entry in `serialization_order`). Consequently, +// objects that act as containers for other potentially-"problematic" objects must add such "children" +// to the queue. +// Most objects use preorder traversal. But things that need uniquing require postorder: +// you want to handle uniquing of `Dict{String,Float64}` before you tackle `Vector{Dict{String,Float64}}`. +// Uniquing is done in `serialization_order`, so the very first mention of such an object must +// be the "source" rather than merely a cross-reference. +static void jl_insert_into_serialization_queue(jl_serializer_state *s, jl_value_t *v, int recursive, int immediate) { - jl_value_t *fld = (jl_value_t*)ptrhash_get(&field_replace, addr); - if (fld == HT_NOTFOUND) - return *addr; - return fld; -} - -#define NBOX_C 1024 - -static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v, int recursive) -{ - // ignore items that are given a special representation - if (v == NULL || jl_is_symbol(v) || v == jl_nothing) { - return; - } - else if (jl_typeis(v, jl_task_type)) { - if (v == (jl_value_t*)s->ptls->root_task) { - jl_serialize_value(s, ((jl_task_t*)v)->tls); - return; + jl_datatype_t *t = (jl_datatype_t*)jl_typeof(v); + jl_queue_for_serialization_(s, (jl_value_t*)t, 1, immediate); + + if (!recursive) + goto done_fields; + + if (s->incremental && jl_is_datatype(v) && immediate) { + jl_datatype_t *dt = (jl_datatype_t*)v; + // ensure super is queued (though possibly not yet handled, since it may have cycles) + jl_queue_for_serialization_(s, (jl_value_t*)dt->super, 1, 1); + // ensure all type parameters are recached + jl_queue_for_serialization_(s, (jl_value_t*)dt->parameters, 1, 1); + jl_value_t *singleton = dt->instance; + if (singleton && needs_uniquing(singleton)) { + assert(jl_needs_serialization(s, singleton)); // should be true, since we visited dt + // do not visit dt->instance for our template object as it leads to unwanted cycles here + // (it may get serialized from elsewhere though) + record_field_change(&dt->instance, jl_nothing); + } + immediate = 0; // do not handle remaining fields immediately (just field types remains) + } + if (s->incremental && jl_is_method_instance(v)) { + if (needs_uniquing(v)) { + // we only need 3 specific fields of this (the rest are not used) + jl_method_instance_t *mi = (jl_method_instance_t*)v; + jl_queue_for_serialization(s, mi->def.value); + jl_queue_for_serialization(s, mi->specTypes); + jl_queue_for_serialization(s, (jl_value_t*)mi->sparam_vals); + recursive = 0; + goto done_fields; + } + else if (needs_recaching(v)) { + // we only need 3 specific fields of this (the rest are restored afterward, if valid) + jl_method_instance_t *mi = (jl_method_instance_t*)v; + record_field_change((jl_value_t**)&mi->uninferred, NULL); + record_field_change((jl_value_t**)&mi->backedges, NULL); + record_field_change((jl_value_t**)&mi->callbacks, NULL); + record_field_change((jl_value_t**)&mi->cache, NULL); } } - else if (jl_typeis(v, jl_int64_type)) { - int64_t i64 = *(int64_t*)v + NBOX_C / 2; - if ((uint64_t)i64 < NBOX_C) - return; - } - else if (jl_typeis(v, jl_int32_type)) { - int32_t i32 = *(int32_t*)v + NBOX_C / 2; - if ((uint32_t)i32 < NBOX_C) - return; - } - else if (jl_typeis(v, jl_uint8_type)) { - return; - } - arraylist_push(&object_worklist, (void*)((uintptr_t)v | recursive)); -} - -static void jl_serialize_value__(jl_serializer_state *s, jl_value_t *v, int recursive) -{ - void **bp = ptrhash_bp(&backref_table, v); - if (*bp != HT_NOTFOUND) { - return; + if (jl_is_typename(v)) { + jl_typename_t *tn = (jl_typename_t*)v; + // don't recurse into several fields (yet) + jl_queue_for_serialization_(s, (jl_value_t*)tn->cache, 0, 1); + jl_queue_for_serialization_(s, (jl_value_t*)tn->linearcache, 0, 1); + if (s->incremental) { + assert(!jl_object_in_image((jl_value_t*)tn->module)); + assert(!jl_object_in_image((jl_value_t*)tn->wrapper)); + } } - size_t item = ++backref_table_numel; - assert(item < ((uintptr_t)1 << RELOC_TAG_OFFSET) && "too many items to serialize"); - char *pos = (char*)HT_NOTFOUND + item; - *bp = (void*)pos; - - // some values have special representations - jl_datatype_t *t = (jl_datatype_t*)jl_typeof(v); - jl_serialize_value(s, t); + if (immediate) // must be things that can be recursively handled, and valid as type parameters + assert(jl_is_immutable(t) || jl_is_typevar(v) || jl_is_symbol(v) || jl_is_svec(v)); - if (t->layout->npointers == 0) { - // skip it + const jl_datatype_layout_t *layout = t->layout; + if (layout->npointers == 0) { + // bitstypes do not require recursion } else if (jl_is_svec(v)) { - if (!recursive) - return; size_t i, l = jl_svec_len(v); jl_value_t **data = jl_svec_data(v); for (i = 0; i < l; i++) { - jl_serialize_value(s, data[i]); + jl_queue_for_serialization_(s, data[i], 1, immediate); } } else if (jl_is_array(v)) { jl_array_t *ar = (jl_array_t*)v; - jl_serialize_value(s, jl_typeof(ar)); + const char *data = (const char*)jl_array_data(ar); if (ar->flags.ptrarray) { size_t i, l = jl_array_len(ar); for (i = 0; i < l; i++) { - jl_serialize_value(s, jl_array_ptr_ref(ar, i)); + jl_value_t *fld = get_replaceable_field(&((jl_value_t**)data)[i], 1); + jl_queue_for_serialization_(s, fld, 1, immediate); } } else if (ar->flags.hasptr) { - const char *data = (const char*)jl_array_data(ar); uint16_t elsz = ar->elsize; size_t i, l = jl_array_len(ar); jl_datatype_t *et = (jl_datatype_t*)jl_tparam0(jl_typeof(ar)); @@ -570,46 +723,90 @@ static void jl_serialize_value__(jl_serializer_state *s, jl_value_t *v, int recu for (i = 0; i < l; i++) { for (j = 0; j < np; j++) { uint32_t ptr = jl_ptr_offset(et, j); - jl_value_t *fld = ((jl_value_t**)data)[ptr]; - JL_GC_PROMISE_ROOTED(fld); - jl_serialize_value(s, fld); + jl_value_t *fld = get_replaceable_field(&((jl_value_t**)data)[ptr], 1); + jl_queue_for_serialization_(s, fld, 1, immediate); } data += elsz; } } } else if (jl_typeis(v, jl_module_type)) { - jl_serialize_module(s, (jl_module_t*)v); + jl_queue_module_for_serialization(s, (jl_module_t*)v); } - else if (jl_is_typename(v)) { - jl_typename_t *tn = (jl_typename_t*)v; - jl_serialize_value(s, tn->name); - jl_serialize_value(s, tn->module); - jl_serialize_value(s, tn->names); - jl_serialize_value(s, tn->wrapper); - jl_serialize_value(s, tn->Typeofwrapper); - jl_serialize_value_(s, (jl_value_t*)tn->cache, 0); - jl_serialize_value_(s, (jl_value_t*)tn->linearcache, 0); - jl_serialize_value(s, tn->mt); - jl_serialize_value(s, tn->partial); - } - else if (t->layout->nfields > 0) { - if (jl_typeis(v, jl_globalref_type)) { - // Don't save the cached binding reference in staticdata - ((jl_globalref_t*)v)->bnd_cache = NULL; - } + else if (layout->nfields > 0) { char *data = (char*)jl_data_ptr(v); - size_t i, np = t->layout->npointers; + size_t i, np = layout->npointers; for (i = 0; i < np; i++) { uint32_t ptr = jl_ptr_offset(t, i); - jl_value_t *fld = get_replaceable_field(&((jl_value_t**)data)[ptr]); - jl_serialize_value(s, fld); + jl_value_t *fld = get_replaceable_field(&((jl_value_t**)data)[ptr], t->name->mutabl); + jl_queue_for_serialization_(s, fld, 1, immediate); } } + +done_fields: ; + + // We've encountered an item we need to cache + void **bp = ptrhash_bp(&serialization_order, v); + assert(*bp != (void*)(uintptr_t)-1); + if (s->incremental) { + void **bp2 = ptrhash_bp(&unique_ready, v); + if (*bp2 == HT_NOTFOUND) + assert(*bp == (void*)(uintptr_t)-2); + else if (*bp != (void*)(uintptr_t)-2) + return; + } + else { + assert(*bp == (void*)(uintptr_t)-2); + } + arraylist_push(&serialization_queue, (void*) v); + size_t idx = serialization_queue.len - 1; + assert(serialization_queue.len < ((uintptr_t)1 << RELOC_TAG_OFFSET) && "too many items to serialize"); + + *bp = (void*)((char*)HT_NOTFOUND + 1 + idx); +} + +static void jl_queue_for_serialization_(jl_serializer_state *s, jl_value_t *v, int recursive, int immediate) +{ + if (!jl_needs_serialization(s, v)) + return; + + jl_value_t *t = jl_typeof(v); + // Items that require postorder traversal must visit their children prior to insertion into + // the worklist/serialization_order (and also before their first use) + if (s->incremental && !immediate) { + if (jl_is_datatype(t) && needs_uniquing(v)) + immediate = 1; + if (jl_is_datatype_singleton((jl_datatype_t*)t) && needs_uniquing(v)) + immediate = 1; + } + + void **bp = ptrhash_bp(&serialization_order, v); + if (*bp == HT_NOTFOUND) { + *bp = (void*)(uintptr_t)(immediate ? -2 : -1); + } + else { + if (!s->incremental || !immediate || !recursive) + return; + void **bp2 = ptrhash_bp(&unique_ready, v); + if (*bp2 == HT_NOTFOUND) + *bp2 = v; // now is unique_ready + else { + assert(*bp != (void*)(uintptr_t)-1); + return; // already was unique_ready + } + assert(*bp != (void*)(uintptr_t)-2); // should be unique_ready then + if (*bp == (void*)(uintptr_t)-1) + *bp = (void*)(uintptr_t)-2; // now immediate + } + + if (immediate) + jl_insert_into_serialization_queue(s, v, recursive, immediate); + else + arraylist_push(&object_worklist, (void*)v); } // Do a pre-order traversal of the to-serialize worklist, in the identical order -// to the calls to jl_serialize_value would occur in a purely recursive +// to the calls to jl_queue_for_serialization would occur in a purely recursive // implementation, but without potentially running out of stack. static void jl_serialize_reachable(jl_serializer_state *s) { @@ -624,10 +821,16 @@ static void jl_serialize_reachable(jl_serializer_state *s) object_worklist.items[j] = tmp; } prevlen = --object_worklist.len; - uintptr_t v = (uintptr_t)object_worklist.items[prevlen]; - int recursive = v & 1; - v &= ~(uintptr_t)1; // untag v - jl_serialize_value__(s, (jl_value_t*)v, recursive); + jl_value_t *v = (jl_value_t*)object_worklist.items[prevlen]; + void **bp = ptrhash_bp(&serialization_order, (void*)v); + assert(*bp != HT_NOTFOUND && *bp != (void*)(uintptr_t)-2); + if (*bp == (void*)(uintptr_t)-1) { // might have been eagerly handled for post-order while in the lazy pre-order queue + *bp = (void*)(uintptr_t)-2; + jl_insert_into_serialization_queue(s, v, 1, 0); + } + else { + assert(s->incremental); + } } } @@ -641,19 +844,6 @@ static void ios_ensureroom(ios_t *s, size_t newsize) JL_NOTSAFEPOINT } } -// Maybe encode a global variable. `gid` is the LLVM index, 0 if the object is not serialized -// in the generated code (and thus not a gvar from that standpoint, maybe only stored in the internal-data sysimg). -// `reloc_id` is the RefTags-encoded `target`. -static void record_gvar(jl_serializer_state *s, int gid, uintptr_t reloc_id) JL_NOTSAFEPOINT -{ - if (gid == 0) - return; - ios_ensureroom(s->gvar_record, gid * sizeof(reloc_t)); - ios_seek(s->gvar_record, (gid - 1) * sizeof(reloc_t)); - write_reloc_t(s->gvar_record, reloc_id); -} - - static void write_padding(ios_t *s, size_t nb) JL_NOTSAFEPOINT { static const char zeros[16] = {0}; @@ -672,11 +862,34 @@ static void write_pointer(ios_t *s) JL_NOTSAFEPOINT write_uint(s, 0); } -// Return the integer `id` for `v`. Generically this is looked up in `backref_table`, +// Records the buildid holding `v` and returns the tagged offset within the corresponding image +static uintptr_t add_external_linkage(jl_serializer_state *s, jl_value_t *v, jl_array_t *link_ids) { + size_t i = external_blob_index(v); + if (i < n_linkage_blobs()) { + assert(link_ids && jl_is_array(link_ids)); + assert(jl_build_ids && jl_is_array(jl_build_ids)); + uint64_t *build_id_data = (uint64_t*)jl_array_data(jl_build_ids); + // We found the sysimg/pkg that this item links against + // Store the image key in `link_ids` + jl_array_grow_end(link_ids, 1); + uint64_t *link_id_data = (uint64_t*)jl_array_data(link_ids); + link_id_data[jl_array_len(link_ids)-1] = build_id_data[i]; + // Compute the relocation code + size_t offset = (uintptr_t)v - (uintptr_t)jl_linkage_blobs.items[2*i]; + offset /= sizeof(void*); + assert(offset < ((uintptr_t)1 << RELOC_TAG_OFFSET) && "offset to external image too large"); + // jl_printf(JL_STDOUT, "External link %ld against blob %d with key %ld at position 0x%lx with offset 0x%lx to \n", jl_array_len(link_ids), i, build_id_data[i>>1], ios_pos(s->s), offset); + // jl_(v); + return ((uintptr_t)ExternalLinkage << RELOC_TAG_OFFSET) + offset; + } + return 0; +} + +// Return the integer `id` for `v`. Generically this is looked up in `serialization_order`, // but symbols, small integers, and a couple of special items (`nothing` and the root Task) // have special handling. -#define backref_id(s, v) _backref_id(s, (jl_value_t*)(v)) -static uintptr_t _backref_id(jl_serializer_state *s, jl_value_t *v) JL_NOTSAFEPOINT +#define backref_id(s, v, link_ids) _backref_id(s, (jl_value_t*)(v), link_ids) +static uintptr_t _backref_id(jl_serializer_state *s, jl_value_t *v, jl_array_t *link_ids) JL_NOTSAFEPOINT { assert(v != NULL && "cannot get backref to NULL object"); void *idx = HT_NOTFOUND; @@ -713,21 +926,44 @@ static uintptr_t _backref_id(jl_serializer_state *s, jl_value_t *v) JL_NOTSAFEPO uint8_t u8 = *(uint8_t*)v; return ((uintptr_t)TagRef << RELOC_TAG_OFFSET) + u8 + 2 + NBOX_C + NBOX_C; } + if (s->incremental && jl_object_in_image(v)) { + assert(link_ids); + uintptr_t item = add_external_linkage(s, v, link_ids); + assert(item && "no external linkage identified"); + return item; + } if (idx == HT_NOTFOUND) { - idx = ptrhash_get(&backref_table, v); - assert(idx != HT_NOTFOUND && "object missed during jl_serialize_value pass"); + idx = ptrhash_get(&serialization_order, v); + if (idx == HT_NOTFOUND) { + jl_(jl_typeof(v)); + jl_(v); + } + assert(idx != HT_NOTFOUND && "object missed during jl_queue_for_serialization pass"); + assert(idx != (void*)(uintptr_t)-1 && "object missed during jl_insert_into_serialization_queue pass"); + assert(idx != (void*)(uintptr_t)-2 && "object missed during jl_insert_into_serialization_queue pass"); } return (char*)idx - 1 - (char*)HT_NOTFOUND; } +static void record_uniquing(jl_serializer_state *s, jl_value_t *fld, uintptr_t offset) JL_NOTSAFEPOINT +{ + if (s->incremental && jl_needs_serialization(s, fld) && needs_uniquing(fld)) { + if (jl_is_datatype(fld) || jl_is_datatype_singleton((jl_datatype_t*)jl_typeof(fld))) + arraylist_push(&s->uniquing_types, (void*)(uintptr_t)offset); + else + arraylist_push(&s->uniquing_objs, (void*)(uintptr_t)offset); + } +} + // Save blank space in stream `s` for a pointer `fld`, storing both location and target // in `relocs_list`. static void write_pointerfield(jl_serializer_state *s, jl_value_t *fld) JL_NOTSAFEPOINT { if (fld != NULL) { arraylist_push(&s->relocs_list, (void*)(uintptr_t)ios_pos(s->s)); - arraylist_push(&s->relocs_list, (void*)backref_id(s, fld)); + arraylist_push(&s->relocs_list, (void*)backref_id(s, fld, s->link_ids_relocs)); + record_uniquing(s, fld, ios_pos(s->s)); } write_pointer(s->s); } @@ -736,26 +972,29 @@ static void write_pointerfield(jl_serializer_state *s, jl_value_t *fld) JL_NOTSA // in `gctags_list`. static void write_gctaggedfield(jl_serializer_state *s, uintptr_t ref) JL_NOTSAFEPOINT { + // jl_printf(JL_STDOUT, "gctaggedfield: position %p, value 0x%lx\n", (void*)(uintptr_t)ios_pos(s->s), ref); arraylist_push(&s->gctags_list, (void*)(uintptr_t)ios_pos(s->s)); arraylist_push(&s->gctags_list, (void*)ref); write_pointer(s->s); } // Special handling from `jl_write_values` for modules -static void jl_write_module(jl_serializer_state *s, uintptr_t item, jl_module_t *m) +static void jl_write_module(jl_serializer_state *s, uintptr_t item, jl_module_t *m) JL_GC_DISABLED { size_t reloc_offset = ios_pos(s->s); size_t tot = sizeof(jl_module_t); ios_write(s->s, (char*)m, tot); // raw memory dump of the `jl_module_t` structure + // will need to recreate the binding table for this + arraylist_push(&s->fixup_objs, (void*)reloc_offset); // Handle the fields requiring special attention jl_module_t *newm = (jl_module_t*)&s->s->buf[reloc_offset]; newm->name = NULL; arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_module_t, name))); - arraylist_push(&s->relocs_list, (void*)backref_id(s, m->name)); + arraylist_push(&s->relocs_list, (void*)backref_id(s, m->name, s->link_ids_relocs)); newm->parent = NULL; arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_module_t, parent))); - arraylist_push(&s->relocs_list, (void*)backref_id(s, m->parent)); + arraylist_push(&s->relocs_list, (void*)backref_id(s, m->parent, s->link_ids_relocs)); newm->primary_world = jl_atomic_load_acquire(&jl_world_counter); // write out the bindings table as a list @@ -772,13 +1011,14 @@ static void jl_write_module(jl_serializer_state *s, uintptr_t item, jl_module_t write_gctaggedfield(s, (uintptr_t)BindingRef << RELOC_TAG_OFFSET); tot += sizeof(void*); size_t binding_reloc_offset = ios_pos(s->s); - record_gvar(s, jl_get_llvm_gv(native_functions, (jl_value_t*)b), - ((uintptr_t)DataRef << RELOC_TAG_OFFSET) + binding_reloc_offset); + ptrhash_put(&bindings, b, (void*)(((uintptr_t)DataRef << RELOC_TAG_OFFSET) + binding_reloc_offset)); write_pointerfield(s, (jl_value_t*)b->name); + jl_value_t *value; if (jl_docmeta_sym && b->name == jl_docmeta_sym && jl_options.strip_metadata) - write_pointerfield(s, jl_nothing); + value = jl_nothing; else - write_pointerfield(s, jl_atomic_load_relaxed(&b->value)); + value = get_replaceable_field((jl_value_t**)&b->value, !b->constp); + write_pointerfield(s, value); write_pointerfield(s, jl_atomic_load_relaxed(&b->globalref)); write_pointerfield(s, (jl_value_t*)b->owner); write_pointerfield(s, jl_atomic_load_relaxed(&b->ty)); @@ -803,7 +1043,7 @@ static void jl_write_module(jl_serializer_state *s, uintptr_t item, jl_module_t size_t i; for (i = 0; i < m->usings.len; i++) { arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_module_t, usings._space[i]))); - arraylist_push(&s->relocs_list, (void*)backref_id(s, m->usings._space[i])); + arraylist_push(&s->relocs_list, (void*)backref_id(s, m->usings._space[i], s->link_ids_relocs)); } } else { @@ -822,92 +1062,74 @@ static void jl_write_module(jl_serializer_state *s, uintptr_t item, jl_module_t } } -#if 0 -static size_t jl_sort_size(jl_datatype_t *dt) +static void record_gvars(jl_serializer_state *s, arraylist_t *globals) JL_NOTSAFEPOINT { - if (dt == jl_simplevector_type) - return SIZE_MAX - 5; - if (dt == jl_string_type) - return SIZE_MAX - 4; - if (dt->name == jl_array_typename) - return SIZE_MAX - 3; - if (dt == jl_datatype_type) - return SIZE_MAX - 2; - if (dt == jl_module_type) - return SIZE_MAX - 1; - return jl_datatype_size(dt); -} -#endif - -// Used by `qsort` to order `backref_table` by `id` -static int sysimg_sort_order(const void *pa, const void *pb) -{ - uintptr_t sa = ((uintptr_t*)pa)[1]; - uintptr_t sb = ((uintptr_t*)pb)[1]; - return (sa > sb ? 1 : (sa < sb ? -1 : 0)); -#if 0 - jl_value_t *a = *(jl_value_t**)pa; - jl_datatype_t *tya = (jl_datatype_t*)jl_typeof(a); - size_t sa = jl_sort_size(tya); - jl_value_t *b = *(jl_value_t**)pb; - jl_datatype_t *tyb = (jl_datatype_t*)jl_typeof(b); - size_t sb = jl_sort_size(tyb); - if (sa == sb) { - sa = tya->uid; - sb = tyb->uid; - } - return (sa > sb ? 1 : (sa < sb ? -1 : 0)); -#endif + for (size_t i = 0; i < globals->len; i++) { + void *g = globals->items[i]; + if (jl_is_binding((uintptr_t)g)) { + if (!ptrhash_has(&bindings, g)) { + // need to deal with foreign bindings here too + assert(s->incremental); + jl_binding_t *b = (jl_binding_t*)g; + jl_value_t *gr = jl_module_globalref(b->owner, b->name); + jl_queue_for_serialization(s, gr); + } + continue; + } + assert(!ptrhash_has(&bindings, g)); + jl_queue_for_serialization(s, g); + } } jl_value_t *jl_find_ptr = NULL; -// The main function for serializing all the items queued in `backref_table` -static void jl_write_values(jl_serializer_state *s) +// The main function for serializing all the items queued in `serialization_order` +// (They are also stored in `serialization_queue` which is order-preserving, unlike the hash table used +// for `serialization_order`). +static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED { - arraylist_t objects_list; - arraylist_new(&objects_list, backref_table_numel * 2); + size_t l = serialization_queue.len; arraylist_new(&layout_table, 0); - arraylist_grow(&layout_table, backref_table_numel); - memset(layout_table.items, 0, backref_table_numel * sizeof(void*)); - - // Order `backref_table` by `id` - size_t i, len = backref_table.size; - void **p = backref_table.table; - for (i = 0; i < len; i += 2) { - char *reloc_id = (char*)p[i + 1]; - if (reloc_id != HT_NOTFOUND) { - jl_value_t *v = (jl_value_t*)p[i]; - uintptr_t item = reloc_id - 1 - (char*)HT_NOTFOUND; - objects_list.items[objects_list.len++] = (void*)v; - objects_list.items[objects_list.len++] = (void*)item; - } - } - assert(backref_table_numel * 2 == objects_list.len); - qsort(objects_list.items, backref_table_numel, sizeof(void*) * 2, sysimg_sort_order); + arraylist_grow(&layout_table, l * 2); + memset(layout_table.items, 0, l * 2 * sizeof(void*)); // Serialize all entries - for (i = 0, len = backref_table_numel * 2; i < len; i += 2) { - jl_value_t *v = (jl_value_t*)objects_list.items[i]; // the object + for (size_t item = 0; item < l; item++) { + jl_value_t *v = (jl_value_t*)serialization_queue.items[item]; // the object JL_GC_PROMISE_ROOTED(v); - uintptr_t item = (uintptr_t)objects_list.items[i + 1]; // the id + assert(!(s->incremental && jl_object_in_image(v))); jl_datatype_t *t = (jl_datatype_t*)jl_typeof(v); assert((t->instance == NULL || t->instance == v) && "detected singleton construction corruption"); // realign stream to expected gc alignment (16 bytes) uintptr_t skip_header_pos = ios_pos(s->s) + sizeof(jl_taggedvalue_t); write_padding(s->s, LLT_ALIGN(skip_header_pos, 16) - skip_header_pos); + // write header - write_gctaggedfield(s, backref_id(s, t)); + if (s->incremental && jl_needs_serialization(s, (jl_value_t*)t) && needs_uniquing((jl_value_t*)t)) + arraylist_push(&s->uniquing_types, (void*)(uintptr_t)(ios_pos(s->s)|1)); + write_gctaggedfield(s, backref_id(s, t, s->link_ids_gctags)); size_t reloc_offset = ios_pos(s->s); assert(item < layout_table.len && layout_table.items[item] == NULL); - layout_table.items[item] = (void*)reloc_offset; // store the inverse mapping of `backref_table` (`id` => object) - record_gvar(s, jl_get_llvm_gv(native_functions, v), ((uintptr_t)DataRef << RELOC_TAG_OFFSET) + reloc_offset); + layout_table.items[item] = (void*)reloc_offset; // store the inverse mapping of `serialization_order` (`id` => object-as-streampos) + + if (s->incremental && needs_uniquing(v)) { + if (jl_is_method_instance(v)) { + jl_method_instance_t *mi = (jl_method_instance_t*)v; + write_pointerfield(s, mi->def.value); + write_pointerfield(s, mi->specTypes); + write_pointerfield(s, (jl_value_t*)mi->sparam_vals); + continue; + } + else if (!jl_is_datatype(v)) { + assert(jl_is_datatype_singleton(t) && "unreachable"); + } + } + else if (s->incremental && needs_recaching(v)) { + arraylist_push(jl_is_datatype(v) ? &s->fixup_types : &s->fixup_objs, (void*)reloc_offset); + } // write data - if (jl_is_cpointer(v)) { - write_pointer(s->s); - } - else if (jl_is_array(v)) { + if (jl_is_array(v)) { // Internal data for types in julia.h with `jl_array_t` field(s) #define JL_ARRAY_ALIGN(jl_value, nbytes) LLT_ALIGN(jl_value, nbytes) jl_array_t *ar = (jl_array_t*)v; @@ -948,10 +1170,15 @@ static void jl_write_values(jl_serializer_state *s) arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_array_t, data))); // relocation location arraylist_push(&s->relocs_list, (void*)(((uintptr_t)ConstDataRef << RELOC_TAG_OFFSET) + data)); // relocation target if (jl_is_cpointer_type(et)) { - // reset Ptr elements to C_NULL + // reset Ptr fields to C_NULL (but keep MAP_FAILED / INVALID_HANDLE) + const intptr_t *data = (const intptr_t*)jl_array_data(ar); size_t i; - for (i = 0; i < alen; i++) - write_pointer(s->const_data); + for (i = 0; i < alen; i++) { + if (data[i] != -1) + write_pointer(s->const_data); + else + ios_write(s->const_data, (char*)&data[i], sizeof(data[i])); + } } else { if (isbitsunion) { @@ -967,11 +1194,11 @@ static void jl_write_values(jl_serializer_state *s) // Pointer eltypes are encoded in the mutable data section size_t data = LLT_ALIGN(ios_pos(s->s), alignment_amt); size_t padding_amt = data - ios_pos(s->s); - write_padding(s->s, padding_amt); headersize += padding_amt; newa->data = (void*)headersize; // relocation offset arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_array_t, data))); // relocation location arraylist_push(&s->relocs_list, (void*)(((uintptr_t)DataRef << RELOC_TAG_OFFSET) + item)); // relocation target + write_padding(s->s, padding_amt); if (ar->flags.hasptr) { // copy all of the data first const char *data = (const char*)jl_array_data(ar); @@ -983,22 +1210,22 @@ static void jl_write_values(jl_serializer_state *s) for (i = 0; i < alen; i++) { for (j = 0; j < np; j++) { size_t offset = i * elsz + jl_ptr_offset(((jl_datatype_t*)et), j) * sizeof(jl_value_t*); - jl_value_t *fld = *(jl_value_t**)&data[offset]; + jl_value_t *fld = get_replaceable_field((jl_value_t**)&data[offset], 1); + size_t fld_pos = reloc_offset + headersize + offset; if (fld != NULL) { - arraylist_push(&s->relocs_list, (void*)(uintptr_t)(reloc_offset + headersize + offset)); // relocation location - arraylist_push(&s->relocs_list, (void*)backref_id(s, fld)); // relocation target - memset(&s->s->buf[reloc_offset + headersize + offset], 0, sizeof(fld)); // relocation offset (none) - } - else { - assert(*(jl_value_t**)&s->s->buf[reloc_offset + headersize + offset] == NULL); + arraylist_push(&s->relocs_list, (void*)(uintptr_t)fld_pos); // relocation location + arraylist_push(&s->relocs_list, (void*)backref_id(s, fld, s->link_ids_relocs)); // relocation target + record_uniquing(s, fld, fld_pos); } + memset(&s->s->buf[fld_pos], 0, sizeof(fld)); // relocation offset (none) } } } else { + jl_value_t **data = (jl_value_t**)jl_array_data(ar); size_t i; for (i = 0; i < alen; i++) { - jl_value_t *e = jl_array_ptr_ref(v, i); + jl_value_t *e = get_replaceable_field(&data[i], 1); write_pointerfield(s, e); } } @@ -1006,19 +1233,16 @@ static void jl_write_values(jl_serializer_state *s) } else if (jl_typeis(v, jl_module_type)) { jl_write_module(s, item, (jl_module_t*)v); - // will need to recreate the binding table for this - arraylist_push(&reinit_list, (void*)item); - arraylist_push(&reinit_list, (void*)2); } else if (jl_typeis(v, jl_task_type)) { jl_error("Task cannot be serialized"); } else if (jl_is_svec(v)) { ios_write(s->s, (char*)v, sizeof(void*)); - size_t i, l = jl_svec_len(v); + size_t ii, l = jl_svec_len(v); assert(l > 0 || (jl_svec_t*)v == jl_emptysvec); - for (i = 0; i < l; i++) { - write_pointerfield(s, jl_svecref(v, i)); + for (ii = 0; ii < l; ii++) { + write_pointerfield(s, jl_svecref(v, ii)); } } else if (jl_is_string(v)) { @@ -1026,6 +1250,8 @@ static void jl_write_values(jl_serializer_state *s) write_uint8(s->s, '\0'); // null-terminated strings for easier C-compatibility } else if (jl_datatype_nfields(t) == 0) { + // The object has no fields, so we just snapshot its byte representation + assert(!t->layout->npointers); assert(t->layout->npointers == 0); ios_write(s->s, (char*)v, jl_datatype_size(t)); } @@ -1058,8 +1284,8 @@ static void jl_write_values(jl_serializer_state *s) write_padding(s->s, offset - tot); tot = offset; size_t fsz = jl_field_size(t, i); - if (t->name->mutabl && jl_is_cpointer_type(jl_field_type(t, i))) { - // reset Ptr fields to C_NULL + if (t->name->mutabl && jl_is_cpointer_type(jl_field_type(t, i)) && *(intptr_t*)slot != -1) { + // reset Ptr fields to C_NULL (but keep MAP_FAILED / INVALID_HANDLE) assert(!jl_field_isptr(t, i)); write_pointer(s->s); } @@ -1072,22 +1298,46 @@ static void jl_write_values(jl_serializer_state *s) size_t np = t->layout->npointers; for (i = 0; i < np; i++) { size_t offset = jl_ptr_offset(t, i) * sizeof(jl_value_t*); - jl_value_t *fld = get_replaceable_field((jl_value_t**)&data[offset]); + jl_value_t *fld = get_replaceable_field((jl_value_t**)&data[offset], t->name->mutabl); + size_t fld_pos = offset + reloc_offset; if (fld != NULL) { - arraylist_push(&s->relocs_list, (void*)(uintptr_t)(offset + reloc_offset)); // relocation location - arraylist_push(&s->relocs_list, (void*)backref_id(s, fld)); // relocation target + arraylist_push(&s->relocs_list, (void*)(uintptr_t)(fld_pos)); // relocation location + arraylist_push(&s->relocs_list, (void*)backref_id(s, fld, s->link_ids_relocs)); // relocation target + record_uniquing(s, fld, fld_pos); } - memset(&s->s->buf[offset + reloc_offset], 0, sizeof(fld)); // relocation offset (none) + memset(&s->s->buf[fld_pos], 0, sizeof(fld)); // relocation offset (none) } // A few objects need additional handling beyond the generic serialization above - if (jl_is_method(v)) { - write_padding(s->s, sizeof(jl_method_t) - tot); - if (((jl_method_t*)v)->ccallable) { - arraylist_push(&ccallable_list, (void*)item); - arraylist_push(&ccallable_list, (void*)3); + + if (s->incremental && jl_typeis(v, jl_typemap_entry_type)) { + jl_typemap_entry_t *newentry = (jl_typemap_entry_t*)&s->s->buf[reloc_offset]; + if (newentry->max_world == ~(size_t)0) { + if (newentry->min_world > 1) { + newentry->min_world = ~(size_t)0; + arraylist_push(&s->fixup_objs, (void*)reloc_offset); + } + } + else { + // garbage newentry - delete it :( + newentry->min_world = 1; + newentry->max_world = 0; } } + else if (jl_is_method(v)) { + write_padding(s->s, sizeof(jl_method_t) - tot); // hidden fields + jl_method_t *m = (jl_method_t*)v; + jl_method_t *newm = (jl_method_t*)&s->s->buf[reloc_offset]; + if (s->incremental) { + if (newm->deleted_world != ~(size_t)0) + newm->deleted_world = 1; + else + arraylist_push(&s->fixup_objs, (void*)reloc_offset); + newm->primary_world = ~(size_t)0; + } + if (m->ccallable) + arraylist_push(&s->ccallable_list, (void*)reloc_offset); + } else if (jl_is_method_instance(v)) { jl_method_instance_t *newmi = (jl_method_instance_t*)&s->s->buf[reloc_offset]; newmi->precompiled = 0; @@ -1097,6 +1347,22 @@ static void jl_write_values(jl_serializer_state *s) jl_code_instance_t *m = (jl_code_instance_t*)v; jl_code_instance_t *newm = (jl_code_instance_t*)&s->s->buf[reloc_offset]; + if (s->incremental) { + arraylist_push(&s->fixup_objs, (void*)reloc_offset); + if (m->min_world > 1) + newm->min_world = ~(size_t)0; // checks that we reprocess this upon deserialization + if (m->max_world != ~(size_t)0) + newm->max_world = 0; + else { + if (m->inferred && ptrhash_has(&s->callers_with_edges, m->def)) + newm->max_world = 1; // sentinel value indicating this will need validation + if (m->min_world > 0 && m->inferred) { + // TODO: also check if this object is part of the codeinst cache + // will check on deserialize if this cache entry is still valid + } + } + } + newm->invoke = NULL; newm->isspecsig = 0; newm->specptr.fptr = NULL; @@ -1157,36 +1423,33 @@ static void jl_write_values(jl_serializer_state *s) arraylist_push(&s->relocs_list, (void*)(((uintptr_t)BuiltinFunctionRef << RELOC_TAG_OFFSET) + builtin_id - 2)); // relocation target } } + else if (jl_is_globalref(v)) { + jl_globalref_t *newg = (jl_globalref_t*)&s->s->buf[reloc_offset]; + // Don't save the cached binding reference in staticdata + // TODO: this should be a relocation pointing to the binding in the new image + newg->bnd_cache = NULL; + if (s->incremental) + arraylist_push(&s->fixup_objs, (void*)reloc_offset); + } else if (jl_is_datatype(v)) { jl_datatype_t *dt = (jl_datatype_t*)v; jl_datatype_t *newdt = (jl_datatype_t*)&s->s->buf[reloc_offset]; - if (dt->layout != NULL) { - newdt->layout = NULL; + if (dt->layout != NULL) { + size_t nf = dt->layout->nfields; + size_t np = dt->layout->npointers; + size_t fieldsize = jl_fielddesc_size(dt->layout->fielddesc_type); char *flddesc = (char*)dt->layout; - void* reloc_from = (void*)(reloc_offset + offsetof(jl_datatype_t, layout)); - void* reloc_to; - - void** bp = ptrhash_bp(&layout_cache, flddesc); - if (*bp == HT_NOTFOUND) { - int64_t streampos = ios_pos(s->const_data); - uintptr_t align = LLT_ALIGN(streampos, sizeof(void*)); - uintptr_t layout = align / sizeof(void*); - *bp = reloc_to = (void*)(((uintptr_t)ConstDataRef << RELOC_TAG_OFFSET) + layout); - - size_t fieldsize = jl_fielddesc_size(dt->layout->fielddesc_type); - size_t layoutsize = sizeof(jl_datatype_layout_t) + dt->layout->nfields * fieldsize; - if (dt->layout->first_ptr != -1) - layoutsize += dt->layout->npointers << dt->layout->fielddesc_type; - write_padding(s->const_data, align - streampos); - ios_write(s->const_data, flddesc, layoutsize); - } - else { - reloc_to = *bp; - } - - arraylist_push(&s->relocs_list, reloc_from); - arraylist_push(&s->relocs_list, reloc_to); + size_t fldsize = sizeof(jl_datatype_layout_t) + nf * fieldsize; + if (dt->layout->first_ptr != -1) + fldsize += np << dt->layout->fielddesc_type; + uintptr_t layout = LLT_ALIGN(ios_pos(s->const_data), sizeof(void*)); + write_padding(s->const_data, layout - ios_pos(s->const_data)); // realign stream + newdt->layout = NULL; // relocation offset + layout /= sizeof(void*); + arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_datatype_t, layout))); // relocation location + arraylist_push(&s->relocs_list, (void*)(((uintptr_t)ConstDataRef << RELOC_TAG_OFFSET) + layout)); // relocation target + ios_write(s->const_data, flddesc, fldsize); } } else if (jl_is_typename(v)) { @@ -1215,8 +1478,7 @@ static void jl_write_values(jl_serializer_state *s) } else if (((jl_datatype_t*)(jl_typeof(v)))->name == jl_idtable_typename) { // will need to rehash this, later (after types are fully constructed) - arraylist_push(&reinit_list, (void*)item); - arraylist_push(&reinit_list, (void*)1); + arraylist_push(&s->fixup_objs, (void*)reloc_offset); } else { write_padding(s->s, jl_datatype_size(t) - tot); @@ -1225,61 +1487,11 @@ static void jl_write_values(jl_serializer_state *s) } } - -// Record all symbols that get referenced by the generated code -// and queue them for pointer relocation -static void jl_write_gv_syms(jl_serializer_state *s, jl_sym_t *v) -{ - // since symbols are static, they might not have had a - // reference anywhere in the code image other than here - int32_t gv = jl_get_llvm_gv(native_functions, (jl_value_t*)v); - if (gv != 0) { - uintptr_t item = backref_id(s, v); - assert(item >> RELOC_TAG_OFFSET == SymbolRef); - record_gvar(s, gv, item); - } - if (v->left) - jl_write_gv_syms(s, v->left); - if (v->right) - jl_write_gv_syms(s, v->right); -} - -// Record all hardcoded-tagged items that get referenced by -// the generated code and queue them for pointer relocation -static void jl_write_gv_tagref(jl_serializer_state *s, jl_value_t *v) -{ - int32_t gv = jl_get_llvm_gv(native_functions, (jl_value_t*)v); - if (gv != 0) { - uintptr_t item = backref_id(s, v); - assert(item >> RELOC_TAG_OFFSET == TagRef); - record_gvar(s, gv, item); - } -} -static void jl_write_gv_tagrefs(jl_serializer_state *s) -{ - // this also ensures all objects referenced in the code have - // references in the system image to their global variable - // since codegen knows that some integer boxes are static, - // they might not have had a reference anywhere in the code - // image other than here - size_t i; - jl_write_gv_tagref(s, (jl_value_t*)s->ptls->root_task); - jl_write_gv_tagref(s, s->ptls->root_task->tls); - jl_write_gv_tagref(s, jl_nothing); - for (i = 0; i < NBOX_C; i++) { - jl_write_gv_tagref(s, jl_box_int32((int32_t)i - NBOX_C / 2)); - jl_write_gv_tagref(s, jl_box_int64((int64_t)i - NBOX_C / 2)); - } - for (i = 0; i < 256; i++) { - jl_write_gv_tagref(s, jl_box_uint8(i)); - } -} - // In deserialization, create Symbols and set up the // index for backreferencing static void jl_read_symbols(jl_serializer_state *s) { - assert(deser_sym.len == nsym_tag); + assert(deser_sym.len == 0); uintptr_t base = (uintptr_t)&s->symbols->buf[0]; uintptr_t end = base + s->symbols->size; while (base < end) { @@ -1331,6 +1543,8 @@ static uintptr_t get_reloc_for_item(uintptr_t reloc_item, size_t reloc_offset) case FunctionRef: assert(offset < JL_API_MAX && "unknown function pointer id"); break; + case ExternalLinkage: + break; case DataRef: default: assert(0 && "corrupt relocation item id"); @@ -1342,7 +1556,7 @@ static uintptr_t get_reloc_for_item(uintptr_t reloc_item, size_t reloc_offset) } // Compute target location at deserialization -static inline uintptr_t get_item_for_reloc(jl_serializer_state *s, uintptr_t base, size_t size, uintptr_t reloc_id) +static inline uintptr_t get_item_for_reloc(jl_serializer_state *s, uintptr_t base, size_t size, uintptr_t reloc_id, jl_array_t *link_ids, int *link_index) { enum RefTags tag = (enum RefTags)(reloc_id >> RELOC_TAG_OFFSET); size_t offset = (reloc_id & (((uintptr_t)1 << RELOC_TAG_OFFSET) - 1)); @@ -1380,11 +1594,11 @@ static inline uintptr_t get_item_for_reloc(jl_serializer_state *s, uintptr_t bas case FunctionRef: switch ((jl_callingconv_t)offset) { case JL_API_BOXED: - if (sysimg_fptrs.base) + if (s->image->fptrs.base) return (uintptr_t)jl_fptr_args; JL_FALLTHROUGH; case JL_API_WITH_PARAMETERS: - if (sysimg_fptrs.base) + if (s->image->fptrs.base) return (uintptr_t)jl_fptr_sparam; return (uintptr_t)NULL; case JL_API_CONST: @@ -1398,17 +1612,35 @@ static inline uintptr_t get_item_for_reloc(jl_serializer_state *s, uintptr_t bas //default: assert("corrupt relocation item id"); } + case ExternalLinkage: + assert(link_ids); + assert(link_index); + assert(jl_build_ids); + uint64_t *link_id_data = (uint64_t*)jl_array_data(link_ids); + uint64_t *build_id_data = (uint64_t*)jl_array_data(jl_build_ids); + assert(0 <= *link_index && *link_index < jl_array_len(link_ids)); + uint64_t build_id = link_id_data[*link_index]; + *link_index += 1; + size_t i = 0, nids = jl_array_len(jl_build_ids); + while (i < nids) { + if (build_id == build_id_data[i]) + break; + i++; + } + assert(i < nids); + assert(2*i < jl_linkage_blobs.len); + return (uintptr_t)jl_linkage_blobs.items[2*i] + offset*sizeof(void*); } abort(); } -static void jl_write_reloclist(ios_t *s, char *base, size_t size, arraylist_t *list) +static void jl_write_offsetlist(ios_t *s, char *base, size_t size, arraylist_t *list) { for (size_t i = 0; i < list->len; i += 2) { size_t last_pos = i ? (size_t)list->items[i - 2] : 0; size_t pos = (size_t)list->items[i]; - size_t item = (size_t)list->items[i + 1]; + size_t item = (size_t)list->items[i + 1]; // item is tagref-encoded uintptr_t *pv = (uintptr_t*)(base + pos); assert(pos < size && pos != 0); *pv = get_reloc_for_item(item, *pv); @@ -1435,19 +1667,32 @@ static void jl_write_reloclist(ios_t *s, char *base, size_t size, arraylist_t *l } +static void jl_write_arraylist(ios_t *s, arraylist_t *list) +{ + write_uint(s, list->len); + ios_write(s, (const char*)list->items, list->len * sizeof(void*)); +} + static void jl_write_relocations(jl_serializer_state *s) { char *base = &s->s->buf[0]; - jl_write_reloclist(s->relocs, base, s->s->size, &s->gctags_list); - jl_write_reloclist(s->relocs, base, s->s->size, &s->relocs_list); + jl_write_offsetlist(s->relocs, base, s->s->size, &s->gctags_list); + jl_write_offsetlist(s->relocs, base, s->s->size, &s->relocs_list); + if (s->incremental) { + jl_write_arraylist(s->relocs, &s->uniquing_types); + jl_write_arraylist(s->relocs, &s->uniquing_objs); + jl_write_arraylist(s->relocs, &s->fixup_types); + } + jl_write_arraylist(s->relocs, &s->fixup_objs); } -static void jl_read_reloclist(jl_serializer_state *s, uint8_t bits) +static void jl_read_reloclist(jl_serializer_state *s, jl_array_t *link_ids, uint8_t bits) { uintptr_t base = (uintptr_t)s->s->buf; size_t size = s->s->size; uintptr_t last_pos = 0; uint8_t *current = (uint8_t *)(s->relocs->buf + s->relocs->bpos); + int link_index = 0; while (1) { // Read the offset of the next object size_t pos_diff = 0; @@ -1469,40 +1714,58 @@ static void jl_read_reloclist(jl_serializer_state *s, uint8_t bits) last_pos = pos; uintptr_t *pv = (uintptr_t *)(base + pos); uintptr_t v = *pv; - v = get_item_for_reloc(s, base, size, v); + v = get_item_for_reloc(s, base, size, v, link_ids, &link_index); *pv = v | bits; } + assert(!link_ids || link_index == jl_array_len(link_ids)); +} + +static void jl_read_arraylist(ios_t *s, arraylist_t *list) +{ + size_t list_len = read_uint(s); + arraylist_new(list, 0); + arraylist_grow(list, list_len); + ios_read(s, (char*)list->items, list_len * sizeof(void*)); } -static char *sysimg_base; -static char *sysimg_relocs; void gc_sweep_sysimg(void) { - if (!sysimg_relocs) + size_t nblobs = n_linkage_blobs(); + if (nblobs == 0) return; - uintptr_t base = (uintptr_t)sysimg_base; - uintptr_t last_pos = 0; - uint8_t *current = (uint8_t *)sysimg_relocs; - while (1) { - // Read the offset of the next object - size_t pos_diff = 0; - size_t cnt = 0; + assert(jl_linkage_blobs.len == 2*nblobs); + assert(jl_image_relocs.len == nblobs); + for (size_t i = 0; i < 2*nblobs; i+=2) { + reloc_t *relocs = (reloc_t*)jl_image_relocs.items[i>>1]; + if (!relocs) + continue; + uintptr_t base = (uintptr_t)jl_linkage_blobs.items[i]; + uintptr_t last_pos = 0; + uint8_t *current = (uint8_t *)relocs; while (1) { - int8_t c = *current++; - pos_diff |= ((size_t)c & 0x7F) << (7 * cnt++); - if ((c >> 7) == 0) + // Read the offset of the next object + size_t pos_diff = 0; + size_t cnt = 0; + while (1) { + int8_t c = *current++; + pos_diff |= ((size_t)c & 0x7F) << (7 * cnt++); + if ((c >> 7) == 0) + break; + } + if (pos_diff == 0) break; - } - if (pos_diff == 0) - break; - uintptr_t pos = last_pos + pos_diff; - last_pos = pos; - jl_taggedvalue_t *o = (jl_taggedvalue_t *)(base + pos); - o->bits.gc = GC_OLD; + uintptr_t pos = last_pos + pos_diff; + last_pos = pos; + jl_taggedvalue_t *o = (jl_taggedvalue_t *)(base + pos); + o->bits.gc = GC_OLD; + } } } +// jl_write_value and jl_read_value are used for storing Julia objects that are adjuncts to +// the image proper. For example, new methods added to external callables require +// insertion into the appropriate method table. #define jl_write_value(s, v) _jl_write_value((s), (jl_value_t*)(v)) static void _jl_write_value(jl_serializer_state *s, jl_value_t *v) { @@ -1510,12 +1773,11 @@ static void _jl_write_value(jl_serializer_state *s, jl_value_t *v) write_reloc_t(s->s, 0); return; } - uintptr_t item = backref_id(s, v); + uintptr_t item = backref_id(s, v, NULL); uintptr_t reloc = get_reloc_for_item(item, 0); write_reloc_t(s->s, reloc); } - static jl_value_t *jl_read_value(jl_serializer_state *s) { uintptr_t base = (uintptr_t)&s->s->buf[0]; @@ -1524,16 +1786,44 @@ static jl_value_t *jl_read_value(jl_serializer_state *s) s->s->bpos += sizeof(reloc_t); if (offset == 0) return NULL; - return (jl_value_t*)get_item_for_reloc(s, base, size, offset); + return (jl_value_t*)get_item_for_reloc(s, base, size, offset, NULL, NULL); +} + +// The next two, `jl_read_offset` and `jl_delayed_reloc`, are essentially a split version +// of `jl_read_value` that allows usage of the relocation data rather than passing NULL +// to `get_item_for_reloc`. +// This works around what would otherwise be an order-dependency conundrum: objects +// that may require relocation data have to be inserted into `serialization_order`, +// and that may include some of the adjunct data that gets serialized via +// `jl_write_value`. But we can't interpret them properly until we read the relocation +// data, and that happens after we pull items out of the serialization stream. +static uintptr_t jl_read_offset(jl_serializer_state *s) +{ + uintptr_t base = (uintptr_t)&s->s->buf[0]; + uintptr_t offset = *(reloc_t*)(base + (uintptr_t)s->s->bpos); + s->s->bpos += sizeof(reloc_t); + return offset; } +static jl_value_t *jl_delayed_reloc(jl_serializer_state *s, uintptr_t offset) JL_GC_DISABLED +{ + if (!offset) + return NULL; + uintptr_t base = (uintptr_t)&s->s->buf[0]; + size_t size = s->s->size; + int link_index = 0; + jl_value_t *ret = (jl_value_t*)get_item_for_reloc(s, base, size, offset, s->link_ids_relocs, &link_index); + assert(link_index < jl_array_len(s->link_ids_relocs)); + return ret; +} -static void jl_update_all_fptrs(jl_serializer_state *s) + +static void jl_update_all_fptrs(jl_serializer_state *s, jl_image_t *image) { - jl_sysimg_fptrs_t fvars = sysimg_fptrs; + jl_sysimg_fptrs_t fvars = image->fptrs; // make these NULL now so we skip trying to restore GlobalVariable pointers later - sysimg_gvars_base = NULL; - sysimg_fptrs.base = NULL; + image->gvars_base = NULL; + image->fptrs.base = NULL; if (fvars.base == NULL) return; int sysimg_fvars_max = s->fptr_record->size / sizeof(void*); @@ -1578,152 +1868,112 @@ static void jl_update_all_fptrs(jl_serializer_state *s) } } // Tell LLVM about the native code - jl_register_fptrs(sysimage_base, &fvars, linfos, sysimg_fvars_max); + jl_register_fptrs(image->base, &fvars, linfos, sysimg_fvars_max); } +static void write_gvars(jl_serializer_state *s, arraylist_t *globals) JL_NOTSAFEPOINT +{ + ios_ensureroom(s->gvar_record, globals->len * sizeof(reloc_t)); + for (size_t i = 0; i < globals->len; i++) { + void *g = globals->items[i]; + if (jl_is_binding((uintptr_t)g)) { + jl_binding_t *b = (jl_binding_t*)g; + void *reloc = ptrhash_get(&bindings, g); + if (reloc != HT_NOTFOUND) { + assert(reloc != (void*)(uintptr_t)-1); + write_reloc_t(s->gvar_record, (uintptr_t)reloc); + continue; + } + // need to deal with foreign bindings here too + assert(s->incremental); + arraylist_push(&s->uniquing_objs, (void*)((i << 2) | 2)); // mark as gvar && !tag + g = (void*)jl_module_globalref(b->owner, b->name); + } + uintptr_t item = backref_id(s, g, s->link_ids_gvars); + uintptr_t reloc = get_reloc_for_item(item, 0); + write_reloc_t(s->gvar_record, reloc); + record_uniquing(s, (jl_value_t*)g, ((i << 2) | 2)); // mark as gvar && !tag + } +} // Pointer relocation for native-code referenced global variables -static void jl_update_all_gvars(jl_serializer_state *s) +static void jl_update_all_gvars(jl_serializer_state *s, jl_image_t *image) { - if (sysimg_gvars_base == NULL) + if (image->gvars_base == NULL) return; - size_t gvname_index = 0; + size_t i = 0; + size_t l = s->gvar_record->size / sizeof(reloc_t); uintptr_t base = (uintptr_t)&s->s->buf[0]; size_t size = s->s->size; reloc_t *gvars = (reloc_t*)&s->gvar_record->buf[0]; - reloc_t *end = gvars + s->gvar_record->size / sizeof(reloc_t); - while (gvars < end) { - uintptr_t offset = *gvars; - if (offset) { - uintptr_t v = get_item_for_reloc(s, base, size, offset); - *sysimg_gvars(sysimg_gvars_base, gvname_index) = v; - } - gvname_index += 1; - gvars++; + int link_index = 0; + for (i = 0; i < l; i++) { + uintptr_t offset = gvars[i]; + uintptr_t v = get_item_for_reloc(s, base, size, offset, s->link_ids_gvars, &link_index); + uintptr_t *gv = sysimg_gvars(image->gvars_base, image->gvars_offsets, i); + *gv = v; } + assert(!s->link_ids_gvars || link_index == jl_array_len(s->link_ids_gvars)); } - -// Reinitialization -static void jl_finalize_serializer(jl_serializer_state *s, arraylist_t *list) +static void jl_root_new_gvars(jl_serializer_state *s, jl_image_t *image) { - size_t i, l; - - // record list of reinitialization functions - l = list->len; - for (i = 0; i < l; i += 2) { - size_t item = (size_t)list->items[i]; - size_t reloc_offset = (size_t)layout_table.items[item]; - assert(reloc_offset != 0); - write_reloc_t(s->s, reloc_offset); - write_uint8(s->s, (uintptr_t)list->items[i + 1]); + if (image->gvars_base == NULL) + return; + size_t i = 0; + size_t l = s->gvar_record->size / sizeof(reloc_t); + for (i = 0; i < l; i++) { + uintptr_t *gv = sysimg_gvars(image->gvars_base, image->gvars_offsets, i); + uintptr_t v = *gv; + if (!jl_is_binding(v)) + v = (uintptr_t)jl_as_global_root((jl_value_t*)v); + *gv = v; } - write_reloc_t(s->s, 0); } -static void jl_reinit_item(jl_value_t *v, uint8_t how) JL_GC_DISABLED +static void jl_compile_extern(jl_method_t *m, void *sysimg_handle) JL_GC_DISABLED { - switch (how) { - case 1: { // rehash IdDict - jl_array_t **a = (jl_array_t**)v; - assert(jl_is_array(*a)); - // Assume *a don't need a write barrier - *a = jl_idtable_rehash(*a, jl_array_len(*a)); - jl_gc_wb(v, *a); - break; - } - case 2: { // rebuild the binding table for module v - jl_module_t *mod = (jl_module_t*)v; - assert(jl_is_module(mod)); - size_t nbindings = mod->bindings.size; - htable_new(&mod->bindings, nbindings); - struct binding { - jl_sym_t *asname; - uintptr_t tag; - jl_binding_t b; - } *b; - b = (struct binding*)&mod[1]; - while (nbindings > 0) { - ptrhash_put(&mod->bindings, b->asname, &b->b); - b += 1; - nbindings -= 1; - } - if (mod->usings.items != &mod->usings._space[0]) { - void **newitems = (void**)malloc_s(mod->usings.max * sizeof(void*)); - memcpy(newitems, mod->usings.items, mod->usings.len * sizeof(void*)); - mod->usings.items = newitems; - } - break; - } - case 3: { // install ccallable entry point in JIT - jl_svec_t *sv = ((jl_method_t*)v)->ccallable; - int success = jl_compile_extern_c(NULL, NULL, jl_sysimg_handle, jl_svecref(sv, 0), jl_svecref(sv, 1)); - assert(success); (void)success; - break; - } - default: - assert(0 && "corrupt deserialization state"); - abort(); - } + // install ccallable entry point in JIT + jl_svec_t *sv = m->ccallable; + int success = jl_compile_extern_c(NULL, NULL, sysimg_handle, jl_svecref(sv, 0), jl_svecref(sv, 1)); + if (!success) + jl_safe_printf("WARNING: @ccallable was already defined for this method name\n"); // enjoy a very bad time + assert(success || !sysimg_handle); } -static void jl_finalize_deserializer(jl_serializer_state *s) JL_GC_DISABLED +static void jl_reinit_ccallable(arraylist_t *ccallable_list, char *base, void *sysimg_handle) { - // run reinitialization functions - uintptr_t base = (uintptr_t)&s->s->buf[0]; - while (1) { - size_t offset; - if (sizeof(reloc_t) <= 4) { - offset = read_uint32(s->s); - } - else { - offset = read_uint64(s->s); - } - if (offset == 0) - break; - jl_value_t *v = (jl_value_t*)(base + offset); - jl_reinit_item(v, read_uint8(s->s)); + for (size_t i = 0; i < ccallable_list->len; i++) { + uintptr_t item = (uintptr_t)ccallable_list->items[i]; + jl_method_t *m = (jl_method_t*)(base + item); + jl_compile_extern(m, sysimg_handle); } } - -// Code below helps slim down the images -static void jl_scan_type_cache_gv(jl_serializer_state *s, jl_svec_t *cache) -{ - size_t l = jl_svec_len(cache), i; - for (i = 0; i < l; i++) { - jl_value_t *ti = jl_svecref(cache, i); - if (ti == jl_nothing) - continue; - if (jl_get_llvm_gv(native_functions, ti)) { - jl_serialize_value(s, ti); - } - else if (jl_is_datatype(ti)) { - jl_value_t *singleton = ((jl_datatype_t*)ti)->instance; - if (singleton && jl_get_llvm_gv(native_functions, singleton)) - jl_serialize_value(s, ti); - } - } -} - -// remove cached types not referenced in the stream +// Code below helps slim down the images by +// removing cached types not referenced in the stream static jl_svec_t *jl_prune_type_cache_hash(jl_svec_t *cache) JL_GC_DISABLED { size_t l = jl_svec_len(cache), i; + if (l == 0) + return cache; for (i = 0; i < l; i++) { jl_value_t *ti = jl_svecref(cache, i); if (ti == jl_nothing) continue; - if (ptrhash_get(&backref_table, ti) == HT_NOTFOUND) + if (ptrhash_get(&serialization_order, ti) == HT_NOTFOUND) jl_svecset(cache, i, jl_nothing); } - void *idx = ptrhash_get(&backref_table, cache); - ptrhash_remove(&backref_table, cache); + void *idx = ptrhash_get(&serialization_order, cache); + assert(idx != HT_NOTFOUND && idx != (void*)(uintptr_t)-1); + assert(serialization_queue.items[(char*)idx - 1 - (char*)HT_NOTFOUND] == cache); cache = cache_rehash_set(cache, l); - ptrhash_put(&backref_table, cache, idx); + // redirect all references to the old cache to relocate to the new cache object + ptrhash_put(&serialization_order, cache, idx); + serialization_queue.items[(char*)idx - 1 - (char*)HT_NOTFOUND] = cache; return cache; } @@ -1734,7 +1984,7 @@ static void jl_prune_type_cache_linear(jl_svec_t *cache) jl_value_t *ti = jl_svecref(cache, i); if (ti == jl_nothing) break; - if (ptrhash_get(&backref_table, ti) != HT_NOTFOUND) + if (ptrhash_get(&serialization_order, ti) != HT_NOTFOUND) jl_svecset(cache, ins++, ti); } while (ins < l) @@ -1777,11 +2027,6 @@ static jl_value_t *strip_codeinfo_meta(jl_method_t *m, jl_value_t *ci_, int orig return ret; } -static void record_field_change(jl_value_t **addr, jl_value_t *newval) -{ - ptrhash_put(&field_replace, (void*)addr, newval); -} - static void strip_specializations_(jl_method_instance_t *mi) { assert(jl_is_method_instance(mi)); @@ -1866,6 +2111,7 @@ static void jl_strip_all_codeinfos(void) // triggering non-relocatability of compressed CodeInfos. // Set the number of such roots in each method when the sysimg is // serialized. +// TODO: move this to `jl_write_values` static int set_nroots_sysimg__(jl_typemap_entry_t *def, void *_env) { jl_method_t *m = def->func.method; @@ -1885,9 +2131,6 @@ static void jl_set_nroots_sysimg(void) // --- entry points --- -static void jl_init_serializer2(int); -static void jl_cleanup_serializer2(void); - jl_array_t *jl_global_roots_table; static jl_mutex_t global_roots_lock; @@ -1931,33 +2174,93 @@ JL_DLLEXPORT jl_value_t *jl_as_global_root(jl_value_t *val JL_MAYBE_UNROOTED) return val; } -static void jl_save_system_image_to_stream(ios_t *f) JL_GC_DISABLED +static void jl_prepare_serialization_data(jl_array_t *mod_array, jl_array_t *newly_inferred, uint64_t worklist_key, + /* outputs */ jl_array_t **extext_methods, + jl_array_t **new_specializations, jl_array_t **method_roots_list, + jl_array_t **ext_targets, jl_array_t **edges) { - jl_gc_collect(JL_GC_FULL); - jl_gc_collect(JL_GC_INCREMENTAL); // sweep finalizers - JL_TIMING(SYSIMG_DUMP); + // extext_methods: [method1, ...], worklist-owned "extending external" methods added to functions owned by modules outside the worklist + // ext_targets: [invokesig1, callee1, matches1, ...] non-worklist callees of worklist-owned methods + // ordinary dispatch: invokesig=NULL, callee is MethodInstance + // `invoke` dispatch: invokesig is signature, callee is MethodInstance + // abstract call: callee is signature + // edges: [caller1, ext_targets_indexes1, ...] for worklist-owned methods calling external methods + + assert(edges_map == NULL); + JL_GC_PUSH1(&edges_map); + + // Save the inferred code from newly inferred, external methods + htable_new(&external_mis, 0); // we need external_mis until after `jl_collect_edges` finishes + *new_specializations = queue_external_cis(newly_inferred); + // Collect the new method roots + htable_t methods_with_newspecs; + htable_new(&methods_with_newspecs, 0); + jl_collect_methods(&methods_with_newspecs, *new_specializations); + *method_roots_list = jl_alloc_vec_any(0); + jl_collect_new_roots(*method_roots_list, &methods_with_newspecs, worklist_key); + htable_free(&methods_with_newspecs); + + // Collect method extensions and edges data + edges_map = jl_alloc_vec_any(0); + *extext_methods = jl_alloc_vec_any(0); + size_t i, len = jl_array_len(mod_array); + for (i = 0; i < len; i++) { + jl_module_t *m = (jl_module_t*)jl_array_ptr_ref(mod_array, i); + assert(jl_is_module(m)); + if (m->parent == m) // some toplevel modules (really just Base) aren't actually + jl_collect_extext_methods_from_mod(*extext_methods, m); + } + jl_collect_methtable_from_mod(*extext_methods, jl_type_type_mt); + jl_collect_missing_backedges(jl_type_type_mt); + jl_collect_methtable_from_mod(*extext_methods, jl_nonfunction_mt); + jl_collect_missing_backedges(jl_nonfunction_mt); + // jl_collect_extext_methods_from_mod and jl_collect_missing_backedges also accumulate data in callers_with_edges. + // Process this to extract `edges` and `ext_targets`. + *ext_targets = jl_alloc_vec_any(0); + *edges = jl_alloc_vec_any(0); + jl_collect_edges(*edges, *ext_targets); + htable_free(&external_mis); + assert(edges_map == NULL); // jl_collect_edges clears this when done - htable_new(&field_replace, 10000); + JL_GC_POP(); +} + +// In addition to the system image (where `worklist = NULL`), this can also save incremental images with external linkage +static void jl_save_system_image_to_stream(ios_t *f, + jl_array_t *worklist, jl_array_t *extext_methods, + jl_array_t *new_specializations, jl_array_t *method_roots_list, + jl_array_t *ext_targets, jl_array_t *edges) JL_GC_DISABLED +{ + htable_new(&field_replace, 0); // strip metadata and IR when requested if (jl_options.strip_metadata || jl_options.strip_ir) jl_strip_all_codeinfos(); - jl_set_nroots_sysimg(); + if (worklist == NULL) + jl_set_nroots_sysimg(); int en = jl_gc_enable(0); - jl_init_serializer2(1); - htable_reset(&backref_table, 250000); - arraylist_new(&reinit_list, 0); - arraylist_new(&ccallable_list, 0); + nsym_tag = 0; + htable_new(&symbol_table, 0); + htable_new(&fptr_to_id, sizeof(id_to_fptrs) / sizeof(*id_to_fptrs)); + uintptr_t i; + for (i = 0; id_to_fptrs[i] != NULL; i++) { + ptrhash_put(&fptr_to_id, (void*)(uintptr_t)id_to_fptrs[i], (void*)(i + 2)); + } + htable_new(&serialization_order, 25000); + htable_new(&unique_ready, 0); + htable_new(&nullptrs, 0); + htable_new(&bindings, 0); arraylist_new(&object_worklist, 0); - backref_table_numel = 0; + arraylist_new(&serialization_queue, 0); ios_t sysimg, const_data, symbols, relocs, gvar_record, fptr_record; - ios_mem(&sysimg, 1000000); - ios_mem(&const_data, 100000); - ios_mem(&symbols, 100000); - ios_mem(&relocs, 100000); - ios_mem(&gvar_record, 100000); - ios_mem(&fptr_record, 100000); + ios_mem(&sysimg, 0); + ios_mem(&const_data, 0); + ios_mem(&symbols, 0); + ios_mem(&relocs, 0); + ios_mem(&gvar_record, 0); + ios_mem(&fptr_record, 0); jl_serializer_state s; + s.incremental = !(worklist == NULL); s.s = &sysimg; s.const_data = &const_data; s.symbols = &symbols; @@ -1967,16 +2270,31 @@ static void jl_save_system_image_to_stream(ios_t *f) JL_GC_DISABLED s.ptls = jl_current_task->ptls; arraylist_new(&s.relocs_list, 0); arraylist_new(&s.gctags_list, 0); - jl_value_t **const*const tags = get_tags(); - - // empty!(Core.ARGS) - if (jl_core_module != NULL) { - jl_array_t *args = (jl_array_t*)jl_get_global(jl_core_module, jl_symbol("ARGS")); - if (args != NULL) { - jl_array_del_end(args, jl_array_len(args)); + arraylist_new(&s.uniquing_types, 0); + arraylist_new(&s.uniquing_objs, 0); + arraylist_new(&s.fixup_types, 0); + arraylist_new(&s.fixup_objs, 0); + arraylist_new(&s.ccallable_list, 0); + s.link_ids_relocs = jl_alloc_array_1d(jl_array_uint64_type, 0); + s.link_ids_gctags = jl_alloc_array_1d(jl_array_uint64_type, 0); + s.link_ids_gvars = jl_alloc_array_1d(jl_array_uint64_type, 0); + htable_new(&s.callers_with_edges, 0); + jl_value_t **const*const tags = get_tags(); // worklist == NULL ? get_tags() : NULL; + + arraylist_t gvars; + arraylist_new(&gvars, 0); + if (native_functions) + jl_get_llvm_gvs(native_functions, &gvars); + + if (worklist == NULL) { + // empty!(Core.ARGS) + if (jl_core_module != NULL) { + jl_array_t *args = (jl_array_t*)jl_get_global(jl_core_module, jl_symbol("ARGS")); + if (args != NULL) { + jl_array_del_end(args, jl_array_len(args)); + } } } - jl_idtable_type = jl_base_module ? jl_get_global(jl_base_module, jl_symbol("IdDict")) : NULL; jl_idtable_typename = jl_base_module ? ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_idtable_type))->name : NULL; jl_bigint_type = jl_base_module ? jl_get_global(jl_base_module, jl_symbol("BigInt")) : NULL; @@ -1993,44 +2311,63 @@ static void jl_save_system_image_to_stream(ios_t *f) JL_GC_DISABLED { // step 1: record values (recursively) that need to go in the image size_t i; - for (i = 0; tags[i] != NULL; i++) { - jl_value_t *tag = *tags[i]; - jl_serialize_value(&s, tag); + if (worklist == NULL) { + for (i = 0; tags[i] != NULL; i++) { + jl_value_t *tag = *tags[i]; + jl_queue_for_serialization(&s, tag); + } + jl_queue_for_serialization(&s, jl_global_roots_table); + jl_queue_for_serialization(&s, s.ptls->root_task->tls); } - jl_serialize_value(&s, jl_global_roots_table); - jl_serialize_reachable(&s); - // step 1.1: check for values only found in the generated code - arraylist_t typenames; - arraylist_new(&typenames, 0); - for (i = 0; i < backref_table.size; i += 2) { - jl_typename_t *tn = (jl_typename_t*)backref_table.table[i]; - if (tn == HT_NOTFOUND || !jl_is_typename(tn)) - continue; - arraylist_push(&typenames, tn); + else { + // To ensure we don't have to manually update the list, go through all tags and queue any that are not otherwise + // judged to be externally-linked + htable_new(&external_objects, NUM_TAGS); + for (size_t i = 0; tags[i] != NULL; i++) { + jl_value_t *tag = *tags[i]; + ptrhash_put(&external_objects, tag, tag); + } + // Queue the worklist itself as the first item we serialize + jl_queue_for_serialization(&s, worklist); + jl_queue_for_serialization(&s, jl_module_init_order); + // Classify the CodeInstances with respect to their need for validation + classify_callers(&s.callers_with_edges, edges); } - for (i = 0; i < typenames.len; i++) { - jl_typename_t *tn = (jl_typename_t*)typenames.items[i]; - jl_scan_type_cache_gv(&s, tn->cache); - jl_scan_type_cache_gv(&s, tn->linearcache); + // step 1.1: as needed, serialize the data needed for insertion into the running system + if (extext_methods) { + assert(ext_targets); + assert(edges); + // Queue method extensions + jl_queue_for_serialization(&s, extext_methods); + // Queue the new specializations + jl_queue_for_serialization(&s, new_specializations); + // Queue the new roots + jl_queue_for_serialization(&s, method_roots_list); + // Queue the edges + jl_queue_for_serialization(&s, ext_targets); + jl_queue_for_serialization(&s, edges); } jl_serialize_reachable(&s); - // step 1.2: prune (garbage collect) some special weak references from + // step 1.2: now that we have marked all bindings (badly), ensure all gvars are part of the sysimage + record_gvars(&s, &gvars); + jl_serialize_reachable(&s); + // step 1.3: prune (garbage collect) some special weak references from // built-in type caches - for (i = 0; i < typenames.len; i++) { - jl_typename_t *tn = (jl_typename_t*)typenames.items[i]; - tn->cache = jl_prune_type_cache_hash(tn->cache); - jl_gc_wb(tn, tn->cache); - jl_prune_type_cache_linear(tn->linearcache); + for (i = 0; i < serialization_queue.len; i++) { + jl_typename_t *tn = (jl_typename_t*)serialization_queue.items[i]; + if (jl_is_typename(tn)) { + tn->cache = jl_prune_type_cache_hash(tn->cache); + jl_gc_wb(tn, tn->cache); + jl_prune_type_cache_linear(tn->linearcache); + } } - arraylist_free(&typenames); } { // step 2: build all the sysimg sections write_padding(&sysimg, sizeof(uintptr_t)); jl_write_values(&s); + write_gvars(&s, &gvars); jl_write_relocations(&s); - jl_write_gv_syms(&s, jl_get_root_symbol()); - jl_write_gv_tagrefs(&s); } if (sysimg.size > ((uintptr_t)1 << RELOC_TAG_OFFSET)) { @@ -2051,8 +2388,10 @@ static void jl_save_system_image_to_stream(ios_t *f) JL_GC_DISABLED ); jl_exit(1); } + htable_free(&s.callers_with_edges); // step 3: combine all of the sections into one file + assert(ios_pos(f) % JL_CACHE_BYTE_ALIGNMENT == 0); write_uint(f, sysimg.size - sizeof(uintptr_t)); ios_seek(&sysimg, sizeof(uintptr_t)); ios_copyall(f, &sysimg); @@ -2090,56 +2429,181 @@ static void jl_save_system_image_to_stream(ios_t *f) JL_GC_DISABLED ios_close(&fptr_record); { // step 4: record locations of special roots - s.s = f; write_padding(f, LLT_ALIGN(ios_pos(f), 8) - ios_pos(f)); - size_t i; - for (i = 0; tags[i] != NULL; i++) { - jl_value_t *tag = *tags[i]; - jl_write_value(&s, tag); + s.s = f; + if (worklist == NULL) { + size_t i; + for (i = 0; tags[i] != NULL; i++) { + jl_value_t *tag = *tags[i]; + jl_write_value(&s, tag); + } + jl_write_value(&s, jl_global_roots_table); + jl_write_value(&s, s.ptls->root_task->tls); + write_uint32(f, jl_get_gs_ctr()); + write_uint(f, jl_atomic_load_acquire(&jl_world_counter)); + write_uint(f, jl_typeinf_world); } - jl_write_value(&s, jl_global_roots_table); - jl_write_value(&s, s.ptls->root_task->tls); - write_uint32(f, jl_get_gs_ctr()); - write_uint(f, jl_atomic_load_acquire(&jl_world_counter)); - write_uint(f, jl_typeinf_world); - jl_finalize_serializer(&s, &reinit_list); - jl_finalize_serializer(&s, &ccallable_list); - } + else { + jl_write_value(&s, worklist); + // save module initialization order + if (jl_module_init_order != NULL) { + size_t i, l = jl_array_len(jl_module_init_order); + for (i = 0; i < l; i++) { + // verify that all these modules were saved + assert(ptrhash_get(&serialization_order, jl_array_ptr_ref(jl_module_init_order, i)) != HT_NOTFOUND); + } + } + jl_write_value(&s, jl_module_init_order); + jl_write_value(&s, extext_methods); + jl_write_value(&s, new_specializations); + jl_write_value(&s, method_roots_list); + jl_write_value(&s, ext_targets); + jl_write_value(&s, edges); + } + write_uint32(f, jl_array_len(s.link_ids_gctags)); + ios_write(f, (char*)jl_array_data(s.link_ids_gctags), jl_array_len(s.link_ids_gctags)*sizeof(uint64_t)); + write_uint32(f, jl_array_len(s.link_ids_relocs)); + ios_write(f, (char*)jl_array_data(s.link_ids_relocs), jl_array_len(s.link_ids_relocs)*sizeof(uint64_t)); + write_uint32(f, jl_array_len(s.link_ids_gvars)); + ios_write(f, (char*)jl_array_data(s.link_ids_gvars), jl_array_len(s.link_ids_gvars)*sizeof(uint64_t)); + jl_write_arraylist(s.s, &s.ccallable_list); + } + // Write the build_id key + uint64_t buildid = 0; + if (worklist) + buildid = jl_worklist_key(worklist); + write_uint32(f, buildid >> 32); + write_uint32(f, buildid & (((uint64_t)1 << 32) - 1)); assert(object_worklist.len == 0); arraylist_free(&object_worklist); + arraylist_free(&serialization_queue); arraylist_free(&layout_table); - arraylist_free(&reinit_list); - arraylist_free(&ccallable_list); + arraylist_free(&s.ccallable_list); arraylist_free(&s.relocs_list); arraylist_free(&s.gctags_list); + arraylist_free(&gvars); htable_free(&field_replace); - jl_cleanup_serializer2(); + if (worklist) + htable_free(&external_objects); + htable_free(&serialization_order); + htable_free(&unique_ready); + htable_free(&nullptrs); + htable_free(&bindings); + htable_free(&symbol_table); + htable_free(&fptr_to_id); + nsym_tag = 0; jl_gc_enable(en); } -JL_DLLEXPORT ios_t *jl_create_system_image(void *_native_data) +static void jl_write_header_for_incremental(ios_t *f, jl_array_t *worklist, jl_array_t **mod_array, jl_array_t **udeps, int64_t *srctextpos, int64_t *checksumpos) { + *mod_array = jl_get_loaded_modules(); // __toplevel__ modules loaded in this session (from Base.loaded_modules_array) + assert(jl_precompile_toplevel_module == NULL); + jl_precompile_toplevel_module = (jl_module_t*)jl_array_ptr_ref(worklist, jl_array_len(worklist)-1); + + write_header(f); + // last word of the header is the checksumpos + *checksumpos = ios_pos(f) - sizeof(uint64_t); + // write description of contents (name, uuid, buildid) + write_worklist_for_header(f, worklist); + // Determine unique (module, abspath, mtime) dependencies for the files defining modules in the worklist + // (see Base._require_dependencies). These get stored in `udeps` and written to the ji-file header. + // Also write Preferences. + // last word of the dependency list is the end of the data / start of the srctextpos + *srctextpos = write_dependency_list(f, worklist, udeps); // srctextpos: position of srctext entry in header index (update later) + // write description of requirements for loading (modules that must be pre-loaded if initialization is to succeed) + // this can return errors during deserialize, + // best to keep it early (before any actual initialization) + write_mod_list(f, *mod_array); +} + + +JL_DLLEXPORT ios_t *jl_create_system_image(void *_native_data, jl_array_t *worklist) +{ + jl_gc_collect(JL_GC_FULL); + jl_gc_collect(JL_GC_INCREMENTAL); // sweep finalizers + JL_TIMING(SYSIMG_DUMP); + + jl_task_t *ct = jl_current_task; ios_t *f = (ios_t*)malloc_s(sizeof(ios_t)); ios_mem(f, 0); + jl_array_t *mod_array = NULL, *udeps = NULL, *extext_methods = NULL, *new_specializations = NULL; + jl_array_t *method_roots_list = NULL, *ext_targets = NULL, *edges = NULL; + JL_GC_PUSH7(&mod_array, &udeps, &extext_methods, &new_specializations, &method_roots_list, &ext_targets, &edges); + int64_t srctextpos = 0; + int64_t checksumpos = 0; + int64_t datastartpos = 0; + if (worklist) { + jl_write_header_for_incremental(f, worklist, &mod_array, &udeps, &srctextpos, &checksumpos); + jl_gc_enable_finalizers(ct, 0); // make sure we don't run any Julia code concurrently after this point + jl_prepare_serialization_data(mod_array, newly_inferred, jl_worklist_key(worklist), &extext_methods, &new_specializations, &method_roots_list, &ext_targets, &edges); + write_padding(f, LLT_ALIGN(ios_pos(f), JL_CACHE_BYTE_ALIGNMENT) - ios_pos(f)); + datastartpos = ios_pos(f); + } native_functions = _native_data; - jl_save_system_image_to_stream(f); + jl_save_system_image_to_stream(f, worklist, extext_methods, new_specializations, method_roots_list, ext_targets, edges); + native_functions = NULL; + if (worklist) { + jl_gc_enable_finalizers(ct, 1); // make sure we don't run any Julia code concurrently before this point + // Go back and update the checksum in the header + int64_t dataendpos = ios_pos(f); + uint32_t checksum = jl_crc32c(0, &f->buf[datastartpos], dataendpos - datastartpos); + ios_seek(f, checksumpos); + write_uint64(f, checksum | ((uint64_t)0xfafbfcfd << 32)); + ios_seek(f, srctextpos); + write_uint64(f, dataendpos); + // Write the source-text for the dependent files + // Go back and update the source-text position to point to the current position + if (udeps) { + ios_seek_end(f); + // Each source-text file is written as + // int32: length of abspath + // char*: abspath + // uint64: length of src text + // char*: src text + // At the end we write int32(0) as a terminal sentinel. + size_t len = jl_array_len(udeps); + ios_t srctext; + for (size_t i = 0; i < len; i++) { + jl_value_t *deptuple = jl_array_ptr_ref(udeps, i); + jl_value_t *depmod = jl_fieldref(deptuple, 0); // module + // Dependencies declared with `include_dependency` are excluded + // because these may not be Julia code (and could be huge) + if (depmod != (jl_value_t*)jl_main_module) { + jl_value_t *dep = jl_fieldref(deptuple, 1); // file abspath + const char *depstr = jl_string_data(dep); + if (!depstr[0]) + continue; + ios_t *srctp = ios_file(&srctext, depstr, 1, 0, 0, 0); + if (!srctp) { + jl_printf(JL_STDERR, "WARNING: could not cache source text for \"%s\".\n", + jl_string_data(dep)); + continue; + } + size_t slen = jl_string_len(dep); + write_int32(f, slen); + ios_write(f, depstr, slen); + int64_t posfile = ios_pos(f); + write_uint64(f, 0); // placeholder for length of this file in bytes + uint64_t filelen = (uint64_t) ios_copyall(f, &srctext); + ios_close(&srctext); + ios_seek(f, posfile); + write_uint64(f, filelen); + ios_seek_end(f); + } + } + } + write_int32(f, 0); // mark the end of the source text + jl_precompile_toplevel_module = NULL; + } + + JL_GC_POP(); return f; } JL_DLLEXPORT size_t ios_write_direct(ios_t *dest, ios_t *src); -JL_DLLEXPORT void jl_save_system_image(const char *fname) -{ - ios_t f; - if (ios_file(&f, fname, 1, 1, 1, 1) == NULL) { - jl_errorf("cannot open system image file \"%s\" for writing", fname); - } - JL_SIGATOMIC_BEGIN(); - jl_save_system_image_to_stream(&f); - ios_close(&f); - JL_SIGATOMIC_END(); -} // Takes in a path of the form "usr/lib/julia/sys.so" (jl_restore_system_image should be passed the same string) JL_DLLEXPORT void jl_preload_sysimg_so(const char *fname) @@ -2165,16 +2629,31 @@ JL_DLLEXPORT void jl_set_sysimg_so(void *handle) if (jl_options.cpu_target == NULL) jl_options.cpu_target = "native"; jl_sysimg_handle = handle; - sysimg_fptrs = jl_init_processor_sysimg(handle); + sysimage.fptrs = jl_init_processor_sysimg(handle); } -static void jl_restore_system_image_from_stream(ios_t *f) JL_GC_DISABLED +#ifndef JL_NDEBUG +// skip the performance optimizations of jl_types_equal and just use subtyping directly +// one of these types is invalid - that's why we're doing the recache type operation +// static int jl_invalid_types_equal(jl_datatype_t *a, jl_datatype_t *b) +// { +// return jl_subtype((jl_value_t*)a, (jl_value_t*)b) && jl_subtype((jl_value_t*)b, (jl_value_t*)a); +// } +#endif + +static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl_array_t *depmods, uint64_t checksum, + /* outputs */ jl_array_t **restored, jl_array_t **init_order, + jl_array_t **extext_methods, + jl_array_t **new_specializations, jl_array_t **method_roots_list, + jl_array_t **ext_targets, jl_array_t **edges, + char **base, arraylist_t *ccallable_list, pkgcachesizes *cachesizes) JL_GC_DISABLED { JL_TIMING(SYSIMG_LOAD); int en = jl_gc_enable(0); - jl_init_serializer2(0); ios_t sysimg, const_data, symbols, relocs, gvar_record, fptr_record; jl_serializer_state s; + s.incremental = restored != NULL; // jl_linkage_blobs.len > 0; + s.image = image; s.s = NULL; s.const_data = &const_data; s.symbols = &symbols; @@ -2184,7 +2663,11 @@ static void jl_restore_system_image_from_stream(ios_t *f) JL_GC_DISABLED s.ptls = jl_current_task->ptls; arraylist_new(&s.relocs_list, 0); arraylist_new(&s.gctags_list, 0); + s.link_ids_relocs = s.link_ids_gctags = s.link_ids_gvars = NULL; jl_value_t **const*const tags = get_tags(); + htable_t new_dt_objs; + htable_new(&new_dt_objs, 0); + arraylist_new(&deser_sym, 0); // step 1: read section map assert(ios_pos(f) == 0 && f->bm == bm_mem); @@ -2222,27 +2705,67 @@ static void jl_restore_system_image_from_stream(ios_t *f) JL_GC_DISABLED ios_skip(f, sizeof_fptr_record); // step 2: get references to special values - s.s = f; ios_seek(f, LLT_ALIGN(ios_pos(f), 8)); assert(!ios_eof(f)); - size_t i; - for (i = 0; tags[i] != NULL; i++) { - jl_value_t **tag = tags[i]; - *tag = jl_read_value(&s); - } - jl_global_roots_table = (jl_array_t*)jl_read_value(&s); - // set typeof extra-special values now that we have the type set by tags above - jl_astaggedvalue(jl_current_task)->header = (uintptr_t)jl_task_type | jl_astaggedvalue(jl_current_task)->header; - jl_astaggedvalue(jl_nothing)->header = (uintptr_t)jl_nothing_type | jl_astaggedvalue(jl_nothing)->header; - s.ptls->root_task->tls = jl_read_value(&s); - jl_gc_wb(s.ptls->root_task, s.ptls->root_task->tls); - jl_init_int32_int64_cache(); - jl_init_box_caches(); - - uint32_t gs_ctr = read_uint32(f); - jl_atomic_store_release(&jl_world_counter, read_uint(f)); - jl_typeinf_world = read_uint(f); - jl_set_gs_ctr(gs_ctr); + s.s = f; + uintptr_t offset_restored = 0, offset_init_order = 0, offset_extext_methods = 0, offset_new_specializations = 0, offset_method_roots_list = 0; + uintptr_t offset_ext_targets = 0, offset_edges = 0; + if (!s.incremental) { + size_t i; + for (i = 0; tags[i] != NULL; i++) { + jl_value_t **tag = tags[i]; + *tag = jl_read_value(&s); + } + jl_global_roots_table = (jl_array_t*)jl_read_value(&s); + // set typeof extra-special values now that we have the type set by tags above + jl_astaggedvalue(jl_current_task)->header = (uintptr_t)jl_task_type | jl_astaggedvalue(jl_current_task)->header; + jl_astaggedvalue(jl_nothing)->header = (uintptr_t)jl_nothing_type | jl_astaggedvalue(jl_nothing)->header; + s.ptls->root_task->tls = jl_read_value(&s); + jl_gc_wb(s.ptls->root_task, s.ptls->root_task->tls); + jl_init_int32_int64_cache(); + jl_init_box_caches(); + + uint32_t gs_ctr = read_uint32(f); + jl_atomic_store_release(&jl_world_counter, read_uint(f)); + jl_typeinf_world = read_uint(f); + jl_set_gs_ctr(gs_ctr); + } + else { + jl_atomic_fetch_add(&jl_world_counter, 1); + offset_restored = jl_read_offset(&s); + offset_init_order = jl_read_offset(&s); + offset_extext_methods = jl_read_offset(&s); + offset_new_specializations = jl_read_offset(&s); + offset_method_roots_list = jl_read_offset(&s); + offset_ext_targets = jl_read_offset(&s); + offset_edges = jl_read_offset(&s); + } + size_t nlinks_gctags = read_uint32(f); + if (nlinks_gctags > 0) { + s.link_ids_gctags = jl_alloc_array_1d(jl_array_uint64_type, nlinks_gctags); + ios_read(f, (char*)jl_array_data(s.link_ids_gctags), nlinks_gctags * sizeof(uint64_t)); + } + size_t nlinks_relocs = read_uint32(f); + if (nlinks_relocs > 0) { + s.link_ids_relocs = jl_alloc_array_1d(jl_array_uint64_type, nlinks_relocs); + ios_read(f, (char*)jl_array_data(s.link_ids_relocs), nlinks_relocs * sizeof(uint64_t)); + } + size_t nlinks_gvars = read_uint32(f); + if (nlinks_gvars > 0) { + s.link_ids_gvars = jl_alloc_array_1d(jl_array_uint64_type, nlinks_gvars); + ios_read(f, (char*)jl_array_data(s.link_ids_gvars), nlinks_gvars * sizeof(uint64_t)); + } + jl_read_arraylist(s.s, ccallable_list ? ccallable_list : &s.ccallable_list); + if (s.incremental) { + assert(restored && init_order && extext_methods && new_specializations && method_roots_list && ext_targets && edges); + *restored = (jl_array_t*)jl_delayed_reloc(&s, offset_restored); + *init_order = (jl_array_t*)jl_delayed_reloc(&s, offset_init_order); + *extext_methods = (jl_array_t*)jl_delayed_reloc(&s, offset_extext_methods); + *new_specializations = (jl_array_t*)jl_delayed_reloc(&s, offset_new_specializations); + *method_roots_list = (jl_array_t*)jl_delayed_reloc(&s, offset_method_roots_list); + *ext_targets = (jl_array_t*)jl_delayed_reloc(&s, offset_ext_targets); + *edges = (jl_array_t*)jl_delayed_reloc(&s, offset_edges); + } s.s = NULL; // step 3: apply relocations @@ -2250,26 +2773,333 @@ static void jl_restore_system_image_from_stream(ios_t *f) JL_GC_DISABLED jl_read_symbols(&s); ios_close(&symbols); - sysimg_base = &sysimg.buf[0]; - sysimg_relocs = &relocs.buf[0]; - jl_gc_set_permalloc_region((void*)sysimg_base, (void*)(sysimg_base + sysimg.size)); + char *image_base = (char*)&sysimg.buf[0]; + reloc_t *relocs_base = (reloc_t*)&relocs.buf[0]; + if (base) + *base = image_base; s.s = &sysimg; - jl_read_reloclist(&s, GC_OLD); // gctags + jl_read_reloclist(&s, s.link_ids_gctags, GC_OLD); // gctags size_t sizeof_tags = ios_pos(&relocs); (void)sizeof_tags; - jl_read_reloclist(&s, 0); // general relocs + jl_read_reloclist(&s, s.link_ids_relocs, 0); // general relocs + // s.link_ids_gvars will be processed in `jl_update_all_gvars` + jl_update_all_gvars(&s, image); // gvars relocs + if (s.incremental) { + jl_read_arraylist(s.relocs, &s.uniquing_types); + jl_read_arraylist(s.relocs, &s.uniquing_objs); + jl_read_arraylist(s.relocs, &s.fixup_types); + } + else { + arraylist_new(&s.uniquing_types, 0); + arraylist_new(&s.uniquing_objs, 0); + arraylist_new(&s.fixup_types, 0); + } + jl_read_arraylist(s.relocs, &s.fixup_objs); + // Perform the uniquing of objects that we don't "own" and consequently can't promise + // weren't created by some other package before this one got loaded: + // - iterate through all objects that need to be uniqued. The first encounter has to be the + // "reconstructable blob". We either look up the object (if something has created it previously) + // or construct it for the first time, crucially outside the pointer range of any pkgimage. + // This ensures it stays unique-worthy. + // - after we've stored the address of the "real" object (which for convenience we do among the data + // written to allow lookup/reconstruction), then we have to update references to that "reconstructable blob": + // instead of performing the relocation within the package image, we instead (re)direct all references + // to the external object. + arraylist_t cleanup_list; + arraylist_new(&cleanup_list, 0); + arraylist_t delay_list; + arraylist_new(&delay_list, 0); + for (size_t i = 0; i < s.uniquing_types.len; i++) { + uintptr_t item = (uintptr_t)s.uniquing_types.items[i]; + // check whether we are operating on the typetag + // (needing to ignore GC bits) or a regular field + int tag = (item & 1) == 1; + // check whether this is a gvar index + int gvar = (item & 2) == 2; + item &= ~(uintptr_t)3; + uintptr_t *pfld; + jl_value_t **obj, *newobj; + if (gvar) { + if (image->gvars_base == NULL) + continue; + item >>= 2; + assert(item < s.gvar_record->size / sizeof(reloc_t)); + pfld = sysimg_gvars(image->gvars_base, image->gvars_offsets, item); + obj = *(jl_value_t***)pfld; + assert(tag == 0); + } + else { + pfld = (uintptr_t*)(image_base + item); + if (tag) + obj = (jl_value_t**)jl_typeof(jl_valueof(pfld)); + else + obj = *(jl_value_t***)pfld; + if ((char*)obj > (char*)pfld) { + assert(tag == 0); + arraylist_push(&delay_list, pfld); + arraylist_push(&delay_list, obj); + ptrhash_put(&new_dt_objs, (void*)obj, obj); // mark obj as invalid + *pfld = (uintptr_t)NULL; + continue; + } + } + jl_value_t *otyp = jl_typeof(obj); // the original type of the object that was written here + assert(image_base < (char*)obj && (char*)obj <= image_base + sizeof_sysimg + sizeof(uintptr_t)); + if (otyp == (jl_value_t*)jl_datatype_type) { + jl_datatype_t *dt = (jl_datatype_t*)obj[0], *newdt; + if (jl_is_datatype(dt)) { + newdt = dt; // already done + } + else { + dt = (jl_datatype_t*)obj; + arraylist_push(&cleanup_list, (void*)obj); + ptrhash_remove(&new_dt_objs, (void*)obj); // unmark obj as invalid before must_be_new_dt + if (must_be_new_dt((jl_value_t*)dt, &new_dt_objs, image_base, sizeof_sysimg)) + newdt = NULL; + else + newdt = jl_lookup_cache_type_(dt); + if (newdt == NULL) { + // make a non-owned copy of obj so we don't accidentally + // assume this is the unique copy later + newdt = jl_new_uninitialized_datatype(); + jl_astaggedvalue(newdt)->bits.gc = GC_OLD; + // leave most fields undefined for now, but we may need instance later, + // and we overwrite the name field (field 0) now so preserve it too + if (dt->instance) { + assert(dt->instance == jl_nothing); + newdt->instance = dt->instance = jl_gc_permobj(0, newdt); + } + static_assert(offsetof(jl_datatype_t, name) == 0, ""); + newdt->name = dt->name; + ptrhash_put(&new_dt_objs, (void*)newdt, dt); + } + else { + assert(newdt->hash == dt->hash); + } + obj[0] = (jl_value_t*)newdt; + } + newobj = (jl_value_t*)newdt; + } + else { + assert(!(image_base < (char*)otyp && (char*)otyp <= image_base + sizeof_sysimg + sizeof(uintptr_t))); + assert(jl_is_datatype_singleton((jl_datatype_t*)otyp) && "unreachable"); + newobj = ((jl_datatype_t*)otyp)->instance; + assert(newobj != jl_nothing); + arraylist_push(&cleanup_list, (void*)obj); + } + if (tag) + *pfld = (uintptr_t)newobj | GC_OLD; + else + *pfld = (uintptr_t)newobj; + assert(!(image_base < (char*)newobj && (char*)newobj <= image_base + sizeof_sysimg + sizeof(uintptr_t))); + assert(jl_typeis(obj, otyp)); + } + // A few fields (reached via super) might be self-recursive. This is rare, but handle them now. + // They cannot be instances though, since the type must fully exist before the singleton field can be allocated + for (size_t i = 0; i < delay_list.len; ) { + uintptr_t *pfld = (uintptr_t*)delay_list.items[i++]; + jl_value_t **obj = (jl_value_t **)delay_list.items[i++]; + assert(jl_is_datatype(obj)); + jl_datatype_t *dt = (jl_datatype_t*)obj[0]; + assert(jl_is_datatype(dt)); + jl_value_t *newobj = (jl_value_t*)dt; + *pfld = (uintptr_t)newobj; + assert(!(image_base < (char*)newobj && (char*)newobj <= image_base + sizeof_sysimg + sizeof(uintptr_t))); + } + arraylist_free(&delay_list); + // now that all the fields of dt are assigned and unique, copy them into + // their final newdt memory location: this ensures we do not accidentally + // think this pkg image has the singular unique copy of it + void **table = new_dt_objs.table; + for (size_t i = 0; i < new_dt_objs.size; i += 2) { + void *dt = table[i + 1]; + if (dt != HT_NOTFOUND) { + jl_datatype_t *newdt = (jl_datatype_t*)table[i]; + jl_typename_t *name = newdt->name; + static_assert(offsetof(jl_datatype_t, name) == 0, ""); + assert(*(void**)dt == (void*)newdt); + *newdt = *(jl_datatype_t*)dt; // copy the datatype fields (except field 1, which we corrupt above) + newdt->name = name; + } + } + // we should never see these pointers again, so scramble their memory, so any attempt to look at them crashes + for (size_t i = 0; i < cleanup_list.len; i++) { + void *item = cleanup_list.items[i]; + jl_taggedvalue_t *o = jl_astaggedvalue(item); + jl_value_t *t = jl_typeof(item); // n.b. might be 0xbabababa already + if (t == (jl_value_t*)jl_datatype_type) + memset(o, 0xba, sizeof(jl_value_t*) + sizeof(jl_datatype_t)); + else + memset(o, 0xba, sizeof(jl_value_t*) + 0); // singleton + } + arraylist_grow(&cleanup_list, -cleanup_list.len); + // finally cache all our new types now + for (size_t i = 0; i < new_dt_objs.size; i += 2) { + void *dt = table[i + 1]; + if (dt != HT_NOTFOUND) { + jl_datatype_t *newdt = (jl_datatype_t*)table[i]; + jl_cache_type_(newdt); + } + } + for (size_t i = 0; i < s.fixup_types.len; i++) { + uintptr_t item = (uintptr_t)s.fixup_types.items[i]; + jl_value_t *obj = (jl_value_t*)(image_base + item); + assert(jl_is_datatype(obj)); + jl_cache_type_((jl_datatype_t*)obj); + } + // Perform fixups: things like updating world ages, inserting methods & specializations, etc. + size_t world = jl_atomic_load_acquire(&jl_world_counter); + for (size_t i = 0; i < s.uniquing_objs.len; i++) { + uintptr_t item = (uintptr_t)s.uniquing_objs.items[i]; + // check whether this is a gvar index + int gvar = (item & 2) == 2; + item &= ~(uintptr_t)3; + uintptr_t *pfld; + jl_value_t **obj, *newobj; + if (gvar) { + if (image->gvars_base == NULL) + continue; + item >>= 2; + assert(item < s.gvar_record->size / sizeof(reloc_t)); + pfld = sysimg_gvars(image->gvars_base, image->gvars_offsets, item); + obj = *(jl_value_t***)pfld; + } + else { + pfld = (uintptr_t*)(image_base + item); + obj = *(jl_value_t***)pfld; + } + jl_value_t *otyp = jl_typeof(obj); // the original type of the object that was written here + if (otyp == (jl_value_t*)jl_method_instance_type) { + assert(image_base < (char*)obj && (char*)obj <= image_base + sizeof_sysimg + sizeof(uintptr_t)); + jl_value_t *m = obj[0]; + if (jl_is_method_instance(m)) { + newobj = m; // already done + } + else { + arraylist_push(&cleanup_list, (void*)obj); + jl_value_t *specTypes = obj[1]; + jl_value_t *sparams = obj[2]; + newobj = (jl_value_t*)jl_specializations_get_linfo((jl_method_t*)m, specTypes, (jl_svec_t*)sparams); + obj[0] = newobj; + } + } + else if (otyp == (jl_value_t*)jl_globalref_type) { + // this actually needs a binding_t object at that gvar slot if we encountered it in the uniquing_objs + jl_globalref_t *g = (jl_globalref_t*)obj; + jl_binding_t *b = jl_get_binding_if_bound(g->mod, g->name); + assert(b); // XXX: actually this is probably quite buggy, since julia's handling of global resolution is rather bad + newobj = (jl_value_t*)b; + } + else { + abort(); // should be unreachable + } + *pfld = (uintptr_t)newobj; + assert(!(image_base < (char*)newobj && (char*)newobj <= image_base + sizeof_sysimg + sizeof(uintptr_t))); + assert(jl_typeis(obj, otyp)); + } + arraylist_free(&s.uniquing_types); + arraylist_free(&s.uniquing_objs); + for (size_t i = 0; i < cleanup_list.len; i++) { + void *item = cleanup_list.items[i]; + jl_taggedvalue_t *o = jl_astaggedvalue(item); + jl_value_t *t = jl_typeof(item); + if (t == (jl_value_t*)jl_method_instance_type) + memset(o, 0xba, sizeof(jl_value_t*) * 3); // only specTypes and sparams fields stored + } + arraylist_free(&cleanup_list); + for (size_t i = 0; i < s.fixup_objs.len; i++) { + uintptr_t item = (uintptr_t)s.fixup_objs.items[i]; + jl_value_t *obj = (jl_value_t*)(image_base + item); + if (jl_typeis(obj, jl_typemap_entry_type)) { + jl_typemap_entry_t *entry = (jl_typemap_entry_t*)obj; + entry->min_world = world; + } + else if (jl_is_method(obj)) { + jl_method_t *m = (jl_method_t*)obj; + m->primary_world = world; + } + else if (jl_is_method_instance(obj)) { + jl_method_instance_t *newobj = jl_specializations_get_or_insert((jl_method_instance_t*)obj); + assert(newobj == (jl_method_instance_t*)obj); // strict insertion expected + (void)newobj; + } + else if (jl_is_code_instance(obj)) { + jl_code_instance_t *ci = (jl_code_instance_t*)obj; + assert(s.incremental); + ci->min_world = world; + if (ci->max_world == 1) { // sentinel value: has edges to external callables + ptrhash_put(&new_code_instance_validate, ci, (void*)(~(uintptr_t)HT_NOTFOUND)); // "HT_FOUND" + } + else if (ci->max_world) { + // It's valid, but it may not be connected + if (!ci->def->cache) + ci->def->cache = ci; + } + else { + // Ensure this code instance is not connected + if (ci->def->cache == ci) + ci->def->cache = NULL; + } + } + else if (jl_is_globalref(obj)) { + continue; // wait until all the module binding tables have been initialized + } + else if (jl_is_module(obj)) { + // rebuild the binding table for module v + // TODO: maybe want to delay this more, but that only strongly matters for async / thread safety + // and we are already bad at that + jl_module_t *mod = (jl_module_t*)obj; + mod->build_id.hi = checksum; + size_t nbindings = mod->bindings.size; + htable_new(&mod->bindings, nbindings); + struct binding { + jl_sym_t *asname; + uintptr_t tag; + jl_binding_t b; + } *b; + b = (struct binding*)&mod[1]; + while (nbindings > 0) { + ptrhash_put(&mod->bindings, b->asname, &b->b); + b += 1; + nbindings -= 1; + } + if (mod->usings.items != &mod->usings._space[0]) { + void **newitems = (void**)malloc_s(mod->usings.max * sizeof(void*)); + memcpy(newitems, mod->usings.items, mod->usings.len * sizeof(void*)); + mod->usings.items = newitems; + } + } + else { + // rehash IdDict + //assert(((jl_datatype_t*)(jl_typeof(obj)))->name == jl_idtable_typename); + jl_array_t **a = (jl_array_t**)obj; + assert(jl_typeis(*a, jl_array_any_type)); + *a = jl_idtable_rehash(*a, jl_array_len(*a)); + jl_gc_wb(obj, *a); + } + } + // Now pick up the globalref binding pointer field, when we can + for (size_t i = 0; i < s.fixup_objs.len; i++) { + uintptr_t item = (uintptr_t)s.fixup_objs.items[i]; + jl_value_t *obj = (jl_value_t*)(image_base + item); + if (jl_is_globalref(obj)) { + jl_globalref_t *r = (jl_globalref_t*)obj; + jl_binding_t *b = jl_get_binding_if_bound(r->mod, r->name); + r->bnd_cache = b && b->value ? b : NULL; + } + } + arraylist_free(&s.fixup_types); + arraylist_free(&s.fixup_objs); + + if (s.incremental) + jl_root_new_gvars(&s, image); ios_close(&relocs); ios_close(&const_data); - jl_update_all_gvars(&s); // gvars relocs ios_close(&gvar_record); - s.s = NULL; - jl_kwcall_mt = ((jl_datatype_t*)jl_typeof(jl_kwcall_func))->name->mt; + htable_free(&new_dt_objs); - s.s = f; - // reinit items except ccallables - jl_finalize_deserializer(&s); s.s = NULL; if (0) { @@ -2289,21 +3119,166 @@ static void jl_restore_system_image_from_stream(ios_t *f) JL_GC_DISABLED (unsigned)sizeof_gvar_record, (unsigned)sizeof_fptr_record); } + if (cachesizes) { + cachesizes->sysdata = sizeof_sysimg; + cachesizes->isbitsdata = sizeof_constdata; + cachesizes->symboldata = sizeof_symbols; + cachesizes->tagslist = sizeof_tags; + cachesizes->reloclist = sizeof_relocations - sizeof_tags; + cachesizes->gvarlist = sizeof_gvar_record; + cachesizes->fptrlist = sizeof_fptr_record; + } + if (!s.incremental) + jl_init_codegen(); s.s = &sysimg; - jl_init_codegen(); - jl_update_all_fptrs(&s); // fptr relocs and registration - // reinit ccallables, which require codegen to be initialized - s.s = f; - jl_finalize_deserializer(&s); + jl_update_all_fptrs(&s, image); // fptr relocs and registration + if (!ccallable_list) { + // TODO: jl_sysimg_handle or img_handle? + jl_reinit_ccallable(&s.ccallable_list, image_base, jl_sysimg_handle); + arraylist_free(&s.ccallable_list); + } + s.s = NULL; ios_close(&fptr_record); ios_close(&sysimg); - s.s = NULL; - jl_gc_reset_alloc_count(); + if (!s.incremental) + jl_gc_reset_alloc_count(); + arraylist_free(&deser_sym); + + // Prepare for later external linkage against the sysimg + // Also sets up images for protection against garbage collection + arraylist_push(&jl_linkage_blobs, (void*)image_base); + arraylist_push(&jl_linkage_blobs, (void*)(image_base + sizeof_sysimg + sizeof(uintptr_t))); + arraylist_push(&jl_image_relocs, (void*)relocs_base); + + // jl_printf(JL_STDOUT, "%ld blobs to link against\n", jl_linkage_blobs.len >> 1); + uint64_t buildid = (((uint64_t)read_uint32(f)) << 32) | read_uint32(f); + if (!jl_build_ids) + jl_build_ids = jl_alloc_array_1d(jl_array_uint64_type, 0); + jl_array_grow_end(jl_build_ids, 1); + uint64_t *build_id_data = (uint64_t*)jl_array_data(jl_build_ids); + build_id_data[jl_array_len(jl_build_ids)-1] = buildid; jl_gc_enable(en); - jl_cleanup_serializer2(); +} + +static jl_value_t *jl_validate_cache_file(ios_t *f, jl_array_t *depmods, uint64_t *checksum, int64_t *dataendpos) +{ + if (ios_eof(f) || 0 == (*checksum = jl_read_verify_header(f)) || (*checksum >> 32 != 0xfafbfcfd)) { + return jl_get_exceptionf(jl_errorexception_type, + "Precompile file header verification checks failed."); + } + { // skip past the mod list + size_t len; + while ((len = read_int32(f))) + ios_skip(f, len + 3 * sizeof(uint64_t)); + } + { // skip past the dependency list + size_t deplen = read_uint64(f); + ios_skip(f, deplen - sizeof(uint64_t)); + *dataendpos = read_uint64(f); + } + + // verify that the system state is valid + return read_verify_mod_list(f, depmods); +} + +// TODO?: refactor to make it easier to create the "package inspector" +static jl_value_t *jl_restore_package_image_from_stream(ios_t *f, jl_image_t *image, jl_array_t *depmods, int complete) +{ + uint64_t checksum = 0; + int64_t dataendpos = 0; + jl_value_t *verify_fail = jl_validate_cache_file(f, depmods, &checksum, &dataendpos); + if (verify_fail) + return verify_fail; + + jl_value_t *restored = NULL; + jl_array_t *init_order = NULL, *extext_methods = NULL, *new_specializations = NULL, *method_roots_list = NULL, *ext_targets = NULL, *edges = NULL; + jl_svec_t *cachesizes_sv = NULL; + char *base; + arraylist_t ccallable_list; + JL_GC_PUSH8(&restored, &init_order, &extext_methods, &new_specializations, &method_roots_list, &ext_targets, &edges, &cachesizes_sv); + + { // make a permanent in-memory copy of f (excluding the header) + ios_bufmode(f, bm_none); + JL_SIGATOMIC_BEGIN(); + size_t len_begin = LLT_ALIGN(ios_pos(f), JL_CACHE_BYTE_ALIGNMENT); + assert(len_begin > 0 && len_begin < dataendpos); + size_t len = dataendpos - len_begin; + char *sysimg = (char*)jl_gc_perm_alloc(len, 0, 64, 0); + ios_seek(f, len_begin); + if (ios_readall(f, sysimg, len) != len || jl_crc32c(0, sysimg, len) != (uint32_t)checksum) { + restored = jl_get_exceptionf(jl_errorexception_type, "Error reading system image file."); + JL_SIGATOMIC_END(); + } + else { + ios_close(f); + ios_static_buffer(f, sysimg, len); + htable_new(&new_code_instance_validate, 0); + pkgcachesizes cachesizes; + jl_restore_system_image_from_stream_(f, image, depmods, checksum, (jl_array_t**)&restored, &init_order, &extext_methods, &new_specializations, &method_roots_list, &ext_targets, &edges, &base, &ccallable_list, &cachesizes); + JL_SIGATOMIC_END(); + + // Insert method extensions + jl_insert_methods(extext_methods); + // No special processing of `new_specializations` is required because recaching handled it + // Add roots to methods + jl_copy_roots(method_roots_list, jl_worklist_key((jl_array_t*)restored)); + // Handle edges + jl_insert_backedges((jl_array_t*)edges, (jl_array_t*)ext_targets, (jl_array_t*)new_specializations); // restore external backedges (needs to be last) + // check new CodeInstances and validate any that lack external backedges + validate_new_code_instances(); + // reinit ccallables + jl_reinit_ccallable(&ccallable_list, base, NULL); + arraylist_free(&ccallable_list); + htable_free(&new_code_instance_validate); + if (complete) { + cachesizes_sv = jl_alloc_svec_uninit(7); + jl_svec_data(cachesizes_sv)[0] = jl_box_long(cachesizes.sysdata); + jl_svec_data(cachesizes_sv)[1] = jl_box_long(cachesizes.isbitsdata); + jl_svec_data(cachesizes_sv)[2] = jl_box_long(cachesizes.symboldata); + jl_svec_data(cachesizes_sv)[3] = jl_box_long(cachesizes.tagslist); + jl_svec_data(cachesizes_sv)[4] = jl_box_long(cachesizes.reloclist); + jl_svec_data(cachesizes_sv)[5] = jl_box_long(cachesizes.gvarlist); + jl_svec_data(cachesizes_sv)[6] = jl_box_long(cachesizes.fptrlist); + restored = (jl_value_t*)jl_svec(8, restored, init_order, extext_methods, new_specializations, method_roots_list, + ext_targets, edges, cachesizes_sv); + } else + restored = (jl_value_t*)jl_svec(2, restored, init_order); + } + } + + JL_GC_POP(); + return restored; +} + +static void jl_restore_system_image_from_stream(ios_t *f, jl_image_t *image) +{ + uint64_t checksum = 0; // TODO: make this real + jl_restore_system_image_from_stream_(f, image, NULL, checksum, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +} + +JL_DLLEXPORT jl_value_t *jl_restore_incremental_from_buf(const char *buf, jl_image_t *image, size_t sz, jl_array_t *depmods, int complete) +{ + ios_t f; + ios_static_buffer(&f, (char*)buf, sz); + jl_value_t *ret = jl_restore_package_image_from_stream(&f, image, depmods, complete); + ios_close(&f); + return ret; +} + +JL_DLLEXPORT jl_value_t *jl_restore_incremental(const char *fname, jl_array_t *depmods, int complete) +{ + ios_t f; + if (ios_file(&f, fname, 1, 0, 0, 0) == NULL) { + return jl_get_exceptionf(jl_errorexception_type, + "Cache file \"%s\" not found.\n", fname); + } + jl_image_t pkgimage = {}; + jl_value_t *ret = jl_restore_package_image_from_stream(&f, &pkgimage, depmods, complete); + ios_close(&f); + return ret; } // TODO: need to enforce that the alignment of the buffer is suitable for vectors @@ -2333,7 +3308,7 @@ JL_DLLEXPORT void jl_restore_system_image(const char *fname) jl_errorf("Error reading system image file."); ios_close(&f); ios_static_buffer(&f, sysimg, len); - jl_restore_system_image_from_stream(&f); + jl_restore_system_image_from_stream(&f, &sysimage); ios_close(&f); JL_SIGATOMIC_END(); } @@ -2344,38 +3319,52 @@ JL_DLLEXPORT void jl_restore_system_image_data(const char *buf, size_t len) ios_t f; JL_SIGATOMIC_BEGIN(); ios_static_buffer(&f, (char*)buf, len); - jl_restore_system_image_from_stream(&f); + jl_restore_system_image_from_stream(&f, &sysimage); ios_close(&f); JL_SIGATOMIC_END(); } -// --- init --- - -static void jl_init_serializer2(int for_serialize) +JL_DLLEXPORT jl_value_t *jl_restore_package_image_from_file(const char *fname, jl_array_t *depmods) { - if (for_serialize) { - htable_new(&symbol_table, 0); - htable_new(&fptr_to_id, sizeof(id_to_fptrs) / sizeof(*id_to_fptrs)); - htable_new(&backref_table, 0); - htable_new(&layout_cache, 0); - uintptr_t i; - for (i = 0; id_to_fptrs[i] != NULL; i++) { - ptrhash_put(&fptr_to_id, (void*)(uintptr_t)id_to_fptrs[i], (void*)(i + 2)); - } + void *pkgimg_handle = jl_dlopen(fname, JL_RTLD_LAZY); + if (!pkgimg_handle) { +#ifdef _OS_WINDOWS_ + int err; + char reason[256]; + err = GetLastError(); + win32_formatmessage(err, reason, sizeof(reason)); +#else + const char *reason = dlerror(); +#endif + jl_errorf("Error opening package file %s: %s\n", fname, reason); } - else { - arraylist_new(&deser_sym, 0); + const char *pkgimg_data; + jl_dlsym(pkgimg_handle, "jl_system_image_data", (void **)&pkgimg_data, 1); + size_t *plen; + jl_dlsym(pkgimg_handle, "jl_system_image_size", (void **)&plen, 1); + + jl_image_t pkgimage; + pkgimage.fptrs = jl_init_processor_pkgimg(pkgimg_handle); + if (!jl_dlsym(pkgimg_handle, "jl_sysimg_gvars_base", (void **)&pkgimage.gvars_base, 0)) { + pkgimage.gvars_base = NULL; } - nsym_tag = 0; -} + jl_dlsym(pkgimg_handle, "jl_sysimg_gvars_offsets", (void **)&pkgimage.gvars_offsets, 1); + pkgimage.gvars_offsets += 1; + jl_value_t* mod = jl_restore_incremental_from_buf(pkgimg_data, &pkgimage, *plen, depmods, 0); -static void jl_cleanup_serializer2(void) -{ - htable_reset(&symbol_table, 0); - htable_reset(&fptr_to_id, 0); - htable_reset(&backref_table, 0); - htable_reset(&layout_cache, 0); - arraylist_free(&deser_sym); + void *pgcstack_func_slot; + jl_dlsym(pkgimg_handle, "jl_pgcstack_func_slot", &pgcstack_func_slot, 0); + if (pgcstack_func_slot) { // Empty package images might miss these + void *pgcstack_key_slot; + jl_dlsym(pkgimg_handle, "jl_pgcstack_key_slot", &pgcstack_key_slot, 1); + jl_pgcstack_getkey((jl_get_pgcstack_func**)pgcstack_func_slot, (jl_pgcstack_key_t*)pgcstack_key_slot); + + size_t *tls_offset_idx; + jl_dlsym(pkgimg_handle, "jl_tls_offset", (void **)&tls_offset_idx, 1); + *tls_offset_idx = (uintptr_t)(jl_tls_offset == -1 ? 0 : jl_tls_offset); + } + + return mod; } #ifdef __cplusplus diff --git a/src/staticdata_utils.c b/src/staticdata_utils.c new file mode 100644 index 00000000000000..3d02dddbd5a708 --- /dev/null +++ b/src/staticdata_utils.c @@ -0,0 +1,1279 @@ +static htable_t new_code_instance_validate; +static htable_t external_mis; + +// inverse of backedges graph (caller=>callees hash) +jl_array_t *edges_map JL_GLOBALLY_ROOTED = NULL; // rooted for the duration of our uses of this + +static void write_float64(ios_t *s, double x) JL_NOTSAFEPOINT +{ + write_uint64(s, *((uint64_t*)&x)); +} + +// Decide if `t` must be new, because it points to something new. +// If it is new, the object (in particular, the super field) might not be entirely +// valid for the cache, so we want to finish transforming it before attempting +// to look in the cache for it +int must_be_new_dt(jl_value_t *t, htable_t *news, char *image_base, size_t sizeof_sysimg) +{ + //if (jl_object_in_image(t)) + // return 0; // fast-path for rejection + assert(ptrhash_get(news, (void*)t) != (void*)t); + if (ptrhash_has(news, (void*)t) || ptrhash_has(news, (void*)jl_typeof(t))) + return 1; + if (!(image_base < (char*)t && (char*)t <= image_base + sizeof_sysimg)) + return 0; // fast-path for rejection + if (jl_is_uniontype(t)) { + jl_uniontype_t *u = (jl_uniontype_t*)t; + return must_be_new_dt(u->a, news, image_base, sizeof_sysimg) || + must_be_new_dt(u->b, news, image_base, sizeof_sysimg); + } + else if (jl_is_unionall(t)) { + jl_unionall_t *ua = (jl_unionall_t*)t; + return must_be_new_dt((jl_value_t*)ua->var, news, image_base, sizeof_sysimg) || + must_be_new_dt(ua->body, news, image_base, sizeof_sysimg); + } + else if (jl_is_typevar(t)) { + jl_tvar_t *tv = (jl_tvar_t*)t; + return must_be_new_dt(tv->lb, news, image_base, sizeof_sysimg) || + must_be_new_dt(tv->ub, news, image_base, sizeof_sysimg); + } + else if (jl_is_vararg(t)) { + jl_vararg_t *tv = (jl_vararg_t*)t; + if (tv->T && must_be_new_dt(tv->T, news, image_base, sizeof_sysimg)) + return 1; + if (tv->N && must_be_new_dt(tv->N, news, image_base, sizeof_sysimg)) + return 1; + } + else if (jl_is_datatype(t)) { + jl_datatype_t *dt = (jl_datatype_t*)t; + assert(jl_object_in_image((jl_value_t*)dt->name) && "type_in_worklist mistake?"); + jl_datatype_t *super = dt->super; + // check if super is news, since then we must be new also + // (it is also possible that super is indeterminate now, wait for `t` + // to be resolved, then will be determined later and fixed up by the + // delay_list, for this and any other references to it). + while (super != jl_any_type) { + assert(super); + if (ptrhash_has(news, (void*)super)) + return 1; + if (!(image_base < (char*)super && (char*)super <= image_base + sizeof_sysimg)) + break; // fast-path for rejection of super + // otherwise super might be something that was not cached even though a later supertype might be + // for example while handling `Type{Mask{4, U} where U}`, if we have `Mask{4, U} <: AbstractSIMDVector{4}` + super = super->super; + } + jl_svec_t *tt = dt->parameters; + size_t i, l = jl_svec_len(tt); + for (i = 0; i < l; i++) + if (must_be_new_dt(jl_tparam(dt, i), news, image_base, sizeof_sysimg)) + return 1; + } + else { + return must_be_new_dt(jl_typeof(t), news, image_base, sizeof_sysimg); + } + return 0; +} + +static uint64_t jl_worklist_key(jl_array_t *worklist) JL_NOTSAFEPOINT +{ + assert(jl_is_array(worklist)); + size_t len = jl_array_len(worklist); + if (len > 0) { + jl_module_t *topmod = (jl_module_t*)jl_array_ptr_ref(worklist, len-1); + assert(jl_is_module(topmod)); + return topmod->build_id.lo; + } + return 0; +} + +static jl_array_t *newly_inferred JL_GLOBALLY_ROOTED /*FIXME*/; +// Mutex for newly_inferred +static jl_mutex_t newly_inferred_mutex; + +// Register array of newly-inferred MethodInstances +// This gets called as the first step of Base.include_package_for_output +JL_DLLEXPORT void jl_set_newly_inferred(jl_value_t* _newly_inferred) +{ + assert(_newly_inferred == NULL || jl_is_array(_newly_inferred)); + newly_inferred = (jl_array_t*) _newly_inferred; +} + +JL_DLLEXPORT void jl_push_newly_inferred(jl_value_t* ci) +{ + JL_LOCK(&newly_inferred_mutex); + size_t end = jl_array_len(newly_inferred); + jl_array_grow_end(newly_inferred, 1); + jl_arrayset(newly_inferred, ci, end); + JL_UNLOCK(&newly_inferred_mutex); +} + + +static int method_instance_in_queue(jl_method_instance_t *mi) +{ + return ptrhash_get(&external_mis, mi) != HT_NOTFOUND; +} + +// compute whether a type references something internal to worklist +// and thus could not have existed before deserialize +// and thus does not need delayed unique-ing +static int type_in_worklist(jl_value_t *v) JL_NOTSAFEPOINT +{ + if (jl_object_in_image(v)) + return 0; // fast-path for rejection + if (jl_is_uniontype(v)) { + jl_uniontype_t *u = (jl_uniontype_t*)v; + return type_in_worklist(u->a) || + type_in_worklist(u->b); + } + else if (jl_is_unionall(v)) { + jl_unionall_t *ua = (jl_unionall_t*)v; + return type_in_worklist((jl_value_t*)ua->var) || + type_in_worklist(ua->body); + } + else if (jl_is_typevar(v)) { + jl_tvar_t *tv = (jl_tvar_t*)v; + return type_in_worklist(tv->lb) || + type_in_worklist(tv->ub); + } + else if (jl_is_vararg(v)) { + jl_vararg_t *tv = (jl_vararg_t*)v; + if (tv->T && type_in_worklist(tv->T)) + return 1; + if (tv->N && type_in_worklist(tv->N)) + return 1; + } + else if (jl_is_datatype(v)) { + jl_datatype_t *dt = (jl_datatype_t*)v; + if (!jl_object_in_image((jl_value_t*)dt->name)) + return 1; + jl_svec_t *tt = dt->parameters; + size_t i, l = jl_svec_len(tt); + for (i = 0; i < l; i++) + if (type_in_worklist(jl_tparam(dt, i))) + return 1; + } + else { + return type_in_worklist(jl_typeof(v)); + } + return 0; +} + +static void mark_backedges_in_worklist(jl_method_instance_t *mi, htable_t *visited, int found) +{ + int oldfound = (char*)ptrhash_get(visited, mi) - (char*)HT_NOTFOUND; + if (oldfound < 3) + return; // not in-progress + ptrhash_put(visited, mi, (void*)((char*)HT_NOTFOUND + 1 + found)); +#ifndef NDEBUG + jl_module_t *mod = mi->def.module; + if (jl_is_method(mod)) + mod = ((jl_method_t*)mod)->module; + assert(jl_is_module(mod)); + assert(!mi->precompiled && jl_object_in_image((jl_value_t*)mod)); + assert(mi->backedges); +#endif + size_t i = 0, n = jl_array_len(mi->backedges); + while (i < n) { + jl_method_instance_t *be; + i = get_next_edge(mi->backedges, i, NULL, &be); + mark_backedges_in_worklist(be, visited, found); + } +} + +// When we infer external method instances, ensure they link back to the +// package. Otherwise they might be, e.g., for external macros +static int has_backedge_to_worklist(jl_method_instance_t *mi, htable_t *visited, int depth) +{ + jl_module_t *mod = mi->def.module; + if (jl_is_method(mod)) + mod = ((jl_method_t*)mod)->module; + assert(jl_is_module(mod)); + if (mi->precompiled || !jl_object_in_image((jl_value_t*)mod)) { + return 1; + } + if (!mi->backedges) { + return 0; + } + void **bp = ptrhash_bp(visited, mi); + // HT_NOTFOUND: not yet analyzed + // HT_NOTFOUND + 1: no link back + // HT_NOTFOUND + 2: does link back + // HT_NOTFOUND + 3 + depth: in-progress + int found = (char*)*bp - (char*)HT_NOTFOUND; + if (found) + return found - 1; + *bp = (void*)((char*)HT_NOTFOUND + 3 + depth); // preliminarily mark as in-progress + size_t i = 0, n = jl_array_len(mi->backedges); + int cycle = 0; + while (i < n) { + jl_method_instance_t *be; + i = get_next_edge(mi->backedges, i, NULL, &be); + int child_found = has_backedge_to_worklist(be, visited, depth + 1); + if (child_found == 1) { + found = 1; + break; + } + else if (child_found >= 2 && child_found - 2 < cycle) { + // record the cycle will resolve at depth "cycle" + cycle = child_found - 2; + assert(cycle); + } + } + if (!found && cycle && cycle != depth) + return cycle + 2; + bp = ptrhash_bp(visited, mi); // re-acquire since rehashing might change the location + *bp = (void*)((char*)HT_NOTFOUND + 1 + found); + if (cycle) { + // If we are the top of the current cycle, now mark all other parts of + // our cycle by re-walking the backedges graph and marking all WIP + // items as found. + // Be careful to only re-walk as far as we had originally scanned above. + // Or if we found a backedge, also mark all of the other parts of the + // cycle as also having an backedge. + n = i; + i = 0; + while (i < n) { + jl_method_instance_t *be; + i = get_next_edge(mi->backedges, i, NULL, &be); + mark_backedges_in_worklist(be, visited, found); + } + } + return found; +} + +// given the list of CodeInstances that were inferred during the +// build, select those that are (1) external, and (2) are inferred to be called +// from the worklist or explicitly added by a `precompile` statement. +// Also prepares for method_instance_in_queue queries. +static jl_array_t *queue_external_cis(jl_array_t *list) +{ + if (list == NULL) + return NULL; + size_t i; + htable_t visited; + assert(jl_is_array(list)); + size_t n0 = jl_array_len(list); + htable_new(&visited, n0); + jl_array_t *new_specializations = jl_alloc_vec_any(0); + JL_GC_PUSH1(&new_specializations); + for (i = 0; i < n0; i++) { + jl_code_instance_t *ci = (jl_code_instance_t*)jl_array_ptr_ref(list, i); + assert(jl_is_code_instance(ci)); + jl_method_instance_t *mi = ci->def; + jl_method_t *m = mi->def.method; + if (jl_is_method(m)) { + if (jl_object_in_image((jl_value_t*)m->module)) { + if (ptrhash_get(&external_mis, mi) == HT_NOTFOUND) { + int found = has_backedge_to_worklist(mi, &visited, 1); + assert(found == 0 || found == 1); + if (found == 1) { + ptrhash_put(&external_mis, mi, mi); + jl_array_ptr_1d_push(new_specializations, (jl_value_t*)ci); + } + } + } + } + } + htable_free(&visited); + JL_GC_POP(); + return new_specializations; +} + +// New roots for external methods +static void jl_collect_methods(htable_t *mset, jl_array_t *new_specializations) +{ + size_t i, l = new_specializations ? jl_array_len(new_specializations) : 0; + jl_value_t *v; + jl_method_t *m; + for (i = 0; i < l; i++) { + v = jl_array_ptr_ref(new_specializations, i); + assert(jl_is_code_instance(v)); + m = ((jl_code_instance_t*)v)->def->def.method; + assert(jl_is_method(m)); + ptrhash_put(mset, (void*)m, (void*)m); + } +} + +static void jl_collect_new_roots(jl_array_t *roots, htable_t *mset, uint64_t key) +{ + size_t i, sz = mset->size; + int nwithkey; + jl_method_t *m; + void **table = mset->table; + jl_array_t *newroots = NULL; + JL_GC_PUSH1(&newroots); + for (i = 0; i < sz; i += 2) { + if (table[i+1] != HT_NOTFOUND) { + m = (jl_method_t*)table[i]; + assert(jl_is_method(m)); + nwithkey = nroots_with_key(m, key); + if (nwithkey) { + jl_array_ptr_1d_push(roots, (jl_value_t*)m); + newroots = jl_alloc_vec_any(nwithkey); + jl_array_ptr_1d_push(roots, (jl_value_t*)newroots); + rle_iter_state rootiter = rle_iter_init(0); + uint64_t *rletable = NULL; + size_t nblocks2 = 0, nroots = jl_array_len(m->roots), k = 0; + if (m->root_blocks) { + rletable = (uint64_t*)jl_array_data(m->root_blocks); + nblocks2 = jl_array_len(m->root_blocks); + } + while (rle_iter_increment(&rootiter, nroots, rletable, nblocks2)) + if (rootiter.key == key) + jl_array_ptr_set(newroots, k++, jl_array_ptr_ref(m->roots, rootiter.i)); + assert(k == nwithkey); + } + } + } + JL_GC_POP(); +} + +// Create the forward-edge map (caller => callees) +// the intent of these functions is to invert the backedges tree +// for anything that points to a method not part of the worklist +// +// from MethodTables +static void jl_collect_missing_backedges(jl_methtable_t *mt) +{ + jl_array_t *backedges = mt->backedges; + if (backedges) { + size_t i, l = jl_array_len(backedges); + for (i = 1; i < l; i += 2) { + jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(backedges, i); + jl_value_t *missing_callee = jl_array_ptr_ref(backedges, i - 1); // signature of abstract callee + jl_array_t *edges = (jl_array_t*)jl_eqtable_get(edges_map, (jl_value_t*)caller, NULL); + if (edges == NULL) { + edges = jl_alloc_vec_any(0); + JL_GC_PUSH1(&edges); + edges_map = jl_eqtable_put(edges_map, (jl_value_t*)caller, (jl_value_t*)edges, NULL); + JL_GC_POP(); + } + jl_array_ptr_1d_push(edges, NULL); + jl_array_ptr_1d_push(edges, missing_callee); + } + } +} + + +// from MethodInstances +static void collect_backedges(jl_method_instance_t *callee, int internal) +{ + jl_array_t *backedges = callee->backedges; + if (backedges) { + size_t i = 0, l = jl_array_len(backedges); + while (i < l) { + jl_value_t *invokeTypes; + jl_method_instance_t *caller; + i = get_next_edge(backedges, i, &invokeTypes, &caller); + jl_array_t *edges = (jl_array_t*)jl_eqtable_get(edges_map, (jl_value_t*)caller, NULL); + if (edges == NULL) { + edges = jl_alloc_vec_any(0); + JL_GC_PUSH1(&edges); + edges_map = jl_eqtable_put(edges_map, (jl_value_t*)caller, (jl_value_t*)edges, NULL); + JL_GC_POP(); + } + jl_array_ptr_1d_push(edges, invokeTypes); + jl_array_ptr_1d_push(edges, (jl_value_t*)callee); + } + } +} + + +// For functions owned by modules not on the worklist, call this on each method. +// - if the method is owned by a worklist module, add it to the list of things to be +// fully serialized +// - Collect all backedges (may be needed later when we invert this list). +static int jl_collect_methcache_from_mod(jl_typemap_entry_t *ml, void *closure) +{ + jl_array_t *s = (jl_array_t*)closure; + jl_method_t *m = ml->func.method; + if (s && !jl_object_in_image((jl_value_t*)m->module)) { + jl_array_ptr_1d_push(s, (jl_value_t*)m); + } + jl_svec_t *specializations = m->specializations; + size_t i, l = jl_svec_len(specializations); + for (i = 0; i < l; i++) { + jl_method_instance_t *callee = (jl_method_instance_t*)jl_svecref(specializations, i); + if ((jl_value_t*)callee != jl_nothing) + collect_backedges(callee, !s); + } + return 1; +} + +static void jl_collect_methtable_from_mod(jl_array_t *s, jl_methtable_t *mt) +{ + jl_typemap_visitor(mt->defs, jl_collect_methcache_from_mod, (void*)s); +} + +// Collect methods of external functions defined by modules in the worklist +// "extext" = "extending external" +// Also collect relevant backedges +static void jl_collect_extext_methods_from_mod(jl_array_t *s, jl_module_t *m) +{ + if (s && !jl_object_in_image((jl_value_t*)m)) + s = NULL; // do not collect any methods + size_t i; + void **table = m->bindings.table; + for (i = 1; i < m->bindings.size; i += 2) { + if (table[i] != HT_NOTFOUND) { + jl_binding_t *b = (jl_binding_t*)table[i]; + if (b->owner == m && b->value && b->constp) { + jl_value_t *bv = jl_unwrap_unionall(b->value); + if (jl_is_datatype(bv)) { + jl_typename_t *tn = ((jl_datatype_t*)bv)->name; + if (tn->module == m && tn->name == b->name && tn->wrapper == b->value) { + jl_methtable_t *mt = tn->mt; + if (mt != NULL && + (jl_value_t*)mt != jl_nothing && + (mt != jl_type_type_mt && mt != jl_nonfunction_mt)) { + assert(mt->module == tn->module); + jl_collect_methtable_from_mod(s, mt); + if (s) + jl_collect_missing_backedges(mt); + } + } + } + else if (jl_is_module(b->value)) { + jl_module_t *child = (jl_module_t*)b->value; + if (child != m && child->parent == m && child->name == b->name) { + // this is the original/primary binding for the submodule + jl_collect_extext_methods_from_mod(s, (jl_module_t*)b->value); + } + } + else if (jl_is_mtable(b->value)) { + jl_methtable_t *mt = (jl_methtable_t*)b->value; + if (mt->module == m && mt->name == b->name) { + // this is probably an external method table, so let's assume so + // as there is no way to precisely distinguish them, + // and the rest of this serializer does not bother + // to handle any method tables specially + jl_collect_methtable_from_mod(s, (jl_methtable_t*)bv); + } + } + } + } + } +} + +static void jl_record_edges(jl_method_instance_t *caller, arraylist_t *wq, jl_array_t *edges) +{ + jl_array_t *callees = NULL; + JL_GC_PUSH2(&caller, &callees); + callees = (jl_array_t*)jl_eqtable_pop(edges_map, (jl_value_t*)caller, NULL, NULL); + if (callees != NULL) { + jl_array_ptr_1d_push(edges, (jl_value_t*)caller); + jl_array_ptr_1d_push(edges, (jl_value_t*)callees); + size_t i, l = jl_array_len(callees); + for (i = 1; i < l; i += 2) { + jl_method_instance_t *c = (jl_method_instance_t*)jl_array_ptr_ref(callees, i); + if (c && jl_is_method_instance(c)) { + arraylist_push(wq, c); + } + } + } + JL_GC_POP(); +} + + +// Extract `edges` and `ext_targets` from `edges_map` +// `edges` = [caller1, targets_indexes1, ...], the list of methods and their edges +// `ext_targets` is [invokesig1, callee1, matches1, ...], the edges for each target +static void jl_collect_edges(jl_array_t *edges, jl_array_t *ext_targets) +{ + size_t world = jl_atomic_load_acquire(&jl_world_counter); + arraylist_t wq; + arraylist_new(&wq, 0); + void **table = (void**)jl_array_data(edges_map); // edges_map is caller => callees + size_t table_size = jl_array_len(edges_map); + for (size_t i = 0; i < table_size; i += 2) { + assert(table == jl_array_data(edges_map) && table_size == jl_array_len(edges_map) && + "edges_map changed during iteration"); + jl_method_instance_t *caller = (jl_method_instance_t*)table[i]; + jl_array_t *callees = (jl_array_t*)table[i + 1]; + if (callees == NULL) + continue; + assert(jl_is_method_instance(caller) && jl_is_method(caller->def.method)); + if (!jl_object_in_image((jl_value_t*)caller->def.method->module) || + method_instance_in_queue(caller)) { + jl_record_edges(caller, &wq, edges); + } + } + while (wq.len) { + jl_method_instance_t *caller = (jl_method_instance_t*)arraylist_pop(&wq); + jl_record_edges(caller, &wq, edges); + } + arraylist_free(&wq); + edges_map = NULL; + htable_t edges_map2; + htable_new(&edges_map2, 0); + htable_t edges_ids; + size_t l = edges ? jl_array_len(edges) : 0; + htable_new(&edges_ids, l); + for (size_t i = 0; i < l / 2; i++) { + jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, i * 2); + void *target = (void*)((char*)HT_NOTFOUND + i + 1); + ptrhash_put(&edges_ids, (void*)caller, target); + } + // process target list to turn it into a memoized validity table + // and compute the old methods list, ready for serialization + jl_value_t *matches = NULL; + jl_array_t *callee_ids = NULL; + JL_GC_PUSH2(&matches, &callee_ids); + for (size_t i = 0; i < l; i += 2) { + jl_array_t *callees = (jl_array_t*)jl_array_ptr_ref(edges, i + 1); + size_t l = jl_array_len(callees); + callee_ids = jl_alloc_array_1d(jl_array_int32_type, l + 1); + int32_t *idxs = (int32_t*)jl_array_data(callee_ids); + idxs[0] = 0; + size_t nt = 0; + for (size_t j = 0; j < l; j += 2) { + jl_value_t *invokeTypes = jl_array_ptr_ref(callees, j); + jl_value_t *callee = jl_array_ptr_ref(callees, j + 1); + assert(callee && "unsupported edge"); + + if (jl_is_method_instance(callee)) { + jl_methtable_t *mt = jl_method_get_table(((jl_method_instance_t*)callee)->def.method); + if (!jl_object_in_image((jl_value_t*)mt->module)) + continue; + } + + // (nullptr, c) => call + // (invokeTypes, c) => invoke + // (nullptr, invokeTypes) => missing call + // (invokeTypes, nullptr) => missing invoke (unused--inferred as Any) + void *target = ptrhash_get(&edges_map2, invokeTypes ? (void*)invokeTypes : (void*)callee); + if (target == HT_NOTFOUND) { + size_t min_valid = 0; + size_t max_valid = ~(size_t)0; + if (invokeTypes) { + jl_methtable_t *mt = jl_method_get_table(((jl_method_instance_t*)callee)->def.method); + if ((jl_value_t*)mt == jl_nothing) { + callee_ids = NULL; // invalid + break; + } + else { + matches = jl_gf_invoke_lookup_worlds(invokeTypes, (jl_value_t*)mt, world, &min_valid, &max_valid); + if (matches == jl_nothing) { + callee_ids = NULL; // invalid + break; + } + matches = (jl_value_t*)((jl_method_match_t*)matches)->method; + } + } + else { + jl_value_t *sig; + if (jl_is_method_instance(callee)) + sig = ((jl_method_instance_t*)callee)->specTypes; + else + sig = callee; + int ambig = 0; + matches = jl_matching_methods((jl_tupletype_t*)sig, jl_nothing, + -1, 0, world, &min_valid, &max_valid, &ambig); + if (matches == jl_nothing) { + callee_ids = NULL; // invalid + break; + } + size_t k; + for (k = 0; k < jl_array_len(matches); k++) { + jl_method_match_t *match = (jl_method_match_t *)jl_array_ptr_ref(matches, k); + jl_array_ptr_set(matches, k, match->method); + } + } + jl_array_ptr_1d_push(ext_targets, invokeTypes); + jl_array_ptr_1d_push(ext_targets, callee); + jl_array_ptr_1d_push(ext_targets, matches); + target = (void*)((char*)HT_NOTFOUND + jl_array_len(ext_targets) / 3); + ptrhash_put(&edges_map2, (void*)callee, target); + } + idxs[++nt] = (char*)target - (char*)HT_NOTFOUND - 1; + } + jl_array_ptr_set(edges, i + 1, callee_ids); // swap callees for ids + if (!callee_ids) + continue; + idxs[0] = nt; + // record place of every method in edges + // add method edges to the callee_ids list + for (size_t j = 0; j < l; j += 2) { + jl_value_t *callee = jl_array_ptr_ref(callees, j + 1); + if (callee && jl_is_method_instance(callee)) { + void *target = ptrhash_get(&edges_ids, (void*)callee); + if (target != HT_NOTFOUND) { + idxs[++nt] = (char*)target - (char*)HT_NOTFOUND - 1; + } + } + } + jl_array_del_end(callee_ids, l - nt); + } + JL_GC_POP(); + htable_free(&edges_map2); +} + +// Headers + +// serialize information about all loaded modules +static void write_mod_list(ios_t *s, jl_array_t *a) +{ + size_t i; + size_t len = jl_array_len(a); + for (i = 0; i < len; i++) { + jl_module_t *m = (jl_module_t*)jl_array_ptr_ref(a, i); + assert(jl_is_module(m)); + if (jl_object_in_image((jl_value_t*)m)) { + const char *modname = jl_symbol_name(m->name); + size_t l = strlen(modname); + write_int32(s, l); + ios_write(s, modname, l); + write_uint64(s, m->uuid.hi); + write_uint64(s, m->uuid.lo); + write_uint64(s, m->build_id.hi); + write_uint64(s, m->build_id.lo); + } + } + write_int32(s, 0); +} + +// "magic" string and version header of .ji file +static const int JI_FORMAT_VERSION = 12; +static const char JI_MAGIC[] = "\373jli\r\n\032\n"; // based on PNG signature +static const uint16_t BOM = 0xFEFF; // byte-order marker +static void write_header(ios_t *s) +{ + ios_write(s, JI_MAGIC, strlen(JI_MAGIC)); + write_uint16(s, JI_FORMAT_VERSION); + ios_write(s, (char *) &BOM, 2); + write_uint8(s, sizeof(void*)); + ios_write(s, JL_BUILD_UNAME, strlen(JL_BUILD_UNAME)+1); + ios_write(s, JL_BUILD_ARCH, strlen(JL_BUILD_ARCH)+1); + ios_write(s, JULIA_VERSION_STRING, strlen(JULIA_VERSION_STRING)+1); + const char *branch = jl_git_branch(), *commit = jl_git_commit(); + ios_write(s, branch, strlen(branch)+1); + ios_write(s, commit, strlen(commit)+1); + write_uint64(s, 0); // eventually will hold checksum for the content portion of this (build_id.hi) +} + +// serialize information about the result of deserializing this file +static void write_worklist_for_header(ios_t *s, jl_array_t *worklist) +{ + int i, l = jl_array_len(worklist); + for (i = 0; i < l; i++) { + jl_module_t *workmod = (jl_module_t*)jl_array_ptr_ref(worklist, i); + if (workmod->parent == jl_main_module || workmod->parent == workmod) { + size_t l = strlen(jl_symbol_name(workmod->name)); + write_int32(s, l); + ios_write(s, jl_symbol_name(workmod->name), l); + write_uint64(s, workmod->uuid.hi); + write_uint64(s, workmod->uuid.lo); + write_uint64(s, workmod->build_id.lo); + } + } + write_int32(s, 0); +} + +static void write_module_path(ios_t *s, jl_module_t *depmod) JL_NOTSAFEPOINT +{ + if (depmod->parent == jl_main_module || depmod->parent == depmod) + return; + const char *mname = jl_symbol_name(depmod->name); + size_t slen = strlen(mname); + write_module_path(s, depmod->parent); + write_int32(s, slen); + ios_write(s, mname, slen); +} + +// Cache file header +// Serialize the global Base._require_dependencies array of pathnames that +// are include dependencies. Also write Preferences and return +// the location of the srctext "pointer" in the header index. +static int64_t write_dependency_list(ios_t *s, jl_array_t* worklist, jl_array_t **udepsp) +{ + int64_t initial_pos = 0; + int64_t pos = 0; + static jl_array_t *deps = NULL; + if (!deps) + deps = (jl_array_t*)jl_get_global(jl_base_module, jl_symbol("_require_dependencies")); + + // unique(deps) to eliminate duplicates while preserving order: + // we preserve order so that the topmost included .jl file comes first + static jl_value_t *unique_func = NULL; + if (!unique_func) + unique_func = jl_get_global(jl_base_module, jl_symbol("unique")); + jl_value_t *uniqargs[2] = {unique_func, (jl_value_t*)deps}; + jl_task_t *ct = jl_current_task; + size_t last_age = ct->world_age; + ct->world_age = jl_atomic_load_acquire(&jl_world_counter); + jl_array_t *udeps = (*udepsp = deps && unique_func ? (jl_array_t*)jl_apply(uniqargs, 2) : NULL); + ct->world_age = last_age; + + // write a placeholder for total size so that we can quickly seek past all of the + // dependencies if we don't need them + initial_pos = ios_pos(s); + write_uint64(s, 0); + size_t i, l = udeps ? jl_array_len(udeps) : 0; + for (i = 0; i < l; i++) { + jl_value_t *deptuple = jl_array_ptr_ref(udeps, i); + jl_value_t *dep = jl_fieldref(deptuple, 1); // file abspath + size_t slen = jl_string_len(dep); + write_int32(s, slen); + ios_write(s, jl_string_data(dep), slen); + write_float64(s, jl_unbox_float64(jl_fieldref(deptuple, 2))); // mtime + jl_module_t *depmod = (jl_module_t*)jl_fieldref(deptuple, 0); // evaluating module + jl_module_t *depmod_top = depmod; + while (depmod_top->parent != jl_main_module && depmod_top->parent != depmod_top) + depmod_top = depmod_top->parent; + unsigned provides = 0; + size_t j, lj = jl_array_len(worklist); + for (j = 0; j < lj; j++) { + jl_module_t *workmod = (jl_module_t*)jl_array_ptr_ref(worklist, j); + if (workmod->parent == jl_main_module || workmod->parent == workmod) { + ++provides; + if (workmod == depmod_top) { + write_int32(s, provides); + write_module_path(s, depmod); + break; + } + } + } + write_int32(s, 0); + } + write_int32(s, 0); // terminator, for ease of reading + + // Calculate Preferences hash for current package. + jl_value_t *prefs_hash = NULL; + jl_value_t *prefs_list = NULL; + JL_GC_PUSH1(&prefs_list); + if (jl_base_module) { + // Toplevel module is the module we're currently compiling, use it to get our preferences hash + jl_value_t * toplevel = (jl_value_t*)jl_get_global(jl_base_module, jl_symbol("__toplevel__")); + jl_value_t * prefs_hash_func = jl_get_global(jl_base_module, jl_symbol("get_preferences_hash")); + jl_value_t * get_compiletime_prefs_func = jl_get_global(jl_base_module, jl_symbol("get_compiletime_preferences")); + + if (toplevel && prefs_hash_func && get_compiletime_prefs_func) { + // Temporary invoke in newest world age + size_t last_age = ct->world_age; + ct->world_age = jl_atomic_load_acquire(&jl_world_counter); + + // call get_compiletime_prefs(__toplevel__) + jl_value_t *args[3] = {get_compiletime_prefs_func, (jl_value_t*)toplevel, NULL}; + prefs_list = (jl_value_t*)jl_apply(args, 2); + + // Call get_preferences_hash(__toplevel__, prefs_list) + args[0] = prefs_hash_func; + args[2] = prefs_list; + prefs_hash = (jl_value_t*)jl_apply(args, 3); + + // Reset world age to normal + ct->world_age = last_age; + } + } + + // If we successfully got the preferences, write it out, otherwise write `0` for this `.ji` file. + if (prefs_hash != NULL && prefs_list != NULL) { + size_t i, l = jl_array_len(prefs_list); + for (i = 0; i < l; i++) { + jl_value_t *pref_name = jl_array_ptr_ref(prefs_list, i); + size_t slen = jl_string_len(pref_name); + write_int32(s, slen); + ios_write(s, jl_string_data(pref_name), slen); + } + write_int32(s, 0); // terminator + write_uint64(s, jl_unbox_uint64(prefs_hash)); + } + else { + // This is an error path, but let's at least generate a valid `.ji` file. + // We declare an empty list of preference names, followed by a zero-hash. + // The zero-hash is not what would be generated for an empty set of preferences, + // and so this `.ji` file will be invalidated by a future non-erroring pass + // through this function. + write_int32(s, 0); + write_uint64(s, 0); + } + JL_GC_POP(); // for prefs_list + + // write a dummy file position to indicate the beginning of the source-text + pos = ios_pos(s); + ios_seek(s, initial_pos); + write_uint64(s, pos - initial_pos); + ios_seek(s, pos); + write_uint64(s, 0); + return pos; +} + + +// Deserialization + +// Add methods to external (non-worklist-owned) functions +static void jl_insert_methods(jl_array_t *list) +{ + size_t i, l = jl_array_len(list); + for (i = 0; i < l; i++) { + jl_method_t *meth = (jl_method_t*)jl_array_ptr_ref(list, i); + assert(jl_is_method(meth)); + assert(!meth->is_for_opaque_closure); + jl_methtable_t *mt = jl_method_get_table(meth); + assert((jl_value_t*)mt != jl_nothing); + jl_method_table_insert(mt, meth, NULL); + } +} + +static void jl_copy_roots(jl_array_t *method_roots_list, uint64_t key) +{ + size_t i, l = jl_array_len(method_roots_list); + for (i = 0; i < l; i+=2) { + jl_method_t *m = (jl_method_t*)jl_array_ptr_ref(method_roots_list, i); + jl_array_t *roots = (jl_array_t*)jl_array_ptr_ref(method_roots_list, i+1); + if (roots) { + assert(jl_is_array(roots)); + jl_append_method_roots(m, key, roots); + } + } +} + +static int remove_code_instance_from_validation(jl_code_instance_t *codeinst) +{ + return ptrhash_remove(&new_code_instance_validate, codeinst); +} + +// verify that these edges intersect with the same methods as before +static jl_array_t *jl_verify_edges(jl_array_t *targets) +{ + size_t world = jl_atomic_load_acquire(&jl_world_counter); + size_t i, l = jl_array_len(targets) / 3; + jl_array_t *valids = jl_alloc_array_1d(jl_array_uint8_type, l); + memset(jl_array_data(valids), 1, l); + jl_value_t *loctag = NULL; + jl_value_t *matches = NULL; + JL_GC_PUSH3(&valids, &matches, &loctag); + for (i = 0; i < l; i++) { + jl_value_t *invokesig = jl_array_ptr_ref(targets, i * 3); + jl_value_t *callee = jl_array_ptr_ref(targets, i * 3 + 1); + jl_value_t *expected = jl_array_ptr_ref(targets, i * 3 + 2); + int valid = 1; + size_t min_valid = 0; + size_t max_valid = ~(size_t)0; + if (invokesig) { + assert(callee && "unsupported edge"); + jl_methtable_t *mt = jl_method_get_table(((jl_method_instance_t*)callee)->def.method); + if ((jl_value_t*)mt == jl_nothing) { + valid = 0; + } + else { + matches = jl_gf_invoke_lookup_worlds(invokesig, (jl_value_t*)mt, world, &min_valid, &max_valid); + if (matches == jl_nothing) { + valid = 0; + } + else { + matches = (jl_value_t*)((jl_method_match_t*)matches)->method; + if (matches != expected) { + valid = 0; + } + } + } + } + else { + jl_value_t *sig; + if (jl_is_method_instance(callee)) + sig = ((jl_method_instance_t*)callee)->specTypes; + else + sig = callee; + assert(jl_is_array(expected)); + int ambig = 0; + // TODO: possibly need to included ambiguities too (for the optimizer correctness)? + matches = jl_matching_methods((jl_tupletype_t*)sig, jl_nothing, + -1, 0, world, &min_valid, &max_valid, &ambig); + if (matches == jl_nothing) { + valid = 0; + } + else { + // setdiff!(matches, expected) + size_t j, k, ins = 0; + if (jl_array_len(matches) != jl_array_len(expected)) { + valid = 0; + } + for (k = 0; k < jl_array_len(matches); k++) { + jl_method_t *match = ((jl_method_match_t*)jl_array_ptr_ref(matches, k))->method; + size_t l = jl_array_len(expected); + for (j = 0; j < l; j++) + if (match == (jl_method_t*)jl_array_ptr_ref(expected, j)) + break; + if (j == l) { + // intersection has a new method or a method was + // deleted--this is now probably no good, just invalidate + // everything about it now + valid = 0; + if (!_jl_debug_method_invalidation) + break; + jl_array_ptr_set(matches, ins++, match); + } + } + if (!valid && _jl_debug_method_invalidation) + jl_array_del_end((jl_array_t*)matches, jl_array_len(matches) - ins); + } + } + jl_array_uint8_set(valids, i, valid); + if (!valid && _jl_debug_method_invalidation) { + jl_array_ptr_1d_push(_jl_debug_method_invalidation, invokesig ? (jl_value_t*)invokesig : callee); + loctag = jl_cstr_to_string("insert_backedges_callee"); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); + loctag = jl_box_int32((int32_t)i); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, matches); + } + //jl_static_show((JL_STREAM*)ios_stderr, (jl_value_t*)invokesig); + //jl_static_show((JL_STREAM*)ios_stderr, (jl_value_t*)callee); + //ios_puts(valid ? "valid\n" : "INVALID\n", ios_stderr); + } + JL_GC_POP(); + return valids; +} + +// Combine all edges relevant to a method into the visited table +static void jl_verify_methods(jl_array_t *edges, jl_array_t *valids, htable_t *visited) +{ + jl_value_t *loctag = NULL; + JL_GC_PUSH1(&loctag); + size_t i, l = jl_array_len(edges) / 2; + htable_new(visited, l); + for (i = 0; i < l; i++) { + jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, 2 * i); + assert(jl_is_method_instance(caller) && jl_is_method(caller->def.method)); + jl_array_t *callee_ids = (jl_array_t*)jl_array_ptr_ref(edges, 2 * i + 1); + assert(jl_typeis((jl_value_t*)callee_ids, jl_array_int32_type)); + int valid = 1; + if (callee_ids == NULL) { + // serializing the edges had failed + valid = 0; + } + else { + int32_t *idxs = (int32_t*)jl_array_data(callee_ids); + size_t j; + for (j = 0; valid && j < idxs[0]; j++) { + int32_t idx = idxs[j + 1]; + valid = jl_array_uint8_ref(valids, idx); + if (!valid && _jl_debug_method_invalidation) { + jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)caller); + loctag = jl_cstr_to_string("verify_methods"); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); + loctag = jl_box_int32((int32_t)idx); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); + } + } + } + ptrhash_put(visited, caller, (void*)(((char*)HT_NOTFOUND) + valid + 1)); + //jl_static_show((JL_STREAM*)ios_stderr, (jl_value_t*)caller); + //ios_puts(valid ? "valid\n" : "INVALID\n", ios_stderr); + // HT_NOTFOUND: valid (no invalid edges) + // HT_NOTFOUND + 1: invalid + // HT_NOTFOUND + 2: need to scan + // HT_NOTFOUND + 3 + depth: in-progress + } + JL_GC_POP(); +} + + +// Propagate the result of cycle-resolution to all edges (recursively) +static int mark_edges_in_worklist(jl_array_t *edges, int idx, jl_method_instance_t *cycle, htable_t *visited, int found) +{ + jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, idx * 2); + int oldfound = (char*)ptrhash_get(visited, caller) - (char*)HT_NOTFOUND; + if (oldfound < 3) + return 0; // not in-progress + if (!found) { + ptrhash_remove(visited, (void*)caller); + } + else { + ptrhash_put(visited, (void*)caller, (void*)((char*)HT_NOTFOUND + 1 + found)); + } + jl_array_t *callee_ids = (jl_array_t*)jl_array_ptr_ref(edges, idx * 2 + 1); + assert(jl_typeis((jl_value_t*)callee_ids, jl_array_int32_type)); + int32_t *idxs = (int32_t*)jl_array_data(callee_ids); + size_t i, badidx = 0, n = jl_array_len(callee_ids); + for (i = idxs[0] + 1; i < n; i++) { + if (mark_edges_in_worklist(edges, idxs[i], cycle, visited, found) && badidx == 0) + badidx = i - idxs[0]; + } + if (_jl_debug_method_invalidation) { + jl_value_t *loctag = NULL; + JL_GC_PUSH1(&loctag); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)caller); + loctag = jl_cstr_to_string("verify_methods"); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); + jl_method_instance_t *callee = cycle; + if (badidx--) + callee = (jl_method_instance_t*)jl_array_ptr_ref(edges, 2 * badidx); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)callee); + JL_GC_POP(); + } + return 1; +} + + +// Visit the entire call graph, starting from edges[idx] to determine if that method is valid +static int jl_verify_graph_edge(jl_array_t *edges, int idx, htable_t *visited, int depth) +{ + jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, idx * 2); + assert(jl_is_method_instance(caller) && jl_is_method(caller->def.method)); + int found = (char*)ptrhash_get(visited, (void*)caller) - (char*)HT_NOTFOUND; + if (found == 0) + return 1; // valid + if (found == 1) + return 0; // invalid + if (found != 2) + return found - 1; // depth + found = 0; + ptrhash_put(visited, (void*)caller, (void*)((char*)HT_NOTFOUND + 3 + depth)); // change 2 to in-progress at depth + jl_array_t *callee_ids = (jl_array_t*)jl_array_ptr_ref(edges, idx * 2 + 1); + assert(jl_typeis((jl_value_t*)callee_ids, jl_array_int32_type)); + int32_t *idxs = (int32_t*)jl_array_data(callee_ids); + int cycle = 0; + size_t i, n = jl_array_len(callee_ids); + for (i = idxs[0] + 1; i < n; i++) { + int32_t idx = idxs[i]; + int child_found = jl_verify_graph_edge(edges, idx, visited, depth + 1); + if (child_found == 0) { + found = 1; + if (_jl_debug_method_invalidation) { + jl_value_t *loctag = NULL; + JL_GC_PUSH1(&loctag); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)caller); + loctag = jl_cstr_to_string("verify_methods"); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, jl_array_ptr_ref(edges, idx * 2)); + JL_GC_POP(); + } + break; + } + else if (child_found >= 2 && child_found - 2 < cycle) { + // record the cycle will resolve at depth "cycle" + cycle = child_found - 2; + assert(cycle); + } + } + if (!found) { + if (cycle && cycle != depth) + return cycle + 2; + ptrhash_remove(visited, (void*)caller); + } + else { // found invalid + ptrhash_put(visited, (void*)caller, (void*)((char*)HT_NOTFOUND + 1 + found)); + } + if (cycle) { + // If we are the top of the current cycle, now mark all other parts of + // our cycle by re-walking the backedges graph and marking all WIP + // items as found. + // Be careful to only re-walk as far as we had originally scanned above. + // Or if we found a backedge, also mark all of the other parts of the + // cycle as also having an backedge. + n = i; + for (i = idxs[0] + 1; i < n; i++) { + mark_edges_in_worklist(edges, idxs[i], caller, visited, found); + } + } + return found ? 0 : 1; +} + +// Visit all entries in edges, verify if they are valid +static jl_array_t *jl_verify_graph(jl_array_t *edges, htable_t *visited) +{ + size_t i, n = jl_array_len(edges) / 2; + jl_array_t *valids = jl_alloc_array_1d(jl_array_uint8_type, n); + JL_GC_PUSH1(&valids); + int8_t *valids_data = (int8_t*)jl_array_data(valids); + for (i = 0; i < n; i++) { + valids_data[i] = jl_verify_graph_edge(edges, i, visited, 1); + } + JL_GC_POP(); + return valids; +} + +// Restore backedges to external targets +// `edges` = [caller1, targets_indexes1, ...], the list of worklist-owned methods calling external methods. +// `ext_targets` is [invokesig1, callee1, matches1, ...], the global set of non-worklist callees of worklist-owned methods. +static void jl_insert_backedges(jl_array_t *edges, jl_array_t *ext_targets, jl_array_t *ci_list) +{ + // determine which CodeInstance objects are still valid in our image + size_t world = jl_atomic_load_acquire(&jl_world_counter); + jl_array_t *valids = jl_verify_edges(ext_targets); + JL_GC_PUSH1(&valids); + htable_t visited; + htable_new(&visited, 0); + jl_verify_methods(edges, valids, &visited); + valids = jl_verify_graph(edges, &visited); + size_t i, l = jl_array_len(edges) / 2; + + // next build a map from external MethodInstances to their CodeInstance for insertion + if (ci_list == NULL) { + htable_reset(&visited, 0); + } + else { + size_t i, l = jl_array_len(ci_list); + htable_reset(&visited, l); + for (i = 0; i < l; i++) { + jl_code_instance_t *ci = (jl_code_instance_t*)jl_array_ptr_ref(ci_list, i); + assert(ptrhash_get(&visited, (void*)ci->def) == HT_NOTFOUND); // check that we don't have multiple cis for same mi + ptrhash_put(&visited, (void*)ci->def, (void*)ci); + } + } + + // next disable any invalid codes, so we do not try to enable them + for (i = 0; i < l; i++) { + jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, 2 * i); + assert(jl_is_method_instance(caller) && jl_is_method(caller->def.method)); + int valid = jl_array_uint8_ref(valids, i); + if (valid) + continue; + void *ci = ptrhash_get(&visited, (void*)caller); + if (ci != HT_NOTFOUND) { + assert(jl_is_code_instance(ci)); + remove_code_instance_from_validation((jl_code_instance_t*)ci); // mark it as handled + } + else { + jl_code_instance_t *codeinst = caller->cache; + while (codeinst) { + remove_code_instance_from_validation(codeinst); // should be left invalid + codeinst = jl_atomic_load_relaxed(&codeinst->next); + } + } + } + + // finally enable any applicable new codes + for (i = 0; i < l; i++) { + jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, 2 * i); + int valid = jl_array_uint8_ref(valids, i); + if (!valid) + continue; + // if this callee is still valid, add all the backedges + jl_array_t *callee_ids = (jl_array_t*)jl_array_ptr_ref(edges, 2 * i + 1); + int32_t *idxs = (int32_t*)jl_array_data(callee_ids); + for (size_t j = 0; j < idxs[0]; j++) { + int32_t idx = idxs[j + 1]; + jl_value_t *invokesig = jl_array_ptr_ref(ext_targets, idx * 3); + jl_value_t *callee = jl_array_ptr_ref(ext_targets, idx * 3 + 1); + if (callee && jl_is_method_instance(callee)) { + jl_method_instance_add_backedge((jl_method_instance_t*)callee, invokesig, caller); + } + else { + jl_value_t *sig = callee == NULL ? invokesig : callee; + jl_methtable_t *mt = jl_method_table_for(sig); + // FIXME: rarely, `callee` has an unexpected `Union` signature, + // see https://github.com/JuliaLang/julia/pull/43990#issuecomment-1030329344 + // Fix the issue and turn this back into an `assert((jl_value_t*)mt != jl_nothing)` + // This workaround exposes us to (rare) 265-violations. + if ((jl_value_t*)mt != jl_nothing) + jl_method_table_add_backedge(mt, sig, (jl_value_t*)caller); + } + } + // then enable it + void *ci = ptrhash_get(&visited, (void*)caller); + if (ci != HT_NOTFOUND) { + // have some new external code to use + assert(jl_is_code_instance(ci)); + jl_code_instance_t *codeinst = (jl_code_instance_t*)ci; + remove_code_instance_from_validation(codeinst); // mark it as handled + assert(codeinst->min_world >= world && codeinst->inferred); + codeinst->max_world = ~(size_t)0; + if (jl_rettype_inferred(caller, world, ~(size_t)0) == jl_nothing) { + jl_mi_cache_insert(caller, codeinst); + } + } + else { + jl_code_instance_t *codeinst = caller->cache; + while (codeinst) { + if (remove_code_instance_from_validation(codeinst)) { // mark it as handled + assert(codeinst->min_world >= world && codeinst->inferred); + codeinst->max_world = ~(size_t)0; + } + codeinst = jl_atomic_load_relaxed(&codeinst->next); + } + } + } + + htable_free(&visited); + JL_GC_POP(); +} + +static void classify_callers(htable_t *callers_with_edges, jl_array_t *edges) +{ + size_t l = edges ? jl_array_len(edges) / 2 : 0; + for (size_t i = 0; i < l; i++) { + jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, 2 * i); + ptrhash_put(callers_with_edges, (void*)caller, (void*)caller); + } +} + +static void validate_new_code_instances(void) +{ + size_t world = jl_atomic_load_acquire(&jl_world_counter); + size_t i; + for (i = 0; i < new_code_instance_validate.size; i += 2) { + if (new_code_instance_validate.table[i+1] != HT_NOTFOUND) { + //assert(0 && "unexpected unprocessed CodeInstance found"); + jl_code_instance_t *ci = (jl_code_instance_t*)new_code_instance_validate.table[i]; + JL_GC_PROMISE_ROOTED(ci); // TODO: this needs a root (or restructuring to avoid it) + assert(ci->min_world >= world && ci->inferred); + ci->max_world = ~(size_t)0; + jl_method_instance_t *caller = ci->def; + if (jl_rettype_inferred(caller, world, ~(size_t)0) == jl_nothing) { + jl_mi_cache_insert(caller, ci); + } + //jl_static_show((JL_STREAM*)ios_stderr, (jl_value_t*)caller); + //ios_puts("FREE\n", ios_stderr); + } + } +} + +static jl_value_t *read_verify_mod_list(ios_t *s, jl_array_t *depmods) +{ + if (!jl_main_module->build_id.lo) { + return jl_get_exceptionf(jl_errorexception_type, + "Main module uuid state is invalid for module deserialization."); + } + size_t i, l = jl_array_len(depmods); + for (i = 0; ; i++) { + size_t len = read_int32(s); + if (len == 0 && i == l) + return NULL; // success + if (len == 0 || i == l) + return jl_get_exceptionf(jl_errorexception_type, "Wrong number of entries in module list."); + char *name = (char*)alloca(len + 1); + ios_readall(s, name, len); + name[len] = '\0'; + jl_uuid_t uuid; + uuid.hi = read_uint64(s); + uuid.lo = read_uint64(s); + jl_uuid_t build_id; + build_id.hi = read_uint64(s); + build_id.lo = read_uint64(s); + jl_sym_t *sym = _jl_symbol(name, len); + jl_module_t *m = (jl_module_t*)jl_array_ptr_ref(depmods, i); + if (!m || !jl_is_module(m) || m->uuid.hi != uuid.hi || m->uuid.lo != uuid.lo || m->name != sym || + m->build_id.hi != build_id.hi || m->build_id.lo != build_id.lo) { + return jl_get_exceptionf(jl_errorexception_type, + "Invalid input in module list: expected %s.", name); + } + } +} + +static int readstr_verify(ios_t *s, const char *str, int include_null) +{ + size_t i, len = strlen(str) + include_null; + for (i = 0; i < len; ++i) + if ((char)read_uint8(s) != str[i]) + return 0; + return 1; +} + +JL_DLLEXPORT uint64_t jl_read_verify_header(ios_t *s) +{ + uint16_t bom; + if (readstr_verify(s, JI_MAGIC, 0) && + read_uint16(s) == JI_FORMAT_VERSION && + ios_read(s, (char *) &bom, 2) == 2 && bom == BOM && + read_uint8(s) == sizeof(void*) && + readstr_verify(s, JL_BUILD_UNAME, 1) && + readstr_verify(s, JL_BUILD_ARCH, 1) && + readstr_verify(s, JULIA_VERSION_STRING, 1) && + readstr_verify(s, jl_git_branch(), 1) && + readstr_verify(s, jl_git_commit(), 1)) + return read_uint64(s); + return 0; +} diff --git a/src/subtype.c b/src/subtype.c index 9a5a9fdbbbfd48..cbb11520190cbf 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -1289,8 +1289,10 @@ static int subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param) return issub; } while (xd != jl_any_type && xd->name != yd->name) { - if (xd->super == NULL) + if (xd->super == NULL) { + assert(xd->parameters && jl_is_typename(xd->name)); jl_errorf("circular type parameter constraint in definition of %s", jl_symbol_name(xd->name->name)); + } xd = xd->super; } if (xd == jl_any_type) return 0; diff --git a/src/support/arraylist.h b/src/support/arraylist.h index 03bfd45f8f525d..6ad2f0e2f28c9e 100644 --- a/src/support/arraylist.h +++ b/src/support/arraylist.h @@ -25,7 +25,7 @@ void arraylist_free(arraylist_t *a) JL_NOTSAFEPOINT; void arraylist_push(arraylist_t *a, void *elt) JL_NOTSAFEPOINT; void *arraylist_pop(arraylist_t *a) JL_NOTSAFEPOINT; -void arraylist_grow(arraylist_t *a, size_t n) JL_NOTSAFEPOINT; +JL_DLLEXPORT void arraylist_grow(arraylist_t *a, size_t n) JL_NOTSAFEPOINT; typedef struct { uint32_t len; diff --git a/src/support/rle.h b/src/support/rle.h index f85d9f35c4b803..bd2fdafc0f79f6 100644 --- a/src/support/rle.h +++ b/src/support/rle.h @@ -10,6 +10,7 @@ extern "C" { #include #include #include +#include "analyzer_annotations.h" /* Run-length encoding (RLE) utilities */ /* In the RLE table, even indexes encode the key (the item classification), odd indexes encode the item index */ @@ -28,8 +29,8 @@ typedef struct _rle_iter_state_t { uint64_t key; // current identifier } rle_iter_state; -rle_iter_state rle_iter_init(/* implicit value of key for indexes prior to first explicit rle pair */ uint64_t key0); -int rle_iter_increment(rle_iter_state *state, /* number of items */ size_t len, uint64_t *rletable, /*length of rletable */ size_t npairs); +rle_iter_state rle_iter_init(/* implicit value of key for indexes prior to first explicit rle pair */ uint64_t key0) JL_NOTSAFEPOINT; +int rle_iter_increment(rle_iter_state *state, /* number of items */ size_t len, uint64_t *rletable, /*length of rletable */ size_t npairs) JL_NOTSAFEPOINT; /* indexing */ typedef struct { @@ -37,8 +38,8 @@ typedef struct { int index; // number of preceding items in the list with the same key } rle_reference; -void rle_index_to_reference(rle_reference *rr, /* item index */ size_t i, uint64_t *rletable, size_t npairs, uint64_t key0); -size_t rle_reference_to_index(rle_reference *rr, uint64_t *rletable, size_t npairs, uint64_t key0); +void rle_index_to_reference(rle_reference *rr, /* item index */ size_t i, uint64_t *rletable, size_t npairs, uint64_t key0) JL_NOTSAFEPOINT; +size_t rle_reference_to_index(rle_reference *rr, uint64_t *rletable, size_t npairs, uint64_t key0) JL_NOTSAFEPOINT; #ifdef __cplusplus diff --git a/src/threading.c b/src/threading.c index e33d22c24581a5..dcb57cce23a794 100644 --- a/src/threading.c +++ b/src/threading.c @@ -291,8 +291,10 @@ JL_DLLEXPORT jl_gcframe_t **jl_get_pgcstack(void) JL_GLOBALLY_ROOTED void jl_pgcstack_getkey(jl_get_pgcstack_func **f, jl_pgcstack_key_t *k) { +#ifndef __clang_gcanalyzer__ if (jl_get_pgcstack_cb == jl_get_pgcstack_init) jl_get_pgcstack_init(); +#endif // for codegen *f = jl_get_pgcstack_cb; *k = jl_pgcstack_key; diff --git a/stdlib/LLD_jll/src/LLD_jll.jl b/stdlib/LLD_jll/src/LLD_jll.jl index d14d740fc5e5b2..80653353a7c172 100644 --- a/stdlib/LLD_jll/src/LLD_jll.jl +++ b/stdlib/LLD_jll/src/LLD_jll.jl @@ -1,4 +1,3 @@ - # This file is a part of Julia. License is MIT: https://julialang.org/license ## dummy stub for https://github.com/JuliaBinaryWrappers/LLD_jll.jl diff --git a/stdlib/Profile/src/Allocs.jl b/stdlib/Profile/src/Allocs.jl index 2bf06550b72d6f..1a52c1ec782dea 100644 --- a/stdlib/Profile/src/Allocs.jl +++ b/stdlib/Profile/src/Allocs.jl @@ -144,9 +144,13 @@ end const BacktraceCache = Dict{BTElement,Vector{StackFrame}} # copied from julia_internal.h -const JL_BUFF_TAG = UInt(0x4eadc000) +JL_BUFF_TAG::UInt = ccall(:jl_get_buff_tag, UInt, ()) const JL_GC_UNKNOWN_TYPE_TAG = UInt(0xdeadaa03) +function __init__() + global JL_BUFF_TAG = ccall(:jl_get_buff_tag, UInt, ()) +end + struct CorruptType end struct BufferType end struct UnknownType end diff --git a/test/precompile.jl b/test/precompile.jl index 5b49ad4a3b31a3..eaf755046d3662 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -1,6 +1,7 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license original_depot_path = copy(Base.DEPOT_PATH) +original_load_path = copy(Base.LOAD_PATH) using Test, Distributed, Random @@ -37,7 +38,7 @@ end # method root provenance -rootid(m::Module) = ccall(:jl_module_build_id, UInt64, (Any,), Base.parentmodule(m)) +rootid(m::Module) = Base.module_build_id(Base.parentmodule(m)) % UInt64 rootid(m::Method) = rootid(m.module) function root_provenance(m::Method, i::Int) @@ -344,7 +345,7 @@ precompile_test_harness(false) do dir modules, (deps, requires), required_modules = Base.parse_cache_header(cachefile) discard_module = mod_fl_mt -> (mod_fl_mt.filename, mod_fl_mt.mtime) - @test modules == [ Base.PkgId(Foo) => Base.module_build_id(Foo) ] + @test modules == [ Base.PkgId(Foo) => Base.module_build_id(Foo) % UInt64 ] @test map(x -> x.filename, deps) == [ Foo_file, joinpath(dir, "foo.jl"), joinpath(dir, "bar.jl") ] @test requires == [ Base.PkgId(Foo) => Base.PkgId(string(FooBase_module)), Base.PkgId(Foo) => Base.PkgId(Foo2), @@ -1554,8 +1555,23 @@ precompile_test_harness("issue #46296") do load_path (@eval (using CodeInstancePrecompile)) end -empty!(Base.DEPOT_PATH) -append!(Base.DEPOT_PATH, original_depot_path) +precompile_test_harness("Recursive types") do load_path + write(joinpath(load_path, "RecursiveTypeDef.jl"), + """ + module RecursiveTypeDef + + struct C{T,O} end + struct A{T,N,O} <: AbstractArray{C{T,A{T,N,O}},N} + sz::NTuple{N,Int} + end + + end + """) + Base.compilecache(Base.PkgId("RecursiveTypeDef")) + (@eval (using RecursiveTypeDef)) + a = Base.invokelatest(RecursiveTypeDef.A{Float64,2,String}, (3, 3)) + @test isa(a, AbstractArray) +end @testset "issue 46778" begin f46778(::Any, ::Type{Int}) = 1 @@ -1563,3 +1579,8 @@ append!(Base.DEPOT_PATH, original_depot_path) @test precompile(Tuple{typeof(f46778), Int, DataType}) @test which(f46778, Tuple{Any,DataType}).specializations[1].cache.invoke != C_NULL end + +empty!(Base.DEPOT_PATH) +append!(Base.DEPOT_PATH, original_depot_path) +empty!(Base.LOAD_PATH) +append!(Base.LOAD_PATH, original_load_path) From e06a5915a9e93ed5d4c25cf819275af18adf8187 Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Sun, 27 Nov 2022 18:26:31 -0500 Subject: [PATCH 047/387] Allow re-initialization and caching of foreign types (#47407) Co-authored-by: Tim Holy Co-authored-by: Max Horn --- src/datatype.c | 16 ++++++ src/jl_exported_funcs.inc | 1 + src/julia_gcext.h | 7 +++ src/staticdata.c | 19 ++++++- test/gcext/.gitignore | 1 + test/gcext/DependsOnForeign/Manifest.toml | 14 +++++ test/gcext/DependsOnForeign/Project.toml | 6 ++ .../DependsOnForeign/src/DependsOnForeign.jl | 14 +++++ test/gcext/Foreign/Manifest.toml | 8 +++ test/gcext/Foreign/Project.toml | 6 ++ test/gcext/Foreign/deps/foreignlib.c | 56 +++++++++++++++++++ test/gcext/Foreign/src/Foreign.jl | 29 ++++++++++ .../ForeignObjSerialization/Manifest.toml | 14 +++++ .../ForeignObjSerialization/Project.toml | 6 ++ .../src/ForeignObjSerialization.jl | 6 ++ test/gcext/Makefile | 24 ++++++-- test/gcext/gcext-test.jl | 33 +++++++++++ 17 files changed, 254 insertions(+), 6 deletions(-) create mode 100644 test/gcext/DependsOnForeign/Manifest.toml create mode 100644 test/gcext/DependsOnForeign/Project.toml create mode 100644 test/gcext/DependsOnForeign/src/DependsOnForeign.jl create mode 100644 test/gcext/Foreign/Manifest.toml create mode 100644 test/gcext/Foreign/Project.toml create mode 100644 test/gcext/Foreign/deps/foreignlib.c create mode 100644 test/gcext/Foreign/src/Foreign.jl create mode 100644 test/gcext/ForeignObjSerialization/Manifest.toml create mode 100644 test/gcext/ForeignObjSerialization/Project.toml create mode 100644 test/gcext/ForeignObjSerialization/src/ForeignObjSerialization.jl diff --git a/src/datatype.c b/src/datatype.c index b225ff3bd4fe24..3fc37f199bfd82 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -826,6 +826,22 @@ JL_DLLEXPORT jl_datatype_t * jl_new_foreign_type(jl_sym_t *name, return bt; } +JL_DLLEXPORT int jl_reinit_foreign_type(jl_datatype_t *dt, + jl_markfunc_t markfunc, + jl_sweepfunc_t sweepfunc) +{ + if (!jl_is_foreign_type(dt)) + return 0; + const jl_datatype_layout_t *layout = dt->layout; + jl_fielddescdyn_t * desc = + (jl_fielddescdyn_t *) ((char *)layout + sizeof(*layout)); + assert(!desc->markfunc); + assert(!desc->sweepfunc); + desc->markfunc = markfunc; + desc->sweepfunc = sweepfunc; + return 1; +} + JL_DLLEXPORT int jl_is_foreign_type(jl_datatype_t *dt) { return jl_is_datatype(dt) && dt->layout && dt->layout->fielddesc_type == 3; diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index f97c9894238591..2e5df94dc72007 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -343,6 +343,7 @@ XX(jl_new_code_info_uninit) \ XX(jl_new_datatype) \ XX(jl_new_foreign_type) \ + XX(jl_reinit_foreign_type) \ XX(jl_new_method_instance_uninit) \ XX(jl_new_method_table) \ XX(jl_new_method_uninit) \ diff --git a/src/julia_gcext.h b/src/julia_gcext.h index 669e80d069fa40..27f0a6b5ec11c4 100644 --- a/src/julia_gcext.h +++ b/src/julia_gcext.h @@ -49,6 +49,13 @@ JL_DLLEXPORT jl_datatype_t *jl_new_foreign_type( int haspointers, int large); + +#define HAVE_JL_REINIT_FOREIGN_TYPE 1 +JL_DLLEXPORT int jl_reinit_foreign_type( + jl_datatype_t *dt, + jl_markfunc_t markfunc, + jl_sweepfunc_t sweepfunc); + JL_DLLEXPORT int jl_is_foreign_type(jl_datatype_t *dt); JL_DLLEXPORT size_t jl_gc_max_internal_obj_size(void); diff --git a/src/staticdata.c b/src/staticdata.c index e1f0f86aa68fc3..7b33db4eadccc2 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -77,6 +77,7 @@ External links: #include "julia.h" #include "julia_internal.h" +#include "julia_gcext.h" #include "builtin_proto.h" #include "processor.h" #include "serialize.h" @@ -1249,6 +1250,9 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED ios_write(s->s, (char*)v, sizeof(void*) + jl_string_len(v)); write_uint8(s->s, '\0'); // null-terminated strings for easier C-compatibility } + else if (jl_is_foreign_type(t) == 1) { + jl_error("Cannot serialize instances of foreign datatypes"); + } else if (jl_datatype_nfields(t) == 0) { // The object has no fields, so we just snapshot its byte representation assert(!t->layout->npointers); @@ -1438,10 +1442,14 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED if (dt->layout != NULL) { size_t nf = dt->layout->nfields; size_t np = dt->layout->npointers; - size_t fieldsize = jl_fielddesc_size(dt->layout->fielddesc_type); + size_t fieldsize = 0; + uint8_t is_foreign_type = dt->layout->fielddesc_type == 3; + if (!is_foreign_type) { + fieldsize = jl_fielddesc_size(dt->layout->fielddesc_type); + } char *flddesc = (char*)dt->layout; size_t fldsize = sizeof(jl_datatype_layout_t) + nf * fieldsize; - if (dt->layout->first_ptr != -1) + if (!is_foreign_type && dt->layout->first_ptr != -1) fldsize += np << dt->layout->fielddesc_type; uintptr_t layout = LLT_ALIGN(ios_pos(s->const_data), sizeof(void*)); write_padding(s->const_data, layout - ios_pos(s->const_data)); // realign stream @@ -1450,6 +1458,13 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_datatype_t, layout))); // relocation location arraylist_push(&s->relocs_list, (void*)(((uintptr_t)ConstDataRef << RELOC_TAG_OFFSET) + layout)); // relocation target ios_write(s->const_data, flddesc, fldsize); + if (is_foreign_type) { + // make sure we have space for the extra hidden pointers + // zero them since they will need to be re-initialized externally + assert(fldsize == sizeof(jl_datatype_layout_t)); + jl_fielddescdyn_t dyn = {0, 0}; + ios_write(s->const_data, (char*)&dyn, sizeof(jl_fielddescdyn_t)); + } } } else if (jl_is_typename(v)) { diff --git a/test/gcext/.gitignore b/test/gcext/.gitignore index 0f8c848e5cea69..829c3297dfa2c5 100644 --- a/test/gcext/.gitignore +++ b/test/gcext/.gitignore @@ -1,2 +1,3 @@ /gcext /gcext-debug +/Foreign/deps diff --git a/test/gcext/DependsOnForeign/Manifest.toml b/test/gcext/DependsOnForeign/Manifest.toml new file mode 100644 index 00000000000000..d830116bb54caf --- /dev/null +++ b/test/gcext/DependsOnForeign/Manifest.toml @@ -0,0 +1,14 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.8.3" +manifest_format = "2.0" +project_hash = "e7199d961a5f4ebad68a3deaf5beaa7406a0afcb" + +[[deps.Foreign]] +deps = ["Libdl"] +path = "../Foreign" +uuid = "de1f6f7a-d7b3-400f-91c2-33f248ee89c4" +version = "0.1.0" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/test/gcext/DependsOnForeign/Project.toml b/test/gcext/DependsOnForeign/Project.toml new file mode 100644 index 00000000000000..b2bee1338c2b76 --- /dev/null +++ b/test/gcext/DependsOnForeign/Project.toml @@ -0,0 +1,6 @@ +name = "DependsOnForeign" +uuid = "4b0716e0-dfb5-4e00-8b44-e2685a41517f" +version = "0.1.0" + +[deps] +Foreign = "de1f6f7a-d7b3-400f-91c2-33f248ee89c4" diff --git a/test/gcext/DependsOnForeign/src/DependsOnForeign.jl b/test/gcext/DependsOnForeign/src/DependsOnForeign.jl new file mode 100644 index 00000000000000..cdf31774956e1d --- /dev/null +++ b/test/gcext/DependsOnForeign/src/DependsOnForeign.jl @@ -0,0 +1,14 @@ +module DependsOnForeign + +using Foreign + +f(obj::FObj) = Base.pointer_from_objref(obj) +precompile(f, (FObj,)) + +const FObjRef = Ref{FObj}() + +function __init__() + FObjRef[] = FObj() +end + +end # module DependsOnForeign diff --git a/test/gcext/Foreign/Manifest.toml b/test/gcext/Foreign/Manifest.toml new file mode 100644 index 00000000000000..25cf111aa50ba7 --- /dev/null +++ b/test/gcext/Foreign/Manifest.toml @@ -0,0 +1,8 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.9.0-DEV" +manifest_format = "2.0" +project_hash = "7b70172a2edbdc772ed789e79d4411d7528eae86" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/test/gcext/Foreign/Project.toml b/test/gcext/Foreign/Project.toml new file mode 100644 index 00000000000000..819f64beee442c --- /dev/null +++ b/test/gcext/Foreign/Project.toml @@ -0,0 +1,6 @@ +name = "Foreign" +uuid = "de1f6f7a-d7b3-400f-91c2-33f248ee89c4" +version = "0.1.0" + +[deps] +Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/test/gcext/Foreign/deps/foreignlib.c b/test/gcext/Foreign/deps/foreignlib.c new file mode 100644 index 00000000000000..72e02e9bef0cf6 --- /dev/null +++ b/test/gcext/Foreign/deps/foreignlib.c @@ -0,0 +1,56 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + +#include "julia.h" +#include "julia_gcext.h" + +// TODO make these atomics +int nmarks = 0; +int nsweeps = 0; + +uintptr_t mark(jl_ptls_t ptls, jl_value_t *p) +{ + nmarks += 1; + return 0; +} + +void sweep(jl_value_t *p) +{ + nsweeps++; +} + +JL_DLLEXPORT jl_datatype_t *declare_foreign(jl_sym_t* name, jl_module_t *module, jl_datatype_t *parent) +{ + return jl_new_foreign_type(name, module, parent, mark, sweep, 1, 0); +} + +// #define GC_MAX_SZCLASS (2032 - sizeof(void *)) + +JL_DLLEXPORT int reinit_foreign(jl_datatype_t *dt) +{ + int ret = jl_reinit_foreign_type(dt, mark, sweep); + nmarks = nsweeps = 0; + if (ret == 0) + return 0; + if (dt->layout->npointers != 1) + return -1; + if (dt->layout->size != 0) + return -2; + return ret; +} + +JL_DLLEXPORT jl_value_t *allocate_foreign(jl_ptls_t ptls, size_t sz, jl_datatype_t *dt) +{ + jl_value_t* obj = jl_gc_alloc_typed(ptls, sz, dt); + jl_gc_schedule_foreign_sweepfunc(ptls, obj); + return obj; +} + +JL_DLLEXPORT int nmark_counter() +{ + return nmarks; +} + +JL_DLLEXPORT int nsweep_counter() +{ + return nsweeps; +} diff --git a/test/gcext/Foreign/src/Foreign.jl b/test/gcext/Foreign/src/Foreign.jl new file mode 100644 index 00000000000000..a1ab79fab586a5 --- /dev/null +++ b/test/gcext/Foreign/src/Foreign.jl @@ -0,0 +1,29 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +module Foreign + +using Libdl + +const foreignlib = joinpath(ENV["BINDIR"], "foreignlib.$(dlext)") + +const FObj = ccall((:declare_foreign, foreignlib), Any, (Any, Any, Any), :FObj, @__MODULE__, Any) +FObj() = ccall((:allocate_foreign, foreignlib), Any, (Ptr{Cvoid}, Csize_t, Any,), Core.getptls(), sizeof(Ptr{Cvoid}), FObj)::FObj + +export FObj + +get_nmark() = ccall((:nmark_counter, foreignlib), Cint, ()) +get_nsweep() = ccall((:nsweep_counter, foreignlib), Cint, ()) + +function __init__() + @assert ccall((:reinit_foreign, foreignlib), Cint, (Any,), FObj) == 1 +end + +allocs(N) = [Foreign.FObj() for _ in 1:N] + +function test(N) + x = allocs(N) + Core.donotdelete(x) + x = nothing +end + +end # module Foreign diff --git a/test/gcext/ForeignObjSerialization/Manifest.toml b/test/gcext/ForeignObjSerialization/Manifest.toml new file mode 100644 index 00000000000000..d830116bb54caf --- /dev/null +++ b/test/gcext/ForeignObjSerialization/Manifest.toml @@ -0,0 +1,14 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.8.3" +manifest_format = "2.0" +project_hash = "e7199d961a5f4ebad68a3deaf5beaa7406a0afcb" + +[[deps.Foreign]] +deps = ["Libdl"] +path = "../Foreign" +uuid = "de1f6f7a-d7b3-400f-91c2-33f248ee89c4" +version = "0.1.0" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/test/gcext/ForeignObjSerialization/Project.toml b/test/gcext/ForeignObjSerialization/Project.toml new file mode 100644 index 00000000000000..1a26ff7884481c --- /dev/null +++ b/test/gcext/ForeignObjSerialization/Project.toml @@ -0,0 +1,6 @@ +name = "ForeignObjSerialization" +uuid = "2c015d96-a6ca-42f0-bc68-f9090de6bc2c" +version = "0.1.0" + +[deps] +Foreign = "de1f6f7a-d7b3-400f-91c2-33f248ee89c4" diff --git a/test/gcext/ForeignObjSerialization/src/ForeignObjSerialization.jl b/test/gcext/ForeignObjSerialization/src/ForeignObjSerialization.jl new file mode 100644 index 00000000000000..e32753aecb3b4c --- /dev/null +++ b/test/gcext/ForeignObjSerialization/src/ForeignObjSerialization.jl @@ -0,0 +1,6 @@ +module ForeignObjSerialization + +using Foreign +const FObjRef = Ref{FObj}(FObj()) + +end # module ForeignObjSerialization diff --git a/test/gcext/Makefile b/test/gcext/Makefile index b3314d1f9b32b8..2a77b76ede50d0 100644 --- a/test/gcext/Makefile +++ b/test/gcext/Makefile @@ -19,18 +19,26 @@ SRCDIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) # get the executable suffix, if any EXE := $(suffix $(abspath $(JULIA))) +OS := $(shell uname) +ifeq ($(OS), Darwin) + DYLIB := .dylib +else + DYLIB := .so +endif + # get compiler and linker flags. (see: `contrib/julia-config.jl`) JULIA_CONFIG := $(JULIA) -e 'include(joinpath(Sys.BINDIR, Base.DATAROOTDIR, "julia", "julia-config.jl"))' -- CPPFLAGS_ADD := CFLAGS_ADD = $(shell $(JULIA_CONFIG) --cflags) LDFLAGS_ADD = -lm $(shell $(JULIA_CONFIG) --ldflags --ldlibs) +DYLIBFLAGS := --shared -fPIC DEBUGFLAGS += -g #============================================================================= -release: $(BIN)/gcext$(EXE) -debug: $(BIN)/gcext-debug$(EXE) +release: $(BIN)/gcext$(EXE) $(BIN)/Foreign/deps/foreignlib$(DYLIB) +debug: $(BIN)/gcext-debug$(EXE) $(BIN)/Foreign/deps/foreignlib-debug$(DYLIB) $(BIN)/gcext$(EXE): $(SRCDIR)/gcext.c $(CC) $^ -o $@ $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) @@ -38,6 +46,12 @@ $(BIN)/gcext$(EXE): $(SRCDIR)/gcext.c $(BIN)/gcext-debug$(EXE): $(SRCDIR)/gcext.c $(CC) $^ -o $@ $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) $(DEBUGFLAGS) +$(BIN)/foreignlib$(DYLIB): $(SRCDIR)/Foreign/deps/foreignlib.c + $(CC) $^ -o $@ $(DYLIBFLAGS) $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) + +$(BIN)/foreignlib-debug$(DYLIB): $(SRCDIR)/Foreign/deps/foreignlib.c + $(CC) $^ -o $@ $(DYLIBFLAGS) $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) $(DEBUGFLAGS) + ifneq ($(abspath $(BIN)),$(abspath $(SRCDIR))) # for demonstration purposes, our demo code is also installed # in $BIN, although this would likely not be typical @@ -45,12 +59,14 @@ $(BIN)/LocalTest.jl: $(SRCDIR)/LocalTest.jl cp $< $@ endif -check: $(BIN)/gcext$(EXE) $(BIN)/LocalTest.jl - $(JULIA) --depwarn=error $(SRCDIR)/gcext-test.jl $< +check: $(BIN)/gcext$(EXE) $(BIN)/LocalTest.jl $(BIN)/foreignlib$(DYLIB) + BINDIR=$(BIN) $(JULIA) --depwarn=error $(SRCDIR)/gcext-test.jl $< @echo SUCCESS clean: -rm -f $(BIN)/gcext-debug$(EXE) $(BIN)/gcext$(EXE) + -rm -f $(BIN)/foreignlib$(DYLIB) + -rm -f $(BIN)/foreignlib-debug$(DYLIB) .PHONY: release debug clean check diff --git a/test/gcext/gcext-test.jl b/test/gcext/gcext-test.jl index 0dc9bbadd92b52..81637392e3c5d5 100644 --- a/test/gcext/gcext-test.jl +++ b/test/gcext/gcext-test.jl @@ -2,6 +2,7 @@ # tests the output of the embedding example is correct using Test +using Pkg if Sys.iswindows() # libjulia needs to be in the same directory as the embedding executable or in path @@ -43,3 +44,35 @@ end @test checknum(lines[5], r"([0-9]+) corrupted auxiliary roots", n -> n == 0) end + +@testset "Package with foreign type" begin + load_path = copy(LOAD_PATH) + push!(LOAD_PATH, joinpath(@__DIR__, "Foreign")) + push!(LOAD_PATH, joinpath(@__DIR__, "DependsOnForeign")) + try + # Force recaching + Base.compilecache(Base.identify_package("Foreign")) + Base.compilecache(Base.identify_package("DependsOnForeign")) + + push!(LOAD_PATH, joinpath(@__DIR__, "ForeignObjSerialization")) + @test_throws ErrorException Base.compilecache(Base.identify_package("ForeignObjSerialization"), Base.DevNull()) + pop!(LOAD_PATH) + + (@eval (using Foreign)) + @test Base.invokelatest(Foreign.get_nmark) == 0 + @test Base.invokelatest(Foreign.get_nsweep) == 0 + + obj = Base.invokelatest(Foreign.FObj) + GC.@preserve obj begin + GC.gc(true) + end + @test Base.invokelatest(Foreign.get_nmark) > 0 + @time Base.invokelatest(Foreign.test, 10) + GC.gc(true) + @test Base.invokelatest(Foreign.get_nsweep) > 0 + (@eval (using DependsOnForeign)) + Base.invokelatest(DependsOnForeign.f, obj) + finally + copy!(LOAD_PATH, load_path) + end +end From 328dd578958d9c2a22ddb11970324ecd04e94314 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Tue, 29 Nov 2022 18:48:15 -0500 Subject: [PATCH 048/387] Fix generator-invocation legality check for varargs generators (#47739) This code was introduced by me back in #31025 to speed up evaluation of generated functions that didn't make use of all of their arguments to make generation decisions. However, it neglected to take into account the possibility that the generator could be varargs. As a result, an unfortunate coincidence of an unused slot in the correct position could have allowed expansion of generators that were not supposed to be expandable. This can cause incorrect inference with all the usual consequences. However, fortunately this coincidence appears to be pretty rare. Fixes https://github.com/JuliaDebug/CassetteOverlay.jl/issues/12 --- base/reflection.jl | 14 +++++++++++++- test/staged.jl | 15 +++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/base/reflection.jl b/base/reflection.jl index 3f5383239e29ed..7c251dfe6a4f29 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1165,13 +1165,25 @@ function may_invoke_generator(method::Method, @nospecialize(atype), sparams::Sim end end end - for i = 1:length(at.parameters) + non_va_args = method.isva ? method.nargs - 1 : method.nargs + for i = 1:non_va_args if !isdispatchelem(at.parameters[i]) if (ast_slotflag(code, 1 + i + nsparams) & SLOT_USED) != 0 return false end end end + if method.isva + # If the va argument is used, we need to ensure that all arguments that + # contribute to the va tuple are dispatchelemes + if (ast_slotflag(code, 1 + method.nargs + nsparams) & SLOT_USED) != 0 + for i = (non_va_args+1):length(at.parameters) + if !isdispatchelem(at.parameters[i]) + return false + end + end + end + end return true end diff --git a/test/staged.jl b/test/staged.jl index b99ef46a2bc1ef..516baea93ec048 100644 --- a/test/staged.jl +++ b/test/staged.jl @@ -305,3 +305,18 @@ end end @test f33243() === 2 @test x33243 === 2 + +# https://github.com/JuliaDebug/CassetteOverlay.jl/issues/12 +# generated function with varargs and unfortunately placed unused slot +@generated function f_vararg_generated(args...) + :($args) +end +g_vararg_generated() = f_vararg_generated((;), (;), Base.inferencebarrier((;))) +let tup = g_vararg_generated() + @test !any(==(Any), tup) + # This is just to make sure that the test is actually testing what we want - + # the test only works if there's an unused that matches the position of the + # inferencebarrier argument above (N.B. the generator function itself + # shifts everything over by 1) + @test code_lowered(first(methods(f_vararg_generated)).generator.gen)[1].slotflags[5] == UInt8(0x00) +end From 41b73e2c71ca844a372af2b75325940cfcb681b6 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Wed, 30 Nov 2022 07:37:13 +0100 Subject: [PATCH 049/387] Bump libuv. (#47707) Adapts to the final version of the constrained/available memory APIs. --- base/sysinfo.jl | 9 ++--- deps/checksums/libuv | 64 +++++++++++++++++------------------ deps/libuv.version | 2 +- src/gc.c | 2 +- stdlib/LibUV_jll/Project.toml | 2 +- 5 files changed, 40 insertions(+), 39 deletions(-) diff --git a/base/sysinfo.jl b/base/sysinfo.jl index 85bf53efba1f23..be11d5fb1cc989 100644 --- a/base/sysinfo.jl +++ b/base/sysinfo.jl @@ -279,11 +279,12 @@ This amount may be constrained, e.g., by Linux control groups. For the unconstra amount, see `Sys.physical_memory()`. """ function total_memory() - memory = ccall(:uv_get_constrained_memory, UInt64, ()) - if memory == 0 - return total_physical_memory() + constrained = ccall(:uv_get_constrained_memory, UInt64, ()) + physical = total_physical_memory() + if 0 < constrained <= physical + return constrained else - return memory + return physical end end diff --git a/deps/checksums/libuv b/deps/checksums/libuv index 844b063287c6d0..81bc2178963d34 100644 --- a/deps/checksums/libuv +++ b/deps/checksums/libuv @@ -1,34 +1,34 @@ -LibUV.v2.0.1+11.aarch64-apple-darwin.tar.gz/md5/60c0a26acbd9c6d35743c19ac917f9b9 -LibUV.v2.0.1+11.aarch64-apple-darwin.tar.gz/sha512/4f62658c10486040ffe04e8e694fbcdb2a07340d8f1d18b703598141f5b377c421e06b7896dc0be8472c6c9f748ff44be109db99304b0442f10eb878bf2af1df -LibUV.v2.0.1+11.aarch64-linux-gnu.tar.gz/md5/215a204f1fb13a8d1fc9b26106814bee -LibUV.v2.0.1+11.aarch64-linux-gnu.tar.gz/sha512/3f20dc865a1ebae98ac75581585c5057b6c27bbfe084580274089f3103b4ad5fceee7dd5822b6f1cee4dfdfe027a379ea5116e37ca331845108380d6c2ecf63f -LibUV.v2.0.1+11.aarch64-linux-musl.tar.gz/md5/b618837c1c2ff1e64578ae043c0a00c3 -LibUV.v2.0.1+11.aarch64-linux-musl.tar.gz/sha512/7a82709a183977237f76cc0048034522466843d583519cec95fc7dd39cab1891b397052c6deb69b8d6fab6d0f57c91b642431b579bfb6c790881509b8daaa24c -LibUV.v2.0.1+11.armv6l-linux-gnueabihf.tar.gz/md5/f09464b716b779b6cccc8e8103313acb -LibUV.v2.0.1+11.armv6l-linux-gnueabihf.tar.gz/sha512/7c39685bbb9beb39670c94a3dea0cfac8685c9ff1116026784e68610d9314c281690f87bba918dfcc60f39e3f5c54ce432ab7365f785510be4108fa2454905dc -LibUV.v2.0.1+11.armv6l-linux-musleabihf.tar.gz/md5/6a483f49e053a1d796c2280a165e5cdd -LibUV.v2.0.1+11.armv6l-linux-musleabihf.tar.gz/sha512/16d6ade651018b20e2b465ee9beab6d6442a8d3942249a90def2797ac2b2c0376173eb9411f26cdd3f82ae9798640f819e139dd3cd70ce7e4684f6154f68fbfa -LibUV.v2.0.1+11.armv7l-linux-gnueabihf.tar.gz/md5/d3c6110ba03be6136d0c0a3740b2bc21 -LibUV.v2.0.1+11.armv7l-linux-gnueabihf.tar.gz/sha512/a41c26cd52c82804bf14d783965ebf4893db0cae7319d9840777485a328237e9f7c54aa3c2dc9a0ee39f98db430b8616de6f60906fbd00771f9a50e989e68fde -LibUV.v2.0.1+11.armv7l-linux-musleabihf.tar.gz/md5/a302e22ac3bc6d0909cd1b2a90c712ac -LibUV.v2.0.1+11.armv7l-linux-musleabihf.tar.gz/sha512/dd0291b86e11dbf7a8cf5b22f862bb0a93dcfd0d5ae009fe0c53f569d012bc2ea4895976c699aabd79ce05f4ae6161ce56263859c1994ea696e50f918fc2f51b -LibUV.v2.0.1+11.i686-linux-gnu.tar.gz/md5/d3b8cfaee74da3f4ba58c6845345ebfe -LibUV.v2.0.1+11.i686-linux-gnu.tar.gz/sha512/9623b84f6411f9b7c5a67f5e346d6661f00103a8417e22018b513efa3b8904268c57c7de21cc2f66a55727060436159f70727beed49b7efc882befd4d399332d -LibUV.v2.0.1+11.i686-linux-musl.tar.gz/md5/0e04697b85d2798c19f56e437eb55e56 -LibUV.v2.0.1+11.i686-linux-musl.tar.gz/sha512/75373bb5a5e3dd8f3fa4a85664bcfa0c651a793d8b104264eafa9626520cfb936025d4b1540c8e6d16a73468b7a1068a5ab4fb3b37762404d1ef7225a85e1664 -LibUV.v2.0.1+11.i686-w64-mingw32.tar.gz/md5/617dfd4290517837ad4c709dc4301733 -LibUV.v2.0.1+11.i686-w64-mingw32.tar.gz/sha512/7069f8bbb876ab5e2a7f0d79f4a297cd7984e1a83eadb1f91f5de86afc951b38e5bf2641883a4b7f327eabbc2f25434453b855ff7d537d30cc5ae6c8a00341d4 -LibUV.v2.0.1+11.powerpc64le-linux-gnu.tar.gz/md5/70f16a63097a353fa45971d3e4313da4 -LibUV.v2.0.1+11.powerpc64le-linux-gnu.tar.gz/sha512/ecc9f39fef7e9917dbadf4a7fd7966d06fb240f73cc2df021d9b8fa1951655d078782f17948abbfb5a21f2b7fcd9c7390af0a05610a9b952d55d53b6826ec312 -LibUV.v2.0.1+11.x86_64-apple-darwin.tar.gz/md5/17fee1aaeb6947614705120a62a21fa4 -LibUV.v2.0.1+11.x86_64-apple-darwin.tar.gz/sha512/cf4c80e797e3d68f54916bae6163d948f0a300f201f2b8209310970751d68eef6c29da571721aa98794c9ae30f7dc655385a5091c716e0402d3241342a1d9544 -LibUV.v2.0.1+11.x86_64-linux-gnu.tar.gz/md5/7e2cfbd1d4cdf2afec2ab18f0f75e812 -LibUV.v2.0.1+11.x86_64-linux-gnu.tar.gz/sha512/8551dbaf242c859010481e12864d75e8df01c69a90b94293402881b50e32105add7f7fdae455144076a2169f37e5796eb528d8ef6fc02226fbbb9d0f1bc6f6d3 -LibUV.v2.0.1+11.x86_64-linux-musl.tar.gz/md5/3879f86977865ceac0ea36e3f563be73 -LibUV.v2.0.1+11.x86_64-linux-musl.tar.gz/sha512/0831c0606e9bed4f819cb8f2abba464c9e0034533abdb5bf6e6e92b9f37644103c39adc4498db5128395dc65da28c93d7cd01bfc474985fa5dd660b04ca14cc1 -LibUV.v2.0.1+11.x86_64-unknown-freebsd.tar.gz/md5/288d9ab3dd95028568880838462c1f35 -LibUV.v2.0.1+11.x86_64-unknown-freebsd.tar.gz/sha512/ac0366d8eb4d0908d5ea55105dc608418455bc601fc22058512e228225cbd1ad2c778f7838b9d2374a6f1661e386f4121bae0f4cecaa18a4ba70a3a743318e24 -LibUV.v2.0.1+11.x86_64-w64-mingw32.tar.gz/md5/2b390151d13474968444b0f07adc92c0 -LibUV.v2.0.1+11.x86_64-w64-mingw32.tar.gz/sha512/6c56a7ab3e28ebcc7e55917b5ba051b4725ca77752b5206f865b306e905d119170cd0bb4e117c7352a95aa13b814ec5e15547ec3904615b561775a17e6993741 +LibUV.v2.0.1+13.aarch64-apple-darwin.tar.gz/md5/1a58ce9dc88984c3b5f7df97af6cbf83 +LibUV.v2.0.1+13.aarch64-apple-darwin.tar.gz/sha512/2bfd482ac759ac88d885371854affa8e358a10fea6c7756e0d1b366bc82ecbea56bdf24ca634525fb2a6fc2b3a5c77b07a4c6dec2923d8bffe2bc962bd3e7f84 +LibUV.v2.0.1+13.aarch64-linux-gnu.tar.gz/md5/7f270dd1e3046c8db432e350dd5cf114 +LibUV.v2.0.1+13.aarch64-linux-gnu.tar.gz/sha512/c0debcf17b54ba9f1588d4b267d610751f739d8ff96936c9d5fb6d8742039f8736c63fa70037322705569e221d73fb83c03b6ba9fb4454442fffd3a9f1a1a2da +LibUV.v2.0.1+13.aarch64-linux-musl.tar.gz/md5/07f56c32d5a2c12e6c351cf9f705631c +LibUV.v2.0.1+13.aarch64-linux-musl.tar.gz/sha512/8037d7aa0cb06850f055fd19cebdcfcf3146dde0d12768a9669bf05dcab91fdf3708798203258cb3f452158bdec7faae41e6afbb0e60b21403e683db3e23a1c9 +LibUV.v2.0.1+13.armv6l-linux-gnueabihf.tar.gz/md5/5558a7f68c7c375f40bc64da59fef0ad +LibUV.v2.0.1+13.armv6l-linux-gnueabihf.tar.gz/sha512/92ed6601cb5aa9a3ea2478a1485849543c9e847c8e85542e72f372a2d37c4c8b90f5ecb1bee1e462db31e1e8dba460f584b3cca9c833989c2b9ee404e355654e +LibUV.v2.0.1+13.armv6l-linux-musleabihf.tar.gz/md5/de6bfb7f0c0468b79e8895f166fb6340 +LibUV.v2.0.1+13.armv6l-linux-musleabihf.tar.gz/sha512/7948d007171bf57b827b489f3627ac74df447f4d696e8226e54e95ef0c8eed5a5ddbf758fbad841bc367f78cd61e6a5899eb478003dca3a79cb494b38cab830b +LibUV.v2.0.1+13.armv7l-linux-gnueabihf.tar.gz/md5/5be35de1d881f80981647c369b9b4ec8 +LibUV.v2.0.1+13.armv7l-linux-gnueabihf.tar.gz/sha512/458e5058ea4e794e0dc790da4c98569676056bac336df69762e8ccfec8f2955dcc55e8d090daa1b191c0ffa41392a04530c9bc28aa27cf411c1df2f1ba14bb97 +LibUV.v2.0.1+13.armv7l-linux-musleabihf.tar.gz/md5/8d034490da1ec2ef3dd3c69336177654 +LibUV.v2.0.1+13.armv7l-linux-musleabihf.tar.gz/sha512/7f595a8ab8b664d229cf6144e9ed1b5936ba8aaa70b92611ddb85bbe9046bb1b94d8417355a5abf058fb00023d4d56be0b2ddfd5dba896cd7b64e84e32dbfc5a +LibUV.v2.0.1+13.i686-linux-gnu.tar.gz/md5/ccb9aba78456c99b8473e8ddd328f90e +LibUV.v2.0.1+13.i686-linux-gnu.tar.gz/sha512/d382d90137db308933257a75e51d90988d6d07663b3b2915478547127d32f73ae6cdb4575d5ee20758f8850c7e85908fe4710c053cb361826621f22bc5b6502d +LibUV.v2.0.1+13.i686-linux-musl.tar.gz/md5/5ade48f16aa26bb68dc046d285c73043 +LibUV.v2.0.1+13.i686-linux-musl.tar.gz/sha512/f5728a5dc567268e59aa2697deb793ae427e11dcb6796c577e3da3ac24225ece5d4a6c4f903d4a7b184d3c3a3c8c1586c34b97e4a75de0a4e23ace720020fa8c +LibUV.v2.0.1+13.i686-w64-mingw32.tar.gz/md5/399d6fbe54dcfb2f997f276cd38fd185 +LibUV.v2.0.1+13.i686-w64-mingw32.tar.gz/sha512/55707e02a4b5bdf9c94683dbaaea1cac58f7735d5ae22009c219ea61ddfab1fe19b9bc6e830fc32207efc588c27f92770d2441b972f351a1bb3fdbbf5671a58b +LibUV.v2.0.1+13.powerpc64le-linux-gnu.tar.gz/md5/26656d4eaae8739099c55054bad54f57 +LibUV.v2.0.1+13.powerpc64le-linux-gnu.tar.gz/sha512/f85f8cfd91e7b1b02b073931ef9a3bb05620641d18ada039744a92b8c40e5a3de8d7c5efa7189b88baf1eb11fbcf9e6d16031b86e40f99f1b7cfebb0f5c5adf1 +LibUV.v2.0.1+13.x86_64-apple-darwin.tar.gz/md5/c7da6b91394a20c43acdf6f680cb62e2 +LibUV.v2.0.1+13.x86_64-apple-darwin.tar.gz/sha512/238d22bd299ae3b0dfd24a5b38d6d0d07b751fb301487a2d1d2f5313ae3596f33492388ea9fbff549293787505fc527e174ebcd4068f1bda43b40bc19e016d89 +LibUV.v2.0.1+13.x86_64-linux-gnu.tar.gz/md5/8c8913068263257cce5042b725918e0e +LibUV.v2.0.1+13.x86_64-linux-gnu.tar.gz/sha512/a848381012d5a20a0c881f5835e479cfff811928ce508cc57041d69668782f2135c14c7e5388e7dbf693ae57aa1825d911f6f450b9e909cce45487b03a581a23 +LibUV.v2.0.1+13.x86_64-linux-musl.tar.gz/md5/16747c066b6d7fe56850c77f66ea7478 +LibUV.v2.0.1+13.x86_64-linux-musl.tar.gz/sha512/833a02f9191edf3b56f1e02f5671f22de6cb27ec3c9f770530ec95d8da7ba0b9c05bcdf6b094224ea8e43ba70918e1599f3237bd98900763daef80c327d3d2de +LibUV.v2.0.1+13.x86_64-unknown-freebsd.tar.gz/md5/71f7d9d9234a0623c4b2ee3a44089b62 +LibUV.v2.0.1+13.x86_64-unknown-freebsd.tar.gz/sha512/e73911c3ec35a2201d42c035ecc86e8bd860604b950cb1b7784ff49e27ef5ac9b1da09b59d359ff25b093b87593a8305105bc43711c12eb9654972e280c26d3c +LibUV.v2.0.1+13.x86_64-w64-mingw32.tar.gz/md5/471d20fa2eac6bfd5d7cdb1b7f58c602 +LibUV.v2.0.1+13.x86_64-w64-mingw32.tar.gz/sha512/3f5ad55268184227378ddcfed0146bf0386c8cf468bc53a348d21195d818db4db768be61fd23e1ee2ecbb52f073815884a04a923d815b9b5992825d144c0633a libuv-e6f0e4900e195c8352f821abe2b3cffc3089547b.tar.gz/md5/c4465d7bff6610761cf37a1e8e3da08c libuv-e6f0e4900e195c8352f821abe2b3cffc3089547b.tar.gz/sha512/3347668b2b377704f3188e8901b130e891d19ac944ab3b7c1f4939d7afa119afff7dc10feaa2a518ec4122968147e31eb8932c6dfc1142a58a4828488f343191 diff --git a/deps/libuv.version b/deps/libuv.version index e4b277e36b099e..01bf4fecc6dc6f 100644 --- a/deps/libuv.version +++ b/deps/libuv.version @@ -4,4 +4,4 @@ LIBUV_JLL_NAME := LibUV ## source build LIBUV_VER := 2 LIBUV_BRANCH=julia-uv2-1.44.2 -LIBUV_SHA1=e6f0e4900e195c8352f821abe2b3cffc3089547b +LIBUV_SHA1=2723e256e952be0b015b3c0086f717c3d365d97e diff --git a/src/gc.c b/src/gc.c index 0fa2077f4edaf7..aebf3300a71a91 100644 --- a/src/gc.c +++ b/src/gc.c @@ -3683,7 +3683,7 @@ void jl_gc_init(void) #ifdef _P64 total_mem = uv_get_total_memory(); uint64_t constrained_mem = uv_get_constrained_memory(); - if (constrained_mem != 0) + if (constrained_mem > 0 && constrained_mem < total_mem) total_mem = constrained_mem; #endif diff --git a/stdlib/LibUV_jll/Project.toml b/stdlib/LibUV_jll/Project.toml index 6f68176fc97e73..29548099214403 100644 --- a/stdlib/LibUV_jll/Project.toml +++ b/stdlib/LibUV_jll/Project.toml @@ -1,6 +1,6 @@ name = "LibUV_jll" uuid = "183b4373-6708-53ba-ad28-60e28bb38547" -version = "2.0.1+11" +version = "2.0.1+13" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" From 5da8d5f17ad9505fdb425c302f3dbac36eef7a55 Mon Sep 17 00:00:00 2001 From: Michael Abbott <32575566+mcabbott@users.noreply.github.com> Date: Wed, 30 Nov 2022 06:11:07 -0500 Subject: [PATCH 050/387] Add a warning about inadvertent Diagonal(::Matrix) (#46864) --- stdlib/LinearAlgebra/src/diagonal.jl | 31 +++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/stdlib/LinearAlgebra/src/diagonal.jl b/stdlib/LinearAlgebra/src/diagonal.jl index 291233ebe2e6ae..1cb4240722ce2e 100644 --- a/stdlib/LinearAlgebra/src/diagonal.jl +++ b/stdlib/LinearAlgebra/src/diagonal.jl @@ -24,13 +24,14 @@ end """ Diagonal(V::AbstractVector) -Construct a matrix with `V` as its diagonal. +Construct a lazy matrix with `V` as its diagonal. -See also [`diag`](@ref), [`diagm`](@ref). +See also [`UniformScaling`](@ref) for the lazy identity matrix `I`, +[`diagm`](@ref) to make a dense matrix, and [`diag`](@ref) to extract diagonal elements. # Examples ```jldoctest -julia> Diagonal([1, 10, 100]) +julia> d = Diagonal([1, 10, 100]) 3×3 Diagonal{$Int, Vector{$Int}}: 1 ⋅ ⋅ ⋅ 10 ⋅ @@ -40,6 +41,30 @@ julia> diagm([7, 13]) 2×2 Matrix{$Int}: 7 0 0 13 + +julia> ans + I +2×2 Matrix{Int64}: + 8 0 + 0 14 + +julia> I(2) +2×2 Diagonal{Bool, Vector{Bool}}: + 1 ⋅ + ⋅ 1 +``` + +Note that a one-column matrix is not treated like a vector, but instead calls the +method `Diagonal(A::AbstractMatrix)` which extracts 1-element `diag(A)`: + +```jldoctest +julia> A = transpose([7.0 13.0]) +2×1 transpose(::Matrix{Float64}) with eltype Float64: + 7.0 + 13.0 + +julia> Diagonal(A) +1×1 Diagonal{Float64, Vector{Float64}}: + 7.0 ``` """ Diagonal(V::AbstractVector) From ee54dd528ea12ff0007c3cd85fff3483449ff45e Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Wed, 30 Nov 2022 14:13:41 +0100 Subject: [PATCH 051/387] reduce ambiguity in *diagonal multiplication code (#47683) --- stdlib/LinearAlgebra/src/bidiag.jl | 106 ++++++++++--------------- stdlib/LinearAlgebra/src/diagonal.jl | 6 +- stdlib/LinearAlgebra/src/hessenberg.jl | 4 +- stdlib/LinearAlgebra/src/tridiag.jl | 54 +------------ 4 files changed, 52 insertions(+), 118 deletions(-) diff --git a/stdlib/LinearAlgebra/src/bidiag.jl b/stdlib/LinearAlgebra/src/bidiag.jl index 218fd67a1b9d22..90f5c03f7fcfb8 100644 --- a/stdlib/LinearAlgebra/src/bidiag.jl +++ b/stdlib/LinearAlgebra/src/bidiag.jl @@ -206,7 +206,7 @@ AbstractMatrix{T}(A::Bidiagonal) where {T} = convert(Bidiagonal{T}, A) convert(::Type{T}, m::AbstractMatrix) where {T<:Bidiagonal} = m isa T ? m : T(m)::T similar(B::Bidiagonal, ::Type{T}) where {T} = Bidiagonal(similar(B.dv, T), similar(B.ev, T), B.uplo) -similar(B::Bidiagonal, ::Type{T}, dims::Union{Dims{1},Dims{2}}) where {T} = zeros(T, dims...) +similar(B::Bidiagonal, ::Type{T}, dims::Union{Dims{1},Dims{2}}) where {T} = similar(B.dv, T, dims) tr(B::Bidiagonal) = sum(B.dv) @@ -407,38 +407,32 @@ end const BiTriSym = Union{Bidiagonal,Tridiagonal,SymTridiagonal} const BiTri = Union{Bidiagonal,Tridiagonal} -@inline mul!(C::AbstractMatrix, A::SymTridiagonal, B::BiTriSym, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::BiTri, B::BiTriSym, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::AbstractMatrix, B::BiTriSym, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::Diagonal, B::BiTriSym, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::Adjoint{<:Any,<:AbstractVecOrMat}, B::BiTriSym, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::Transpose{<:Any,<:AbstractVecOrMat}, B::BiTriSym, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -# for A::SymTridiagonal see tridiagonal.jl -@inline mul!(C::AbstractVector, A::BiTri, B::AbstractVector, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::BiTri, B::AbstractMatrix, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::BiTri, B::Diagonal, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::SymTridiagonal, B::Diagonal, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::BiTri, B::Transpose{<:Any,<:AbstractVecOrMat}, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::BiTri, B::Adjoint{<:Any,<:AbstractVecOrMat}, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) +@inline mul!(C::AbstractVector, A::BiTriSym, B::AbstractVector, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) +@inline mul!(C::AbstractMatrix, A::BiTriSym, B::AbstractMatrix, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) +@inline mul!(C::AbstractMatrix, A::BiTriSym, B::Transpose{<:Any,<:AbstractVecOrMat}, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) +@inline mul!(C::AbstractMatrix, A::BiTriSym, B::Adjoint{<:Any,<:AbstractVecOrMat}, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) +@inline mul!(C::AbstractMatrix, A::BiTriSym, B::Diagonal, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) +@inline mul!(C::AbstractMatrix, A::AbstractMatrix, B::BiTriSym, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) +@inline mul!(C::AbstractMatrix, A::Adjoint{<:Any,<:AbstractVecOrMat}, B::BiTriSym, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) +@inline mul!(C::AbstractMatrix, A::Transpose{<:Any,<:AbstractVecOrMat}, B::BiTriSym, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) +@inline mul!(C::AbstractMatrix, A::BiTriSym, B::BiTriSym, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) +@inline mul!(C::AbstractMatrix, A::Diagonal, B::BiTriSym, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) function check_A_mul_B!_sizes(C, A, B) - require_one_based_indexing(C) - require_one_based_indexing(A) - require_one_based_indexing(B) - nA, mA = size(A) - nB, mB = size(B) - nC, mC = size(C) - if nA != nC - throw(DimensionMismatch("sizes size(A)=$(size(A)) and size(C) = $(size(C)) must match at first entry.")) - elseif mA != nB - throw(DimensionMismatch("second entry of size(A)=$(size(A)) and first entry of size(B) = $(size(B)) must match.")) - elseif mB != mC - throw(DimensionMismatch("sizes size(B)=$(size(B)) and size(C) = $(size(C)) must match at first second entry.")) + mA, nA = size(A) + mB, nB = size(B) + mC, nC = size(C) + if mA != mC + throw(DimensionMismatch("first dimension of A, $mA, and first dimension of output C, $mC, must match")) + elseif nA != mB + throw(DimensionMismatch("second dimension of A, $nA, and first dimension of B, $mB, must match")) + elseif nB != nC + throw(DimensionMismatch("second dimension of output C, $nC, and second dimension of B, $nB, must match")) end end # function to get the internally stored vectors for Bidiagonal and [Sym]Tridiagonal -# to avoid allocations in A_mul_B_td! below (#24324, #24578) +# to avoid allocations in _mul! below (#24324, #24578) _diag(A::Tridiagonal, k) = k == -1 ? A.dl : k == 0 ? A.d : A.du _diag(A::SymTridiagonal, k) = k == 0 ? A.dv : A.ev function _diag(A::Bidiagonal, k) @@ -451,8 +445,7 @@ function _diag(A::Bidiagonal, k) end end -function A_mul_B_td!(C::AbstractMatrix, A::BiTriSym, B::BiTriSym, - _add::MulAddMul = MulAddMul()) +function _mul!(C::AbstractMatrix, A::BiTriSym, B::BiTriSym, _add::MulAddMul = MulAddMul()) check_A_mul_B!_sizes(C, A, B) n = size(A,1) n <= 3 && return mul!(C, Array(A), Array(B), _add.alpha, _add.beta) @@ -509,10 +502,11 @@ function A_mul_B_td!(C::AbstractMatrix, A::BiTriSym, B::BiTriSym, C end -function A_mul_B_td!(C::AbstractMatrix, A::BiTriSym, B::Diagonal, - _add::MulAddMul = MulAddMul()) +function _mul!(C::AbstractMatrix, A::BiTriSym, B::Diagonal, _add::MulAddMul = MulAddMul()) + require_one_based_indexing(C) check_A_mul_B!_sizes(C, A, B) n = size(A,1) + iszero(n) && return C n <= 3 && return mul!(C, Array(A), Array(B), _add.alpha, _add.beta) _rmul_or_fill!(C, _add.beta) # see the same use above iszero(_add.alpha) && return C @@ -544,10 +538,8 @@ function A_mul_B_td!(C::AbstractMatrix, A::BiTriSym, B::Diagonal, C end -function A_mul_B_td!(C::AbstractVecOrMat, A::BiTriSym, B::AbstractVecOrMat, - _add::MulAddMul = MulAddMul()) - require_one_based_indexing(C) - require_one_based_indexing(B) +function _mul!(C::AbstractVecOrMat, A::BiTriSym, B::AbstractVecOrMat, _add::MulAddMul = MulAddMul()) + require_one_based_indexing(C, B) nA = size(A,1) nB = size(B,2) if !(size(C,1) == size(B,1) == nA) @@ -556,6 +548,7 @@ function A_mul_B_td!(C::AbstractVecOrMat, A::BiTriSym, B::AbstractVecOrMat, if size(C,2) != nB throw(DimensionMismatch("A has second dimension $nA, B has $(size(B,2)), C has $(size(C,2)) but all must match")) end + iszero(nA) && return C iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) nA <= 3 && return mul!(C, Array(A), Array(B), _add.alpha, _add.beta) l = _diag(A, -1) @@ -575,8 +568,8 @@ function A_mul_B_td!(C::AbstractVecOrMat, A::BiTriSym, B::AbstractVecOrMat, C end -function A_mul_B_td!(C::AbstractMatrix, A::AbstractMatrix, B::BiTriSym, - _add::MulAddMul = MulAddMul()) +function _mul!(C::AbstractMatrix, A::AbstractMatrix, B::BiTriSym, _add::MulAddMul = MulAddMul()) + require_one_based_indexing(C, A) check_A_mul_B!_sizes(C, A, B) iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) n = size(A,1) @@ -610,8 +603,8 @@ function A_mul_B_td!(C::AbstractMatrix, A::AbstractMatrix, B::BiTriSym, C end -function A_mul_B_td!(C::AbstractMatrix, A::Diagonal, B::BiTriSym, - _add::MulAddMul = MulAddMul()) +function _mul!(C::AbstractMatrix, A::Diagonal, B::BiTriSym, _add::MulAddMul = MulAddMul()) + require_one_based_indexing(C) check_A_mul_B!_sizes(C, A, B) n = size(A,1) n <= 3 && return mul!(C, Array(A), Array(B), _add.alpha, _add.beta) @@ -648,51 +641,38 @@ end function *(A::UpperOrUnitUpperTriangular, B::Bidiagonal) TS = promote_op(matprod, eltype(A), eltype(B)) - C = A_mul_B_td!(zeros(TS, size(A)), A, B) + C = mul!(similar(A, TS, size(A)), A, B) return B.uplo == 'U' ? UpperTriangular(C) : C end function *(A::LowerOrUnitLowerTriangular, B::Bidiagonal) TS = promote_op(matprod, eltype(A), eltype(B)) - C = A_mul_B_td!(zeros(TS, size(A)), A, B) + C = mul!(similar(A, TS, size(A)), A, B) return B.uplo == 'L' ? LowerTriangular(C) : C end function *(A::Bidiagonal, B::UpperOrUnitUpperTriangular) TS = promote_op(matprod, eltype(A), eltype(B)) - C = A_mul_B_td!(zeros(TS, size(A)), A, B) + C = mul!(similar(B, TS, size(B)), A, B) return A.uplo == 'U' ? UpperTriangular(C) : C end function *(A::Bidiagonal, B::LowerOrUnitLowerTriangular) TS = promote_op(matprod, eltype(A), eltype(B)) - C = A_mul_B_td!(zeros(TS, size(A)), A, B) + C = mul!(similar(B, TS, size(B)), A, B) return A.uplo == 'L' ? LowerTriangular(C) : C end -function *(A::BiTri, B::Diagonal) - TS = promote_op(matprod, eltype(A), eltype(B)) - A_mul_B_td!(similar(A, TS), A, B) -end - -function *(A::Diagonal, B::BiTri) - TS = promote_op(matprod, eltype(A), eltype(B)) - A_mul_B_td!(similar(B, TS), A, B) -end - function *(A::Diagonal, B::SymTridiagonal) - TS = promote_op(matprod, eltype(A), eltype(B)) - A_mul_B_td!(Tridiagonal(zeros(TS, size(A, 1)-1), zeros(TS, size(A, 1)), zeros(TS, size(A, 1)-1)), A, B) + TS = promote_op(*, eltype(A), eltype(B)) + out = Tridiagonal(similar(A, TS, size(A, 1)-1), similar(A, TS, size(A, 1)), similar(A, TS, size(A, 1)-1)) + mul!(out, A, B) end function *(A::SymTridiagonal, B::Diagonal) - TS = promote_op(matprod, eltype(A), eltype(B)) - A_mul_B_td!(Tridiagonal(zeros(TS, size(A, 1)-1), zeros(TS, size(A, 1)), zeros(TS, size(A, 1)-1)), A, B) -end - -function *(A::BiTriSym, B::BiTriSym) - TS = promote_op(matprod, eltype(A), eltype(B)) - mul!(similar(A, TS, size(A)), A, B) + TS = promote_op(*, eltype(A), eltype(B)) + out = Tridiagonal(similar(A, TS, size(A, 1)-1), similar(A, TS, size(A, 1)), similar(A, TS, size(A, 1)-1)) + mul!(out, A, B) end function dot(x::AbstractVector, B::Bidiagonal, y::AbstractVector) @@ -924,7 +904,7 @@ function inv(B::Bidiagonal{T}) where T end # Eigensystems -eigvals(M::Bidiagonal) = M.dv +eigvals(M::Bidiagonal) = copy(M.dv) function eigvecs(M::Bidiagonal{T}) where T n = length(M.dv) Q = Matrix{T}(undef, n,n) diff --git a/stdlib/LinearAlgebra/src/diagonal.jl b/stdlib/LinearAlgebra/src/diagonal.jl index 1cb4240722ce2e..03c5a2bbdeba4d 100644 --- a/stdlib/LinearAlgebra/src/diagonal.jl +++ b/stdlib/LinearAlgebra/src/diagonal.jl @@ -121,7 +121,7 @@ Construct an uninitialized `Diagonal{T}` of length `n`. See `undef`. Diagonal{T}(::UndefInitializer, n::Integer) where T = Diagonal(Vector{T}(undef, n)) similar(D::Diagonal, ::Type{T}) where {T} = Diagonal(similar(D.diag, T)) -similar(::Diagonal, ::Type{T}, dims::Union{Dims{1},Dims{2}}) where {T} = zeros(T, dims...) +similar(D::Diagonal, ::Type{T}, dims::Union{Dims{1},Dims{2}}) where {T} = similar(D.diag, T, dims) copyto!(D1::Diagonal, D2::Diagonal) = (copyto!(D1.diag, D2.diag); D1) @@ -270,8 +270,12 @@ function (*)(D::Diagonal, V::AbstractVector) end (*)(A::AbstractMatrix, D::Diagonal) = + mul!(similar(A, promote_op(*, eltype(A), eltype(D.diag))), A, D) +(*)(A::HermOrSym, D::Diagonal) = mul!(similar(A, promote_op(*, eltype(A), eltype(D.diag)), size(A)), A, D) (*)(D::Diagonal, A::AbstractMatrix) = + mul!(similar(A, promote_op(*, eltype(A), eltype(D.diag))), D, A) +(*)(D::Diagonal, A::HermOrSym) = mul!(similar(A, promote_op(*, eltype(A), eltype(D.diag)), size(A)), D, A) rmul!(A::AbstractMatrix, D::Diagonal) = @inline mul!(A, A, D) diff --git a/stdlib/LinearAlgebra/src/hessenberg.jl b/stdlib/LinearAlgebra/src/hessenberg.jl index eb7c3642ae7b49..c7c4c3a50a2398 100644 --- a/stdlib/LinearAlgebra/src/hessenberg.jl +++ b/stdlib/LinearAlgebra/src/hessenberg.jl @@ -164,12 +164,12 @@ end function *(H::UpperHessenberg, B::Bidiagonal) TS = promote_op(matprod, eltype(H), eltype(B)) - A = A_mul_B_td!(zeros(TS, size(H)), H, B) + A = mul!(similar(H, TS, size(H)), H, B) return B.uplo == 'U' ? UpperHessenberg(A) : A end function *(B::Bidiagonal, H::UpperHessenberg) TS = promote_op(matprod, eltype(B), eltype(H)) - A = A_mul_B_td!(zeros(TS, size(H)), B, H) + A = mul!(similar(H, TS, size(H)), B, H) return B.uplo == 'U' ? UpperHessenberg(A) : A end diff --git a/stdlib/LinearAlgebra/src/tridiag.jl b/stdlib/LinearAlgebra/src/tridiag.jl index e43e9e699e3a99..8821dc064a960f 100644 --- a/stdlib/LinearAlgebra/src/tridiag.jl +++ b/stdlib/LinearAlgebra/src/tridiag.jl @@ -149,7 +149,7 @@ function size(A::SymTridiagonal, d::Integer) end similar(S::SymTridiagonal, ::Type{T}) where {T} = SymTridiagonal(similar(S.dv, T), similar(S.ev, T)) -similar(S::SymTridiagonal, ::Type{T}, dims::Union{Dims{1},Dims{2}}) where {T} = zeros(T, dims...) +similar(S::SymTridiagonal, ::Type{T}, dims::Union{Dims{1},Dims{2}}) where {T} = similar(S.dv, T, dims) copyto!(dest::SymTridiagonal, src::SymTridiagonal) = (copyto!(dest.dv, src.dv); copyto!(dest.ev, _evview(src)); dest) @@ -215,56 +215,6 @@ end \(B::Number, A::SymTridiagonal) = SymTridiagonal(B\A.dv, B\A.ev) ==(A::SymTridiagonal, B::SymTridiagonal) = (A.dv==B.dv) && (_evview(A)==_evview(B)) -@inline mul!(A::AbstractVector, B::SymTridiagonal, C::AbstractVector, - alpha::Number, beta::Number) = - _mul!(A, B, C, MulAddMul(alpha, beta)) -@inline mul!(A::AbstractMatrix, B::SymTridiagonal, C::AbstractVecOrMat, - alpha::Number, beta::Number) = - _mul!(A, B, C, MulAddMul(alpha, beta)) -# disambiguation -@inline mul!(C::AbstractMatrix, A::SymTridiagonal, B::Transpose{<:Any,<:AbstractVecOrMat}, - alpha::Number, beta::Number) = - _mul!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::SymTridiagonal, B::Adjoint{<:Any,<:AbstractVecOrMat}, - alpha::Number, beta::Number) = - _mul!(C, A, B, MulAddMul(alpha, beta)) - -@inline function _mul!(C::AbstractVecOrMat, S::SymTridiagonal, B::AbstractVecOrMat, - _add::MulAddMul) - m, n = size(B, 1), size(B, 2) - if !(m == size(S, 1) == size(C, 1)) - throw(DimensionMismatch("A has first dimension $(size(S,1)), B has $(size(B,1)), C has $(size(C,1)) but all must match")) - end - if n != size(C, 2) - throw(DimensionMismatch("second dimension of B, $n, doesn't match second dimension of C, $(size(C,2))")) - end - - if m == 0 - return C - elseif iszero(_add.alpha) - return _rmul_or_fill!(C, _add.beta) - end - - α = S.dv - β = S.ev - @inbounds begin - for j = 1:n - x₊ = B[1, j] - x₀ = zero(x₊) - # If m == 1 then β[1] is out of bounds - β₀ = m > 1 ? zero(β[1]) : zero(eltype(β)) - for i = 1:m - 1 - x₋, x₀, x₊ = x₀, x₊, B[i + 1, j] - β₋, β₀ = β₀, β[i] - _modify!(_add, β₋*x₋ + α[i]*x₀ + β₀*x₊, C, (i, j)) - end - _modify!(_add, β₀*x₀ + α[m]*x₊, C, (m, j)) - end - end - - return C -end - function dot(x::AbstractVector, S::SymTridiagonal, y::AbstractVector) require_one_based_indexing(x, y) nx, ny = length(x), length(y) @@ -605,7 +555,7 @@ Matrix(M::Tridiagonal{T}) where {T} = Matrix{promote_type(T, typeof(zero(T)))}(M Array(M::Tridiagonal) = Matrix(M) similar(M::Tridiagonal, ::Type{T}) where {T} = Tridiagonal(similar(M.dl, T), similar(M.d, T), similar(M.du, T)) -similar(M::Tridiagonal, ::Type{T}, dims::Union{Dims{1},Dims{2}}) where {T} = zeros(T, dims...) +similar(M::Tridiagonal, ::Type{T}, dims::Union{Dims{1},Dims{2}}) where {T} = similar(M.d, T, dims) # Operations on Tridiagonal matrices copyto!(dest::Tridiagonal, src::Tridiagonal) = (copyto!(dest.dl, src.dl); copyto!(dest.d, src.d); copyto!(dest.du, src.du); dest) From 060a4920a03d062ee42c911ea262f53e3da45bbe Mon Sep 17 00:00:00 2001 From: Max Horn Date: Wed, 30 Nov 2022 18:16:18 +0100 Subject: [PATCH 052/387] Provider cycleclock() for 32bit ARM targets (#47358) Based on https://github.com/google/benchmark/blob/main/src/cycleclock.h --- src/julia_internal.h | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/julia_internal.h b/src/julia_internal.h index 6ddfa5d92072c9..6e3b036177c7b7 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -20,6 +20,9 @@ #else #define sleep(x) Sleep(1000*x) #endif +#if defined(_CPU_ARM_) +#include +#endif #ifdef __cplusplus extern "C" { @@ -216,6 +219,26 @@ static inline uint64_t cycleclock(void) JL_NOTSAFEPOINT int64_t virtual_timer_value; __asm__ volatile("mrs %0, cntvct_el0" : "=r"(virtual_timer_value)); return virtual_timer_value; +#elif defined(_CPU_ARM_) + // V6 is the earliest arch that has a standard cyclecount +#if (__ARM_ARCH >= 6) + uint32_t pmccntr; + uint32_t pmuseren; + uint32_t pmcntenset; + // Read the user mode perf monitor counter access permissions. + asm volatile("mrc p15, 0, %0, c9, c14, 0" : "=r"(pmuseren)); + if (pmuseren & 1) { // Allows reading perfmon counters for user mode code. + asm volatile("mrc p15, 0, %0, c9, c12, 1" : "=r"(pmcntenset)); + if (pmcntenset & 0x80000000ul) { // Is it counting? + asm volatile("mrc p15, 0, %0, c9, c13, 0" : "=r"(pmccntr)); + // The counter is set up to count every 64th cycle + return (int64_t)(pmccntr) * 64; // Should optimize to << 6 + } + } +#endif + struct timeval tv; + gettimeofday(&tv, NULL); + return (int64_t)(tv.tv_sec) * 1000000 + tv.tv_usec; #elif defined(_CPU_PPC64_) // This returns a time-base, which is not always precisely a cycle-count. // https://reviews.llvm.org/D78084 From f4534d16b47d11ce18902ff4cd8ac0936e5ce971 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 30 Nov 2022 14:19:17 -0500 Subject: [PATCH 053/387] strengthen setglobal to default to release-consume ordering (#47742) In looking at a TSAN report recently, I noticed that globals were getting stored as atomic-unordered (since c92ab5e79ea #44182), instead of atomic-release as intended (since 46135dfce9074e5bf94eb277de28a33cad9cc14f #45484). --- src/builtins.c | 2 +- src/codegen.cpp | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/builtins.c b/src/builtins.c index bf9f886d92ba81..90cc544b47986e 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1201,7 +1201,7 @@ JL_CALLABLE(jl_f_getglobal) JL_CALLABLE(jl_f_setglobal) { - enum jl_memory_order order = jl_memory_order_monotonic; + enum jl_memory_order order = jl_memory_order_release; JL_NARGS(setglobal!, 3, 4); if (nargs == 4) { JL_TYPECHK(setglobal!, symbol, args[3]); diff --git a/src/codegen.cpp b/src/codegen.cpp index ce8f8f4adf48c7..cdc5e00a262817 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2881,6 +2881,7 @@ static bool emit_f_opglobal(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, const jl_cgval_t &sym = argv[2]; const jl_cgval_t &val = argv[3]; enum jl_memory_order order = jl_memory_order_unspecified; + assert(f == jl_builtin_setglobal && modifyop == nullptr && "unimplemented"); if (nargs == 4) { const jl_cgval_t &arg4 = argv[4]; @@ -2890,7 +2891,7 @@ static bool emit_f_opglobal(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, return false; } else - order = jl_memory_order_monotonic; + order = jl_memory_order_release; if (order == jl_memory_order_invalid || order == jl_memory_order_notatomic) { emit_atomic_error(ctx, order == jl_memory_order_invalid ? "invalid atomic ordering" : "setglobal!: module binding cannot be written non-atomically"); @@ -4690,7 +4691,7 @@ static void emit_assignment(jl_codectx_t &ctx, jl_value_t *l, jl_value_t *r, ssi bp = global_binding_pointer(ctx, jl_globalref_mod(l), jl_globalref_name(l), &bnd, true); } if (bp != NULL) { - emit_globalset(ctx, bnd, bp, rval_info, AtomicOrdering::Unordered); + emit_globalset(ctx, bnd, bp, rval_info, AtomicOrdering::Release); // Global variable. Does not need debug info because the debugger knows about // its memory location. } From 784233531265cb84fa3bea3b161397c8fb701d4a Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Thu, 1 Dec 2022 05:32:32 +0900 Subject: [PATCH 054/387] add `raise` optional keyword argument to `Base._which` (#47747) --- base/reflection.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/base/reflection.jl b/base/reflection.jl index 7c251dfe6a4f29..384e4c805f8069 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1498,7 +1498,8 @@ print_statement_costs(args...; kwargs...) = print_statement_costs(stdout, args.. function _which(@nospecialize(tt::Type); method_table::Union{Nothing,Core.MethodTable}=nothing, - world::UInt=get_world_counter()) + world::UInt=get_world_counter(), + raise::Bool=true) if method_table === nothing table = Core.Compiler.InternalMethodTable(world) else @@ -1506,7 +1507,8 @@ function _which(@nospecialize(tt::Type); end match, = Core.Compiler.findsup(tt, table) if match === nothing - error("no unique matching method found for the specified argument types") + raise && error("no unique matching method found for the specified argument types") + return nothing end return match end From c46834b51ab88bafe5e0cc3586be98c1c0bf7805 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Thu, 1 Dec 2022 05:32:46 +0900 Subject: [PATCH 055/387] follow up #47739 (#47746) --- base/reflection.jl | 5 +++-- test/staged.jl | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/base/reflection.jl b/base/reflection.jl index 384e4c805f8069..18b0b81b3236ef 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1165,7 +1165,8 @@ function may_invoke_generator(method::Method, @nospecialize(atype), sparams::Sim end end end - non_va_args = method.isva ? method.nargs - 1 : method.nargs + nargs = Int(method.nargs) + non_va_args = method.isva ? nargs - 1 : nargs for i = 1:non_va_args if !isdispatchelem(at.parameters[i]) if (ast_slotflag(code, 1 + i + nsparams) & SLOT_USED) != 0 @@ -1176,7 +1177,7 @@ function may_invoke_generator(method::Method, @nospecialize(atype), sparams::Sim if method.isva # If the va argument is used, we need to ensure that all arguments that # contribute to the va tuple are dispatchelemes - if (ast_slotflag(code, 1 + method.nargs + nsparams) & SLOT_USED) != 0 + if (ast_slotflag(code, 1 + nargs + nsparams) & SLOT_USED) != 0 for i = (non_va_args+1):length(at.parameters) if !isdispatchelem(at.parameters[i]) return false diff --git a/test/staged.jl b/test/staged.jl index 516baea93ec048..8c260c0048acd9 100644 --- a/test/staged.jl +++ b/test/staged.jl @@ -313,10 +313,10 @@ end end g_vararg_generated() = f_vararg_generated((;), (;), Base.inferencebarrier((;))) let tup = g_vararg_generated() - @test !any(==(Any), tup) + @test all(==(typeof((;))), tup) # This is just to make sure that the test is actually testing what we want - # the test only works if there's an unused that matches the position of the # inferencebarrier argument above (N.B. the generator function itself # shifts everything over by 1) - @test code_lowered(first(methods(f_vararg_generated)).generator.gen)[1].slotflags[5] == UInt8(0x00) + @test only(code_lowered(only(methods(f_vararg_generated)).generator.gen)).slotflags[5] == UInt8(0x00) end From 04a4edbf8d60f737d9bdb33d1f125ca9c2fb0ce3 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 30 Nov 2022 22:28:15 +0100 Subject: [PATCH 056/387] Use `checked_mul` in `length(::ProductIterator)` (#43429) Co-authored-by: Steven G. Johnson --- base/iterators.jl | 8 ++++++-- test/iterators.jl | 6 ++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/base/iterators.jl b/base/iterators.jl index 8cf92aac8ff405..6cf8a6502959a3 100644 --- a/base/iterators.jl +++ b/base/iterators.jl @@ -14,13 +14,17 @@ using .Base: @propagate_inbounds, @isdefined, @boundscheck, @inbounds, Generator, AbstractRange, AbstractUnitRange, UnitRange, LinearIndices, (:), |, +, -, *, !==, !, ==, !=, <=, <, >, >=, missing, - any, _counttuple, eachindex, ntuple, zero, prod, in, firstindex, lastindex, + any, _counttuple, eachindex, ntuple, zero, prod, reduce, in, firstindex, lastindex, tail, fieldtypes, min, max, minimum, zero, oneunit, promote, promote_shape using Core: @doc if Base !== Core.Compiler using .Base: cld, fld, SubArray, view, resize!, IndexCartesian +using .Base.Checked: checked_mul +else + # Checked.checked_mul is not available during bootstrapping: + const checked_mul = * end import .Base: @@ -1055,7 +1059,7 @@ _prod_axes1(a, A) = throw(ArgumentError("Cannot compute indices for object of type $(typeof(a))")) ndims(p::ProductIterator) = length(axes(p)) -length(P::ProductIterator) = prod(size(P)) +length(P::ProductIterator) = reduce(checked_mul, size(P); init=1) IteratorEltype(::Type{ProductIterator{Tuple{}}}) = HasEltype() IteratorEltype(::Type{ProductIterator{Tuple{I}}}) where {I} = IteratorEltype(I) diff --git a/test/iterators.jl b/test/iterators.jl index 9132c7b8a23a84..baf0095dcc43ed 100644 --- a/test/iterators.jl +++ b/test/iterators.jl @@ -336,21 +336,25 @@ let a = 1:2, c = Int32(1):Int32(0) # length + @test length(product()) == 1 @test length(product(a)) == 2 @test length(product(a, b)) == 20 @test length(product(a, b, c)) == 0 # size + @test size(product()) == tuple() @test size(product(a)) == (2,) @test size(product(a, b)) == (2, 10) @test size(product(a, b, c)) == (2, 10, 0) # eltype + @test eltype(product()) == Tuple{} @test eltype(product(a)) == Tuple{Int} @test eltype(product(a, b)) == Tuple{Int, Float64} @test eltype(product(a, b, c)) == Tuple{Int, Float64, Int32} # ndims + @test ndims(product()) == 0 @test ndims(product(a)) == 1 @test ndims(product(a, b)) == 2 @test ndims(product(a, b, c)) == 3 @@ -425,6 +429,8 @@ let a = 1:2, @test_throws ArgumentError size(product(itr)) @test_throws ArgumentError ndims(product(itr)) end + + @test_throws OverflowError length(product(1:typemax(Int), 1:typemax(Int))) end # IteratorSize trait business From a9e6b5adb50ec66db536dbc3bd88a0a20740e831 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Thu, 1 Dec 2022 18:48:24 +0900 Subject: [PATCH 057/387] inlining: follow up #47371, remove unused `override_effects` keyword arg (#47762) This keyword argument was introduced by #47305, which was then reverted by #47371. Now it's dead, so let's remove it. --- base/compiler/ssair/inlining.jl | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 0c946a7348a804..fab5f0a390743e 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -839,8 +839,7 @@ end # the general resolver for usual and const-prop'ed calls function resolve_todo(mi::MethodInstance, result::Union{MethodMatch,InferenceResult}, argtypes::Vector{Any}, @nospecialize(info::CallInfo), flag::UInt8, - state::InliningState; invokesig::Union{Nothing,Vector{Any}}=nothing, - override_effects::Effects = EFFECTS_UNKNOWN′) + state::InliningState; invokesig::Union{Nothing,Vector{Any}}=nothing) et = InliningEdgeTracker(state.et, invokesig) #XXX: update_valid_age!(min_valid[1], max_valid[1], sv) @@ -861,10 +860,6 @@ function resolve_todo(mi::MethodInstance, result::Union{MethodMatch,InferenceRes (; src, effects) = cached_result end - if override_effects !== EFFECTS_UNKNOWN′ - effects = override_effects - end - # the duplicated check might have been done already within `analyze_method!`, but still # we need it here too since we may come here directly using a constant-prop' result if !state.params.inlining || is_stmt_noinline(flag) @@ -942,8 +937,7 @@ can_inline_typevars(m::MethodMatch, argtypes::Vector{Any}) = can_inline_typevars function analyze_method!(match::MethodMatch, argtypes::Vector{Any}, @nospecialize(info::CallInfo), flag::UInt8, state::InliningState; - allow_typevars::Bool, invokesig::Union{Nothing,Vector{Any}}=nothing, - override_effects::Effects=EFFECTS_UNKNOWN′) + allow_typevars::Bool, invokesig::Union{Nothing,Vector{Any}}=nothing) method = match.method spec_types = match.spec_types @@ -973,13 +967,12 @@ function analyze_method!(match::MethodMatch, argtypes::Vector{Any}, mi = specialize_method(match; preexisting=true) # Union{Nothing, MethodInstance} if mi === nothing et = InliningEdgeTracker(state.et, invokesig) - effects = override_effects - effects === EFFECTS_UNKNOWN′ && (effects = info_effects(nothing, match, state)) + effects = info_effects(nothing, match, state) return compileable_specialization(match, effects, et, info; compilesig_invokes=state.params.compilesig_invokes) end - return resolve_todo(mi, match, argtypes, info, flag, state; invokesig, override_effects) + return resolve_todo(mi, match, argtypes, info, flag, state; invokesig) end function retrieve_ir_for_inlining(mi::MethodInstance, src::Array{UInt8, 1}) @@ -1178,7 +1171,6 @@ function handle_invoke_call!(todo::Vector{Pair{Int,Any}}, end result = info.result invokesig = sig.argtypes - override_effects = EFFECTS_UNKNOWN′ if isa(result, ConcreteResult) item = concrete_result_item(result, state, info; invokesig) else @@ -1187,12 +1179,12 @@ function handle_invoke_call!(todo::Vector{Pair{Int,Any}}, mi = result.result.linfo validate_sparams(mi.sparam_vals) || return nothing if argtypes_to_type(argtypes) <: mi.def.sig - item = resolve_todo(mi, result.result, argtypes, info, flag, state; invokesig, override_effects) + item = resolve_todo(mi, result.result, argtypes, info, flag, state; invokesig) handle_single_case!(todo, ir, idx, stmt, item, state.params, true) return nothing end end - item = analyze_method!(match, argtypes, info, flag, state; allow_typevars=false, invokesig, override_effects) + item = analyze_method!(match, argtypes, info, flag, state; allow_typevars=false, invokesig) end handle_single_case!(todo, ir, idx, stmt, item, state.params, true) return nothing @@ -1305,7 +1297,6 @@ function handle_any_const_result!(cases::Vector{InliningCase}, @nospecialize(result), match::MethodMatch, argtypes::Vector{Any}, @nospecialize(info::CallInfo), flag::UInt8, state::InliningState; allow_abstract::Bool, allow_typevars::Bool) - override_effects = EFFECTS_UNKNOWN′ if isa(result, ConcreteResult) return handle_concrete_result!(cases, result, state, info) end @@ -1319,7 +1310,7 @@ function handle_any_const_result!(cases::Vector{InliningCase}, return handle_const_prop_result!(cases, result, argtypes, info, flag, state; allow_abstract, allow_typevars) else @assert result === nothing - return handle_match!(cases, match, argtypes, info, flag, state; allow_abstract, allow_typevars, override_effects) + return handle_match!(cases, match, argtypes, info, flag, state; allow_abstract, allow_typevars) end end @@ -1450,14 +1441,14 @@ end function handle_match!(cases::Vector{InliningCase}, match::MethodMatch, argtypes::Vector{Any}, @nospecialize(info::CallInfo), flag::UInt8, state::InliningState; - allow_abstract::Bool, allow_typevars::Bool, override_effects::Effects) + allow_abstract::Bool, allow_typevars::Bool) spec_types = match.spec_types allow_abstract || isdispatchtuple(spec_types) || return false # We may see duplicated dispatch signatures here when a signature gets widened # during abstract interpretation: for the purpose of inlining, we can just skip # processing this dispatch candidate (unless unmatched type parameters are present) !allow_typevars && _any(case->case.sig === spec_types, cases) && return true - item = analyze_method!(match, argtypes, info, flag, state; allow_typevars, override_effects) + item = analyze_method!(match, argtypes, info, flag, state; allow_typevars) item === nothing && return false push!(cases, InliningCase(spec_types, item)) return true From 8a75bcbddbc624a91dc1f2a107e81c4f0aed4353 Mon Sep 17 00:00:00 2001 From: Oscar Smith Date: Thu, 1 Dec 2022 10:06:56 -0500 Subject: [PATCH 058/387] faster `BigFloat(::Float64)` (#47546) * faster `BigFloat(::Float64)` This is roughly 2x faster for precision<10000. It's a little slower for precisions >10^6 (about 20%) but IMO this doesn't really matter since for high precision things like multiplication will be way slower than construction. --- base/mpfr.jl | 44 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/base/mpfr.jl b/base/mpfr.jl index 119b0dd67b79fb..047f682e769016 100644 --- a/base/mpfr.jl +++ b/base/mpfr.jl @@ -209,12 +209,46 @@ for (fJ, fC) in ((:si,:Clong), (:ui,:Culong)) end function BigFloat(x::Float64, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[]) - z = BigFloat(;precision=precision) - ccall((:mpfr_set_d, :libmpfr), Int32, (Ref{BigFloat}, Float64, MPFRRoundingMode), z, x, r) - if isnan(x) && signbit(x) != signbit(z) - z.sign = -z.sign + z = BigFloat(;precision) + # punt on the hard case where we might have to deal with rounding + # we could use this path in all cases, but mpfr_set_d has a lot of overhead. + if precision <= Base.significand_bits(Float64) + ccall((:mpfr_set_d, :libmpfr), Int32, (Ref{BigFloat}, Float64, MPFRRoundingMode), z, x, r) + if isnan(x) && signbit(x) != signbit(z) + z.sign = -z.sign + end + return z end - return z + z.sign = 1-2*signbit(x) + if iszero(x) || !isfinite(x) + if isinf(x) + z.exp = Clong(2) - typemax(Clong) + elseif isnan(x) + z.exp = Clong(1) - typemax(Clong) + else + z.exp = - typemax(Clong) + end + return z + end + z.exp = 1 + exponent(x) + # BigFloat doesn't have an implicit bit + val = reinterpret(UInt64, significand(x))<<11 | typemin(Int64) + nlimbs = (precision + 8*Core.sizeof(Limb) - 1) ÷ (8*Core.sizeof(Limb)) + + # Limb is a CLong which is a UInt32 on windows (thank M$) which makes this more complicated and slower. + if Limb === UInt64 + for i in 1:nlimbs-1 + unsafe_store!(z.d, 0x0, i) + end + unsafe_store!(z.d, val, nlimbs) + else + for i in 1:nlimbs-2 + unsafe_store!(z.d, 0x0, i) + end + unsafe_store!(z.d, val % UInt32, nlimbs-1) + unsafe_store!(z.d, (val >> 32) % UInt32, nlimbs) + end + z end function BigFloat(x::BigInt, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[]) From 293ab472a3d18a4726a77963dc5729aff416c936 Mon Sep 17 00:00:00 2001 From: Dilum Aluthge Date: Thu, 1 Dec 2022 11:48:27 -0500 Subject: [PATCH 059/387] Update the libuv checksums (#47763) --- deps/checksums/libuv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deps/checksums/libuv b/deps/checksums/libuv index 81bc2178963d34..709fba71f159be 100644 --- a/deps/checksums/libuv +++ b/deps/checksums/libuv @@ -30,5 +30,5 @@ LibUV.v2.0.1+13.x86_64-unknown-freebsd.tar.gz/md5/71f7d9d9234a0623c4b2ee3a44089b LibUV.v2.0.1+13.x86_64-unknown-freebsd.tar.gz/sha512/e73911c3ec35a2201d42c035ecc86e8bd860604b950cb1b7784ff49e27ef5ac9b1da09b59d359ff25b093b87593a8305105bc43711c12eb9654972e280c26d3c LibUV.v2.0.1+13.x86_64-w64-mingw32.tar.gz/md5/471d20fa2eac6bfd5d7cdb1b7f58c602 LibUV.v2.0.1+13.x86_64-w64-mingw32.tar.gz/sha512/3f5ad55268184227378ddcfed0146bf0386c8cf468bc53a348d21195d818db4db768be61fd23e1ee2ecbb52f073815884a04a923d815b9b5992825d144c0633a -libuv-e6f0e4900e195c8352f821abe2b3cffc3089547b.tar.gz/md5/c4465d7bff6610761cf37a1e8e3da08c -libuv-e6f0e4900e195c8352f821abe2b3cffc3089547b.tar.gz/sha512/3347668b2b377704f3188e8901b130e891d19ac944ab3b7c1f4939d7afa119afff7dc10feaa2a518ec4122968147e31eb8932c6dfc1142a58a4828488f343191 +libuv-2723e256e952be0b015b3c0086f717c3d365d97e.tar.gz/md5/d2284d7f6fa75d6a35673d22e1be058b +libuv-2723e256e952be0b015b3c0086f717c3d365d97e.tar.gz/sha512/68d6ab740945b9ce3475118ce3d186fb67d7e8125784cc0c827df23d63f50c40c0261ef37365d8c11ab9462a8dd4e2e6b19e91e3c84b64d8fb84fd3894afc4ac From cc25a1369472756c63c4da81abbc106e2790b4f0 Mon Sep 17 00:00:00 2001 From: Simeon Schaub Date: Thu, 1 Dec 2022 21:42:20 +0100 Subject: [PATCH 060/387] fix unescaping in `global` expressions (#47719) This fixes some issues around macro hygiene in `global` expressions. Apparently we always treat l-values in global expressions as being escaped, but we still need to be careful to handle type annotations and destructuring correctly. --- src/macroexpand.scm | 29 +++++++++++++++++++++-------- test/syntax.jl | 23 +++++++++++++++++++++++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/macroexpand.scm b/src/macroexpand.scm index 516dd9b29f354f..2933ca4888c4eb 100644 --- a/src/macroexpand.scm +++ b/src/macroexpand.scm @@ -183,6 +183,19 @@ (cadr e) e)) +(define (unescape-global-lhs e env m parent-scope inarg) + (cond ((not (pair? e)) e) + ((eq? (car e) 'escape) (cadr e)) + ((memq (car e) '(parameters tuple)) + (list* (car e) (map (lambda (e) + (unescape-global-lhs e env m parent-scope inarg)) + (cdr e)))) + ((and (memq (car e) '(|::| kw)) (length= e 3)) + (list (car e) (unescape-global-lhs (cadr e) env m parent-scope inarg) + (resolve-expansion-vars-with-new-env (caddr e) env m parent-scope inarg))) + (else + (resolve-expansion-vars-with-new-env e env m parent-scope inarg)))) + (define (typedef-expr-name e) (cond ((atom? e) e) ((or (eq? (car e) 'curly) (eq? (car e) '<:)) (typedef-expr-name (cadr e))) @@ -344,14 +357,14 @@ (m (cadr scope)) (parent-scope (cdr parent-scope))) (resolve-expansion-vars-with-new-env (cadr e) env m parent-scope inarg)))) - ((global) (let ((arg (cadr e))) - (cond ((symbol? arg) e) - ((assignment? arg) - `(global - (= ,(unescape (cadr arg)) - ,(resolve-expansion-vars-with-new-env (caddr arg) env m parent-scope inarg)))) - (else - `(global ,(resolve-expansion-vars-with-new-env arg env m parent-scope inarg)))))) + ((global) + `(global + ,@(map (lambda (arg) + (if (assignment? arg) + `(= ,(unescape-global-lhs (cadr arg) env m parent-scope inarg) + ,(resolve-expansion-vars-with-new-env (caddr arg) env m parent-scope inarg)) + (unescape-global-lhs arg env m parent-scope inarg))) + (cdr e)))) ((using import export meta line inbounds boundscheck loopinfo inline noinline) (map unescape e)) ((macrocall) e) ; invalid syntax anyways, so just act like it's quoted. ((symboliclabel) e) diff --git a/test/syntax.jl b/test/syntax.jl index cff8628290081a..fe9f6c43332e50 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -3429,3 +3429,26 @@ end elseif false || (()->true)() 42 end)) == 42 + +macro _macroexpand(x, m=__module__) + :($__source__; macroexpand($m, Expr(:var"hygienic-scope", $(esc(Expr(:quote, x))), $m))) +end + +@testset "unescaping in :global expressions" begin + m = @__MODULE__ + @test @_macroexpand(global x::T) == :(global x::$(GlobalRef(m, :T))) + @test @_macroexpand(global (x, $(esc(:y)))) == :(global (x, y)) + @test @_macroexpand(global (x::S, $(esc(:y))::$(esc(:T)))) == + :(global (x::$(GlobalRef(m, :S)), y::T)) + @test @_macroexpand(global (; x, $(esc(:y)))) == :(global (; x, y)) + @test @_macroexpand(global (; x::S, $(esc(:y))::$(esc(:T)))) == + :(global (; x::$(GlobalRef(m, :S)), y::T)) + + @test @_macroexpand(global x::T = a) == :(global x::$(GlobalRef(m, :T)) = $(GlobalRef(m, :a))) + @test @_macroexpand(global (x, $(esc(:y))) = a) == :(global (x, y) = $(GlobalRef(m, :a))) + @test @_macroexpand(global (x::S, $(esc(:y))::$(esc(:T))) = a) == + :(global (x::$(GlobalRef(m, :S)), y::T) = $(GlobalRef(m, :a))) + @test @_macroexpand(global (; x, $(esc(:y))) = a) == :(global (; x, y) = $(GlobalRef(m, :a))) + @test @_macroexpand(global (; x::S, $(esc(:y))::$(esc(:T))) = a) == + :(global (; x::$(GlobalRef(m, :S)), y::T) = $(GlobalRef(m, :a))) +end From b572839ac134f9d5a7b96456abceb15bc2375b5c Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Fri, 2 Dec 2022 16:07:46 +0900 Subject: [PATCH 061/387] minor perf optimization on `abstract_call` (#47765) --- base/compiler/abstractinterpretation.jl | 29 +++++++++++++++---------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index d95692599f5f00..03f81309446aa3 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1959,7 +1959,7 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), return CallMeta(rt, effects, NoCallInfo()) elseif isa(f, Core.OpaqueClosure) # calling an OpaqueClosure about which we have no information returns no information - return CallMeta(Any, Effects(), NoCallInfo()) + return CallMeta(typeof(f).parameters[2], Effects(), NoCallInfo()) elseif f === TypeVar # Manually look through the definition of TypeVar to # make sure to be able to get `PartialTypeVar`s out. @@ -2085,20 +2085,25 @@ function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, si::StmtIn argtypes = arginfo.argtypes ft = widenslotwrapper(argtypes[1]) f = singleton_type(ft) - if isa(ft, PartialOpaque) - newargtypes = copy(argtypes) - newargtypes[1] = ft.env - return abstract_call_opaque_closure(interp, - ft, ArgInfo(arginfo.fargs, newargtypes), si, sv, #=check=#true) - elseif (uft = unwrap_unionall(widenconst(ft)); isa(uft, DataType) && uft.name === typename(Core.OpaqueClosure)) - return CallMeta(rewrap_unionall((uft::DataType).parameters[2], widenconst(ft)), Effects(), NoCallInfo()) - elseif f === nothing - # non-constant function, but the number of arguments is known - # and the ft is not a Builtin or IntrinsicFunction - if hasintersect(widenconst(ft), Union{Builtin, Core.OpaqueClosure}) + if f === nothing + if isa(ft, PartialOpaque) + newargtypes = copy(argtypes) + newargtypes[1] = ft.env + return abstract_call_opaque_closure(interp, + ft, ArgInfo(arginfo.fargs, newargtypes), si, sv, #=check=#true) + end + wft = widenconst(ft) + if hasintersect(wft, Builtin) add_remark!(interp, sv, "Could not identify method table for call") return CallMeta(Any, Effects(), NoCallInfo()) + elseif hasintersect(wft, Core.OpaqueClosure) + uft = unwrap_unionall(wft) + if isa(uft, DataType) + return CallMeta(rewrap_unionall(uft.parameters[2], wft), Effects(), NoCallInfo()) + end + return CallMeta(Any, Effects(), NoCallInfo()) end + # non-constant function, but the number of arguments is known and the `f` is not a builtin or intrinsic max_methods = max_methods === nothing ? get_max_methods(sv.mod, interp) : max_methods return abstract_call_gf_by_type(interp, nothing, arginfo, si, argtypes_to_type(argtypes), sv, max_methods) end From 89671aed1c33ba482faa04c72c1e1f9cfd4c127a Mon Sep 17 00:00:00 2001 From: Philip Swannell <18028484+PGS62@users.noreply.github.com> Date: Fri, 2 Dec 2022 07:39:10 +0000 Subject: [PATCH 062/387] doc: fix a typo in the FAQ (#47767) --- doc/src/manual/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/manual/faq.md b/doc/src/manual/faq.md index ef3e77b14f1db4..741843bca33e50 100644 --- a/doc/src/manual/faq.md +++ b/doc/src/manual/faq.md @@ -1049,7 +1049,7 @@ The Stable version of Julia is the latest released version of Julia, this is the It has the latest features, including improved performance. The Stable version of Julia is versioned according to [SemVer](https://semver.org/) as v1.x.y. A new minor release of Julia corresponding to a new Stable version is made approximately every 4-5 months after a few weeks of testing as a release candidate. -Unlike the LTS version the a Stable version will not normally receive bugfixes after another Stable version of Julia has been released. +Unlike the LTS version the Stable version will not normally receive bugfixes after another Stable version of Julia has been released. However, upgrading to the next Stable release will always be possible as each release of Julia v1.x will continue to run code written for earlier versions. You may prefer the LTS (Long Term Support) version of Julia if you are looking for a very stable code base. From 2a0eb7012944a828806a6cfd18d89182c00f16a0 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Fri, 2 Dec 2022 18:03:38 -0500 Subject: [PATCH 063/387] Allow Module as type parameters (#47749) The intended use case for this is generated functions that want to generate some reference to a module-specific generic function. The current solution is to duplicate the generated function into every module (probably using a package-provided macro) or to have some sort of registry system in the package providing the generated function. Both of these seem a bit ugly and I don't think there's any particularly good reason not to allow Modules to be type parameters. Admittedly, modules are not part of the scope contemplated by #33387 as they are mutable, but I think the mental model of modules is that they're immutable references to a namespace and what's actually mutable is the namespace itself (i.e. people wouldn't expect two modules that happen to have the same content be `==`). This makes me think it still fits the mental model. --- base/compiler/typeutils.jl | 4 ++-- src/builtins.c | 14 ++++++++++---- src/init.c | 2 +- src/julia.h | 3 ++- src/module.c | 15 ++++++++------- src/toplevel.c | 8 ++++---- test/core.jl | 13 +++++++++++++ test/precompile.jl | 17 +++++++++++++++++ 8 files changed, 57 insertions(+), 19 deletions(-) diff --git a/base/compiler/typeutils.jl b/base/compiler/typeutils.jl index 8207153e822b03..15ee6fc4bd625b 100644 --- a/base/compiler/typeutils.jl +++ b/base/compiler/typeutils.jl @@ -132,12 +132,12 @@ function valid_as_lattice(@nospecialize(x)) end function valid_typeof_tparam(@nospecialize(t)) - if t === Symbol || isbitstype(t) + if t === Symbol || t === Module || isbitstype(t) return true end isconcretetype(t) || return false if t <: NamedTuple - t = t.parameters[2] + t = t.parameters[2]::DataType end if t <: Tuple for p in t.parameters diff --git a/src/builtins.c b/src/builtins.c index 90cc544b47986e..824f0112d6acb5 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -198,7 +198,7 @@ static int egal_types(const jl_value_t *a, const jl_value_t *b, jl_typeenv_t *en return egal_types(vma->N, vmb->N, env, tvar_names); return !vma->N && !vmb->N; } - if (dt == jl_symbol_type) + if (dt == jl_symbol_type || dt == jl_module_type) return 0; assert(!dt->name->mutabl); return jl_egal__bits(a, b, dt); @@ -414,6 +414,10 @@ static uintptr_t NOINLINE jl_object_id__cold(jl_datatype_t *dt, jl_value_t *v) J return memhash32_seed(jl_string_data(v), jl_string_len(v), 0xedc3b677); #endif } + if (dt == jl_module_type) { + jl_module_t *m = (jl_module_t*)v; + return m->hash; + } if (dt->name->mutabl) return inthash((uintptr_t)v); return immut_id_(dt, v, dt->hash); @@ -1269,7 +1273,8 @@ static int is_nestable_type_param(jl_value_t *t) size_t i, l = jl_nparams(t); for (i = 0; i < l; i++) { jl_value_t *pi = jl_tparam(t, i); - if (!(pi == (jl_value_t*)jl_symbol_type || jl_isbits(pi) || is_nestable_type_param(pi))) + if (!(pi == (jl_value_t*)jl_symbol_type || jl_isbits(pi) || is_nestable_type_param(pi) || + jl_is_module(pi))) return 0; } return 1; @@ -1284,7 +1289,8 @@ int jl_valid_type_param(jl_value_t *v) if (jl_is_vararg(v)) return 0; // TODO: maybe more things - return jl_is_type(v) || jl_is_typevar(v) || jl_is_symbol(v) || jl_isbits(jl_typeof(v)); + return jl_is_type(v) || jl_is_typevar(v) || jl_is_symbol(v) || jl_isbits(jl_typeof(v)) || + jl_is_module(v); } JL_CALLABLE(jl_f_apply_type) @@ -1896,7 +1902,7 @@ void jl_init_intrinsic_properties(void) JL_GC_DISABLED void jl_init_intrinsic_functions(void) JL_GC_DISABLED { - jl_module_t *inm = jl_new_module(jl_symbol("Intrinsics")); + jl_module_t *inm = jl_new_module(jl_symbol("Intrinsics"), NULL); inm->parent = jl_core_module; jl_set_const(jl_core_module, jl_symbol("Intrinsics"), (jl_value_t*)inm); jl_mk_builtin_func(jl_intrinsic_type, "IntrinsicFunction", jl_f_intrinsic_call); diff --git a/src/init.c b/src/init.c index 89f4153ff15384..feee29d02ea700 100644 --- a/src/init.c +++ b/src/init.c @@ -821,7 +821,7 @@ static NOINLINE void _finish_julia_init(JL_IMAGE_SEARCH rel, jl_ptls_t ptls, jl_ jl_init_serializer(); if (!jl_options.image_file) { - jl_core_module = jl_new_module(jl_symbol("Core")); + jl_core_module = jl_new_module(jl_symbol("Core"), NULL); jl_core_module->parent = jl_core_module; jl_type_typename->mt->module = jl_core_module; jl_top_module = jl_core_module; diff --git a/src/julia.h b/src/julia.h index 769a2923e20fe4..c79f7d0d1303a7 100644 --- a/src/julia.h +++ b/src/julia.h @@ -603,6 +603,7 @@ typedef struct _jl_module_t { uint8_t istopmod; int8_t max_methods; jl_mutex_t lock; + intptr_t hash; } jl_module_t; typedef struct { @@ -1615,7 +1616,7 @@ extern JL_DLLEXPORT jl_module_t *jl_main_module JL_GLOBALLY_ROOTED; extern JL_DLLEXPORT jl_module_t *jl_core_module JL_GLOBALLY_ROOTED; extern JL_DLLEXPORT jl_module_t *jl_base_module JL_GLOBALLY_ROOTED; extern JL_DLLEXPORT jl_module_t *jl_top_module JL_GLOBALLY_ROOTED; -JL_DLLEXPORT jl_module_t *jl_new_module(jl_sym_t *name); +JL_DLLEXPORT jl_module_t *jl_new_module(jl_sym_t *name, jl_module_t *parent); JL_DLLEXPORT void jl_set_module_nospecialize(jl_module_t *self, int on); JL_DLLEXPORT void jl_set_module_optlevel(jl_module_t *self, int lvl); JL_DLLEXPORT int jl_get_module_optlevel(jl_module_t *m); diff --git a/src/module.c b/src/module.c index 605bcd3c2b7737..d507dc69ff7b2d 100644 --- a/src/module.c +++ b/src/module.c @@ -11,7 +11,7 @@ extern "C" { #endif -JL_DLLEXPORT jl_module_t *jl_new_module_(jl_sym_t *name, uint8_t default_names) +JL_DLLEXPORT jl_module_t *jl_new_module_(jl_sym_t *name, jl_module_t *parent, uint8_t default_names) { jl_task_t *ct = jl_current_task; const jl_uuid_t uuid_zero = {0, 0}; @@ -19,7 +19,7 @@ JL_DLLEXPORT jl_module_t *jl_new_module_(jl_sym_t *name, uint8_t default_names) jl_module_type); assert(jl_is_symbol(name)); m->name = name; - m->parent = NULL; + m->parent = parent; m->istopmod = 0; m->uuid = uuid_zero; static unsigned int mcounter; // simple counter backup, in case hrtime is not incrementing @@ -34,6 +34,8 @@ JL_DLLEXPORT jl_module_t *jl_new_module_(jl_sym_t *name, uint8_t default_names) m->compile = -1; m->infer = -1; m->max_methods = -1; + m->hash = parent == NULL ? bitmix(name->hash, jl_module_type->hash) : + bitmix(name->hash, parent->hash); JL_MUTEX_INIT(&m->lock); htable_new(&m->bindings, 0); arraylist_new(&m->usings, 0); @@ -50,9 +52,9 @@ JL_DLLEXPORT jl_module_t *jl_new_module_(jl_sym_t *name, uint8_t default_names) return m; } -JL_DLLEXPORT jl_module_t *jl_new_module(jl_sym_t *name) +JL_DLLEXPORT jl_module_t *jl_new_module(jl_sym_t *name, jl_module_t *parent) { - return jl_new_module_(name, 1); + return jl_new_module_(name, parent, 1); } uint32_t jl_module_next_counter(jl_module_t *m) @@ -63,10 +65,9 @@ uint32_t jl_module_next_counter(jl_module_t *m) JL_DLLEXPORT jl_value_t *jl_f_new_module(jl_sym_t *name, uint8_t std_imports, uint8_t default_names) { // TODO: should we prohibit this during incremental compilation? - jl_module_t *m = jl_new_module_(name, default_names); + // TODO: the parent module is a lie + jl_module_t *m = jl_new_module_(name, jl_main_module, default_names); JL_GC_PUSH1(&m); - m->parent = jl_main_module; // TODO: this is a lie - jl_gc_wb(m, m->parent); if (std_imports) jl_add_standard_imports(m); JL_GC_POP(); diff --git a/src/toplevel.c b/src/toplevel.c index 84c3b77ade7f54..baacb4235a838c 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -48,7 +48,7 @@ JL_DLLEXPORT void jl_add_standard_imports(jl_module_t *m) void jl_init_main_module(void) { assert(jl_main_module == NULL); - jl_main_module = jl_new_module(jl_symbol("Main")); + jl_main_module = jl_new_module(jl_symbol("Main"), NULL); jl_main_module->parent = jl_main_module; jl_set_const(jl_main_module, jl_symbol("Core"), (jl_value_t*)jl_core_module); @@ -134,7 +134,8 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex jl_type_error("module", (jl_value_t*)jl_symbol_type, (jl_value_t*)name); } - jl_module_t *newm = jl_new_module(name); + int is_parent__toplevel__ = jl_is__toplevel__mod(parent_module); + jl_module_t *newm = jl_new_module(name, is_parent__toplevel__ ? NULL : parent_module); jl_value_t *form = (jl_value_t*)newm; JL_GC_PUSH1(&form); JL_LOCK(&jl_modules_mutex); @@ -145,7 +146,7 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex // copy parent environment info into submodule newm->uuid = parent_module->uuid; - if (jl_is__toplevel__mod(parent_module)) { + if (is_parent__toplevel__) { newm->parent = newm; jl_register_root_module(newm); if (jl_options.incremental) { @@ -153,7 +154,6 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex } } else { - newm->parent = parent_module; jl_binding_t *b = jl_get_binding_wr(parent_module, name, 1); jl_declare_constant(b); jl_value_t *old = NULL; diff --git a/test/core.jl b/test/core.jl index ec292262db6a0a..4baf78ed2977c3 100644 --- a/test/core.jl +++ b/test/core.jl @@ -7873,3 +7873,16 @@ let # https://github.com/JuliaLang/julia/issues/46918 @test isempty(String(take!(stderr))) # make sure no error has happened @test String(take!(stdout)) == "nothing IO IO" end + +# Modules allowed as type parameters and usable in generated functions +module ModTparamTest + foo_test_mod_tparam() = 1 +end +foo_test_mod_tparam() = 2 + +struct ModTparamTestStruct{M}; end +@generated function ModTparamTestStruct{M}() where {M} + return :($(GlobalRef(M, :foo_test_mod_tparam))()) +end +@test ModTparamTestStruct{@__MODULE__}() == 2 +@test ModTparamTestStruct{ModTparamTest}() == 1 diff --git a/test/precompile.jl b/test/precompile.jl index eaf755046d3662..806887646e1372 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -1580,6 +1580,23 @@ end @test which(f46778, Tuple{Any,DataType}).specializations[1].cache.invoke != C_NULL end + +precompile_test_harness("Module tparams") do load_path + write(joinpath(load_path, "ModuleTparams.jl"), + """ + module ModuleTparams + module TheTParam + end + + struct ParamStruct{T}; end + const the_struct = ParamStruct{TheTParam}() + end + """) + Base.compilecache(Base.PkgId("ModuleTparams")) + (@eval (using ModuleTparams)) + @test ModuleTparams.the_struct === Base.invokelatest(ModuleTparams.ParamStruct{ModuleTparams.TheTParam}) +end + empty!(Base.DEPOT_PATH) append!(Base.DEPOT_PATH, original_depot_path) empty!(Base.LOAD_PATH) From cee0a0494c70208b6cd5a32ccdf75d954a429870 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Sat, 3 Dec 2022 06:19:20 +0600 Subject: [PATCH 064/387] Refactor and document sorting dispatch (#47383) * create an internal `_sort!` function and use it (rename the existing `_sort!` to `__sort!`) * test for several of bugs that slipped through test suite * Give each sorting pass and DEFAULT_STABLE a docstring * add pretty printing for the new algorithms that are much more flexible than the old ones * fix unexpected allocations in Radix Sort fixes #47474 in this PR rather than separate to avoid dealing with the merge * support and test backwards compatibility with packages that depend in sorting internals * support 3-, 5-, and 6-argument sort! for backwards compatibility * overhall scratch space handling make _sort! return scratch space rather than sorted vector so that things like IEEEFloatOptimization can re-use the scratch space allocated on their first recursive call * test handling -0.0 in IEEEFloatOptimization * fix and test bug where countsort's correct overflow behavior triggers error due to unexpected promotion to UInt --- base/sort.jl | 1287 ++++++++++++++++++++++++++++++----------------- test/sorting.jl | 171 ++++++- 2 files changed, 968 insertions(+), 490 deletions(-) diff --git a/base/sort.jl b/base/sort.jl index e995a64a9f76f7..932da36b9e1d61 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -86,7 +86,7 @@ issorted(itr; issorted(itr, ord(lt,by,rev,order)) function partialsort!(v::AbstractVector, k::Union{Integer,OrdinalRange}, o::Ordering) - sort!(v, _PartialQuickSort(k), o) + _sort!(v, _PartialQuickSort(k), o, (;)) maybeview(v, k) end @@ -407,112 +407,315 @@ function insorted end insorted(x, v::AbstractVector; kw...) = !isempty(searchsorted(v, x; kw...)) insorted(x, r::AbstractRange) = in(x, r) -## sorting algorithms ## +## Alternative keyword management -abstract type Algorithm end +macro getkw(syms...) + getters = (getproperty(Sort, Symbol(:_, sym)) for sym in syms) + Expr(:block, (:($(esc(:((kw, $sym) = $getter(v, o, kw))))) for (sym, getter) in zip(syms, getters))...) +end -struct InsertionSortAlg <: Algorithm end -struct MergeSortAlg <: Algorithm end -struct AdaptiveSortAlg <: Algorithm end +for (sym, deps, exp, type) in [ + (:lo, (), :(firstindex(v)), Integer), + (:hi, (), :(lastindex(v)), Integer), + (:mn, (), :(throw(ArgumentError("mn is needed but has not been computed"))), :(eltype(v))), + (:mx, (), :(throw(ArgumentError("mx is needed but has not been computed"))), :(eltype(v))), + (:scratch, (), nothing, :(Union{Nothing, Vector})), # could have different eltype + (:allow_legacy_dispatch, (), true, Bool)] + usym = Symbol(:_, sym) + @eval function $usym(v, o, kw) + # using missing instead of nothing because scratch could === nothing. + res = get(kw, $(Expr(:quote, sym)), missing) + res !== missing && return kw, res::$type + @getkw $(deps...) + $sym = $exp + (;kw..., $sym), $sym::$type + end +end + +## Scratch space management """ - PartialQuickSort(lo::Union{Integer, Missing}, hi::Union{Integer, Missing}) + make_scratch(scratch::Union{Nothing, Vector}, T::Type, len::Integer) -Indicate that a sorting function should use the partial quick sort algorithm. +Returns `(s, t)` where `t` is an `AbstractVector` of type `T` with length at least `len` +that is backed by the `Vector` `s`. If `scratch !== nothing`, then `s === scratch`. -Partial quick sort finds and sorts the elements that would end up in positions -`lo:hi` using [`QuickSort`](@ref). +This function will allocate a new vector if `scratch === nothing`, `resize!` `scratch` if it +is too short, and `reinterpret` `scratch` if its eltype is not `T`. +""" +function make_scratch(scratch::Nothing, T::Type, len::Integer) + s = Vector{T}(undef, len) + s, s +end +function make_scratch(scratch::Vector{T}, ::Type{T}, len::Integer) where T + len > length(scratch) && resize!(scratch, len) + scratch, scratch +end +function make_scratch(scratch::Vector, T::Type, len::Integer) + len_bytes = len * sizeof(T) + len_scratch = div(len_bytes, sizeof(eltype(scratch))) + len_scratch > length(scratch) && resize!(scratch, len_scratch) + scratch, reinterpret(T, scratch) +end + + +## sorting algorithm components ## -Characteristics: - * *stable*: preserves the ordering of elements which compare equal - (e.g. "a" and "A" in a sort of letters which ignores case). - * *not in-place* in memory. - * *divide-and-conquer*: sort strategy similar to [`MergeSort`](@ref). """ -struct PartialQuickSort{L<:Union{Integer,Missing}, H<:Union{Integer,Missing}} <: Algorithm - lo::L - hi::H + _sort!(v::AbstractVector, a::Algorithm, o::Ordering, kw; t, offset) + +An internal function that sorts `v` using the algorithm `a` under the ordering `o`, +subject to specifications provided in `kw` (such as `lo` and `hi` in which case it only +sorts `view(v, lo:hi)`) + +Returns a scratch space if provided or constructed during the sort, or `nothing` if +no scratch space is present. + +!!! note + `_sort!` modifies but does not return `v`. + +A returned scratch space will be a `Vector{T}` where `T` is usually the eltype of `v`. There +are some exceptions, for example if `eltype(v) == Union{Missing, T}` then the scratch space +may be be a `Vector{T}` due to `MissingOptimization` changing the eltype of `v` to `T`. + +`t` is an appropriate scratch space for the algorithm at hand, to be accessed as +`t[i + offset]`. `t` is used for an algorithm to pass a scratch space back to itself in +internal or recursive calls. +""" +function _sort! end + +abstract type Algorithm end + + +""" + MissingOptimization(next) <: Algorithm + +Filter out missing values. + +Missing values are placed after other values according to `DirectOrdering`s. This pass puts +them there and passes on a view into the original vector that excludes the missing values. +This pass is triggered for both `sort([1, missing, 3])` and `sortperm([1, missing, 3])`. +""" +struct MissingOptimization{T <: Algorithm} <: Algorithm + next::T end -PartialQuickSort(k::Integer) = PartialQuickSort(missing, k) -PartialQuickSort(k::OrdinalRange) = PartialQuickSort(first(k), last(k)) -_PartialQuickSort(k::Integer) = PartialQuickSort(k, k) -_PartialQuickSort(k::OrdinalRange) = PartialQuickSort(k) + +struct WithoutMissingVector{T, U} <: AbstractVector{T} + data::U + function WithoutMissingVector(data; unsafe=false) + if !unsafe && any(ismissing, data) + throw(ArgumentError("data must not contain missing values")) + end + new{nonmissingtype(eltype(data)), typeof(data)}(data) + end +end +Base.@propagate_inbounds function Base.getindex(v::WithoutMissingVector, i::Integer) + out = v.data[i] + @assert !(out isa Missing) + out::eltype(v) +end +Base.@propagate_inbounds function Base.setindex!(v::WithoutMissingVector{T}, x::T, i) where T + v.data[i] = x + v +end +Base.size(v::WithoutMissingVector) = size(v.data) """ - InsertionSort + send_to_end!(f::Function, v::AbstractVector; [lo, hi]) -Indicate that a sorting function should use the insertion sort algorithm. +Send every element of `v` for which `f` returns `true` to the end of the vector and return +the index of the last element which for which `f` returns `false`. -Insertion sort traverses the collection one element at a time, inserting -each element into its correct, sorted position in the output vector. +`send_to_end!(f, v, lo, hi)` is equivalent to `send_to_end!(f, view(v, lo:hi))+lo-1` -Characteristics: - * *stable*: preserves the ordering of elements which - compare equal (e.g. "a" and "A" in a sort of letters - which ignores case). - * *in-place* in memory. - * *quadratic performance* in the number of elements to be sorted: - it is well-suited to small collections but should not be used for large ones. +Preserves the order of the elements that are not sent to the end. """ -const InsertionSort = InsertionSortAlg() +function send_to_end!(f::F, v::AbstractVector; lo=firstindex(v), hi=lastindex(v)) where F <: Function + i = lo + @inbounds while i <= hi && !f(v[i]) + i += 1 + end + j = i + 1 + @inbounds while j <= hi + if !f(v[j]) + v[i], v[j] = v[j], v[i] + i += 1 + end + j += 1 + end + i - 1 +end +""" + send_to_end!(f::Function, v::AbstractVector, o::DirectOrdering[, end_stable]; lo, hi) +Return `(a, b)` where `v[a:b]` are the elements that are not sent to the end. + +If `o isa ReverseOrdering` then the "end" of `v` is `v[lo]`. + +If `end_stable` is set, the elements that are sent to the end are stable instead of the +elements that are not """ - QuickSort +@inline send_to_end!(f::F, v::AbstractVector, ::ForwardOrdering, end_stable=false; lo, hi) where F <: Function = + end_stable ? (lo, hi-send_to_end!(!f, view(v, hi:-1:lo))) : (lo, send_to_end!(f, v; lo, hi)) +@inline send_to_end!(f::F, v::AbstractVector, ::ReverseOrdering, end_stable=false; lo, hi) where F <: Function = + end_stable ? (send_to_end!(!f, v; lo, hi)+1, hi) : (hi-send_to_end!(f, view(v, hi:-1:lo))+1, hi) + + +function _sort!(v::AbstractVector, a::MissingOptimization, o::Ordering, kw) + @getkw lo hi + if nonmissingtype(eltype(v)) != eltype(v) && o isa DirectOrdering + lo, hi = send_to_end!(ismissing, v, o; lo, hi) + _sort!(WithoutMissingVector(v, unsafe=true), a.next, o, (;kw..., lo, hi)) + elseif eltype(v) <: Integer && o isa Perm{DirectOrdering} && nonmissingtype(eltype(o.data)) != eltype(o.data) + lo, hi = send_to_end!(i -> ismissing(@inbounds o.data[i]), v, o) + _sort!(v, a.next, Perm(o.order, WithoutMissingVector(o.data, unsafe=true)), (;kw..., lo, hi)) + else + _sort!(v, a.next, o, kw) + end +end -Indicate that a sorting function should use the quick sort algorithm. -Quick sort picks a pivot element, partitions the array based on the pivot, -and then sorts the elements before and after the pivot recursively. +""" + IEEEFloatOptimization(next) <: Algorithm -Characteristics: - * *stable*: preserves the ordering of elements which compare equal - (e.g. "a" and "A" in a sort of letters which ignores case). - * *not in-place* in memory. - * *divide-and-conquer*: sort strategy similar to [`MergeSort`](@ref). - * *good performance* for almost all large collections. - * *quadratic worst case runtime* in pathological cases - (vanishingly rare for non-malicious input) +Move NaN values to the end, partition by sign, and reinterpret the rest as unsigned integers. + +IEEE floating point numbers (`Float64`, `Float32`, and `Float16`) compare the same as +unsigned integers with the bits with a few exceptions. This pass + +This pass is triggered for both `sort([1.0, NaN, 3.0])` and `sortperm([1.0, NaN, 3.0])`. """ -const QuickSort = PartialQuickSort(missing, missing) -const QuickSortAlg = PartialQuickSort{Missing, Missing} # Exists for backward compatibility +struct IEEEFloatOptimization{T <: Algorithm} <: Algorithm + next::T +end + +UIntType(::Type{Float16}) = UInt16 +UIntType(::Type{Float32}) = UInt32 +UIntType(::Type{Float64}) = UInt64 +after_zero(::ForwardOrdering, x) = !signbit(x) +after_zero(::ReverseOrdering, x) = signbit(x) +is_concrete_IEEEFloat(T::Type) = T <: Base.IEEEFloat && isconcretetype(T) +function _sort!(v::AbstractVector, a::IEEEFloatOptimization, o::Ordering, kw) + @getkw lo hi + if is_concrete_IEEEFloat(eltype(v)) && o isa DirectOrdering + lo, hi = send_to_end!(isnan, v, o, true; lo, hi) + iv = reinterpret(UIntType(eltype(v)), v) + j = send_to_end!(x -> after_zero(o, x), v; lo, hi) + scratch = _sort!(iv, a.next, Reverse, (;kw..., lo, hi=j)) + if scratch === nothing # Union split + _sort!(iv, a.next, Forward, (;kw..., lo=j+1, hi, scratch)) + else + _sort!(iv, a.next, Forward, (;kw..., lo=j+1, hi, scratch)) + end + elseif eltype(v) <: Integer && o isa Perm && o.order isa DirectOrdering && is_concrete_IEEEFloat(eltype(o.data)) + lo, hi = send_to_end!(i -> isnan(@inbounds o.data[i]), v, o.order, true; lo, hi) + ip = reinterpret(UIntType(eltype(o.data)), o.data) + j = send_to_end!(i -> after_zero(o.order, @inbounds o.data[i]), v; lo, hi) + scratch = _sort!(v, a.next, Perm(Reverse, ip), (;kw..., lo, hi=j)) + if scratch === nothing # Union split + _sort!(v, a.next, Perm(Forward, ip), (;kw..., lo=j+1, hi, scratch)) + else + _sort!(v, a.next, Perm(Forward, ip), (;kw..., lo=j+1, hi, scratch)) + end + else + _sort!(v, a.next, o, kw) + end +end + """ - MergeSort + BoolOptimization(next) <: Algorithm -Indicate that a sorting function should use the merge sort algorithm. +Sort `AbstractVector{Bool}`s using a specialized version of counting sort. -Merge sort divides the collection into subcollections and -repeatedly merges them, sorting each subcollection at each step, -until the entire collection has been recombined in sorted form. +Accesses each element at most twice (one read and one write), and performs at most two +comparisons. +""" +struct BoolOptimization{T <: Algorithm} <: Algorithm + next::T +end +_sort!(v::AbstractVector, a::BoolOptimization, o::Ordering, kw) = _sort!(v, a.next, o, kw) +function _sort!(v::AbstractVector{Bool}, ::BoolOptimization, o::Ordering, kw) + first = lt(o, false, true) ? false : lt(o, true, false) ? true : return v + @getkw lo hi scratch + count = 0 + @inbounds for i in lo:hi + if v[i] == first + count += 1 + end + end + @inbounds v[lo:lo+count-1] .= first + @inbounds v[lo+count:hi] .= !first + scratch +end -Characteristics: - * *stable*: preserves the ordering of elements which compare - equal (e.g. "a" and "A" in a sort of letters which ignores - case). - * *not in-place* in memory. - * *divide-and-conquer* sort strategy. + +""" + IsUIntMappable(yes, no) <: Algorithm + +Determines if the elements of a vector can be mapped to unsigned integers while preserving +their order under the specified ordering. + +If they can be, dispatch to the `yes` algorithm and record the unsigned integer type that +the elements may be mapped to. Otherwise dispatch to the `no` algorithm. """ -const MergeSort = MergeSortAlg() +struct IsUIntMappable{T <: Algorithm, U <: Algorithm} <: Algorithm + yes::T + no::U +end +function _sort!(v::AbstractVector, a::IsUIntMappable, o::Ordering, kw) + if UIntMappable(eltype(v), o) !== nothing + _sort!(v, a.yes, o, kw) + else + _sort!(v, a.no, o, kw) + end +end + """ - AdaptiveSort + Small{N}(small=SMALL_ALGORITHM, big) <: Algorithm -Indicate that a sorting function should use the fastest available stable algorithm. +Sort inputs with `length(lo:hi) <= N` using the `small` algorithm. Otherwise use the `big` +algorithm. +""" +struct Small{N, T <: Algorithm, U <: Algorithm} <: Algorithm + small::T + big::U +end +Small{N}(small, big) where N = Small{N, typeof(small), typeof(big)}(small, big) +Small{N}(big) where N = Small{N}(SMALL_ALGORITHM, big) +function _sort!(v::AbstractVector, a::Small{N}, o::Ordering, kw) where N + @getkw lo hi + if (hi-lo) < N + _sort!(v, a.small, o, kw) + else + _sort!(v, a.big, o, kw) + end +end + + +struct InsertionSortAlg <: Algorithm end -Currently, AdaptiveSort uses - * [`InsertionSort`](@ref) for short vectors - * [`QuickSort`](@ref) for vectors that are not [`UIntMappable`](@ref) - * Radix sort for long vectors - * Counting sort for vectors of integers spanning a short range """ -const AdaptiveSort = AdaptiveSortAlg() + InsertionSort -const DEFAULT_UNSTABLE = AdaptiveSort -const DEFAULT_STABLE = AdaptiveSort -const SMALL_ALGORITHM = InsertionSort -const SMALL_THRESHOLD = 20 +Use the insertion sort algorithm. -function sort!(v::AbstractVector, lo::Integer, hi::Integer, ::InsertionSortAlg, o::Ordering) +Insertion sort traverses the collection one element at a time, inserting +each element into its correct, sorted position in the output vector. + +Characteristics: +* *stable*: preserves the ordering of elements which compare equal +(e.g. "a" and "A" in a sort of letters which ignores case). +* *in-place* in memory. +* *quadratic performance* in the number of elements to be sorted: +it is well-suited to small collections but should not be used for large ones. +""" +const InsertionSort = InsertionSortAlg() +const SMALL_ALGORITHM = InsertionSortAlg() + +function _sort!(v::AbstractVector, ::InsertionSortAlg, o::Ordering, kw) + @getkw lo hi scratch lo_plus_1 = (lo + 1)::Integer @inbounds for i = lo_plus_1:hi j = i @@ -527,9 +730,249 @@ function sort!(v::AbstractVector, lo::Integer, hi::Integer, ::InsertionSortAlg, end v[j] = x end - return v + scratch +end + + +""" + CheckSorted(next) <: Algorithm + +Check if the input is already sorted and for large inputs, also check if it is +reverse-sorted. The reverse-sorted check is unstable. +""" +struct CheckSorted{T <: Algorithm} <: Algorithm + next::T +end +function _sort!(v::AbstractVector, a::CheckSorted, o::Ordering, kw) + @getkw lo hi scratch + + # For most arrays, a presorted check is cheap (overhead < 5%) and for most large + # arrays it is essentially free (<1%). + _issorted(v, lo, hi, o) && return scratch + + # For most large arrays, a reverse-sorted check is essentially free (overhead < 1%) + if hi-lo >= 500 && _issorted(v, lo, hi, ReverseOrdering(o)) + # If reversing is valid, do so. This does violates stability. + reverse!(v, lo, hi) + return scratch + end + + _sort!(v, a.next, o, kw) +end + + +""" + ComputeExtrema(next) <: Algorithm + +Compute the extrema of the input under the provided order. + +If the minimum is no less than the maximum, then the input is already sorted. Otherwise, +dispatch to the `next` algorithm. +""" +struct ComputeExtrema{T <: Algorithm} <: Algorithm + next::T +end +function _sort!(v::AbstractVector, a::ComputeExtrema, o::Ordering, kw) + @getkw lo hi scratch + mn = mx = v[lo] + @inbounds for i in (lo+1):hi + vi = v[i] + lt(o, vi, mn) && (mn = vi) + lt(o, mx, vi) && (mx = vi) + end + mn, mx + + lt(o, mn, mx) || return scratch # all same + + _sort!(v, a.next, o, (;kw..., mn, mx)) +end + + +""" + ConsiderCountingSort(counting=CountingSort(), next) <: Algorithm + +If the input's range is small enough, use the `counting` algorithm. Otherwise, dispatch to +the `next` algorithm. + +For most types, the threshold is if the range is shorter than half the length, but for types +larger than Int64, bitshifts are expensive and RadixSort is not viable, so the threshold is +much more generous. +""" +struct ConsiderCountingSort{T <: Algorithm, U <: Algorithm} <: Algorithm + counting::T + next::U +end +ConsiderCountingSort(next) = ConsiderCountingSort(CountingSort(), next) +function _sort!(v::AbstractVector{<:Integer}, a::ConsiderCountingSort, o::DirectOrdering, kw) + @getkw lo hi mn mx + range = maybe_unsigned(o === Reverse ? mn-mx : mx-mn) + + if range < (sizeof(eltype(v)) > 8 ? 5(hi-lo)-100 : div(hi-lo, 2)) + _sort!(v, a.counting, o, kw) + else + _sort!(v, a.next, o, kw) + end +end +_sort!(v::AbstractVector, a::ConsiderCountingSort, o::Ordering, kw) = _sort!(v, a.next, o, kw) + + +""" + CountingSort <: Algorithm + +Use the counting sort algorithm. + +`CountingSort` is an algorithm for sorting integers that runs in Θ(length + range) time and +space. It counts the number of occurrences of each value in the input and then iterates +through those counts repopulating the input with the values in sorted order. +""" +struct CountingSort <: Algorithm end +maybe_reverse(o::ForwardOrdering, x) = x +maybe_reverse(o::ReverseOrdering, x) = reverse(x) +function _sort!(v::AbstractVector{<:Integer}, ::CountingSort, o::DirectOrdering, kw) + @getkw lo hi mn mx scratch + range = o === Reverse ? mn-mx : mx-mn + offs = 1 - (o === Reverse ? mx : mn) + + counts = fill(0, range+1) # TODO use scratch (but be aware of type stability) + @inbounds for i = lo:hi + counts[v[i] + offs] += 1 + end + + idx = lo + @inbounds for i = maybe_reverse(o, 1:range+1) + lastidx = idx + counts[i] - 1 + val = i-offs + for j = idx:lastidx + v[j] = val + end + idx = lastidx + 1 + end + + scratch +end + + +""" + ConsiderRadixSort(radix=RadixSort(), next) <: Algorithm + +If the number of bits in the input's range is small enough and the input supports efficient +bitshifts, use the `radix` algorithm. Otherwise, dispatch to the `next` algorithm. +""" +struct ConsiderRadixSort{T <: Algorithm, U <: Algorithm} <: Algorithm + radix::T + next::U +end +ConsiderRadixSort(next) = ConsiderRadixSort(RadixSort(), next) +function _sort!(v::AbstractVector, a::ConsiderRadixSort, o::DirectOrdering, kw) + @getkw lo hi mn mx + urange = uint_map(mx, o)-uint_map(mn, o) + bits = unsigned(8sizeof(urange) - leading_zeros(urange)) + if sizeof(eltype(v)) <= 8 && bits+70 < 22log(hi-lo) + _sort!(v, a.radix, o, kw) + else + _sort!(v, a.next, o, kw) + end +end + + +""" + RadixSort <: Algorithm + +Use the radix sort algorithm. + +`RadixSort` is a stable least significant bit first radix sort algorithm that runs in +`O(length * log(range))` time and linear space. + +It first sorts the entire vector by the last `chunk_size` bits, then by the second +to last `chunk_size` bits, and so on. Stability means that it will not reorder two elements +that compare equal. This is essential so that the order introduced by earlier, +less significant passes is preserved by later passes. + +Each pass divides the input into `2^chunk_size == mask+1` buckets. To do this, it + * counts the number of entries that fall into each bucket + * uses those counts to compute the indices to move elements of those buckets into + * moves elements into the computed indices in the swap array + * switches the swap and working array + +`chunk_size` is larger for larger inputs and determined by an empirical heuristic. +""" +struct RadixSort <: Algorithm end +function _sort!(v::AbstractVector, a::RadixSort, o::DirectOrdering, kw) + @getkw lo hi mn mx scratch + umn = uint_map(mn, o) + urange = uint_map(mx, o)-umn + bits = unsigned(8sizeof(urange) - leading_zeros(urange)) + + # At this point, we are committed to radix sort. + u = uint_map!(v, lo, hi, o) + + # we subtract umn to avoid radixing over unnecessary bits. For example, + # Int32[3, -1, 2] uint_maps to UInt32[0x80000003, 0x7fffffff, 0x80000002] + # which uses all 32 bits, but once we subtract umn = 0x7fffffff, we are left with + # UInt32[0x00000004, 0x00000000, 0x00000003] which uses only 3 bits, and + # Float32[2.012, 400.0, 12.345] uint_maps to UInt32[0x3fff3b63, 0x3c37ffff, 0x414570a4] + # which is reduced to UInt32[0x03c73b64, 0x00000000, 0x050d70a5] using only 26 bits. + # the overhead for this subtraction is small enough that it is worthwhile in many cases. + + # this is faster than u[lo:hi] .-= umn as of v1.9.0-DEV.100 + @inbounds for i in lo:hi + u[i] -= umn + end + + scratch, t = make_scratch(scratch, eltype(v), hi-lo+1) + tu = reinterpret(eltype(u), t) + if radix_sort!(u, lo, hi, bits, tu, 1-lo) + uint_unmap!(v, u, lo, hi, o, umn) + else + uint_unmap!(v, tu, lo, hi, o, umn, 1-lo) + end + scratch end + +""" + PartialQuickSort(lo::Union{Integer, Missing}, hi::Union{Integer, Missing}, next::Algorithm) <: Algorithm + +Indicate that a sorting function should use the partial quick sort algorithm. + +Partial quick sort finds and sorts the elements that would end up in positions `lo:hi` using +[`QuickSort`](@ref). It is recursive and uses the `next` algorithm for small chunks + +Characteristics: + * *stable*: preserves the ordering of elements which compare equal + (e.g. "a" and "A" in a sort of letters which ignores case). + * *not in-place* in memory. + * *divide-and-conquer*: sort strategy similar to [`MergeSort`](@ref). +""" +struct PartialQuickSort{L<:Union{Integer,Missing}, H<:Union{Integer,Missing}, T<:Algorithm} <: Algorithm + lo::L + hi::H + next::T +end +PartialQuickSort(k::Integer) = PartialQuickSort(missing, k, SMALL_ALGORITHM) +PartialQuickSort(k::OrdinalRange) = PartialQuickSort(first(k), last(k), SMALL_ALGORITHM) +_PartialQuickSort(k::Integer) = InitialOptimizations(PartialQuickSort(k:k)) +_PartialQuickSort(k::OrdinalRange) = InitialOptimizations(PartialQuickSort(k)) + +""" + QuickSort + +Indicate that a sorting function should use the quick sort algorithm. + +Quick sort picks a pivot element, partitions the array based on the pivot, +and then sorts the elements before and after the pivot recursively. + +Characteristics: + * *stable*: preserves the ordering of elements which compare equal + (e.g. "a" and "A" in a sort of letters which ignores case). + * *not in-place* in memory. + * *divide-and-conquer*: sort strategy similar to [`MergeSort`](@ref). + * *good performance* for almost all large collections. + * *quadratic worst case runtime* in pathological cases + (vanishingly rare for non-malicious input) +""" +const QuickSort = PartialQuickSort(missing, missing, SMALL_ALGORITHM) + # select a pivot for QuickSort # # This method is redefined to rand(lo:hi) in Random.jl @@ -542,147 +985,127 @@ select_pivot(lo::Integer, hi::Integer) = typeof(hi-lo)(hash(lo) % (hi-lo+1)) + l # # returns (pivot, pivot_index) where pivot_index is the location the pivot # should end up, but does not set t[pivot_index] = pivot -function partition!(t::AbstractVector, lo::Integer, hi::Integer, o::Ordering, v::AbstractVector, rev::Bool) +function partition!(t::AbstractVector, lo::Integer, hi::Integer, offset::Integer, o::Ordering, v::AbstractVector, rev::Bool) pivot_index = select_pivot(lo, hi) - trues = 0 @inbounds begin pivot = v[pivot_index] while lo < pivot_index x = v[lo] fx = rev ? !lt(o, x, pivot) : lt(o, pivot, x) - t[(fx ? hi : lo) - trues] = x - trues += fx + t[(fx ? hi : lo) - offset] = x + offset += fx lo += 1 end while lo < hi x = v[lo+1] fx = rev ? lt(o, pivot, x) : !lt(o, x, pivot) - t[(fx ? hi : lo) - trues] = x - trues += fx + t[(fx ? hi : lo) - offset] = x + offset += fx lo += 1 end end - # pivot_index = lo-trues + # pivot_index = lo-offset # t[pivot_index] is whatever it was before # t[pivot_index] >* pivot, reverse stable - pivot, lo-trues + pivot, lo-offset end -function sort!(v::AbstractVector, lo::Integer, hi::Integer, a::PartialQuickSort, - o::Ordering, t::AbstractVector=similar(v), swap=false, rev=false; - check_presorted=true) +function _sort!(v::AbstractVector, a::PartialQuickSort, o::Ordering, kw; + t=nothing, offset=nothing, swap=false, rev=false) + @getkw lo hi scratch - if check_presorted && !rev && !swap - # Even if we are only sorting a short region, we can only short-circuit if the whole - # vector is presorted. A weaker condition is possible, but unlikely to be useful. - if _issorted(v, lo, hi, o) - return v - elseif _issorted(v, lo, hi, Lt((x, y) -> !lt(o, x, y))) - # Reverse only if necessary. Using issorted(..., Reverse(o)) would violate stability. - return reverse!(v, lo, hi) - end + if t === nothing + scratch, t = make_scratch(scratch, eltype(v), hi-lo+1) + offset = 1-lo + kw = (;kw..., scratch) end while lo < hi && hi - lo > SMALL_THRESHOLD - pivot, j = swap ? partition!(v, lo, hi, o, t, rev) : partition!(t, lo, hi, o, v, rev) + pivot, j = swap ? partition!(v, lo+offset, hi+offset, offset, o, t, rev) : partition!(t, lo, hi, -offset, o, v, rev) + j -= !swap*offset @inbounds v[j] = pivot swap = !swap # For QuickSort, a.lo === a.hi === missing, so the first two branches get skipped if !ismissing(a.lo) && j <= a.lo # Skip sorting the lower part - swap && copyto!(v, lo, t, lo, j-lo) + swap && copyto!(v, lo, t, lo+offset, j-lo) rev && reverse!(v, lo, j-1) lo = j+1 rev = !rev elseif !ismissing(a.hi) && a.hi <= j # Skip sorting the upper part - swap && copyto!(v, j+1, t, j+1, hi-j) + swap && copyto!(v, j+1, t, j+1+offset, hi-j) rev || reverse!(v, j+1, hi) hi = j-1 elseif j-lo < hi-j # Sort the lower part recursively because it is smaller. Recursing on the # smaller part guarantees O(log(n)) stack space even on pathological inputs. - sort!(v, lo, j-1, a, o, t, swap, rev; check_presorted=false) + _sort!(v, a, o, (;kw..., lo, hi=j-1); t, offset, swap, rev) lo = j+1 rev = !rev else # Sort the higher part recursively - sort!(v, j+1, hi, a, o, t, swap, !rev; check_presorted=false) + _sort!(v, a, o, (;kw..., lo=j+1, hi); t, offset, swap, rev=!rev) hi = j-1 end end - hi < lo && return v - swap && copyto!(v, lo, t, lo, hi-lo+1) + hi < lo && return scratch + swap && copyto!(v, lo, t, lo+offset, hi-lo+1) rev && reverse!(v, lo, hi) - sort!(v, lo, hi, SMALL_ALGORITHM, o) + _sort!(v, a.next, o, (;kw..., lo, hi)) end -function sort!(v::AbstractVector{T}, lo::Integer, hi::Integer, a::MergeSortAlg, o::Ordering, - t0::Union{AbstractVector{T}, Nothing}=nothing) where T - @inbounds if lo < hi - hi-lo <= SMALL_THRESHOLD && return sort!(v, lo, hi, SMALL_ALGORITHM, o) - m = midpoint(lo, hi) - - t = t0 === nothing ? similar(v, m-lo+1) : t0 - length(t) < m-lo+1 && resize!(t, m-lo+1) - require_one_based_indexing(t) +""" + StableCheckSorted(next) <: Algorithm - sort!(v, lo, m, a, o, t) - sort!(v, m+1, hi, a, o, t) +Check if an input is sorted and/or reverse-sorted. - i, j = 1, lo - while j <= m - t[i] = v[j] - i += 1 - j += 1 - end - - i, k = 1, lo - while k < j <= hi - if lt(o, v[j], t[i]) - v[k] = v[j] - j += 1 - else - v[k] = t[i] - i += 1 - end - k += 1 - end - while k < j - v[k] = t[i] - k += 1 - i += 1 - end +The definition of reverse-sorted is that for every pair of adjacent elements, the latter is +less than the former. This is stricter than `issorted(v, Reverse(o))` to avoid swapping pairs +of elements that compare equal. +""" +struct StableCheckSorted{T<:Algorithm} <: Algorithm + next::T +end +function _sort!(v::AbstractVector, a::StableCheckSorted, o::Ordering, kw) + @getkw lo hi scratch + if _issorted(v, lo, hi, o) + return scratch + elseif _issorted(v, lo, hi, Lt((x, y) -> !lt(o, x, y))) + # Reverse only if necessary. Using issorted(..., Reverse(o)) would violate stability. + reverse!(v, lo, hi) + return scratch end - return v + _sort!(v, a.next, o, kw) end -# This is a stable least significant bit first radix sort. -# -# That is, it first sorts the entire vector by the last chunk_size bits, then by the second -# to last chunk_size bits, and so on. Stability means that it will not reorder two elements -# that compare equal. This is essential so that the order introduced by earlier, -# less significant passes is preserved by later passes. -# -# Each pass divides the input into 2^chunk_size == mask+1 buckets. To do this, it -# * counts the number of entries that fall into each bucket -# * uses those counts to compute the indices to move elements of those buckets into -# * moves elements into the computed indices in the swap array -# * switches the swap and working array -# -# In the case of an odd number of passes, the returned vector will === the input vector t, -# not v. This is one of the many reasons radix_sort! is not exported. + +# The return value indicates whether v is sorted (true) or t is sorted (false) +# This is one of the many reasons radix_sort! is not exported. function radix_sort!(v::AbstractVector{U}, lo::Integer, hi::Integer, bits::Unsigned, - t::AbstractVector{U}, chunk_size=radix_chunk_size_heuristic(lo, hi, bits)) where U <: Unsigned + t::AbstractVector{U}, offset::Integer, + chunk_size=radix_chunk_size_heuristic(lo, hi, bits)) where U <: Unsigned # bits is unsigned for performance reasons. - mask = UInt(1) << chunk_size - 1 - counts = Vector{Int}(undef, mask+2) - - @inbounds for shift in 0:chunk_size:bits-1 - + counts = Vector{Int}(undef, 1 << chunk_size + 1) # TODO use scratch for this + + shift = 0 + while true + @noinline radix_sort_pass!(t, lo, hi, offset, counts, v, shift, chunk_size) + # the latest data resides in t + shift += chunk_size + shift < bits || return false + @noinline radix_sort_pass!(v, lo+offset, hi+offset, -offset, counts, t, shift, chunk_size) + # the latest data resides in v + shift += chunk_size + shift < bits || return true + end +end +function radix_sort_pass!(t, lo, hi, offset, counts, v, shift, chunk_size) + mask = UInt(1) << chunk_size - 1 # mask is defined in pass so that the compiler + @inbounds begin # ↳ knows it's shape # counts[2:mask+2] will store the number of elements that fall into each bucket. # if chunk_size = 8, counts[2] is bucket 0x00 and counts[257] is bucket 0xff. counts .= 0 @@ -703,15 +1126,10 @@ function radix_sort!(v::AbstractVector{U}, lo::Integer, hi::Integer, bits::Unsig x = v[k] # lookup the element i = (x >> shift)&mask + 1 # compute its bucket's index for this pass j = counts[i] # lookup the target index - t[j] = x # put the element where it belongs + t[j + offset] = x # put the element where it belongs counts[i] = j + 1 # increment the target index for the next end # ↳ element in this bucket - - v, t = t, v # swap the now sorted destination vector t back into primary vector v - end - - v end function radix_chunk_size_heuristic(lo::Integer, hi::Integer, bits::Unsigned) # chunk_size is the number of bits to radix over at once. @@ -726,23 +1144,6 @@ function radix_chunk_size_heuristic(lo::Integer, hi::Integer, bits::Unsigned) UInt8(cld(bits, cld(bits, guess))) end -# For AbstractVector{Bool}, counting sort is always best. -# This is an implementation of counting sort specialized for Bools. -# Accepts unused scratch space to avoid method ambiguity. -function sort!(v::AbstractVector{Bool}, lo::Integer, hi::Integer, ::AdaptiveSortAlg, o::Ordering, - t::Union{AbstractVector{Bool}, Nothing}=nothing) - first = lt(o, false, true) ? false : lt(o, true, false) ? true : return v - count = 0 - @inbounds for i in lo:hi - if v[i] == first - count += 1 - end - end - @inbounds v[lo:lo+count-1] .= first - @inbounds v[lo+count:hi] .= !first - v -end - maybe_unsigned(x::Integer) = x # this is necessary to avoid calling unsigned on BigInt maybe_unsigned(x::BitSigned) = unsigned(x) function _extrema(v::AbstractVector, lo::Integer, hi::Integer, o::Ordering) @@ -761,129 +1162,152 @@ function _issorted(v::AbstractVector, lo::Integer, hi::Integer, o::Ordering) end true end -function sort!(v::AbstractVector{T}, lo::Integer, hi::Integer, ::AdaptiveSortAlg, o::Ordering, - t::Union{AbstractVector{T}, Nothing}=nothing) where T - # if the sorting task is not UIntMappable, then we can't radix sort or sort_int_range! - # so we skip straight to the fallback algorithm which is comparison based. - U = UIntMappable(eltype(v), o) - U === nothing && return sort!(v, lo, hi, QuickSort, o) - - # to avoid introducing excessive detection costs for the trivial sorting problem - # and to avoid overflow, we check for small inputs before any other runtime checks - hi <= lo && return v - lenm1 = maybe_unsigned(hi-lo) # adding 1 would risk overflow - # only count sort on a short range can compete with insertion sort when lenm1 < 40 - # and the optimization is not worth the detection cost, so we use insertion sort. - lenm1 < 40 && return sort!(v, lo, hi, SMALL_ALGORITHM, o) - # For most arrays, a presorted check is cheap (overhead < 5%) and for most large - # arrays it is essentially free (<1%). Insertion sort runs in a fast O(n) on presorted - # input and this guarantees presorted input will always be efficiently handled - _issorted(v, lo, hi, o) && return v - - # For large arrays, a reverse-sorted check is essentially free (overhead < 1%) - if lenm1 >= 500 && _issorted(v, lo, hi, ReverseOrdering(o)) - # If reversing is valid, do so. This does not violate stability - # because being UIntMappable implies a linear order. - reverse!(v, lo, hi) - return v - end - # UInt128 does not support fast bit shifting so we never - # dispatch to radix sort but we may still perform count sort - if sizeof(U) > 8 - if T <: Integer && o isa DirectOrdering - v_min, v_max = _extrema(v, lo, hi, Forward) - v_range = maybe_unsigned(v_max-v_min) - v_range == 0 && return v # all same - - # we know lenm1 ≥ 40, so this will never underflow. - # if lenm1 > 3.7e18 (59 exabytes), then this may incorrectly dispatch to fallback - if v_range < 5lenm1-100 # count sort will outperform comparison sort if v's range is small - return sort_int_range!(v, Int(v_range+1), v_min, o === Forward ? identity : reverse, lo, hi) - end - end - return sort!(v, lo, hi, QuickSort, o; check_presorted=false) - end +## default sorting policy ## - v_min, v_max = _extrema(v, lo, hi, o) - lt(o, v_min, v_max) || return v # all same - if T <: Integer && o isa DirectOrdering - R = o === Reverse - v_range = maybe_unsigned(R ? v_min-v_max : v_max-v_min) - if v_range < div(lenm1, 2) # count sort will be superior if v's range is very small - return sort_int_range!(v, Int(v_range+1), R ? v_max : v_min, R ? reverse : identity, lo, hi) - end - end +""" + InitialOptimizations(next) <: Algorithm - u_min, u_max = uint_map(v_min, o), uint_map(v_max, o) - u_range = maybe_unsigned(u_max-u_min) - if u_range < div(lenm1, 2) # count sort will be superior if u's range is very small - u = uint_map!(v, lo, hi, o) - sort_int_range!(u, Int(u_range+1), u_min, identity, lo, hi) - return uint_unmap!(v, u, lo, hi, o) - end +Attempt to apply a suite of low-cost optimizations to the input vector before sorting. - # if u's range is small, then once we subtract out v_min, we'll get a vector like - # UInt16[0x001a, 0x0015, 0x0006, 0x001b, 0x0008, 0x000c, 0x0001, 0x000e, 0x001c, 0x0009] - # where we only need to radix over the last few bits (5, in the example). - bits = unsigned(8sizeof(u_range) - leading_zeros(u_range)) - - # radix sort runs in O(bits * lenm1), quick sort runs in O(lenm1 * log(lenm1)). - # dividing both sides by lenm1 and introducing empirical constant factors yields - # the following heuristic for when QuickSort is faster than RadixSort - if 22log(lenm1) < bits + 70 - return if lenm1 > 80 - sort!(v, lo, hi, QuickSort, o; check_presorted=false) - else - sort!(v, lo, hi, SMALL_ALGORITHM, o) - end - end +`InitialOptimizations` is an implementation detail and subject to change or removal in +future versions of Julia. - # At this point, we are committed to radix sort. - u = uint_map!(v, lo, hi, o) +If `next` is stable, then `InitialOptimizations(next)` is also stable. - # we subtract u_min to avoid radixing over unnecessary bits. For example, - # Int32[3, -1, 2] uint_maps to UInt32[0x80000003, 0x7fffffff, 0x80000002] - # which uses all 32 bits, but once we subtract u_min = 0x7fffffff, we are left with - # UInt32[0x00000004, 0x00000000, 0x00000003] which uses only 3 bits, and - # Float32[2.012, 400.0, 12.345] uint_maps to UInt32[0x3fff3b63, 0x3c37ffff, 0x414570a4] - # which is reduced to UInt32[0x03c73b64, 0x00000000, 0x050d70a5] using only 26 bits. - # the overhead for this subtraction is small enough that it is worthwhile in many cases. +The specific optimizations attempted by `InitialOptimizations` are +[`MissingOptimization`](@ref), [`BoolOptimization`](@ref), dispatch to +[`InsertionSort`](@ref) for inputs with `length <= 10`, and [`IEEEFloatOptimization`](@ref). +""" +InitialOptimizations(next) = MissingOptimization( + BoolOptimization( + Small{10}( + IEEEFloatOptimization( + next)))) +""" + DEFAULT_STABLE - # this is faster than u[lo:hi] .-= u_min as of v1.9.0-DEV.100 - @inbounds for i in lo:hi - u[i] -= u_min - end +The default sorting algorithm. - len = lenm1 + 1 - if t !== nothing && checkbounds(Bool, t, lo:hi) # Fully preallocated and aligned scratch space - u2 = radix_sort!(u, lo, hi, bits, reinterpret(U, t)) - uint_unmap!(v, u2, lo, hi, o, u_min) - elseif t !== nothing && (applicable(resize!, t, len) || length(t) >= len) # Viable scratch space - length(t) >= len || resize!(t, len) - t1 = axes(t, 1) isa OneTo ? t : view(t, firstindex(t):lastindex(t)) - u2 = radix_sort!(view(u, lo:hi), 1, len, bits, reinterpret(U, t1)) - uint_unmap!(view(v, lo:hi), u2, 1, len, o, u_min) - else # No viable scratch space - u2 = radix_sort!(u, lo, hi, bits, similar(u)) - uint_unmap!(v, u2, lo, hi, o, u_min) - end -end +This algorithm is guaranteed to be stable (i.e. it will not reorder elements that compare +equal). It makes an effort to be fast for most inputs. -## generic sorting methods ## +The algorithms used by `DEFAULT_STABLE` are an implementation detail. See extended help +for the current dispatch system. -defalg(v::AbstractArray) = DEFAULT_STABLE +# Extended Help -function sort!(v::AbstractVector{T}, alg::Algorithm, - order::Ordering, t::Union{AbstractVector{T}, Nothing}=nothing) where T - sort!(v, firstindex(v), lastindex(v), alg, order, t) -end +`DEFAULT_STABLE` is composed of two parts: the [`InitialOptimizations`](@ref) and a hybrid +of Radix, Insertion, Counting, Quick sorts. + +We begin with MissingOptimization because it has no runtime cost when it is not +triggered and can enable other optimizations to be applied later. For example, +BoolOptimization cannot apply to an `AbstractVector{Union{Missing, Bool}}`, but after +[`MissingOptimization`](@ref) is applied, that input will be converted into am +`AbstractVector{Bool}`. + +We next apply [`BoolOptimization`](@ref) because it also has no runtime cost when it is not +triggered and when it is triggered, it is an incredibly efficient algorithm (sorting `Bool`s +is quite easy). + +Next, we dispatch to [`InsertionSort`](@ref) for inputs with `length <= 10`. This dispatch +occurs before the [`IEEEFloatOptimization`](@ref) pass because the +[`IEEEFloatOptimization`](@ref)s are not beneficial for very small inputs. + +To conclude the [`InitialOptimizations`](@ref), we apply [`IEEEFloatOptimization`](@ref). + +After these optimizations, we branch on whether radix sort and related algorithms can be +applied to the input vector and ordering. We conduct this branch by testing if +`UIntMappable(v, order) !== nothing`. That is, we see if we know of a reversible mapping +from `eltype(v)` to `UInt` that preserves the ordering `order`. We perform this check after +the initial optimizations because they can change the input vector's type and ordering to +make them `UIntMappable`. + +If the input is not [`UIntMappable`](@ref), then we perform a presorted check and dispatch +to [`QuickSort`](@ref). + +Otherwise, we dispatch to [`InsertionSort`](@ref) for inputs with `length <= 40` and then +perform a presorted check ([`CheckSorted`](@ref)). + +We check for short inputs before performing the presorted check to avoid the overhead of the +check for small inputs. Because the alternate dispatch is to [`InseritonSort`](@ref) which +has efficient `O(n)` runtime on presorted inputs, the check is not necessary for small +inputs. + +We check if the input is reverse-sorted for long vectors (more than 500 elements) because +the check is essentially free unless the input is almost entirely reverse sorted. + +Note that once the input is determined to be [`UIntMappable`](@ref), we know the order forms +a [total order](wikipedia.org/wiki/Total_order) over the inputs and so it is impossible to +perform an unstable sort because no two elements can compare equal unless they _are_ equal, +in which case switching them is undetectable. We utilize this fact to perform a more +aggressive reverse sorted check that will reverse the vector `[3, 2, 2, 1]`. + +After these potential fast-paths are tried and failed, we [`ComputeExtrema`](@ref) of the +input. This computation has a fairly fast `O(n)` runtime, but we still try to delay it until +it is necessary. + +Next, we [`ConsiderCountingSort`](@ref). If the range the input is small compared to its +length, we apply [`CountingSort`](@ref). + +Next, we [`ConsiderRadixSort`](@ref). This is similar to the dispatch to counting sort, +but we conside rthe number of _bits_ in the range, rather than the range itself. +Consequently, we apply [`RadixSort`](@ref) for any reasonably long inputs that reach this +stage. -function sort!(v::AbstractVector{T}, lo::Integer, hi::Integer, alg::Algorithm, - order::Ordering, t::Union{AbstractVector{T}, Nothing}=nothing) where T - sort!(v, lo, hi, alg, order) +Finally, if the input has length less than 80, we dispatch to [`InsertionSort`](@ref) and +otherwise we dispatch to [`QuickSort`](@ref). +""" +const DEFAULT_STABLE = InitialOptimizations( + IsUIntMappable( + Small{40}( + CheckSorted( + ComputeExtrema( + ConsiderCountingSort( + ConsiderRadixSort( + Small{80}( + QuickSort)))))), + StableCheckSorted( + QuickSort))) +""" + DEFAULT_UNSTABLE + +An efficient sorting algorithm. + +The algorithms used by `DEFAULT_UNSTABLE` are an implementation detail. They are currently +the same as those used by [`DEFAULT_STABLE`](@ref), but this is subject to change in future. +""" +const DEFAULT_UNSTABLE = DEFAULT_STABLE +const SMALL_THRESHOLD = 20 + +function Base.show(io::IO, alg::Algorithm) + print_tree(io, alg, 0) end +function print_tree(io::IO, alg::Algorithm, cols::Int) + print(io, " "^cols) + show_type(io, alg) + print(io, '(') + for (i, name) in enumerate(fieldnames(typeof(alg))) + arg = getproperty(alg, name) + i > 1 && print(io, ',') + if arg isa Algorithm + println(io) + print_tree(io, arg, cols+1) + else + i > 1 && print(io, ' ') + print(io, arg) + end + end + print(io, ')') +end +show_type(io::IO, alg::Algorithm) = Base.show_type_name(io, typeof(alg).name) +show_type(io::IO, alg::Small{N}) where N = print(io, "Base.Sort.Small{$N}") + +defalg(v::AbstractArray) = DEFAULT_STABLE +defalg(v::AbstractArray{<:Union{Number, Missing}}) = DEFAULT_UNSTABLE +defalg(v::AbstractArray{Missing}) = DEFAULT_UNSTABLE # for method disambiguation +defalg(v::AbstractArray{Union{}}) = DEFAULT_UNSTABLE # for method disambiguation """ sort!(v; alg::Algorithm=defalg(v), lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward) @@ -931,31 +1355,9 @@ function sort!(v::AbstractVector{T}; by=identity, rev::Union{Bool,Nothing}=nothing, order::Ordering=Forward, - scratch::Union{AbstractVector{T}, Nothing}=nothing) where T - sort!(v, alg, ord(lt,by,rev,order), scratch) -end - -# sort! for vectors of few unique integers -function sort_int_range!(x::AbstractVector{<:Integer}, rangelen, minval, maybereverse, - lo=firstindex(x), hi=lastindex(x)) - offs = 1 - minval - - counts = fill(0, rangelen) - @inbounds for i = lo:hi - counts[x[i] + offs] += 1 - end - - idx = lo - @inbounds for i = maybereverse(1:rangelen) - lastidx = idx + counts[i] - 1 - val = i-offs - for j = idx:lastidx - x[j] = val - end - idx = lastidx + 1 - end - - return x + scratch::Union{Vector{T}, Nothing}=nothing) where T + _sort!(v, alg, ord(lt,by,rev,order), (;scratch)) + v end """ @@ -1081,7 +1483,7 @@ function partialsortperm!(ix::AbstractVector{<:Integer}, v::AbstractVector, end # do partial quicksort - sort!(ix, _PartialQuickSort(k), Perm(ord(lt, by, rev, order), v)) + _sort!(ix, _PartialQuickSort(k), Perm(ord(lt, by, rev, order), v), (;)) maybeview(ix, k) end @@ -1141,7 +1543,7 @@ function sortperm(A::AbstractArray; by=identity, rev::Union{Bool,Nothing}=nothing, order::Ordering=Forward, - scratch::Union{AbstractVector{<:Integer}, Nothing}=nothing, + scratch::Union{Vector{<:Integer}, Nothing}=nothing, dims...) #to optionally specify dims argument ordr = ord(lt,by,rev,order) if ordr === Forward && isa(A,Vector) && eltype(A)<:Integer @@ -1205,7 +1607,7 @@ function sortperm!(ix::AbstractArray{T}, A::AbstractArray; rev::Union{Bool,Nothing}=nothing, order::Ordering=Forward, initialized::Bool=false, - scratch::Union{AbstractVector{T}, Nothing}=nothing, + scratch::Union{Vector{T}, Nothing}=nothing, dims...) where T <: Integer #to optionally specify dims argument (typeof(A) <: AbstractVector) == (:dims in keys(dims)) && throw(ArgumentError("Dims argument incorrect for type $(typeof(A))")) axes(ix) == axes(A) || throw(ArgumentError("index array must have the same size/axes as the source array, $(axes(ix)) != $(axes(A))")) @@ -1278,7 +1680,7 @@ function sort(A::AbstractArray{T}; by=identity, rev::Union{Bool,Nothing}=nothing, order::Ordering=Forward, - scratch::Union{AbstractVector{T}, Nothing}=similar(A, size(A, dims))) where T + scratch::Union{Vector{T}, Nothing}=nothing) where T dim = dims order = ord(lt,by,rev,order) n = length(axes(A, dim)) @@ -1295,14 +1697,27 @@ function sort(A::AbstractArray{T}; end end -@noinline function sort_chunks!(Av, n, alg, order, t) +@noinline function sort_chunks!(Av, n, alg, order, scratch) inds = LinearIndices(Av) - for s = first(inds):n:last(inds) - sort!(Av, s, s+n-1, alg, order, t) + sort_chunks!(Av, n, alg, order, scratch, first(inds), last(inds)) +end + +@noinline function sort_chunks!(Av, n, alg, order, scratch::Nothing, fst, lst) + for lo = fst:n:lst + s = _sort!(Av, alg, order, (; lo, hi=lo+n-1, scratch)) + s !== nothing && return sort_chunks!(Av, n, alg, order, s, lo+n, lst) end Av end +@noinline function sort_chunks!(Av, n, alg, order, scratch::AbstractVector, fst, lst) + for lo = fst:n:lst + _sort!(Av, alg, order, (; lo, hi=lo+n-1, scratch)) + end + Av +end + + """ sort!(A; dims::Integer, alg::Algorithm=defalg(A), lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward) @@ -1338,14 +1753,14 @@ function sort!(A::AbstractArray{T}; lt=isless, by=identity, rev::Union{Bool,Nothing}=nothing, - order::Ordering=Forward, - scratch::Union{AbstractVector{T}, Nothing}=similar(A, size(A, dims))) where T - _sort!(A, Val(dims), alg, ord(lt, by, rev, order), scratch) + order::Ordering=Forward, # TODO stop eagerly over-allocating. + scratch::Union{Vector{T}, Nothing}=similar(A, size(A, dims))) where T + __sort!(A, Val(dims), alg, ord(lt, by, rev, order), scratch) end -function _sort!(A::AbstractArray{T}, ::Val{K}, +function __sort!(A::AbstractArray{T}, ::Val{K}, alg::Algorithm, order::Ordering, - scratch::Union{AbstractVector{T}, Nothing}) where {K,T} + scratch::Union{Vector{T}, Nothing}) where {K,T} nd = ndims(A) 1 <= K <= nd || throw(ArgumentError("dimension out of range")) @@ -1353,7 +1768,7 @@ function _sort!(A::AbstractArray{T}, ::Val{K}, remdims = ntuple(i -> i == K ? 1 : axes(A, i), nd) for idx in CartesianIndices(remdims) Av = view(A, ntuple(i -> i == K ? Colon() : idx[i], nd)...) - sort!(Av, alg, order, scratch) + sort!(Av; alg, order, scratch) end A end @@ -1436,175 +1851,109 @@ function uint_map!(v::AbstractVector, lo::Integer, hi::Integer, order::Ordering) end function uint_unmap!(v::AbstractVector, u::AbstractVector{U}, lo::Integer, hi::Integer, - order::Ordering, offset::U=zero(U)) where U <: Unsigned + order::Ordering, offset::U=zero(U), + index_offset::Integer=0) where U <: Unsigned @inbounds for i in lo:hi - v[i] = uint_unmap(eltype(v), u[i]+offset, order) + v[i] = uint_unmap(eltype(v), u[i+index_offset]+offset, order) end v end -## fast clever sorting for floats ## - -module Float -using ..Sort -using ...Order -using Base: IEEEFloat - -import Core.Intrinsics: slt_int -import ..Sort: sort!, UIntMappable, uint_map, uint_unmap -import ...Order: lt, DirectOrdering - -# fpsort is not safe for vectors of mixed bitwidth such as Vector{Union{Float32, Float64}}. -# This type allows us to dispatch only when it is safe to do so. See #42739 for more info. -const FPSortable = Union{ - AbstractVector{Union{Float16, Missing}}, - AbstractVector{Union{Float32, Missing}}, - AbstractVector{Union{Float64, Missing}}, - AbstractVector{Float16}, - AbstractVector{Float32}, - AbstractVector{Float64}, - AbstractVector{Missing}} -struct Left <: Ordering end -struct Right <: Ordering end +### Unused constructs for backward compatibility ### -left(::DirectOrdering) = Left() -right(::DirectOrdering) = Right() +struct MergeSortAlg{T <: Algorithm} <: Algorithm + next::T +end -left(o::Perm) = Perm(left(o.order), o.data) -right(o::Perm) = Perm(right(o.order), o.data) +""" + MergeSort -lt(::Left, x::T, y::T) where {T<:IEEEFloat} = slt_int(y, x) -lt(::Right, x::T, y::T) where {T<:IEEEFloat} = slt_int(x, y) +Indicate that a sorting function should use the merge sort algorithm. -uint_map(x::Float16, ::Left) = ~reinterpret(UInt16, x) -uint_unmap(::Type{Float16}, u::UInt16, ::Left) = reinterpret(Float16, ~u) -uint_map(x::Float16, ::Right) = reinterpret(UInt16, x) -uint_unmap(::Type{Float16}, u::UInt16, ::Right) = reinterpret(Float16, u) -UIntMappable(::Type{Float16}, ::Union{Left, Right}) = UInt16 +Merge sort divides the collection into subcollections and +repeatedly merges them, sorting each subcollection at each step, +until the entire collection has been recombined in sorted form. -uint_map(x::Float32, ::Left) = ~reinterpret(UInt32, x) -uint_unmap(::Type{Float32}, u::UInt32, ::Left) = reinterpret(Float32, ~u) -uint_map(x::Float32, ::Right) = reinterpret(UInt32, x) -uint_unmap(::Type{Float32}, u::UInt32, ::Right) = reinterpret(Float32, u) -UIntMappable(::Type{Float32}, ::Union{Left, Right}) = UInt32 +Characteristics: + * *stable*: preserves the ordering of elements which compare + equal (e.g. "a" and "A" in a sort of letters which ignores + case). + * *not in-place* in memory. + * *divide-and-conquer* sort strategy. +""" +const MergeSort = MergeSortAlg(SMALL_ALGORITHM) -uint_map(x::Float64, ::Left) = ~reinterpret(UInt64, x) -uint_unmap(::Type{Float64}, u::UInt64, ::Left) = reinterpret(Float64, ~u) -uint_map(x::Float64, ::Right) = reinterpret(UInt64, x) -uint_unmap(::Type{Float64}, u::UInt64, ::Right) = reinterpret(Float64, u) -UIntMappable(::Type{Float64}, ::Union{Left, Right}) = UInt64 +function _sort!(v::AbstractVector, a::MergeSortAlg, o::Ordering, kw; t=nothing, offset=nothing) + @getkw lo hi scratch + @inbounds if lo < hi + hi-lo <= SMALL_THRESHOLD && return _sort!(v, a.next, o, kw) -isnan(o::DirectOrdering, x::IEEEFloat) = (x!=x) -isnan(o::DirectOrdering, x::Missing) = false -isnan(o::Perm, i::Integer) = isnan(o.order,o.data[i]) + m = midpoint(lo, hi) -ismissing(o::DirectOrdering, x::IEEEFloat) = false -ismissing(o::DirectOrdering, x::Missing) = true -ismissing(o::Perm, i::Integer) = ismissing(o.order,o.data[i]) + if t === nothing + scratch, t = make_scratch(scratch, eltype(v), m-lo+1) + end -allowsmissing(::AbstractVector{T}, ::DirectOrdering) where {T} = T >: Missing -allowsmissing(::AbstractVector{<:Integer}, - ::Perm{<:DirectOrdering,<:AbstractVector{T}}) where {T} = - T >: Missing + _sort!(v, a, o, (;kw..., hi=m, scratch); t, offset) + _sort!(v, a, o, (;kw..., lo=m+1, scratch); t, offset) -function specials2left!(testf::Function, v::AbstractVector, o::Ordering, - lo::Integer=firstindex(v), hi::Integer=lastindex(v)) - i = lo - @inbounds while i <= hi && testf(o,v[i]) - i += 1 - end - j = i + 1 - @inbounds while j <= hi - if testf(o,v[j]) - v[i], v[j] = v[j], v[i] + i, j = 1, lo + while j <= m + t[i] = v[j] i += 1 + j += 1 end - j += 1 - end - return i, hi -end -function specials2right!(testf::Function, v::AbstractVector, o::Ordering, - lo::Integer=firstindex(v), hi::Integer=lastindex(v)) - i = hi - @inbounds while lo <= i && testf(o,v[i]) - i -= 1 - end - j = i - 1 - @inbounds while lo <= j - if testf(o,v[j]) - v[i], v[j] = v[j], v[i] - i -= 1 + + i, k = 1, lo + while k < j <= hi + if lt(o, v[j], t[i]) + v[k] = v[j] + j += 1 + else + v[k] = t[i] + i += 1 + end + k += 1 + end + while k < j + v[k] = t[i] + k += 1 + i += 1 end - j -= 1 end - return lo, i + + scratch end -function specials2left!(v::AbstractVector, a::Algorithm, o::Ordering) - lo, hi = firstindex(v), lastindex(v) - if allowsmissing(v, o) - i, _ = specials2left!((v, o) -> ismissing(v, o) || isnan(v, o), v, o, lo, hi) - sort!(v, lo, i-1, a, o) - return i, hi - else - return specials2left!(isnan, v, o, lo, hi) - end +# Support 3-, 5-, and 6-argument versions of sort! for calling into the internals in the old way +sort!(v::AbstractVector, a::Algorithm, o::Ordering) = sort!(v, firstindex(v), lastindex(v), a, o) +function sort!(v::AbstractVector, lo::Integer, hi::Integer, a::Algorithm, o::Ordering) + _sort!(v, a, o, (; lo, hi, allow_legacy_dispatch=false)) + v end -function specials2right!(v::AbstractVector, a::Algorithm, o::Ordering) - lo, hi = firstindex(v), lastindex(v) - if allowsmissing(v, o) - _, i = specials2right!((v, o) -> ismissing(v, o) || isnan(v, o), v, o, lo, hi) - sort!(v, i+1, hi, a, o) - return lo, i - else - return specials2right!(isnan, v, o, lo, hi) - end +sort!(v::AbstractVector, lo::Integer, hi::Integer, a::Algorithm, o::Ordering, _) = sort!(v, lo, hi, a, o) +function sort!(v::AbstractVector, lo::Integer, hi::Integer, a::Algorithm, o::Ordering, scratch::Vector) + _sort!(v, a, o, (; lo, hi, scratch, allow_legacy_dispatch=false)) + v end -specials2end!(v::AbstractVector, a::Algorithm, o::ForwardOrdering) = - specials2right!(v, a, o) -specials2end!(v::AbstractVector, a::Algorithm, o::ReverseOrdering) = - specials2left!(v, a, o) -specials2end!(v::AbstractVector{<:Integer}, a::Algorithm, o::Perm{<:ForwardOrdering}) = - specials2right!(v, a, o) -specials2end!(v::AbstractVector{<:Integer}, a::Algorithm, o::Perm{<:ReverseOrdering}) = - specials2left!(v, a, o) - -issignleft(o::ForwardOrdering, x::IEEEFloat) = lt(o, x, zero(x)) -issignleft(o::ReverseOrdering, x::IEEEFloat) = lt(o, x, -zero(x)) -issignleft(o::Perm, i::Integer) = issignleft(o.order, o.data[i]) - -function fpsort!(v::AbstractVector{T}, a::Algorithm, o::Ordering, - t::Union{AbstractVector{T}, Nothing}=nothing) where T - # fpsort!'s optimizations speed up comparisons, of which there are O(nlogn). - # The overhead is O(n). For n < 10, it's not worth it. - length(v) < 10 && return sort!(v, firstindex(v), lastindex(v), SMALL_ALGORITHM, o, t) - - i, j = lo, hi = specials2end!(v,a,o) - @inbounds while true - while i <= j && issignleft(o,v[i]); i += 1; end - while i <= j && !issignleft(o,v[j]); j -= 1; end - i <= j || break - v[i], v[j] = v[j], v[i] - i += 1; j -= 1 +# Support dispatch on custom algorithms in the old way +# sort!(::AbstractVector, ::Integer, ::Integer, ::MyCustomAlgorithm, ::Ordering) = ... +function _sort!(v::AbstractVector, a::Algorithm, o::Ordering, kw) + @getkw lo hi scratch allow_legacy_dispatch + if allow_legacy_dispatch + sort!(v, lo, hi, a, o) + scratch + else + # This error prevents infinite recursion for unknown algorithms + throw(ArgumentError("Base.Sort._sort!(::$(typeof(v)), ::$(typeof(a)), ::$(typeof(o))) is not defined")) end - sort!(v, lo, j, a, left(o), t) - sort!(v, i, hi, a, right(o), t) - return v -end - - -function sort!(v::FPSortable, a::Algorithm, o::DirectOrdering, - t::Union{FPSortable, Nothing}=nothing) - fpsort!(v, a, o, t) -end -function sort!(v::AbstractVector{T}, a::Algorithm, o::Perm{<:DirectOrdering,<:FPSortable}, - t::Union{AbstractVector{T}, Nothing}=nothing) where T <: Union{Signed, Unsigned} - fpsort!(v, a, o, t) end -end # module Sort.Float +# Keep old internal types so that people can keep dispatching with +# sort!(::AbstractVector, ::Integer, ::Integer, ::Base.QuickSortAlg, ::Ordering) = ... +const QuickSortAlg = typeof(QuickSort) end # module Sort diff --git a/test/sorting.jl b/test/sorting.jl index 4a0299b2217c2d..37bad7d23c94b0 100644 --- a/test/sorting.jl +++ b/test/sorting.jl @@ -79,8 +79,9 @@ end end @testset "stability" begin - for Alg in [InsertionSort, MergeSort, QuickSort, Base.Sort.AdaptiveSort, Base.DEFAULT_STABLE, - PartialQuickSort(missing, 1729), PartialQuickSort(1729, missing)] + for Alg in [InsertionSort, MergeSort, QuickSort, Base.DEFAULT_STABLE, + PartialQuickSort(missing, 1729, Base.Sort.SMALL_ALGORITHM), + PartialQuickSort(1729, missing, Base.Sort.SMALL_ALGORITHM)] @test issorted(sort(1:2000, alg=Alg, by=x->0)) @test issorted(sort(1:2000, alg=Alg, by=x->x÷100)) end @@ -534,11 +535,11 @@ end @test issorted(a) a = view([9:-1:0;], :)::SubArray - Base.Sort.sort_int_range!(a, 10, 0, identity) # test it supports non-Vector + Base.Sort._sort!(a, Base.Sort.CountingSort(), Base.Forward, (; mn=0, mx=9)) # test it supports non-Vector @test issorted(a) a = OffsetArray([9:-1:0;], -5) - Base.Sort.sort_int_range!(a, 10, 0, identity) + Base.Sort._sort!(a, Base.Sort.CountingSort(), Base.Forward, (; mn=0, mx=9)) @test issorted(a) end @@ -632,9 +633,9 @@ end @testset "uint mappings" begin #Construct value lists - floats = [T[-π, -1.0, -1/π, 1/π, 1.0, π, -0.0, 0.0, Inf, -Inf, NaN, -NaN, - prevfloat(T(0)), nextfloat(T(0)), prevfloat(T(Inf)), nextfloat(T(-Inf))] - for T in [Float16, Float32, Float64]] + floats = [reinterpret(U, vcat(T[-π, -1.0, -1/π, 1/π, 1.0, π, -0.0, 0.0, Inf, -Inf, NaN, -NaN, + prevfloat(T(0)), nextfloat(T(0)), prevfloat(T(Inf)), nextfloat(T(-Inf))], randnans(4))) + for (U, T) in [(UInt16, Float16), (UInt32, Float32), (UInt64, Float64)]] ints = [T[17, -T(17), 0, -one(T), 1, typemax(T), typemin(T), typemax(T)-1, typemin(T)+1] for T in Base.BitInteger_types] @@ -650,21 +651,18 @@ end UIntN(::Val{8}) = UInt64 UIntN(::Val{16}) = UInt128 map(vals) do x + x isa Base.ReinterpretArray && return T = eltype(x) U = UIntN(Val(sizeof(T))) append!(x, rand(T, 4)) append!(x, reinterpret.(T, rand(U, 4))) - if T <: AbstractFloat - mask = reinterpret(U, T(NaN)) - append!(x, reinterpret.(T, mask .| rand(U, 4))) - end end for x in vals T = eltype(x) U = UIntN(Val(sizeof(T))) - for order in [Forward, Reverse, Base.Sort.Float.Left(), Base.Sort.Float.Right(), By(Forward, identity)] - if order isa Base.Order.By || ((T <: AbstractFloat) == (order isa DirectOrdering)) + for order in [Forward, Reverse, By(Forward, identity)] + if order isa Base.Order.By @test Base.Sort.UIntMappable(T, order) === nothing continue end @@ -681,10 +679,6 @@ end for a in x for b in x - if order === Base.Sort.Float.Left() || order === Base.Sort.Float.Right() - # Left and Right orderings guarantee homogeneous sign and no NaNs - (isnan(a) || isnan(b) || signbit(a) != signbit(b)) && continue - end @test Base.Order.lt(order, a, b) === Base.Order.lt(Forward, Base.Sort.uint_map(a, order), Base.Sort.uint_map(b, order)) end end @@ -705,7 +699,7 @@ end # Nevertheless, it still works... for alg in [InsertionSort, MergeSort, QuickSort, - Base.Sort.AdaptiveSort, Base.DEFAULT_STABLE, Base.DEFAULT_UNSTABLE] + Base.DEFAULT_STABLE, Base.DEFAULT_UNSTABLE] @test sort(v, alg=alg, lt = <=) == s end @test partialsort(v, 172, lt = <=) == s[172] @@ -716,7 +710,7 @@ end # this invalid lt order. perm = reverse(sortperm(v, rev=true)) for alg in [InsertionSort, MergeSort, QuickSort, - Base.Sort.AdaptiveSort, Base.DEFAULT_STABLE, Base.DEFAULT_UNSTABLE] + Base.DEFAULT_STABLE, Base.DEFAULT_UNSTABLE] @test sort(1:n, alg=alg, lt = (i,j) -> v[i]<=v[j]) == perm end @test partialsort(1:n, 172, lt = (i,j) -> v[i]<=v[j]) == perm[172] @@ -724,7 +718,7 @@ end # lt can be very poorly behaved and sort will still permute its input in some way. for alg in [InsertionSort, MergeSort, QuickSort, - Base.Sort.AdaptiveSort, Base.DEFAULT_STABLE, Base.DEFAULT_UNSTABLE] + Base.DEFAULT_STABLE, Base.DEFAULT_UNSTABLE] @test sort!(sort(v, alg=alg, lt = (x,y) -> rand([false, true]))) == s end @test partialsort(v, 172, lt = (x,y) -> rand([false, true])) ∈ 1:5 @@ -739,7 +733,6 @@ end @test issorted(k[idx], rev=true) end -# This testset is at the end of the file because it is slow @testset "sort(x; scratch)" begin for n in [1,10,100,1000] v = rand(n) @@ -770,6 +763,142 @@ end end end +@testset "Unions with missing" begin + @test issorted(sort(shuffle!(vcat(fill(missing, 10), rand(Int, 100))))) +end + +@testset "Specific algorithms" begin + let + requires_uint_mappable = Union{Base.Sort.RadixSort, Base.Sort.ConsiderRadixSort, + Base.Sort.CountingSort, Base.Sort.ConsiderCountingSort, + typeof(Base.Sort.DEFAULT_STABLE.next.next.big.next.yes), + typeof(Base.Sort.DEFAULT_STABLE.next.next.big.next.yes.big), + typeof(Base.Sort.DEFAULT_STABLE.next.next.big.next.yes.big.next)} + + function test_alg(kw, alg, float=true) + for order in [Base.Forward, Base.Reverse, Base.By(x -> x^2)] + order isa Base.By && alg isa requires_uint_mappable && continue + for n in [1,7,179,1312] + + n == 1 && alg isa Base.Sort.RadixSort && continue + + x = rand(1:n+1, n) + y = sort(x; order) + @test Base.Sort._sort!(x, alg, order, (;kw(y)...)) !== x + @test all(y .=== x) + + alg isa requires_uint_mappable && continue + + x = randn(n) + y = sort(x; order) + @test Base.Sort._sort!(x, alg, order, (;kw(y)...)) !== x + @test all(y .=== x) + end + end + end + test_alg(alg) = test_alg(x -> (), alg) + + function test_alg_rec(alg, extrema=false) + if extrema + test_alg(alg) do y + (;mn=first(y),mx=last(y)) + end + else + test_alg(alg) + end + extrema |= alg isa Base.Sort.ComputeExtrema + for name in fieldnames(typeof(alg)) + a = getfield(alg, name) + a isa Base.Sort.Algorithm && test_alg_rec(a, extrema) + end + end + + test_alg_rec(Base.DEFAULT_STABLE) + end +end + +@testset "show(::Algorithm)" begin + @test eval(Meta.parse(string(Base.DEFAULT_STABLE))) === Base.DEFAULT_STABLE + lines = split(string(Base.DEFAULT_STABLE), '\n') + @test 10 < maximum(length, lines) < 100 + @test 1 < length(lines) < 30 +end + +@testset "Extensibility" begin + # Defining new algorithms & backwards compatibility with packages that use sorting internals + + struct MyFirstAlg <: Base.Sort.Algorithm end + + @test_throws ArgumentError sort([1,2,3], alg=MyFirstAlg()) # not a stack overflow error + + v = shuffle(vcat(fill(missing, 10), rand(Int, 100))) + + # The pre 1.9 dispatch method + function Base.sort!(v::AbstractVector{Int}, lo::Integer, hi::Integer, ::MyFirstAlg, o::Base.Order.Ordering) + v[lo:hi] .= 7 + end + @test sort([1,2,3], alg=MyFirstAlg()) == [7,7,7] + @test all(sort(v, alg=Base.Sort.InitialOptimizations(MyFirstAlg())) .=== vcat(fill(7, 100), fill(missing, 10))) + + # Using the old hook with old entry-point + @test sort!([3,1,2], MyFirstAlg(), Base.Forward) == [7,7,7] + @test sort!([3,1,2], 1, 3, MyFirstAlg(), Base.Forward) == [7,7,7] + + # Use the pre 1.9 entry-point into the internals + function Base.sort!(v::AbstractVector{Int}, lo::Integer, hi::Integer, ::MyFirstAlg, o::Base.Order.Ordering) + sort!(v, lo, hi, Base.DEFAULT_STABLE, o) + end + @test sort([3,1,2], alg=MyFirstAlg()) == [1,2,3] + @test issorted(sort(v, alg=Base.Sort.InitialOptimizations(MyFirstAlg()))) + + # Another pre 1.9 entry-point into the internals + @test issorted(sort!(rand(100), InsertionSort, Base.Order.Forward)) + + struct MySecondAlg <: Base.Sort.Algorithm end + # A new dispatch method + function Base.Sort._sort!(v::AbstractVector, ::MySecondAlg, o::Base.Order.Ordering, kw) + Base.Sort.@getkw lo hi + v[lo:hi] .= 9 + end + @test sort([1,2,3], alg=MySecondAlg()) == [9,9,9] + @test all(sort(v, alg=Base.Sort.InitialOptimizations(MySecondAlg())) .=== vcat(fill(9, 100), fill(missing, 10))) +end + +@testset "sort!(v, lo, hi, alg, order)" begin + v = Vector{Float64}(undef, 4000) + for alg in [MergeSort, QuickSort, InsertionSort, Base.DEFAULT_STABLE, Base.DEFAULT_UNSTABLE] + rand!(v) + sort!(v, 1, 2000, alg, Base.Forward) + @test issorted(v[1:2000]) + @test !issorted(v) + + sort!(v, 2001, 4000, alg, Base.Forward) + @test issorted(v[1:2000]) + @test issorted(v[2001:4000]) + @test !issorted(v) + + sort!(v, 1001, 3000, alg, Base.Forward) + @test issorted(v[1:1000]) + @test issorted(v[1001:3000]) + @test issorted(v[3001:4000]) + @test !issorted(v[1:2000]) + @test !issorted(v[2001:4000]) + @test !issorted(v) + end +end + +@testset "IEEEFloatOptimization with -0.0" begin + x = vcat(round.(100 .* randn(1000)) ./ 100) # Also test lots of duplicates + x[rand(1:1000, 5)] .= 0.0 + x[rand(1:1000, 5)] .= -0.0 # To be sure that -0.0 is present + @test issorted(sort!(x)) +end + +@testset "Count sort near the edge of its range" begin + @test issorted(sort(rand(typemin(Int):typemin(Int)+100, 1000))) + @test issorted(sort(rand(typemax(Int)-100:typemax(Int), 1000))) +end + # This testset is at the end of the file because it is slow. @testset "searchsorted" begin numTypes = [ Int8, Int16, Int32, Int64, Int128, From d069fb2f89641ebe1002d2feee9074ead5476ce8 Mon Sep 17 00:00:00 2001 From: DilumAluthgeBot <43731525+DilumAluthgeBot@users.noreply.github.com> Date: Sat, 3 Dec 2022 11:55:36 -0500 Subject: [PATCH 065/387] =?UTF-8?q?=F0=9F=A4=96=20Bump=20the=20SparseArray?= =?UTF-8?q?s=20stdlib=20from=20311b4b4=20to=205f164a0=20(#47752)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dilum Aluthge --- .../md5 | 1 - .../sha512 | 1 - .../md5 | 1 + .../sha512 | 1 + stdlib/SparseArrays.version | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 deps/checksums/SparseArrays-311b4b4130d9f28a6b5eb55cb7c818e4f7858719.tar.gz/md5 delete mode 100644 deps/checksums/SparseArrays-311b4b4130d9f28a6b5eb55cb7c818e4f7858719.tar.gz/sha512 create mode 100644 deps/checksums/SparseArrays-5f164a06067d3efab49f1337d0f3662bbd9960f4.tar.gz/md5 create mode 100644 deps/checksums/SparseArrays-5f164a06067d3efab49f1337d0f3662bbd9960f4.tar.gz/sha512 diff --git a/deps/checksums/SparseArrays-311b4b4130d9f28a6b5eb55cb7c818e4f7858719.tar.gz/md5 b/deps/checksums/SparseArrays-311b4b4130d9f28a6b5eb55cb7c818e4f7858719.tar.gz/md5 deleted file mode 100644 index d0045196ae71d2..00000000000000 --- a/deps/checksums/SparseArrays-311b4b4130d9f28a6b5eb55cb7c818e4f7858719.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -87c15983360c6167f3e47dca72b5d1b9 diff --git a/deps/checksums/SparseArrays-311b4b4130d9f28a6b5eb55cb7c818e4f7858719.tar.gz/sha512 b/deps/checksums/SparseArrays-311b4b4130d9f28a6b5eb55cb7c818e4f7858719.tar.gz/sha512 deleted file mode 100644 index 5d819a83649990..00000000000000 --- a/deps/checksums/SparseArrays-311b4b4130d9f28a6b5eb55cb7c818e4f7858719.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -9c08116308495a485a600ff31b07fe1e55d2986494ee4a03708dad43c1897df5243a406f0fd9abcaaeebdc28a0f966cb807c0ff6efa3b3dd96e9c1977988f96f diff --git a/deps/checksums/SparseArrays-5f164a06067d3efab49f1337d0f3662bbd9960f4.tar.gz/md5 b/deps/checksums/SparseArrays-5f164a06067d3efab49f1337d0f3662bbd9960f4.tar.gz/md5 new file mode 100644 index 00000000000000..eef5429730f5b9 --- /dev/null +++ b/deps/checksums/SparseArrays-5f164a06067d3efab49f1337d0f3662bbd9960f4.tar.gz/md5 @@ -0,0 +1 @@ +6dadec6acd00f7fc91ab5e0dd8d619ca diff --git a/deps/checksums/SparseArrays-5f164a06067d3efab49f1337d0f3662bbd9960f4.tar.gz/sha512 b/deps/checksums/SparseArrays-5f164a06067d3efab49f1337d0f3662bbd9960f4.tar.gz/sha512 new file mode 100644 index 00000000000000..6d6c0c372c9806 --- /dev/null +++ b/deps/checksums/SparseArrays-5f164a06067d3efab49f1337d0f3662bbd9960f4.tar.gz/sha512 @@ -0,0 +1 @@ +b954e8e2ff1098cac9c97d5d331c3a1f722299da692ba1a44e19b2dfd7eec4cd6a062ce0bc48b698016e701d632579e663df23b0ec14fa71cf7f9c59f9351945 diff --git a/stdlib/SparseArrays.version b/stdlib/SparseArrays.version index 0bc6587c6a0ee9..a5f9a0d71d6971 100644 --- a/stdlib/SparseArrays.version +++ b/stdlib/SparseArrays.version @@ -1,4 +1,4 @@ SPARSEARRAYS_BRANCH = main -SPARSEARRAYS_SHA1 = 311b4b4130d9f28a6b5eb55cb7c818e4f7858719 +SPARSEARRAYS_SHA1 = 5f164a06067d3efab49f1337d0f3662bbd9960f4 SPARSEARRAYS_GIT_URL := https://github.com/JuliaSparse/SparseArrays.jl.git SPARSEARRAYS_TAR_URL = https://api.github.com/repos/JuliaSparse/SparseArrays.jl/tarball/$1 From 8cbe1297f98aa477da5d98ebfaa4027205b35440 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Sat, 3 Dec 2022 21:18:07 +0200 Subject: [PATCH 066/387] check that file exist in whitespace checker before trying to read it (#47773) --- contrib/check-whitespace.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/check-whitespace.jl b/contrib/check-whitespace.jl index 4d078d400daea4..a000370026eae8 100755 --- a/contrib/check-whitespace.jl +++ b/contrib/check-whitespace.jl @@ -27,6 +27,7 @@ for path in eachline(`git ls-files -- $patterns`) file_err(msg) = push!(errors, (path, 0, msg)) line_err(msg) = push!(errors, (path, lineno, msg)) + isfile(path) || continue for line in eachline(path, keep=true) lineno += 1 contains(line, '\r') && file_err("non-UNIX line endings") From 327b7acb8da726fcafec37a388fa58132a3032ce Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Sat, 3 Dec 2022 18:26:47 -0500 Subject: [PATCH 067/387] Comment out test in subtype that causes hang due to StackOverflow(#47792) --- test/subtype.jl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/subtype.jl b/test/subtype.jl index 23aabf38e4fa1a..70f3dd864cdbed 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -2201,11 +2201,12 @@ T46784{B<:Val, M<:AbstractMatrix} = Tuple{<:Union{B, <:Val{<:B}}, M, Union{Abstr @testset "known subtype/intersect issue" begin #issue 45874 - let S = Pair{Val{P}, AbstractVector{<:Union{P,<:AbstractMatrix{P}}}} where P, - T = Pair{Val{R}, AbstractVector{<:Union{P,<:AbstractMatrix{P}}}} where {P,R} - @test_broken S <: T - @test_broken typeintersect(S,T) === S - end + # Causes a hang due to jl_critical_error calling back into malloc... + # let S = Pair{Val{P}, AbstractVector{<:Union{P,<:AbstractMatrix{P}}}} where P, + # T = Pair{Val{R}, AbstractVector{<:Union{P,<:AbstractMatrix{P}}}} where {P,R} + # @test_broken S <: T + # @test_broken typeintersect(S,T) === S + # end #issue 44395 @test_broken typeintersect( From 0feaf5cc3a6cec0a4f056e4e72ed6469769268a4 Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Sat, 3 Dec 2022 20:46:27 -0500 Subject: [PATCH 068/387] Prioritize build_dir for generated headers (#47783) --- src/julia.h | 6 ++++-- src/julia_internal.h | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/julia.h b/src/julia.h index c79f7d0d1303a7..c40d3ce3b88e5e 100644 --- a/src/julia.h +++ b/src/julia.h @@ -4,7 +4,9 @@ #define JULIA_H #ifdef LIBRARY_EXPORTS -#include "jl_internal_funcs.inc" +// Generated file, needs to be searched in include paths so that the builddir +// retains priority +#include #undef jl_setjmp #undef jl_longjmp #undef jl_egal @@ -2190,7 +2192,7 @@ JL_DLLEXPORT int jl_generating_output(void) JL_NOTSAFEPOINT; #define JL_OPTIONS_USE_COMPILED_MODULES_NO 0 // Version information -#include "julia_version.h" +#include // Generated file JL_DLLEXPORT extern int jl_ver_major(void); JL_DLLEXPORT extern int jl_ver_minor(void); diff --git a/src/julia_internal.h b/src/julia_internal.h index 6e3b036177c7b7..c4ac3e2b350099 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -1684,7 +1684,9 @@ JL_DLLEXPORT uint32_t jl_crc32c(uint32_t crc, const char *buf, size_t len); #endif #ifdef USE_DTRACE -#include "uprobes.h.gen" +// Generated file, needs to be searched in include paths so that the builddir +// retains priority +#include // uprobes.h.gen on systems with DTrace, is auto-generated to include // `JL_PROBE_{PROBE}` and `JL_PROBE_{PROBE}_ENABLED()` macros for every probe From ec8d014a6181a5266ee656e893d5769a7c203b5a Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Mon, 5 Dec 2022 20:11:20 +0900 Subject: [PATCH 069/387] allow `Base._which` to take `method_table::CC.MethodTableView` (#47801) Might be useful for some external `AbstractInterpreter` implementation. --- base/compiler/compiler.jl | 3 +++ base/compiler/methodtable.jl | 2 -- base/reflection.jl | 6 ++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/base/compiler/compiler.jl b/base/compiler/compiler.jl index db14dbb07f6e9e..dae06d3e5b27f5 100644 --- a/base/compiler/compiler.jl +++ b/base/compiler/compiler.jl @@ -31,6 +31,9 @@ macro noinline() Expr(:meta, :noinline) end convert(::Type{Any}, Core.@nospecialize x) = x convert(::Type{T}, x::T) where {T} = x +# mostly used by compiler/methodtable.jl, but also by reflection.jl +abstract type MethodTableView end + # essential files and libraries include("essentials.jl") include("ctypes.jl") diff --git a/base/compiler/methodtable.jl b/base/compiler/methodtable.jl index c3def0879f2ed1..4456bd600fecc5 100644 --- a/base/compiler/methodtable.jl +++ b/base/compiler/methodtable.jl @@ -1,7 +1,5 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -abstract type MethodTableView; end - struct MethodLookupResult # Really Vector{Core.MethodMatch}, but it's easier to represent this as # and work with Vector{Any} on the C side. diff --git a/base/reflection.jl b/base/reflection.jl index 18b0b81b3236ef..0157616a4b8e3d 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1498,13 +1498,15 @@ end print_statement_costs(args...; kwargs...) = print_statement_costs(stdout, args...; kwargs...) function _which(@nospecialize(tt::Type); - method_table::Union{Nothing,Core.MethodTable}=nothing, + method_table::Union{Nothing,Core.MethodTable,Core.Compiler.MethodTableView}=nothing, world::UInt=get_world_counter(), raise::Bool=true) if method_table === nothing table = Core.Compiler.InternalMethodTable(world) - else + elseif method_table isa Core.MethodTable table = Core.Compiler.OverlayMethodTable(world, method_table) + else + table = method_table end match, = Core.Compiler.findsup(tt, table) if match === nothing From 1a55d60677ac5e7c3301bc481fe3e87287b5c875 Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Mon, 5 Dec 2022 16:25:11 +0100 Subject: [PATCH 070/387] Remove obsolete methods/type restrictions in LinAlg (#47754) --- stdlib/LinearAlgebra/src/hessenberg.jl | 12 ++--- stdlib/LinearAlgebra/src/special.jl | 67 +++++--------------------- stdlib/LinearAlgebra/src/tridiag.jl | 5 +- stdlib/LinearAlgebra/test/special.jl | 13 ++++- 4 files changed, 31 insertions(+), 66 deletions(-) diff --git a/stdlib/LinearAlgebra/src/hessenberg.jl b/stdlib/LinearAlgebra/src/hessenberg.jl index c7c4c3a50a2398..cbdb6e6150a591 100644 --- a/stdlib/LinearAlgebra/src/hessenberg.jl +++ b/stdlib/LinearAlgebra/src/hessenberg.jl @@ -173,19 +173,15 @@ function *(B::Bidiagonal, H::UpperHessenberg) return B.uplo == 'U' ? UpperHessenberg(A) : A end -/(H::UpperHessenberg, B::Bidiagonal) = _rdiv(H, B) -/(H::UpperHessenberg{<:Number}, B::Bidiagonal{<:Number}) = _rdiv(H, B) -function _rdiv(H::UpperHessenberg, B::Bidiagonal) +function /(H::UpperHessenberg, B::Bidiagonal) T = typeof(oneunit(eltype(H))/oneunit(eltype(B))) - A = _rdiv!(zeros(T, size(H)), H, B) + A = _rdiv!(similar(H, T, size(H)), H, B) return B.uplo == 'U' ? UpperHessenberg(A) : A end -\(B::Bidiagonal{<:Number}, H::UpperHessenberg{<:Number}) = _ldiv(B, H) -\(B::Bidiagonal, H::UpperHessenberg) = _ldiv(B, H) -function _ldiv(B::Bidiagonal, H::UpperHessenberg) +function \(B::Bidiagonal, H::UpperHessenberg) T = typeof(oneunit(eltype(B))\oneunit(eltype(H))) - A = ldiv!(zeros(T, size(H)), B, H) + A = ldiv!(similar(H, T, size(H)), B, H) return B.uplo == 'U' ? UpperHessenberg(A) : A end diff --git a/stdlib/LinearAlgebra/src/special.jl b/stdlib/LinearAlgebra/src/special.jl index 8af8625a0e8170..d208e80c6c5b17 100644 --- a/stdlib/LinearAlgebra/src/special.jl +++ b/stdlib/LinearAlgebra/src/special.jl @@ -225,66 +225,21 @@ function (-)(A::SymTridiagonal, B::Bidiagonal) Tridiagonal((B.uplo == 'U' ? (typeof(newdv)(_evview(A)), newdv, _evview(A)-B.ev) : (_evview(A)-B.ev, newdv, typeof(newdv)(_evview(A))))...) end -# fixing uniform scaling problems from #28994 -# {<:Number} is required due to the test case from PR #27289 where eltype is a matrix. - -function (+)(A::Tridiagonal{<:Number}, B::UniformScaling) - newd = A.d .+ B.λ - Tridiagonal(typeof(newd)(A.dl), newd, typeof(newd)(A.du)) -end - -function (+)(A::SymTridiagonal{<:Number}, B::UniformScaling) - newdv = A.dv .+ B.λ - SymTridiagonal(newdv, typeof(newdv)(A.ev)) -end - -function (+)(A::Bidiagonal{<:Number}, B::UniformScaling) - newdv = A.dv .+ B.λ - Bidiagonal(newdv, typeof(newdv)(A.ev), A.uplo) -end - -function (+)(A::Diagonal{<:Number}, B::UniformScaling) - Diagonal(A.diag .+ B.λ) -end - -function (+)(A::UniformScaling, B::Tridiagonal{<:Number}) - newd = A.λ .+ B.d - Tridiagonal(typeof(newd)(B.dl), newd, typeof(newd)(B.du)) -end - -function (+)(A::UniformScaling, B::SymTridiagonal{<:Number}) - newdv = A.λ .+ B.dv - SymTridiagonal(newdv, typeof(newdv)(B.ev)) +function (-)(A::UniformScaling, B::Tridiagonal) + d = Ref(A) .- B.d + Tridiagonal(convert(typeof(d), -B.dl), d, convert(typeof(d), -B.du)) end - -function (+)(A::UniformScaling, B::Bidiagonal{<:Number}) - newdv = A.λ .+ B.dv - Bidiagonal(newdv, typeof(newdv)(B.ev), B.uplo) +function (-)(A::UniformScaling, B::SymTridiagonal) + dv = Ref(A) .- B.dv + SymTridiagonal(dv, convert(typeof(dv), -B.ev)) end - -function (+)(A::UniformScaling, B::Diagonal{<:Number}) - Diagonal(A.λ .+ B.diag) +function (-)(A::UniformScaling, B::Bidiagonal) + dv = Ref(A) .- B.dv + Bidiagonal(dv, convert(typeof(dv), -B.ev), B.uplo) end - -function (-)(A::UniformScaling, B::Tridiagonal{<:Number}) - newd = A.λ .- B.d - Tridiagonal(typeof(newd)(-B.dl), newd, typeof(newd)(-B.du)) -end - -function (-)(A::UniformScaling, B::SymTridiagonal{<:Number}) - newdv = A.λ .- B.dv - SymTridiagonal(newdv, typeof(newdv)(-B.ev)) +function (-)(A::UniformScaling, B::Diagonal) + Diagonal(Ref(A) .- B.diag) end - -function (-)(A::UniformScaling, B::Bidiagonal{<:Number}) - newdv = A.λ .- B.dv - Bidiagonal(newdv, typeof(newdv)(-B.ev), B.uplo) -end - -function (-)(A::UniformScaling, B::Diagonal{<:Number}) - Diagonal(A.λ .- B.diag) -end - lmul!(Q::AbstractQ, B::AbstractTriangular) = lmul!(Q, full!(B)) lmul!(Q::QRPackedQ, B::AbstractTriangular) = lmul!(Q, full!(B)) # disambiguation lmul!(Q::Adjoint{<:Any,<:AbstractQ}, B::AbstractTriangular) = lmul!(Q, full!(B)) diff --git a/stdlib/LinearAlgebra/src/tridiag.jl b/stdlib/LinearAlgebra/src/tridiag.jl index 8821dc064a960f..2739400bb393c1 100644 --- a/stdlib/LinearAlgebra/src/tridiag.jl +++ b/stdlib/LinearAlgebra/src/tridiag.jl @@ -213,7 +213,10 @@ end *(B::Number, A::SymTridiagonal) = SymTridiagonal(B*A.dv, B*A.ev) /(A::SymTridiagonal, B::Number) = SymTridiagonal(A.dv/B, A.ev/B) \(B::Number, A::SymTridiagonal) = SymTridiagonal(B\A.dv, B\A.ev) -==(A::SymTridiagonal, B::SymTridiagonal) = (A.dv==B.dv) && (_evview(A)==_evview(B)) +==(A::SymTridiagonal{<:Number}, B::SymTridiagonal{<:Number}) = + (A.dv == B.dv) && (_evview(A) == _evview(B)) +==(A::SymTridiagonal, B::SymTridiagonal) = + size(A) == size(B) && all(i -> A[i,i] == B[i,i], axes(A, 1)) && (_evview(A) == _evview(B)) function dot(x::AbstractVector, S::SymTridiagonal, y::AbstractVector) require_one_based_indexing(x, y) diff --git a/stdlib/LinearAlgebra/test/special.jl b/stdlib/LinearAlgebra/test/special.jl index 78cbf655933cd3..465c9ad5a4951d 100644 --- a/stdlib/LinearAlgebra/test/special.jl +++ b/stdlib/LinearAlgebra/test/special.jl @@ -191,7 +191,7 @@ end push!(mats, SymTridiagonal(Vector{T}(diag), Vector{T}(offdiag))) end - for op in (+,*) # to do: fix when operation is - and the matrix has a range as the underlying representation and we get a step size of 0. + for op in (+,-,*) for A in mats for B in mats @test (op)(A, B) ≈ (op)(Matrix(A), Matrix(B)) ≈ Matrix((op)(A, B)) @@ -206,6 +206,17 @@ end end end end + diag = [randn(ComplexF64, 2, 2) for _ in 1:3] + odiag = [randn(ComplexF64, 2, 2) for _ in 1:2] + for A in (Diagonal(diag), + Bidiagonal(diag, odiag, :U), + Bidiagonal(diag, odiag, :L), + Tridiagonal(odiag, diag, odiag), + SymTridiagonal(diag, odiag)), B in uniformscalingmats + @test (A + B)::typeof(A) == (B + A)::typeof(A) + @test (A - B)::typeof(A) == ((A + (-B))::typeof(A)) + @test (B - A)::typeof(A) == ((B + (-A))::typeof(A)) + end end From e5a82fb0488f69c7bee0c0c508a350e99f979f1e Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Mon, 5 Dec 2022 11:23:32 -0500 Subject: [PATCH 071/387] Fix gcext test (#47802) --- test/gcext/gcext.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/gcext/gcext.c b/test/gcext/gcext.c index 842d6004ab9656..90b5ee82d80b50 100644 --- a/test/gcext/gcext.c +++ b/test/gcext/gcext.c @@ -612,8 +612,7 @@ int main() jl_gc_set_cb_root_scanner(abort_with_error, 1); jl_gc_set_cb_root_scanner(abort_with_error, 0); // Create module to store types in. - module = jl_new_module(jl_symbol("TestGCExt")); - module->parent = jl_main_module; + module = jl_new_module(jl_symbol("TestGCExt"), jl_main_module); jl_set_const(jl_main_module, jl_symbol("TestGCExt"), (jl_value_t *)module); // Define Julia types for our stack implementation. datatype_stack = jl_new_foreign_type( From a8b399416208d91061324814ff8ae080a918e48b Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Mon, 5 Dec 2022 14:40:25 -0500 Subject: [PATCH 072/387] Set `OPENBLAS_NUM_THREADS=1` on local Distributed workers (#47803) This should prevent LinearAlgebra from trying to increase our OpenBLAS thread count in its `__init__()` method when we're not trying to enable threaded BLAS. --- stdlib/Distributed/src/managers.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/stdlib/Distributed/src/managers.jl b/stdlib/Distributed/src/managers.jl index 03adfd1371d15c..57f58598e85dcf 100644 --- a/stdlib/Distributed/src/managers.jl +++ b/stdlib/Distributed/src/managers.jl @@ -487,6 +487,13 @@ function launch(manager::LocalManager, params::Dict, launched::Array, c::Conditi if get(env, "JULIA_DEPOT_PATH", nothing) === nothing env["JULIA_DEPOT_PATH"] = join(DEPOT_PATH, pathsep) end + + # If we haven't explicitly asked for threaded BLAS, prevent OpenBLAS from starting + # up with multiple threads, thereby sucking up a bunch of wasted memory on Windows. + if !params[:enable_threaded_blas] && + get(env, "OPENBLAS_NUM_THREADS", nothing) === nothing + env["OPENBLAS_NUM_THREADS"] = "1" + end # Set the active project on workers using JULIA_PROJECT. # Users can opt-out of this by (i) passing `env = ...` or (ii) passing # `--project=...` as `exeflags` to addprocs(...). From 92bb4f09c8ad740dc5e204892a881c58720d6359 Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Mon, 5 Dec 2022 14:56:05 -0500 Subject: [PATCH 073/387] Change `@test ex broken=true` to error if `ex` evaluates to non-boolean Just like `@test ex` errors if `ex` does not evaluable to a boolean, the `broken=true` variant should as well. As it stands, right now the following does not result in an error: ``` @test 1 broken=true ``` While the following does result in an error: ``` @test 1 ``` The intention of `broken=true` is to allow for tests to be added such that they pass although something is broken, and then fail once the missing functionality has been added, so that the developer knows to go and adjust the tests for the newly-added functionality. It is often used in situations such as: ``` @test partially_implemented_test() broken=Sys.iswindows() ``` Sometimes, the function under test throws an error in certain situations, and this test can even be used to track a function erroring out, and notifying the developer when it no longer throws: ``` @test this_throws() broken=true ``` However, this occasionally leads to improper usage such as: ``` @test this_usually_returns_an_object_but_sometimes_throws() broken=true ``` This test, when it throws, passes (because of `broken=true`) but then when it returns a non-boolean, it _also_ passes. This is Very Bad (TM). This PR fixes the state of affairs by making a non-boolean return an error here. --- stdlib/Test/src/Test.jl | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index c19d131781b8fe..b59b7e5554e9c8 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -674,8 +674,13 @@ function do_broken_test(result::ExecutionResult, orig_expr) # Assume the test is broken and only change if the result is true if isa(result, Returned) value = result.value - if isa(value, Bool) && value - testres = Error(:test_unbroken, orig_expr, value, nothing, result.source) + if isa(value, Bool) + if value + testres = Error(:test_unbroken, orig_expr, value, nothing, result.source) + end + else + # If the result is non-Boolean, this counts as an Error + testres = Error(:test_nonbool, orig_expr, value, nothing, result.source) end end record(get_testset(), testres) From c3bf18013a9a86a46b69077ae9230e472b942f05 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Tue, 6 Dec 2022 20:37:38 +0900 Subject: [PATCH 074/387] don't override line info when code generation gives it explicitly (#47750) * respect given line info when code generation produces `CodeInfo` This should improve a readability of stacktrace for code generated by Cassette-like mechanism that produces `CodeInfo` from `@generated` function. * add test --- src/method.c | 6 ++++-- test/staged.jl | 12 ++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/method.c b/src/method.c index d0485f239824b5..098c5df5aed98f 100644 --- a/src/method.c +++ b/src/method.c @@ -504,7 +504,10 @@ void jl_add_function_name_to_lineinfo(jl_code_info_t *ci, jl_value_t *name) jl_value_t *file = jl_fieldref_noalloc(ln, 2); lno = jl_fieldref(ln, 3); inl = jl_fieldref(ln, 4); - jl_value_t *ln_name = (jl_is_int32(inl) && jl_unbox_int32(inl) == 0) ? name : jl_fieldref_noalloc(ln, 1); + // respect a given linetable if available + jl_value_t *ln_name = jl_fieldref_noalloc(ln, 1); + if (jl_is_symbol(ln_name) && (jl_sym_t*)ln_name == jl_symbol("none") && jl_is_int32(inl) && jl_unbox_int32(inl) == 0) + ln_name = name; rt = jl_new_struct(jl_lineinfonode_type, mod, ln_name, file, lno, inl); jl_array_ptr_set(li, i, rt); } @@ -587,7 +590,6 @@ JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo) else { // Lower the user's expression and resolve references to the type parameters func = jl_expand_and_resolve(ex, def->module, linfo->sparam_vals); - if (!jl_is_code_info(func)) { if (jl_is_expr(func) && ((jl_expr_t*)func)->head == jl_error_sym) { ct->ptls->in_pure_callback = 0; diff --git a/test/staged.jl b/test/staged.jl index 8c260c0048acd9..4a7fa3d7f4c845 100644 --- a/test/staged.jl +++ b/test/staged.jl @@ -320,3 +320,15 @@ let tup = g_vararg_generated() # shifts everything over by 1) @test only(code_lowered(only(methods(f_vararg_generated)).generator.gen)).slotflags[5] == UInt8(0x00) end + +# respect a given linetable in code generation +# https://github.com/JuliaLang/julia/pull/47750 +let match = Base._which(Tuple{typeof(sin),Int}) + mi = Core.Compiler.specialize_method(match) + lwr = Core.Compiler.retrieve_code_info(mi) + @test all(lin->lin.method===:sin, lwr.linetable) + @generated sin_generated(a) = lwr + src = only(code_lowered(sin_generated, (Int,))) + @test all(lin->lin.method===:sin, src.linetable) + @test sin_generated(42) == sin(42) +end From cf5ae0369ceae078cf6a29d7aa34f48a5a53531e Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Tue, 6 Dec 2022 09:49:40 -0300 Subject: [PATCH 075/387] Add native julia fmod (#47501) * Add native julia rem Co-authored-by: Alex Arslan --- base/float.jl | 106 ++++++++++++++++++++++++++++++++++++++++- test/numbers.jl | 123 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 227 insertions(+), 2 deletions(-) diff --git a/base/float.jl b/base/float.jl index 75a2e0fcacc448..6109710d7a851d 100644 --- a/base/float.jl +++ b/base/float.jl @@ -101,6 +101,8 @@ exponent_one(::Type{Float16}) = 0x3c00 exponent_half(::Type{Float16}) = 0x3800 significand_mask(::Type{Float16}) = 0x03ff +mantissa(x::T) where {T} = reinterpret(Unsigned, x) & significand_mask(T) + for T in (Float16, Float32, Float64) @eval significand_bits(::Type{$T}) = $(trailing_ones(significand_mask(T))) @eval exponent_bits(::Type{$T}) = $(sizeof(T)*8 - significand_bits(T) - 1) @@ -414,9 +416,109 @@ muladd(x::T, y::T, z::T) where {T<:IEEEFloat} = muladd_float(x, y, z) # TODO: faster floating point fld? # TODO: faster floating point mod? -rem(x::T, y::T) where {T<:IEEEFloat} = rem_float(x, y) +function unbiased_exponent(x::T) where {T<:IEEEFloat} + return (reinterpret(Unsigned, x) & exponent_mask(T)) >> significand_bits(T) +end + +function explicit_mantissa_noinfnan(x::T) where {T<:IEEEFloat} + m = mantissa(x) + issubnormal(x) || (m |= significand_mask(T) + uinttype(T)(1)) + return m +end + +function _to_float(number::U, ep) where {U<:Unsigned} + F = floattype(U) + S = signed(U) + epint = unsafe_trunc(S,ep) + lz::signed(U) = unsafe_trunc(S, Core.Intrinsics.ctlz_int(number) - U(exponent_bits(F))) + number <<= lz + epint -= lz + bits = U(0) + if epint >= 0 + bits = number & significand_mask(F) + bits |= ((epint + S(1)) << significand_bits(F)) & exponent_mask(F) + else + bits = (number >> -epint) & significand_mask(F) + end + return reinterpret(F, bits) +end + +@assume_effects :terminates_locally :nothrow function rem_internal(x::T, y::T) where {T<:IEEEFloat} + xuint = reinterpret(Unsigned, x) + yuint = reinterpret(Unsigned, y) + if xuint <= yuint + if xuint < yuint + return x + end + return zero(T) + end + + e_x = unbiased_exponent(x) + e_y = unbiased_exponent(y) + # Most common case where |y| is "very normal" and |x/y| < 2^EXPONENT_WIDTH + if e_y > (significand_bits(T)) && (e_x - e_y) <= (exponent_bits(T)) + m_x = explicit_mantissa_noinfnan(x) + m_y = explicit_mantissa_noinfnan(y) + d = urem_int((m_x << (e_x - e_y)), m_y) + iszero(d) && return zero(T) + return _to_float(d, e_y - uinttype(T)(1)) + end + # Both are subnormals + if e_x == 0 && e_y == 0 + return reinterpret(T, urem_int(xuint, yuint) & significand_mask(T)) + end + + m_x = explicit_mantissa_noinfnan(x) + e_x -= uinttype(T)(1) + m_y = explicit_mantissa_noinfnan(y) + lz_m_y = uinttype(T)(exponent_bits(T)) + if e_y > 0 + e_y -= uinttype(T)(1) + else + m_y = mantissa(y) + lz_m_y = Core.Intrinsics.ctlz_int(m_y) + end + + tz_m_y = Core.Intrinsics.cttz_int(m_y) + sides_zeroes_cnt = lz_m_y + tz_m_y + + # n>0 + exp_diff = e_x - e_y + # Shift hy right until the end or n = 0 + right_shift = min(exp_diff, tz_m_y) + m_y >>= right_shift + exp_diff -= right_shift + e_y += right_shift + # Shift hx left until the end or n = 0 + left_shift = min(exp_diff, uinttype(T)(exponent_bits(T))) + m_x <<= left_shift + exp_diff -= left_shift + + m_x = urem_int(m_x, m_y) + iszero(m_x) && return zero(T) + iszero(exp_diff) && return _to_float(m_x, e_y) + + while exp_diff > sides_zeroes_cnt + exp_diff -= sides_zeroes_cnt + m_x <<= sides_zeroes_cnt + m_x = urem_int(m_x, m_y) + end + m_x <<= exp_diff + m_x = urem_int(m_x, m_y) + return _to_float(m_x, e_y) +end + +function rem(x::T, y::T) where {T<:IEEEFloat} + if isfinite(x) && !iszero(x) && isfinite(y) && !iszero(y) + return copysign(rem_internal(abs(x), abs(y)), x) + elseif isinf(x) || isnan(y) || iszero(y) # y can still be Inf + return T(NaN) + else + return x + end +end -function mod(x::T, y::T) where T<:AbstractFloat +function mod(x::T, y::T) where {T<:AbstractFloat} r = rem(x,y) if r == 0 copysign(r,y) diff --git a/test/numbers.jl b/test/numbers.jl index 70f5f6f346d306..870acd37c089cd 100644 --- a/test/numbers.jl +++ b/test/numbers.jl @@ -2929,3 +2929,126 @@ end @test false == ceil(Bool, -0.7) end end + +@testset "modf" begin + @testset "remd" begin + denorm_min = nextfloat(0.0) + minfloat = floatmin(Float64) + maxfloat = floatmax(Float64) + values = [3.0,denorm_min,-denorm_min, minfloat, + -minfloat, maxfloat, -maxfloat] + # rem (0, y) == 0 for y != 0. + for val in values + @test isequal(rem(0.0, val), 0.0) + end + # rem (-0, y) == -0 for y != 0. + for val in values + @test isequal(rem(-0.0, val), -0.0) + end + # rem (+Inf, y) == NaN + values2 = [3.0,-1.1,0.0,-0.0,denorm_min,minfloat, + maxfloat,Inf,-Inf] + for val in values2 + @test isequal(rem(Inf, val), NaN) + end + # rem (-Inf, y) == NaN + for val in values2 + @test isequal(rem(-Inf, val), NaN) + end + # rem (x, +0) == NaN + values3 = values2[begin:end-2] + for val in values3 + @test isequal(rem(val, 0.0), NaN) + end + # rem (x, -0) == NaN + for val in values3 + @test isequal(rem(val, -0.0), NaN) + end + # rem (x, +Inf) == x for x not infinite. + @test isequal(rem(0.0, Inf), 0.0) + @test isequal(rem(-0.0, Inf), -0.0) + @test isequal(rem(denorm_min, Inf), denorm_min) + @test isequal(rem(minfloat, Inf), minfloat) + @test isequal(rem(maxfloat, Inf), maxfloat) + @test isequal(rem(3.0, Inf), 3.0) + # rem (x, -Inf) == x for x not infinite. + @test isequal(rem(0.0, -Inf), 0.0) + @test isequal(rem(-0.0, -Inf), -0.0) + @test isequal(rem(denorm_min, -Inf), denorm_min) + @test isequal(rem(minfloat, -Inf), minfloat) + @test isequal(rem(maxfloat, -Inf), maxfloat) + @test isequal(rem(3.0, -Inf), 3.0) + #NaN tests + @test isequal(rem(0.0, NaN), NaN) + @test isequal(rem(1.0, NaN), NaN) + @test isequal(rem(Inf, NaN), NaN) + @test isequal(rem(NaN, 0.0), NaN) + @test isequal(rem(NaN, 1.0), NaN) + @test isequal(rem(NaN, Inf), NaN) + @test isequal(rem(NaN, NaN), NaN) + #Sign tests + @test isequal(rem(6.5, 2.25), 2.0) + @test isequal(rem(-6.5, 2.25), -2.0) + @test isequal(rem(6.5, -2.25), 2.0) + @test isequal(rem(-6.5, -2.25), -2.0) + values4 = [maxfloat,-maxfloat,minfloat,-minfloat, + denorm_min, -denorm_min] + for val in values4 + @test isequal(rem(maxfloat,val), 0.0) + end + for val in values4 + @test isequal(rem(-maxfloat,val), -0.0) + end + @test isequal(rem(minfloat, maxfloat), minfloat) + @test isequal(rem(minfloat, -maxfloat), minfloat) + values5 = values4[begin+2:end] + for val in values5 + @test isequal(rem(minfloat,val), 0.0) + end + @test isequal(rem(-minfloat, maxfloat), -minfloat) + @test isequal(rem(-minfloat, -maxfloat), -minfloat) + for val in values5 + @test isequal(rem(-minfloat,val), -0.0) + end + values6 = values4[begin:end-2] + for val in values6 + @test isequal(rem(denorm_min,val), denorm_min) + end + @test isequal(rem(denorm_min, denorm_min), 0.0) + @test isequal(rem(denorm_min, -denorm_min), 0.0) + for val in values6 + @test isequal(rem(-denorm_min,val), -denorm_min) + end + @test isequal(rem(-denorm_min, denorm_min), -0.0) + @test isequal(rem(-denorm_min, -denorm_min), -0.0) + #Max value tests + values7 = [0x3p-1074,-0x3p-1074,0x3p-1073,-0x3p-1073] + for val in values7 + @test isequal(rem(0x1p1023,val), 0x1p-1073) + end + @test isequal(rem(0x1p1023, 0x3p-1022), 0x1p-1021) + @test isequal(rem(0x1p1023, -0x3p-1022), 0x1p-1021) + for val in values7 + @test isequal(rem(-0x1p1023,val), -0x1p-1073) + end + @test isequal(rem(-0x1p1023, 0x3p-1022), -0x1p-1021) + @test isequal(rem(-0x1p1023, -0x3p-1022), -0x1p-1021) + + end + + @testset "remf" begin + @test isequal(rem(Float32(0x1p127), Float32(0x3p-149)), Float32(0x1p-149)) + @test isequal(rem(Float32(0x1p127), -Float32(0x3p-149)), Float32(0x1p-149)) + @test isequal(rem(Float32(0x1p127), Float32(0x3p-148)), Float32(0x1p-147)) + @test isequal(rem(Float32(0x1p127), -Float32(0x3p-148)), Float32(0x1p-147)) + @test isequal(rem(Float32(0x1p127), Float32(0x3p-126)), Float32(0x1p-125)) + @test isequal(rem(Float32(0x1p127), -Float32(0x3p-126)), Float32(0x1p-125)) + @test isequal(rem(-Float32(0x1p127), Float32(0x3p-149)), -Float32(0x1p-149)) + @test isequal(rem(-Float32(0x1p127), -Float32(0x3p-149)), -Float32(0x1p-149)) + @test isequal(rem(-Float32(0x1p127), Float32(0x3p-148)), -Float32(0x1p-147)) + @test isequal(rem(-Float32(0x1p127), -Float32(0x3p-148)), -Float32(0x1p-147)) + @test isequal(rem(-Float32(0x1p127), Float32(0x3p-126)), -Float32(0x1p-125)) + @test isequal(rem(-Float32(0x1p127), -Float32(0x3p-126)), -Float32(0x1p-125)) + end + +end From 63830a6f2050e61b7b2aca78e2462487fd3f59d0 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Tue, 6 Dec 2022 12:15:39 -0700 Subject: [PATCH 076/387] Generalize Bool parse method to AbstractString (#47782) * Generalize Bool parse method to AbstractString Fixes https://github.com/JuliaStrings/InlineStrings.jl/issues/57. We currently have a specialization for `parse(Bool, ::Union{String, SubString{String})` where `true` and `false` are parsed appropriately. The restriction to `Union{String, SubString{String}}`, however, means we don't get this behavior for other `AbstractString`s. In the linked issue above, for InlineStrings, we end up going through the generic integer parsing codepath which results in an `InexactError` when we try to do `Bool(10)`. The proposal in this PR takes advantage of the fact that there is only the 2 comparisons where we do `_memcmp` that require the input string to be "dense" (in memory), and otherwise, we just do a comparison against a `SubString` of the input string. Relatedly, I've wanted to introduce the concept of an abstrac type like: ```julia abstract type MemoryAddressableString <: AbstractString ``` where the additional required interface would be being able to call `pointer(::MemoryAddressableString)`, since a lot of our string algorithms depend on doing these kind of pointer operations and hence makes it quite a pain to implement your own custom string type. * Apply suggestions from code review Co-authored-by: Stefan Karpinski Co-authored-by: Nick Robinson Co-authored-by: Stefan Karpinski Co-authored-by: Nick Robinson --- base/parse.jl | 15 ++++++++++----- test/parse.jl | 10 ++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/base/parse.jl b/base/parse.jl index 1c911c96e1479c..e5b3d2ae3bc90c 100644 --- a/base/parse.jl +++ b/base/parse.jl @@ -176,7 +176,7 @@ function tryparse_internal(::Type{T}, s::AbstractString, startpos::Int, endpos:: return n end -function tryparse_internal(::Type{Bool}, sbuff::Union{String,SubString{String}}, +function tryparse_internal(::Type{Bool}, sbuff::AbstractString, startpos::Int, endpos::Int, base::Integer, raise::Bool) if isempty(sbuff) raise && throw(ArgumentError("input string is empty")) @@ -202,10 +202,15 @@ function tryparse_internal(::Type{Bool}, sbuff::Union{String,SubString{String}}, end len = endpos - startpos + 1 - p = pointer(sbuff) + startpos - 1 - GC.@preserve sbuff begin - (len == 4) && (0 == _memcmp(p, "true", 4)) && (return true) - (len == 5) && (0 == _memcmp(p, "false", 5)) && (return false) + if sbuff isa Union{String, SubString{String}} + p = pointer(sbuff) + startpos - 1 + GC.@preserve sbuff begin + (len == 4) && (0 == _memcmp(p, "true", 4)) && (return true) + (len == 5) && (0 == _memcmp(p, "false", 5)) && (return false) + end + else + (len == 4) && (SubString(sbuff, startpos:startpos+3) == "true") && (return true) + (len == 5) && (SubString(sbuff, startpos:startpos+4) == "false") && (return false) end if raise diff --git a/test/parse.jl b/test/parse.jl index 7cf6d059f0a717..69092b2c4188df 100644 --- a/test/parse.jl +++ b/test/parse.jl @@ -41,6 +41,16 @@ Base.iterate(::Issue29451String, i::Integer=1) = i == 1 ? ('0', 2) : nothing @test Issue29451String() == "0" @test parse(Int, Issue29451String()) == 0 +# https://github.com/JuliaStrings/InlineStrings.jl/issues/57 +struct InlineStringIssue57 <: AbstractString end +Base.ncodeunits(::InlineStringIssue57) = 4 +Base.lastindex(::InlineStringIssue57) = 4 +Base.isvalid(::InlineStringIssue57, i::Integer) = 0 < i < 5 +Base.iterate(::InlineStringIssue57, i::Integer=1) = i == 1 ? ('t', 2) : i == 2 ? ('r', 3) : i == 3 ? ('u', 4) : i == 4 ? ('e', 5) : nothing +Base.:(==)(::SubString{InlineStringIssue57}, x::String) = x == "true" + +@test parse(Bool, InlineStringIssue57()) + @testset "Issue 20587, T=$T" for T in Any[BigInt, Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8] T === BigInt && continue # TODO: make BigInt pass this test for s in ["", " ", " "] From 6a8fb810a0cad37addb7a8cf4a832cd7b053f877 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Wed, 7 Dec 2022 10:47:14 +0900 Subject: [PATCH 077/387] devdocs: update blog posts that explain our inference algorithm (#47809) --- doc/src/devdocs/inference.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/src/devdocs/inference.md b/doc/src/devdocs/inference.md index 253dcf3e63c012..5c54ae3c7121d0 100644 --- a/doc/src/devdocs/inference.md +++ b/doc/src/devdocs/inference.md @@ -2,12 +2,12 @@ ## How inference works -[Type inference](https://en.wikipedia.org/wiki/Type_inference) refers -to the process of deducing the types of later values from the types of -input values. Julia's approach to inference has been described in blog -posts -([1](https://juliacomputing.com/blog/2016/04/inference-convergence/), -[2](https://juliacomputing.com/blog/2017/05/inference-converage2/)). +In Julia compiler, "type inference" refers to the process of deducing the types of later +values from the types of input values. Julia's approach to inference has been described in +the blog posts below: +1. [Shows a simplified implementation of the data-flow analysis algorithm, that Julia's type inference routine is based on.](https://aviatesk.github.io/posts/data-flow-problem/) +2. [Gives a high level view of inference with a focus on its inter-procedural convergence guarantee.](https://juliacomputing.com/blog/2016/04/inference-convergence/) +3. [Explains a refinement on the algorithm introduced in 2.](https://juliacomputing.com/blog/2017/05/inference-converage2/) ## Debugging compiler.jl From 4c90b2ae58c5cce391cff06afba8474d7c67086c Mon Sep 17 00:00:00 2001 From: Colin Caine Date: Wed, 7 Dec 2022 01:48:07 +0000 Subject: [PATCH 078/387] doc: clarify that atol = absolute tolerance and rtol = relative tolerance (#47819) --- base/floatfuncs.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/floatfuncs.jl b/base/floatfuncs.jl index b3db0f087d2114..9b8ca4b04ee280 100644 --- a/base/floatfuncs.jl +++ b/base/floatfuncs.jl @@ -245,8 +245,8 @@ end Inexact equality comparison. Two numbers compare equal if their relative distance *or* their absolute distance is within tolerance bounds: `isapprox` returns `true` if -`norm(x-y) <= max(atol, rtol*max(norm(x), norm(y)))`. The default `atol` is zero and the -default `rtol` depends on the types of `x` and `y`. The keyword argument `nans` determines +`norm(x-y) <= max(atol, rtol*max(norm(x), norm(y)))`. The default `atol` (absolute tolerance) is zero and the +default `rtol` (relative tolerance) depends on the types of `x` and `y`. The keyword argument `nans` determines whether or not NaN values are considered equal (defaults to false). For real or complex floating-point values, if an `atol > 0` is not specified, `rtol` defaults to From 894f1a5aa1412373954bed51da64d3b32e44e15e Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Wed, 7 Dec 2022 12:12:55 +0900 Subject: [PATCH 079/387] inference: refine `[Inference|Optimization]Params` (#47810) This commit does 2 things: 1. refine the names of parameters, mainly lowercasing some of them (according to our naming convention) 2. add more documentations explaining the meaning of each parameter 3. add `Effects`-like keyword-based constructors --- base/compiler/abstractinterpretation.jl | 34 +-- base/compiler/ssair/inlining.jl | 4 +- base/compiler/types.jl | 297 ++++++++++++++++-------- base/compiler/typeutils.jl | 10 +- doc/src/manual/types.md | 2 +- 5 files changed, 231 insertions(+), 116 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 03f81309446aa3..d11bb43c03ee09 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -19,7 +19,7 @@ call_result_unused(si::StmtInfo) = !si.used function get_max_methods(mod::Module, interp::AbstractInterpreter) max_methods = ccall(:jl_get_module_max_methods, Cint, (Any,), mod) % Int - max_methods < 0 ? InferenceParams(interp).MAX_METHODS : max_methods + max_methods < 0 ? InferenceParams(interp).max_methods : max_methods end function get_max_methods(@nospecialize(f), mod::Module, interp::AbstractInterpreter) @@ -62,7 +62,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), # no overlayed calls, try an additional effort now to check if this call # isn't overlayed rather than just handling it conservatively matches = find_matching_methods(arginfo.argtypes, atype, method_table(interp), - InferenceParams(interp).MAX_UNION_SPLITTING, max_methods) + InferenceParams(interp).max_union_splitting, max_methods) if !isa(matches, FailedMethodMatch) nonoverlayed = matches.nonoverlayed end @@ -78,7 +78,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), argtypes = arginfo.argtypes matches = find_matching_methods(argtypes, atype, method_table(interp), - InferenceParams(interp).MAX_UNION_SPLITTING, max_methods) + InferenceParams(interp).max_union_splitting, max_methods) if isa(matches, FailedMethodMatch) add_remark!(interp, sv, matches.reason) return CallMeta(Any, Effects(), NoCallInfo()) @@ -122,7 +122,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), splitunions = false # TODO: this used to trigger a bug in inference recursion detection, and is unmaintained now # sigtuple = unwrap_unionall(sig)::DataType - # splitunions = 1 < unionsplitcost(sigtuple.parameters) * napplicable <= InferenceParams(interp).MAX_UNION_SPLITTING + # splitunions = 1 < unionsplitcost(sigtuple.parameters) * napplicable <= InferenceParams(interp).max_union_splitting if splitunions splitsigs = switchtupleunion(sig) for sig_n in splitsigs @@ -276,9 +276,9 @@ end any_ambig(m::UnionSplitMethodMatches) = any(any_ambig, m.info.matches) function find_matching_methods(argtypes::Vector{Any}, @nospecialize(atype), method_table::MethodTableView, - union_split::Int, max_methods::Int) + max_union_splitting::Int, max_methods::Int) # NOTE this is valid as far as any "constant" lattice element doesn't represent `Union` type - if 1 < unionsplitcost(argtypes) <= union_split + if 1 < unionsplitcost(argtypes) <= max_union_splitting split_argtypes = switchtupleunion(argtypes) infos = MethodMatchInfo[] applicable = Any[] @@ -599,7 +599,7 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp end # see if the type is actually too big (relative to the caller), and limit it if required - newsig = limit_type_size(sig, comparison, hardlimit ? comparison : sv.linfo.specTypes, InferenceParams(interp).TUPLE_COMPLEXITY_LIMIT_DEPTH, spec_len) + newsig = limit_type_size(sig, comparison, hardlimit ? comparison : sv.linfo.specTypes, InferenceParams(interp).tuple_complexity_limit_depth, spec_len) if newsig !== sig # continue inference, but note that we've limited parameter complexity @@ -641,7 +641,7 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp # while !(newsig in seen) # push!(seen, newsig) # lsig = length((unwrap_unionall(sig)::DataType).parameters) - # newsig = limit_type_size(newsig, sig, sv.linfo.specTypes, InferenceParams(interp).TUPLE_COMPLEXITY_LIMIT_DEPTH, lsig) + # newsig = limit_type_size(newsig, sig, sv.linfo.specTypes, InferenceParams(interp).tuple_complexity_limit_depth, lsig) # recomputed = ccall(:jl_type_intersection_with_env, Any, (Any, Any), newsig, method.sig)::SimpleVector # newsig = recomputed[2] # end @@ -1436,13 +1436,13 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n calls = CallMeta[call] stateordonet_widened = widenconst(stateordonet) - # Try to unroll the iteration up to MAX_TUPLE_SPLAT, which covers any finite + # Try to unroll the iteration up to max_tuple_splat, which covers any finite # length iterators, or interesting prefix while true if stateordonet_widened === Nothing return ret, AbstractIterationInfo(calls) end - if Nothing <: stateordonet_widened || length(ret) >= InferenceParams(interp).MAX_TUPLE_SPLAT + if Nothing <: stateordonet_widened || length(ret) >= InferenceParams(interp).max_tuple_splat break end if !isa(stateordonet_widened, DataType) || !(stateordonet_widened <: Tuple) || isvatuple(stateordonet_widened) || length(stateordonet_widened.parameters) != 2 @@ -1520,7 +1520,7 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: end res = Union{} nargs = length(aargtypes) - splitunions = 1 < unionsplitcost(aargtypes) <= InferenceParams(interp).MAX_APPLY_UNION_ENUM + splitunions = 1 < unionsplitcost(aargtypes) <= InferenceParams(interp).max_apply_union_enum ctypes = [Any[aft]] infos = Vector{MaybeAbstractIterationInfo}[MaybeAbstractIterationInfo[]] effects = EFFECTS_TOTAL @@ -1728,14 +1728,14 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, (; fargs a = ssa_def_slot(fargs[2], sv) a2 = argtypes[2] if isa(a, SlotNumber) - cndt = isa_condition(a2, argtypes[3], InferenceParams(interp).MAX_UNION_SPLITTING, rt) + cndt = isa_condition(a2, argtypes[3], InferenceParams(interp).max_union_splitting, rt) if cndt !== nothing return Conditional(a, cndt.thentype, cndt.elsetype) end end if isa(a2, MustAlias) if !isa(rt, Const) # skip refinement when the field is known precisely (just optimization) - cndt = isa_condition(a2, argtypes[3], InferenceParams(interp).MAX_UNION_SPLITTING) + cndt = isa_condition(a2, argtypes[3], InferenceParams(interp).max_union_splitting) if cndt !== nothing return form_mustalias_conditional(a2, cndt.thentype, cndt.elsetype) end @@ -1749,18 +1749,18 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, (; fargs # if doing a comparison to a singleton, consider returning a `Conditional` instead if isa(aty, Const) if isa(b, SlotNumber) - cndt = egal_condition(aty, bty, InferenceParams(interp).MAX_UNION_SPLITTING, rt) + cndt = egal_condition(aty, bty, InferenceParams(interp).max_union_splitting, rt) return Conditional(b, cndt.thentype, cndt.elsetype) elseif isa(bty, MustAlias) && !isa(rt, Const) # skip refinement when the field is known precisely (just optimization) - cndt = egal_condition(aty, bty.fldtyp, InferenceParams(interp).MAX_UNION_SPLITTING) + cndt = egal_condition(aty, bty.fldtyp, InferenceParams(interp).max_union_splitting) return form_mustalias_conditional(bty, cndt.thentype, cndt.elsetype) end elseif isa(bty, Const) if isa(a, SlotNumber) - cndt = egal_condition(bty, aty, InferenceParams(interp).MAX_UNION_SPLITTING, rt) + cndt = egal_condition(bty, aty, InferenceParams(interp).max_union_splitting, rt) return Conditional(a, cndt.thentype, cndt.elsetype) elseif isa(aty, MustAlias) && !isa(rt, Const) # skip refinement when the field is known precisely (just optimization) - cndt = egal_condition(bty, aty.fldtyp, InferenceParams(interp).MAX_UNION_SPLITTING) + cndt = egal_condition(bty, aty.fldtyp, InferenceParams(interp).max_union_splitting) return form_mustalias_conditional(aty, cndt.thentype, cndt.elsetype) end end diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index fab5f0a390743e..43b9caa1b31546 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -1019,7 +1019,7 @@ rewrite_invoke_exprargs!(expr::Expr) = (expr.args = invoke_rewrite(expr.args); e function is_valid_type_for_apply_rewrite(@nospecialize(typ), params::OptimizationParams) if isa(typ, Const) && (v = typ.val; isa(v, SimpleVector)) - length(v) > params.MAX_TUPLE_SPLAT && return false + length(v) > params.max_tuple_splat && return false for p in v is_inlineable_constant(p) || return false end @@ -1032,7 +1032,7 @@ function is_valid_type_for_apply_rewrite(@nospecialize(typ), params::Optimizatio end isa(typ, DataType) || return false if typ.name === Tuple.name - return !isvatuple(typ) && length(typ.parameters) <= params.MAX_TUPLE_SPLAT + return !isvatuple(typ) && length(typ.parameters) <= params.max_tuple_splat else return false end diff --git a/base/compiler/types.jl b/base/compiler/types.jl index ff0d4f62e4968b..1514b3f101a60f 100644 --- a/base/compiler/types.jl +++ b/base/compiler/types.jl @@ -65,115 +65,230 @@ function InferenceResult(linfo::MethodInstance, argtypes::ForwardableArgtypes) end """ - OptimizationParams + inf_params::InferenceParams + +Parameters that control abstract interpretation-based type inference operation. + +--- +- `inf_params.max_methods::Int = 3`\\ + Type inference gives up analysis on a call when there are more than `max_methods` matching + methods. This trades off between compiler latency and generated code performance. + Typically, considering many methods means spending _lots_ of time obtaining poor type + information, so this option should be kept low. [`Base.Experimental.@max_methods`](@ref) + can have a more fine-grained control on this configuration with per-module or per-method + annotation basis. +--- +- `inf_params.max_union_splitting::Int = 4`\\ + Specifies the maximum number of union-tuples to swap or expand before computing the set of + matching methods or conditional types. +--- +- `inf_params.max_apply_union_enum::Int = 8`\\ + Specifies the maximum number of union-tuples to swap or expand when inferring a call to + `Core._apply_iterate`. +--- +- `inf_params.max_tuple_splat::Int = 32`\\ + When attempting to infer a call to `Core._apply_iterate`, abort the analysis if the tuple + contains more than this many elements. +--- +- `inf_params.tuple_complexity_limit_depth::Int = 3`\\ + Specifies the maximum depth of large tuple type that can appear as specialized method + signature when inferring a recursive call graph. +--- +- `inf_params.ipo_constant_propagation::Bool = true`\\ + If `false`, disables analysis with extended lattice information, i.e. disables any of + the concrete evaluation, semi-concrete interpretation and constant propagation entirely. + [`Base.@constprop :none`](@ref Base.@constprop) can have a more fine-grained control on + this configuration with per-method annotation basis. +--- +- `inf_params.aggressive_constant_propagation::Bool = false`\\ + If `true`, forces constant propagation on any methods when any extended lattice + information available. [`Base.@constprop :aggressive`](@ref Base.@constprop) can have a + more fine-grained control on this configuration with per-method annotation basis. +--- +- `inf_params.unoptimize_throw_blocks::Bool = true`\\ + If `true`, skips inferring calls that are in a block that is known to `throw`. + It may improve the compiler latency without sacrificing the runtime performance + in common situations. +--- +- `inf_params.assume_bindings_static::Bool = false`\\ + If `true`, assumes that no new bindings will be added, i.e. a non-existing binding at + inference time can be assumed to always not exist at runtime (and thus e.g. any access to + it will `throw`). Defaults to `false` since this assumption does not hold in Julia's + semantics for native code execution. +--- +""" +struct InferenceParams + max_methods::Int + max_union_splitting::Int + max_apply_union_enum::Int + max_tuple_splat::Int + tuple_complexity_limit_depth::Int + ipo_constant_propagation::Bool + aggressive_constant_propagation::Bool + unoptimize_throw_blocks::Bool + assume_bindings_static::Bool + + function InferenceParams( + max_methods::Int, + max_union_splitting::Int, + max_apply_union_enum::Int, + max_tuple_splat::Int, + tuple_complexity_limit_depth::Int, + ipo_constant_propagation::Bool, + aggressive_constant_propagation::Bool, + unoptimize_throw_blocks::Bool, + assume_bindings_static::Bool) + return new( + max_methods, + max_union_splitting, + max_apply_union_enum, + max_tuple_splat, + tuple_complexity_limit_depth, + ipo_constant_propagation, + aggressive_constant_propagation, + unoptimize_throw_blocks, + assume_bindings_static) + end +end +function InferenceParams( + params::InferenceParams = InferenceParams( # default constructor + #=max_methods::Int=# 3, + #=max_union_splitting::Int=# 4, + #=max_apply_union_enum::Int=# 8, + #=max_tuple_splat::Int=# 32, + #=tuple_complexity_limit_depth::Int=# 3, + #=ipo_constant_propagation::Bool=# true, + #=aggressive_constant_propagation::Bool=# false, + #=unoptimize_throw_blocks::Bool=# true, + #=assume_bindings_static::Bool=# false); + max_methods::Int = params.max_methods, + max_union_splitting::Int = params.max_union_splitting, + max_apply_union_enum::Int = params.max_apply_union_enum, + max_tuple_splat::Int = params.max_tuple_splat, + tuple_complexity_limit_depth::Int = params.tuple_complexity_limit_depth, + ipo_constant_propagation::Bool = params.ipo_constant_propagation, + aggressive_constant_propagation::Bool = params.aggressive_constant_propagation, + unoptimize_throw_blocks::Bool = params.unoptimize_throw_blocks, + assume_bindings_static::Bool = params.assume_bindings_static) + return InferenceParams( + max_methods, + max_union_splitting, + max_apply_union_enum, + max_tuple_splat, + tuple_complexity_limit_depth, + ipo_constant_propagation, + aggressive_constant_propagation, + unoptimize_throw_blocks, + assume_bindings_static) +end + +""" + opt_params::OptimizationParams Parameters that control optimizer operation. + +--- +- `opt_params.inlining::Bool = inlining_enabled()`\\ + Controls whether or not inlining is enabled. +--- +- `opt_params.inline_cost_threshold::Int = 100`\\ + Specifies the number of CPU cycles beyond which it's not worth inlining. +--- +- `opt_params.inline_nonleaf_penalty::Int = 1000`\\ + Specifies the penalty cost for a dynamic dispatch. +--- +- `opt_params.inline_tupleret_bonus::Int = 250`\\ + Specifies the extra inlining willingness for a method specialization with non-concrete + tuple return types (in hopes of splitting it up). `opt_params.inline_tupleret_bonus` will + be added to `opt_params.inline_cost_threshold` when making inlining decision. +--- +- `opt_params.inline_error_path_cost::Int = 20`\\ + Specifies the penalty cost for an un-optimized dynamic call in a block that is known to + `throw`. See also [`(inf_params::InferenceParams).unoptimize_throw_blocks`](@ref InferenceParams). +--- +- `opt_params.max_tuple_splat::Int = 32`\\ + When attempting to inline `Core._apply_iterate`, abort the optimization if the tuple + contains more than this many elements. +--- +- `opt_params.compilesig_invokes::Bool = true`\\ + If `true`, gives the inliner license to change which `MethodInstance` to invoke when + generating `:invoke` expression based on the [`@nospecialize`](@ref) annotation, + in order to avoid over-specialization. +--- +- `opt_params.trust_inference::Bool = false`\\ + If `false`, the inliner will unconditionally generate a fallback block when union-splitting + a callsite, in case of existing subtyping bugs. This option may be removed in the future. +--- +- `opt_params.assume_fatal_throw::Bool = false`\\ + If `true`, gives the optimizer license to assume that any `throw` is fatal and thus the + state after a `throw` is not externally observable. In particular, this gives the + optimizer license to move side effects (that are proven not observed within a particular + code path) across a throwing call. Defaults to `false`. +--- """ struct OptimizationParams - inlining::Bool # whether inlining is enabled - inline_cost_threshold::Int # number of CPU cycles beyond which it's not worth inlining - inline_nonleaf_penalty::Int # penalty for dynamic dispatch - inline_tupleret_bonus::Int # extra inlining willingness for non-concrete tuple return types (in hopes of splitting it up) - inline_error_path_cost::Int # cost of (un-optimized) calls in blocks that throw - + inlining::Bool + inline_cost_threshold::Int + inline_nonleaf_penalty::Int + inline_tupleret_bonus::Int + inline_error_path_cost::Int + max_tuple_splat::Int compilesig_invokes::Bool trust_inference::Bool - - """ - assume_fatal_throw::Bool - - If `true`, gives the optimizer license to assume that any `throw` is fatal - and thus the state after a `throw` is not externally observable. In particular, - this gives the optimizer license to move side effects (that are proven not observed - within a particular code path) across a throwing call. Defaults to `false`. - """ assume_fatal_throw::Bool - MAX_TUPLE_SPLAT::Int - - function OptimizationParams(; - inlining::Bool = inlining_enabled(), - inline_cost_threshold::Int = 100, - inline_nonleaf_penalty::Int = 1000, - inline_tupleret_bonus::Int = 250, - inline_error_path_cost::Int = 20, - tuple_splat::Int = 32, - compilesig_invokes::Bool = true, - trust_inference::Bool = false, - assume_fatal_throw::Bool = false - ) + function OptimizationParams( + inlining::Bool, + inline_cost_threshold::Int, + inline_nonleaf_penalty::Int, + inline_tupleret_bonus::Int, + inline_error_path_cost::Int, + max_tuple_splat::Int, + compilesig_invokes::Bool, + trust_inference::Bool, + assume_fatal_throw::Bool) return new( inlining, inline_cost_threshold, inline_nonleaf_penalty, inline_tupleret_bonus, inline_error_path_cost, + max_tuple_splat, compilesig_invokes, trust_inference, - assume_fatal_throw, - tuple_splat, - ) + assume_fatal_throw) end end - -""" - InferenceParams - -Parameters that control type inference operation. -""" -struct InferenceParams - ipo_constant_propagation::Bool - aggressive_constant_propagation::Bool - unoptimize_throw_blocks::Bool - - # don't consider more than N methods. this trades off between - # compiler performance and generated code performance. - # typically, considering many methods means spending lots of time - # obtaining poor type information. - # It is important for N to be >= the number of methods in the error() - # function, so we can still know that error() is always Bottom. - MAX_METHODS::Int - # the maximum number of union-tuples to swap / expand - # before computing the set of matching methods - MAX_UNION_SPLITTING::Int - # the maximum number of union-tuples to swap / expand - # when inferring a call to _apply_iterate - MAX_APPLY_UNION_ENUM::Int - - # parameters limiting large (tuple) types - TUPLE_COMPLEXITY_LIMIT_DEPTH::Int - - # when attempting to inline _apply_iterate, abort the optimization if the - # tuple contains more than this many elements - MAX_TUPLE_SPLAT::Int - - # Assume that no new bindings will be added, i.e. a non-existing binding - # at inference time can be assumed to always error. - assume_bindings_static::Bool - - function InferenceParams(; - ipo_constant_propagation::Bool = true, - aggressive_constant_propagation::Bool = false, - unoptimize_throw_blocks::Bool = true, - max_methods::Int = 3, - union_splitting::Int = 4, - apply_union_enum::Int = 8, - tupletype_depth::Int = 3, - tuple_splat::Int = 32, - assume_bindings_static::Bool = false, - ) - return new( - ipo_constant_propagation, - aggressive_constant_propagation, - unoptimize_throw_blocks, - max_methods, - union_splitting, - apply_union_enum, - tupletype_depth, - tuple_splat, - assume_bindings_static - ) - end +function OptimizationParams( + params::OptimizationParams = OptimizationParams( + #=inlining::Bool=# inlining_enabled(), + #=inline_cost_threshold::Int=# 100, + #=inline_nonleaf_penalty::Int=# 1000, + #=inline_tupleret_bonus::Int=# 250, + #=inline_error_path_cost::Int=# 20, + #=max_tuple_splat::Int=# 32, + #=compilesig_invokes::Bool=# true, + #=trust_inference::Bool=# false, + #=assume_fatal_throw::Bool=# false); + inlining::Bool = params.inlining, + inline_cost_threshold::Int = params.inline_cost_threshold, + inline_nonleaf_penalty::Int = params.inline_nonleaf_penalty, + inline_tupleret_bonus::Int = params.inline_tupleret_bonus, + inline_error_path_cost::Int = params.inline_error_path_cost, + max_tuple_splat::Int = params.max_tuple_splat, + compilesig_invokes::Bool = params.compilesig_invokes, + trust_inference::Bool = params.trust_inference, + assume_fatal_throw::Bool = params.assume_fatal_throw) + return OptimizationParams( + inlining, + inline_cost_threshold, + inline_nonleaf_penalty, + inline_tupleret_bonus, + inline_error_path_cost, + max_tuple_splat, + compilesig_invokes, + trust_inference, + assume_fatal_throw) end """ diff --git a/base/compiler/typeutils.jl b/base/compiler/typeutils.jl index 15ee6fc4bd625b..9a282da101d022 100644 --- a/base/compiler/typeutils.jl +++ b/base/compiler/typeutils.jl @@ -164,14 +164,14 @@ end # return an upper-bound on type `a` with type `b` removed # such that `return <: a` && `Union{return, b} == Union{a, b}` -function typesubtract(@nospecialize(a), @nospecialize(b), MAX_UNION_SPLITTING::Int) +function typesubtract(@nospecialize(a), @nospecialize(b), max_union_splitting::Int) if a <: b && isnotbrokensubtype(a, b) return Bottom end ua = unwrap_unionall(a) if isa(ua, Union) - uua = typesubtract(rewrap_unionall(ua.a, a), b, MAX_UNION_SPLITTING) - uub = typesubtract(rewrap_unionall(ua.b, a), b, MAX_UNION_SPLITTING) + uua = typesubtract(rewrap_unionall(ua.a, a), b, max_union_splitting) + uub = typesubtract(rewrap_unionall(ua.b, a), b, max_union_splitting) return Union{valid_as_lattice(uua) ? uua : Union{}, valid_as_lattice(uub) ? uub : Union{}} elseif a isa DataType @@ -179,7 +179,7 @@ function typesubtract(@nospecialize(a), @nospecialize(b), MAX_UNION_SPLITTING::I if ub isa DataType if a.name === ub.name === Tuple.name && length(a.parameters) == length(ub.parameters) - if 1 < unionsplitcost(a.parameters) <= MAX_UNION_SPLITTING + if 1 < unionsplitcost(a.parameters) <= max_union_splitting ta = switchtupleunion(a) return typesubtract(Union{ta...}, b, 0) elseif b isa DataType @@ -200,7 +200,7 @@ function typesubtract(@nospecialize(a), @nospecialize(b), MAX_UNION_SPLITTING::I ap = a.parameters[i] bp = b.parameters[i] (isvarargtype(ap) || isvarargtype(bp)) && return a - ta[i] = typesubtract(ap, bp, min(2, MAX_UNION_SPLITTING)) + ta[i] = typesubtract(ap, bp, min(2, max_union_splitting)) return Tuple{ta...} end end diff --git a/doc/src/manual/types.md b/doc/src/manual/types.md index 9a5a6062f10bb1..ce61b1a25a0dcc 100644 --- a/doc/src/manual/types.md +++ b/doc/src/manual/types.md @@ -1615,5 +1615,5 @@ in unfavorable cases, you can easily end up making the performance of your code In particular, you would never want to write actual code as illustrated above. For more information about the proper (and improper) uses of `Val`, please read [the more extensive discussion in the performance tips](@ref man-performance-value-type). -[^1]: "Small" is defined by the `MAX_UNION_SPLITTING` constant, which is currently set to 4. +[^1]: "Small" is defined by the `max_union_splitting` configuration, which currently defaults to 4. [^2]: A few popular languages have singleton types, including Haskell, Scala and Ruby. From 3e9ca03602c99779361881ed784e4a34e9de0e20 Mon Sep 17 00:00:00 2001 From: Allen Hill Date: Tue, 6 Dec 2022 22:39:38 -0500 Subject: [PATCH 080/387] Add fast `isdisjoint` method for ranges (#46356) Co-authored-by: Lilith Orion Hafner Co-authored-by: Oscar Smith --- base/abstractset.jl | 21 +++++++++++++++++++++ test/sets.jl | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/base/abstractset.jl b/base/abstractset.jl index 62784af2bd67b7..5d0d65dad2de6e 100644 --- a/base/abstractset.jl +++ b/base/abstractset.jl @@ -476,6 +476,27 @@ function isdisjoint(a, b) _isdisjoint(a, b) end +function isdisjoint(a::AbstractRange{T}, b::AbstractRange{T}) where T + (isempty(a) || isempty(b)) && return true + fa, la = extrema(a) + fb, lb = extrema(b) + if (la < fb) | (lb < fa) + return true + else + return _overlapping_range_isdisjoint(a, b) + end +end + +_overlapping_range_isdisjoint(a::AbstractRange{T}, b::AbstractRange{T}) where T = invoke(isdisjoint, Tuple{Any,Any}, a, b) + +function _overlapping_range_isdisjoint(a::AbstractRange{T}, b::AbstractRange{T}) where T<:Integer + if abs(step(a)) == abs(step(b)) + return mod(minimum(a), step(a)) != mod(minimum(b), step(a)) + else + return invoke(isdisjoint, Tuple{Any,Any}, a, b) + end +end + ## partial ordering of sets by containment ==(a::AbstractSet, b::AbstractSet) = length(a) == length(b) && a ⊆ b diff --git a/test/sets.jl b/test/sets.jl index b52d8136232318..4056bc34150ffe 100644 --- a/test/sets.jl +++ b/test/sets.jl @@ -420,6 +420,48 @@ end @test issubset(Set(Bool[]), rand(Bool, 100)) == true # neither has a fast in, right doesn't have a length @test isdisjoint([1, 3, 5, 7, 9], Iterators.filter(iseven, 1:10)) + + # range fast-path + for (truth, a, b) in ( + # Integers + (true, 1:10, 11:20), # not overlapping + (false, 1:10, 5:20), # partial overlap + (false, 5:9, 1:10), # complete overlap + # complete overlap, unequal steps + (false, 3:6:60, 9:9:60), + (true, 4:6:60, 9:9:60), + (true, 0:6:12, 9:9:60), + (false, 6:6:18, 9:9:60), + (false, 12:6:18, 9:9:60), + (false, 18:6:18, 9:9:60), + (true, 1:2:3, 2:3:5), + (true, 1:4:5, 2:1:4), + (false, 4:12:124, 1:1:8), + # potential overflow + (false, 0x1:0x3:0x4, 0x4:0x3:0x4), + (true, 0x3:0x3:0x6, 0x4:0x3:0x4), + (false, typemax(Int8):Int8(3):typemax(Int8), typemin(Int8):Int8(3):typemax(Int8)), + # Chars + (true, 'a':'l', 'o':'p'), # not overlapping + (false, 'a':'l', 'h':'p'), # partial overlap + (false, 'a':'l', 'c':'e'), # complete overlap + # Floats + (true, 1.:10., 11.:20.), # not overlapping + (false, 1.:10., 5.:20.), # partial overlap + (false, 5.:9., 1.:10.), # complete overlap + # Inputs that may hang + (false, -6011687643038262928:3545293653953105048, -6446834672754204848:3271267329311042532), + ) + @test isdisjoint(a, b) == truth + @test isdisjoint(b, a) == truth + @test isdisjoint(a, reverse(b)) == truth + @test isdisjoint(reverse(a), b) == truth + @test isdisjoint(b, reverse(a)) == truth + @test isdisjoint(reverse(b), a) == truth + end + @test isdisjoint(10:9, 1:10) # empty range + @test !isdisjoint(1e-100:.1:1, 0:.1:1) + @test !isdisjoint(eps()/4:.1:.71, 0:.1:1) end @testset "unique" begin From 5060aedd56e2a2a6320b7fbe3b23541281901bb3 Mon Sep 17 00:00:00 2001 From: Udoh Jeremiah Date: Wed, 7 Dec 2022 12:03:32 +0100 Subject: [PATCH 081/387] correct incomplete sentence (#47524) * correct incomplete sentence * provide link for rata die --- stdlib/Dates/docs/src/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stdlib/Dates/docs/src/index.md b/stdlib/Dates/docs/src/index.md index 906c697e0043eb..e0e09a919a0856 100644 --- a/stdlib/Dates/docs/src/index.md +++ b/stdlib/Dates/docs/src/index.md @@ -361,7 +361,7 @@ julia> Dates.monthabbr(t;locale="french") ``` Since the abbreviated versions of the days are not loaded, trying to use the -function `dayabbr` will error. +function `dayabbr` will throw an error. ```jldoctest tdate2 julia> Dates.dayabbr(t;locale="french") @@ -640,8 +640,8 @@ by 10. As Julia [`Date`](@ref) and [`DateTime`](@ref) values are represented according to the ISO 8601 standard, `0000-01-01T00:00:00` was chosen as base (or "rounding epoch") from which to begin the count of days (and milliseconds) used in rounding calculations. (Note that this differs slightly -from Julia's internal representation of [`Date`](@ref) s using Rata Die notation; but since the -ISO 8601 standard is most visible to the end user, `0000-01-01T00:00:00` was chosen as the rounding +from Julia's internal representation of [`Date`](@ref) s using [Rata Die notation](https://en.wikipedia.org/wiki/Rata_Die); +but since the ISO 8601 standard is most visible to the end user, `0000-01-01T00:00:00` was chosen as the rounding epoch instead of the `0000-12-31T00:00:00` used internally to minimize confusion.) The only exception to the use of `0000-01-01T00:00:00` as the rounding epoch is when rounding From de4f1c3176e3766c6f7304dcac404dbaffb831c7 Mon Sep 17 00:00:00 2001 From: jonathan-conder-sm <63538679+jonathan-conder-sm@users.noreply.github.com> Date: Wed, 7 Dec 2022 13:47:25 +0000 Subject: [PATCH 082/387] Fix libjulia install name and libjulia-internal rpath on OS X (#47220) --- Makefile | 14 ++++++++++++-- cli/Makefile | 4 ++-- src/Makefile | 7 +++++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index ad9dcac6bbb7df..eb6704a1fa1e09 100644 --- a/Makefile +++ b/Makefile @@ -384,8 +384,18 @@ endif fi; endif - # Set rpath for libjulia-internal, which is moving from `../lib` to `../lib/julia`. We only need to do this for Linux/FreeBSD -ifneq (,$(findstring $(OS),Linux FreeBSD)) + # Set rpath for libjulia-internal, which is moving from `../lib` to `../lib/julia`. +ifeq ($(OS), Darwin) +ifneq ($(DARWIN_FRAMEWORK),1) +ifeq ($(JULIA_BUILD_MODE),release) + install_name_tool -add_rpath @loader_path/$(reverse_private_libdir_rel)/ $(DESTDIR)$(private_libdir)/libjulia-internal.$(SHLIB_EXT) + install_name_tool -add_rpath @loader_path/$(reverse_private_libdir_rel)/ $(DESTDIR)$(private_libdir)/libjulia-codegen.$(SHLIB_EXT) +else ifeq ($(JULIA_BUILD_MODE),debug) + install_name_tool -add_rpath @loader_path/$(reverse_private_libdir_rel)/ $(DESTDIR)$(private_libdir)/libjulia-internal-debug.$(SHLIB_EXT) + install_name_tool -add_rpath @loader_path/$(reverse_private_libdir_rel)/ $(DESTDIR)$(private_libdir)/libjulia-codegen-debug.$(SHLIB_EXT) +endif +endif +else ifneq (,$(findstring $(OS),Linux FreeBSD)) ifeq ($(JULIA_BUILD_MODE),release) $(PATCHELF) --set-rpath '$$ORIGIN:$$ORIGIN/$(reverse_private_libdir_rel)' $(DESTDIR)$(private_libdir)/libjulia-internal.$(SHLIB_EXT) $(PATCHELF) --set-rpath '$$ORIGIN:$$ORIGIN/$(reverse_private_libdir_rel)' $(DESTDIR)$(private_libdir)/libjulia-codegen.$(SHLIB_EXT) diff --git a/cli/Makefile b/cli/Makefile index dfe7b594ee46eb..d360a98412cf61 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -112,7 +112,7 @@ endif $(build_shlibdir)/libjulia.$(JL_MAJOR_MINOR_SHLIB_EXT): $(LIB_OBJS) $(SRCDIR)/list_strip_symbols.h | $(build_shlibdir) $(build_libdir) @$(call PRINT_LINK, $(CC) $(call IMPLIB_FLAGS,$@.tmp) $(LOADER_CFLAGS) -DLIBRARY_EXPORTS -shared $(SHIPFLAGS) $(LIB_OBJS) -o $@ \ $(JLIBLDFLAGS) $(LOADER_LDFLAGS) $(call SONAME_FLAGS,libjulia.$(JL_MAJOR_SHLIB_EXT))) - @$(INSTALL_NAME_CMD)libjulia.$(SHLIB_EXT) $@ + @$(INSTALL_NAME_CMD)libjulia.$(JL_MAJOR_SHLIB_EXT) $@ ifeq ($(OS), WINNT) @# Note that if the objcopy command starts getting too long, we can use `@file` to read @# command-line options from `file` instead. @@ -122,7 +122,7 @@ endif $(build_shlibdir)/libjulia-debug.$(JL_MAJOR_MINOR_SHLIB_EXT): $(LIB_DOBJS) $(SRCDIR)/list_strip_symbols.h | $(build_shlibdir) $(build_libdir) @$(call PRINT_LINK, $(CC) $(call IMPLIB_FLAGS,$@.tmp) $(LOADER_CFLAGS) -DLIBRARY_EXPORTS -shared $(DEBUGFLAGS) $(LIB_DOBJS) -o $@ \ $(JLIBLDFLAGS) $(LOADER_LDFLAGS) $(call SONAME_FLAGS,libjulia-debug.$(JL_MAJOR_SHLIB_EXT))) - @$(INSTALL_NAME_CMD)libjulia-debug.$(SHLIB_EXT) $@ + @$(INSTALL_NAME_CMD)libjulia-debug.$(JL_MAJOR_SHLIB_EXT) $@ ifeq ($(OS), WINNT) @$(call PRINT_ANALYZE, $(OBJCOPY) $(build_libdir)/$(notdir $@).tmp.a $(STRIP_EXPORTED_FUNCS) $(build_libdir)/$(notdir $@).a && rm $(build_libdir)/$(notdir $@).tmp.a) endif diff --git a/src/Makefile b/src/Makefile index 8ac15083a73e29..380d2687e75a10 100644 --- a/src/Makefile +++ b/src/Makefile @@ -150,6 +150,9 @@ CLANG_LDFLAGS := $(LLVM_LDFLAGS) ifeq ($(OS), Darwin) CLANG_LDFLAGS += -Wl,-undefined,dynamic_lookup OSLIBS += $(SRCDIR)/mach_dyld_atfork.tbd +LIBJULIA_PATH_REL := @rpath/libjulia +else +LIBJULIA_PATH_REL := libjulia endif COMMON_LIBPATHS := -L$(build_libdir) -L$(build_shlibdir) @@ -174,8 +177,8 @@ SHIPFLAGS += "-DJL_SYSTEM_IMAGE_PATH=\"$(build_private_libdir_rel)/sys.$(SHLIB_ DEBUGFLAGS += "-DJL_SYSTEM_IMAGE_PATH=\"$(build_private_libdir_rel)/sys-debug.$(SHLIB_EXT)\"" # Add SONAME defines so we can embed proper `dlopen()` calls. -SHIPFLAGS += "-DJL_LIBJULIA_SONAME=\"libjulia.$(JL_MAJOR_SHLIB_EXT)\"" "-DJL_LIBJULIA_INTERNAL_SONAME=\"libjulia-internal.$(JL_MAJOR_SHLIB_EXT)\"" -DEBUGFLAGS += "-DJL_LIBJULIA_SONAME=\"libjulia-debug.$(JL_MAJOR_SHLIB_EXT)\"" "-DJL_LIBJULIA_INTERNAL_SONAME=\"libjulia-internal-debug.$(JL_MAJOR_SHLIB_EXT)\"" +SHIPFLAGS += "-DJL_LIBJULIA_SONAME=\"$(LIBJULIA_PATH_REL).$(JL_MAJOR_SHLIB_EXT)\"" +DEBUGFLAGS += "-DJL_LIBJULIA_SONAME=\"$(LIBJULIA_PATH_REL)-debug.$(JL_MAJOR_SHLIB_EXT)\"" ifeq ($(USE_CROSS_FLISP), 1) FLISPDIR := $(BUILDDIR)/flisp/host From 495a004bda33e284b0acc612f5ced9ba1eb9a777 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Wed, 7 Dec 2022 16:01:47 +0100 Subject: [PATCH 083/387] Add support for "package extensions" to code loading (#47695) * Add support for "glue packages" to code loading This allows packages to define "glue packages" which are modules that are automatically loaded when a set of other packages are loaded into the Julia session. --- NEWS.md | 5 +- base/loading.jl | 243 ++++++++++++++++-- doc/src/manual/code-loading.md | 39 +++ test/loading.jl | 31 +++ .../project/Extensions/ExtDep.jl/Project.toml | 3 + .../Extensions/ExtDep.jl/src/ExtDep.jl | 5 + test/project/Extensions/ExtDep2/Project.toml | 3 + .../project/Extensions/ExtDep2/src/ExtDep2.jl | 5 + .../HasDepWithExtensions.jl/Manifest.toml | 25 ++ .../HasDepWithExtensions.jl/Project.toml | 8 + .../src/HasDepWithExtensions.jl | 13 + .../Extensions/HasExtensions.jl/Manifest.toml | 7 + .../Extensions/HasExtensions.jl/Project.toml | 11 + .../HasExtensions.jl/ext/Extension.jl | 13 + .../ext/ExtensionFolder/ExtensionFolder.jl | 9 + .../HasExtensions.jl/src/HasExtensions.jl | 10 + 16 files changed, 406 insertions(+), 24 deletions(-) create mode 100644 test/project/Extensions/ExtDep.jl/Project.toml create mode 100644 test/project/Extensions/ExtDep.jl/src/ExtDep.jl create mode 100644 test/project/Extensions/ExtDep2/Project.toml create mode 100644 test/project/Extensions/ExtDep2/src/ExtDep2.jl create mode 100644 test/project/Extensions/HasDepWithExtensions.jl/Manifest.toml create mode 100644 test/project/Extensions/HasDepWithExtensions.jl/Project.toml create mode 100644 test/project/Extensions/HasDepWithExtensions.jl/src/HasDepWithExtensions.jl create mode 100644 test/project/Extensions/HasExtensions.jl/Manifest.toml create mode 100644 test/project/Extensions/HasExtensions.jl/Project.toml create mode 100644 test/project/Extensions/HasExtensions.jl/ext/Extension.jl create mode 100644 test/project/Extensions/HasExtensions.jl/ext/ExtensionFolder/ExtensionFolder.jl create mode 100644 test/project/Extensions/HasExtensions.jl/src/HasExtensions.jl diff --git a/NEWS.md b/NEWS.md index 1a0cd19825320c..7d90c6f70ce101 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,7 +4,6 @@ Julia v1.10 Release Notes New language features --------------------- - Language changes ---------------- @@ -39,6 +38,10 @@ Standard library changes #### Package Manager +- "Package Extensions": support for loading a piece of code based on other + packages being loaded in the Julia session. + This has similar applications as the Requires.jl package but also + supports precompilation and setting compatibility. #### LinearAlgebra diff --git a/base/loading.jl b/base/loading.jl index 1e168d8a29e62b..ea350ff72d960d 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -564,7 +564,7 @@ function manifest_deps_get(env::String, where::PkgId, name::String)::Union{Nothi return PkgId(pkg_uuid, name) end # look for manifest file and `where` stanza - return explicit_manifest_deps_get(project_file, uuid, name) + return explicit_manifest_deps_get(project_file, where, name) elseif project_file # if env names a directory, search it return implicit_manifest_deps_get(env, where, name) @@ -578,7 +578,7 @@ function manifest_uuid_path(env::String, pkg::PkgId)::Union{Nothing,String,Missi proj = project_file_name_uuid(project_file, pkg.name) if proj == pkg # if `pkg` matches the project, return the project itself - return project_file_path(project_file, pkg.name) + return project_file_path(project_file) end # look for manifest file and `where` stanza return explicit_manifest_uuid_path(project_file, pkg) @@ -598,7 +598,7 @@ function project_file_name_uuid(project_file::String, name::String)::PkgId return PkgId(uuid, name) end -function project_file_path(project_file::String, name::String) +function project_file_path(project_file::String) d = parsed_toml(project_file) joinpath(dirname(project_file), get(d, "path", "")::String) end @@ -716,7 +716,7 @@ end # find `where` stanza and return the PkgId for `name` # return `nothing` if it did not find `where` (indicating caller should continue searching) -function explicit_manifest_deps_get(project_file::String, where::UUID, name::String)::Union{Nothing,PkgId} +function explicit_manifest_deps_get(project_file::String, where::PkgId, name::String)::Union{Nothing,PkgId} manifest_file = project_file_manifest_path(project_file) manifest_file === nothing && return nothing # manifest not found--keep searching LOAD_PATH d = get_deps(parsed_toml(manifest_file)) @@ -728,16 +728,15 @@ function explicit_manifest_deps_get(project_file::String, where::UUID, name::Str entry = entry::Dict{String, Any} uuid = get(entry, "uuid", nothing)::Union{String, Nothing} uuid === nothing && continue - if UUID(uuid) === where + if UUID(uuid) === where.uuid found_where = true # deps is either a list of names (deps = ["DepA", "DepB"]) or # a table of entries (deps = {"DepA" = "6ea...", "DepB" = "55d..."} deps = get(entry, "deps", nothing)::Union{Vector{String}, Dict{String, Any}, Nothing} - deps === nothing && continue if deps isa Vector{String} found_name = name in deps break - else + elseif deps isa Dict{String, Any} deps = deps::Dict{String, Any} for (dep, uuid) in deps uuid::String @@ -746,6 +745,36 @@ function explicit_manifest_deps_get(project_file::String, where::UUID, name::Str end end end + else # Check for extensions + extensions = get(entry, "extensions", nothing) + if extensions !== nothing + if haskey(extensions, where.name) && where.uuid == uuid5(UUID(uuid), where.name) + found_where = true + if name == dep_name + return PkgId(UUID(uuid), name) + end + exts = extensions[where.name]::Union{String, Vector{String}} + if (exts isa String && name == exts) || (exts isa Vector{String} && name in exts) + weakdeps = get(entry, "weakdeps", nothing)::Union{Vector{String}, Dict{String, Any}, Nothing} + if weakdeps !== nothing + if weakdeps isa Vector{String} + found_name = name in weakdeps + break + elseif weakdeps isa Dict{String, Any} + weakdeps = weakdeps::Dict{String, Any} + for (dep, uuid) in weakdeps + uuid::String + if dep === name + return PkgId(UUID(uuid), name) + end + end + end + end + end + # `name` is not an ext, do standard lookup as if this was the parent + return identify_package(PkgId(UUID(uuid), dep_name), name) + end + end end end end @@ -769,13 +798,27 @@ function explicit_manifest_uuid_path(project_file::String, pkg::PkgId)::Union{No d = get_deps(parsed_toml(manifest_file)) entries = get(d, pkg.name, nothing)::Union{Nothing, Vector{Any}} - entries === nothing && return nothing # TODO: allow name to mismatch? - for entry in entries - entry = entry::Dict{String, Any} - uuid = get(entry, "uuid", nothing)::Union{Nothing, String} - uuid === nothing && continue - if UUID(uuid) === pkg.uuid - return explicit_manifest_entry_path(manifest_file, pkg, entry) + if entries !== nothing + for entry in entries + entry = entry::Dict{String, Any} + uuid = get(entry, "uuid", nothing)::Union{Nothing, String} + uuid === nothing && continue + if UUID(uuid) === pkg.uuid + return explicit_manifest_entry_path(manifest_file, pkg, entry) + end + end + end + # Extensions + for (name, entries::Vector{Any}) in d + for entry in entries + uuid = get(entry, "uuid", nothing)::Union{Nothing, String} + extensions = get(entry, "extensions", nothing)::Union{Nothing, Dict{String, Any}} + if extensions !== nothing && haskey(extensions, pkg.name) && uuid !== nothing && uuid5(UUID(uuid), pkg.name) == pkg.uuid + p = normpath(dirname(locate_package(PkgId(UUID(uuid), name))), "..") + extfiledir = joinpath(p, "ext", pkg.name, pkg.name * ".jl") + isfile(extfiledir) && return extfiledir + return joinpath(p, "ext", pkg.name * ".jl") + end end end return nothing @@ -958,6 +1001,7 @@ end function run_package_callbacks(modkey::PkgId) assert_havelock(require_lock) unlock(require_lock) + run_extension_callbacks() try for callback in package_callbacks invokelatest(callback, modkey) @@ -972,6 +1016,154 @@ function run_package_callbacks(modkey::PkgId) nothing end + +############## +# Extensions # +############## + +mutable struct ExtensionId + const id::PkgId # Could be symbol? + const parentid::PkgId + const triggers::Vector{PkgId} # What packages have to be loaded for the extension to get loaded + triggered::Bool + succeeded::Bool +end + +const EXT_DORMITORY = ExtensionId[] + +function insert_extension_triggers(pkg::PkgId) + pkg.uuid === nothing && return + for env in load_path() + insert_extension_triggers(env, pkg) + break # For now, only insert triggers for packages in the first load_path. + end +end + +function insert_extension_triggers(env::String, pkg::PkgId)::Union{Nothing,Missing} + project_file = env_project_file(env) + if project_file isa String + manifest_file = project_file_manifest_path(project_file) + manifest_file === nothing && return + d = get_deps(parsed_toml(manifest_file)) + for (dep_name, entries) in d + entries::Vector{Any} + for entry in entries + entry = entry::Dict{String, Any} + uuid = get(entry, "uuid", nothing)::Union{String, Nothing} + uuid === nothing && continue + if UUID(uuid) == pkg.uuid + weakdeps = get(entry, "weakdeps", nothing)::Union{Nothing, Vector{String}, Dict{String,Any}} + extensions = get(entry, "extensions", nothing)::Union{Nothing, Dict{String, Any}} + extensions === nothing && return + weakdeps === nothing && return + if weakdeps isa Dict{String, Any} + return _insert_extension_triggers(pkg, extensions, weakdeps) + end + + d_weakdeps = Dict{String, String}() + for (dep_name, entries) in d + dep_name in weakdeps || continue + entries::Vector{Any} + if length(entries) != 1 + error("expected a single entry for $(repr(name)) in $(repr(project_file))") + end + entry = first(entries)::Dict{String, Any} + uuid = get(entry, "uuid", nothing)::Union{String, Nothing} + d_weakdeps[dep_name] = uuid + end + @assert length(d_weakdeps) == length(weakdeps) + return _insert_extension_triggers(pkg, extensions, d_weakdeps) + end + end + end + end + return nothing +end + +function _insert_extension_triggers(parent::PkgId, extensions::Dict{String, <:Any}, weakdeps::Dict{String, <:Any}) + for (ext::String, triggers::Union{String, Vector{String}}) in extensions + triggers isa String && (triggers = [triggers]) + triggers_id = PkgId[] + id = PkgId(uuid5(parent.uuid, ext), ext) + for trigger in triggers + # TODO: Better error message if this lookup fails? + uuid_trigger = UUID(weakdeps[trigger]::String) + push!(triggers_id, PkgId(uuid_trigger, trigger)) + end + gid = ExtensionId(id, parent, triggers_id, false, false) + push!(EXT_DORMITORY, gid) + end +end + +function run_extension_callbacks(; force::Bool=false) + try + # TODO, if `EXT_DORMITORY` becomes very long, do something smarter + for extid in EXT_DORMITORY + extid.succeeded && continue + !force && extid.triggered && continue + if all(x -> haskey(Base.loaded_modules, x), extid.triggers) + ext_not_allowed_load = nothing + extid.triggered = true + # It is possible that some of the triggers were loaded in an environment + # below the one of the parent. This will cause a load failure when the + # pkg ext tries to load the triggers. Therefore, check this first + # before loading the pkg ext. + for trigger in extid.triggers + pkgenv = Base.identify_package_env(extid.id, trigger.name) + if pkgenv === nothing + ext_not_allowed_load = trigger + break + else + pkg, env = pkgenv + path = Base.locate_package(pkg, env) + if path === nothing + ext_not_allowed_load = trigger + break + end + end + end + if ext_not_allowed_load !== nothing + @debug "Extension $(extid.id.name) of $(extid.parentid.name) not loaded due to \ + $(ext_not_allowed_load.name) loaded in environment lower in load path" + else + require(extid.id) + @debug "Extension $(extid.id.name) of $(extid.parentid.name) loaded" + end + extid.succeeded = true + end + end + catch + # Try to continue loading if loading an extension errors + errs = current_exceptions() + @error "Error during loading of extension" exception=errs + end + nothing +end + +""" + load_extensions() + +Loads all the (not yet loaded) extensions that have their extension-dependencies loaded. +This is used in cases where the automatic loading of an extension failed +due to some problem with the extension. Instead of restarting the Julia session, +the extension can be fixed, and this function run. +""" +retry_load_extensions() = run_extension_callbacks(; force=true) + +""" + get_extension(parent::Module, extension::Symbol) + +Return the module for `extension` of `parent` or return `nothing` if the extension is not loaded. +""" +get_extension(parent::Module, ext::Symbol) = get_extension(PkgId(parent), ext) +function get_extension(parentid::PkgId, ext::Symbol) + parentid.uuid === nothing && return nothing + extid = PkgId(uuid5(parentid.uuid, string(ext)), string(ext)) + return get(loaded_modules, extid, nothing) +end + +# End extensions + # loads a precompile cache file, after checking stale_cachefile tests function _tryrequire_from_serialized(modkey::PkgId, build_id::UInt128) assert_havelock(require_lock) @@ -995,6 +1187,7 @@ function _tryrequire_from_serialized(modkey::PkgId, build_id::UInt128) notify(loading, loaded, all=true) end if loaded isa Module + insert_extension_triggers(modkey) run_package_callbacks(modkey) end end @@ -1035,6 +1228,7 @@ function _tryrequire_from_serialized(modkey::PkgId, path::String, sourcepath::St notify(loading, loaded, all=true) end if loaded isa Module + insert_extension_triggers(modkey) run_package_callbacks(modkey) end end @@ -1239,7 +1433,7 @@ function require(into::Module, mod::Symbol) LOADING_CACHE[] = LoadingCache() try uuidkey_env = identify_package_env(into, String(mod)) - # Core.println("require($(PkgId(into)), $mod) -> $uuidkey from env \"$env\"") + # Core.println("require($(PkgId(into)), $mod) -> $uuidkey_env") if uuidkey_env === nothing where = PkgId(into) if where.uuid === nothing @@ -1279,14 +1473,6 @@ function require(into::Module, mod::Symbol) end end -mutable struct PkgOrigin - path::Union{String,Nothing} - cachepath::Union{String,Nothing} - version::Union{VersionNumber,Nothing} -end -PkgOrigin() = PkgOrigin(nothing, nothing, nothing) -const pkgorigins = Dict{PkgId,PkgOrigin}() - require(uuidkey::PkgId) = @lock require_lock _require_prelocked(uuidkey) function _require_prelocked(uuidkey::PkgId, env=nothing) @@ -1297,6 +1483,7 @@ function _require_prelocked(uuidkey::PkgId, env=nothing) error("package `$(uuidkey.name)` did not define the expected \ module `$(uuidkey.name)`, check for typos in package module name") end + insert_extension_triggers(uuidkey) # After successfully loading, notify downstream consumers run_package_callbacks(uuidkey) else @@ -1305,6 +1492,14 @@ function _require_prelocked(uuidkey::PkgId, env=nothing) return newm end +mutable struct PkgOrigin + path::Union{String,Nothing} + cachepath::Union{String,Nothing} + version::Union{VersionNumber,Nothing} +end +PkgOrigin() = PkgOrigin(nothing, nothing, nothing) +const pkgorigins = Dict{PkgId,PkgOrigin}() + const loaded_modules = Dict{PkgId,Module}() const loaded_modules_order = Vector{Module}() const module_keys = IdDict{Module,PkgId}() # the reverse @@ -1479,6 +1674,7 @@ function _require_from_serialized(uuidkey::PkgId, path::String) set_pkgorigin_version_path(uuidkey, nothing) newm = _tryrequire_from_serialized(uuidkey, path) newm isa Module || throw(newm) + insert_extension_triggers(uuidkey) # After successfully loading, notify downstream consumers run_package_callbacks(uuidkey) return newm @@ -1711,6 +1907,7 @@ function create_expr_cache(pkg::PkgId, input::String, output::String, concrete_d "w", stdout) # write data over stdin to avoid the (unlikely) case of exceeding max command line size write(io.in, """ + empty!(Base.EXT_DORMITORY) # If we have a custom sysimage with `EXT_DORMITORY` prepopulated Base.include_package_for_output($(pkg_str(pkg)), $(repr(abspath(input))), $(repr(depot_path)), $(repr(dl_load_path)), $(repr(load_path)), $deps, $(repr(source_path(nothing)))) """) diff --git a/doc/src/manual/code-loading.md b/doc/src/manual/code-loading.md index d6f359f83d5cb4..f9575b0159d8c0 100644 --- a/doc/src/manual/code-loading.md +++ b/doc/src/manual/code-loading.md @@ -348,7 +348,46 @@ The subscripted `rootsᵢ`, `graphᵢ` and `pathsᵢ` variables correspond to th 2. Packages in non-primary environments can end up using incompatible versions of their dependencies even if their own environments are entirely compatible. This can happen when one of their dependencies is shadowed by a version in an earlier environment in the stack (either by graph or path, or both). Since the primary environment is typically the environment of a project you're working on, while environments later in the stack contain additional tools, this is the right trade-off: it's better to break your development tools but keep the project working. When such incompatibilities occur, you'll typically want to upgrade your dev tools to versions that are compatible with the main project. +### "Extension"s +An "extension" is a module that is automatically loaded when a specified set of other packages (its "extension dependencies") are loaded in the current Julia session. The extension dependencies of an extension are a subset of those packages listed under the `[weakdeps]` section of a Project file. Extensions are defined under the `[extensions]` section in the project file: + +```toml +name = "MyPackage" + +[weakdeps] +ExtDep = "c9a23..." # uuid +OtherExtDep = "862e..." # uuid + +[extensions] +BarExt = ["ExtDep", "OtherExtDep"] +FooExt = "ExtDep" +... +``` + +The keys under `extensions` are the name of the extensions. +They are loaded when all the packages on the right hand side (the extension dependencies) of that extension are loaded. +If an extension only has one extension dependency the list of extension dependencies can be written as just a string for brevity. +The location for the entry point of the extension is either in `ext/FooExt.jl` or `ext/FooExt/FooExt.jl` for +extension `FooExt`. +The content of an extension is often structured as: + +``` +module FooExt + +# Load main package and extension dependencies +using MyPackage, ExtDep + +# Extend functionality in main package with types from the extension dependencies +MyPackage.func(x::ExtDep.SomeStruct) = ... + +end +``` + +When a package with extensions is added to an environment, the `weakdeps` and `extensions` sections +are stored in the manifest file in the section for that package. The dependency lookup rules for +a package are the same as for its "parent" except that the listed extension dependencies are also considered as +dependencies. ### Package/Environment Preferences Preferences are dictionaries of metadata that influence package behavior within an environment. diff --git a/test/loading.jl b/test/loading.jl index d057f0b3c37024..99f39ae2375324 100644 --- a/test/loading.jl +++ b/test/loading.jl @@ -991,5 +991,36 @@ end end end +@testset "Extensions" begin + old_depot_path = copy(DEPOT_PATH) + try + tmp = mktempdir() + push!(empty!(DEPOT_PATH), joinpath(tmp, "depot")) + + proj = joinpath(@__DIR__, "project", "Extensions", "HasDepWithExtensions.jl") + for i in 1:2 # Once when requiring precomilation, once where it is already precompiled + cmd = `$(Base.julia_cmd()) --project=$proj --startup-file=no -e ' + begin + using HasExtensions + # Base.get_extension(HasExtensions, :Extension) === nothing || error("unexpectedly got an extension") + HasExtensions.ext_loaded && error("ext_loaded set") + using HasDepWithExtensions + # Base.get_extension(HasExtensions, :Extension).extvar == 1 || error("extvar in Extension not set") + HasExtensions.ext_loaded || error("ext_loaded not set") + HasExtensions.ext_folder_loaded && error("ext_folder_loaded set") + HasDepWithExtensions.do_something() || error("do_something errored") + using ExtDep2 + HasExtensions.ext_folder_loaded || error("ext_folder_loaded not set") + + end + '` + @test success(cmd) + end + finally + copy!(DEPOT_PATH, old_depot_path) + end +end + + empty!(Base.DEPOT_PATH) append!(Base.DEPOT_PATH, original_depot_path) diff --git a/test/project/Extensions/ExtDep.jl/Project.toml b/test/project/Extensions/ExtDep.jl/Project.toml new file mode 100644 index 00000000000000..93c5e3925f06b6 --- /dev/null +++ b/test/project/Extensions/ExtDep.jl/Project.toml @@ -0,0 +1,3 @@ +name = "ExtDep" +uuid = "fa069be4-f60b-4d4c-8b95-f8008775090c" +version = "0.1.0" diff --git a/test/project/Extensions/ExtDep.jl/src/ExtDep.jl b/test/project/Extensions/ExtDep.jl/src/ExtDep.jl new file mode 100644 index 00000000000000..f0ca8c62d04b23 --- /dev/null +++ b/test/project/Extensions/ExtDep.jl/src/ExtDep.jl @@ -0,0 +1,5 @@ +module ExtDep + +struct ExtDepStruct end + +end # module ExtDep diff --git a/test/project/Extensions/ExtDep2/Project.toml b/test/project/Extensions/ExtDep2/Project.toml new file mode 100644 index 00000000000000..b25b99615b185d --- /dev/null +++ b/test/project/Extensions/ExtDep2/Project.toml @@ -0,0 +1,3 @@ +name = "ExtDep2" +uuid = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d" +version = "0.1.0" diff --git a/test/project/Extensions/ExtDep2/src/ExtDep2.jl b/test/project/Extensions/ExtDep2/src/ExtDep2.jl new file mode 100644 index 00000000000000..969905e25992fe --- /dev/null +++ b/test/project/Extensions/ExtDep2/src/ExtDep2.jl @@ -0,0 +1,5 @@ +module ExtDep2 + +greet() = print("Hello World!") + +end # module ExtDep2 diff --git a/test/project/Extensions/HasDepWithExtensions.jl/Manifest.toml b/test/project/Extensions/HasDepWithExtensions.jl/Manifest.toml new file mode 100644 index 00000000000000..c96e3ef508ca8b --- /dev/null +++ b/test/project/Extensions/HasDepWithExtensions.jl/Manifest.toml @@ -0,0 +1,25 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.10.0-DEV" +manifest_format = "2.0" +project_hash = "7cbe1857ecc6692a8cc8be428a5ad5073531ff98" + +[[deps.ExtDep]] +path = "../ExtDep.jl" +uuid = "fa069be4-f60b-4d4c-8b95-f8008775090c" +version = "0.1.0" + +[[deps.ExtDep2]] +path = "../ExtDep2" +uuid = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d" +version = "0.1.0" + +[[deps.HasExtensions]] +weakdeps = ["ExtDep", "ExtDep2"] +path = "../HasExtensions.jl" +uuid = "4d3288b3-3afc-4bb6-85f3-489fffe514c8" +version = "0.1.0" + + [deps.HasExtensions.extensions] + Extension = "ExtDep" + ExtensionFolder = ["ExtDep", "ExtDep2"] diff --git a/test/project/Extensions/HasDepWithExtensions.jl/Project.toml b/test/project/Extensions/HasDepWithExtensions.jl/Project.toml new file mode 100644 index 00000000000000..8f308a9fbee727 --- /dev/null +++ b/test/project/Extensions/HasDepWithExtensions.jl/Project.toml @@ -0,0 +1,8 @@ +name = "HasDepWithExtensions" +uuid = "d4ef3d4a-8e22-4710-85d8-c6cf2eb9efca" +version = "0.1.0" + +[deps] +ExtDep = "fa069be4-f60b-4d4c-8b95-f8008775090c" +ExtDep2 = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d" +HasExtensions = "4d3288b3-3afc-4bb6-85f3-489fffe514c8" diff --git a/test/project/Extensions/HasDepWithExtensions.jl/src/HasDepWithExtensions.jl b/test/project/Extensions/HasDepWithExtensions.jl/src/HasDepWithExtensions.jl new file mode 100644 index 00000000000000..d64cbc680e3a56 --- /dev/null +++ b/test/project/Extensions/HasDepWithExtensions.jl/src/HasDepWithExtensions.jl @@ -0,0 +1,13 @@ +module HasDepWithExtensions + +using HasExtensions: HasExtensions, HasExtensionsStruct +using ExtDep: ExtDepStruct +# Loading ExtDep makes the extension "Extension" load + +function do_something() + HasExtensions.foo(HasExtensionsStruct()) == 1 || error() + HasExtensions.foo(ExtDepStruct()) == 2 || error() + return true +end + +end # module diff --git a/test/project/Extensions/HasExtensions.jl/Manifest.toml b/test/project/Extensions/HasExtensions.jl/Manifest.toml new file mode 100644 index 00000000000000..55f7958701a75a --- /dev/null +++ b/test/project/Extensions/HasExtensions.jl/Manifest.toml @@ -0,0 +1,7 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.10.0-DEV" +manifest_format = "2.0" +project_hash = "c87947f1f1f070eea848950c304d668a112dec3d" + +[deps] diff --git a/test/project/Extensions/HasExtensions.jl/Project.toml b/test/project/Extensions/HasExtensions.jl/Project.toml new file mode 100644 index 00000000000000..72577de36d65de --- /dev/null +++ b/test/project/Extensions/HasExtensions.jl/Project.toml @@ -0,0 +1,11 @@ +name = "HasExtensions" +uuid = "4d3288b3-3afc-4bb6-85f3-489fffe514c8" +version = "0.1.0" + +[weakdeps] +ExtDep = "fa069be4-f60b-4d4c-8b95-f8008775090c" +ExtDep2 = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d" + +[extensions] +Extension = "ExtDep" +ExtensionFolder = ["ExtDep", "ExtDep2"] diff --git a/test/project/Extensions/HasExtensions.jl/ext/Extension.jl b/test/project/Extensions/HasExtensions.jl/ext/Extension.jl new file mode 100644 index 00000000000000..9216c403a485ad --- /dev/null +++ b/test/project/Extensions/HasExtensions.jl/ext/Extension.jl @@ -0,0 +1,13 @@ +module Extension + +using HasExtensions, ExtDep + +HasExtensions.foo(::ExtDep.ExtDepStruct) = 2 + +function __init__() + HasExtensions.ext_loaded = true +end + +const extvar = 1 + +end diff --git a/test/project/Extensions/HasExtensions.jl/ext/ExtensionFolder/ExtensionFolder.jl b/test/project/Extensions/HasExtensions.jl/ext/ExtensionFolder/ExtensionFolder.jl new file mode 100644 index 00000000000000..1fb90d7989ca9e --- /dev/null +++ b/test/project/Extensions/HasExtensions.jl/ext/ExtensionFolder/ExtensionFolder.jl @@ -0,0 +1,9 @@ +module ExtensionFolder + +using ExtDep, ExtDep2, HasExtensions + +function __init__() + HasExtensions.ext_folder_loaded = true +end + +end diff --git a/test/project/Extensions/HasExtensions.jl/src/HasExtensions.jl b/test/project/Extensions/HasExtensions.jl/src/HasExtensions.jl new file mode 100644 index 00000000000000..dbfaeec4f88120 --- /dev/null +++ b/test/project/Extensions/HasExtensions.jl/src/HasExtensions.jl @@ -0,0 +1,10 @@ +module HasExtensions + +struct HasExtensionsStruct end + +foo(::HasExtensionsStruct) = 1 + +ext_loaded = false +ext_folder_loaded = false + +end # module From f00d729c16c80e6e090505144e454ec655601bb8 Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Wed, 7 Dec 2022 17:58:39 +0100 Subject: [PATCH 084/387] Bring back methods for addition of `UniformScaling` (#47824) --- stdlib/LinearAlgebra/src/special.jl | 89 ++++++++++++++--------------- 1 file changed, 42 insertions(+), 47 deletions(-) diff --git a/stdlib/LinearAlgebra/src/special.jl b/stdlib/LinearAlgebra/src/special.jl index d208e80c6c5b17..9c7594b9a13cf3 100644 --- a/stdlib/LinearAlgebra/src/special.jl +++ b/stdlib/LinearAlgebra/src/special.jl @@ -117,7 +117,7 @@ end # the off diagonal could be a different type after the operation resulting in # an error. See issue #28994 -function (+)(A::Bidiagonal, B::Diagonal) +@commutative function (+)(A::Bidiagonal, B::Diagonal) newdv = A.dv + B.diag Bidiagonal(newdv, typeof(newdv)(A.ev), A.uplo) end @@ -127,104 +127,98 @@ function (-)(A::Bidiagonal, B::Diagonal) Bidiagonal(newdv, typeof(newdv)(A.ev), A.uplo) end -function (+)(A::Diagonal, B::Bidiagonal) - newdv = A.diag + B.dv - Bidiagonal(newdv, typeof(newdv)(B.ev), B.uplo) -end - function (-)(A::Diagonal, B::Bidiagonal) - newdv = A.diag-B.dv + newdv = A.diag - B.dv Bidiagonal(newdv, typeof(newdv)(-B.ev), B.uplo) end -function (+)(A::Diagonal, B::SymTridiagonal) - newdv = A.diag+B.dv - SymTridiagonal(A.diag+B.dv, typeof(newdv)(B.ev)) +@commutative function (+)(A::Diagonal, B::SymTridiagonal) + newdv = A.diag + B.dv + SymTridiagonal(A.diag + B.dv, typeof(newdv)(B.ev)) end function (-)(A::Diagonal, B::SymTridiagonal) - newdv = A.diag-B.dv + newdv = A.diag - B.dv SymTridiagonal(newdv, typeof(newdv)(-B.ev)) end -function (+)(A::SymTridiagonal, B::Diagonal) - newdv = A.dv+B.diag - SymTridiagonal(newdv, typeof(newdv)(A.ev)) -end - function (-)(A::SymTridiagonal, B::Diagonal) - newdv = A.dv-B.diag + newdv = A.dv - B.diag SymTridiagonal(newdv, typeof(newdv)(A.ev)) end # this set doesn't have the aforementioned problem -+(A::Tridiagonal, B::SymTridiagonal) = Tridiagonal(A.dl+_evview(B), A.d+B.dv, A.du+_evview(B)) +@commutative (+)(A::Tridiagonal, B::SymTridiagonal) = Tridiagonal(A.dl+_evview(B), A.d+B.dv, A.du+_evview(B)) -(A::Tridiagonal, B::SymTridiagonal) = Tridiagonal(A.dl-_evview(B), A.d-B.dv, A.du-_evview(B)) -+(A::SymTridiagonal, B::Tridiagonal) = Tridiagonal(_evview(A)+B.dl, A.dv+B.d, _evview(A)+B.du) -(A::SymTridiagonal, B::Tridiagonal) = Tridiagonal(_evview(A)-B.dl, A.dv-B.d, _evview(A)-B.du) - -function (+)(A::Diagonal, B::Tridiagonal) - newdv = A.diag+B.d +@commutative function (+)(A::Diagonal, B::Tridiagonal) + newdv = A.diag + B.d Tridiagonal(typeof(newdv)(B.dl), newdv, typeof(newdv)(B.du)) end function (-)(A::Diagonal, B::Tridiagonal) - newdv = A.diag-B.d + newdv = A.diag - B.d Tridiagonal(typeof(newdv)(-B.dl), newdv, typeof(newdv)(-B.du)) end -function (+)(A::Tridiagonal, B::Diagonal) - newdv = A.d+B.diag - Tridiagonal(typeof(newdv)(A.dl), newdv, typeof(newdv)(A.du)) -end - function (-)(A::Tridiagonal, B::Diagonal) - newdv = A.d-B.diag + newdv = A.d - B.diag Tridiagonal(typeof(newdv)(A.dl), newdv, typeof(newdv)(A.du)) end -function (+)(A::Bidiagonal, B::Tridiagonal) - newdv = A.dv+B.d +@commutative function (+)(A::Bidiagonal, B::Tridiagonal) + newdv = A.dv + B.d Tridiagonal((A.uplo == 'U' ? (typeof(newdv)(B.dl), newdv, A.ev+B.du) : (A.ev+B.dl, newdv, typeof(newdv)(B.du)))...) end function (-)(A::Bidiagonal, B::Tridiagonal) - newdv = A.dv-B.d + newdv = A.dv - B.d Tridiagonal((A.uplo == 'U' ? (typeof(newdv)(-B.dl), newdv, A.ev-B.du) : (A.ev-B.dl, newdv, typeof(newdv)(-B.du)))...) end -function (+)(A::Tridiagonal, B::Bidiagonal) - newdv = A.d+B.dv - Tridiagonal((B.uplo == 'U' ? (typeof(newdv)(A.dl), newdv, A.du+B.ev) : (A.dl+B.ev, newdv, typeof(newdv)(A.du)))...) -end - function (-)(A::Tridiagonal, B::Bidiagonal) - newdv = A.d-B.dv + newdv = A.d - B.dv Tridiagonal((B.uplo == 'U' ? (typeof(newdv)(A.dl), newdv, A.du-B.ev) : (A.dl-B.ev, newdv, typeof(newdv)(A.du)))...) end -function (+)(A::Bidiagonal, B::SymTridiagonal) - newdv = A.dv+B.dv +@commutative function (+)(A::Bidiagonal, B::SymTridiagonal) + newdv = A.dv + B.dv Tridiagonal((A.uplo == 'U' ? (typeof(newdv)(_evview(B)), A.dv+B.dv, A.ev+_evview(B)) : (A.ev+_evview(B), A.dv+B.dv, typeof(newdv)(_evview(B))))...) end function (-)(A::Bidiagonal, B::SymTridiagonal) - newdv = A.dv-B.dv + newdv = A.dv - B.dv Tridiagonal((A.uplo == 'U' ? (typeof(newdv)(-_evview(B)), newdv, A.ev-_evview(B)) : (A.ev-_evview(B), newdv, typeof(newdv)(-_evview(B))))...) end -function (+)(A::SymTridiagonal, B::Bidiagonal) - newdv = A.dv+B.dv - Tridiagonal((B.uplo == 'U' ? (typeof(newdv)(_evview(A)), newdv, _evview(A)+B.ev) : (_evview(A)+B.ev, newdv, typeof(newdv)(_evview(A))))...) -end - function (-)(A::SymTridiagonal, B::Bidiagonal) - newdv = A.dv-B.dv + newdv = A.dv - B.dv Tridiagonal((B.uplo == 'U' ? (typeof(newdv)(_evview(A)), newdv, _evview(A)-B.ev) : (_evview(A)-B.ev, newdv, typeof(newdv)(_evview(A))))...) end +@commutative function (+)(A::Tridiagonal, B::UniformScaling) + newd = A.d .+ Ref(B) + Tridiagonal(typeof(newd)(A.dl), newd, typeof(newd)(A.du)) +end + +@commutative function (+)(A::SymTridiagonal, B::UniformScaling) + newdv = A.dv .+ Ref(B) + SymTridiagonal(newdv, typeof(newdv)(A.ev)) +end + +@commutative function (+)(A::Bidiagonal, B::UniformScaling) + newdv = A.dv .+ Ref(B) + Bidiagonal(newdv, typeof(newdv)(A.ev), A.uplo) +end + +@commutative function (+)(A::Diagonal, B::UniformScaling) + Diagonal(A.diag .+ Ref(B)) +end + +# StructuredMatrix - UniformScaling = StructuredMatrix + (-UniformScaling) => +# no need to define reversed order function (-)(A::UniformScaling, B::Tridiagonal) d = Ref(A) .- B.d Tridiagonal(convert(typeof(d), -B.dl), d, convert(typeof(d), -B.du)) @@ -240,6 +234,7 @@ end function (-)(A::UniformScaling, B::Diagonal) Diagonal(Ref(A) .- B.diag) end + lmul!(Q::AbstractQ, B::AbstractTriangular) = lmul!(Q, full!(B)) lmul!(Q::QRPackedQ, B::AbstractTriangular) = lmul!(Q, full!(B)) # disambiguation lmul!(Q::Adjoint{<:Any,<:AbstractQ}, B::AbstractTriangular) = lmul!(Q, full!(B)) From db00cc1a8455ceb4a8dc3cb8dd6ded3d62e46dcb Mon Sep 17 00:00:00 2001 From: DilumAluthgeBot <43731525+DilumAluthgeBot@users.noreply.github.com> Date: Wed, 7 Dec 2022 16:00:25 -0500 Subject: [PATCH 085/387] =?UTF-8?q?=F0=9F=A4=96=20Bump=20the=20Pkg=20stdli?= =?UTF-8?q?b=20from=20ed6a5497e=20to=205d8b9ddb8=20(#47828)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dilum Aluthge --- .../Pkg-5d8b9ddb89ef7eff7c4d032cd4a7e33778c0bbde.tar.gz/md5 | 1 + .../Pkg-5d8b9ddb89ef7eff7c4d032cd4a7e33778c0bbde.tar.gz/sha512 | 1 + .../Pkg-ed6a5497e46ed541b2718c404c0f468b7f92263a.tar.gz/md5 | 1 - .../Pkg-ed6a5497e46ed541b2718c404c0f468b7f92263a.tar.gz/sha512 | 1 - stdlib/Pkg.version | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 deps/checksums/Pkg-5d8b9ddb89ef7eff7c4d032cd4a7e33778c0bbde.tar.gz/md5 create mode 100644 deps/checksums/Pkg-5d8b9ddb89ef7eff7c4d032cd4a7e33778c0bbde.tar.gz/sha512 delete mode 100644 deps/checksums/Pkg-ed6a5497e46ed541b2718c404c0f468b7f92263a.tar.gz/md5 delete mode 100644 deps/checksums/Pkg-ed6a5497e46ed541b2718c404c0f468b7f92263a.tar.gz/sha512 diff --git a/deps/checksums/Pkg-5d8b9ddb89ef7eff7c4d032cd4a7e33778c0bbde.tar.gz/md5 b/deps/checksums/Pkg-5d8b9ddb89ef7eff7c4d032cd4a7e33778c0bbde.tar.gz/md5 new file mode 100644 index 00000000000000..24682e718f7017 --- /dev/null +++ b/deps/checksums/Pkg-5d8b9ddb89ef7eff7c4d032cd4a7e33778c0bbde.tar.gz/md5 @@ -0,0 +1 @@ +e0841b6343d50524c3bf694cab48ac16 diff --git a/deps/checksums/Pkg-5d8b9ddb89ef7eff7c4d032cd4a7e33778c0bbde.tar.gz/sha512 b/deps/checksums/Pkg-5d8b9ddb89ef7eff7c4d032cd4a7e33778c0bbde.tar.gz/sha512 new file mode 100644 index 00000000000000..15829d6b80fa30 --- /dev/null +++ b/deps/checksums/Pkg-5d8b9ddb89ef7eff7c4d032cd4a7e33778c0bbde.tar.gz/sha512 @@ -0,0 +1 @@ +89ed36a9e9b4b297d9480474401b2b337d736bc307684bb4d35841159400ff651d5fc57d7cd643a0d4a9dbd01d2773e86e32b3cbfb9e5a8df5dac64990ea99d0 diff --git a/deps/checksums/Pkg-ed6a5497e46ed541b2718c404c0f468b7f92263a.tar.gz/md5 b/deps/checksums/Pkg-ed6a5497e46ed541b2718c404c0f468b7f92263a.tar.gz/md5 deleted file mode 100644 index 8e1c22b677fcd8..00000000000000 --- a/deps/checksums/Pkg-ed6a5497e46ed541b2718c404c0f468b7f92263a.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -4fe1e70708ff64fae949facfa3a7d419 diff --git a/deps/checksums/Pkg-ed6a5497e46ed541b2718c404c0f468b7f92263a.tar.gz/sha512 b/deps/checksums/Pkg-ed6a5497e46ed541b2718c404c0f468b7f92263a.tar.gz/sha512 deleted file mode 100644 index 72bc2e7bdaf205..00000000000000 --- a/deps/checksums/Pkg-ed6a5497e46ed541b2718c404c0f468b7f92263a.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -806b5e215a4670b6bceaa85b20ebf305f07fd84700e02f2471ed52c18ee01323dd151141efff1904678aedbf832b72c6ab9fb031ea30189c897d934870c99c35 diff --git a/stdlib/Pkg.version b/stdlib/Pkg.version index faff813896433b..9e91595d927d02 100644 --- a/stdlib/Pkg.version +++ b/stdlib/Pkg.version @@ -1,4 +1,4 @@ PKG_BRANCH = master -PKG_SHA1 = ed6a5497e46ed541b2718c404c0f468b7f92263a +PKG_SHA1 = 5d8b9ddb89ef7eff7c4d032cd4a7e33778c0bbde PKG_GIT_URL := https://github.com/JuliaLang/Pkg.jl.git PKG_TAR_URL = https://api.github.com/repos/JuliaLang/Pkg.jl/tarball/$1 From c8662b593a245e3d1efa5b0d2b60175cfc23ebc7 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 7 Dec 2022 17:15:33 -0500 Subject: [PATCH 086/387] more field property annotations (#47677) Mostly looking at fixing more implicit data race with more atomics. Some of this (at the Julia level) is not strictly necessary since we already intend to start using implicit release-consume ordering for references. But since the user should not be changing these fields anyways, or risk severe breakage, this should be fine to mark explicitly. --- base/Base.jl | 2 +- base/compiler/tfuncs.jl | 58 ++++++++++------- base/operators.jl | 2 +- base/reflection.jl | 23 +++++++ doc/src/base/base.md | 1 + src/Makefile | 2 +- src/ccall.cpp | 10 +-- src/codegen.cpp | 76 +++++++++++++---------- src/datatype.c | 2 +- src/gf.c | 29 ++++----- src/init.c | 2 +- src/interpreter.c | 4 +- src/jitlayers.cpp | 2 +- src/jltypes.c | 26 ++++++-- src/julia.h | 54 +++++++--------- src/method.c | 24 ++++--- src/module.c | 69 +++++++++++--------- src/rtutils.c | 2 +- src/stackwalk.c | 2 +- src/staticdata.c | 4 +- src/toplevel.c | 2 +- stdlib/Serialization/src/Serialization.jl | 4 +- test/atomics.jl | 6 +- test/core.jl | 18 +++++- test/stacktraces.jl | 2 +- 25 files changed, 259 insertions(+), 167 deletions(-) diff --git a/base/Base.jl b/base/Base.jl index 29a6f9ed4366d4..0c53a8bc9124b1 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -112,7 +112,7 @@ function Core.kwcall(kwargs, ::typeof(invoke), f, T, args...) return invoke(Core.kwcall, T, kwargs, f, args...) end # invoke does not have its own call cache, but kwcall for invoke does -typeof(invoke).name.mt.max_args = 3 # invoke, f, T, args... +setfield!(typeof(invoke).name.mt, :max_args, 3, :monotonic) # invoke, f, T, args... # core operations & types include("promotion.jl") diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 50c2f1e15a0898..82ed8f19a37b62 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -809,13 +809,14 @@ function try_compute_fieldidx(typ::DataType, @nospecialize(field)) return field end -function getfield_boundscheck(argtypes::Vector{Any}) # ::Union{Bool, Nothing, Type{Bool}} +function getfield_boundscheck(argtypes::Vector{Any}) # ::Union{Bool, Nothing} if length(argtypes) == 2 return true elseif length(argtypes) == 3 boundscheck = argtypes[3] - if boundscheck === Const(:not_atomic) # TODO: this is assuming not atomic - boundscheck = Bool + isvarargtype(boundscheck) && return nothing + if widenconst(boundscheck) === Symbol + return true end elseif length(argtypes) == 4 boundscheck = argtypes[4] @@ -823,22 +824,43 @@ function getfield_boundscheck(argtypes::Vector{Any}) # ::Union{Bool, Nothing, Ty return nothing end isvarargtype(boundscheck) && return nothing - widenconst(boundscheck) !== Bool && return nothing + widenconst(boundscheck) === Bool || return nothing boundscheck = widenconditional(boundscheck) if isa(boundscheck, Const) - return boundscheck.val + return boundscheck.val::Bool else - return Bool + return nothing end end -function getfield_nothrow(argtypes::Vector{Any}) - boundscheck = getfield_boundscheck(argtypes) +function getfield_nothrow(argtypes::Vector{Any}, boundscheck::Union{Bool,Nothing}=getfield_boundscheck(argtypes)) + @specialize boundscheck boundscheck === nothing && return false - return getfield_nothrow(argtypes[1], argtypes[2], !(boundscheck === false)) + ordering = Const(:not_atomic) + if length(argtypes) == 3 + isvarargtype(argtypes[3]) && return false + if widenconst(argtypes[3]) !== Bool + ordering = argtypes[3] + end + elseif length(argtypes) == 4 + ordering = argtypes[4] + elseif length(argtypes) != 2 + return false + end + isvarargtype(ordering) && return false + widenconst(ordering) === Symbol || return false + if isa(ordering, Const) + ordering = ordering.val::Symbol + if ordering !== :not_atomic # TODO: this is assuming not atomic + return false + end + return getfield_nothrow(argtypes[1], argtypes[2], !(boundscheck === false)) + else + return false + end end function getfield_nothrow(@nospecialize(s00), @nospecialize(name), boundscheck::Bool) - # If we don't have boundscheck and don't know the field, don't even bother + # If we don't have boundscheck off and don't know the field, don't even bother if boundscheck isa(name, Const) || return false end @@ -880,7 +902,6 @@ function getfield_nothrow(@nospecialize(s00), @nospecialize(name), boundscheck:: if isa(s, DataType) # Can't say anything about abstract types isabstracttype(s) && return false - s.name.atomicfields == C_NULL || return false # TODO: currently we're only testing for ordering === :not_atomic # If all fields are always initialized, and bounds check is disabled, we can assume # we don't throw if !boundscheck && s.name.n_uninitialized == 0 @@ -890,6 +911,7 @@ function getfield_nothrow(@nospecialize(s00), @nospecialize(name), boundscheck:: isa(name, Const) || return false field = try_compute_fieldidx(s, name.val) field === nothing && return false + isfieldatomic(s, field) && return false # TODO: currently we're only testing for ordering === :not_atomic field <= datatype_min_ninitialized(s) && return true # `try_compute_fieldidx` already check for field index bound. !isvatuple(s) && isbitstype(fieldtype(s0, field)) && return true @@ -1210,12 +1232,12 @@ function setfield!_nothrow(@specialize(𝕃::AbstractLattice), s00, name, v) # Can't say anything about abstract types isabstracttype(s) && return false ismutabletype(s) || return false - s.name.atomicfields == C_NULL || return false # TODO: currently we're only testing for ordering === :not_atomic isa(name, Const) || return false field = try_compute_fieldidx(s, name.val) field === nothing && return false # `try_compute_fieldidx` already check for field index bound. isconst(s, field) && return false + isfieldatomic(s, field) && return false # TODO: currently we're only testing for ordering === :not_atomic v_expected = fieldtype(s0, field) return v ⊑ v_expected end @@ -2074,20 +2096,14 @@ function getfield_effects(argtypes::Vector{Any}, @nospecialize(rt)) if !(length(argtypes) ≥ 2 && getfield_notundefined(widenconst(obj), argtypes[2])) consistent = ALWAYS_FALSE end - if getfield_boundscheck(argtypes) !== true + nothrow = getfield_nothrow(argtypes, true) + if !nothrow && getfield_boundscheck(argtypes) !== true # If we cannot independently prove inboundsness, taint consistency. # The inbounds-ness assertion requires dynamic reachability, while # :consistent needs to be true for all input values. # N.B. We do not taint for `--check-bounds=no` here -that happens in # InferenceState. - if length(argtypes) ≥ 2 && getfield_nothrow(argtypes[1], argtypes[2], true) - nothrow = true - else - consistent = ALWAYS_FALSE - nothrow = false - end - else - nothrow = getfield_nothrow(argtypes) + consistent = ALWAYS_FALSE end if hasintersect(widenconst(obj), Module) inaccessiblememonly = getglobal_effects(argtypes, rt).inaccessiblememonly diff --git a/base/operators.jl b/base/operators.jl index 4d3faec6f61cb6..2542acbde27d48 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -568,7 +568,7 @@ function afoldl(op, a, bs...) end return y end -typeof(afoldl).name.mt.max_args = 34 +setfield!(typeof(afoldl).name.mt, :max_args, 34, :monotonic) for op in (:+, :*, :&, :|, :xor, :min, :max, :kron) @eval begin diff --git a/base/reflection.jl b/base/reflection.jl index 0157616a4b8e3d..2b559b73261c65 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -294,6 +294,29 @@ function isconst(@nospecialize(t::Type), s::Int) return unsafe_load(Ptr{UInt32}(constfields), 1 + s÷32) & (1 << (s%32)) != 0 end +""" + isfieldatomic(t::DataType, s::Union{Int,Symbol}) -> Bool + +Determine whether a field `s` is declared `@atomic` in a given type `t`. +""" +function isfieldatomic(@nospecialize(t::Type), s::Symbol) + t = unwrap_unionall(t) + isa(t, DataType) || return false + return isfieldatomic(t, fieldindex(t, s, false)) +end +function isfieldatomic(@nospecialize(t::Type), s::Int) + t = unwrap_unionall(t) + # TODO: what to do for `Union`? + isa(t, DataType) || return false # uncertain + ismutabletype(t) || return false # immutable structs are never atomic + 1 <= s <= length(t.name.names) || return false # OOB reads are not atomic (they always throw) + atomicfields = t.name.atomicfields + atomicfields === C_NULL && return false + s -= 1 + return unsafe_load(Ptr{UInt32}(atomicfields), 1 + s÷32) & (1 << (s%32)) != 0 +end + + """ @locals() diff --git a/doc/src/base/base.md b/doc/src/base/base.md index 704a0ff9fe2f11..72a8ec4db613c2 100644 --- a/doc/src/base/base.md +++ b/doc/src/base/base.md @@ -188,6 +188,7 @@ Base.fieldcount Base.hasfield Core.nfields Base.isconst +Base.isfieldatomic ``` ### Memory layout diff --git a/src/Makefile b/src/Makefile index 380d2687e75a10..11d5afa963a8cf 100644 --- a/src/Makefile +++ b/src/Makefile @@ -455,7 +455,7 @@ SA_EXCEPTIONS-jloptions.c := -Xanalyzer -analyzer-config -Xana SA_EXCEPTIONS-subtype.c := -Xanalyzer -analyzer-config -Xanalyzer silence-checkers="core.uninitialized.Assign;core.UndefinedBinaryOperatorResult" SA_EXCEPTIONS-codegen.c := -Xanalyzer -analyzer-config -Xanalyzer silence-checkers="core" # these need to be annotated (and possibly fixed) -SKIP_IMPLICIT_ATOMICS := module.c staticdata.c codegen.cpp +SKIP_IMPLICIT_ATOMICS := staticdata.c # these need to be annotated (and possibly fixed) SKIP_GC_CHECK := codegen.cpp rtutils.c diff --git a/src/ccall.cpp b/src/ccall.cpp index b2e66a1345f964..f1fe94a0d0ede9 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -85,7 +85,7 @@ static bool runtime_sym_gvs(jl_codectx_t &ctx, const char *f_lib, const char *f_ else { std::string name = "ccalllib_"; name += llvm::sys::path::filename(f_lib); - name += std::to_string(globalUniqueGeneratedNames++); + name += std::to_string(jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1)); runtime_lib = true; auto &libgv = ctx.emission_context.libMapGV[f_lib]; if (libgv.first == NULL) { @@ -105,7 +105,7 @@ static bool runtime_sym_gvs(jl_codectx_t &ctx, const char *f_lib, const char *f_ std::string name = "ccall_"; name += f_name; name += "_"; - name += std::to_string(globalUniqueGeneratedNames++); + name += std::to_string(jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1)); auto T_pvoidfunc = JuliaType::get_pvoidfunc_ty(M->getContext()); llvmgv = new GlobalVariable(*M, T_pvoidfunc, false, GlobalVariable::ExternalLinkage, @@ -215,7 +215,7 @@ static Value *runtime_sym_lookup( std::string gvname = "libname_"; gvname += f_name; gvname += "_"; - gvname += std::to_string(globalUniqueGeneratedNames++); + gvname += std::to_string(jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1)); llvmgv = new GlobalVariable(*jl_Module, T_pvoidfunc, false, GlobalVariable::ExternalLinkage, Constant::getNullValue(T_pvoidfunc), gvname); @@ -244,7 +244,7 @@ static GlobalVariable *emit_plt_thunk( libptrgv = prepare_global_in(M, libptrgv); llvmgv = prepare_global_in(M, llvmgv); std::string fname; - raw_string_ostream(fname) << "jlplt_" << f_name << "_" << globalUniqueGeneratedNames++; + raw_string_ostream(fname) << "jlplt_" << f_name << "_" << jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1); Function *plt = Function::Create(functype, GlobalVariable::ExternalLinkage, fname, M); @@ -858,7 +858,7 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar // Make sure to find a unique name std::string ir_name; while (true) { - raw_string_ostream(ir_name) << (ctx.f->getName().str()) << "u" << globalUniqueGeneratedNames++; + raw_string_ostream(ir_name) << (ctx.f->getName().str()) << "u" << jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1); if (jl_Module->getFunction(ir_name) == NULL) break; } diff --git a/src/codegen.cpp b/src/codegen.cpp index cdc5e00a262817..024f30ad576e93 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1198,7 +1198,7 @@ static const auto &builtin_func_map() { static const auto jl_new_opaque_closure_jlcall_func = new JuliaFunction{XSTR(jl_new_opaque_closure_jlcall), get_func_sig, get_func_attrs}; -static std::atomic globalUniqueGeneratedNames{0}; +static _Atomic(int) globalUniqueGeneratedNames{1}; // --- code generation --- extern "C" { @@ -2315,7 +2315,7 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex) if (b && b->constp) { if (b->deprecated) cg_bdw(ctx, b); - return b->value; + return jl_atomic_load_relaxed(&b->value); } return NULL; } @@ -2337,7 +2337,7 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex) if (b && b->constp) { if (b->deprecated) cg_bdw(ctx, b); - return b->value; + return jl_atomic_load_relaxed(&b->value); } } } @@ -2579,14 +2579,17 @@ static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t * if (bp == NULL) return jl_cgval_t(); bp = julia_binding_pvalue(ctx, bp); - if (bnd && bnd->value != NULL) { - if (bnd->constp) { - return mark_julia_const(ctx, bnd->value); + if (bnd) { + jl_value_t *v = jl_atomic_load_acquire(&bnd->value); // acquire value for ty + if (v != NULL) { + if (bnd->constp) + return mark_julia_const(ctx, v); + LoadInst *v = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*))); + v->setOrdering(order); + tbaa_decorate(ctx.tbaa().tbaa_binding, v); + jl_value_t *ty = jl_atomic_load_relaxed(&bnd->ty); + return mark_julia_type(ctx, v, true, ty); } - LoadInst *v = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*))); - v->setOrdering(order); - tbaa_decorate(ctx.tbaa().tbaa_binding, v); - return mark_julia_type(ctx, v, true, bnd->ty); } // todo: use type info to avoid undef check return emit_checked_var(ctx, bp, name, false, ctx.tbaa().tbaa_binding); @@ -2595,15 +2598,17 @@ static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t * static void emit_globalset(jl_codectx_t &ctx, jl_binding_t *bnd, Value *bp, const jl_cgval_t &rval_info, AtomicOrdering Order) { Value *rval = boxed(ctx, rval_info); - if (bnd && !bnd->constp && bnd->ty && jl_subtype(rval_info.typ, bnd->ty)) { - StoreInst *v = ctx.builder.CreateAlignedStore(rval, julia_binding_pvalue(ctx, bp), Align(sizeof(void*))); - v->setOrdering(Order); - tbaa_decorate(ctx.tbaa().tbaa_binding, v); - emit_write_barrier_binding(ctx, bp, rval); - } - else { - ctx.builder.CreateCall(prepare_call(jlcheckassign_func), { bp, mark_callee_rooted(ctx, rval) }); + if (bnd && !bnd->constp) { + jl_value_t *ty = jl_atomic_load_relaxed(&bnd->ty); + if (ty && jl_subtype(rval_info.typ, ty)) { // TODO: use typeassert here instead + StoreInst *v = ctx.builder.CreateAlignedStore(rval, julia_binding_pvalue(ctx, bp), Align(sizeof(void*))); + v->setOrdering(Order); + tbaa_decorate(ctx.tbaa().tbaa_binding, v); + emit_write_barrier_binding(ctx, bp, rval); + return; + } } + ctx.builder.CreateCall(prepare_call(jlcheckassign_func), { bp, mark_callee_rooted(ctx, rval) }); } static Value *emit_box_compare(jl_codectx_t &ctx, const jl_cgval_t &arg1, const jl_cgval_t &arg2, @@ -4028,6 +4033,7 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, const if (ctx.use_cache) { // optimization: emit the correct name immediately, if we know it // TODO: use `emitted` map here too to try to consolidate names? + // WARNING: isspecsig is protected by the codegen-lock. If that lock is removed, then the isspecsig load needs to be properly atomically sequenced with this. auto invoke = jl_atomic_load_relaxed(&codeinst->invoke); auto fptr = jl_atomic_load_relaxed(&codeinst->specptr.fptr); if (fptr) { @@ -4043,7 +4049,7 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, const need_to_emit = false; } if (need_to_emit) { - raw_string_ostream(name) << (specsig ? "j_" : "j1_") << name_from_method_instance(mi) << "_" << globalUniqueGeneratedNames++; + raw_string_ostream(name) << (specsig ? "j_" : "j1_") << name_from_method_instance(mi) << "_" << jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1); protoname = StringRef(name); } jl_returninfo_t::CallingConv cc = jl_returninfo_t::CallingConv::Boxed; @@ -4324,7 +4330,7 @@ static jl_cgval_t emit_isdefined(jl_codectx_t &ctx, jl_value_t *sym) } jl_binding_t *bnd = jl_get_binding(modu, name); if (bnd) { - if (bnd->value != NULL) + if (jl_atomic_load_relaxed(&bnd->value) != NULL) return mark_julia_const(ctx, jl_true); Value *bp = julia_binding_gv(ctx, bnd); bp = julia_binding_pvalue(ctx, bp); @@ -5052,10 +5058,8 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ } JL_CATCH { jl_value_t *e = jl_current_exception(); - // errors. boo. root it somehow :( - bnd = jl_get_binding_wr(ctx.module, (jl_sym_t*)jl_gensym(), 1); - bnd->value = e; - bnd->constp = 1; + // errors. boo. :( + e = jl_as_global_root(e); raise_exception(ctx, literal_pointer_val(ctx, e)); return ghostValue(ctx, jl_nothing_type); } @@ -5359,7 +5363,7 @@ static Function *emit_tojlinvoke(jl_code_instance_t *codeinst, Module *M, jl_cod ++EmittedToJLInvokes; jl_codectx_t ctx(M->getContext(), params); std::string name; - raw_string_ostream(name) << "tojlinvoke" << globalUniqueGeneratedNames++; + raw_string_ostream(name) << "tojlinvoke" << jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1); Function *f = Function::Create(ctx.types().T_jlfunc, GlobalVariable::InternalLinkage, name, M); @@ -5530,18 +5534,21 @@ static Function* gen_cfun_wrapper( if (lam && params.cache) { // TODO: this isn't ideal to be unconditionally calling type inference (and compile) from here codeinst = jl_compile_method_internal(lam, world); - assert(codeinst->invoke); - if (codeinst->invoke == jl_fptr_args_addr) { - callptr = codeinst->specptr.fptr; + auto invoke = jl_atomic_load_relaxed(&codeinst->invoke); + auto fptr = jl_atomic_load_relaxed(&codeinst->specptr.fptr); + assert(invoke); + // WARNING: this invoke load is protected by the codegen-lock. If that lock is removed, then the isspecsig load needs to be properly atomically sequenced with this. + if (invoke == jl_fptr_args_addr) { + callptr = fptr; calltype = 1; } - else if (codeinst->invoke == jl_fptr_const_return_addr) { + else if (invoke == jl_fptr_const_return_addr) { // don't need the fptr callptr = (void*)codeinst->rettype_const; calltype = 2; } else if (codeinst->isspecsig) { - callptr = codeinst->specptr.fptr; + callptr = fptr; calltype = 3; } astrt = codeinst->rettype; @@ -5555,7 +5562,7 @@ static Function* gen_cfun_wrapper( } std::string funcName; - raw_string_ostream(funcName) << "jlcapi_" << name << "_" << globalUniqueGeneratedNames++; + raw_string_ostream(funcName) << "jlcapi_" << name << "_" << jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1); Module *M = into; // Safe because ctx lock is held by params AttributeList attributes = sig.attributes; @@ -6748,7 +6755,7 @@ static jl_llvm_functions_t if (unadorned_name[0] == '@') unadorned_name++; #endif - funcName << unadorned_name << "_" << globalUniqueGeneratedNames++; + funcName << unadorned_name << "_" << jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1); declarations.specFunctionObject = funcName.str(); // allocate Function declarations and wrapper objects @@ -6790,7 +6797,7 @@ static jl_llvm_functions_t }(); std::string wrapName; - raw_string_ostream(wrapName) << "jfptr_" << unadorned_name << "_" << globalUniqueGeneratedNames++; + raw_string_ostream(wrapName) << "jfptr_" << unadorned_name << "_" << jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1); declarations.functionObject = wrapName; (void)gen_invoke_wrapper(lam, jlrettype, returninfo, retarg, declarations.functionObject, M, ctx.emission_context); // TODO: add attributes: maybe_mark_argument_dereferenceable(Arg, argType) @@ -8225,7 +8232,7 @@ jl_llvm_functions_t jl_emit_codeinst( if (// and there is something to delete (test this before calling jl_ir_inlining_cost) inferred != jl_nothing && // don't delete inlineable code, unless it is constant - (codeinst->invoke == jl_fptr_const_return_addr || + (jl_atomic_load_relaxed(&codeinst->invoke) == jl_fptr_const_return_addr || (jl_ir_inlining_cost((jl_array_t*)inferred) == UINT16_MAX)) && // don't delete code when generating a precompile file !(params.imaging || jl_options.incremental)) { @@ -8269,6 +8276,7 @@ void jl_compile_workqueue( // We need to emit a trampoline that loads the target address in an extern_module from a GV // Right now we will unecessarily emit a function we have already compiled in a native module // again in a calling module. + // WARNING: isspecsig is protected by the codegen-lock. If that lock is removed, then the isspecsig load needs to be properly atomically sequenced with this. if (params.cache && invoke != NULL) { auto fptr = jl_atomic_load_relaxed(&codeinst->specptr.fptr); if (invoke == jl_fptr_args_addr) { diff --git a/src/datatype.c b/src/datatype.c index 3fc37f199bfd82..920475af955292 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -51,7 +51,7 @@ JL_DLLEXPORT jl_methtable_t *jl_new_method_table(jl_sym_t *name, jl_module_t *mo jl_atomic_store_relaxed(&mt->defs, jl_nothing); jl_atomic_store_relaxed(&mt->leafcache, (jl_array_t*)jl_an_empty_vec_any); jl_atomic_store_relaxed(&mt->cache, jl_nothing); - mt->max_args = 0; + jl_atomic_store_relaxed(&mt->max_args, 0); mt->backedges = NULL; JL_MUTEX_INIT(&mt->writelock); mt->offs = 0; diff --git a/src/gf.c b/src/gf.c index 77cb7d236168db..df50b422160061 100644 --- a/src/gf.c +++ b/src/gf.c @@ -874,7 +874,7 @@ JL_DLLEXPORT int jl_isa_compileable_sig( if ((jl_value_t*)mt != jl_nothing) { // try to refine estimate of min and max if (kwmt && kwmt != jl_type_type_mt && kwmt != jl_nonfunction_mt && kwmt != jl_kwcall_mt) - nspec_min = kwmt->max_args + 2 + 2 * (mt == jl_kwcall_mt); + nspec_min = jl_atomic_load_relaxed(&kwmt->max_args) + 2 + 2 * (mt == jl_kwcall_mt); else nspec_max = nspec_min; } @@ -1081,7 +1081,7 @@ static jl_method_instance_t *cache_method( int cache_with_orig = 1; jl_tupletype_t *compilationsig = tt; jl_methtable_t *kwmt = mt == jl_kwcall_mt ? jl_kwmethod_table_for(definition->sig) : mt; - intptr_t nspec = (kwmt == NULL || kwmt == jl_type_type_mt || kwmt == jl_nonfunction_mt || kwmt == jl_kwcall_mt ? definition->nargs + 1 : kwmt->max_args + 2 + 2 * (mt == jl_kwcall_mt)); + intptr_t nspec = (kwmt == NULL || kwmt == jl_type_type_mt || kwmt == jl_nonfunction_mt || kwmt == jl_kwcall_mt ? definition->nargs + 1 : jl_atomic_load_relaxed(&kwmt->max_args) + 2 + 2 * (mt == jl_kwcall_mt)); jl_compilation_sig(tt, sparams, definition, nspec, &newparams); if (newparams) { compilationsig = jl_apply_tuple_type(newparams); @@ -1368,8 +1368,9 @@ static void update_max_args(jl_methtable_t *mt, jl_value_t *type) size_t na = jl_nparams(type); if (jl_va_tuple_kind((jl_datatype_t*)type) == JL_VARARG_UNBOUND) na--; - if (na > mt->max_args) - mt->max_args = na; + // update occurs inside mt->writelock + if (na > jl_atomic_load_relaxed(&mt->max_args)) + jl_atomic_store_relaxed(&mt->max_args, na); } jl_array_t *_jl_debug_method_invalidation JL_GLOBALLY_ROOTED = NULL; @@ -2160,8 +2161,8 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t jl_code_instance_t *ucache = jl_get_method_inferred(unspec, (jl_value_t*)jl_any_type, 1, ~(size_t)0); // ask codegen to make the fptr for unspec if (jl_atomic_load_acquire(&ucache->invoke) == NULL) { - if (def->source == jl_nothing && (ucache->def->uninferred == jl_nothing || - ucache->def->uninferred == NULL)) { + if (def->source == jl_nothing && (jl_atomic_load_relaxed(&ucache->def->uninferred) == jl_nothing || + jl_atomic_load_relaxed(&ucache->def->uninferred) == NULL)) { jl_printf(JL_STDERR, "source not available for "); jl_static_show(JL_STDERR, (jl_value_t*)mi); jl_printf(JL_STDERR, "\n"); @@ -2245,7 +2246,7 @@ JL_DLLEXPORT jl_value_t *jl_normalize_to_compilable_sig(jl_methtable_t *mt, jl_t jl_svec_t *newparams = NULL; JL_GC_PUSH2(&tt, &newparams); jl_methtable_t *kwmt = mt == jl_kwcall_mt ? jl_kwmethod_table_for(m->sig) : mt; - intptr_t nspec = (kwmt == NULL || kwmt == jl_type_type_mt || kwmt == jl_nonfunction_mt || kwmt == jl_kwcall_mt ? m->nargs + 1 : kwmt->max_args + 2 + 2 * (mt == jl_kwcall_mt)); + intptr_t nspec = (kwmt == NULL || kwmt == jl_type_type_mt || kwmt == jl_nonfunction_mt || kwmt == jl_kwcall_mt ? m->nargs + 1 : jl_atomic_load_relaxed(&kwmt->max_args) + 2 + 2 * (mt == jl_kwcall_mt)); jl_compilation_sig(ti, env, m, nspec, &newparams); tt = (newparams ? jl_apply_tuple_type(newparams) : ti); int is_compileable = ((jl_datatype_t*)ti)->isdispatchtuple || @@ -2408,7 +2409,7 @@ static void jl_compile_now(jl_method_instance_t *mi) JL_DLLEXPORT void jl_compile_method_instance(jl_method_instance_t *mi, jl_tupletype_t *types, size_t world) { size_t tworld = jl_typeinf_world; - mi->precompiled = 1; + jl_atomic_store_relaxed(&mi->precompiled, 1); if (jl_generating_output()) { jl_compile_now(mi); // In addition to full compilation of the compilation-signature, if `types` is more specific (e.g. due to nospecialize), @@ -2421,14 +2422,14 @@ JL_DLLEXPORT void jl_compile_method_instance(jl_method_instance_t *mi, jl_tuplet jl_value_t *types2 = NULL; JL_GC_PUSH2(&tpenv2, &types2); types2 = jl_type_intersection_env((jl_value_t*)types, (jl_value_t*)mi->def.method->sig, &tpenv2); - jl_method_instance_t *li2 = jl_specializations_get_linfo(mi->def.method, (jl_value_t*)types2, tpenv2); + jl_method_instance_t *mi2 = jl_specializations_get_linfo(mi->def.method, (jl_value_t*)types2, tpenv2); JL_GC_POP(); - li2->precompiled = 1; - if (jl_rettype_inferred(li2, world, world) == jl_nothing) - (void)jl_type_infer(li2, world, 1); + jl_atomic_store_relaxed(&mi2->precompiled, 1); + if (jl_rettype_inferred(mi2, world, world) == jl_nothing) + (void)jl_type_infer(mi2, world, 1); if (jl_typeinf_func && mi->def.method->primary_world <= tworld) { - if (jl_rettype_inferred(li2, tworld, tworld) == jl_nothing) - (void)jl_type_infer(li2, tworld, 1); + if (jl_rettype_inferred(mi2, tworld, tworld) == jl_nothing) + (void)jl_type_infer(mi2, tworld, 1); } } } diff --git a/src/init.c b/src/init.c index feee29d02ea700..19e4dafef44d38 100644 --- a/src/init.c +++ b/src/init.c @@ -902,7 +902,7 @@ static void post_boot_hooks(void) jl_pair_type = core("Pair"); jl_kwcall_func = core("kwcall"); jl_kwcall_mt = ((jl_datatype_t*)jl_typeof(jl_kwcall_func))->name->mt; - jl_kwcall_mt->max_args = 0; + jl_atomic_store_relaxed(&jl_kwcall_mt->max_args, 0); jl_weakref_type = (jl_datatype_t*)core("WeakRef"); jl_vecelement_typename = ((jl_datatype_t*)jl_unwrap_unionall(core("VecElement")))->name; diff --git a/src/interpreter.c b/src/interpreter.c index 37deb24188873f..cf9ddd5b50630d 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -632,7 +632,7 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, jl_code_info_t *jl_code_for_interpreter(jl_method_instance_t *mi) { - jl_code_info_t *src = (jl_code_info_t*)mi->uninferred; + jl_code_info_t *src = (jl_code_info_t*)jl_atomic_load_relaxed(&mi->uninferred); if (jl_is_method(mi->def.value)) { if (!src || (jl_value_t*)src == jl_nothing) { if (mi->def.method->source) { @@ -646,7 +646,7 @@ jl_code_info_t *jl_code_for_interpreter(jl_method_instance_t *mi) if (src && (jl_value_t*)src != jl_nothing) { JL_GC_PUSH1(&src); src = jl_uncompress_ir(mi->def.method, NULL, (jl_array_t*)src); - mi->uninferred = (jl_value_t*)src; + jl_atomic_store_release(&mi->uninferred, (jl_value_t*)src); jl_gc_wb(mi, src); JL_GC_POP(); } diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index b6a30d3380b27e..f5a0623cd8df66 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -481,7 +481,7 @@ void jl_generate_fptr_for_unspecialized_impl(jl_code_instance_t *unspec) src = jl_uncompress_ir(def, NULL, (jl_array_t*)src); } else { - src = (jl_code_info_t*)unspec->def->uninferred; + src = (jl_code_info_t*)jl_atomic_load_relaxed(&unspec->def->uninferred); } assert(src && jl_is_code_info(src)); ++UnspecFPtrCount; diff --git a/src/jltypes.c b/src/jltypes.c index 0daeae58b6b64e..9f93d60fb1acd4 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -2050,7 +2050,9 @@ void jl_init_types(void) JL_GC_DISABLED jl_any_type /*jl_int32_type*/, jl_any_type /*jl_uint8_type*/); const static uint32_t datatype_constfields[1] = { 0x00000057 }; // (1<<0)|(1<<1)|(1<<2)|(1<<4)|(1<<6) + const static uint32_t datatype_atomicfields[1] = { 0x00000028 }; // (1<<3)|(1<<5) jl_datatype_type->name->constfields = datatype_constfields; + jl_datatype_type->name->atomicfields = datatype_atomicfields; jl_precompute_memoized_dt(jl_datatype_type, 1); jl_typename_type->name = jl_new_typename_in(jl_symbol("TypeName"), core, 0, 1); @@ -2074,7 +2076,9 @@ void jl_init_types(void) JL_GC_DISABLED jl_any_type /*jl_uint8_type*/, jl_any_type /*jl_uint8_type*/); const static uint32_t typename_constfields[1] = { 0x00003a3f }; // (1<<0)|(1<<1)|(1<<2)|(1<<3)|(1<<4)|(1<<5)|(1<<9)|(1<<11)|(1<<12)|(1<<13) + const static uint32_t typename_atomicfields[1] = { 0x00000180 }; // (1<<7)|(1<<8) jl_typename_type->name->constfields = typename_constfields; + jl_typename_type->name->atomicfields = typename_atomicfields; jl_precompute_memoized_dt(jl_typename_type, 1); jl_methtable_type->name = jl_new_typename_in(jl_symbol("MethodTable"), core, 0, 1); @@ -2093,7 +2097,9 @@ void jl_init_types(void) JL_GC_DISABLED jl_any_type/*voidpointer*/, jl_any_type/*int32*/, jl_any_type/*uint8*/, jl_any_type/*uint8*/); const static uint32_t methtable_constfields[1] = { 0x00000020 }; // (1<<5); + const static uint32_t methtable_atomicfields[1] = { 0x0000001e }; // (1<<1)|(1<<2)|(1<<3)|(1<<4); jl_methtable_type->name->constfields = methtable_constfields; + jl_methtable_type->name->atomicfields = methtable_atomicfields; jl_precompute_memoized_dt(jl_methtable_type, 1); jl_symbol_type->name = jl_new_typename_in(jl_symbol("Symbol"), core, 0, 1); @@ -2133,21 +2139,27 @@ void jl_init_types(void) JL_GC_DISABLED jl_perm_symsvec(2, "a", "b"), jl_svec(2, jl_any_type, jl_any_type), jl_emptysvec, 0, 0, 2); + // It seems like we probably usually end up needing the box for kinds (used in an Any context), so force it to exist + jl_uniontype_type->name->mayinlinealloc = 0; jl_tvar_type = jl_new_datatype(jl_symbol("TypeVar"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(3, "name", "lb", "ub"), jl_svec(3, jl_symbol_type, jl_any_type, jl_any_type), jl_emptysvec, 0, 1, 3); + const static uint32_t tvar_constfields[1] = { 0x00000007 }; // all fields are constant, even though TypeVar itself has identity + jl_tvar_type->name->constfields = tvar_constfields; jl_unionall_type = jl_new_datatype(jl_symbol("UnionAll"), core, type_type, jl_emptysvec, jl_perm_symsvec(2, "var", "body"), jl_svec(2, jl_tvar_type, jl_any_type), jl_emptysvec, 0, 0, 2); + jl_unionall_type->name->mayinlinealloc = 0; jl_vararg_type = jl_new_datatype(jl_symbol("TypeofVararg"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(2, "T", "N"), jl_svec(2, jl_any_type, jl_any_type), jl_emptysvec, 0, 0, 0); + jl_vararg_type->name->mayinlinealloc = 0; jl_svec_t *anytuple_params = jl_svec(1, jl_wrap_vararg((jl_value_t*)jl_any_type, (jl_value_t*)NULL)); jl_anytuple_type = jl_new_datatype(jl_symbol("Tuple"), core, jl_any_type, anytuple_params, @@ -2243,6 +2255,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_any_type), jl_emptysvec, 0, 1, 6); + const static uint32_t typemap_level_atomicfields[1] = { 0x0000003f }; // (1<<0)|(1<<1)|(1<<2)|(1<<3)|(1<<4)|(1<<5) + jl_typemap_level_type->name->atomicfields = typemap_level_atomicfields; jl_typemap_entry_type = jl_new_datatype(jl_symbol("TypeMapEntry"), core, jl_any_type, jl_emptysvec, @@ -2270,8 +2284,10 @@ void jl_init_types(void) JL_GC_DISABLED jl_bool_type), jl_emptysvec, 0, 1, 4); - const static uint32_t typemap_entry_constfields[1] = { 0x000003fe }; // (1<<1)|(1<<2)|(1<<3)|(1<<4)|(1<<5)|(1<<6)|(1<<7)|(1<<8)|(1<<9); + const static uint32_t typemap_entry_constfields[1] = { 0x000003fe }; // (1<<1)|(1<<2)|(1<<3)|(1<<4)|(1<<5)|(1<<6)|(1<<7)|(1<<8)|(1<<9) + const static uint32_t typemap_entry_atomicfields[1] = { 0x00000001 }; // (1<<0) jl_typemap_entry_type->name->constfields = typemap_entry_constfields; + jl_typemap_entry_type->name->atomicfields = typemap_entry_atomicfields; jl_function_type = jl_new_abstracttype((jl_value_t*)jl_symbol("Function"), core, jl_any_type, jl_emptysvec); jl_builtin_type = jl_new_abstracttype((jl_value_t*)jl_symbol("Builtin"), core, jl_function_type, jl_emptysvec); @@ -2529,8 +2545,12 @@ void jl_init_types(void) JL_GC_DISABLED jl_bool_type), jl_emptysvec, 0, 1, 3); + // These fields should be constant, but Serialization wants to mutate them in initialization //const static uint32_t method_instance_constfields[1] = { 0x00000007 }; // (1<<0)|(1<<1)|(1<<2); + const static uint32_t method_instance_atomicfields[1] = { 0x00000148 }; // (1<<3)|(1<<6)|(1<<8); + //Fields 4 and 5 must be protected by method->write_lock, and thus all operations on jl_method_instance_t are threadsafe. TODO: except inInference //jl_method_instance_type->name->constfields = method_instance_constfields; + jl_method_instance_type->name->atomicfields = method_instance_atomicfields; jl_code_instance_type = jl_new_datatype(jl_symbol("CodeInstance"), core, @@ -2570,6 +2590,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_svecset(jl_code_instance_type->types, 1, jl_code_instance_type); const static uint32_t code_instance_constfields[1] = { 0b000001010110001 }; // Set fields 1, 5-6, 8, 10 as const const static uint32_t code_instance_atomicfields[1] = { 0b110100101000010 }; // Set fields 2, 7, 9, 12, 14-15 as atomic + //Fields 3-4 are only operated on by construction and deserialization, so are const at runtime //Fields 11 and 15 must be protected by locks, and thus all operations on jl_code_instance_t are threadsafe jl_code_instance_type->name->constfields = code_instance_constfields; jl_code_instance_type->name->atomicfields = code_instance_atomicfields; @@ -2742,9 +2763,6 @@ void jl_init_types(void) JL_GC_DISABLED // override the preferred layout for a couple types jl_lineinfonode_type->name->mayinlinealloc = 0; // FIXME: assumed to be a pointer by codegen - // It seems like we probably usually end up needing the box for kinds (used in an Any context)--but is that true? - jl_uniontype_type->name->mayinlinealloc = 0; - jl_unionall_type->name->mayinlinealloc = 0; } #ifdef __cplusplus diff --git a/src/julia.h b/src/julia.h index c40d3ce3b88e5e..f7cf69710fa624 100644 --- a/src/julia.h +++ b/src/julia.h @@ -364,12 +364,12 @@ struct _jl_method_instance_t { } def; // pointer back to the context for this code jl_value_t *specTypes; // argument types this was specialized for jl_svec_t *sparam_vals; // static parameter values, indexed by def.method->sparam_syms - jl_value_t *uninferred; // cached uncompressed code, for generated functions, top-level thunks, or the interpreter + _Atomic(jl_value_t*) uninferred; // cached uncompressed code, for generated functions, top-level thunks, or the interpreter jl_array_t *backedges; // list of method-instances which call this method-instance; `invoke` records (invokesig, caller) pairs jl_array_t *callbacks; // list of callback functions to inform external caches about invalidations _Atomic(struct _jl_code_instance_t*) cache; uint8_t inInference; // flags to tell if inference is running on this object - uint8_t precompiled; // true if this instance was generated by an explicit `precompile(...)` call + _Atomic(uint8_t) precompiled; // true if this instance was generated by an explicit `precompile(...)` call }; // OpaqueClosure @@ -400,39 +400,29 @@ typedef struct _jl_code_instance_t { //TODO: uint8_t absolute_max; // whether true max world is unknown // purity results -#ifdef JL_USE_ANON_UNIONS_FOR_PURITY_FLAGS // see also encode_effects() and decode_effects() in `base/compiler/effects.jl`, - union { - uint32_t ipo_purity_bits; - struct { - uint8_t ipo_consistent : 2; - uint8_t ipo_effect_free : 2; - uint8_t ipo_nothrow : 2; - uint8_t ipo_terminates : 2; - uint8_t ipo_nonoverlayed : 1; - uint8_t ipo_notaskstate : 2; - uint8_t ipo_inaccessiblememonly : 2; - } ipo_purity_flags; - }; - union { - uint32_t purity_bits; - struct { - uint8_t consistent : 2; - uint8_t effect_free : 2; - uint8_t nothrow : 2; - uint8_t terminates : 2; - uint8_t nonoverlayed : 1; - uint8_t notaskstate : 2; - uint8_t inaccessiblememonly : 2; - } purity_flags; - }; -#else uint32_t ipo_purity_bits; + // ipo_purity_flags: + // uint8_t ipo_consistent : 2; + // uint8_t ipo_effect_free : 2; + // uint8_t ipo_nothrow : 2; + // uint8_t ipo_terminates : 2; + // uint8_t ipo_nonoverlayed : 1; + // uint8_t ipo_notaskstate : 2; + // uint8_t ipo_inaccessiblememonly : 2; _Atomic(uint32_t) purity_bits; -#endif + // purity_flags: + // uint8_t consistent : 2; + // uint8_t effect_free : 2; + // uint8_t nothrow : 2; + // uint8_t terminates : 2; + // uint8_t nonoverlayed : 1; + // uint8_t notaskstate : 2; + // uint8_t inaccessiblememonly : 2; jl_value_t *argescapes; // escape information of call arguments // compilation state cache + // WARNING: isspecsig is protected by the codegen-lock. If that lock is removed, then the isspecsig load needs to be properly atomically sequenced with the invoke pointers. uint8_t isspecsig; // if specptr is a specialized function signature for specTypes->rettype _Atomic(uint8_t) precompile; // if set, this will be added to the output system image uint8_t relocatability; // nonzero if all roots are built into sysimg or tagged by module key @@ -546,7 +536,7 @@ typedef struct _jl_datatype_t { jl_svec_t *types; jl_value_t *instance; // for singletons const jl_datatype_layout_t *layout; - // memoized properties + // memoized properties (set on construction) uint32_t hash; uint8_t hasfreetypevars:1; // majority part of isconcrete computation uint8_t isconcretetype:1; // whether this type can have instances @@ -660,7 +650,7 @@ typedef struct _jl_methtable_t { _Atomic(jl_typemap_t*) defs; _Atomic(jl_array_t*) leafcache; _Atomic(jl_typemap_t*) cache; - intptr_t max_args; // max # of non-vararg arguments in a signature + _Atomic(intptr_t) max_args; // max # of non-vararg arguments in a signature jl_module_t *module; // used for incremental serialization to locate original binding jl_array_t *backedges; // (sig, caller::MethodInstance) pairs jl_mutex_t writelock; @@ -1562,7 +1552,7 @@ JL_DLLEXPORT jl_value_t *jl_get_nth_field(jl_value_t *v, size_t i); // Like jl_get_nth_field above, but asserts if it needs to allocate JL_DLLEXPORT jl_value_t *jl_get_nth_field_noalloc(jl_value_t *v JL_PROPAGATES_ROOT, size_t i) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_value_t *jl_get_nth_field_checked(jl_value_t *v, size_t i); -JL_DLLEXPORT void jl_set_nth_field(jl_value_t *v, size_t i, jl_value_t *rhs) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_set_nth_field(jl_value_t *v, size_t i, jl_value_t *rhs); JL_DLLEXPORT int jl_field_isdefined(jl_value_t *v, size_t i) JL_NOTSAFEPOINT; JL_DLLEXPORT int jl_field_isdefined_checked(jl_value_t *v, size_t i); JL_DLLEXPORT jl_value_t *jl_get_field(jl_value_t *o, const char *fld); diff --git a/src/method.c b/src/method.c index 098c5df5aed98f..3da88ac8211ac8 100644 --- a/src/method.c +++ b/src/method.c @@ -442,12 +442,12 @@ JL_DLLEXPORT jl_method_instance_t *jl_new_method_instance_uninit(void) li->def.value = NULL; li->specTypes = NULL; li->sparam_vals = jl_emptysvec; - li->uninferred = NULL; + jl_atomic_store_relaxed(&li->uninferred, NULL); li->backedges = NULL; li->callbacks = NULL; jl_atomic_store_relaxed(&li->cache, NULL); li->inInference = 0; - li->precompiled = 0; + jl_atomic_store_relaxed(&li->precompiled, 0); return li; } @@ -555,8 +555,10 @@ JL_DLLEXPORT jl_code_info_t *jl_expand_and_resolve(jl_value_t *ex, jl_module_t * // effectively described by the tuple (specTypes, env, Method) inside linfo JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo) { - if (linfo->uninferred) { - return (jl_code_info_t*)jl_copy_ast((jl_value_t*)linfo->uninferred); + jl_value_t *uninferred = jl_atomic_load_relaxed(&linfo->uninferred); + if (uninferred) { + assert(jl_is_code_info(uninferred)); // make sure this did not get `nothing` put here + return (jl_code_info_t*)jl_copy_ast((jl_value_t*)uninferred); } JL_TIMING(STAGED_FUNCTION); @@ -599,13 +601,22 @@ JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo) } } + jl_add_function_name_to_lineinfo(func, (jl_value_t*)def->name); + // If this generated function has an opaque closure, cache it for // correctness of method identity for (int i = 0; i < jl_array_len(func->code); ++i) { jl_value_t *stmt = jl_array_ptr_ref(func->code, i); if (jl_is_expr(stmt) && ((jl_expr_t*)stmt)->head == jl_new_opaque_closure_sym) { - linfo->uninferred = jl_copy_ast((jl_value_t*)func); - jl_gc_wb(linfo, linfo->uninferred); + jl_value_t *uninferred = jl_copy_ast((jl_value_t*)func); + jl_value_t *old = NULL; + if (jl_atomic_cmpswap(&linfo->uninferred, &old, uninferred)) { + jl_gc_wb(linfo, uninferred); + } + else { + assert(jl_is_code_info(old)); + func = (jl_code_info_t*)old; + } break; } } @@ -613,7 +624,6 @@ JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo) ct->ptls->in_pure_callback = last_in; jl_lineno = last_lineno; ct->world_age = last_age; - jl_add_function_name_to_lineinfo(func, (jl_value_t*)def->name); } JL_CATCH { ct->ptls->in_pure_callback = last_in; diff --git a/src/module.c b/src/module.c index d507dc69ff7b2d..ec62e6d83f2aa3 100644 --- a/src/module.c +++ b/src/module.c @@ -28,7 +28,7 @@ JL_DLLEXPORT jl_module_t *jl_new_module_(jl_sym_t *name, jl_module_t *parent, ui m->build_id.lo++; // build id 0 is invalid m->build_id.hi = ~(uint64_t)0; m->primary_world = 0; - m->counter = 1; + jl_atomic_store_relaxed(&m->counter, 1); m->nospecialize = 0; m->optlevel = -1; m->compile = -1; @@ -162,10 +162,10 @@ static jl_binding_t *new_binding(jl_sym_t *name) assert(jl_is_symbol(name)); jl_binding_t *b = (jl_binding_t*)jl_gc_alloc_buf(ct->ptls, sizeof(jl_binding_t)); b->name = name; - b->value = NULL; + jl_atomic_store_relaxed(&b->value, NULL); b->owner = NULL; - b->ty = NULL; - b->globalref = NULL; + jl_atomic_store_relaxed(&b->ty, NULL); + jl_atomic_store_relaxed(&b->globalref, NULL); b->constp = 0; b->exportp = 0; b->imported = 0; @@ -246,11 +246,11 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_ else { JL_UNLOCK(&m->lock); jl_binding_t *b2 = jl_get_binding(b->owner, b->name); - if (b2 == NULL || b2->value == NULL) + if (b2 == NULL || jl_atomic_load_relaxed(&b2->value) == NULL) jl_errorf("invalid method definition: imported function %s.%s does not exist", jl_symbol_name(b->owner->name), jl_symbol_name(b->name)); // TODO: we might want to require explicitly importing types to add constructors - if (!b->imported && !jl_is_type(b2->value)) { + if (!b->imported && (!b2->constp || !jl_is_type(jl_atomic_load_relaxed(&b2->value)))) { jl_errorf("error in method definition: function %s.%s must be explicitly imported to be extended", jl_symbol_name(b->owner->name), jl_symbol_name(b->name)); } @@ -310,7 +310,7 @@ static jl_binding_t *using_resolve_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl continue; if (owner != NULL && tempb->owner != b->owner && !tempb->deprecated && !b->deprecated && - !(tempb->constp && tempb->value && b->constp && b->value == tempb->value)) { + !(tempb->constp && b->constp && jl_atomic_load_relaxed(&tempb->value) && b->constp && jl_atomic_load_relaxed(&b->value) == jl_atomic_load_relaxed(&tempb->value))) { if (warn) { // mark this binding resolved (by creating it or setting the owner), to avoid repeating the warning (void)jl_get_binding_wr(m, var, 1); @@ -460,9 +460,12 @@ JL_DLLEXPORT jl_value_t *jl_module_globalref(jl_module_t *m, jl_sym_t *var) static int eq_bindings(jl_binding_t *a, jl_binding_t *b) { - if (a==b) return 1; - if (a->name == b->name && a->owner == b->owner) return 1; - if (a->constp && a->value && b->constp && b->value == a->value) return 1; + if (a == b) + return 1; + if (a->name == b->name && a->owner == b->owner) + return 1; + if (a->constp && b->constp && jl_atomic_load_relaxed(&a->value) && jl_atomic_load_relaxed(&b->value) == jl_atomic_load_relaxed(&a->value)) + return 1; return 0; } @@ -487,7 +490,7 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_s } else { if (b->deprecated) { - if (b->value == jl_nothing) { + if (jl_atomic_load_relaxed(&b->value) == jl_nothing) { return; } else if (to != jl_main_module && to != jl_base_module && @@ -524,7 +527,7 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_s else if (bto->owner != to && bto->owner != NULL) { // already imported from somewhere else jl_binding_t *bval = jl_get_binding(to, asname); - if (bval->constp && bval->value && b->constp && b->value == bval->value) { + if (bval->constp && b->constp && jl_atomic_load_relaxed(&bval->value) && jl_atomic_load_relaxed(&b->value) == jl_atomic_load_relaxed(&bval->value)) { // equivalent binding bto->imported = (explici!=0); JL_UNLOCK(&to->lock); @@ -538,10 +541,10 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_s } return; } - else if (bto->constp || bto->value) { + else if (bto->constp || jl_atomic_load_relaxed(&bto->value)) { // conflict with name owned by destination module assert(bto->owner == to); - if (bto->constp && bto->value && b->constp && b->value == bto->value) { + if (bto->constp && b->constp && jl_atomic_load_relaxed(&bto->value) && jl_atomic_load_relaxed(&b->value) == jl_atomic_load_relaxed(&bto->value)) { // equivalent binding JL_UNLOCK(&to->lock); } @@ -654,7 +657,7 @@ JL_DLLEXPORT void jl_module_export(jl_module_t *from, jl_sym_t *s) JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var) { jl_binding_t *b = jl_get_binding(m, var); - return b && (b->value != NULL); + return b && (jl_atomic_load_relaxed(&b->value) != NULL); } JL_DLLEXPORT int jl_defines_or_exports_p(jl_module_t *m, jl_sym_t *var) @@ -662,7 +665,7 @@ JL_DLLEXPORT int jl_defines_or_exports_p(jl_module_t *m, jl_sym_t *var) JL_LOCK(&m->lock); jl_binding_t *b = (jl_binding_t*)ptrhash_get(&m->bindings, var); JL_UNLOCK(&m->lock); - return b != HT_NOTFOUND && (b->exportp || b->owner==m); + return b != HT_NOTFOUND && (b->exportp || b->owner == m); } JL_DLLEXPORT int jl_module_exports_p(jl_module_t *m, jl_sym_t *var) @@ -711,8 +714,11 @@ JL_DLLEXPORT void jl_set_global(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *va JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT) { + // this function is mostly only used during initialization, so the data races here are not too important to us jl_binding_t *bp = jl_get_binding_wr(m, var, 1); - if (bp->value == NULL) { + if (jl_atomic_load_relaxed(&bp->value) == NULL) { + jl_value_t *old_ty = NULL; + jl_atomic_cmpswap_relaxed(&bp->ty, &old_ty, (jl_value_t*)jl_any_type); uint8_t constp = 0; // if (jl_atomic_cmpswap(&bp->constp, &constp, 1)) { if (constp = bp->constp, bp->constp = 1, constp == 0) { @@ -722,8 +728,6 @@ JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var return; } } - jl_value_t *old_ty = NULL; - jl_atomic_cmpswap_relaxed(&bp->ty, &old_ty, (jl_value_t*)jl_any_type); } jl_errorf("invalid redefinition of constant %s", jl_symbol_name(bp->name)); @@ -738,7 +742,7 @@ JL_DLLEXPORT int jl_binding_is_const(jl_binding_t *b) JL_DLLEXPORT int jl_binding_boundp(jl_binding_t *b) { assert(b); - return b->value != 0; + return jl_atomic_load_relaxed(&b->value) != NULL; } JL_DLLEXPORT int jl_is_const(jl_module_t *m, jl_sym_t *var) @@ -788,27 +792,30 @@ void jl_binding_deprecation_warning(jl_module_t *m, jl_binding_t *b) if (b->deprecated == 1 && jl_options.depwarn) { if (jl_options.depwarn != JL_OPTIONS_DEPWARN_ERROR) jl_printf(JL_STDERR, "WARNING: "); - jl_binding_t *dep_message_binding = NULL; + jl_value_t *dep_message = NULL; if (b->owner) { jl_printf(JL_STDERR, "%s.%s is deprecated", jl_symbol_name(b->owner->name), jl_symbol_name(b->name)); - dep_message_binding = jl_get_dep_message_binding(b->owner, b); + jl_binding_t *dep_message_binding = jl_get_dep_message_binding(b->owner, b); + if (dep_message_binding != NULL) + dep_message = jl_atomic_load_relaxed(&dep_message_binding->value); } else { jl_printf(JL_STDERR, "%s is deprecated", jl_symbol_name(b->name)); } - if (dep_message_binding && dep_message_binding->value) { - if (jl_isa(dep_message_binding->value, (jl_value_t*)jl_string_type)) { - jl_uv_puts(JL_STDERR, jl_string_data(dep_message_binding->value), - jl_string_len(dep_message_binding->value)); + JL_GC_PUSH1(&dep_message); + if (dep_message != NULL) { + if (jl_is_string(dep_message)) { + jl_uv_puts(JL_STDERR, jl_string_data(dep_message), jl_string_len(dep_message)); } else { - jl_static_show(JL_STDERR, dep_message_binding->value); + jl_static_show(JL_STDERR, dep_message); } } else { - jl_value_t *v = b->value; + jl_value_t *v = jl_atomic_load_relaxed(&b->value); + dep_message = v; if (v) { if (jl_is_type(v) || jl_is_module(v)) { jl_printf(JL_STDERR, ", use "); @@ -817,8 +824,7 @@ void jl_binding_deprecation_warning(jl_module_t *m, jl_binding_t *b) } else { jl_methtable_t *mt = jl_gf_mtable(v); - if (mt != NULL && (mt->defs != jl_nothing || - jl_isa(v, (jl_value_t*)jl_builtin_type))) { + if (mt != NULL) { jl_printf(JL_STDERR, ", use "); if (mt->module != jl_core_module) { jl_static_show(JL_STDERR, (jl_value_t*)mt->module); @@ -831,6 +837,7 @@ void jl_binding_deprecation_warning(jl_module_t *m, jl_binding_t *b) } } jl_printf(JL_STDERR, "\n"); + JL_GC_POP(); if (jl_options.depwarn != JL_OPTIONS_DEPWARN_ERROR) { if (jl_lineno == 0) { @@ -887,7 +894,7 @@ JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_value_t *rhs) JL_DLLEXPORT void jl_declare_constant(jl_binding_t *b) { - if (b->value != NULL && !b->constp) { + if (jl_atomic_load_relaxed(&b->value) != NULL && !b->constp) { jl_errorf("cannot declare %s constant; it already has a value", jl_symbol_name(b->name)); } diff --git a/src/rtutils.c b/src/rtutils.c index f34303b9aeea53..6ac35760a5fc6f 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -748,7 +748,7 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt else { n += jl_static_show_x(out, (jl_value_t*)li->def.module, depth); n += jl_printf(out, ". -> "); - n += jl_static_show_x(out, li->uninferred, depth); + n += jl_static_show_x(out, jl_atomic_load_relaxed(&li->uninferred), depth); } } else if (vt == jl_typename_type) { diff --git a/src/stackwalk.c b/src/stackwalk.c index e81a7cda8249b5..481b0abf9d701d 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -661,7 +661,7 @@ void jl_print_bt_entry_codeloc(jl_bt_element_t *bt_entry) JL_NOTSAFEPOINT jl_value_t *code = jl_bt_entry_jlvalue(bt_entry, 0); if (jl_is_method_instance(code)) { // When interpreting a method instance, need to unwrap to find the code info - code = ((jl_method_instance_t*)code)->uninferred; + code = jl_atomic_load_relaxed(&((jl_method_instance_t*)code)->uninferred); } if (jl_is_code_info(code)) { jl_code_info_t *src = (jl_code_info_t*)code; diff --git a/src/staticdata.c b/src/staticdata.c index 7b33db4eadccc2..2098596b9b612d 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -1344,7 +1344,7 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED } else if (jl_is_method_instance(v)) { jl_method_instance_t *newmi = (jl_method_instance_t*)&s->s->buf[reloc_offset]; - newmi->precompiled = 0; + jl_atomic_store_relaxed(&newmi->precompiled, 0); } else if (jl_is_code_instance(v)) { // Handle the native-code pointers @@ -2062,7 +2062,7 @@ static void strip_specializations_(jl_method_instance_t *mi) codeinst = jl_atomic_load_relaxed(&codeinst->next); } if (jl_options.strip_ir) { - record_field_change(&mi->uninferred, NULL); + record_field_change((jl_value_t**)&mi->uninferred, NULL); record_field_change((jl_value_t**)&mi->backedges, NULL); record_field_change((jl_value_t**)&mi->callbacks, NULL); } diff --git a/src/toplevel.c b/src/toplevel.c index baacb4235a838c..0b0b819a723a23 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -579,7 +579,7 @@ int jl_needs_lowering(jl_value_t *e) JL_NOTSAFEPOINT static jl_method_instance_t *method_instance_for_thunk(jl_code_info_t *src, jl_module_t *module) { jl_method_instance_t *li = jl_new_method_instance_uninit(); - li->uninferred = (jl_value_t*)src; + jl_atomic_store_relaxed(&li->uninferred, (jl_value_t*)src); li->specTypes = (jl_value_t*)jl_emptytuple_type; li->def.module = module; return li; diff --git a/stdlib/Serialization/src/Serialization.jl b/stdlib/Serialization/src/Serialization.jl index ce9220f7864a7d..e36b5c67e22829 100644 --- a/stdlib/Serialization/src/Serialization.jl +++ b/stdlib/Serialization/src/Serialization.jl @@ -1089,7 +1089,7 @@ function deserialize(s::AbstractSerializer, ::Type{Core.MethodInstance}) deserialize_cycle(s, linfo) tag = Int32(read(s.io, UInt8)::UInt8) if tag != UNDEFREF_TAG - linfo.uninferred = handle_deserialize(s, tag)::CodeInfo + setfield!(linfo, :uninferred, handle_deserialize(s, tag)::CodeInfo, :monotonic) end tag = Int32(read(s.io, UInt8)::UInt8) if tag != UNDEFREF_TAG @@ -1342,7 +1342,7 @@ function deserialize_typename(s::AbstractSerializer, number) mt.offs = 0 end mt.name = mtname - mt.max_args = maxa + setfield!(mt, :max_args, maxa, :monotonic) ccall(:jl_set_nth_field, Cvoid, (Any, Csize_t, Any), tn, Base.fieldindex(Core.TypeName, :mt)-1, mt) for def in defs if isdefined(def, :sig) diff --git a/test/atomics.jl b/test/atomics.jl index 15ffd84a2c0a23..e93afc3bff2c26 100644 --- a/test/atomics.jl +++ b/test/atomics.jl @@ -266,8 +266,10 @@ test_field_operators(ARefxy{Float64}(123_10, 123_20)) nothing end @noinline function test_field_orderings(r, x, y) - _test_field_orderings(Ref(copy(r)), x, y) - _test_field_orderings(Ref{Any}(copy(r)), x, y) + @testset "$r" begin + _test_field_orderings(Ref(copy(r)), x, y) + _test_field_orderings(Ref{Any}(copy(r)), x, y) + end nothing end @noinline test_field_orderings(x, y) = (@nospecialize; test_field_orderings(ARefxy(x, y), x, y)) diff --git a/test/core.jl b/test/core.jl index 4baf78ed2977c3..116b514b2ed5fb 100644 --- a/test/core.jl +++ b/test/core.jl @@ -16,15 +16,31 @@ for (T, c) in ( (Core.CodeInfo, []), (Core.CodeInstance, [:def, :rettype, :rettype_const, :ipo_purity_bits, :argescapes]), (Core.Method, [#=:name, :module, :file, :line, :primary_world, :sig, :slot_syms, :external_mt, :nargs, :called, :nospecialize, :nkw, :isva, :pure, :is_for_opaque_closure, :constprop=#]), - (Core.MethodInstance, [#=:def, :specTypes, :sparam_vals]=#]), + (Core.MethodInstance, [#=:def, :specTypes, :sparam_vals=#]), (Core.MethodTable, [:module]), (Core.TypeMapEntry, [:sig, :simplesig, :guardsigs, :min_world, :max_world, :func, :isleafsig, :issimplesig, :va]), (Core.TypeMapLevel, []), (Core.TypeName, [:name, :module, :names, :atomicfields, :constfields, :wrapper, :mt, :hash, :n_uninitialized, :flags]), (DataType, [:name, :super, :parameters, :instance, :hash]), + (TypeVar, [:name, :ub, :lb]), ) @test Set((fieldname(T, i) for i in 1:fieldcount(T) if isconst(T, i))) == Set(c) end +# +# sanity tests that our built-in types are marked correctly for atomic fields +for (T, c) in ( + (Core.CodeInfo, []), + (Core.CodeInstance, [:next, :inferred, :purity_bits, :invoke, :specptr, :precompile]), + (Core.Method, []), + (Core.MethodInstance, [:uninferred, :cache, :precompiled]), + (Core.MethodTable, [:defs, :leafcache, :cache, :max_args]), + (Core.TypeMapEntry, [:next]), + (Core.TypeMapLevel, [:arg1, :targ, :name1, :tname, :list, :any]), + (Core.TypeName, [:cache, :linearcache]), + (DataType, [:types, :layout]), + ) + @test Set((fieldname(T, i) for i in 1:fieldcount(T) if Base.isfieldatomic(T, i))) == Set(c) +end @test_throws(ErrorException("setfield!: const field .name of type DataType cannot be changed"), setfield!(Int, :name, Int.name)) diff --git a/test/stacktraces.jl b/test/stacktraces.jl index cbb07a60e456b8..fb873c1a5cfb77 100644 --- a/test/stacktraces.jl +++ b/test/stacktraces.jl @@ -104,7 +104,7 @@ let src = Meta.lower(Main, quote let x = 1 end end).args[1]::Core.CodeInfo, li = ccall(:jl_new_method_instance_uninit, Ref{Core.MethodInstance}, ()), sf - li.uninferred = src + setfield!(li, :uninferred, src, :monotonic) li.specTypes = Tuple{} li.def = @__MODULE__ sf = StackFrame(:a, :b, 3, li, false, false, 0) From c0d9367d049c2674c97f147e3bb2d69f00ce1e81 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 30 Nov 2022 16:13:50 -0500 Subject: [PATCH 087/387] fixes for jl_rewrap_unionall This behaved a bit differently than Base.rewrap_unionall, which meant it might make types like `Any where T` from `supertype(struct A{T} <: Any)`. This can confuse subtyping, which does not expect other types to appear to be wider than Any. --- src/gf.c | 2 +- src/jltypes.c | 26 +++++++++++++++++++++++++- src/julia_internal.h | 1 + src/subtype.c | 6 +++--- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/gf.c b/src/gf.c index df50b422160061..85a7bf5c643480 100644 --- a/src/gf.c +++ b/src/gf.c @@ -2468,7 +2468,7 @@ static jl_value_t *jl_argtype_with_function(jl_function_t *f, jl_value_t *types0 for(i=0; i < l; i++) jl_svecset(tt, i+1, jl_tparam(types,i)); tt = (jl_value_t*)jl_apply_tuple_type((jl_svec_t*)tt); - tt = jl_rewrap_unionall(tt, types0); + tt = jl_rewrap_unionall_(tt, types0); JL_GC_POP(); return tt; } diff --git a/src/jltypes.c b/src/jltypes.c index 9f93d60fb1acd4..b73d5ecab82aa1 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -1093,12 +1093,36 @@ jl_value_t *jl_unwrap_unionall(jl_value_t *v) } // wrap `t` in the same unionalls that surround `u` +// where `t` is derived from `u`, so the error checks in jl_type_unionall are unnecessary jl_value_t *jl_rewrap_unionall(jl_value_t *t, jl_value_t *u) { if (!jl_is_unionall(u)) return t; - JL_GC_PUSH1(&t); t = jl_rewrap_unionall(t, ((jl_unionall_t*)u)->body); + jl_tvar_t *v = ((jl_unionall_t*)u)->var; + // normalize `T where T<:S` => S + if (t == (jl_value_t*)v) + return v->ub; + // where var doesn't occur in body just return body + if (!jl_has_typevar(t, v)) + return t; + JL_GC_PUSH1(&t); + //if (v->lb == v->ub) // TODO maybe + // t = jl_substitute_var(body, v, v->ub); + //else + t = jl_new_struct(jl_unionall_type, v, t); + JL_GC_POP(); + return t; +} + +// wrap `t` in the same unionalls that surround `u` +// where `t` is extended from `u`, so the checks in jl_rewrap_unionall are unnecessary +jl_value_t *jl_rewrap_unionall_(jl_value_t *t, jl_value_t *u) +{ + if (!jl_is_unionall(u)) + return t; + t = jl_rewrap_unionall_(t, ((jl_unionall_t*)u)->body); + JL_GC_PUSH1(&t); t = jl_new_struct(jl_unionall_type, ((jl_unionall_t*)u)->var, t); JL_GC_POP(); return t; diff --git a/src/julia_internal.h b/src/julia_internal.h index c4ac3e2b350099..1e59cf6f18b5ab 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -698,6 +698,7 @@ JL_DLLEXPORT jl_value_t *jl_instantiate_type_in_env(jl_value_t *ty, jl_unionall_ jl_value_t *jl_substitute_var(jl_value_t *t, jl_tvar_t *var, jl_value_t *val); JL_DLLEXPORT jl_value_t *jl_unwrap_unionall(jl_value_t *v JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_value_t *jl_rewrap_unionall(jl_value_t *t, jl_value_t *u); +JL_DLLEXPORT jl_value_t *jl_rewrap_unionall_(jl_value_t *t, jl_value_t *u); int jl_count_union_components(jl_value_t *v); JL_DLLEXPORT jl_value_t *jl_nth_union_component(jl_value_t *v JL_PROPAGATES_ROOT, int i) JL_NOTSAFEPOINT; int jl_find_union_component(jl_value_t *haystack, jl_value_t *needle, unsigned *nth) JL_NOTSAFEPOINT; diff --git a/src/subtype.c b/src/subtype.c index cbb11520190cbf..1d9d3d875675da 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -2890,8 +2890,8 @@ static jl_value_t *intersect_sub_datatype(jl_datatype_t *xd, jl_datatype_t *yd, jl_value_t *super_pattern=NULL; JL_GC_PUSH2(&isuper, &super_pattern); jl_value_t *wrapper = xd->name->wrapper; - super_pattern = jl_rewrap_unionall((jl_value_t*)((jl_datatype_t*)jl_unwrap_unionall(wrapper))->super, - wrapper); + super_pattern = jl_rewrap_unionall_((jl_value_t*)((jl_datatype_t*)jl_unwrap_unionall(wrapper))->super, + wrapper); int envsz = jl_subtype_env_size(super_pattern); jl_value_t *ii = jl_bottom_type; { @@ -3528,7 +3528,7 @@ jl_value_t *jl_type_intersection_env_s(jl_value_t *a, jl_value_t *b, jl_svec_t * if (jl_is_uniontype(ans_unwrapped)) { ans_unwrapped = switch_union_tuple(((jl_uniontype_t*)ans_unwrapped)->a, ((jl_uniontype_t*)ans_unwrapped)->b); if (ans_unwrapped != NULL) { - *ans = jl_rewrap_unionall(ans_unwrapped, *ans); + *ans = jl_rewrap_unionall_(ans_unwrapped, *ans); } } JL_GC_POP(); From 16d3b9205b3223a9c843f49e6c03e190c52726f5 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Sat, 19 Nov 2022 17:03:16 -0500 Subject: [PATCH 088/387] call specialized method instance when encountering unspecialized sparams In some instances, the preferred compilation signature will require sparams to be provided at runtime. When we build the cache around these, we need to make sure the method instance we are calling has those values computed for the current signature, and not use the widened signature. But we can still compile for the widened signature, we just need to make sure we create a cache entry for every narrower call signature. Fix #47476 --- src/gf.c | 85 ++++++++++++++++++++++++++++++++++++++--------- src/jitlayers.cpp | 2 +- test/core.jl | 12 +++++++ 3 files changed, 82 insertions(+), 17 deletions(-) diff --git a/src/gf.c b/src/gf.c index 85a7bf5c643480..b816f1c8cfe50e 100644 --- a/src/gf.c +++ b/src/gf.c @@ -826,15 +826,6 @@ static void jl_compilation_sig( jl_svecset(limited, i, lastdeclt); } *newparams = limited; - // now there is a problem: the widened signature is more - // general than just the given arguments, so it might conflict - // with another definition that doesn't have cache instances yet. - // to fix this, we insert guard cache entries for all intersections - // of this signature and definitions. those guard entries will - // supersede this one in conflicted cases, alerting us that there - // should actually be a cache miss. - // TODO: the above analysis assumes that there will never - // be a call attempted that should throw a no-method error JL_GC_POP(); } } @@ -1078,18 +1069,35 @@ static jl_method_instance_t *cache_method( jl_svec_t *newparams = NULL; JL_GC_PUSH5(&temp, &temp2, &temp3, &newmeth, &newparams); + // Consider if we can cache with the preferred compile signature + // so that we can minimize the number of required cache entries. int cache_with_orig = 1; jl_tupletype_t *compilationsig = tt; jl_methtable_t *kwmt = mt == jl_kwcall_mt ? jl_kwmethod_table_for(definition->sig) : mt; intptr_t nspec = (kwmt == NULL || kwmt == jl_type_type_mt || kwmt == jl_nonfunction_mt || kwmt == jl_kwcall_mt ? definition->nargs + 1 : jl_atomic_load_relaxed(&kwmt->max_args) + 2 + 2 * (mt == jl_kwcall_mt)); jl_compilation_sig(tt, sparams, definition, nspec, &newparams); if (newparams) { - compilationsig = jl_apply_tuple_type(newparams); - temp2 = (jl_value_t*)compilationsig; - // In most cases `!jl_isa_compileable_sig(tt, definition))`, + temp2 = (jl_value_t*)jl_apply_tuple_type(newparams); + // Now there may be a problem: the widened signature is more general + // than just the given arguments, so it might conflict with another + // definition that does not have cache instances yet. To fix this, we + // may insert guard cache entries for all intersections of this + // signature and definitions. Those guard entries will supersede this + // one in conflicted cases, alerting us that there should actually be a + // cache miss. Alternatively, we may use the original signature in the + // cache, but use this return for compilation. + // + // In most cases `!jl_isa_compileable_sig(tt, definition)`, // although for some cases, (notably Varargs) // we might choose a replacement type that's preferable but not strictly better - cache_with_orig = !jl_subtype((jl_value_t*)compilationsig, definition->sig); + int issubty; + temp = jl_type_intersection_env_s(temp2, (jl_value_t*)definition->sig, &newparams, &issubty); + assert(temp != (jl_value_t*)jl_bottom_type); (void)temp; + if (jl_egal((jl_value_t*)newparams, (jl_value_t*)sparams)) { + cache_with_orig = !issubty; + compilationsig = (jl_datatype_t*)temp2; + } + newparams = NULL; } // TODO: maybe assert(jl_isa_compileable_sig(compilationsig, definition)); newmeth = jl_specializations_get_linfo(definition, (jl_value_t*)compilationsig, sparams); @@ -1110,6 +1118,8 @@ static jl_method_instance_t *cache_method( size_t i, l = jl_array_len(temp); for (i = 0; i < l; i++) { jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(temp, i); + if (matc->method == definition) + continue; jl_svec_t *env = matc->sparams; int k, l; for (k = 0, l = jl_svec_len(env); k < l; k++) { @@ -1128,9 +1138,7 @@ static jl_method_instance_t *cache_method( cache_with_orig = 1; break; } - if (matc->method != definition) { - guards++; - } + guards++; } } if (!cache_with_orig && guards > 0) { @@ -2096,11 +2104,35 @@ static void record_precompile_statement(jl_method_instance_t *mi) JL_UNLOCK(&precomp_statement_out_lock); } +jl_method_instance_t *jl_normalize_to_compilable_mi(jl_method_instance_t *mi JL_PROPAGATES_ROOT); + jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t world) { + // quick check if we already have a compiled result jl_code_instance_t *codeinst = jl_method_compiled(mi, world); if (codeinst) return codeinst; + + // if mi has a better (wider) signature for compilation use that instead + // and just copy it here for caching + jl_method_instance_t *mi2 = jl_normalize_to_compilable_mi(mi); + if (mi2 != mi) { + jl_code_instance_t *codeinst2 = jl_compile_method_internal(mi2, world); + jl_code_instance_t *codeinst = jl_get_method_inferred( + mi, codeinst2->rettype, + codeinst2->min_world, codeinst2->max_world); + if (jl_atomic_load_relaxed(&codeinst->invoke) == NULL) { + // once set, don't change invoke-ptr, as that leads to race conditions + // with the (not) simultaneous updates to invoke and specptr + codeinst->isspecsig = codeinst2->isspecsig; + codeinst->rettype_const = codeinst2->rettype_const; + jl_atomic_store_release(&codeinst->specptr.fptr, jl_atomic_load_relaxed(&codeinst2->specptr.fptr)); + jl_atomic_store_release(&codeinst->invoke, jl_atomic_load_relaxed(&codeinst2->invoke)); + } + // don't call record_precompile_statement here, since we already compiled it as mi2 which is better + return codeinst; + } + int compile_option = jl_options.compile_enabled; jl_method_t *def = mi->def.method; // disabling compilation per-module can override global setting @@ -2135,6 +2167,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t } } } + // if that didn't work and compilation is off, try running in the interpreter if (compile_option == JL_OPTIONS_COMPILE_OFF || compile_option == JL_OPTIONS_COMPILE_MIN) { @@ -2255,6 +2288,26 @@ JL_DLLEXPORT jl_value_t *jl_normalize_to_compilable_sig(jl_methtable_t *mt, jl_t return is_compileable ? (jl_value_t*)tt : jl_nothing; } +jl_method_instance_t *jl_normalize_to_compilable_mi(jl_method_instance_t *mi JL_PROPAGATES_ROOT) +{ + jl_method_t *def = mi->def.method; + if (!jl_is_method(def)) + return mi; + jl_methtable_t *mt = jl_method_get_table(def); + if ((jl_value_t*)mt == jl_nothing) + return mi; + jl_value_t *compilationsig = jl_normalize_to_compilable_sig(mt, (jl_datatype_t*)mi->specTypes, mi->sparam_vals, def); + if (compilationsig == jl_nothing || jl_egal(compilationsig, mi->specTypes)) + return mi; + jl_svec_t *env = NULL; + JL_GC_PUSH2(&compilationsig, &env); + jl_value_t *ti = jl_type_intersection_env((jl_value_t*)mi->specTypes, (jl_value_t*)def->sig, &env); + assert(ti != jl_bottom_type); (void)ti; + mi = jl_specializations_get_linfo(def, (jl_value_t*)compilationsig, env); + JL_GC_POP(); + return mi; +} + // return a MethodInstance for a compileable method_match jl_method_instance_t *jl_method_match_to_mi(jl_method_match_t *match, size_t world, size_t min_valid, size_t max_valid, int mt_cache) { diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index f5a0623cd8df66..da5e8c58fdecdd 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -267,7 +267,7 @@ static jl_callptr_t _jl_compile_codeinst( // hack to export this pointer value to jl_dump_method_disasm jl_atomic_store_release(&this_code->specptr.fptr, (void*)getAddressForFunction(decls.specFunctionObject)); } - if (this_code== codeinst) + if (this_code == codeinst) fptr = addr; } diff --git a/test/core.jl b/test/core.jl index 116b514b2ed5fb..7733a5162c910e 100644 --- a/test/core.jl +++ b/test/core.jl @@ -7902,3 +7902,15 @@ struct ModTparamTestStruct{M}; end end @test ModTparamTestStruct{@__MODULE__}() == 2 @test ModTparamTestStruct{ModTparamTest}() == 1 + +# issue #47476 +f47476(::Union{Int, NTuple{N,Int}}...) where {N} = N +# force it to populate the MethodInstance specializations cache +# with the correct sparams +code_typed(f47476, (Vararg{Union{Int, NTuple{2,Int}}},)); +code_typed(f47476, (Int, Vararg{Union{Int, NTuple{2,Int}}},)); +code_typed(f47476, (Int, Int, Vararg{Union{Int, NTuple{2,Int}}},)) +code_typed(f47476, (Int, Int, Int, Vararg{Union{Int, NTuple{2,Int}}},)) +code_typed(f47476, (Int, Int, Int, Int, Vararg{Union{Int, NTuple{2,Int}}},)) +@test f47476(1, 2, 3, 4, 5, 6, (7, 8)) === 2 +@test_throws UndefVarError(:N) f47476(1, 2, 3, 4, 5, 6, 7) From 71ab5fa95fcef70fca73dbde2a398675ad564553 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Mon, 21 Nov 2022 14:45:08 -0500 Subject: [PATCH 089/387] ensure sparams are cached correctly for widened methods Follow-up issue found while working on #47476 --- base/compiler/typeinfer.jl | 2 +- base/compiler/utilities.jl | 11 +++-- src/gf.c | 98 ++++++++++++++++++++++--------------- src/julia.h | 2 +- src/precompile.c | 4 +- stdlib/Random/src/Random.jl | 2 +- test/compiler/inference.jl | 2 +- test/core.jl | 4 ++ test/precompile.jl | 4 +- 9 files changed, 79 insertions(+), 50 deletions(-) diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 81d0f06608a316..29c4a7e6e477a3 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -347,7 +347,7 @@ function maybe_compress_codeinfo(interp::AbstractInterpreter, linfo::MethodInsta return ci end if may_discard_trees(interp) - cache_the_tree = ci.inferred && (is_inlineable(ci) || isa_compileable_sig(linfo.specTypes, def)) + cache_the_tree = ci.inferred && (is_inlineable(ci) || isa_compileable_sig(linfo.specTypes, linfo.sparam_vals, def)) else cache_the_tree = true end diff --git a/base/compiler/utilities.jl b/base/compiler/utilities.jl index 2915870ae2ea58..007aaa31fdd557 100644 --- a/base/compiler/utilities.jl +++ b/base/compiler/utilities.jl @@ -152,8 +152,8 @@ function get_compileable_sig(method::Method, @nospecialize(atype), sparams::Simp mt, atype, sparams, method) end -isa_compileable_sig(@nospecialize(atype), method::Method) = - !iszero(ccall(:jl_isa_compileable_sig, Int32, (Any, Any), atype, method)) +isa_compileable_sig(@nospecialize(atype), sparams::SimpleVector, method::Method) = + !iszero(ccall(:jl_isa_compileable_sig, Int32, (Any, Any, Any), atype, sparams, method)) # eliminate UnionAll vars that might be degenerate due to having identical bounds, # or a concrete upper bound and appearing covariantly. @@ -200,7 +200,12 @@ function specialize_method(method::Method, @nospecialize(atype), sparams::Simple if compilesig new_atype = get_compileable_sig(method, atype, sparams) new_atype === nothing && return nothing - atype = new_atype + if atype !== new_atype + sp_ = ccall(:jl_type_intersection_with_env, Any, (Any, Any), new_atype, method.sig)::SimpleVector + if sparams === sp_[2]::SimpleVector + atype = new_atype + end + end end if preexisting # check cached specializations diff --git a/src/gf.c b/src/gf.c index b816f1c8cfe50e..fa12b2dc0f2e17 100644 --- a/src/gf.c +++ b/src/gf.c @@ -637,13 +637,14 @@ static void jl_compilation_sig( for (i = 0; i < np; i++) { jl_value_t *elt = jl_tparam(tt, i); jl_value_t *decl_i = jl_nth_slot_type(decl, i); + jl_value_t *type_i = jl_rewrap_unionall(decl_i, decl); size_t i_arg = (i < nargs - 1 ? i : nargs - 1); - if (jl_is_kind(decl_i)) { + if (jl_is_kind(type_i)) { // if we can prove the match was against the kind (not a Type) // we want to put that in the cache instead if (!*newparams) *newparams = jl_svec_copy(tt->parameters); - elt = decl_i; + elt = type_i; jl_svecset(*newparams, i, elt); } else if (jl_is_type_type(elt)) { @@ -652,7 +653,7 @@ static void jl_compilation_sig( // and the result of matching the type signature // needs to be restricted to the concrete type 'kind' jl_value_t *kind = jl_typeof(jl_tparam0(elt)); - if (jl_subtype(kind, decl_i) && !jl_subtype((jl_value_t*)jl_type_type, decl_i)) { + if (jl_subtype(kind, type_i) && !jl_subtype((jl_value_t*)jl_type_type, type_i)) { // if we can prove the match was against the kind (not a Type) // it's simpler (and thus better) to put that cache instead if (!*newparams) *newparams = jl_svec_copy(tt->parameters); @@ -664,7 +665,7 @@ static void jl_compilation_sig( // not triggered for isdispatchtuple(tt), this attempts to handle // some cases of adapting a random signature into a compilation signature // if we get a kind, where we don't expect to accept one, widen it to something more expected (Type{T}) - if (!(jl_subtype(elt, decl_i) && !jl_subtype((jl_value_t*)jl_type_type, decl_i))) { + if (!(jl_subtype(elt, type_i) && !jl_subtype((jl_value_t*)jl_type_type, type_i))) { if (!*newparams) *newparams = jl_svec_copy(tt->parameters); elt = (jl_value_t*)jl_type_type; jl_svecset(*newparams, i, elt); @@ -703,7 +704,7 @@ static void jl_compilation_sig( jl_svecset(*newparams, i, jl_type_type); } else if (jl_is_type_type(elt)) { // elt isa Type{T} - if (very_general_type(decl_i)) { + if (!jl_has_free_typevars(decl_i) && very_general_type(type_i)) { /* Here's a fairly simple heuristic: if this argument slot's declared type is general (Type or Any), @@ -742,15 +743,13 @@ static void jl_compilation_sig( */ if (!*newparams) *newparams = jl_svec_copy(tt->parameters); if (i < nargs || !definition->isva) { - jl_value_t *di = jl_type_intersection(decl_i, (jl_value_t*)jl_type_type); + jl_value_t *di = jl_type_intersection(type_i, (jl_value_t*)jl_type_type); assert(di != (jl_value_t*)jl_bottom_type); // issue #11355: DataType has a UID and so would take precedence in the cache if (jl_is_kind(di)) jl_svecset(*newparams, i, (jl_value_t*)jl_type_type); else jl_svecset(*newparams, i, di); - // TODO: recompute static parameter values, so in extreme cases we - // can give `T=Type` instead of `T=Type{Type{Type{...`. /* make editors happy:}}} */ } else { jl_svecset(*newparams, i, (jl_value_t*)jl_type_type); @@ -759,14 +758,15 @@ static void jl_compilation_sig( } int notcalled_func = (i_arg > 0 && i_arg <= 8 && !(definition->called & (1 << (i_arg - 1))) && + !jl_has_free_typevars(decl_i) && jl_subtype(elt, (jl_value_t*)jl_function_type)); - if (notcalled_func && (decl_i == (jl_value_t*)jl_any_type || - decl_i == (jl_value_t*)jl_function_type || - (jl_is_uniontype(decl_i) && // Base.Callable - ((((jl_uniontype_t*)decl_i)->a == (jl_value_t*)jl_function_type && - ((jl_uniontype_t*)decl_i)->b == (jl_value_t*)jl_type_type) || - (((jl_uniontype_t*)decl_i)->b == (jl_value_t*)jl_function_type && - ((jl_uniontype_t*)decl_i)->a == (jl_value_t*)jl_type_type))))) { + if (notcalled_func && (type_i == (jl_value_t*)jl_any_type || + type_i == (jl_value_t*)jl_function_type || + (jl_is_uniontype(type_i) && // Base.Callable + ((((jl_uniontype_t*)type_i)->a == (jl_value_t*)jl_function_type && + ((jl_uniontype_t*)type_i)->b == (jl_value_t*)jl_type_type) || + (((jl_uniontype_t*)type_i)->b == (jl_value_t*)jl_function_type && + ((jl_uniontype_t*)type_i)->a == (jl_value_t*)jl_type_type))))) { // and attempt to despecialize types marked Function, Callable, or Any // when called with a subtype of Function but is not called if (!*newparams) *newparams = jl_svec_copy(tt->parameters); @@ -833,6 +833,7 @@ static void jl_compilation_sig( // compute whether this type signature is a possible return value from jl_compilation_sig given a concrete-type for `tt` JL_DLLEXPORT int jl_isa_compileable_sig( jl_tupletype_t *type, + jl_svec_t *sparams, jl_method_t *definition) { jl_value_t *decl = definition->sig; @@ -886,6 +887,7 @@ JL_DLLEXPORT int jl_isa_compileable_sig( for (i = 0; i < np; i++) { jl_value_t *elt = jl_tparam(type, i); jl_value_t *decl_i = jl_nth_slot_type((jl_value_t*)decl, i); + jl_value_t *type_i = jl_rewrap_unionall(decl_i, decl); size_t i_arg = (i < nargs - 1 ? i : nargs - 1); if (jl_is_vararg(elt)) { @@ -919,25 +921,26 @@ JL_DLLEXPORT int jl_isa_compileable_sig( if (jl_is_kind(elt)) { // kind slots always get guard entries (checking for subtypes of Type) - if (jl_subtype(elt, decl_i) && !jl_subtype((jl_value_t*)jl_type_type, decl_i)) + if (jl_subtype(elt, type_i) && !jl_subtype((jl_value_t*)jl_type_type, type_i)) continue; // TODO: other code paths that could reach here return 0; } - else if (jl_is_kind(decl_i)) { + else if (jl_is_kind(type_i)) { return 0; } if (jl_is_type_type(jl_unwrap_unionall(elt))) { - int iscalled = i_arg > 0 && i_arg <= 8 && (definition->called & (1 << (i_arg - 1))); + int iscalled = (i_arg > 0 && i_arg <= 8 && (definition->called & (1 << (i_arg - 1)))) || + jl_has_free_typevars(decl_i); if (jl_types_equal(elt, (jl_value_t*)jl_type_type)) { - if (!iscalled && very_general_type(decl_i)) + if (!iscalled && very_general_type(type_i)) continue; if (i >= nargs && definition->isva) continue; return 0; } - if (!iscalled && very_general_type(decl_i)) + if (!iscalled && very_general_type(type_i)) return 0; if (!jl_is_datatype(elt)) return 0; @@ -949,7 +952,7 @@ JL_DLLEXPORT int jl_isa_compileable_sig( jl_value_t *kind = jl_typeof(jl_tparam0(elt)); if (kind == jl_bottom_type) return 0; // Type{Union{}} gets normalized to typeof(Union{}) - if (jl_subtype(kind, decl_i) && !jl_subtype((jl_value_t*)jl_type_type, decl_i)) + if (jl_subtype(kind, type_i) && !jl_subtype((jl_value_t*)jl_type_type, type_i)) return 0; // gets turned into a kind else if (jl_is_type_type(jl_tparam0(elt)) && @@ -963,7 +966,7 @@ JL_DLLEXPORT int jl_isa_compileable_sig( this can be determined using a type intersection. */ if (i < nargs || !definition->isva) { - jl_value_t *di = jl_type_intersection(decl_i, (jl_value_t*)jl_type_type); + jl_value_t *di = jl_type_intersection(type_i, (jl_value_t*)jl_type_type); JL_GC_PUSH1(&di); assert(di != (jl_value_t*)jl_bottom_type); if (jl_is_kind(di)) { @@ -984,14 +987,15 @@ JL_DLLEXPORT int jl_isa_compileable_sig( } int notcalled_func = (i_arg > 0 && i_arg <= 8 && !(definition->called & (1 << (i_arg - 1))) && + !jl_has_free_typevars(decl_i) && jl_subtype(elt, (jl_value_t*)jl_function_type)); - if (notcalled_func && (decl_i == (jl_value_t*)jl_any_type || - decl_i == (jl_value_t*)jl_function_type || - (jl_is_uniontype(decl_i) && // Base.Callable - ((((jl_uniontype_t*)decl_i)->a == (jl_value_t*)jl_function_type && - ((jl_uniontype_t*)decl_i)->b == (jl_value_t*)jl_type_type) || - (((jl_uniontype_t*)decl_i)->b == (jl_value_t*)jl_function_type && - ((jl_uniontype_t*)decl_i)->a == (jl_value_t*)jl_type_type))))) { + if (notcalled_func && (type_i == (jl_value_t*)jl_any_type || + type_i == (jl_value_t*)jl_function_type || + (jl_is_uniontype(type_i) && // Base.Callable + ((((jl_uniontype_t*)type_i)->a == (jl_value_t*)jl_function_type && + ((jl_uniontype_t*)type_i)->b == (jl_value_t*)jl_type_type) || + (((jl_uniontype_t*)type_i)->b == (jl_value_t*)jl_function_type && + ((jl_uniontype_t*)type_i)->a == (jl_value_t*)jl_type_type))))) { // and attempt to despecialize types marked Function, Callable, or Any // when called with a subtype of Function but is not called if (elt == (jl_value_t*)jl_function_type) @@ -1087,7 +1091,7 @@ static jl_method_instance_t *cache_method( // cache miss. Alternatively, we may use the original signature in the // cache, but use this return for compilation. // - // In most cases `!jl_isa_compileable_sig(tt, definition)`, + // In most cases `!jl_isa_compileable_sig(tt, sparams, definition)`, // although for some cases, (notably Varargs) // we might choose a replacement type that's preferable but not strictly better int issubty; @@ -1099,7 +1103,7 @@ static jl_method_instance_t *cache_method( } newparams = NULL; } - // TODO: maybe assert(jl_isa_compileable_sig(compilationsig, definition)); + // TODO: maybe assert(jl_isa_compileable_sig(compilationsig, sparams, definition)); newmeth = jl_specializations_get_linfo(definition, (jl_value_t*)compilationsig, sparams); jl_tupletype_t *cachett = tt; @@ -2281,9 +2285,21 @@ JL_DLLEXPORT jl_value_t *jl_normalize_to_compilable_sig(jl_methtable_t *mt, jl_t jl_methtable_t *kwmt = mt == jl_kwcall_mt ? jl_kwmethod_table_for(m->sig) : mt; intptr_t nspec = (kwmt == NULL || kwmt == jl_type_type_mt || kwmt == jl_nonfunction_mt || kwmt == jl_kwcall_mt ? m->nargs + 1 : jl_atomic_load_relaxed(&kwmt->max_args) + 2 + 2 * (mt == jl_kwcall_mt)); jl_compilation_sig(ti, env, m, nspec, &newparams); - tt = (newparams ? jl_apply_tuple_type(newparams) : ti); - int is_compileable = ((jl_datatype_t*)ti)->isdispatchtuple || - jl_isa_compileable_sig(tt, m); + int is_compileable = ((jl_datatype_t*)ti)->isdispatchtuple; + if (newparams) { + tt = jl_apply_tuple_type(newparams); + if (!is_compileable) { + // compute new env, if used below + jl_value_t *ti = jl_type_intersection_env((jl_value_t*)tt, (jl_value_t*)m->sig, &newparams); + assert(ti != jl_bottom_type); (void)ti; + env = newparams; + } + } + else { + tt = ti; + } + if (!is_compileable) + is_compileable = jl_isa_compileable_sig(tt, env, m); JL_GC_POP(); return is_compileable ? (jl_value_t*)tt : jl_nothing; } @@ -2301,7 +2317,7 @@ jl_method_instance_t *jl_normalize_to_compilable_mi(jl_method_instance_t *mi JL_ return mi; jl_svec_t *env = NULL; JL_GC_PUSH2(&compilationsig, &env); - jl_value_t *ti = jl_type_intersection_env((jl_value_t*)mi->specTypes, (jl_value_t*)def->sig, &env); + jl_value_t *ti = jl_type_intersection_env((jl_value_t*)compilationsig, (jl_value_t*)def->sig, &env); assert(ti != jl_bottom_type); (void)ti; mi = jl_specializations_get_linfo(def, (jl_value_t*)compilationsig, env); JL_GC_POP(); @@ -2318,7 +2334,7 @@ jl_method_instance_t *jl_method_match_to_mi(jl_method_match_t *match, size_t wor if (jl_is_datatype(ti)) { jl_methtable_t *mt = jl_method_get_table(m); if ((jl_value_t*)mt != jl_nothing) { - // get the specialization without caching it + // get the specialization, possibly also caching it if (mt_cache && ((jl_datatype_t*)ti)->isdispatchtuple) { // Since we also use this presence in the cache // to trigger compilation when producing `.ji` files, @@ -2330,11 +2346,15 @@ jl_method_instance_t *jl_method_match_to_mi(jl_method_match_t *match, size_t wor } else { jl_value_t *tt = jl_normalize_to_compilable_sig(mt, ti, env, m); - JL_GC_PUSH1(&tt); if (tt != jl_nothing) { + JL_GC_PUSH2(&tt, &env); + if (!jl_egal(tt, (jl_value_t*)ti)) { + jl_value_t *ti = jl_type_intersection_env((jl_value_t*)tt, (jl_value_t*)m->sig, &env); + assert(ti != jl_bottom_type); (void)ti; + } mi = jl_specializations_get_linfo(m, (jl_value_t*)tt, env); + JL_GC_POP(); } - JL_GC_POP(); } } } @@ -2397,7 +2417,7 @@ jl_method_instance_t *jl_get_compile_hint_specialization(jl_tupletype_t *types J size_t count = 0; for (i = 0; i < n; i++) { jl_method_match_t *match1 = (jl_method_match_t*)jl_array_ptr_ref(matches, i); - if (jl_isa_compileable_sig(types, match1->method)) + if (jl_isa_compileable_sig(types, match1->sparams, match1->method)) jl_array_ptr_set(matches, count++, (jl_value_t*)match1); } jl_array_del_end((jl_array_t*)matches, n - count); diff --git a/src/julia.h b/src/julia.h index f7cf69710fa624..2c9be8ae1aa2a3 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1433,7 +1433,7 @@ STATIC_INLINE int jl_is_concrete_type(jl_value_t *v) JL_NOTSAFEPOINT return jl_is_datatype(v) && ((jl_datatype_t*)v)->isconcretetype; } -JL_DLLEXPORT int jl_isa_compileable_sig(jl_tupletype_t *type, jl_method_t *definition); +JL_DLLEXPORT int jl_isa_compileable_sig(jl_tupletype_t *type, jl_svec_t *sparams, jl_method_t *definition); // type constructors JL_DLLEXPORT jl_typename_t *jl_new_typename_in(jl_sym_t *name, jl_module_t *inmodule, int abstract, int mutabl); diff --git a/src/precompile.c b/src/precompile.c index 9c9c79b154a324..ebe7afae69f641 100644 --- a/src/precompile.c +++ b/src/precompile.c @@ -269,7 +269,7 @@ static void jl_compile_all_defs(jl_array_t *mis) size_t i, l = jl_array_len(allmeths); for (i = 0; i < l; i++) { jl_method_t *m = (jl_method_t*)jl_array_ptr_ref(allmeths, i); - if (jl_isa_compileable_sig((jl_tupletype_t*)m->sig, m)) { + if (jl_is_datatype(m->sig) && jl_isa_compileable_sig((jl_tupletype_t*)m->sig, jl_emptysvec, m)) { // method has a single compilable specialization, e.g. its definition // signature is concrete. in this case we can just hint it. jl_compile_hint((jl_tupletype_t*)m->sig); @@ -354,7 +354,7 @@ static void *jl_precompile_(jl_array_t *m) mi = (jl_method_instance_t*)item; size_t min_world = 0; size_t max_world = ~(size_t)0; - if (mi != jl_atomic_load_relaxed(&mi->def.method->unspecialized) && !jl_isa_compileable_sig((jl_tupletype_t*)mi->specTypes, mi->def.method)) + if (mi != jl_atomic_load_relaxed(&mi->def.method->unspecialized) && !jl_isa_compileable_sig((jl_tupletype_t*)mi->specTypes, mi->sparam_vals, mi->def.method)) mi = jl_get_specialization1((jl_tupletype_t*)mi->specTypes, jl_atomic_load_acquire(&jl_world_counter), &min_world, &max_world, 0); if (mi) jl_array_ptr_1d_push(m2, (jl_value_t*)mi); diff --git a/stdlib/Random/src/Random.jl b/stdlib/Random/src/Random.jl index 95125422eeee5e..bc016fc1cd0576 100644 --- a/stdlib/Random/src/Random.jl +++ b/stdlib/Random/src/Random.jl @@ -256,7 +256,7 @@ rand(rng::AbstractRNG, ::UniformT{T}) where {T} = rand(rng, T) rand(rng::AbstractRNG, X) = rand(rng, Sampler(rng, X, Val(1))) # this is needed to disambiguate rand(rng::AbstractRNG, X::Dims) = rand(rng, Sampler(rng, X, Val(1))) -rand(rng::AbstractRNG=default_rng(), ::Type{X}=Float64) where {X} = rand(rng, Sampler(rng, X, Val(1)))::X +rand(rng::AbstractRNG=default_rng(), ::Type{X}=Float64) where {X} = rand(rng, Sampler(rng, X, Val(1)))::X rand(X) = rand(default_rng(), X) rand(::Type{X}) where {X} = rand(default_rng(), X) diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index d440b0097ac535..1cfc3dae125f88 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -406,7 +406,7 @@ f11366(x::Type{Ref{T}}) where {T} = Ref{x} let f(T) = Type{T} - @test Base.return_types(f, Tuple{Type{Int}}) == [Type{Type{Int}}] + @test Base.return_types(f, Tuple{Type{Int}}) == Any[Type{Type{Int}}] end # issue #9222 diff --git a/test/core.jl b/test/core.jl index 7733a5162c910e..8f018d6aa09508 100644 --- a/test/core.jl +++ b/test/core.jl @@ -7914,3 +7914,7 @@ code_typed(f47476, (Int, Int, Int, Vararg{Union{Int, NTuple{2,Int}}},)) code_typed(f47476, (Int, Int, Int, Int, Vararg{Union{Int, NTuple{2,Int}}},)) @test f47476(1, 2, 3, 4, 5, 6, (7, 8)) === 2 @test_throws UndefVarError(:N) f47476(1, 2, 3, 4, 5, 6, 7) + +vect47476(::Type{T}) where {T} = T +@test vect47476(Type{Type{Type{Int32}}}) === Type{Type{Type{Int32}}} +@test vect47476(Type{Type{Type{Int64}}}) === Type{Type{Type{Int64}}} diff --git a/test/precompile.jl b/test/precompile.jl index 806887646e1372..688286a113b550 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -1493,8 +1493,8 @@ end f(x, y) = x + y f(x::Int, y) = 2x + y end - precompile(M.f, (Int, Any)) - precompile(M.f, (AbstractFloat, Any)) + @test precompile(M.f, (Int, Any)) + @test precompile(M.f, (AbstractFloat, Any)) mis = map(methods(M.f)) do m m.specializations[1] end From 9e5e28fa3e85b1d1fee247a814ec7f017a78a83c Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 22 Nov 2022 11:50:15 -0500 Subject: [PATCH 090/387] ensure types are UnionAll wrapped are cached correctly for widened Vararg methods And fix a related accuracy issue in jl_isa_compileable_sig Follow-up issue found while working on #47476 --- src/gf.c | 167 +++++++++++++++++++++++++++++++++------------------ test/core.jl | 9 +++ 2 files changed, 117 insertions(+), 59 deletions(-) diff --git a/src/gf.c b/src/gf.c index fa12b2dc0f2e17..6d705f15a482cc 100644 --- a/src/gf.c +++ b/src/gf.c @@ -606,6 +606,46 @@ jl_value_t *jl_nth_slot_type(jl_value_t *sig, size_t i) JL_NOTSAFEPOINT // return 1; //} +static jl_value_t *inst_varargp_in_env(jl_value_t *decl, jl_svec_t *sparams) +{ + jl_value_t *unw = jl_unwrap_unionall(decl); + jl_value_t *vm = jl_tparam(unw, jl_nparams(unw) - 1); + assert(jl_is_vararg(vm)); + int nsp = jl_svec_len(sparams); + if (nsp > 0 && jl_has_free_typevars(vm)) { + JL_GC_PUSH1(&vm); + assert(jl_subtype_env_size(decl) == nsp); + vm = jl_instantiate_type_in_env(vm, (jl_unionall_t*)decl, jl_svec_data(sparams)); + assert(jl_is_vararg(vm)); + // rewrap_unionall(lastdeclt, sparams) if any sparams isa TypeVar + // for example, `Tuple{Vararg{Union{Nothing,Int,Val{T}}}} where T` + // and the user called it with `Tuple{Vararg{Union{Nothing,Int},N}}`, then T is unbound + jl_value_t **sp = jl_svec_data(sparams); + while (jl_is_unionall(decl)) { + jl_tvar_t *v = (jl_tvar_t*)*sp; + if (jl_is_typevar(v)) { + // must unwrap and re-wrap Vararg object explicitly here since jl_type_unionall handles it differently + jl_value_t *T = ((jl_vararg_t*)vm)->T; + jl_value_t *N = ((jl_vararg_t*)vm)->N; + int T_has_tv = T && jl_has_typevar(T, v); + int N_has_tv = N && jl_has_typevar(N, v); // n.b. JL_VARARG_UNBOUND check means this should be false + assert(!N_has_tv || N == (jl_value_t*)v); + if (T_has_tv) + vm = jl_type_unionall(v, T); + if (N_has_tv) + N = NULL; + vm = (jl_value_t*)jl_wrap_vararg(vm, N); // this cannot throw for these inputs + } + sp++; + decl = ((jl_unionall_t*)decl)->body; + nsp--; + } + assert(nsp == 0); + JL_GC_POP(); + } + return vm; +} + static jl_value_t *ml_matches(jl_methtable_t *mt, jl_tupletype_t *type, int lim, int include_ambiguous, int intersections, size_t world, int cache_result, @@ -634,10 +674,12 @@ static void jl_compilation_sig( assert(jl_is_tuple_type(tt)); size_t i, np = jl_nparams(tt); size_t nargs = definition->nargs; // == jl_nparams(jl_unwrap_unionall(decl)); + jl_value_t *type_i = NULL; + JL_GC_PUSH1(&type_i); for (i = 0; i < np; i++) { jl_value_t *elt = jl_tparam(tt, i); jl_value_t *decl_i = jl_nth_slot_type(decl, i); - jl_value_t *type_i = jl_rewrap_unionall(decl_i, decl); + type_i = jl_rewrap_unionall(decl_i, decl); size_t i_arg = (i < nargs - 1 ? i : nargs - 1); if (jl_is_kind(type_i)) { @@ -779,15 +821,9 @@ static void jl_compilation_sig( // supertype of any other method signatures. so far we are conservative // and the types we find should be bigger. if (jl_nparams(tt) >= nspec && jl_va_tuple_kind((jl_datatype_t*)decl) == JL_VARARG_UNBOUND) { - jl_svec_t *limited = jl_alloc_svec(nspec); - JL_GC_PUSH1(&limited); if (!*newparams) *newparams = tt->parameters; - size_t i; - for (i = 0; i < nspec - 1; i++) { - jl_svecset(limited, i, jl_svecref(*newparams, i)); - } - jl_value_t *lasttype = jl_svecref(*newparams, i - 1); - // if all subsequent arguments are subtypes of lasttype, specialize + type_i = jl_svecref(*newparams, nspec - 2); + // if all subsequent arguments are subtypes of type_i, specialize // on that instead of decl. for example, if decl is // (Any...) // and type is @@ -795,39 +831,35 @@ static void jl_compilation_sig( // then specialize as (Symbol...), but if type is // (Symbol, Int32, Expr) // then specialize as (Any...) - size_t j = i; + size_t j = nspec - 1; int all_are_subtypes = 1; for (; j < jl_svec_len(*newparams); j++) { jl_value_t *paramj = jl_svecref(*newparams, j); if (jl_is_vararg(paramj)) paramj = jl_unwrap_vararg(paramj); - if (!jl_subtype(paramj, lasttype)) { + if (!jl_subtype(paramj, type_i)) { all_are_subtypes = 0; break; } } if (all_are_subtypes) { // avoid Vararg{Type{Type{...}}} - if (jl_is_type_type(lasttype) && jl_is_type_type(jl_tparam0(lasttype))) - lasttype = (jl_value_t*)jl_type_type; - jl_svecset(limited, i, jl_wrap_vararg(lasttype, (jl_value_t*)NULL)); + if (jl_is_type_type(type_i) && jl_is_type_type(jl_tparam0(type_i))) + type_i = (jl_value_t*)jl_type_type; + type_i = (jl_value_t*)jl_wrap_vararg(type_i, (jl_value_t*)NULL); // this cannot throw for these inputs } else { - jl_value_t *unw = jl_unwrap_unionall(decl); - jl_value_t *lastdeclt = jl_tparam(unw, jl_nparams(unw) - 1); - assert(jl_is_vararg(lastdeclt)); - int nsp = jl_svec_len(sparams); - if (nsp > 0 && jl_has_free_typevars(lastdeclt)) { - assert(jl_subtype_env_size(decl) == nsp); - lastdeclt = jl_instantiate_type_in_env(lastdeclt, (jl_unionall_t*)decl, jl_svec_data(sparams)); - // TODO: rewrap_unionall(lastdeclt, sparams) if any sparams isa TypeVar??? - // TODO: if we made any replacements above, sparams may now be incorrect - } - jl_svecset(limited, i, lastdeclt); + type_i = inst_varargp_in_env(decl, sparams); + } + jl_svec_t *limited = jl_alloc_svec(nspec); + size_t i; + for (i = 0; i < nspec - 1; i++) { + jl_svecset(limited, i, jl_svecref(*newparams, i)); } + jl_svecset(limited, i, type_i); *newparams = limited; - JL_GC_POP(); } + JL_GC_POP(); } // compute whether this type signature is a possible return value from jl_compilation_sig given a concrete-type for `tt` @@ -865,18 +897,20 @@ JL_DLLEXPORT int jl_isa_compileable_sig( jl_methtable_t *kwmt = mt == jl_kwcall_mt ? jl_kwmethod_table_for(decl) : mt; if ((jl_value_t*)mt != jl_nothing) { // try to refine estimate of min and max - if (kwmt && kwmt != jl_type_type_mt && kwmt != jl_nonfunction_mt && kwmt != jl_kwcall_mt) + if (kwmt != NULL && kwmt != jl_type_type_mt && kwmt != jl_nonfunction_mt && kwmt != jl_kwcall_mt) + // new methods may be added, increasing nspec_min later nspec_min = jl_atomic_load_relaxed(&kwmt->max_args) + 2 + 2 * (mt == jl_kwcall_mt); else + // nspec is always nargs+1, regardless of the other contents of these mt nspec_max = nspec_min; } - int isbound = (jl_va_tuple_kind((jl_datatype_t*)decl) == JL_VARARG_UNBOUND); + int isunbound = (jl_va_tuple_kind((jl_datatype_t*)decl) == JL_VARARG_UNBOUND); if (jl_is_vararg(jl_tparam(type, np - 1))) { - if (!isbound || np < nspec_min || np > nspec_max) + if (!isunbound || np < nspec_min || np > nspec_max) return 0; } else { - if (np < nargs - 1 || (isbound && np >= nspec_max)) + if (np < nargs - 1 || (isunbound && np >= nspec_max)) return 0; } } @@ -884,37 +918,37 @@ JL_DLLEXPORT int jl_isa_compileable_sig( return 0; } + jl_value_t *type_i = NULL; + JL_GC_PUSH1(&type_i); for (i = 0; i < np; i++) { jl_value_t *elt = jl_tparam(type, i); - jl_value_t *decl_i = jl_nth_slot_type((jl_value_t*)decl, i); - jl_value_t *type_i = jl_rewrap_unionall(decl_i, decl); size_t i_arg = (i < nargs - 1 ? i : nargs - 1); if (jl_is_vararg(elt)) { - elt = jl_unwrap_vararg(elt); - if (jl_has_free_typevars(decl_i)) { - // TODO: in this case, answer semi-conservatively that these varargs are always compilable - // we don't have the ability to get sparams, so deciding if elt - // is a potential result of jl_instantiate_type_in_env for decl_i - // for any sparams that is consistent with the rest of the arguments - // seems like it would be extremely difficult - // and hopefully the upstream code probably gave us something reasonable - continue; - } - else if (jl_egal(elt, decl_i)) { - continue; + type_i = inst_varargp_in_env(decl, sparams); + if (jl_has_free_typevars(type_i)) { + JL_GC_POP(); + return 0; // something went badly wrong? } - else if (jl_is_type_type(elt) && jl_is_type_type(jl_tparam0(elt))) { - return 0; + if (jl_egal(elt, type_i)) + continue; // elt could be chosen by inst_varargp_in_env for these sparams + elt = jl_unwrap_vararg(elt); + if (jl_is_type_type(elt) && jl_is_type_type(jl_tparam0(elt))) { + JL_GC_POP(); + return 0; // elt would be set equal to jl_type_type instead } - // else, it needs to meet the usual rules + // else, elt also needs to meet the usual rules } + jl_value_t *decl_i = jl_nth_slot_type(decl, i); + type_i = jl_rewrap_unionall(decl_i, decl); + if (i_arg > 0 && i_arg <= sizeof(definition->nospecialize) * 8 && (definition->nospecialize & (1 << (i_arg - 1)))) { if (!jl_has_free_typevars(decl_i) && !jl_is_kind(decl_i)) { if (jl_egal(elt, decl_i)) continue; + JL_GC_POP(); return 0; } } @@ -923,10 +957,12 @@ JL_DLLEXPORT int jl_isa_compileable_sig( // kind slots always get guard entries (checking for subtypes of Type) if (jl_subtype(elt, type_i) && !jl_subtype((jl_value_t*)jl_type_type, type_i)) continue; - // TODO: other code paths that could reach here + // TODO: other code paths that could reach here? + JL_GC_POP(); return 0; } else if (jl_is_kind(type_i)) { + JL_GC_POP(); return 0; } @@ -938,22 +974,31 @@ JL_DLLEXPORT int jl_isa_compileable_sig( continue; if (i >= nargs && definition->isva) continue; + JL_GC_POP(); return 0; } - if (!iscalled && very_general_type(type_i)) + if (!iscalled && very_general_type(type_i)) { + JL_GC_POP(); return 0; - if (!jl_is_datatype(elt)) + } + if (!jl_is_datatype(elt)) { + JL_GC_POP(); return 0; + } // if the declared type was not Any or Union{Type, ...}, // then the match must been with kind, such as UnionAll or DataType, // and the result of matching the type signature // needs to be corrected to the concrete type 'kind' (and not to Type) jl_value_t *kind = jl_typeof(jl_tparam0(elt)); - if (kind == jl_bottom_type) + if (kind == jl_bottom_type) { + JL_GC_POP(); return 0; // Type{Union{}} gets normalized to typeof(Union{}) - if (jl_subtype(kind, type_i) && !jl_subtype((jl_value_t*)jl_type_type, type_i)) + } + if (jl_subtype(kind, type_i) && !jl_subtype((jl_value_t*)jl_type_type, type_i)) { + JL_GC_POP(); return 0; // gets turned into a kind + } else if (jl_is_type_type(jl_tparam0(elt)) && // give up on specializing static parameters for Type{Type{Type{...}}} @@ -966,20 +1011,20 @@ JL_DLLEXPORT int jl_isa_compileable_sig( this can be determined using a type intersection. */ if (i < nargs || !definition->isva) { - jl_value_t *di = jl_type_intersection(type_i, (jl_value_t*)jl_type_type); - JL_GC_PUSH1(&di); - assert(di != (jl_value_t*)jl_bottom_type); - if (jl_is_kind(di)) { + type_i = jl_type_intersection(type_i, (jl_value_t*)jl_type_type); + assert(type_i != (jl_value_t*)jl_bottom_type); + if (jl_is_kind(type_i)) { JL_GC_POP(); return 0; } - else if (!jl_types_equal(di, elt)) { + else if (!jl_types_equal(type_i, elt)) { JL_GC_POP(); return 0; } - JL_GC_POP(); + continue; } else { + JL_GC_POP(); return 0; } } @@ -1000,12 +1045,16 @@ JL_DLLEXPORT int jl_isa_compileable_sig( // when called with a subtype of Function but is not called if (elt == (jl_value_t*)jl_function_type) continue; + JL_GC_POP(); return 0; } - if (!jl_is_concrete_type(elt)) + if (!jl_is_concrete_type(elt)) { + JL_GC_POP(); return 0; + } } + JL_GC_POP(); return 1; } diff --git a/test/core.jl b/test/core.jl index 8f018d6aa09508..bab6be0de5644c 100644 --- a/test/core.jl +++ b/test/core.jl @@ -7918,3 +7918,12 @@ code_typed(f47476, (Int, Int, Int, Int, Vararg{Union{Int, NTuple{2,Int}}},)) vect47476(::Type{T}) where {T} = T @test vect47476(Type{Type{Type{Int32}}}) === Type{Type{Type{Int32}}} @test vect47476(Type{Type{Type{Int64}}}) === Type{Type{Type{Int64}}} + +g47476(::Union{Nothing,Int,Val{T}}...) where {T} = T +@test_throws UndefVarError(:T) g47476(nothing, 1, nothing, 2, nothing, 3, nothing, 4, nothing, 5) +@test g47476(nothing, 1, nothing, 2, nothing, 3, nothing, 4, nothing, 5, Val(6)) === 6 +let spec = only(methods(g47476)).specializations + @test !isempty(spec) + @test any(mi -> mi !== nothing && Base.isvatuple(mi.specTypes), spec) + @test all(mi -> mi === nothing || !Base.has_free_typevars(mi.specTypes), spec) +end From 6bfc6ac7187f04cc2554425ecc8a2fa42b4f1af8 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Fri, 9 Dec 2022 10:49:14 +0900 Subject: [PATCH 091/387] inference: simplify `sptypes_from_meth_instance` (#47836) --- base/compiler/inferencestate.jl | 59 +++++++++++++++------------------ 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index 49315fd67834c0..61b2fe1f27c723 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -383,55 +383,50 @@ function sptypes_from_meth_instance(linfo::MethodInstance) for i = 1:length(sp) v = sp[i] if v isa TypeVar - fromArg = 0 - # if this parameter came from arg::Type{T}, then `arg` is more precise than - # Type{T} where lb<:T<:ub - sig = linfo.def.sig - temp = sig + temp = linfo.def.sig for j = 1:i-1 temp = temp.body end - Pi = temp.var + vᵢ = (temp::UnionAll).var while temp isa UnionAll temp = temp.body end sigtypes = (temp::DataType).parameters for j = 1:length(sigtypes) - tj = sigtypes[j] - if isType(tj) && tj.parameters[1] === Pi - fromArg = j - break + sⱼ = sigtypes[j] + if isType(sⱼ) && sⱼ.parameters[1] === vᵢ + # if this parameter came from `arg::Type{T}`, + # then `arg` is more precise than `Type{T} where lb<:T<:ub` + ty = fieldtype(linfo.specTypes, j) + @goto ty_computed end end - if fromArg > 0 - ty = fieldtype(linfo.specTypes, fromArg) + ub = v.ub + while ub isa TypeVar + ub = ub.ub + end + if has_free_typevars(ub) + ub = Any + end + lb = v.lb + while lb isa TypeVar + lb = lb.lb + end + if has_free_typevars(lb) + lb = Bottom + end + if Any <: ub && lb <: Bottom + ty = Any else - ub = v.ub - while ub isa TypeVar - ub = ub.ub - end - if has_free_typevars(ub) - ub = Any - end - lb = v.lb - while lb isa TypeVar - lb = lb.lb - end - if has_free_typevars(lb) - lb = Bottom - end - if Any <: ub && lb <: Bottom - ty = Any - else - tv = TypeVar(v.name, lb, ub) - ty = UnionAll(tv, Type{tv}) - end + tv = TypeVar(v.name, lb, ub) + ty = UnionAll(tv, Type{tv}) end elseif isvarargtype(v) ty = Int else ty = Const(v) end + @label ty_computed sp[i] = ty end return sp From 2a0d58a32f49573299e1f4cca04bac0f6e6c7717 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Fri, 9 Dec 2022 03:54:34 -0500 Subject: [PATCH 092/387] abstract_apply: Don't drop effects of `iterate`'d calls (#47846) We were accidentally dropping the effects of calls from `iterate` calls performed during abstract_iteration. This allowed calls that were not actually eligible for (semi-)concrete evaluation to go through that path anyway. This could cause incorrect results (see test), though it was usually fine, since iterate call tend to not have side effects. It was noticed however in #47688, because it forced irinterp down a path that was not meant to be reachable (resulting in a TODO error message). For good measure, let's also address this todo (since it is reachable by external absint if they want), but the missing effect propagation was the more serious bug here. --- base/compiler/abstractinterpretation.jl | 62 +++++++++++++++---------- base/compiler/ssair/inlining.jl | 8 ++-- base/compiler/ssair/irinterp.jl | 16 +++++-- base/compiler/stmtinfo.jl | 1 + test/compiler/inference.jl | 16 +++++++ 5 files changed, 72 insertions(+), 31 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index d11bb43c03ee09..ba3edcdf2d73b0 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1342,6 +1342,14 @@ function ssa_def_slot(@nospecialize(arg), sv::InferenceState) return arg end +struct AbstractIterationResult + cti::Vector{Any} + info::MaybeAbstractIterationInfo + ai_effects::Effects +end +AbstractIterationResult(cti::Vector{Any}, info::MaybeAbstractIterationInfo) = + AbstractIterationResult(cti, info, EFFECTS_TOTAL) + # `typ` is the inferred type for expression `arg`. # if the expression constructs a container (e.g. `svec(x,y,z)`), # refine its type to an array of element types. @@ -1352,14 +1360,14 @@ function precise_container_type(interp::AbstractInterpreter, @nospecialize(itft) if isa(typ, PartialStruct) widet = typ.typ if isa(widet, DataType) && widet.name === Tuple.name - return typ.fields, nothing + return AbstractIterationResult(typ.fields, nothing) end end if isa(typ, Const) val = typ.val if isa(val, SimpleVector) || isa(val, Tuple) - return Any[ Const(val[i]) for i in 1:length(val) ], nothing # avoid making a tuple Generator here! + return AbstractIterationResult(Any[ Const(val[i]) for i in 1:length(val) ], nothing) # avoid making a tuple Generator here! end end @@ -1374,12 +1382,12 @@ function precise_container_type(interp::AbstractInterpreter, @nospecialize(itft) if isa(tti, Union) utis = uniontypes(tti) if any(@nospecialize(t) -> !isa(t, DataType) || !(t <: Tuple) || !isknownlength(t), utis) - return Any[Vararg{Any}], nothing + return AbstractIterationResult(Any[Vararg{Any}], nothing, EFFECTS_UNKNOWN′) end ltp = length((utis[1]::DataType).parameters) for t in utis if length((t::DataType).parameters) != ltp - return Any[Vararg{Any}], nothing + return AbstractIterationResult(Any[Vararg{Any}], nothing) end end result = Any[ Union{} for _ in 1:ltp ] @@ -1390,12 +1398,12 @@ function precise_container_type(interp::AbstractInterpreter, @nospecialize(itft) result[j] = tmerge(result[j], rewrap_unionall(tps[j], tti0)) end end - return result, nothing + return AbstractIterationResult(result, nothing) elseif tti0 <: Tuple if isa(tti0, DataType) - return Any[ p for p in tti0.parameters ], nothing + return AbstractIterationResult(Any[ p for p in tti0.parameters ], nothing) elseif !isa(tti, DataType) - return Any[Vararg{Any}], nothing + return AbstractIterationResult(Any[Vararg{Any}], nothing) else len = length(tti.parameters) last = tti.parameters[len] @@ -1404,12 +1412,14 @@ function precise_container_type(interp::AbstractInterpreter, @nospecialize(itft) if va elts[len] = Vararg{elts[len]} end - return elts, nothing + return AbstractIterationResult(elts, nothing) end - elseif tti0 === SimpleVector || tti0 === Any - return Any[Vararg{Any}], nothing + elseif tti0 === SimpleVector + return AbstractIterationResult(Any[Vararg{Any}], nothing) + elseif tti0 === Any + return AbstractIterationResult(Any[Vararg{Any}], nothing, EFFECTS_UNKNOWN′) elseif tti0 <: Array - return Any[Vararg{eltype(tti0)}], nothing + return AbstractIterationResult(Any[Vararg{eltype(tti0)}], nothing) else return abstract_iteration(interp, itft, typ, sv) end @@ -1420,7 +1430,7 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n if isa(itft, Const) iteratef = itft.val else - return Any[Vararg{Any}], nothing + return AbstractIterationResult(Any[Vararg{Any}], nothing, EFFECTS_UNKNOWN′) end @assert !isvarargtype(itertype) call = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[itft, itertype]), StmtInfo(true), sv) @@ -1430,7 +1440,7 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n # WARNING: Changes to the iteration protocol must be reflected here, # this is not just an optimization. # TODO: this doesn't realize that Array, SimpleVector, Tuple, and NamedTuple do not use the iterate protocol - stateordonet === Bottom && return Any[Bottom], AbstractIterationInfo(CallMeta[CallMeta(Bottom, call.effects, info)]) + stateordonet === Bottom && return AbstractIterationResult(Any[Bottom], AbstractIterationInfo(CallMeta[CallMeta(Bottom, call.effects, info)], true)) valtype = statetype = Bottom ret = Any[] calls = CallMeta[call] @@ -1440,7 +1450,7 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n # length iterators, or interesting prefix while true if stateordonet_widened === Nothing - return ret, AbstractIterationInfo(calls) + return AbstractIterationResult(ret, AbstractIterationInfo(calls, true)) end if Nothing <: stateordonet_widened || length(ret) >= InferenceParams(interp).max_tuple_splat break @@ -1452,7 +1462,7 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n # If there's no new information in this statetype, don't bother continuing, # the iterator won't be finite. if ⊑(typeinf_lattice(interp), nstatetype, statetype) - return Any[Bottom], nothing + return AbstractIterationResult(Any[Bottom], AbstractIterationInfo(calls, false), EFFECTS_THROWS) end valtype = getfield_tfunc(typeinf_lattice(interp), stateordonet, Const(1)) push!(ret, valtype) @@ -1482,7 +1492,7 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n # ... but cannot terminate if !may_have_terminated # ... and cannot have terminated prior to this loop - return Any[Bottom], nothing + return AbstractIterationResult(Any[Bottom], AbstractIterationInfo(calls, false), EFFECTS_UNKNOWN′) else # iterator may have terminated prior to this loop, but not during it valtype = Bottom @@ -1492,13 +1502,15 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n end valtype = tmerge(valtype, nounion.parameters[1]) statetype = tmerge(statetype, nounion.parameters[2]) - stateordonet = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[Const(iteratef), itertype, statetype]), StmtInfo(true), sv).rt + call = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[Const(iteratef), itertype, statetype]), StmtInfo(true), sv) + push!(calls, call) + stateordonet = call.rt stateordonet_widened = widenconst(stateordonet) end if valtype !== Union{} push!(ret, Vararg{valtype}) end - return ret, nothing + return AbstractIterationResult(ret, AbstractIterationInfo(calls, false)) end # do apply(af, fargs...), where af is a function value @@ -1529,13 +1541,9 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: infos′ = Vector{MaybeAbstractIterationInfo}[] for ti in (splitunions ? uniontypes(aargtypes[i]) : Any[aargtypes[i]]) if !isvarargtype(ti) - cti_info = precise_container_type(interp, itft, ti, sv) - cti = cti_info[1]::Vector{Any} - info = cti_info[2]::MaybeAbstractIterationInfo + (;cti, info, ai_effects) = precise_container_type(interp, itft, ti, sv) else - cti_info = precise_container_type(interp, itft, unwrapva(ti), sv) - cti = cti_info[1]::Vector{Any} - info = cti_info[2]::MaybeAbstractIterationInfo + (;cti, info, ai_effects) = precise_container_type(interp, itft, unwrapva(ti), sv) # We can't represent a repeating sequence of the same types, # so tmerge everything together to get one type that represents # everything. @@ -1548,6 +1556,12 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: end cti = Any[Vararg{argt}] end + effects = merge_effects(effects, ai_effects) + if info !== nothing + for call in info.each + effects = merge_effects(effects, call.effects) + end + end if any(@nospecialize(t) -> t === Bottom, cti) continue end diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 43b9caa1b31546..63319509b672ba 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -729,7 +729,7 @@ function rewrite_apply_exprargs!(todo::Vector{Pair{Int,Any}}, def = argexprs[i] def_type = argtypes[i] thisarginfo = arginfos[i-arg_start] - if thisarginfo === nothing + if thisarginfo === nothing || !thisarginfo.complete if def_type isa PartialStruct # def_type.typ <: Tuple is assumed def_argtypes = def_type.fields @@ -1134,9 +1134,9 @@ function inline_apply!(todo::Vector{Pair{Int,Any}}, for i = (arg_start + 1):length(argtypes) thisarginfo = nothing if !is_valid_type_for_apply_rewrite(argtypes[i], state.params) - if isa(info, ApplyCallInfo) && info.arginfo[i-arg_start] !== nothing - thisarginfo = info.arginfo[i-arg_start] - else + isa(info, ApplyCallInfo) || return nothing + thisarginfo = info.arginfo[i-arg_start] + if thisarginfo === nothing || !thisarginfo.complete return nothing end end diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index ad10064b2d74ae..89e0851e84a60c 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -66,7 +66,17 @@ function kill_def_use!(tpdum::TwoPhaseDefUseMap, def::Int, use::Int) if !tpdum.complete tpdum.ssa_uses[def] -= 1 else - @assert false && "TODO" + range = tpdum.ssa_uses[def]:(def == length(tpdum.ssa_uses) ? length(tpdum.data) : (tpdum.ssa_uses[def + 1] - 1)) + # TODO: Sorted + useidx = findfirst(idx->tpdum.data[idx] == use, range) + @assert useidx !== nothing + idx = range[useidx] + while idx < lastindex(range) + ndata = tpdum.data[idx+1] + ndata == 0 && break + tpdum.data[idx] = ndata + end + tpdum.data[idx + 1] = 0 end end kill_def_use!(tpdum::TwoPhaseDefUseMap, def::SSAValue, use::Int) = @@ -262,11 +272,11 @@ function process_terminator!(ir::IRCode, idx::Int, bb::Int, end return false elseif isa(inst, GotoNode) - backedge = inst.label < bb + backedge = inst.label <= bb !backedge && push!(ip, inst.label) return backedge elseif isa(inst, GotoIfNot) - backedge = inst.dest < bb + backedge = inst.dest <= bb !backedge && push!(ip, inst.dest) push!(ip, bb + 1) return backedge diff --git a/base/compiler/stmtinfo.jl b/base/compiler/stmtinfo.jl index 556c0082e45324..23f8c3aba908ec 100644 --- a/base/compiler/stmtinfo.jl +++ b/base/compiler/stmtinfo.jl @@ -114,6 +114,7 @@ Each (abstract) call to `iterate`, corresponds to one entry in `ainfo.each::Vect """ struct AbstractIterationInfo each::Vector{CallMeta} + complete::Bool end const MaybeAbstractIterationInfo = Union{Nothing, AbstractIterationInfo} diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index d440b0097ac535..a6a8684f67595d 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -4633,3 +4633,19 @@ end |> only === Type{Float64} # Issue #46839: `abstract_invoke` should handle incorrect call type @test only(Base.return_types(()->invoke(BitSet, Any, x), ())) === Union{} @test only(Base.return_types(()->invoke(BitSet, Union{Tuple{Int32},Tuple{Int64}}, 1), ())) === Union{} + +# Issue #47688: Abstract iteration should take into account `iterate` effects +global it_count47688 = 0 +struct CountsIterate47688{N}; end +function Base.iterate(::CountsIterate47688{N}, n=0) where N + global it_count47688 += 1 + n <= N ? (n, n+1) : nothing +end +foo47688() = tuple(CountsIterate47688{5}()...) +bar47688() = foo47688() +@test only(Base.return_types(bar47688)) == NTuple{6, Int} +@test it_count47688 == 0 +@test isa(bar47688(), NTuple{6, Int}) +@test it_count47688 == 7 +@test isa(foo47688(), NTuple{6, Int}) +@test it_count47688 == 14 From 150590ce51e806c14f068d16f5ea1d813d7e5cb7 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Fri, 9 Dec 2022 15:34:19 +0600 Subject: [PATCH 093/387] Fix typo in docstring (#47834) Co-authored-by: Fredrik Ekre --- base/asyncevent.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/asyncevent.jl b/base/asyncevent.jl index 183f38613a50ff..a26945bbb11051 100644 --- a/base/asyncevent.jl +++ b/base/asyncevent.jl @@ -250,8 +250,8 @@ Create a timer that runs the function `callback` at each timer expiration. Waiting tasks are woken and the function `callback` is called after an initial delay of `delay` seconds, and then repeating with the given `interval` in seconds. If `interval` is equal to `0`, the callback is only run once. The function `callback` is called with a single argument, the timer -itself. Stop a timer by calling `close`. The `cb` may still be run one final time, if the timer has -already expired. +itself. Stop a timer by calling `close`. The `callback` may still be run one final time, if the timer +has already expired. # Examples From 490fdcef06b21f51b6b1336883c8eff54cad36f8 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Sat, 10 Dec 2022 06:38:49 +0900 Subject: [PATCH 094/387] =?UTF-8?q?inference:=20thread=20through=20`?= =?UTF-8?q?=F0=9D=95=83::AbstractLattice`=20argument=20for=20all=20tfuncs?= =?UTF-8?q?=20(#47848)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- base/compiler/abstractinterpretation.jl | 25 +- base/compiler/inferenceresult.jl | 4 +- base/compiler/ssair/inlining.jl | 13 +- base/compiler/ssair/passes.jl | 21 +- base/compiler/tfuncs.jl | 291 +++++++----- base/compiler/typelimits.jl | 31 +- test/compiler/inference.jl | 581 +++++++++++++----------- test/compiler/inline.jl | 7 +- 8 files changed, 524 insertions(+), 449 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index ba3edcdf2d73b0..2160999f30c9e9 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1445,6 +1445,7 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n ret = Any[] calls = CallMeta[call] stateordonet_widened = widenconst(stateordonet) + 𝕃ᵢ = typeinf_lattice(interp) # Try to unroll the iteration up to max_tuple_splat, which covers any finite # length iterators, or interesting prefix @@ -1458,13 +1459,13 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n if !isa(stateordonet_widened, DataType) || !(stateordonet_widened <: Tuple) || isvatuple(stateordonet_widened) || length(stateordonet_widened.parameters) != 2 break end - nstatetype = getfield_tfunc(typeinf_lattice(interp), stateordonet, Const(2)) + nstatetype = getfield_tfunc(𝕃ᵢ, stateordonet, Const(2)) # If there's no new information in this statetype, don't bother continuing, # the iterator won't be finite. - if ⊑(typeinf_lattice(interp), nstatetype, statetype) + if ⊑(𝕃ᵢ, nstatetype, statetype) return AbstractIterationResult(Any[Bottom], AbstractIterationInfo(calls, false), EFFECTS_THROWS) end - valtype = getfield_tfunc(typeinf_lattice(interp), stateordonet, Const(1)) + valtype = getfield_tfunc(𝕃ᵢ, stateordonet, Const(1)) push!(ret, valtype) statetype = nstatetype call = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[Const(iteratef), itertype, statetype]), StmtInfo(true), sv) @@ -1476,8 +1477,7 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n # the precise (potentially const) state type # statetype and valtype are reinitialized in the first iteration below from the # (widened) stateordonet, which has not yet been fully analyzed in the loop above - statetype = Bottom - valtype = Bottom + valtype = statetype = Bottom may_have_terminated = Nothing <: stateordonet_widened while valtype !== Any nounion = typeintersect(stateordonet_widened, Tuple{Any,Any}) @@ -1824,7 +1824,7 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, (; fargs thentype = Bottom elsetype = Bottom for ty in uniontypes(uty) - cnd = isdefined_tfunc(ty, fld) + cnd = isdefined_tfunc(𝕃ᵢ, ty, fld) if isa(cnd, Const) if cnd.val::Bool thentype = tmerge(thentype, ty) @@ -1987,7 +1987,7 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), elseif la == 3 ub_var = argtypes[3] end - return CallMeta(typevar_tfunc(n, lb_var, ub_var), EFFECTS_UNKNOWN, NoCallInfo()) + return CallMeta(typevar_tfunc(𝕃ᵢ, n, lb_var, ub_var), EFFECTS_UNKNOWN, NoCallInfo()) elseif f === UnionAll return CallMeta(abstract_call_unionall(argtypes), EFFECTS_UNKNOWN, NoCallInfo()) elseif f === Tuple && la == 2 @@ -2248,7 +2248,8 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp sv::Union{InferenceState, IRCode}, mi::Union{MethodInstance, Nothing})::RTEffects effects = EFFECTS_UNKNOWN ehead = e.head - ⊑ᵢ = ⊑(typeinf_lattice(interp)) + 𝕃ᵢ = typeinf_lattice(interp) + ⊑ᵢ = ⊑(𝕃ᵢ) if ehead === :call ea = e.args argtypes = collect_argtypes(interp, ea, vtypes, sv) @@ -2280,7 +2281,7 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp at = widenslotwrapper(abstract_eval_value(interp, e.args[i+1], vtypes, sv)) ft = fieldtype(t, i) nothrow && (nothrow = at ⊑ᵢ ft) - at = tmeet(typeinf_lattice(interp), at, ft) + at = tmeet(𝕃ᵢ, at, ft) at === Bottom && @goto always_throw if ismutable && !isconst(t, i) ats[i] = ft # can't constrain this field (as it may be modified later) @@ -2288,8 +2289,8 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp end allconst &= isa(at, Const) if !anyrefine - anyrefine = has_nontrivial_const_info(typeinf_lattice(interp), at) || # constant information - ⋤(typeinf_lattice(interp), at, ft) # just a type-level information, but more precise than the declared type + anyrefine = has_nontrivial_const_info(𝕃ᵢ, at) || # constant information + ⋤(𝕃ᵢ, at, ft) # just a type-level information, but more precise than the declared type end ats[i] = at end @@ -2354,7 +2355,7 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp t = Bottom else mi′ = isa(sv, InferenceState) ? sv.linfo : mi - t = _opaque_closure_tfunc(argtypes[1], argtypes[2], argtypes[3], + t = _opaque_closure_tfunc(𝕃ᵢ, argtypes[1], argtypes[2], argtypes[3], argtypes[4], argtypes[5:end], mi′) if isa(t, PartialOpaque) && isa(sv, InferenceState) && !call_result_unused(sv, sv.currpc) # Infer this now so that the specialization is available to diff --git a/base/compiler/inferenceresult.jl b/base/compiler/inferenceresult.jl index 792495cb6dc0db..f54173c899cbc3 100644 --- a/base/compiler/inferenceresult.jl +++ b/base/compiler/inferenceresult.jl @@ -103,7 +103,7 @@ function va_process_argtypes(@nospecialize(va_handler!), given_argtypes::Vector{ else last = nargs end - isva_given_argtypes[nargs] = tuple_tfunc(given_argtypes[last:end]) + isva_given_argtypes[nargs] = tuple_tfunc(fallback_lattice, given_argtypes[last:end]) va_handler!(isva_given_argtypes, last) end return isva_given_argtypes @@ -158,7 +158,7 @@ function most_general_argtypes(method::Union{Method, Nothing}, @nospecialize(spe vargtype_elements[i] = Const(atyp.parameters[1]) end end - vargtype = tuple_tfunc(vargtype_elements) + vargtype = tuple_tfunc(fallback_lattice, vargtype_elements) end end cache_argtypes[nargs] = vargtype diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 63319509b672ba..94bef6c9e7a7ec 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -778,7 +778,7 @@ function rewrite_apply_exprargs!(todo::Vector{Pair{Int,Any}}, # See if we can inline this call to `iterate` handle_call!(todo, ir, state1.id, new_stmt, new_info, flag, new_sig, istate) if i != length(thisarginfo.each) - valT = getfield_tfunc(call.rt, Const(1)) + valT = getfield_tfunc(OptimizerLattice(), call.rt, Const(1)) val_extracted = insert_node!(ir, idx, NewInstruction( Expr(:call, GlobalRef(Core, :getfield), state1, 1), valT)) @@ -786,7 +786,7 @@ function rewrite_apply_exprargs!(todo::Vector{Pair{Int,Any}}, push!(new_argtypes, valT) state_extracted = insert_node!(ir, idx, NewInstruction( Expr(:call, GlobalRef(Core, :getfield), state1, 2), - getfield_tfunc(call.rt, Const(2)))) + getfield_tfunc(OptimizerLattice(), call.rt, Const(2)))) state = Core.svec(state_extracted) end end @@ -1039,19 +1039,20 @@ function is_valid_type_for_apply_rewrite(@nospecialize(typ), params::Optimizatio end function inline_splatnew!(ir::IRCode, idx::Int, stmt::Expr, @nospecialize(rt)) - nf = nfields_tfunc(rt) + 𝕃ₒ = OptimizerLattice() + nf = nfields_tfunc(𝕃ₒ, rt) if nf isa Const eargs = stmt.args tup = eargs[2] tt = argextype(tup, ir) - tnf = nfields_tfunc(tt) + tnf = nfields_tfunc(𝕃ₒ, tt) # TODO: hoisting this tnf.val === nf.val check into codegen # would enable us to almost always do this transform if tnf isa Const && tnf.val === nf.val n = tnf.val::Int new_argexprs = Any[eargs[1]] for j = 1:n - atype = getfield_tfunc(tt, Const(j)) + atype = getfield_tfunc(𝕃ₒ, tt, Const(j)) new_call = Expr(:call, Core.getfield, tup, j) new_argexpr = insert_node!(ir, idx, NewInstruction(new_call, atype)) push!(new_argexprs, new_argexpr) @@ -1060,7 +1061,7 @@ function inline_splatnew!(ir::IRCode, idx::Int, stmt::Expr, @nospecialize(rt)) stmt.args = new_argexprs end end - nothing + return nothing end function call_sig(ir::IRCode, stmt::Expr) diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index 4b5ee76c28bacc..974f7939d97f56 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -504,7 +504,7 @@ end make_MaybeUndef(@nospecialize(typ)) = isa(typ, MaybeUndef) ? typ : MaybeUndef(typ) """ - lift_comparison!(cmp, compact::IncrementalCompact, idx::Int, stmt::Expr) + lift_comparison!(cmp, compact::IncrementalCompact, idx::Int, stmt::Expr, 𝕃ₒ::AbstractLattice) Replaces `cmp(φ(x, y)::Union{X,Y}, constant)` by `φ(cmp(x, constant), cmp(y, constant))`, where `cmp(x, constant)` and `cmp(y, constant)` can be replaced with constant `Bool`eans. @@ -520,7 +520,7 @@ function lift_comparison! end function lift_comparison!(::typeof(===), compact::IncrementalCompact, idx::Int, stmt::Expr, lifting_cache::IdDict{Pair{AnySSAValue, Any}, AnySSAValue}, - 𝕃ₒ::AbstractLattice = OptimizerLattice()) + 𝕃ₒ::AbstractLattice) args = stmt.args length(args) == 3 || return lhs, rhs = args[2], args[3] @@ -536,35 +536,34 @@ function lift_comparison!(::typeof(===), compact::IncrementalCompact, else return end - egal_tfunc_opt(@nospecialize(x), @nospecialize(y)) = egal_tfunc(𝕃ₒ, x, y) - lift_comparison_leaves!(egal_tfunc_opt, compact, val, cmp, lifting_cache, idx) + lift_comparison_leaves!(egal_tfunc, compact, val, cmp, lifting_cache, idx, 𝕃ₒ) end function lift_comparison!(::typeof(isa), compact::IncrementalCompact, idx::Int, stmt::Expr, lifting_cache::IdDict{Pair{AnySSAValue, Any}, AnySSAValue}, - 𝕃ₒ::AbstractLattice = OptimizerLattice()) + 𝕃ₒ::AbstractLattice) args = stmt.args length(args) == 3 || return cmp = argextype(args[3], compact) val = args[2] - isa_tfunc_opt(@nospecialize(v), @nospecialize(typ)) = isa_tfunc(𝕃ₒ, v, typ) - lift_comparison_leaves!(isa_tfunc_opt, compact, val, cmp, lifting_cache, idx) + lift_comparison_leaves!(isa_tfunc, compact, val, cmp, lifting_cache, idx, 𝕃ₒ) end function lift_comparison!(::typeof(isdefined), compact::IncrementalCompact, idx::Int, stmt::Expr, lifting_cache::IdDict{Pair{AnySSAValue, Any}, AnySSAValue}, - 𝕃ₒ::AbstractLattice = OptimizerLattice()) + 𝕃ₒ::AbstractLattice) args = stmt.args length(args) == 3 || return cmp = argextype(args[3], compact) isa(cmp, Const) || return # `isdefined_tfunc` won't return Const val = args[2] - lift_comparison_leaves!(isdefined_tfunc, compact, val, cmp, lifting_cache, idx) + lift_comparison_leaves!(isdefined_tfunc, compact, val, cmp, lifting_cache, idx, 𝕃ₒ) end function lift_comparison_leaves!(@specialize(tfunc), compact::IncrementalCompact, @nospecialize(val), @nospecialize(cmp), - lifting_cache::IdDict{Pair{AnySSAValue, Any}, AnySSAValue}, idx::Int) + lifting_cache::IdDict{Pair{AnySSAValue, Any}, AnySSAValue}, idx::Int, + 𝕃ₒ::AbstractLattice) typeconstraint = widenconst(argextype(val, compact)) if isa(val, Union{OldSSAValue, SSAValue}) val, typeconstraint = simple_walk_constraint(compact, val, typeconstraint) @@ -577,7 +576,7 @@ function lift_comparison_leaves!(@specialize(tfunc), lifted_leaves = nothing for i = 1:length(leaves) leaf = leaves[i] - result = tfunc(argextype(leaf, compact), cmp) + result = tfunc(𝕃ₒ, argextype(leaf, compact), cmp) if isa(result, Const) if lifted_leaves === nothing lifted_leaves = LiftedLeaves() diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 82ed8f19a37b62..df56e7942d5f8c 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -45,7 +45,7 @@ function add_tfunc(f::Function, minarg::Int, maxarg::Int, @nospecialize(tfunc), push!(T_FFUNC_COST, cost) end -add_tfunc(throw, 1, 1, (@nospecialize(x)) -> Bottom, 0) +add_tfunc(throw, 1, 1, (@specialize(𝕃::AbstractLattice), @nospecialize(x)) -> Bottom, 0) # the inverse of typeof_tfunc # returns (type, isexact, isconcrete, istype) @@ -98,12 +98,12 @@ function instanceof_tfunc(@nospecialize(t)) end return Any, false, false, false end -bitcast_tfunc(@nospecialize(t), @nospecialize(x)) = instanceof_tfunc(t)[1] -math_tfunc(@nospecialize(x)) = widenconst(x) -math_tfunc(@nospecialize(x), @nospecialize(y)) = widenconst(x) -math_tfunc(@nospecialize(x), @nospecialize(y), @nospecialize(z)) = widenconst(x) -fptoui_tfunc(@nospecialize(t), @nospecialize(x)) = bitcast_tfunc(t, x) -fptosi_tfunc(@nospecialize(t), @nospecialize(x)) = bitcast_tfunc(t, x) +bitcast_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(t), @nospecialize(x)) = instanceof_tfunc(t)[1] +math_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(x)) = widenconst(x) +math_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(x), @nospecialize(y)) = widenconst(x) +math_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(x), @nospecialize(y), @nospecialize(z)) = widenconst(x) +fptoui_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(t), @nospecialize(x)) = bitcast_tfunc(𝕃, t, x) +fptosi_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(t), @nospecialize(x)) = bitcast_tfunc(𝕃, t, x) ## conversion ## add_tfunc(bitcast, 2, 2, bitcast_tfunc, 1) @@ -169,7 +169,7 @@ add_tfunc(rint_llvm, 1, 1, math_tfunc, 10) add_tfunc(sqrt_llvm, 1, 1, math_tfunc, 20) add_tfunc(sqrt_llvm_fast, 1, 1, math_tfunc, 20) ## same-type comparisons ## -cmp_tfunc(@nospecialize(x), @nospecialize(y)) = Bool +cmp_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(x), @nospecialize(y)) = Bool add_tfunc(eq_int, 2, 2, cmp_tfunc, 1) add_tfunc(ne_int, 2, 2, cmp_tfunc, 1) add_tfunc(slt_int, 2, 2, cmp_tfunc, 1) @@ -187,7 +187,7 @@ add_tfunc(lt_float_fast, 2, 2, cmp_tfunc, 1) add_tfunc(le_float_fast, 2, 2, cmp_tfunc, 1) ## checked arithmetic ## -chk_tfunc(@nospecialize(x), @nospecialize(y)) = Tuple{widenconst(x), Bool} +chk_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(x), @nospecialize(y)) = Tuple{widenconst(x), Bool} add_tfunc(checked_sadd_int, 2, 2, chk_tfunc, 10) add_tfunc(checked_uadd_int, 2, 2, chk_tfunc, 10) add_tfunc(checked_ssub_int, 2, 2, chk_tfunc, 10) @@ -195,15 +195,21 @@ add_tfunc(checked_usub_int, 2, 2, chk_tfunc, 10) add_tfunc(checked_smul_int, 2, 2, chk_tfunc, 10) add_tfunc(checked_umul_int, 2, 2, chk_tfunc, 10) ## other, misc intrinsics ## -add_tfunc(Core.Intrinsics.llvmcall, 3, INT_INF, - (@nospecialize(fptr), @nospecialize(rt), @nospecialize(at), a...) -> instanceof_tfunc(rt)[1], 10) -cglobal_tfunc(@nospecialize(fptr)) = Ptr{Cvoid} -cglobal_tfunc(@nospecialize(fptr), @nospecialize(t)) = (isType(t) ? Ptr{t.parameters[1]} : Ptr) -cglobal_tfunc(@nospecialize(fptr), t::Const) = (isa(t.val, Type) ? Ptr{t.val} : Ptr) +function llvmcall_tfunc(@specialize(𝕃::AbstractLattice), fptr, rt, at, a...) + @nospecialize fptr rt at a + return instanceof_tfunc(rt)[1] +end +add_tfunc(Core.Intrinsics.llvmcall, 3, INT_INF, llvmcall_tfunc, 10) +cglobal_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(fptr)) = Ptr{Cvoid} +function cglobal_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(fptr), @nospecialize(t)) + isa(t, Const) && return isa(t.val, Type) ? Ptr{t.val} : Ptr + return isType(t) ? Ptr{t.parameters[1]} : Ptr +end add_tfunc(Core.Intrinsics.cglobal, 1, 2, cglobal_tfunc, 5) -add_tfunc(Core.Intrinsics.have_fma, 1, 1, @nospecialize(x)->Bool, 1) +add_tfunc(Core.Intrinsics.have_fma, 1, 1, (@specialize(𝕃::AbstractLattice), @nospecialize(x))->Bool, 1) +add_tfunc(Core.Intrinsics.arraylen, 1, 1, (@specialize(𝕃::AbstractLattice), @nospecialize(x))->Int, 4) -function ifelse_tfunc(@nospecialize(cnd), @nospecialize(x), @nospecialize(y)) +function ifelse_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(cnd), @nospecialize(x), @nospecialize(y)) cnd = widenslotwrapper(cnd) if isa(cnd, Const) if cnd.val === true @@ -271,8 +277,11 @@ function isdefined_nothrow(@specialize(𝕃::AbstractLattice), @nospecialize(x), end end -isdefined_tfunc(arg1, sym, order) = (@nospecialize; isdefined_tfunc(arg1, sym)) -function isdefined_tfunc(@nospecialize(arg1), @nospecialize(sym)) +function isdefined_tfunc(@specialize(𝕃::AbstractLattice), arg1, sym, order) + @nospecialize arg1 sym order + return isdefined_tfunc(𝕃, arg1, sym) +end +function isdefined_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(arg1), @nospecialize(sym)) if isa(arg1, Const) arg1t = typeof(arg1.val) else @@ -326,8 +335,8 @@ function isdefined_tfunc(@nospecialize(arg1), @nospecialize(sym)) elseif isa(a1, Union) # Results can only be `Const` or `Bool` return tmerge(fallback_lattice, - isdefined_tfunc(rewrap_unionall(a1.a, arg1t), sym), - isdefined_tfunc(rewrap_unionall(a1.b, arg1t), sym)) + isdefined_tfunc(𝕃, rewrap_unionall(a1.a, arg1t), sym), + isdefined_tfunc(𝕃, rewrap_unionall(a1.b, arg1t), sym)) end return Bool end @@ -385,15 +394,15 @@ function _const_sizeof(@nospecialize(x)) end return Const(size) end -function sizeof_tfunc(@nospecialize(x)) +function sizeof_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(x)) x = widenmustalias(x) isa(x, Const) && return _const_sizeof(x.val) isa(x, Conditional) && return _const_sizeof(Bool) isconstType(x) && return _const_sizeof(x.parameters[1]) xu = unwrap_unionall(x) if isa(xu, Union) - return tmerge(sizeof_tfunc(rewrap_unionall(xu.a, x)), - sizeof_tfunc(rewrap_unionall(xu.b, x))) + return tmerge(sizeof_tfunc(𝕃, rewrap_unionall(xu.a, x)), + sizeof_tfunc(𝕃, rewrap_unionall(xu.b, x))) end # Core.sizeof operates on either a type or a value. First check which # case we're in. @@ -416,7 +425,7 @@ function sizeof_tfunc(@nospecialize(x)) return Int end add_tfunc(Core.sizeof, 1, 1, sizeof_tfunc, 1) -function nfields_tfunc(@nospecialize(x)) +function nfields_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(x)) isa(x, Const) && return Const(nfields(x.val)) isa(x, Conditional) && return Const(0) xt = widenconst(x) @@ -429,23 +438,23 @@ function nfields_tfunc(@nospecialize(x)) elseif x.name === _NAMEDTUPLE_NAME length(x.parameters) == 2 || return Int names = x.parameters[1] - isa(names, Tuple{Vararg{Symbol}}) || return nfields_tfunc(rewrap_unionall(x.parameters[2], xt)) + isa(names, Tuple{Vararg{Symbol}}) || return nfields_tfunc(𝕃, rewrap_unionall(x.parameters[2], xt)) return Const(length(names)) else return Const(isdefined(x, :types) ? length(x.types) : length(x.name.names)) end end if isa(x, Union) - na = nfields_tfunc(x.a) + na = nfields_tfunc(𝕃, x.a) na === Int && return Int - return tmerge(na, nfields_tfunc(x.b)) + return tmerge(na, nfields_tfunc(𝕃, x.b)) end return Int end add_tfunc(nfields, 1, 1, nfields_tfunc, 1) -add_tfunc(Core._expr, 1, INT_INF, (@nospecialize args...)->Expr, 100) -add_tfunc(svec, 0, INT_INF, (@nospecialize args...)->SimpleVector, 20) -function typevar_tfunc(@nospecialize(n), @nospecialize(lb_arg), @nospecialize(ub_arg)) +add_tfunc(Core._expr, 1, INT_INF, (@specialize(𝕃::AbstractLattice), @nospecialize args...)->Expr, 100) +add_tfunc(svec, 0, INT_INF, (@specialize(𝕃::AbstractLattice), @nospecialize args...)->SimpleVector, 20) +function typevar_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(n), @nospecialize(lb_arg), @nospecialize(ub_arg)) lb = Union{} ub = Any ub_certain = lb_certain = true @@ -494,10 +503,9 @@ function typevar_nothrow(n, lb, ub) return true end add_tfunc(Core._typevar, 3, 3, typevar_tfunc, 100) -add_tfunc(applicable, 1, INT_INF, (@nospecialize(f), args...)->Bool, 100) -add_tfunc(Core.Intrinsics.arraylen, 1, 1, @nospecialize(x)->Int, 4) +add_tfunc(applicable, 1, INT_INF, (@specialize(𝕃::AbstractLattice), @nospecialize(f), @nospecialize(args...))->Bool, 100) -function arraysize_tfunc(@nospecialize(ary), @nospecialize(dim)) +function arraysize_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(ary), @nospecialize(dim)) hasintersect(widenconst(ary), Array) || return Bottom hasintersect(widenconst(dim), Int) || return Bottom return Int @@ -556,8 +564,33 @@ function pointer_eltype(@nospecialize(ptr)) end return Any end -function atomic_pointermodify_tfunc(ptr, op, v, order) - @nospecialize + +function pointerref_tfunc(@specialize(𝕃::AbstractLattice), a, i, align) + @nospecialize a i align + return pointer_eltype(a) +end +function pointerset_tfunc(@specialize(𝕃::AbstractLattice), a, v, i, align) + @nospecialize a v i align + return a +end +function atomic_fence_tfunc(@specialize(𝕃::AbstractLattice), order) + @nospecialize order + return Nothing +end +function atomic_pointerref_tfunc(@specialize(𝕃::AbstractLattice), a, order) + @nospecialize a order + return pointer_eltype(a) +end +function atomic_pointerset_tfunc(@specialize(𝕃::AbstractLattice), a, v, order) + @nospecialize a v order + return a +end +function atomic_pointerswap_tfunc(@specialize(𝕃::AbstractLattice), a, v, order) + @nospecialize a v order + return pointer_eltype(a) +end +function atomic_pointermodify_tfunc(@specialize(𝕃::AbstractLattice), ptr, op, v, order) + @nospecialize ptr op v order a = widenconst(ptr) if !has_free_typevars(a) unw = unwrap_unionall(a) @@ -570,8 +603,8 @@ function atomic_pointermodify_tfunc(ptr, op, v, order) end return Pair end -function atomic_pointerreplace_tfunc(ptr, x, v, success_order, failure_order) - @nospecialize +function atomic_pointerreplace_tfunc(@specialize(𝕃::AbstractLattice), ptr, x, v, success_order, failure_order) + @nospecialize ptr x v success_order failure_order a = widenconst(ptr) if !has_free_typevars(a) unw = unwrap_unionall(a) @@ -583,16 +616,16 @@ function atomic_pointerreplace_tfunc(ptr, x, v, success_order, failure_order) end return ccall(:jl_apply_cmpswap_type, Any, (Any,), T) where T end -add_tfunc(pointerref, 3, 3, (a, i, align) -> (@nospecialize; pointer_eltype(a)), 4) -add_tfunc(pointerset, 4, 4, (a, v, i, align) -> (@nospecialize; a), 5) -add_tfunc(atomic_fence, 1, 1, (order) -> (@nospecialize; Nothing), 4) -add_tfunc(atomic_pointerref, 2, 2, (a, order) -> (@nospecialize; pointer_eltype(a)), 4) -add_tfunc(atomic_pointerset, 3, 3, (a, v, order) -> (@nospecialize; a), 5) -add_tfunc(atomic_pointerswap, 3, 3, (a, v, order) -> (@nospecialize; pointer_eltype(a)), 5) +add_tfunc(pointerref, 3, 3, pointerref_tfunc, 4) +add_tfunc(pointerset, 4, 4, pointerset_tfunc, 5) +add_tfunc(atomic_fence, 1, 1, atomic_fence_tfunc, 4) +add_tfunc(atomic_pointerref, 2, 2, atomic_pointerref_tfunc, 4) +add_tfunc(atomic_pointerset, 3, 3, atomic_pointerset_tfunc, 5) +add_tfunc(atomic_pointerswap, 3, 3, atomic_pointerswap_tfunc, 5) add_tfunc(atomic_pointermodify, 4, 4, atomic_pointermodify_tfunc, 5) add_tfunc(atomic_pointerreplace, 5, 5, atomic_pointerreplace_tfunc, 5) -add_tfunc(donotdelete, 0, INT_INF, (@nospecialize args...)->Nothing, 0) -function compilerbarrier_tfunc(@nospecialize(setting), @nospecialize(val)) +add_tfunc(donotdelete, 0, INT_INF, (@specialize(𝕃::AbstractLattice), @nospecialize args...)->Nothing, 0) +function compilerbarrier_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(setting), @nospecialize(val)) # strongest barrier if a precise information isn't available at compiler time # XXX we may want to have "compile-time" error instead for such case isa(setting, Const) || return Any @@ -609,7 +642,7 @@ function compilerbarrier_tfunc(@nospecialize(setting), @nospecialize(val)) end end add_tfunc(compilerbarrier, 2, 2, compilerbarrier_tfunc, 5) -add_tfunc(Core.finalizer, 2, 4, (@nospecialize args...)->Nothing, 5) +add_tfunc(Core.finalizer, 2, 4, (@specialize(𝕃::AbstractLattice), @nospecialize args...)->Nothing, 5) function compilerbarrier_nothrow(@nospecialize(setting), @nospecialize(val)) return isa(setting, Const) && contains_is((:type, :const, :conditional), setting.val) @@ -631,7 +664,7 @@ function typeof_concrete_vararg(t::DataType) return nothing end -function typeof_tfunc(@nospecialize(t)) +function typeof_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(t)) isa(t, Const) && return Const(typeof(t.val)) t = widenconst(t) if isType(t) @@ -652,8 +685,8 @@ function typeof_tfunc(@nospecialize(t)) return Type{<:t} end elseif isa(t, Union) - a = widenconst(_typeof_tfunc(t.a)) - b = widenconst(_typeof_tfunc(t.b)) + a = widenconst(_typeof_tfunc(𝕃, t.a)) + b = widenconst(_typeof_tfunc(𝕃, t.b)) return Union{a, b} elseif isa(t, UnionAll) u = unwrap_unionall(t) @@ -667,23 +700,23 @@ function typeof_tfunc(@nospecialize(t)) return rewrap_unionall(Type{u}, t) end end - return rewrap_unionall(widenconst(typeof_tfunc(u)), t) + return rewrap_unionall(widenconst(typeof_tfunc(𝕃, u)), t) end return DataType # typeof(anything)::DataType end # helper function of `typeof_tfunc`, which accepts `TypeVar` -function _typeof_tfunc(@nospecialize(t)) +function _typeof_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(t)) if isa(t, TypeVar) - return t.ub !== Any ? _typeof_tfunc(t.ub) : DataType + return t.ub !== Any ? _typeof_tfunc(𝕃, t.ub) : DataType end - return typeof_tfunc(t) + return typeof_tfunc(𝕃, t) end add_tfunc(typeof, 1, 1, typeof_tfunc, 1) -function typeassert_tfunc(@nospecialize(v), @nospecialize(t)) +function typeassert_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(v), @nospecialize(t)) t = instanceof_tfunc(t)[1] t === Any && return v - return tmeet(v, t) + return tmeet(𝕃, v, t) end add_tfunc(typeassert, 2, 2, typeassert_tfunc, 4) @@ -731,7 +764,6 @@ function isa_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(v), @nospec # TODO: handle non-leaftype(t) by testing against lower and upper bounds return Bool end -isa_tfunc(@nospecialize(v), @nospecialize(t)) = isa_tfunc(fallback_lattice, v, t) add_tfunc(isa, 2, 2, isa_tfunc, 1) function isa_nothrow(@specialize(𝕃::AbstractLattice), @nospecialize(obj), @nospecialize(typ)) @@ -739,7 +771,7 @@ function isa_nothrow(@specialize(𝕃::AbstractLattice), @nospecialize(obj), @no return typ ⊑ Type end -function subtype_tfunc(@nospecialize(a), @nospecialize(b)) +function subtype_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(a), @nospecialize(b)) a, isexact_a = instanceof_tfunc(a) b, isexact_b = instanceof_tfunc(b) if !has_free_typevars(a) && !has_free_typevars(b) @@ -793,7 +825,6 @@ function fieldcount_noerror(@nospecialize t) return isdefined(t, :types) ? length(t.types) : length(t.name.names) end - function try_compute_fieldidx(typ::DataType, @nospecialize(field)) if isa(field, Symbol) field = fieldindex(typ, field, false) @@ -920,17 +951,14 @@ function getfield_nothrow(@nospecialize(s00), @nospecialize(name), boundscheck:: return false end -function getfield_tfunc(@specialize(lattice::AbstractLattice), @nospecialize(s00), +function getfield_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(s00), @nospecialize(name), @nospecialize(boundscheck_or_order)) t = isvarargtype(boundscheck_or_order) ? unwrapva(boundscheck_or_order) : widenconst(boundscheck_or_order) hasintersect(t, Symbol) || hasintersect(t, Bool) || return Bottom - return getfield_tfunc(lattice, s00, name) -end -function getfield_tfunc(@nospecialize(s00), name, boundscheck_or_order) - return getfield_tfunc(fallback_lattice, s00, name, boundscheck_or_order) + return getfield_tfunc(𝕃, s00, name) end -function getfield_tfunc(@specialize(lattice::AbstractLattice), @nospecialize(s00), +function getfield_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(s00), @nospecialize(name), @nospecialize(order), @nospecialize(boundscheck)) hasintersect(widenconst(order), Symbol) || return Bottom if isvarargtype(boundscheck) @@ -939,13 +967,11 @@ function getfield_tfunc(@specialize(lattice::AbstractLattice), @nospecialize(s00 else hasintersect(widenconst(boundscheck), Bool) || return Bottom end - return getfield_tfunc(lattice, s00, name) + return getfield_tfunc(𝕃, s00, name) end -function getfield_tfunc(@nospecialize(s00), @nospecialize(name), @nospecialize(order), @nospecialize(boundscheck)) - return getfield_tfunc(fallback_lattice, s00, name, order, boundscheck) +function getfield_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(s00), @nospecialize(name)) + return _getfield_tfunc(𝕃, s00, name, false) end -getfield_tfunc(@nospecialize(s00), @nospecialize(name)) = _getfield_tfunc(fallback_lattice, s00, name, false) -getfield_tfunc(@specialize(lattice::AbstractLattice), @nospecialize(s00), @nospecialize(name)) = _getfield_tfunc(lattice, s00, name, false) function _getfield_fieldindex(s::DataType, name::Const) nv = name.val @@ -1186,15 +1212,15 @@ function is_undefref_fieldtype(@nospecialize ftyp) return !has_free_typevars(ftyp) && !allocatedinline(ftyp) end -function setfield!_tfunc(o, f, v, order) - @nospecialize +function setfield!_tfunc(@specialize(𝕃::AbstractLattice), o, f, v, order) + @nospecialize o f v order if !isvarargtype(order) hasintersect(widenconst(order), Symbol) || return Bottom end - return setfield!_tfunc(o, f, v) + return setfield!_tfunc(𝕃, o, f, v) end -function setfield!_tfunc(o, f, v) - @nospecialize +function setfield!_tfunc(@specialize(𝕃::AbstractLattice), o, f, v) + @nospecialize o f v mutability_errorcheck(o) || return Bottom ft = _getfield_tfunc(fallback_lattice, o, f, true) ft === Bottom && return Bottom @@ -1244,15 +1270,24 @@ function setfield!_nothrow(@specialize(𝕃::AbstractLattice), s00, name, v) return false end -swapfield!_tfunc(o, f, v, order) = (@nospecialize; getfield_tfunc(o, f)) -swapfield!_tfunc(o, f, v) = (@nospecialize; getfield_tfunc(o, f)) -modifyfield!_tfunc(o, f, op, v, order) = (@nospecialize; modifyfield!_tfunc(o, f, op, v)) -function modifyfield!_tfunc(o, f, op, v) - @nospecialize - T = _fieldtype_tfunc(o, isconcretetype(o), f) +function swapfield!_tfunc(@specialize(𝕃::AbstractLattice), o, f, v, order) + @nospecialize o f v order + return getfield_tfunc(𝕃, o, f) +end +function swapfield!_tfunc(@specialize(𝕃::AbstractLattice), o, f, v) + @nospecialize o f v + return getfield_tfunc(𝕃, o, f) +end +function modifyfield!_tfunc(@specialize(𝕃::AbstractLattice), o, f, op, v, order) + @nospecialize o f op v order + return modifyfield!_tfunc(𝕃, o, f, op, v) +end +function modifyfield!_tfunc(@specialize(𝕃::AbstractLattice), o, f, op, v) + @nospecialize o f op v + T = _fieldtype_tfunc(𝕃, o, f, isconcretetype(o)) T === Bottom && return Bottom PT = Const(Pair) - return instanceof_tfunc(apply_type_tfunc(PT, T, T))[1] + return instanceof_tfunc(apply_type_tfunc(𝕃, PT, T, T))[1] end function abstract_modifyfield!(interp::AbstractInterpreter, argtypes::Vector{Any}, si::StmtInfo, sv::InferenceState) nargs = length(argtypes) @@ -1262,35 +1297,42 @@ function abstract_modifyfield!(interp::AbstractInterpreter, argtypes::Vector{Any else 5 <= nargs <= 6 || return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) end + 𝕃ᵢ = typeinf_lattice(interp) o = unwrapva(argtypes[2]) f = unwrapva(argtypes[3]) - RT = modifyfield!_tfunc(o, f, Any, Any) + RT = modifyfield!_tfunc(𝕃ᵢ, o, f, Any, Any) info = NoCallInfo() if nargs >= 5 && RT !== Bottom # we may be able to refine this to a PartialStruct by analyzing `op(o.f, v)::T` # as well as compute the info for the method matches op = unwrapva(argtypes[4]) v = unwrapva(argtypes[5]) - TF = getfield_tfunc(typeinf_lattice(interp), o, f) + TF = getfield_tfunc(𝕃ᵢ, o, f) callinfo = abstract_call(interp, ArgInfo(nothing, Any[op, TF, v]), StmtInfo(true), sv, #=max_methods=# 1) TF2 = tmeet(callinfo.rt, widenconst(TF)) if TF2 === Bottom RT = Bottom - elseif isconcretetype(RT) && has_nontrivial_const_info(typeinf_lattice(interp), TF2) # isconcrete condition required to form a PartialStruct + elseif isconcretetype(RT) && has_nontrivial_const_info(𝕃ᵢ, TF2) # isconcrete condition required to form a PartialStruct RT = PartialStruct(RT, Any[TF, TF2]) end info = ModifyFieldInfo(callinfo.info) end return CallMeta(RT, Effects(), info) end -replacefield!_tfunc(o, f, x, v, success_order, failure_order) = (@nospecialize; replacefield!_tfunc(o, f, x, v)) -replacefield!_tfunc(o, f, x, v, success_order) = (@nospecialize; replacefield!_tfunc(o, f, x, v)) -function replacefield!_tfunc(o, f, x, v) - @nospecialize - T = _fieldtype_tfunc(o, isconcretetype(o), f) +function replacefield!_tfunc(@specialize(𝕃::AbstractLattice), o, f, x, v, success_order, failure_order) + @nospecialize o f x v success_order failure_order + return replacefield!_tfunc(𝕃, o, f, x, v) +end +function replacefield!_tfunc(@specialize(𝕃::AbstractLattice), o, f, x, v, success_order) + @nospecialize o f x v success_order + return replacefield!_tfunc(𝕃, o, f, x, v) +end +function replacefield!_tfunc(@specialize(𝕃::AbstractLattice), o, f, x, v) + @nospecialize o f x v + T = _fieldtype_tfunc(𝕃, o, f, isconcretetype(o)) T === Bottom && return Bottom PT = Const(ccall(:jl_apply_cmpswap_type, Any, (Any,), T) where T) - return instanceof_tfunc(apply_type_tfunc(PT, T))[1] + return instanceof_tfunc(apply_type_tfunc(𝕃, PT, T))[1] end # we could use tuple_tfunc instead of widenconst, but `o` is mutable, so that is unlikely to be beneficial @@ -1360,8 +1402,12 @@ function _fieldtype_nothrow(@nospecialize(s), exact::Bool, name::Const) return true end -fieldtype_tfunc(s0, name, boundscheck) = (@nospecialize; fieldtype_tfunc(s0, name)) -function fieldtype_tfunc(@nospecialize(s0), @nospecialize(name)) +function fieldtype_tfunc(@specialize(𝕃::AbstractLattice), s0, name, boundscheck) + @nospecialize s0 name boundscheck + return fieldtype_tfunc(𝕃, s0, name) +end +function fieldtype_tfunc(@specialize(𝕃::AbstractLattice), s0, name) + @nospecialize s0 name s0 = widenmustalias(s0) if s0 === Bottom return Bottom @@ -1381,21 +1427,22 @@ function fieldtype_tfunc(@nospecialize(s0), @nospecialize(name)) su = unwrap_unionall(s0) if isa(su, Union) - return tmerge(fieldtype_tfunc(rewrap_unionall(su.a, s0), name), - fieldtype_tfunc(rewrap_unionall(su.b, s0), name)) + return tmerge(fieldtype_tfunc(𝕃, rewrap_unionall(su.a, s0), name), + fieldtype_tfunc(𝕃, rewrap_unionall(su.b, s0), name)) end s, exact = instanceof_tfunc(s0) s === Bottom && return Bottom - return _fieldtype_tfunc(s, exact, name) + return _fieldtype_tfunc(𝕃, s, name, exact) end -function _fieldtype_tfunc(@nospecialize(s), exact::Bool, @nospecialize(name)) +function _fieldtype_tfunc(@specialize(𝕃::AbstractLattice), s, name, exact::Bool) + @nospecialize s0 name exact = exact && !has_free_typevars(s) u = unwrap_unionall(s) if isa(u, Union) - ta0 = _fieldtype_tfunc(rewrap_unionall(u.a, s), exact, name) - tb0 = _fieldtype_tfunc(rewrap_unionall(u.b, s), exact, name) + ta0 = _fieldtype_tfunc(𝕃, rewrap_unionall(u.a, s), name, exact) + tb0 = _fieldtype_tfunc(𝕃, rewrap_unionall(u.b, s), name, exact) ta0 ⊑ tb0 && return tb0 tb0 ⊑ ta0 && return ta0 ta, exacta, _, istypea = instanceof_tfunc(ta0) @@ -1559,7 +1606,8 @@ const _tvarnames = Symbol[:_A, :_B, :_C, :_D, :_E, :_F, :_G, :_H, :_I, :_J, :_K, :_N, :_O, :_P, :_Q, :_R, :_S, :_T, :_U, :_V, :_W, :_X, :_Y, :_Z] # TODO: handle e.g. apply_type(T, R::Union{Type{Int32},Type{Float64}}) -function apply_type_tfunc(@nospecialize(headtypetype), @nospecialize args...) +function apply_type_tfunc(@specialize(𝕃::AbstractLattice), headtypetype, args...) + @nospecialize headtypetype args headtypetype = widenslotwrapper(headtypetype) if isa(headtypetype, Const) headtype = headtypetype.val @@ -1724,7 +1772,7 @@ add_tfunc(apply_type, 1, INT_INF, apply_type_tfunc, 10) # convert the dispatch tuple type argtype to the real (concrete) type of # the tuple of those values -function tuple_tfunc(@specialize(lattice::AbstractLattice), argtypes::Vector{Any}) +function tuple_tfunc(@specialize(𝕃::AbstractLattice), argtypes::Vector{Any}) argtypes = anymap(widenslotwrapper, argtypes) all_are_const = true for i in 1:length(argtypes) @@ -1740,7 +1788,7 @@ function tuple_tfunc(@specialize(lattice::AbstractLattice), argtypes::Vector{Any anyinfo = false for i in 1:length(argtypes) x = argtypes[i] - if has_nontrivial_const_info(lattice, x) + if has_nontrivial_const_info(𝕃, x) anyinfo = true else if !isvarargtype(x) @@ -1778,12 +1826,13 @@ function tuple_tfunc(@specialize(lattice::AbstractLattice), argtypes::Vector{Any issingletontype(typ) && return Const(typ.instance) return anyinfo ? PartialStruct(typ, argtypes) : typ end -tuple_tfunc(argtypes::Vector{Any}) = tuple_tfunc(fallback_lattice, argtypes) -arrayref_tfunc(@nospecialize(boundscheck), @nospecialize(ary), @nospecialize idxs...) = - _arrayref_tfunc(boundscheck, ary, idxs) -function _arrayref_tfunc(@nospecialize(boundscheck), @nospecialize(ary), - @nospecialize idxs::Tuple) +function arrayref_tfunc(@specialize(𝕃::AbstractLattice), boundscheck, ary, idxs...) + @nospecialize boundscheck ary idxs + return _arrayref_tfunc(𝕃, boundscheck, ary, idxs) +end +function _arrayref_tfunc(@specialize(𝕃::AbstractLattice), boundscheck, ary, idxs::Tuple) + @nospecialize boundscheck ary idxs isempty(idxs) && return Bottom array_builtin_common_errorcheck(boundscheck, ary, idxs) || return Bottom return array_elmtype(ary) @@ -1791,9 +1840,9 @@ end add_tfunc(arrayref, 3, INT_INF, arrayref_tfunc, 20) add_tfunc(const_arrayref, 3, INT_INF, arrayref_tfunc, 20) -function arrayset_tfunc(@nospecialize(boundscheck), @nospecialize(ary), @nospecialize(item), - @nospecialize idxs...) - hasintersect(widenconst(item), _arrayref_tfunc(boundscheck, ary, idxs)) || return Bottom +function arrayset_tfunc(@specialize(𝕃::AbstractLattice), boundscheck, ary, item, idxs...) + @nospecialize boundscheck ary item idxs + hasintersect(widenconst(item), _arrayref_tfunc(𝕃, boundscheck, ary, idxs)) || return Bottom return ary end add_tfunc(arrayset, 4, INT_INF, arrayset_tfunc, 20) @@ -1826,9 +1875,8 @@ function array_elmtype(@nospecialize ary) return Any end -function _opaque_closure_tfunc(@nospecialize(arg), @nospecialize(lb), @nospecialize(ub), +function _opaque_closure_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(arg), @nospecialize(lb), @nospecialize(ub), @nospecialize(source), env::Vector{Any}, linfo::MethodInstance) - argt, argt_exact = instanceof_tfunc(arg) lbt, lb_exact = instanceof_tfunc(lb) if !lb_exact @@ -1842,7 +1890,7 @@ function _opaque_closure_tfunc(@nospecialize(arg), @nospecialize(lb), @nospecial (isa(source, Const) && isa(source.val, Method)) || return t - return PartialOpaque(t, tuple_tfunc(env), linfo, source.val) + return PartialOpaque(t, tuple_tfunc(𝕃, env), linfo, source.val) end # whether getindex for the elements can potentially throw UndefRef @@ -2229,14 +2277,7 @@ function builtin_tfunction(interp::AbstractInterpreter, @nospecialize(f), argtyp # wrong # of args return Bottom end - if f === getfield - return getfield_tfunc(𝕃ᵢ, argtypes...) - elseif f === (===) - return egal_tfunc(𝕃ᵢ, argtypes...) - elseif f === isa - return isa_tfunc(𝕃ᵢ, argtypes...) - end - return tf[3](argtypes...) + return tf[3](𝕃ᵢ, argtypes...) end # Query whether the given intrinsic is nothrow @@ -2441,7 +2482,8 @@ function getglobal_nothrow(@nospecialize(M), @nospecialize(s)) end return false end -function getglobal_tfunc(@nospecialize(M), @nospecialize(s), @nospecialize(_=Symbol)) +function getglobal_tfunc(@specialize(𝕃::AbstractLattice), M, s, order=Symbol) + @nospecialize M s order if M isa Const && s isa Const M, s = M.val, s.val if M isa Module && s isa Symbol @@ -2453,8 +2495,8 @@ function getglobal_tfunc(@nospecialize(M), @nospecialize(s), @nospecialize(_=Sym end return Any end -function setglobal!_tfunc(@nospecialize(M), @nospecialize(s), @nospecialize(v), - @nospecialize(_=Symbol)) +function setglobal!_tfunc(@specialize(𝕃::AbstractLattice), M, s, v, order=Symbol) + @nospecialize M s v order if !(hasintersect(widenconst(M), Module) && hasintersect(widenconst(s), Symbol)) return Bottom end @@ -2493,7 +2535,8 @@ function get_binding_type_effect_free(@nospecialize(M), @nospecialize(s)) end return false end -function get_binding_type_tfunc(@nospecialize(M), @nospecialize(s)) +function get_binding_type_tfunc(@specialize(𝕃::AbstractLattice), M, s) + @nospecialize M s if get_binding_type_effect_free(M, s) return Const(Core.get_binding_type((M::Const).val, (s::Const).val)) end diff --git a/base/compiler/typelimits.jl b/base/compiler/typelimits.jl index 74fe35f244060a..845e46ab01ed7b 100644 --- a/base/compiler/typelimits.jl +++ b/base/compiler/typelimits.jl @@ -304,7 +304,7 @@ end # A simplified type_more_complex query over the extended lattice # (assumes typeb ⊑ typea) -function issimplertype(lattice::AbstractLattice, @nospecialize(typea), @nospecialize(typeb)) +function issimplertype(𝕃::AbstractLattice, @nospecialize(typea), @nospecialize(typeb)) typea = ignorelimited(typea) typeb = ignorelimited(typeb) typea isa MaybeUndef && (typea = typea.typ) # n.b. does not appear in inference @@ -315,14 +315,14 @@ function issimplertype(lattice::AbstractLattice, @nospecialize(typea), @nospecia for i = 1:length(typea.fields) ai = unwrapva(typea.fields[i]) bi = fieldtype(aty, i) - is_lattice_equal(lattice, ai, bi) && continue + is_lattice_equal(𝕃, ai, bi) && continue tni = _typename(widenconst(ai)) if tni isa Const bi = (tni.val::Core.TypeName).wrapper - is_lattice_equal(lattice, ai, bi) && continue + is_lattice_equal(𝕃, ai, bi) && continue end - bi = getfield_tfunc(lattice, typeb, Const(i)) - is_lattice_equal(lattice, ai, bi) && continue + bi = getfield_tfunc(𝕃, typeb, Const(i)) + is_lattice_equal(𝕃, ai, bi) && continue # It is not enough for ai to be simpler than bi: it must exactly equal # (for this, an invariant struct field, by contrast to # type_more_complex above which handles covariant tuples). @@ -335,24 +335,24 @@ function issimplertype(lattice::AbstractLattice, @nospecialize(typea), @nospecia typeb isa Const && return true typeb isa Conditional || return false is_same_conditionals(typea, typeb) || return false - issimplertype(lattice, typea.thentype, typeb.thentype) || return false - issimplertype(lattice, typea.elsetype, typeb.elsetype) || return false + issimplertype(𝕃, typea.thentype, typeb.thentype) || return false + issimplertype(𝕃, typea.elsetype, typeb.elsetype) || return false elseif typea isa InterConditional # ibid typeb isa Const && return true typeb isa InterConditional || return false is_same_conditionals(typea, typeb) || return false - issimplertype(lattice, typea.thentype, typeb.thentype) || return false - issimplertype(lattice, typea.elsetype, typeb.elsetype) || return false + issimplertype(𝕃, typea.thentype, typeb.thentype) || return false + issimplertype(𝕃, typea.elsetype, typeb.elsetype) || return false elseif typea isa MustAlias typeb isa MustAlias || return false issubalias(typeb, typea) || return false - issimplertype(lattice, typea.vartyp, typeb.vartyp) || return false - issimplertype(lattice, typea.fldtyp, typeb.fldtyp) || return false + issimplertype(𝕃, typea.vartyp, typeb.vartyp) || return false + issimplertype(𝕃, typea.fldtyp, typeb.fldtyp) || return false elseif typea isa InterMustAlias typeb isa InterMustAlias || return false issubalias(typeb, typea) || return false - issimplertype(lattice, typea.vartyp, typeb.vartyp) || return false - issimplertype(lattice, typea.fldtyp, typeb.fldtyp) || return false + issimplertype(𝕃, typea.vartyp, typeb.vartyp) || return false + issimplertype(𝕃, typea.fldtyp, typeb.fldtyp) || return false elseif typea isa PartialOpaque # TODO end @@ -373,7 +373,6 @@ end return nothing end - function tmerge(lattice::OptimizerLattice, @nospecialize(typea), @nospecialize(typeb)) r = tmerge_fast_path(lattice, typea, typeb) r !== nothing && return r @@ -498,8 +497,8 @@ function tmerge(lattice::PartialsLattice, @nospecialize(typea), @nospecialize(ty bty = widenconst(typeb) if aty === bty # must have egal here, since we do not create PartialStruct for non-concrete types - typea_nfields = nfields_tfunc(typea) - typeb_nfields = nfields_tfunc(typeb) + typea_nfields = nfields_tfunc(lattice, typea) + typeb_nfields = nfields_tfunc(lattice, typeb) isa(typea_nfields, Const) || return aty isa(typeb_nfields, Const) || return aty type_nfields = typea_nfields.val::Int diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index a6a8684f67595d..086f64aaed2713 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -273,7 +273,7 @@ barTuple2() = fooTuple{tuple(:y)}() @test Base.return_types(barTuple1,Tuple{})[1] == Base.return_types(barTuple2,Tuple{})[1] == fooTuple{(:y,)} # issue #6050 -@test Core.Compiler.getfield_tfunc( +@test Core.Compiler.getfield_tfunc(Core.Compiler.fallback_lattice, Dict{Int64,Tuple{UnitRange{Int64},UnitRange{Int64}}}, Core.Compiler.Const(:vals)) == Array{Tuple{UnitRange{Int64},UnitRange{Int64}},1} @@ -717,7 +717,8 @@ mutable struct HasAbstractlyTypedField end f_infer_abstract_fieldtype() = fieldtype(HasAbstractlyTypedField, :x) @test Base.return_types(f_infer_abstract_fieldtype, ()) == Any[Type{Union{Int,String}}] -let fieldtype_tfunc = Core.Compiler.fieldtype_tfunc, +let fieldtype_tfunc(@nospecialize args...) = + Core.Compiler.fieldtype_tfunc(Core.Compiler.fallback_lattice, args...), fieldtype_nothrow(@nospecialize(s0), @nospecialize(name)) = Core.Compiler.fieldtype_nothrow( Core.Compiler.OptimizerLattice(), s0, name) @test fieldtype_tfunc(Union{}, :x) == Union{} @@ -1128,12 +1129,6 @@ let f(x) = isdefined(x, :NonExistentField) ? 1 : "" @test Base.return_types(f, (ComplexF32,)) == Any[String] @test Union{Int,String} <: Base.return_types(f, (AbstractArray,))[1] end -import Core.Compiler: isdefined_tfunc -@test isdefined_tfunc(ComplexF32, Const(())) === Union{} -@test isdefined_tfunc(ComplexF32, Const(1)) === Const(true) -@test isdefined_tfunc(ComplexF32, Const(2)) === Const(true) -@test isdefined_tfunc(ComplexF32, Const(3)) === Const(false) -@test isdefined_tfunc(ComplexF32, Const(0)) === Const(false) mutable struct SometimesDefined x function SometimesDefined() @@ -1144,36 +1139,61 @@ mutable struct SometimesDefined return v end end -@test isdefined_tfunc(SometimesDefined, Const(:x)) == Bool -@test isdefined_tfunc(SometimesDefined, Const(:y)) === Const(false) -@test isdefined_tfunc(Const(Base), Const(:length)) === Const(true) -@test isdefined_tfunc(Const(Base), Symbol) == Bool -@test isdefined_tfunc(Const(Base), Const(:NotCurrentlyDefinedButWhoKnows)) == Bool -@test isdefined_tfunc(Core.SimpleVector, Const(1)) === Const(false) -@test Const(false) ⊑ isdefined_tfunc(Const(:x), Symbol) -@test Const(false) ⊑ isdefined_tfunc(Const(:x), Const(:y)) -@test isdefined_tfunc(Vector{Int}, Const(1)) == Const(false) -@test isdefined_tfunc(Vector{Any}, Const(1)) == Const(false) -@test isdefined_tfunc(Module, Int) === Union{} -@test isdefined_tfunc(Tuple{Any,Vararg{Any}}, Const(0)) === Const(false) -@test isdefined_tfunc(Tuple{Any,Vararg{Any}}, Const(1)) === Const(true) -@test isdefined_tfunc(Tuple{Any,Vararg{Any}}, Const(2)) === Bool -@test isdefined_tfunc(Tuple{Any,Vararg{Any}}, Const(3)) === Bool -@testset "isdefined check for `NamedTuple`s" begin - # concrete `NamedTuple`s - @test isdefined_tfunc(NamedTuple{(:x,:y),Tuple{Int,Int}}, Const(:x)) === Const(true) - @test isdefined_tfunc(NamedTuple{(:x,:y),Tuple{Int,Int}}, Const(:y)) === Const(true) - @test isdefined_tfunc(NamedTuple{(:x,:y),Tuple{Int,Int}}, Const(:z)) === Const(false) - # non-concrete `NamedTuple`s - @test isdefined_tfunc(NamedTuple{(:x,:y),<:Tuple{Int,Any}}, Const(:x)) === Const(true) - @test isdefined_tfunc(NamedTuple{(:x,:y),<:Tuple{Int,Any}}, Const(:y)) === Const(true) - @test isdefined_tfunc(NamedTuple{(:x,:y),<:Tuple{Int,Any}}, Const(:z)) === Const(false) -end struct UnionIsdefinedA; x; end struct UnionIsdefinedB; x; end -@test isdefined_tfunc(Union{UnionIsdefinedA,UnionIsdefinedB}, Const(:x)) === Const(true) -@test isdefined_tfunc(Union{UnionIsdefinedA,UnionIsdefinedB}, Const(:y)) === Const(false) -@test isdefined_tfunc(Union{UnionIsdefinedA,Nothing}, Const(:x)) === Bool +let isdefined_tfunc(@nospecialize xs...) = + Core.Compiler.isdefined_tfunc(Core.Compiler.fallback_lattice, xs...) + @test isdefined_tfunc(typeof(NamedTuple()), Const(0)) === Const(false) + @test isdefined_tfunc(typeof(NamedTuple()), Const(1)) === Const(false) + @test isdefined_tfunc(typeof((a=1,b=2)), Const(:a)) === Const(true) + @test isdefined_tfunc(typeof((a=1,b=2)), Const(:b)) === Const(true) + @test isdefined_tfunc(typeof((a=1,b=2)), Const(:c)) === Const(false) + @test isdefined_tfunc(typeof((a=1,b=2)), Const(0)) === Const(false) + @test isdefined_tfunc(typeof((a=1,b=2)), Const(1)) === Const(true) + @test isdefined_tfunc(typeof((a=1,b=2)), Const(2)) === Const(true) + @test isdefined_tfunc(typeof((a=1,b=2)), Const(3)) === Const(false) + @test isdefined_tfunc(NamedTuple, Const(1)) == Bool + @test isdefined_tfunc(NamedTuple, Symbol) == Bool + @test Const(false) ⊑ isdefined_tfunc(NamedTuple{(:x,:y)}, Const(:z)) + @test Const(true) ⊑ isdefined_tfunc(NamedTuple{(:x,:y)}, Const(1)) + @test Const(false) ⊑ isdefined_tfunc(NamedTuple{(:x,:y)}, Const(3)) + @test Const(true) ⊑ isdefined_tfunc(NamedTuple{(:x,:y)}, Const(:y)) + + @test isdefined_tfunc(ComplexF32, Const(())) === Union{} + @test isdefined_tfunc(ComplexF32, Const(1)) === Const(true) + @test isdefined_tfunc(ComplexF32, Const(2)) === Const(true) + @test isdefined_tfunc(ComplexF32, Const(3)) === Const(false) + @test isdefined_tfunc(ComplexF32, Const(0)) === Const(false) + @test isdefined_tfunc(SometimesDefined, Const(:x)) == Bool + @test isdefined_tfunc(SometimesDefined, Const(:y)) === Const(false) + @test isdefined_tfunc(Const(Base), Const(:length)) === Const(true) + @test isdefined_tfunc(Const(Base), Symbol) == Bool + @test isdefined_tfunc(Const(Base), Const(:NotCurrentlyDefinedButWhoKnows)) == Bool + @test isdefined_tfunc(Core.SimpleVector, Const(1)) === Const(false) + @test Const(false) ⊑ isdefined_tfunc(Const(:x), Symbol) + @test Const(false) ⊑ isdefined_tfunc(Const(:x), Const(:y)) + @test isdefined_tfunc(Vector{Int}, Const(1)) == Const(false) + @test isdefined_tfunc(Vector{Any}, Const(1)) == Const(false) + @test isdefined_tfunc(Module, Int) === Union{} + @test isdefined_tfunc(Tuple{Any,Vararg{Any}}, Const(0)) === Const(false) + @test isdefined_tfunc(Tuple{Any,Vararg{Any}}, Const(1)) === Const(true) + @test isdefined_tfunc(Tuple{Any,Vararg{Any}}, Const(2)) === Bool + @test isdefined_tfunc(Tuple{Any,Vararg{Any}}, Const(3)) === Bool + @testset "isdefined check for `NamedTuple`s" begin + # concrete `NamedTuple`s + @test isdefined_tfunc(NamedTuple{(:x,:y),Tuple{Int,Int}}, Const(:x)) === Const(true) + @test isdefined_tfunc(NamedTuple{(:x,:y),Tuple{Int,Int}}, Const(:y)) === Const(true) + @test isdefined_tfunc(NamedTuple{(:x,:y),Tuple{Int,Int}}, Const(:z)) === Const(false) + # non-concrete `NamedTuple`s + @test isdefined_tfunc(NamedTuple{(:x,:y),<:Tuple{Int,Any}}, Const(:x)) === Const(true) + @test isdefined_tfunc(NamedTuple{(:x,:y),<:Tuple{Int,Any}}, Const(:y)) === Const(true) + @test isdefined_tfunc(NamedTuple{(:x,:y),<:Tuple{Int,Any}}, Const(:z)) === Const(false) + end + @test isdefined_tfunc(Union{UnionIsdefinedA,UnionIsdefinedB}, Const(:x)) === Const(true) + @test isdefined_tfunc(Union{UnionIsdefinedA,UnionIsdefinedB}, Const(:y)) === Const(false) + @test isdefined_tfunc(Union{UnionIsdefinedA,Nothing}, Const(:x)) === Bool +end + # https://github.com/aviatesk/JET.jl/issues/379 fJET379(x::Union{Complex{T}, T}) where T = isdefined(x, :im) @test only(Base.return_types(fJET379)) === Bool @@ -1223,22 +1243,6 @@ copy_dims_pair(out, dim::Colon, tail...) = copy_dims_pair(out => dim, tail...) @test Base.return_types(copy_dims_pair, (Tuple{}, Vararg{Union{Int,Colon}})) == Any[Tuple{}, Tuple{}, Tuple{}] @test all(m -> 5 < count_specializations(m) < 15, methods(copy_dims_pair)) # currently about 7 -@test isdefined_tfunc(typeof(NamedTuple()), Const(0)) === Const(false) -@test isdefined_tfunc(typeof(NamedTuple()), Const(1)) === Const(false) -@test isdefined_tfunc(typeof((a=1,b=2)), Const(:a)) === Const(true) -@test isdefined_tfunc(typeof((a=1,b=2)), Const(:b)) === Const(true) -@test isdefined_tfunc(typeof((a=1,b=2)), Const(:c)) === Const(false) -@test isdefined_tfunc(typeof((a=1,b=2)), Const(0)) === Const(false) -@test isdefined_tfunc(typeof((a=1,b=2)), Const(1)) === Const(true) -@test isdefined_tfunc(typeof((a=1,b=2)), Const(2)) === Const(true) -@test isdefined_tfunc(typeof((a=1,b=2)), Const(3)) === Const(false) -@test isdefined_tfunc(NamedTuple, Const(1)) == Bool -@test isdefined_tfunc(NamedTuple, Symbol) == Bool -@test Const(false) ⊑ isdefined_tfunc(NamedTuple{(:x,:y)}, Const(:z)) -@test Const(true) ⊑ isdefined_tfunc(NamedTuple{(:x,:y)}, Const(1)) -@test Const(false) ⊑ isdefined_tfunc(NamedTuple{(:x,:y)}, Const(3)) -@test Const(true) ⊑ isdefined_tfunc(NamedTuple{(:x,:y)}, Const(:y)) - # splatting an ::Any should still allow inference to use types of parameters preceding it f22364(::Int, ::Any...) = 0 f22364(::String, ::Any...) = 0.0 @@ -1347,7 +1351,8 @@ isdefined_f3(x) = isdefined(x, 3) @test @inferred(isdefined_f3(())) == false @test find_call(first(code_typed(isdefined_f3, Tuple{Tuple{Vararg{Int}}})[1]), isdefined, 3) -let isa_tfunc = Core.Compiler.isa_tfunc +let isa_tfunc(@nospecialize xs...) = + Core.Compiler.isa_tfunc(Core.Compiler.fallback_lattice, xs...) @test isa_tfunc(Array, Const(AbstractArray)) === Const(true) @test isa_tfunc(Array, Type{AbstractArray}) === Const(true) @test isa_tfunc(Array, Type{AbstractArray{Int}}) == Bool @@ -1386,7 +1391,8 @@ let isa_tfunc = Core.Compiler.isa_tfunc @test isa_tfunc(Union{Int64, Float64}, Type{AbstractArray}) === Const(false) end -let subtype_tfunc = Core.Compiler.subtype_tfunc +let subtype_tfunc(@nospecialize xs...) = + Core.Compiler.subtype_tfunc(Core.Compiler.fallback_lattice, xs...) @test subtype_tfunc(Type{<:Array}, Const(AbstractArray)) === Const(true) @test subtype_tfunc(Type{<:Array}, Type{AbstractArray}) === Const(true) @test subtype_tfunc(Type{<:Array}, Type{AbstractArray{Int}}) == Bool @@ -1508,89 +1514,99 @@ egal_conditional_lattice3(x, y) = x === y + y ? "" : 1 @test Base.return_types(egal_conditional_lattice3, (Int64, Int64)) == Any[Union{Int, String}] @test Base.return_types(egal_conditional_lattice3, (Int32, Int64)) == Any[Int] -using Core.Compiler: PartialStruct, nfields_tfunc, sizeof_tfunc, sizeof_nothrow -@test sizeof_tfunc(Const(Ptr)) === sizeof_tfunc(Union{Ptr, Int, Type{Ptr{Int8}}, Type{Int}}) === Const(Sys.WORD_SIZE ÷ 8) -@test sizeof_tfunc(Type{Ptr}) === Const(sizeof(Ptr)) -@test sizeof_nothrow(Union{Ptr, Int, Type{Ptr{Int8}}, Type{Int}}) -@test sizeof_nothrow(Const(Ptr)) -@test sizeof_nothrow(Type{Ptr}) -@test sizeof_nothrow(Type{Union{Ptr{Int}, Int}}) -@test !sizeof_nothrow(Const(Tuple)) -@test !sizeof_nothrow(Type{Vector{Int}}) -@test !sizeof_nothrow(Type{Union{Int, String}}) -@test sizeof_nothrow(String) -@test !sizeof_nothrow(Type{String}) -@test sizeof_tfunc(Type{Union{Int64, Int32}}) == Const(Core.sizeof(Union{Int64, Int32})) -let PT = PartialStruct(Tuple{Int64,UInt64}, Any[Const(10), UInt64]) - @test sizeof_tfunc(PT) === Const(16) - @test nfields_tfunc(PT) === Const(2) - @test sizeof_nothrow(PT) -end -@test nfields_tfunc(Type) === Int -@test nfields_tfunc(Number) === Int -@test nfields_tfunc(Int) === Const(0) -@test nfields_tfunc(Complex) === Const(2) -@test nfields_tfunc(Type{Type{Int}}) === Const(nfields(DataType)) -@test nfields_tfunc(UnionAll) === Const(2) -@test nfields_tfunc(DataType) === Const(nfields(DataType)) -@test nfields_tfunc(Type{Int}) === Const(nfields(DataType)) -@test nfields_tfunc(Type{Integer}) === Const(nfields(DataType)) -@test nfields_tfunc(Type{Complex}) === Int -@test nfields_tfunc(typeof(Union{})) === Const(0) -@test nfields_tfunc(Type{Union{}}) === Const(0) -@test nfields_tfunc(Tuple{Int, Vararg{Int}}) === Int -@test nfields_tfunc(Tuple{Int, Integer}) === Const(2) -@test nfields_tfunc(Union{Tuple{Int, Float64}, Tuple{Int, Int}}) === Const(2) -@test nfields_tfunc(@NamedTuple{a::Int,b::Integer}) === Const(2) -@test nfields_tfunc(NamedTuple{(:a,:b),T} where T<:Tuple{Int,Integer}) === Const(2) -@test nfields_tfunc(NamedTuple{(:a,:b)}) === Const(2) -@test nfields_tfunc(NamedTuple{names,Tuple{Any,Any}} where names) === Const(2) -@test nfields_tfunc(Union{NamedTuple{(:a,:b)},NamedTuple{(:c,:d)}}) === Const(2) - -using Core.Compiler: typeof_tfunc -@test typeof_tfunc(Tuple{Vararg{Int}}) == Type{Tuple{Vararg{Int,N}}} where N -@test typeof_tfunc(Tuple{Any}) == Type{<:Tuple{Any}} -@test typeof_tfunc(Type{Array}) === DataType -@test typeof_tfunc(Type{<:Array}) === DataType -@test typeof_tfunc(Array{Int}) == Type{Array{Int,N}} where N -@test typeof_tfunc(AbstractArray{Int}) == Type{<:AbstractArray{Int,N}} where N -@test typeof_tfunc(Union{<:T, <:Real} where T<:Complex) == Union{Type{Complex{T}} where T<:Real, Type{<:Real}} +let nfields_tfunc(@nospecialize xs...) = + Core.Compiler.nfields_tfunc(Core.Compiler.fallback_lattice, xs...) + sizeof_tfunc(@nospecialize xs...) = + Core.Compiler.sizeof_tfunc(Core.Compiler.fallback_lattice, xs...) + sizeof_nothrow(@nospecialize xs...) = + Core.Compiler.sizeof_nothrow(xs...) + @test sizeof_tfunc(Const(Ptr)) === sizeof_tfunc(Union{Ptr, Int, Type{Ptr{Int8}}, Type{Int}}) === Const(Sys.WORD_SIZE ÷ 8) + @test sizeof_tfunc(Type{Ptr}) === Const(sizeof(Ptr)) + @test sizeof_nothrow(Union{Ptr, Int, Type{Ptr{Int8}}, Type{Int}}) + @test sizeof_nothrow(Const(Ptr)) + @test sizeof_nothrow(Type{Ptr}) + @test sizeof_nothrow(Type{Union{Ptr{Int}, Int}}) + @test !sizeof_nothrow(Const(Tuple)) + @test !sizeof_nothrow(Type{Vector{Int}}) + @test !sizeof_nothrow(Type{Union{Int, String}}) + @test sizeof_nothrow(String) + @test !sizeof_nothrow(Type{String}) + @test sizeof_tfunc(Type{Union{Int64, Int32}}) == Const(Core.sizeof(Union{Int64, Int32})) + let PT = Core.Compiler.PartialStruct(Tuple{Int64,UInt64}, Any[Const(10), UInt64]) + @test sizeof_tfunc(PT) === Const(16) + @test nfields_tfunc(PT) === Const(2) + @test sizeof_nothrow(PT) + end + @test nfields_tfunc(Type) === Int + @test nfields_tfunc(Number) === Int + @test nfields_tfunc(Int) === Const(0) + @test nfields_tfunc(Complex) === Const(2) + @test nfields_tfunc(Type{Type{Int}}) === Const(nfields(DataType)) + @test nfields_tfunc(UnionAll) === Const(2) + @test nfields_tfunc(DataType) === Const(nfields(DataType)) + @test nfields_tfunc(Type{Int}) === Const(nfields(DataType)) + @test nfields_tfunc(Type{Integer}) === Const(nfields(DataType)) + @test nfields_tfunc(Type{Complex}) === Int + @test nfields_tfunc(typeof(Union{})) === Const(0) + @test nfields_tfunc(Type{Union{}}) === Const(0) + @test nfields_tfunc(Tuple{Int, Vararg{Int}}) === Int + @test nfields_tfunc(Tuple{Int, Integer}) === Const(2) + @test nfields_tfunc(Union{Tuple{Int, Float64}, Tuple{Int, Int}}) === Const(2) + @test nfields_tfunc(@NamedTuple{a::Int,b::Integer}) === Const(2) + @test nfields_tfunc(NamedTuple{(:a,:b),T} where T<:Tuple{Int,Integer}) === Const(2) + @test nfields_tfunc(NamedTuple{(:a,:b)}) === Const(2) + @test nfields_tfunc(NamedTuple{names,Tuple{Any,Any}} where names) === Const(2) + @test nfields_tfunc(Union{NamedTuple{(:a,:b)},NamedTuple{(:c,:d)}}) === Const(2) +end + +let typeof_tfunc(@nospecialize xs...) = + Core.Compiler.typeof_tfunc(Core.Compiler.fallback_lattice, xs...) + @test typeof_tfunc(Tuple{Vararg{Int}}) == Type{Tuple{Vararg{Int,N}}} where N + @test typeof_tfunc(Tuple{Any}) == Type{<:Tuple{Any}} + @test typeof_tfunc(Type{Array}) === DataType + @test typeof_tfunc(Type{<:Array}) === DataType + @test typeof_tfunc(Array{Int}) == Type{Array{Int,N}} where N + @test typeof_tfunc(AbstractArray{Int}) == Type{<:AbstractArray{Int,N}} where N + @test typeof_tfunc(Union{<:T, <:Real} where T<:Complex) == Union{Type{Complex{T}} where T<:Real, Type{<:Real}} +end f_typeof_tfunc(x) = typeof(x) @test Base.return_types(f_typeof_tfunc, (Union{<:T, Int} where T<:Complex,)) == Any[Union{Type{Int}, Type{Complex{T}} where T<:Real}] # arrayref / arrayset / arraysize -import Core.Compiler: Const, arrayref_tfunc, arrayset_tfunc, arraysize_tfunc -@test arrayref_tfunc(Const(true), Vector{Int}, Int) === Int -@test arrayref_tfunc(Const(true), Vector{<:Integer}, Int) === Integer -@test arrayref_tfunc(Const(true), Vector, Int) === Any -@test arrayref_tfunc(Const(true), Vector{Int}, Int, Vararg{Int}) === Int -@test arrayref_tfunc(Const(true), Vector{Int}, Vararg{Int}) === Int -@test arrayref_tfunc(Const(true), Vector{Int}) === Union{} -@test arrayref_tfunc(Const(true), String, Int) === Union{} -@test arrayref_tfunc(Const(true), Vector{Int}, Float64) === Union{} -@test arrayref_tfunc(Int, Vector{Int}, Int) === Union{} -@test arrayset_tfunc(Const(true), Vector{Int}, Int, Int) === Vector{Int} -let ua = Vector{<:Integer} - @test arrayset_tfunc(Const(true), ua, Int, Int) === ua -end -@test arrayset_tfunc(Const(true), Vector, Int, Int) === Vector -@test arrayset_tfunc(Const(true), Any, Int, Int) === Any -@test arrayset_tfunc(Const(true), Vector{String}, String, Int, Vararg{Int}) === Vector{String} -@test arrayset_tfunc(Const(true), Vector{String}, String, Vararg{Int}) === Vector{String} -@test arrayset_tfunc(Const(true), Vector{String}, String) === Union{} -@test arrayset_tfunc(Const(true), String, Char, Int) === Union{} -@test arrayset_tfunc(Const(true), Vector{Int}, Int, Float64) === Union{} -@test arrayset_tfunc(Int, Vector{Int}, Int, Int) === Union{} -@test arrayset_tfunc(Const(true), Vector{Int}, Float64, Int) === Union{} -@test arraysize_tfunc(Vector, Int) === Int -@test arraysize_tfunc(Vector, Float64) === Union{} -@test arraysize_tfunc(String, Int) === Union{} - -let tuple_tfunc - function tuple_tfunc(@nospecialize xs...) - return Core.Compiler.tuple_tfunc(Any[xs...]) - end +import Core.Compiler: Const +let arrayref_tfunc(@nospecialize xs...) = Core.Compiler.arrayref_tfunc(Core.Compiler.fallback_lattice, xs...) + arrayset_tfunc(@nospecialize xs...) = Core.Compiler.arrayset_tfunc(Core.Compiler.fallback_lattice, xs...) + arraysize_tfunc(@nospecialize xs...) = Core.Compiler.arraysize_tfunc(Core.Compiler.fallback_lattice, xs...) + @test arrayref_tfunc(Const(true), Vector{Int}, Int) === Int + @test arrayref_tfunc(Const(true), Vector{<:Integer}, Int) === Integer + @test arrayref_tfunc(Const(true), Vector, Int) === Any + @test arrayref_tfunc(Const(true), Vector{Int}, Int, Vararg{Int}) === Int + @test arrayref_tfunc(Const(true), Vector{Int}, Vararg{Int}) === Int + @test arrayref_tfunc(Const(true), Vector{Int}) === Union{} + @test arrayref_tfunc(Const(true), String, Int) === Union{} + @test arrayref_tfunc(Const(true), Vector{Int}, Float64) === Union{} + @test arrayref_tfunc(Int, Vector{Int}, Int) === Union{} + @test arrayset_tfunc(Const(true), Vector{Int}, Int, Int) === Vector{Int} + let ua = Vector{<:Integer} + @test arrayset_tfunc(Const(true), ua, Int, Int) === ua + end + @test arrayset_tfunc(Const(true), Vector, Int, Int) === Vector + @test arrayset_tfunc(Const(true), Any, Int, Int) === Any + @test arrayset_tfunc(Const(true), Vector{String}, String, Int, Vararg{Int}) === Vector{String} + @test arrayset_tfunc(Const(true), Vector{String}, String, Vararg{Int}) === Vector{String} + @test arrayset_tfunc(Const(true), Vector{String}, String) === Union{} + @test arrayset_tfunc(Const(true), String, Char, Int) === Union{} + @test arrayset_tfunc(Const(true), Vector{Int}, Int, Float64) === Union{} + @test arrayset_tfunc(Int, Vector{Int}, Int, Int) === Union{} + @test arrayset_tfunc(Const(true), Vector{Int}, Float64, Int) === Union{} + @test arraysize_tfunc(Vector, Int) === Int + @test arraysize_tfunc(Vector, Float64) === Union{} + @test arraysize_tfunc(String, Int) === Union{} +end + +let tuple_tfunc(@nospecialize xs...) = + Core.Compiler.tuple_tfunc(Core.Compiler.fallback_lattice, Any[xs...]) @test Core.Compiler.widenconst(tuple_tfunc(Type{Int})) === Tuple{DataType} # https://github.com/JuliaLang/julia/issues/44705 @test tuple_tfunc(Union{Type{Int32},Type{Int64}}) === Tuple{Type} @@ -1608,7 +1624,7 @@ g23024(TT::Tuple{DataType}) = f23024(TT[1], v23024) @test g23024((UInt8,)) === 2 @test !Core.Compiler.isconstType(Type{typeof(Union{})}) # could be Core.TypeofBottom or Type{Union{}} at runtime -@test !isa(Core.Compiler.getfield_tfunc(Type{Core.TypeofBottom}, Core.Compiler.Const(:name)), Core.Compiler.Const) +@test !isa(Core.Compiler.getfield_tfunc(Core.Compiler.fallback_lattice, Type{Core.TypeofBottom}, Core.Compiler.Const(:name)), Core.Compiler.Const) @test Base.return_types(supertype, (Type{typeof(Union{})},)) == Any[Any] # issue #23685 @@ -1667,44 +1683,48 @@ g_test_constant() = (f_constant(3) == 3 && f_constant(4) == 4 ? true : "BAD") f_pure_add() = (1 + 1 == 2) ? true : "FAIL" @test @inferred f_pure_add() -# inference of `T.mutable` -@test Core.Compiler.getfield_tfunc(Const(Int.name), Const(:flags)) == Const(0x4) -@test Core.Compiler.getfield_tfunc(Const(Vector{Int}.name), Const(:flags)) == Const(0x2) -@test Core.Compiler.getfield_tfunc(Core.TypeName, Const(:flags)) == UInt8 - -# getfield on abstract named tuples. issue #32698 -import Core.Compiler: getfield_tfunc, Const -@test getfield_tfunc(NamedTuple{(:id, :y), T} where {T <: Tuple{Int, Union{Float64, Missing}}}, - Const(:y)) == Union{Missing, Float64} -@test getfield_tfunc(NamedTuple{(:id, :y), T} where {T <: Tuple{Int, Union{Float64, Missing}}}, - Const(2)) == Union{Missing, Float64} -@test getfield_tfunc(NamedTuple{(:id, :y), T} where {T <: Tuple{Int, Union{Float64, Missing}}}, - Symbol) == Union{Missing, Float64, Int} -@test getfield_tfunc(NamedTuple{<:Any, T} where {T <: Tuple{Int, Union{Float64, Missing}}}, - Symbol) == Union{Missing, Float64, Int} -@test getfield_tfunc(NamedTuple{<:Any, T} where {T <: Tuple{Int, Union{Float64, Missing}}}, - Int) == Union{Missing, Float64, Int} -@test getfield_tfunc(NamedTuple{<:Any, T} where {T <: Tuple{Int, Union{Float64, Missing}}}, - Const(:x)) == Union{Missing, Float64, Int} - +import Core: Const mutable struct ARef{T} @atomic x::T end -@test getfield_tfunc(ARef{Int},Const(:x),Symbol) === Int -@test getfield_tfunc(ARef{Int},Const(:x),Bool) === Int -@test getfield_tfunc(ARef{Int},Const(:x),Symbol,Bool) === Int -@test getfield_tfunc(ARef{Int},Const(:x),Symbol,Vararg{Symbol}) === Int # `Vararg{Symbol}` might be empty -@test getfield_tfunc(ARef{Int},Const(:x),Vararg{Symbol}) === Int -@test getfield_tfunc(ARef{Int},Const(:x),Any,) === Int -@test getfield_tfunc(ARef{Int},Const(:x),Any,Any) === Int -@test getfield_tfunc(ARef{Int},Const(:x),Any,Vararg{Any}) === Int -@test getfield_tfunc(ARef{Int},Const(:x),Vararg{Any}) === Int -@test getfield_tfunc(ARef{Int},Const(:x),Int) === Union{} -@test getfield_tfunc(ARef{Int},Const(:x),Bool,Symbol) === Union{} -@test getfield_tfunc(ARef{Int},Const(:x),Symbol,Symbol) === Union{} -@test getfield_tfunc(ARef{Int},Const(:x),Bool,Bool) === Union{} - -import Core.Compiler: setfield!_tfunc, setfield!_nothrow, Const +let getfield_tfunc(@nospecialize xs...) = + Core.Compiler.getfield_tfunc(Core.Compiler.fallback_lattice, xs...) + + # inference of `T.mutable` + @test getfield_tfunc(Const(Int.name), Const(:flags)) == Const(0x4) + @test getfield_tfunc(Const(Vector{Int}.name), Const(:flags)) == Const(0x2) + @test getfield_tfunc(Core.TypeName, Const(:flags)) == UInt8 + + # getfield on abstract named tuples. issue #32698 + @test getfield_tfunc(NamedTuple{(:id, :y), T} where {T <: Tuple{Int, Union{Float64, Missing}}}, + Const(:y)) == Union{Missing, Float64} + @test getfield_tfunc(NamedTuple{(:id, :y), T} where {T <: Tuple{Int, Union{Float64, Missing}}}, + Const(2)) == Union{Missing, Float64} + @test getfield_tfunc(NamedTuple{(:id, :y), T} where {T <: Tuple{Int, Union{Float64, Missing}}}, + Symbol) == Union{Missing, Float64, Int} + @test getfield_tfunc(NamedTuple{<:Any, T} where {T <: Tuple{Int, Union{Float64, Missing}}}, + Symbol) == Union{Missing, Float64, Int} + @test getfield_tfunc(NamedTuple{<:Any, T} where {T <: Tuple{Int, Union{Float64, Missing}}}, + Int) == Union{Missing, Float64, Int} + @test getfield_tfunc(NamedTuple{<:Any, T} where {T <: Tuple{Int, Union{Float64, Missing}}}, + Const(:x)) == Union{Missing, Float64, Int} + + @test getfield_tfunc(ARef{Int},Const(:x),Symbol) === Int + @test getfield_tfunc(ARef{Int},Const(:x),Bool) === Int + @test getfield_tfunc(ARef{Int},Const(:x),Symbol,Bool) === Int + @test getfield_tfunc(ARef{Int},Const(:x),Symbol,Vararg{Symbol}) === Int # `Vararg{Symbol}` might be empty + @test getfield_tfunc(ARef{Int},Const(:x),Vararg{Symbol}) === Int + @test getfield_tfunc(ARef{Int},Const(:x),Any,) === Int + @test getfield_tfunc(ARef{Int},Const(:x),Any,Any) === Int + @test getfield_tfunc(ARef{Int},Const(:x),Any,Vararg{Any}) === Int + @test getfield_tfunc(ARef{Int},Const(:x),Vararg{Any}) === Int + @test getfield_tfunc(ARef{Int},Const(:x),Int) === Union{} + @test getfield_tfunc(ARef{Int},Const(:x),Bool,Symbol) === Union{} + @test getfield_tfunc(ARef{Int},Const(:x),Symbol,Symbol) === Union{} + @test getfield_tfunc(ARef{Int},Const(:x),Bool,Bool) === Union{} +end + +import Core.Compiler: Const mutable struct XY{X,Y} x::X y::Y @@ -1715,102 +1735,106 @@ mutable struct ABCDconst c const d::Union{Int,Nothing} end -@test setfield!_tfunc(Base.RefValue{Int}, Const(:x), Int) === Int -@test setfield!_tfunc(Base.RefValue{Int}, Const(:x), Int, Symbol) === Int -@test setfield!_tfunc(Base.RefValue{Int}, Const(1), Int) === Int -@test setfield!_tfunc(Base.RefValue{Int}, Const(1), Int, Symbol) === Int -@test setfield!_tfunc(Base.RefValue{Int}, Int, Int) === Int -@test setfield!_tfunc(Base.RefValue{Any}, Const(:x), Int) === Int -@test setfield!_tfunc(Base.RefValue{Any}, Const(:x), Int, Symbol) === Int -@test setfield!_tfunc(Base.RefValue{Any}, Const(1), Int) === Int -@test setfield!_tfunc(Base.RefValue{Any}, Const(1), Int, Symbol) === Int -@test setfield!_tfunc(Base.RefValue{Any}, Int, Int) === Int -@test setfield!_tfunc(XY{Any,Any}, Const(1), Int) === Int -@test setfield!_tfunc(XY{Any,Any}, Const(2), Float64) === Float64 -@test setfield!_tfunc(XY{Int,Float64}, Const(1), Int) === Int -@test setfield!_tfunc(XY{Int,Float64}, Const(2), Float64) === Float64 -@test setfield!_tfunc(ABCDconst, Const(:c), Any) === Any -@test setfield!_tfunc(ABCDconst, Const(3), Any) === Any -@test setfield!_tfunc(ABCDconst, Symbol, Any) === Any -@test setfield!_tfunc(ABCDconst, Int, Any) === Any -@test setfield!_tfunc(Union{Base.RefValue{Any},Some{Any}}, Const(:x), Int) === Int -@test setfield!_tfunc(Union{Base.RefValue,Some{Any}}, Const(:x), Int) === Int -@test setfield!_tfunc(Union{Base.RefValue{Any},Some{Any}}, Const(1), Int) === Int -@test setfield!_tfunc(Union{Base.RefValue,Some{Any}}, Const(1), Int) === Int -@test setfield!_tfunc(Union{Base.RefValue{Any},Some{Any}}, Symbol, Int) === Int -@test setfield!_tfunc(Union{Base.RefValue,Some{Any}}, Symbol, Int) === Int -@test setfield!_tfunc(Union{Base.RefValue{Any},Some{Any}}, Int, Int) === Int -@test setfield!_tfunc(Union{Base.RefValue,Some{Any}}, Int, Int) === Int -@test setfield!_tfunc(Any, Symbol, Int) === Int -@test setfield!_tfunc(Any, Int, Int) === Int -@test setfield!_tfunc(Any, Any, Int) === Int -@test setfield!_tfunc(Base.RefValue{Int}, Const(:x), Float64) === Union{} -@test setfield!_tfunc(Base.RefValue{Int}, Const(:x), Float64, Symbol) === Union{} -@test setfield!_tfunc(Base.RefValue{Int}, Const(1), Float64) === Union{} -@test setfield!_tfunc(Base.RefValue{Int}, Const(1), Float64, Symbol) === Union{} -@test setfield!_tfunc(Base.RefValue{Int}, Int, Float64) === Union{} -@test setfield!_tfunc(Base.RefValue{Any}, Const(:y), Int) === Union{} -@test setfield!_tfunc(Base.RefValue{Any}, Const(:y), Int, Bool) === Union{} -@test setfield!_tfunc(Base.RefValue{Any}, Const(2), Int) === Union{} -@test setfield!_tfunc(Base.RefValue{Any}, Const(2), Int, Bool) === Union{} -@test setfield!_tfunc(Base.RefValue{Any}, String, Int) === Union{} -@test setfield!_tfunc(Some{Any}, Const(:value), Int) === Union{} -@test setfield!_tfunc(Some, Const(:value), Int) === Union{} -@test setfield!_tfunc(Some{Any}, Const(1), Int) === Union{} -@test setfield!_tfunc(Some, Const(1), Int) === Union{} -@test setfield!_tfunc(Some{Any}, Symbol, Int) === Union{} -@test setfield!_tfunc(Some, Symbol, Int) === Union{} -@test setfield!_tfunc(Some{Any}, Int, Int) === Union{} -@test setfield!_tfunc(Some, Int, Int) === Union{} -@test setfield!_tfunc(Const(@__MODULE__), Const(:v), Int) === Union{} -@test setfield!_tfunc(Const(@__MODULE__), Int, Int) === Union{} -@test setfield!_tfunc(Module, Const(:v), Int) === Union{} -@test setfield!_tfunc(Union{Module,Base.RefValue{Any}}, Const(:v), Int) === Union{} -@test setfield!_tfunc(ABCDconst, Const(:a), Any) === Union{} -@test setfield!_tfunc(ABCDconst, Const(:b), Any) === Union{} -@test setfield!_tfunc(ABCDconst, Const(:d), Any) === Union{} -@test setfield!_tfunc(ABCDconst, Const(1), Any) === Union{} -@test setfield!_tfunc(ABCDconst, Const(2), Any) === Union{} -@test setfield!_tfunc(ABCDconst, Const(4), Any) === Union{} -let 𝕃ₒ = Core.Compiler.OptimizerLattice() - @test setfield!_nothrow(𝕃ₒ, Base.RefValue{Int}, Const(:x), Int) - @test setfield!_nothrow(𝕃ₒ, Base.RefValue{Int}, Const(1), Int) - @test setfield!_nothrow(𝕃ₒ, Base.RefValue{Any}, Const(:x), Int) - @test setfield!_nothrow(𝕃ₒ, Base.RefValue{Any}, Const(1), Int) - @test setfield!_nothrow(𝕃ₒ, XY{Any,Any}, Const(:x), Int) - @test setfield!_nothrow(𝕃ₒ, XY{Any,Any}, Const(:x), Any) - @test setfield!_nothrow(𝕃ₒ, XY{Int,Float64}, Const(:x), Int) - @test setfield!_nothrow(𝕃ₒ, ABCDconst, Const(:c), Any) - @test setfield!_nothrow(𝕃ₒ, ABCDconst, Const(3), Any) - @test !setfield!_nothrow(𝕃ₒ, XY{Int,Float64}, Symbol, Any) - @test !setfield!_nothrow(𝕃ₒ, XY{Int,Float64}, Int, Any) - @test !setfield!_nothrow(𝕃ₒ, Base.RefValue{Int}, Const(:x), Any) - @test !setfield!_nothrow(𝕃ₒ, Base.RefValue{Int}, Const(1), Any) - @test !setfield!_nothrow(𝕃ₒ, Base.RefValue{Any}, Const(:x), Int, Symbol) - @test !setfield!_nothrow(𝕃ₒ, Base.RefValue{Any}, Symbol, Int) - @test !setfield!_nothrow(𝕃ₒ, Base.RefValue{Any}, Int, Int) - @test !setfield!_nothrow(𝕃ₒ, XY{Int,Float64}, Const(:y), Int) - @test !setfield!_nothrow(𝕃ₒ, XY{Int,Float64}, Symbol, Int) - @test !setfield!_nothrow(𝕃ₒ, XY{Int,Float64}, Int, Int) - @test !setfield!_nothrow(𝕃ₒ, ABCDconst, Const(:a), Any) - @test !setfield!_nothrow(𝕃ₒ, ABCDconst, Const(:b), Any) - @test !setfield!_nothrow(𝕃ₒ, ABCDconst, Const(:d), Any) - @test !setfield!_nothrow(𝕃ₒ, ABCDconst, Symbol, Any) - @test !setfield!_nothrow(𝕃ₒ, ABCDconst, Const(1), Any) - @test !setfield!_nothrow(𝕃ₒ, ABCDconst, Const(2), Any) - @test !setfield!_nothrow(𝕃ₒ, ABCDconst, Const(4), Any) - @test !setfield!_nothrow(𝕃ₒ, ABCDconst, Int, Any) - @test !setfield!_nothrow(𝕃ₒ, Union{Base.RefValue{Any},Some{Any}}, Const(:x), Int) - @test !setfield!_nothrow(𝕃ₒ, Union{Base.RefValue,Some{Any}}, Const(:x), Int) - @test !setfield!_nothrow(𝕃ₒ, Union{Base.RefValue{Any},Some{Any}}, Const(1), Int) - @test !setfield!_nothrow(𝕃ₒ, Union{Base.RefValue,Some{Any}}, Const(1), Int) - @test !setfield!_nothrow(𝕃ₒ, Union{Base.RefValue{Any},Some{Any}}, Symbol, Int) - @test !setfield!_nothrow(𝕃ₒ, Union{Base.RefValue,Some{Any}}, Symbol, Int) - @test !setfield!_nothrow(𝕃ₒ, Union{Base.RefValue{Any},Some{Any}}, Int, Int) - @test !setfield!_nothrow(𝕃ₒ, Union{Base.RefValue,Some{Any}}, Int, Int) - @test !setfield!_nothrow(𝕃ₒ, Any, Symbol, Int) - @test !setfield!_nothrow(𝕃ₒ, Any, Int, Int) - @test !setfield!_nothrow(𝕃ₒ, Any, Any, Int) +let setfield!_tfunc(@nospecialize xs...) = + Core.Compiler.setfield!_tfunc(Core.Compiler.fallback_lattice, xs...) + @test setfield!_tfunc(Base.RefValue{Int}, Const(:x), Int) === Int + @test setfield!_tfunc(Base.RefValue{Int}, Const(:x), Int, Symbol) === Int + @test setfield!_tfunc(Base.RefValue{Int}, Const(1), Int) === Int + @test setfield!_tfunc(Base.RefValue{Int}, Const(1), Int, Symbol) === Int + @test setfield!_tfunc(Base.RefValue{Int}, Int, Int) === Int + @test setfield!_tfunc(Base.RefValue{Any}, Const(:x), Int) === Int + @test setfield!_tfunc(Base.RefValue{Any}, Const(:x), Int, Symbol) === Int + @test setfield!_tfunc(Base.RefValue{Any}, Const(1), Int) === Int + @test setfield!_tfunc(Base.RefValue{Any}, Const(1), Int, Symbol) === Int + @test setfield!_tfunc(Base.RefValue{Any}, Int, Int) === Int + @test setfield!_tfunc(XY{Any,Any}, Const(1), Int) === Int + @test setfield!_tfunc(XY{Any,Any}, Const(2), Float64) === Float64 + @test setfield!_tfunc(XY{Int,Float64}, Const(1), Int) === Int + @test setfield!_tfunc(XY{Int,Float64}, Const(2), Float64) === Float64 + @test setfield!_tfunc(ABCDconst, Const(:c), Any) === Any + @test setfield!_tfunc(ABCDconst, Const(3), Any) === Any + @test setfield!_tfunc(ABCDconst, Symbol, Any) === Any + @test setfield!_tfunc(ABCDconst, Int, Any) === Any + @test setfield!_tfunc(Union{Base.RefValue{Any},Some{Any}}, Const(:x), Int) === Int + @test setfield!_tfunc(Union{Base.RefValue,Some{Any}}, Const(:x), Int) === Int + @test setfield!_tfunc(Union{Base.RefValue{Any},Some{Any}}, Const(1), Int) === Int + @test setfield!_tfunc(Union{Base.RefValue,Some{Any}}, Const(1), Int) === Int + @test setfield!_tfunc(Union{Base.RefValue{Any},Some{Any}}, Symbol, Int) === Int + @test setfield!_tfunc(Union{Base.RefValue,Some{Any}}, Symbol, Int) === Int + @test setfield!_tfunc(Union{Base.RefValue{Any},Some{Any}}, Int, Int) === Int + @test setfield!_tfunc(Union{Base.RefValue,Some{Any}}, Int, Int) === Int + @test setfield!_tfunc(Any, Symbol, Int) === Int + @test setfield!_tfunc(Any, Int, Int) === Int + @test setfield!_tfunc(Any, Any, Int) === Int + @test setfield!_tfunc(Base.RefValue{Int}, Const(:x), Float64) === Union{} + @test setfield!_tfunc(Base.RefValue{Int}, Const(:x), Float64, Symbol) === Union{} + @test setfield!_tfunc(Base.RefValue{Int}, Const(1), Float64) === Union{} + @test setfield!_tfunc(Base.RefValue{Int}, Const(1), Float64, Symbol) === Union{} + @test setfield!_tfunc(Base.RefValue{Int}, Int, Float64) === Union{} + @test setfield!_tfunc(Base.RefValue{Any}, Const(:y), Int) === Union{} + @test setfield!_tfunc(Base.RefValue{Any}, Const(:y), Int, Bool) === Union{} + @test setfield!_tfunc(Base.RefValue{Any}, Const(2), Int) === Union{} + @test setfield!_tfunc(Base.RefValue{Any}, Const(2), Int, Bool) === Union{} + @test setfield!_tfunc(Base.RefValue{Any}, String, Int) === Union{} + @test setfield!_tfunc(Some{Any}, Const(:value), Int) === Union{} + @test setfield!_tfunc(Some, Const(:value), Int) === Union{} + @test setfield!_tfunc(Some{Any}, Const(1), Int) === Union{} + @test setfield!_tfunc(Some, Const(1), Int) === Union{} + @test setfield!_tfunc(Some{Any}, Symbol, Int) === Union{} + @test setfield!_tfunc(Some, Symbol, Int) === Union{} + @test setfield!_tfunc(Some{Any}, Int, Int) === Union{} + @test setfield!_tfunc(Some, Int, Int) === Union{} + @test setfield!_tfunc(Const(@__MODULE__), Const(:v), Int) === Union{} + @test setfield!_tfunc(Const(@__MODULE__), Int, Int) === Union{} + @test setfield!_tfunc(Module, Const(:v), Int) === Union{} + @test setfield!_tfunc(Union{Module,Base.RefValue{Any}}, Const(:v), Int) === Union{} + @test setfield!_tfunc(ABCDconst, Const(:a), Any) === Union{} + @test setfield!_tfunc(ABCDconst, Const(:b), Any) === Union{} + @test setfield!_tfunc(ABCDconst, Const(:d), Any) === Union{} + @test setfield!_tfunc(ABCDconst, Const(1), Any) === Union{} + @test setfield!_tfunc(ABCDconst, Const(2), Any) === Union{} + @test setfield!_tfunc(ABCDconst, Const(4), Any) === Union{} +end +let setfield!_nothrow(@nospecialize xs...) = + Core.Compiler.setfield!_nothrow(Core.Compiler.OptimizerLattice(), xs...) + @test setfield!_nothrow(Base.RefValue{Int}, Const(:x), Int) + @test setfield!_nothrow(Base.RefValue{Int}, Const(1), Int) + @test setfield!_nothrow(Base.RefValue{Any}, Const(:x), Int) + @test setfield!_nothrow(Base.RefValue{Any}, Const(1), Int) + @test setfield!_nothrow(XY{Any,Any}, Const(:x), Int) + @test setfield!_nothrow(XY{Any,Any}, Const(:x), Any) + @test setfield!_nothrow(XY{Int,Float64}, Const(:x), Int) + @test setfield!_nothrow(ABCDconst, Const(:c), Any) + @test setfield!_nothrow(ABCDconst, Const(3), Any) + @test !setfield!_nothrow(XY{Int,Float64}, Symbol, Any) + @test !setfield!_nothrow(XY{Int,Float64}, Int, Any) + @test !setfield!_nothrow(Base.RefValue{Int}, Const(:x), Any) + @test !setfield!_nothrow(Base.RefValue{Int}, Const(1), Any) + @test !setfield!_nothrow(Base.RefValue{Any}, Const(:x), Int, Symbol) + @test !setfield!_nothrow(Base.RefValue{Any}, Symbol, Int) + @test !setfield!_nothrow(Base.RefValue{Any}, Int, Int) + @test !setfield!_nothrow(XY{Int,Float64}, Const(:y), Int) + @test !setfield!_nothrow(XY{Int,Float64}, Symbol, Int) + @test !setfield!_nothrow(XY{Int,Float64}, Int, Int) + @test !setfield!_nothrow(ABCDconst, Const(:a), Any) + @test !setfield!_nothrow(ABCDconst, Const(:b), Any) + @test !setfield!_nothrow(ABCDconst, Const(:d), Any) + @test !setfield!_nothrow(ABCDconst, Symbol, Any) + @test !setfield!_nothrow(ABCDconst, Const(1), Any) + @test !setfield!_nothrow(ABCDconst, Const(2), Any) + @test !setfield!_nothrow(ABCDconst, Const(4), Any) + @test !setfield!_nothrow(ABCDconst, Int, Any) + @test !setfield!_nothrow(Union{Base.RefValue{Any},Some{Any}}, Const(:x), Int) + @test !setfield!_nothrow(Union{Base.RefValue,Some{Any}}, Const(:x), Int) + @test !setfield!_nothrow(Union{Base.RefValue{Any},Some{Any}}, Const(1), Int) + @test !setfield!_nothrow(Union{Base.RefValue,Some{Any}}, Const(1), Int) + @test !setfield!_nothrow(Union{Base.RefValue{Any},Some{Any}}, Symbol, Int) + @test !setfield!_nothrow(Union{Base.RefValue,Some{Any}}, Symbol, Int) + @test !setfield!_nothrow(Union{Base.RefValue{Any},Some{Any}}, Int, Int) + @test !setfield!_nothrow(Union{Base.RefValue,Some{Any}}, Int, Int) + @test !setfield!_nothrow(Any, Symbol, Int) + @test !setfield!_nothrow(Any, Int, Int) + @test !setfield!_nothrow(Any, Any, Int) end struct Foo_22708 @@ -2257,6 +2281,8 @@ import Core.Compiler: MustAlias, Const, PartialStruct, ⊑, tmerge let 𝕃ᵢ = InferenceLattice(MustAliasesLattice(BaseInferenceLattice.instance)) ⊑(@nospecialize(a), @nospecialize(b)) = Core.Compiler.:⊑(𝕃ᵢ, a, b) tmerge(@nospecialize(a), @nospecialize(b)) = Core.Compiler.tmerge(𝕃ᵢ, a, b) + isa_tfunc(@nospecialize xs...) = Core.Compiler.isa_tfunc(𝕃ᵢ, xs...) + ifelse_tfunc(@nospecialize xs...) = Core.Compiler.ifelse_tfunc(𝕃ᵢ, xs...) @test (MustAlias(2, AliasableField{Any}, 1, Int) ⊑ Int) @test !(Int ⊑ MustAlias(2, AliasableField{Any}, 1, Int)) @@ -2267,11 +2293,11 @@ let 𝕃ᵢ = InferenceLattice(MustAliasesLattice(BaseInferenceLattice.instance) @test tmerge(MustAlias(2, AliasableField{Any}, 1, Int), Const(nothing)) === Union{Int,Nothing} @test tmerge(Const(nothing), MustAlias(2, AliasableField{Any}, 1, Any)) === Any @test tmerge(Const(nothing), MustAlias(2, AliasableField{Any}, 1, Int)) === Union{Int,Nothing} - @test Core.Compiler.isa_tfunc(𝕃ᵢ, MustAlias(2, AliasableField{Any}, 1, Bool), Const(Bool)) === Const(true) - @test Core.Compiler.isa_tfunc(𝕃ᵢ, MustAlias(2, AliasableField{Any}, 1, Bool), Type{Bool}) === Const(true) - @test Core.Compiler.isa_tfunc(𝕃ᵢ, MustAlias(2, AliasableField{Any}, 1, Int), Type{Bool}) === Const(false) - @test Core.Compiler.ifelse_tfunc(MustAlias(2, AliasableField{Any}, 1, Bool), Int, Int) === Int - @test Core.Compiler.ifelse_tfunc(MustAlias(2, AliasableField{Any}, 1, Int), Int, Int) === Union{} + @test isa_tfunc(MustAlias(2, AliasableField{Any}, 1, Bool), Const(Bool)) === Const(true) + @test isa_tfunc(MustAlias(2, AliasableField{Any}, 1, Bool), Type{Bool}) === Const(true) + @test isa_tfunc(MustAlias(2, AliasableField{Any}, 1, Int), Type{Bool}) === Const(false) + @test ifelse_tfunc(MustAlias(2, AliasableField{Any}, 1, Bool), Int, Int) === Int + @test ifelse_tfunc(MustAlias(2, AliasableField{Any}, 1, Int), Int, Int) === Union{} end maybeget_mustalias_tmerge(x::AliasableField) = x.f @@ -2704,10 +2730,12 @@ end |> only === Int # `apply_type_tfunc` should always return accurate result for empty NamedTuple case import Core: Const -import Core.Compiler: apply_type_tfunc -@test apply_type_tfunc(Const(NamedTuple), Const(()), Type{T} where T<:Tuple{}) === Const(typeof((;))) -@test apply_type_tfunc(Const(NamedTuple), Const(()), Type{T} where T<:Tuple) === Const(typeof((;))) -@test apply_type_tfunc(Const(NamedTuple), Tuple{Vararg{Symbol}}, Type{Tuple{}}) === Const(typeof((;))) +let apply_type_tfunc(@nospecialize xs...) = + Core.Compiler.apply_type_tfunc(Core.Compiler.fallback_lattice, xs...) + @test apply_type_tfunc(Const(NamedTuple), Const(()), Type{T} where T<:Tuple{}) === Const(typeof((;))) + @test apply_type_tfunc(Const(NamedTuple), Const(()), Type{T} where T<:Tuple) === Const(typeof((;))) + @test apply_type_tfunc(Const(NamedTuple), Tuple{Vararg{Symbol}}, Type{Tuple{}}) === Const(typeof((;))) +end # Don't pessimize apply_type to anything worse than Type and yield Bottom for invalid Unions @test only(Base.return_types(Core.apply_type, Tuple{Type{Union}})) == Type{Union{}} @@ -3701,7 +3729,7 @@ f_generator_splat(t::Tuple) = tuple((identity(l) for l in t)...) # Issue #36710 - sizeof(::UnionAll) tfunc correctness @test (sizeof(Ptr),) == sizeof.((Ptr,)) == sizeof.((Ptr{Cvoid},)) -@test Core.Compiler.sizeof_tfunc(UnionAll) === Int +@test Core.Compiler.sizeof_tfunc(Core.Compiler.fallback_lattice, UnionAll) === Int @test !Core.Compiler.sizeof_nothrow(UnionAll) @test only(Base.return_types(Core._expr)) === Expr @@ -4461,8 +4489,9 @@ end == Rational # vararg-tuple comparison within `PartialStruct` # https://github.com/JuliaLang/julia/issues/44965 -let t = Core.Compiler.tuple_tfunc(Any[Core.Const(42), Vararg{Any}]) - @test Core.Compiler.issimplertype(Core.Compiler.fallback_lattice, t, t) +let 𝕃ᵢ = Core.Compiler.fallback_lattice + t = Core.Compiler.tuple_tfunc(𝕃ᵢ, Any[Core.Const(42), Vararg{Any}]) + @test Core.Compiler.issimplertype(𝕃ᵢ, t, t) end # check the inference convergence with an empty vartable: diff --git a/test/compiler/inline.jl b/test/compiler/inline.jl index 1119c6d01b8e9e..c8cfacd09cd9fa 100644 --- a/test/compiler/inline.jl +++ b/test/compiler/inline.jl @@ -1664,8 +1664,11 @@ let src = code_typed1(call_twice_sitofp, (Int,)) end # Test getfield modeling of Type{Ref{_A}} where _A -@test Core.Compiler.getfield_tfunc(Type, Core.Compiler.Const(:parameters)) !== Union{} -@test !isa(Core.Compiler.getfield_tfunc(Type{Tuple{Union{Int, Float64}, Int}}, Core.Compiler.Const(:name)), Core.Compiler.Const) +let getfield_tfunc(@nospecialize xs...) = + Core.Compiler.getfield_tfunc(Core.Compiler.fallback_lattice, xs...) + @test getfield_tfunc(Type, Core.Const(:parameters)) !== Union{} + @test !isa(getfield_tfunc(Type{Tuple{Union{Int, Float64}, Int}}, Core.Const(:name)), Core.Const) +end @test fully_eliminated(Base.ismutable, Tuple{Base.RefValue}) # TODO: Remove compute sparams for vararg_retrival From c41b44d8debc002fde70ae5965d259e6dc9cdb95 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Sat, 10 Dec 2022 16:59:28 +0900 Subject: [PATCH 095/387] remove the toplevel `@nospecialize` annotation from tfuncs.jl (#47850) --- base/compiler/tfuncs.jl | 339 ++++++++++++++++++++-------------------- 1 file changed, 171 insertions(+), 168 deletions(-) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index df56e7942d5f8c..f804bd54610198 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -4,7 +4,51 @@ # constants # ############# -@nospecialize +""" + @nospecs def + +Adds `@nospecialize` annotation to non-annotated arguments of `def`. +```julia +(Core.Compiler) julia> @macroexpand @nospecs function tfunc(𝕃::AbstractLattice, x, y::Bool, zs...) + x, ys + end +:(function tfunc(\$(Expr(:meta, :specialize, :(𝕃::AbstractLattice))), x, y::Bool, zs...) + #= REPL[3]:1 =# + \$(Expr(:meta, :nospecialize, :x, :zs)) + #= REPL[3]:2 =# + (x, ys) + end) +``` +""" +macro nospecs(ex) + is_function_def(ex) || throw(ArgumentError("expected function definition")) + args, body = ex.args + if isexpr(args, :call) + args = args.args[2:end] # skip marking `@nospecialize` on the function itself + else + @assert isexpr(args, :tuple) # anonymous function + args = args.args + end + names = Symbol[] + for arg in args + isexpr(arg, :macrocall) && continue + if isexpr(arg, :...) + arg = arg.args[1] + elseif isexpr(arg, :kw) + arg = arg.args[1] + end + isexpr(arg, :(::)) && continue + @assert arg isa Symbol + push!(names, arg) + end + @assert isexpr(body, :block) + if !isempty(names) + lin = first(body.args)::LineNumberNode + nospec = Expr(:macrocall, Symbol("@nospecialize"), lin, names...) + insert!(body.args, 2, nospec) + end + return esc(ex) +end const INT_INF = typemax(Int) # integer infinity @@ -38,14 +82,13 @@ function add_tfunc(f::IntrinsicFunction, minarg::Int, maxarg::Int, @nospecialize T_IFUNC[idx] = (minarg, maxarg, tfunc) T_IFUNC_COST[idx] = cost end -# TODO: add @nospecialize on `f` and declare its type as `Builtin` when that's supported -function add_tfunc(f::Function, minarg::Int, maxarg::Int, @nospecialize(tfunc), cost::Int) +function add_tfunc(@nospecialize(f::Builtin), minarg::Int, maxarg::Int, @nospecialize(tfunc), cost::Int) push!(T_FFUNC_KEY, f) push!(T_FFUNC_VAL, (minarg, maxarg, tfunc)) push!(T_FFUNC_COST, cost) end -add_tfunc(throw, 1, 1, (@specialize(𝕃::AbstractLattice), @nospecialize(x)) -> Bottom, 0) +add_tfunc(throw, 1, 1, @nospecs((𝕃::AbstractLattice, x)->Bottom), 0) # the inverse of typeof_tfunc # returns (type, isexact, isconcrete, istype) @@ -98,12 +141,12 @@ function instanceof_tfunc(@nospecialize(t)) end return Any, false, false, false end -bitcast_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(t), @nospecialize(x)) = instanceof_tfunc(t)[1] -math_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(x)) = widenconst(x) -math_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(x), @nospecialize(y)) = widenconst(x) -math_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(x), @nospecialize(y), @nospecialize(z)) = widenconst(x) -fptoui_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(t), @nospecialize(x)) = bitcast_tfunc(𝕃, t, x) -fptosi_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(t), @nospecialize(x)) = bitcast_tfunc(𝕃, t, x) +@nospecs bitcast_tfunc(𝕃::AbstractLattice, t, x) = instanceof_tfunc(t)[1] +@nospecs math_tfunc(𝕃::AbstractLattice, x) = widenconst(x) +@nospecs math_tfunc(𝕃::AbstractLattice, x, y) = widenconst(x) +@nospecs math_tfunc(𝕃::AbstractLattice, x, y, z) = widenconst(x) +@nospecs fptoui_tfunc(𝕃::AbstractLattice, t, x) = bitcast_tfunc(𝕃, t, x) +@nospecs fptosi_tfunc(𝕃::AbstractLattice, t, x) = bitcast_tfunc(𝕃, t, x) ## conversion ## add_tfunc(bitcast, 2, 2, bitcast_tfunc, 1) @@ -169,7 +212,7 @@ add_tfunc(rint_llvm, 1, 1, math_tfunc, 10) add_tfunc(sqrt_llvm, 1, 1, math_tfunc, 20) add_tfunc(sqrt_llvm_fast, 1, 1, math_tfunc, 20) ## same-type comparisons ## -cmp_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(x), @nospecialize(y)) = Bool +@nospecs cmp_tfunc(𝕃::AbstractLattice, x, y) = Bool add_tfunc(eq_int, 2, 2, cmp_tfunc, 1) add_tfunc(ne_int, 2, 2, cmp_tfunc, 1) add_tfunc(slt_int, 2, 2, cmp_tfunc, 1) @@ -187,7 +230,7 @@ add_tfunc(lt_float_fast, 2, 2, cmp_tfunc, 1) add_tfunc(le_float_fast, 2, 2, cmp_tfunc, 1) ## checked arithmetic ## -chk_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(x), @nospecialize(y)) = Tuple{widenconst(x), Bool} +@nospecs chk_tfunc(𝕃::AbstractLattice, x, y) = Tuple{widenconst(x), Bool} add_tfunc(checked_sadd_int, 2, 2, chk_tfunc, 10) add_tfunc(checked_uadd_int, 2, 2, chk_tfunc, 10) add_tfunc(checked_ssub_int, 2, 2, chk_tfunc, 10) @@ -195,21 +238,20 @@ add_tfunc(checked_usub_int, 2, 2, chk_tfunc, 10) add_tfunc(checked_smul_int, 2, 2, chk_tfunc, 10) add_tfunc(checked_umul_int, 2, 2, chk_tfunc, 10) ## other, misc intrinsics ## -function llvmcall_tfunc(@specialize(𝕃::AbstractLattice), fptr, rt, at, a...) - @nospecialize fptr rt at a +@nospecs function llvmcall_tfunc(𝕃::AbstractLattice, fptr, rt, at, a...) return instanceof_tfunc(rt)[1] end add_tfunc(Core.Intrinsics.llvmcall, 3, INT_INF, llvmcall_tfunc, 10) -cglobal_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(fptr)) = Ptr{Cvoid} -function cglobal_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(fptr), @nospecialize(t)) +@nospecs cglobal_tfunc(𝕃::AbstractLattice, fptr) = Ptr{Cvoid} +@nospecs function cglobal_tfunc(𝕃::AbstractLattice, fptr, t) isa(t, Const) && return isa(t.val, Type) ? Ptr{t.val} : Ptr return isType(t) ? Ptr{t.parameters[1]} : Ptr end add_tfunc(Core.Intrinsics.cglobal, 1, 2, cglobal_tfunc, 5) -add_tfunc(Core.Intrinsics.have_fma, 1, 1, (@specialize(𝕃::AbstractLattice), @nospecialize(x))->Bool, 1) -add_tfunc(Core.Intrinsics.arraylen, 1, 1, (@specialize(𝕃::AbstractLattice), @nospecialize(x))->Int, 4) +add_tfunc(Core.Intrinsics.have_fma, 1, 1, @nospecs((𝕃::AbstractLattice, x)->Bool), 1) +add_tfunc(Core.Intrinsics.arraylen, 1, 1, @nospecs((𝕃::AbstractLattice, x)->Int), 4) -function ifelse_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(cnd), @nospecialize(x), @nospecialize(y)) +@nospecs function ifelse_tfunc(𝕃::AbstractLattice, cnd, x, y) cnd = widenslotwrapper(cnd) if isa(cnd, Const) if cnd.val === true @@ -226,17 +268,16 @@ function ifelse_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(cnd), @n end add_tfunc(Core.ifelse, 3, 3, ifelse_tfunc, 1) -function ifelse_nothrow(@specialize(𝕃::AbstractLattice), @nospecialize(cond), @nospecialize(x), @nospecialize(y)) +@nospecs function ifelse_nothrow(𝕃::AbstractLattice, cond, x, y) ⊑ = Core.Compiler.:⊑(𝕃) return cond ⊑ Bool end -egal_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(x), @nospecialize(y)) = - egal_tfunc(widenlattice(𝕃), x, y) -function egal_tfunc(@specialize(𝕃::MustAliasesLattice), @nospecialize(x), @nospecialize(y)) +@nospecs egal_tfunc(𝕃::AbstractLattice, x, y) = egal_tfunc(widenlattice(𝕃), x, y) +@nospecs function egal_tfunc(𝕃::MustAliasesLattice, x, y) return egal_tfunc(widenlattice(𝕃), widenmustalias(x), widenmustalias(y)) end -function egal_tfunc(@specialize(𝕃::ConditionalsLattice), @nospecialize(x), @nospecialize(y)) +@nospecs function egal_tfunc(𝕃::ConditionalsLattice, x, y) if isa(x, Conditional) y = widenconditional(y) if isa(y, Const) @@ -254,7 +295,7 @@ function egal_tfunc(@specialize(𝕃::ConditionalsLattice), @nospecialize(x), @n end return egal_tfunc(widenlattice(𝕃), x, y) end -function egal_tfunc(::ConstsLattice, @nospecialize(x), @nospecialize(y)) +@nospecs function egal_tfunc(::ConstsLattice, x, y) if isa(x, Const) && isa(y, Const) return Const(x.val === y.val) elseif !hasintersect(widenconst(x), widenconst(y)) @@ -265,10 +306,10 @@ function egal_tfunc(::ConstsLattice, @nospecialize(x), @nospecialize(y)) end return Bool end -egal_tfunc(::JLTypeLattice, @nospecialize(x), @nospecialize(y)) = Bool +@nospecs egal_tfunc(::JLTypeLattice, x, y) = Bool add_tfunc(===, 2, 2, egal_tfunc, 1) -function isdefined_nothrow(@specialize(𝕃::AbstractLattice), @nospecialize(x), @nospecialize(name)) +@nospecs function isdefined_nothrow(𝕃::AbstractLattice, x, name) ⊑ = Core.Compiler.:⊑(𝕃) if hasintersect(widenconst(x), Module) return name ⊑ Symbol @@ -277,11 +318,10 @@ function isdefined_nothrow(@specialize(𝕃::AbstractLattice), @nospecialize(x), end end -function isdefined_tfunc(@specialize(𝕃::AbstractLattice), arg1, sym, order) - @nospecialize arg1 sym order +@nospecs function isdefined_tfunc(𝕃::AbstractLattice, arg1, sym, order) return isdefined_tfunc(𝕃, arg1, sym) end -function isdefined_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(arg1), @nospecialize(sym)) +@nospecs function isdefined_tfunc(𝕃::AbstractLattice, arg1, sym) if isa(arg1, Const) arg1t = typeof(arg1.val) else @@ -394,7 +434,7 @@ function _const_sizeof(@nospecialize(x)) end return Const(size) end -function sizeof_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(x)) +@nospecs function sizeof_tfunc(𝕃::AbstractLattice, x) x = widenmustalias(x) isa(x, Const) && return _const_sizeof(x.val) isa(x, Conditional) && return _const_sizeof(Bool) @@ -425,7 +465,7 @@ function sizeof_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(x)) return Int end add_tfunc(Core.sizeof, 1, 1, sizeof_tfunc, 1) -function nfields_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(x)) +@nospecs function nfields_tfunc(𝕃::AbstractLattice, x) isa(x, Const) && return Const(nfields(x.val)) isa(x, Conditional) && return Const(0) xt = widenconst(x) @@ -452,9 +492,9 @@ function nfields_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(x)) return Int end add_tfunc(nfields, 1, 1, nfields_tfunc, 1) -add_tfunc(Core._expr, 1, INT_INF, (@specialize(𝕃::AbstractLattice), @nospecialize args...)->Expr, 100) -add_tfunc(svec, 0, INT_INF, (@specialize(𝕃::AbstractLattice), @nospecialize args...)->SimpleVector, 20) -function typevar_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(n), @nospecialize(lb_arg), @nospecialize(ub_arg)) +add_tfunc(Core._expr, 1, INT_INF, @nospecs((𝕃::AbstractLattice, args...)->Expr), 100) +add_tfunc(svec, 0, INT_INF, @nospecs((𝕃::AbstractLattice, args...)->SimpleVector), 20) +@nospecs function typevar_tfunc(𝕃::AbstractLattice, n, lb_arg, ub_arg) lb = Union{} ub = Any ub_certain = lb_certain = true @@ -488,7 +528,7 @@ function typevar_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(n), @no end return TypeVar end -function typebound_nothrow(b) +@nospecs function typebound_nothrow(b) b = widenconst(b) (b ⊑ TypeVar) && return true if isType(b) @@ -496,23 +536,23 @@ function typebound_nothrow(b) end return false end -function typevar_nothrow(n, lb, ub) +@nospecs function typevar_nothrow(n, lb, ub) (n ⊑ Symbol) || return false typebound_nothrow(lb) || return false typebound_nothrow(ub) || return false return true end add_tfunc(Core._typevar, 3, 3, typevar_tfunc, 100) -add_tfunc(applicable, 1, INT_INF, (@specialize(𝕃::AbstractLattice), @nospecialize(f), @nospecialize(args...))->Bool, 100) +add_tfunc(applicable, 1, INT_INF, @nospecs((𝕃::AbstractLattice, f, args...)->Bool), 100) -function arraysize_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(ary), @nospecialize(dim)) +@nospecs function arraysize_tfunc(𝕃::AbstractLattice, ary, dim) hasintersect(widenconst(ary), Array) || return Bottom hasintersect(widenconst(dim), Int) || return Bottom return Int end add_tfunc(arraysize, 2, 2, arraysize_tfunc, 4) -function arraysize_nothrow(@nospecialize(ary), @nospecialize(dim)) +@nospecs function arraysize_nothrow(ary, dim) ary ⊑ Array || return false if isa(dim, Const) dimval = dim.val @@ -565,32 +605,25 @@ function pointer_eltype(@nospecialize(ptr)) return Any end -function pointerref_tfunc(@specialize(𝕃::AbstractLattice), a, i, align) - @nospecialize a i align +@nospecs function pointerref_tfunc(𝕃::AbstractLattice, a, i, align) return pointer_eltype(a) end -function pointerset_tfunc(@specialize(𝕃::AbstractLattice), a, v, i, align) - @nospecialize a v i align +@nospecs function pointerset_tfunc(𝕃::AbstractLattice, a, v, i, align) return a end -function atomic_fence_tfunc(@specialize(𝕃::AbstractLattice), order) - @nospecialize order +@nospecs function atomic_fence_tfunc(𝕃::AbstractLattice, order) return Nothing end -function atomic_pointerref_tfunc(@specialize(𝕃::AbstractLattice), a, order) - @nospecialize a order +@nospecs function atomic_pointerref_tfunc(𝕃::AbstractLattice, a, order) return pointer_eltype(a) end -function atomic_pointerset_tfunc(@specialize(𝕃::AbstractLattice), a, v, order) - @nospecialize a v order +@nospecs function atomic_pointerset_tfunc(𝕃::AbstractLattice, a, v, order) return a end -function atomic_pointerswap_tfunc(@specialize(𝕃::AbstractLattice), a, v, order) - @nospecialize a v order +@nospecs function atomic_pointerswap_tfunc(𝕃::AbstractLattice, a, v, order) return pointer_eltype(a) end -function atomic_pointermodify_tfunc(@specialize(𝕃::AbstractLattice), ptr, op, v, order) - @nospecialize ptr op v order +@nospecs function atomic_pointermodify_tfunc(𝕃::AbstractLattice, ptr, op, v, order) a = widenconst(ptr) if !has_free_typevars(a) unw = unwrap_unionall(a) @@ -603,8 +636,7 @@ function atomic_pointermodify_tfunc(@specialize(𝕃::AbstractLattice), ptr, op, end return Pair end -function atomic_pointerreplace_tfunc(@specialize(𝕃::AbstractLattice), ptr, x, v, success_order, failure_order) - @nospecialize ptr x v success_order failure_order +@nospecs function atomic_pointerreplace_tfunc(𝕃::AbstractLattice, ptr, x, v, success_order, failure_order) a = widenconst(ptr) if !has_free_typevars(a) unw = unwrap_unionall(a) @@ -624,8 +656,8 @@ add_tfunc(atomic_pointerset, 3, 3, atomic_pointerset_tfunc, 5) add_tfunc(atomic_pointerswap, 3, 3, atomic_pointerswap_tfunc, 5) add_tfunc(atomic_pointermodify, 4, 4, atomic_pointermodify_tfunc, 5) add_tfunc(atomic_pointerreplace, 5, 5, atomic_pointerreplace_tfunc, 5) -add_tfunc(donotdelete, 0, INT_INF, (@specialize(𝕃::AbstractLattice), @nospecialize args...)->Nothing, 0) -function compilerbarrier_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(setting), @nospecialize(val)) +add_tfunc(donotdelete, 0, INT_INF, @nospecs((𝕃::AbstractLattice, args...)->Nothing), 0) +@nospecs function compilerbarrier_tfunc(𝕃::AbstractLattice, setting, val) # strongest barrier if a precise information isn't available at compiler time # XXX we may want to have "compile-time" error instead for such case isa(setting, Const) || return Any @@ -642,9 +674,9 @@ function compilerbarrier_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize end end add_tfunc(compilerbarrier, 2, 2, compilerbarrier_tfunc, 5) -add_tfunc(Core.finalizer, 2, 4, (@specialize(𝕃::AbstractLattice), @nospecialize args...)->Nothing, 5) +add_tfunc(Core.finalizer, 2, 4, @nospecs((𝕃::AbstractLattice, args...)->Nothing), 5) -function compilerbarrier_nothrow(@nospecialize(setting), @nospecialize(val)) +@nospecs function compilerbarrier_nothrow(setting, val) return isa(setting, Const) && contains_is((:type, :const, :conditional), setting.val) end @@ -664,7 +696,7 @@ function typeof_concrete_vararg(t::DataType) return nothing end -function typeof_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(t)) +@nospecs function typeof_tfunc(𝕃::AbstractLattice, t) isa(t, Const) && return Const(typeof(t.val)) t = widenconst(t) if isType(t) @@ -705,7 +737,7 @@ function typeof_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(t)) return DataType # typeof(anything)::DataType end # helper function of `typeof_tfunc`, which accepts `TypeVar` -function _typeof_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(t)) +@nospecs function _typeof_tfunc(𝕃::AbstractLattice, t) if isa(t, TypeVar) return t.ub !== Any ? _typeof_tfunc(𝕃, t.ub) : DataType end @@ -713,14 +745,14 @@ function _typeof_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(t)) end add_tfunc(typeof, 1, 1, typeof_tfunc, 1) -function typeassert_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(v), @nospecialize(t)) +@nospecs function typeassert_tfunc(𝕃::AbstractLattice, v, t) t = instanceof_tfunc(t)[1] t === Any && return v return tmeet(𝕃, v, t) end add_tfunc(typeassert, 2, 2, typeassert_tfunc, 4) -function typeassert_nothrow(@specialize(𝕃::AbstractLattice), @nospecialize(v), @nospecialize(t)) +@nospecs function typeassert_nothrow(𝕃::AbstractLattice, v, t) ⊑ = Core.Compiler.:⊑(𝕃) # ty, exact = instanceof_tfunc(t) # return exact && v ⊑ ty @@ -731,7 +763,7 @@ function typeassert_nothrow(@specialize(𝕃::AbstractLattice), @nospecialize(v) return false end -function isa_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(v), @nospecialize(tt)) +@nospecs function isa_tfunc(𝕃::AbstractLattice, v, tt) t, isexact = instanceof_tfunc(tt) if t === Bottom # check if t could be equivalent to typeof(Bottom), since that's valid in `isa`, but the set of `v` is empty @@ -766,12 +798,12 @@ function isa_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(v), @nospec end add_tfunc(isa, 2, 2, isa_tfunc, 1) -function isa_nothrow(@specialize(𝕃::AbstractLattice), @nospecialize(obj), @nospecialize(typ)) +@nospecs function isa_nothrow(𝕃::AbstractLattice, obj, typ) ⊑ = Core.Compiler.:⊑(𝕃) return typ ⊑ Type end -function subtype_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(a), @nospecialize(b)) +@nospecs function subtype_tfunc(𝕃::AbstractLattice, a, b) a, isexact_a = instanceof_tfunc(a) b, isexact_b = instanceof_tfunc(b) if !has_free_typevars(a) && !has_free_typevars(b) @@ -789,7 +821,7 @@ function subtype_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(a), @no end add_tfunc(<:, 2, 2, subtype_tfunc, 10) -function subtype_nothrow(@specialize(𝕃::AbstractLattice), @nospecialize(lty), @nospecialize(rty)) +@nospecs function subtype_nothrow(𝕃::AbstractLattice, lty, rty) ⊑ = Core.Compiler.:⊑(𝕃) return lty ⊑ Type && rty ⊑ Type end @@ -865,7 +897,6 @@ function getfield_boundscheck(argtypes::Vector{Any}) # ::Union{Bool, Nothing} end function getfield_nothrow(argtypes::Vector{Any}, boundscheck::Union{Bool,Nothing}=getfield_boundscheck(argtypes)) - @specialize boundscheck boundscheck === nothing && return false ordering = Const(:not_atomic) if length(argtypes) == 3 @@ -890,7 +921,7 @@ function getfield_nothrow(argtypes::Vector{Any}, boundscheck::Union{Bool,Nothing return false end end -function getfield_nothrow(@nospecialize(s00), @nospecialize(name), boundscheck::Bool) +@nospecs function getfield_nothrow(s00, name, boundscheck::Bool) # If we don't have boundscheck off and don't know the field, don't even bother if boundscheck isa(name, Const) || return false @@ -951,15 +982,13 @@ function getfield_nothrow(@nospecialize(s00), @nospecialize(name), boundscheck:: return false end -function getfield_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(s00), - @nospecialize(name), @nospecialize(boundscheck_or_order)) +@nospecs function getfield_tfunc(𝕃::AbstractLattice, s00, name, boundscheck_or_order) t = isvarargtype(boundscheck_or_order) ? unwrapva(boundscheck_or_order) : widenconst(boundscheck_or_order) hasintersect(t, Symbol) || hasintersect(t, Bool) || return Bottom return getfield_tfunc(𝕃, s00, name) end -function getfield_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(s00), - @nospecialize(name), @nospecialize(order), @nospecialize(boundscheck)) +@nospecs function getfield_tfunc(𝕃::AbstractLattice, s00, name, order, boundscheck) hasintersect(widenconst(order), Symbol) || return Bottom if isvarargtype(boundscheck) t = unwrapva(boundscheck) @@ -969,9 +998,7 @@ function getfield_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(s00), end return getfield_tfunc(𝕃, s00, name) end -function getfield_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(s00), @nospecialize(name)) - return _getfield_tfunc(𝕃, s00, name, false) -end +@nospecs getfield_tfunc(𝕃::AbstractLattice, s00, name) = _getfield_tfunc(𝕃, s00, name, false) function _getfield_fieldindex(s::DataType, name::Const) nv = name.val @@ -999,34 +1026,34 @@ function _getfield_tfunc_const(@nospecialize(sv), name::Const) return nothing end -function _getfield_tfunc(@specialize(lattice::InferenceLattice), @nospecialize(s00), @nospecialize(name), setfield::Bool) +@nospecs function _getfield_tfunc(𝕃::InferenceLattice, s00, name, setfield::Bool) if isa(s00, LimitedAccuracy) # This will error, but it's better than duplicating the error here s00 = widenconst(s00) end - return _getfield_tfunc(widenlattice(lattice), s00, name, setfield) + return _getfield_tfunc(widenlattice(𝕃), s00, name, setfield) end -function _getfield_tfunc(@specialize(lattice::OptimizerLattice), @nospecialize(s00), @nospecialize(name), setfield::Bool) +@nospecs function _getfield_tfunc(𝕃::OptimizerLattice, s00, name, setfield::Bool) # If undef, that's a Union, but that doesn't affect the rt when tmerged # into the unwrapped result. isa(s00, MaybeUndef) && (s00 = s00.typ) - return _getfield_tfunc(widenlattice(lattice), s00, name, setfield) + return _getfield_tfunc(widenlattice(𝕃), s00, name, setfield) end -function _getfield_tfunc(@specialize(lattice::AnyConditionalsLattice), @nospecialize(s00), @nospecialize(name), setfield::Bool) +@nospecs function _getfield_tfunc(𝕃::AnyConditionalsLattice, s00, name, setfield::Bool) if isa(s00, AnyConditional) return Bottom # Bool has no fields end - return _getfield_tfunc(widenlattice(lattice), s00, name, setfield) + return _getfield_tfunc(widenlattice(𝕃), s00, name, setfield) end -function _getfield_tfunc(@specialize(𝕃::AnyMustAliasesLattice), @nospecialize(s00), @nospecialize(name), setfield::Bool) +@nospecs function _getfield_tfunc(𝕃::AnyMustAliasesLattice, s00, name, setfield::Bool) s00 = widenmustalias(s00) return _getfield_tfunc(widenlattice(𝕃), s00, name, setfield) end -function _getfield_tfunc(@specialize(lattice::PartialsLattice), @nospecialize(s00), @nospecialize(name), setfield::Bool) +@nospecs function _getfield_tfunc(𝕃::PartialsLattice, s00, name, setfield::Bool) if isa(s00, PartialStruct) s = widenconst(s00) sty = unwrap_unionall(s)::DataType @@ -1038,11 +1065,10 @@ function _getfield_tfunc(@specialize(lattice::PartialsLattice), @nospecialize(s0 end s00 = s end - - return _getfield_tfunc(widenlattice(lattice), s00, name, setfield) + return _getfield_tfunc(widenlattice(𝕃), s00, name, setfield) end -function _getfield_tfunc(lattice::ConstsLattice, @nospecialize(s00), @nospecialize(name), setfield::Bool) +@nospecs function _getfield_tfunc(𝕃::ConstsLattice, s00, name, setfield::Bool) if isa(s00, Const) sv = s00.val if isa(name, Const) @@ -1059,14 +1085,14 @@ function _getfield_tfunc(lattice::ConstsLattice, @nospecialize(s00), @nospeciali end s00 = widenconst(s00) end - return _getfield_tfunc(widenlattice(lattice), s00, name, setfield) + return _getfield_tfunc(widenlattice(𝕃), s00, name, setfield) end -function _getfield_tfunc(lattice::JLTypeLattice, @nospecialize(s00), @nospecialize(name), setfield::Bool) +@nospecs function _getfield_tfunc(𝕃::JLTypeLattice, s00, name, setfield::Bool) s = unwrap_unionall(s00) if isa(s, Union) - return tmerge(_getfield_tfunc(lattice, rewrap_unionall(s.a, s00), name, setfield), - _getfield_tfunc(lattice, rewrap_unionall(s.b, s00), name, setfield)) + return tmerge(_getfield_tfunc(𝕃, rewrap_unionall(s.a, s00), name, setfield), + _getfield_tfunc(𝕃, rewrap_unionall(s.b, s00), name, setfield)) end if isType(s) if isconstType(s) @@ -1114,7 +1140,7 @@ function _getfield_tfunc(lattice::JLTypeLattice, @nospecialize(s00), @nospeciali if !(_ts <: Tuple) return Any end - return _getfield_tfunc(lattice, _ts, name, setfield) + return _getfield_tfunc(𝕃, _ts, name, setfield) end ftypes = datatype_fieldtypes(s) nf = length(ftypes) @@ -1161,7 +1187,7 @@ function _getfield_tfunc(lattice::JLTypeLattice, @nospecialize(s00), @nospeciali return rewrap_unionall(R, s00) end -function getfield_notundefined(@nospecialize(typ0), @nospecialize(name)) +@nospecs function getfield_notundefined(typ0, name) typ = unwrap_unionall(typ0) if isa(typ, Union) return getfield_notundefined(rewrap_unionall(typ.a, typ0), name) && @@ -1212,15 +1238,13 @@ function is_undefref_fieldtype(@nospecialize ftyp) return !has_free_typevars(ftyp) && !allocatedinline(ftyp) end -function setfield!_tfunc(@specialize(𝕃::AbstractLattice), o, f, v, order) - @nospecialize o f v order +@nospecs function setfield!_tfunc(𝕃::AbstractLattice, o, f, v, order) if !isvarargtype(order) hasintersect(widenconst(order), Symbol) || return Bottom end return setfield!_tfunc(𝕃, o, f, v) end -function setfield!_tfunc(@specialize(𝕃::AbstractLattice), o, f, v) - @nospecialize o f v +@nospecs function setfield!_tfunc(𝕃::AbstractLattice, o, f, v) mutability_errorcheck(o) || return Bottom ft = _getfield_tfunc(fallback_lattice, o, f, true) ft === Bottom && return Bottom @@ -1241,13 +1265,11 @@ function _mutability_errorcheck(@nospecialize objt0) return true end -function setfield!_nothrow(@specialize(𝕃::AbstractLattice), s00, name, v, order) - @nospecialize s00 name v order +@nospecs function setfield!_nothrow(𝕃::AbstractLattice, s00, name, v, order) order === Const(:not_atomic) || return false # currently setfield!_nothrow is assuming not atomic return setfield!_nothrow(𝕃, s00, name, v) end -function setfield!_nothrow(@specialize(𝕃::AbstractLattice), s00, name, v) - @nospecialize s00 name v +@nospecs function setfield!_nothrow(𝕃::AbstractLattice, s00, name, v) ⊑ = Core.Compiler.:⊑(𝕃) s0 = widenconst(s00) s = unwrap_unionall(s0) @@ -1270,20 +1292,16 @@ function setfield!_nothrow(@specialize(𝕃::AbstractLattice), s00, name, v) return false end -function swapfield!_tfunc(@specialize(𝕃::AbstractLattice), o, f, v, order) - @nospecialize o f v order +@nospecs function swapfield!_tfunc(𝕃::AbstractLattice, o, f, v, order) return getfield_tfunc(𝕃, o, f) end -function swapfield!_tfunc(@specialize(𝕃::AbstractLattice), o, f, v) - @nospecialize o f v +@nospecs function swapfield!_tfunc(𝕃::AbstractLattice, o, f, v) return getfield_tfunc(𝕃, o, f) end -function modifyfield!_tfunc(@specialize(𝕃::AbstractLattice), o, f, op, v, order) - @nospecialize o f op v order +@nospecs function modifyfield!_tfunc(𝕃::AbstractLattice, o, f, op, v, order) return modifyfield!_tfunc(𝕃, o, f, op, v) end -function modifyfield!_tfunc(@specialize(𝕃::AbstractLattice), o, f, op, v) - @nospecialize o f op v +@nospecs function modifyfield!_tfunc(𝕃::AbstractLattice, o, f, op, v) T = _fieldtype_tfunc(𝕃, o, f, isconcretetype(o)) T === Bottom && return Bottom PT = Const(Pair) @@ -1319,16 +1337,13 @@ function abstract_modifyfield!(interp::AbstractInterpreter, argtypes::Vector{Any end return CallMeta(RT, Effects(), info) end -function replacefield!_tfunc(@specialize(𝕃::AbstractLattice), o, f, x, v, success_order, failure_order) - @nospecialize o f x v success_order failure_order +@nospecs function replacefield!_tfunc(𝕃::AbstractLattice, o, f, x, v, success_order, failure_order) return replacefield!_tfunc(𝕃, o, f, x, v) end -function replacefield!_tfunc(@specialize(𝕃::AbstractLattice), o, f, x, v, success_order) - @nospecialize o f x v success_order +@nospecs function replacefield!_tfunc(𝕃::AbstractLattice, o, f, x, v, success_order) return replacefield!_tfunc(𝕃, o, f, x, v) end -function replacefield!_tfunc(@specialize(𝕃::AbstractLattice), o, f, x, v) - @nospecialize o f x v +@nospecs function replacefield!_tfunc(𝕃::AbstractLattice, o, f, x, v) T = _fieldtype_tfunc(𝕃, o, f, isconcretetype(o)) T === Bottom && return Bottom PT = Const(ccall(:jl_apply_cmpswap_type, Any, (Any,), T) where T) @@ -1344,7 +1359,7 @@ add_tfunc(swapfield!, 3, 4, swapfield!_tfunc, 3) add_tfunc(modifyfield!, 4, 5, modifyfield!_tfunc, 3) add_tfunc(replacefield!, 4, 6, replacefield!_tfunc, 3) -function fieldtype_nothrow(@specialize(𝕃::AbstractLattice), @nospecialize(s0), @nospecialize(name)) +@nospecs function fieldtype_nothrow(𝕃::AbstractLattice, s0, name) s0 === Bottom && return true # unreachable ⊑ = Core.Compiler.:⊑(𝕃) if s0 === Any || s0 === Type || DataType ⊑ s0 || UnionAll ⊑ s0 @@ -1402,12 +1417,10 @@ function _fieldtype_nothrow(@nospecialize(s), exact::Bool, name::Const) return true end -function fieldtype_tfunc(@specialize(𝕃::AbstractLattice), s0, name, boundscheck) - @nospecialize s0 name boundscheck +@nospecs function fieldtype_tfunc(𝕃::AbstractLattice, s0, name, boundscheck) return fieldtype_tfunc(𝕃, s0, name) end -function fieldtype_tfunc(@specialize(𝕃::AbstractLattice), s0, name) - @nospecialize s0 name +@nospecs function fieldtype_tfunc(𝕃::AbstractLattice, s0, name) s0 = widenmustalias(s0) if s0 === Bottom return Bottom @@ -1436,8 +1449,7 @@ function fieldtype_tfunc(@specialize(𝕃::AbstractLattice), s0, name) return _fieldtype_tfunc(𝕃, s, name, exact) end -function _fieldtype_tfunc(@specialize(𝕃::AbstractLattice), s, name, exact::Bool) - @nospecialize s0 name +@nospecs function _fieldtype_tfunc(𝕃::AbstractLattice, s, name, exact::Bool) exact = exact && !has_free_typevars(s) u = unwrap_unionall(s) if isa(u, Union) @@ -1545,7 +1557,7 @@ valid_tparam_type(T::DataType) = valid_typeof_tparam(T) valid_tparam_type(U::Union) = valid_tparam_type(U.a) && valid_tparam_type(U.b) valid_tparam_type(U::UnionAll) = valid_tparam_type(unwrap_unionall(U)) -function apply_type_nothrow(@specialize(lattice::AbstractLattice), argtypes::Vector{Any}, @nospecialize(rt)) +function apply_type_nothrow(𝕃::AbstractLattice, argtypes::Vector{Any}, @nospecialize(rt)) rt === Type && return false length(argtypes) >= 1 || return false headtypetype = argtypes[1] @@ -1564,7 +1576,7 @@ function apply_type_nothrow(@specialize(lattice::AbstractLattice), argtypes::Vec for i = 2:length(argtypes) isa(u, UnionAll) || return false ai = widenconditional(argtypes[i]) - if ⊑(lattice, ai, TypeVar) || ai === DataType + if ⊑(𝕃, ai, TypeVar) || ai === DataType # We don't know anything about the bounds of this typevar, but as # long as the UnionAll is not constrained, that's ok. if !(u.var.lb === Union{} && u.var.ub === Any) @@ -1606,8 +1618,7 @@ const _tvarnames = Symbol[:_A, :_B, :_C, :_D, :_E, :_F, :_G, :_H, :_I, :_J, :_K, :_N, :_O, :_P, :_Q, :_R, :_S, :_T, :_U, :_V, :_W, :_X, :_Y, :_Z] # TODO: handle e.g. apply_type(T, R::Union{Type{Int32},Type{Float64}}) -function apply_type_tfunc(@specialize(𝕃::AbstractLattice), headtypetype, args...) - @nospecialize headtypetype args +@nospecs function apply_type_tfunc(𝕃::AbstractLattice, headtypetype, args...) headtypetype = widenslotwrapper(headtypetype) if isa(headtypetype, Const) headtype = headtypetype.val @@ -1772,7 +1783,7 @@ add_tfunc(apply_type, 1, INT_INF, apply_type_tfunc, 10) # convert the dispatch tuple type argtype to the real (concrete) type of # the tuple of those values -function tuple_tfunc(@specialize(𝕃::AbstractLattice), argtypes::Vector{Any}) +function tuple_tfunc(𝕃::AbstractLattice, argtypes::Vector{Any}) argtypes = anymap(widenslotwrapper, argtypes) all_are_const = true for i in 1:length(argtypes) @@ -1782,7 +1793,7 @@ function tuple_tfunc(@specialize(𝕃::AbstractLattice), argtypes::Vector{Any}) end end if all_are_const - return Const(ntuple(i -> argtypes[i].val, length(argtypes))) + return Const(ntuple(i::Int->argtypes[i].val, length(argtypes))) end params = Vector{Any}(undef, length(argtypes)) anyinfo = false @@ -1827,12 +1838,10 @@ function tuple_tfunc(@specialize(𝕃::AbstractLattice), argtypes::Vector{Any}) return anyinfo ? PartialStruct(typ, argtypes) : typ end -function arrayref_tfunc(@specialize(𝕃::AbstractLattice), boundscheck, ary, idxs...) - @nospecialize boundscheck ary idxs +@nospecs function arrayref_tfunc(𝕃::AbstractLattice, boundscheck, ary, idxs...) return _arrayref_tfunc(𝕃, boundscheck, ary, idxs) end -function _arrayref_tfunc(@specialize(𝕃::AbstractLattice), boundscheck, ary, idxs::Tuple) - @nospecialize boundscheck ary idxs +@nospecs function _arrayref_tfunc(𝕃::AbstractLattice, boundscheck, ary, @nospecialize idxs::Tuple) isempty(idxs) && return Bottom array_builtin_common_errorcheck(boundscheck, ary, idxs) || return Bottom return array_elmtype(ary) @@ -1840,15 +1849,13 @@ end add_tfunc(arrayref, 3, INT_INF, arrayref_tfunc, 20) add_tfunc(const_arrayref, 3, INT_INF, arrayref_tfunc, 20) -function arrayset_tfunc(@specialize(𝕃::AbstractLattice), boundscheck, ary, item, idxs...) - @nospecialize boundscheck ary item idxs +@nospecs function arrayset_tfunc(𝕃::AbstractLattice, boundscheck, ary, item, idxs...) hasintersect(widenconst(item), _arrayref_tfunc(𝕃, boundscheck, ary, idxs)) || return Bottom return ary end add_tfunc(arrayset, 4, INT_INF, arrayset_tfunc, 20) -function array_builtin_common_errorcheck(@nospecialize(boundscheck), @nospecialize(ary), - @nospecialize idxs::Tuple) +@nospecs function array_builtin_common_errorcheck(boundscheck, ary, @nospecialize idxs::Tuple) hasintersect(widenconst(boundscheck), Bool) || return false hasintersect(widenconst(ary), Array) || return false for i = 1:length(idxs) @@ -1875,8 +1882,7 @@ function array_elmtype(@nospecialize ary) return Any end -function _opaque_closure_tfunc(@specialize(𝕃::AbstractLattice), @nospecialize(arg), @nospecialize(lb), @nospecialize(ub), - @nospecialize(source), env::Vector{Any}, linfo::MethodInstance) +@nospecs function _opaque_closure_tfunc(𝕃::AbstractLattice, arg, lb, ub, source, env::Vector{Any}, linfo::MethodInstance) argt, argt_exact = instanceof_tfunc(arg) lbt, lb_exact = instanceof_tfunc(lb) if !lb_exact @@ -1924,8 +1930,7 @@ function array_builtin_common_nothrow(argtypes::Vector{Any}, first_idx_idx::Int) return false end -function array_builtin_common_typecheck( - @nospecialize(boundscheck), @nospecialize(arytype), +@nospecs function array_builtin_common_typecheck(boundscheck, arytype, argtypes::Vector{Any}, first_idx_idx::Int) (boundscheck ⊑ Bool && arytype ⊑ Array) || return false for i = first_idx_idx:length(argtypes) @@ -1934,7 +1939,7 @@ function array_builtin_common_typecheck( return true end -function arrayset_typecheck(@nospecialize(arytype), @nospecialize(elmtype)) +@nospecs function arrayset_typecheck(arytype, elmtype) # Check that we can determine the element type arytype = widenconst(arytype) isa(arytype, DataType) || return false @@ -1946,7 +1951,7 @@ function arrayset_typecheck(@nospecialize(arytype), @nospecialize(elmtype)) end # Query whether the given builtin is guaranteed not to throw given the argtypes -function _builtin_nothrow(@specialize(𝕃::AbstractLattice), @nospecialize(f), argtypes::Vector{Any}, @nospecialize(rt)) +@nospecs function _builtin_nothrow(𝕃::AbstractLattice, f, argtypes::Vector{Any}, rt) ⊑ = Core.Compiler.:⊑(𝕃) if f === arrayset array_builtin_common_nothrow(argtypes, 4) || return false @@ -2120,7 +2125,7 @@ const _SPECIAL_BUILTINS = Any[ Core._apply_iterate, ] -function isdefined_effects(@specialize(𝕃::AbstractLattice), argtypes::Vector{Any}) +function isdefined_effects(𝕃::AbstractLattice, argtypes::Vector{Any}) # consistent if the first arg is immutable na = length(argtypes) na == 0 && return EFFECTS_THROWS @@ -2183,7 +2188,7 @@ function getglobal_effects(argtypes::Vector{Any}, @nospecialize(rt)) return Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly) end -function builtin_effects(@specialize(𝕃::AbstractLattice), f::Builtin, argtypes::Vector{Any}, @nospecialize(rt)) +function builtin_effects(𝕃::AbstractLattice, @nospecialize(f::Builtin), argtypes::Vector{Any}, @nospecialize(rt)) if isa(f, IntrinsicFunction) return intrinsic_effects(f, argtypes) end @@ -2221,10 +2226,10 @@ function builtin_effects(@specialize(𝕃::AbstractLattice), f::Builtin, argtype end end -function builtin_nothrow(@specialize(lattice::AbstractLattice), @nospecialize(f), argtypes::Vector{Any}, @nospecialize(rt)) +function builtin_nothrow(𝕃::AbstractLattice, @nospecialize(f), argtypes::Vector{Any}, @nospecialize(rt)) rt === Bottom && return false contains_is(_PURE_BUILTINS, f) && return true - return _builtin_nothrow(lattice, f, argtypes, rt) + return _builtin_nothrow(𝕃, f, argtypes, rt) end function builtin_tfunction(interp::AbstractInterpreter, @nospecialize(f), argtypes::Vector{Any}, @@ -2234,7 +2239,7 @@ function builtin_tfunction(interp::AbstractInterpreter, @nospecialize(f), argtyp return tuple_tfunc(𝕃ᵢ, argtypes) end if isa(f, IntrinsicFunction) - if is_pure_intrinsic_infer(f) && _all(@nospecialize(a) -> isa(a, Const), argtypes) + if is_pure_intrinsic_infer(f) && all(@nospecialize(a) -> isa(a, Const), argtypes) argvals = anymap(@nospecialize(a) -> (a::Const).val, argtypes) try return Const(f(argvals...)) @@ -2282,11 +2287,11 @@ end # Query whether the given intrinsic is nothrow -_iszero(x) = x === Intrinsics.xor_int(x, x) -_isneg1(x) = _iszero(Intrinsics.not_int(x)) -_istypemin(x) = !_iszero(x) && Intrinsics.neg_int(x) === x +_iszero(@nospecialize x) = x === Intrinsics.xor_int(x, x) +_isneg1(@nospecialize x) = _iszero(Intrinsics.not_int(x)) +_istypemin(@nospecialize x) = !_iszero(x) && Intrinsics.neg_int(x) === x -function intrinsic_nothrow(f::IntrinsicFunction, argtypes::Array{Any, 1}) +function intrinsic_nothrow(f::IntrinsicFunction, argtypes::Vector{Any}) # First check that we have the correct number of arguments iidx = Int(reinterpret(Int32, f::IntrinsicFunction)) + 1 if iidx < 1 || iidx > length(T_IFUNC) @@ -2376,8 +2381,11 @@ function is_pure_intrinsic_infer(f::IntrinsicFunction) end # whether `f` is effect free if nothrow -intrinsic_effect_free_if_nothrow(f) = f === Intrinsics.pointerref || - f === Intrinsics.have_fma || is_pure_intrinsic_infer(f) +function intrinsic_effect_free_if_nothrow(@nospecialize f) + return f === Intrinsics.pointerref || + f === Intrinsics.have_fma || + is_pure_intrinsic_infer(f) +end function intrinsic_effects(f::IntrinsicFunction, argtypes::Vector{Any}) if f === Intrinsics.llvmcall @@ -2469,11 +2477,11 @@ function global_order_nothrow(@nospecialize(o), loading::Bool, storing::Bool) end return false end -function getglobal_nothrow(@nospecialize(M), @nospecialize(s), @nospecialize(o)) +@nospecs function getglobal_nothrow(M, s, o) global_order_nothrow(o, #=loading=#true, #=storing=#false) || return false return getglobal_nothrow(M, s) end -function getglobal_nothrow(@nospecialize(M), @nospecialize(s)) +@nospecs function getglobal_nothrow(M, s) if M isa Const && s isa Const M, s = M.val, s.val if M isa Module && s isa Symbol @@ -2482,8 +2490,7 @@ function getglobal_nothrow(@nospecialize(M), @nospecialize(s)) end return false end -function getglobal_tfunc(@specialize(𝕃::AbstractLattice), M, s, order=Symbol) - @nospecialize M s order +@nospecs function getglobal_tfunc(𝕃::AbstractLattice, M, s, order=Symbol) if M isa Const && s isa Const M, s = M.val, s.val if M isa Module && s isa Symbol @@ -2495,8 +2502,7 @@ function getglobal_tfunc(@specialize(𝕃::AbstractLattice), M, s, order=Symbol) end return Any end -function setglobal!_tfunc(@specialize(𝕃::AbstractLattice), M, s, v, order=Symbol) - @nospecialize M s v order +@nospecs function setglobal!_tfunc(𝕃::AbstractLattice, M, s, v, order=Symbol) if !(hasintersect(widenconst(M), Module) && hasintersect(widenconst(s), Symbol)) return Bottom end @@ -2504,11 +2510,11 @@ function setglobal!_tfunc(@specialize(𝕃::AbstractLattice), M, s, v, order=Sym end add_tfunc(getglobal, 2, 3, getglobal_tfunc, 1) add_tfunc(setglobal!, 3, 4, setglobal!_tfunc, 3) -function setglobal!_nothrow(@nospecialize(M), @nospecialize(s), @nospecialize(newty), @nospecialize(o)) +@nospecs function setglobal!_nothrow(M, s, newty, o) global_order_nothrow(o, #=loading=#false, #=storing=#true) || return false return setglobal!_nothrow(M, s, newty) end -function setglobal!_nothrow(@nospecialize(M), @nospecialize(s), @nospecialize(newty)) +@nospecs function setglobal!_nothrow(M, s, newty) if M isa Const && s isa Const M, s = M.val, s.val if isa(M, Module) && isa(s, Symbol) @@ -2526,7 +2532,7 @@ function global_assignment_nothrow(M::Module, s::Symbol, @nospecialize(newty)) return false end -function get_binding_type_effect_free(@nospecialize(M), @nospecialize(s)) +@nospecs function get_binding_type_effect_free(M, s) if M isa Const && s isa Const M, s = M.val, s.val if M isa Module && s isa Symbol @@ -2535,8 +2541,7 @@ function get_binding_type_effect_free(@nospecialize(M), @nospecialize(s)) end return false end -function get_binding_type_tfunc(@specialize(𝕃::AbstractLattice), M, s) - @nospecialize M s +@nospecs function get_binding_type_tfunc(𝕃::AbstractLattice, M, s) if get_binding_type_effect_free(M, s) return Const(Core.get_binding_type((M::Const).val, (s::Const).val)) end @@ -2544,7 +2549,7 @@ function get_binding_type_tfunc(@specialize(𝕃::AbstractLattice), M, s) end add_tfunc(Core.get_binding_type, 2, 2, get_binding_type_tfunc, 0) -function get_binding_type_nothrow(@specialize(𝕃::AbstractLattice), @nospecialize(M), @nospecialize(s)) +@nospecs function get_binding_type_nothrow(𝕃::AbstractLattice, M, s) ⊑ = Core.Compiler.:⊑(𝕃) return M ⊑ Module && s ⊑ Symbol end @@ -2632,5 +2637,3 @@ function _new_array_nothrow(@nospecialize(atype), ndims::Int, dims::Vector{Csize (Ptr{Csize_t}, Ptr{Csize_t}, UInt32, Ptr{Csize_t}, Csize_t), #=nel=#RefValue{Csize_t}(), #=tot=#RefValue{Csize_t}(), ndims, dims, elsz) == 0 end - -@specialize From 13157eb6d352f3363a63666b2b986d4e950e22b4 Mon Sep 17 00:00:00 2001 From: Kevin Bonham Date: Sat, 10 Dec 2022 04:16:11 -0500 Subject: [PATCH 096/387] add getindex(nt::NamedTuple, ::Colon) method (#47842) --- base/namedtuple.jl | 1 + test/namedtuple.jl | 1 + 2 files changed, 2 insertions(+) diff --git a/base/namedtuple.jl b/base/namedtuple.jl index c994cd977be082..6ed09a99e11ec0 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -135,6 +135,7 @@ firstindex(t::NamedTuple) = 1 lastindex(t::NamedTuple) = nfields(t) getindex(t::NamedTuple, i::Int) = getfield(t, i) getindex(t::NamedTuple, i::Symbol) = getfield(t, i) +getindex(t::NamedTuple, ::Colon) = t @inline getindex(t::NamedTuple, idxs::Tuple{Vararg{Symbol}}) = NamedTuple{idxs}(t) @inline getindex(t::NamedTuple, idxs::AbstractVector{Symbol}) = NamedTuple{Tuple(idxs)}(t) indexed_iterate(t::NamedTuple, i::Int, state=1) = (getfield(t, i), i+1) diff --git a/test/namedtuple.jl b/test/namedtuple.jl index 82efed0a080df1..816a0b17a79c27 100644 --- a/test/namedtuple.jl +++ b/test/namedtuple.jl @@ -26,6 +26,7 @@ @test (x=4, y=5, z=6)[[:x, :y]] == (x=4, y=5) @test (x=4, y=5, z=6)[[:x]] == (x=4,) @test (x=4, y=5, z=6)[()] == NamedTuple() +@test (x=4, y=5, z=6)[:] == (x=4, y=5, z=6) @test NamedTuple()[()] == NamedTuple() @test_throws ErrorException (x=4, y=5, z=6).a @test_throws BoundsError (a=2,)[0] From 05328b84c4f379ce1d855219fa8c6d90c5993e5e Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Sat, 10 Dec 2022 17:47:23 +0600 Subject: [PATCH 097/387] Fix typo (#47858) More than one lock may be taken --- base/threadingconstructs.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/threadingconstructs.jl b/base/threadingconstructs.jl index 271d6ea9f7664b..643cd95e57ebf3 100644 --- a/base/threadingconstructs.jl +++ b/base/threadingconstructs.jl @@ -179,7 +179,7 @@ unsynchronized memory accesses may result in undefined behavior. For example, the above conditions imply that: -- The lock taken in an iteration *must* be released within the same iteration. +- A lock taken in an iteration *must* be released within the same iteration. - Communicating between iterations using blocking primitives like `Channel`s is incorrect. - Write only to locations not shared across iterations (unless a lock or atomic operation is used). From 5a6c80828698d58405ca38b27628d426665324b5 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Sat, 10 Dec 2022 17:24:55 +0100 Subject: [PATCH 098/387] Fix physical_memory exports. (#47859) --- base/sysinfo.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/sysinfo.jl b/base/sysinfo.jl index be11d5fb1cc989..b885d88a5f3cb6 100644 --- a/base/sysinfo.jl +++ b/base/sysinfo.jl @@ -20,8 +20,8 @@ export BINDIR, loadavg, free_memory, total_memory, - physical_free_memory, - physical_total_memory, + free_physical_memory, + total_physical_memory, isapple, isbsd, isdragonfly, From 0457fde5a625b516ee3807e9f7420d9784fd9ff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Bar=C4=87?= Date: Sun, 11 Dec 2022 00:55:54 +0100 Subject: [PATCH 099/387] deps/llvm.mk: completely disable building LLVM bindings (#47862) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit without setting -DLLVM_ENABLE_BINDINGS=OFF LLVM will attempt to build OCaml bindings and install them onto the running system which will cause the build to fail Signed-off-by: Maciej Barć --- deps/llvm.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/llvm.mk b/deps/llvm.mk index 78d037ec126d09..81dcff1ce4c84f 100644 --- a/deps/llvm.mk +++ b/deps/llvm.mk @@ -101,7 +101,7 @@ endif 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 +LLVM_CMAKE += -DLLVM_BINDINGS_LIST="" -DLLVM_ENABLE_BINDINGS=OFF -DLLVM_INCLUDE_DOCS=Off -DLLVM_ENABLE_TERMINFO=Off -DHAVE_HISTEDIT_H=Off -DHAVE_LIBEDIT=Off ifeq ($(LLVM_ASSERTIONS), 1) LLVM_CMAKE += -DLLVM_ENABLE_ASSERTIONS:BOOL=ON endif # LLVM_ASSERTIONS From 704e1173879719a165b6896ecb8688baec48c449 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Sun, 11 Dec 2022 15:52:30 +0900 Subject: [PATCH 100/387] inference: simplify `is_const_prop_profitable_arg` (#47857) We have actually forwarded `PartialStruct` always. --- base/compiler/abstractinterpretation.jl | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 2160999f30c9e9..0cabee6631f052 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1144,10 +1144,14 @@ end function is_const_prop_profitable_arg(@nospecialize(arg)) # have new information from argtypes that wasn't available from the signature if isa(arg, PartialStruct) - for b in arg.fields - isconstType(b) && return true - is_const_prop_profitable_arg(b) && return true - end + return true # might be a bit aggressive, may want to enable some check like follows: + # for i = 1:length(arg.fields) + # fld = arg.fields[i] + # isconstType(fld) && return true + # is_const_prop_profitable_arg(fld) && return true + # fld ⊏ fieldtype(arg.typ, i) && return true + # end + # return false end isa(arg, PartialOpaque) && return true isa(arg, Const) || return true From 41dbc1c9e394dacb8e64e2276b0c019b03e618ac Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Sun, 11 Dec 2022 15:52:46 +0900 Subject: [PATCH 101/387] don't mark const-prop' profitability for `PartialTypeVar` (#47856) We should not mark it as const-prop' propfitable since it's not forwardable anyway. --- base/compiler/abstractinterpretation.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 0cabee6631f052..3c722834fd88cd 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1154,6 +1154,7 @@ function is_const_prop_profitable_arg(@nospecialize(arg)) # return false end isa(arg, PartialOpaque) && return true + isa(arg, PartialTypeVar) && return false # this isn't forwardable isa(arg, Const) || return true val = arg.val # don't consider mutable values useful constants From 827b16552f843f1123a2fb39152657f0e0a48055 Mon Sep 17 00:00:00 2001 From: Jorge Fernandez-de-Cossio-Diaz Date: Sun, 11 Dec 2022 19:28:19 +0100 Subject: [PATCH 102/387] Explain Reentrant in RentrantLock docstring (#47817) --- base/lock.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/lock.jl b/base/lock.jl index 8a15d3f95b2391..1321b0c0f48c7d 100644 --- a/base/lock.jl +++ b/base/lock.jl @@ -7,8 +7,8 @@ const ThreadSynchronizer = GenericCondition{Threads.SpinLock} ReentrantLock() Creates a re-entrant lock for synchronizing [`Task`](@ref)s. The same task can -acquire the lock as many times as required. Each [`lock`](@ref) must be matched -with an [`unlock`](@ref). +acquire the lock as many times as required (this is what the "Reentrant" part +of the name means). Each [`lock`](@ref) must be matched with an [`unlock`](@ref). Calling 'lock' will also inhibit running of finalizers on that thread until the corresponding 'unlock'. Use of the standard lock pattern illustrated below From f08232996dca3b57c6f21b8ccd2a63de6fd72128 Mon Sep 17 00:00:00 2001 From: Oscar Smith Date: Sun, 11 Dec 2022 14:24:22 -0500 Subject: [PATCH 103/387] Dict: decrement `h.ndel` when overwriting deleted entry (#47825) Fixes https://github.com/JuliaLang/julia/issues/47823 by decrementing `h.ndel` when overwriting deleted entries as well as making sure to not add unnecessary tombstones when deleting. Co-authored-by: Christian Rorvik Co-authored-by: Petr Vana Date: Sun, 11 Dec 2022 15:34:57 -0500 Subject: [PATCH 104/387] Fix missing GC root in Symbol construction (#47865) The `Symbol` constructor in boot.jl was not using the unsafe_convert mechanism, becuase it is unavailable at this point in bootstrap. However, it was also not GC-rooting the string some other way, resulting in potential memory corruption. Fix that by manually inlining the :foreigncall and setting up the root appropriately. --- base/boot.jl | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/base/boot.jl b/base/boot.jl index 77760acade42bc..33b2cd07688ad6 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -503,16 +503,19 @@ Array{T}(A::AbstractArray{S,N}) where {T,N,S} = Array{T,N}(A) AbstractArray{T}(A::AbstractArray{S,N}) where {T,S,N} = AbstractArray{T,N}(A) # primitive Symbol constructors + +## Helper for proper GC rooting without unsafe_convert +eval(Core, quote + _Symbol(ptr::Ptr{UInt8}, sz::Int, root::Any) = $(Expr(:foreigncall, QuoteNode(:jl_symbol_n), + Ref{Symbol}, svec(Ptr{UInt8}, Int), 0, QuoteNode(:ccall), :ptr, :sz, :root)) +end) + function Symbol(s::String) @_foldable_meta - return ccall(:jl_symbol_n, Ref{Symbol}, (Ptr{UInt8}, Int), - ccall(:jl_string_ptr, Ptr{UInt8}, (Any,), s), - sizeof(s)) + return _Symbol(ccall(:jl_string_ptr, Ptr{UInt8}, (Any,), s), sizeof(s), s) end function Symbol(a::Array{UInt8,1}) - return ccall(:jl_symbol_n, Ref{Symbol}, (Ptr{UInt8}, Int), - ccall(:jl_array_ptr, Ptr{UInt8}, (Any,), a), - Intrinsics.arraylen(a)) + return _Symbol(ccall(:jl_array_ptr, Ptr{UInt8}, (Any,), a), Intrinsics.arraylen(a), a) end Symbol(s::Symbol) = s From 4ff707227f5d362cfae0a1d3ba48d0a294cd73c0 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Mon, 12 Dec 2022 12:16:54 +0900 Subject: [PATCH 105/387] inference: normalize lattice naming (#47851) --- base/compiler/abstractinterpretation.jl | 70 +++----- base/compiler/abstractlattice.jl | 213 ++++++++++++++++-------- base/compiler/inferenceresult.jl | 14 +- base/compiler/tfuncs.jl | 4 +- base/compiler/typelimits.jl | 4 +- base/compiler/typeutils.jl | 16 +- base/compiler/utilities.jl | 6 + test/compiler/AbstractInterpreter.jl | 11 +- 8 files changed, 186 insertions(+), 152 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 3c722834fd88cd..6e24f3bae2de56 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -917,6 +917,8 @@ struct ConstCallResults new(rt, const_result, effects, edge) end +# TODO MustAlias forwarding + struct ConditionalArgtypes <: ForwardableArgtypes arginfo::ArgInfo sv::InferenceState @@ -1069,7 +1071,7 @@ function maybe_get_const_prop_profitable(interp::AbstractInterpreter, add_remark!(interp, sv, "[constprop] Disabled by argument and rettype heuristics") return nothing end - all_overridden = is_all_overridden(arginfo, sv) + all_overridden = is_all_overridden(interp, arginfo, sv) if !force && !const_prop_function_heuristic(interp, f, arginfo, nargs, all_overridden, is_nothrow(sv.ipo_effects), sv) add_remark!(interp, sv, "[constprop] Disabled by function heuristic") @@ -1128,46 +1130,28 @@ end # determines heuristically whether if constant propagation can be worthwhile # by checking if any of given `argtypes` is "interesting" enough to be propagated -function const_prop_argument_heuristic(interp::AbstractInterpreter, (; fargs, argtypes)::ArgInfo, sv::InferenceState) +function const_prop_argument_heuristic(interp::AbstractInterpreter, arginfo::ArgInfo, sv::InferenceState) + 𝕃ᵢ = typeinf_lattice(interp) + argtypes = arginfo.argtypes for i in 1:length(argtypes) a = argtypes[i] - if isa(a, Conditional) && fargs !== nothing - is_const_prop_profitable_conditional(a, fargs, sv) && return true + if has_conditional(𝕃ᵢ) && isa(a, Conditional) && arginfo.fargs !== nothing + is_const_prop_profitable_conditional(a, arginfo.fargs, sv) && return true else a = widenslotwrapper(a) - has_nontrivial_const_info(typeinf_lattice(interp), a) && is_const_prop_profitable_arg(a) && return true + has_nontrivial_extended_info(𝕃ᵢ, a) && is_const_prop_profitable_arg(𝕃ᵢ, a) && return true end end return false end -function is_const_prop_profitable_arg(@nospecialize(arg)) - # have new information from argtypes that wasn't available from the signature - if isa(arg, PartialStruct) - return true # might be a bit aggressive, may want to enable some check like follows: - # for i = 1:length(arg.fields) - # fld = arg.fields[i] - # isconstType(fld) && return true - # is_const_prop_profitable_arg(fld) && return true - # fld ⊏ fieldtype(arg.typ, i) && return true - # end - # return false - end - isa(arg, PartialOpaque) && return true - isa(arg, PartialTypeVar) && return false # this isn't forwardable - isa(arg, Const) || return true - val = arg.val - # don't consider mutable values useful constants - return isa(val, Symbol) || isa(val, Type) || !ismutable(val) -end - function is_const_prop_profitable_conditional(cnd::Conditional, fargs::Vector{Any}, sv::InferenceState) slotid = find_constrained_arg(cnd, fargs, sv) if slotid !== nothing return true end # as a minor optimization, we just check the result is a constant or not, - # since both `has_nontrivial_const_info`/`is_const_prop_profitable_arg` return `true` + # since both `has_nontrivial_extended_info`/`is_const_prop_profitable_arg` return `true` # for `Const(::Bool)` return isa(widenconditional(cnd), Const) end @@ -1184,14 +1168,14 @@ function find_constrained_arg(cnd::Conditional, fargs::Vector{Any}, sv::Inferenc end # checks if all argtypes has additional information other than what `Type` can provide -function is_all_overridden((; fargs, argtypes)::ArgInfo, sv::InferenceState) +function is_all_overridden(interp::AbstractInterpreter, (; fargs, argtypes)::ArgInfo, sv::InferenceState) + 𝕃ᵢ = typeinf_lattice(interp) for i in 1:length(argtypes) a = argtypes[i] - if isa(a, Conditional) && fargs !== nothing + if has_conditional(𝕃ᵢ) && isa(a, Conditional) && fargs !== nothing is_const_prop_profitable_conditional(a, fargs, sv) || return false else - a = widenslotwrapper(a) - is_forwardable_argtype(a) || return false + is_forwardable_argtype(𝕃ᵢ, widenslotwrapper(a)) || return false end end return true @@ -1204,11 +1188,11 @@ function force_const_prop(interp::AbstractInterpreter, @nospecialize(f), method: istopfunction(f, :setproperty!) end -function const_prop_function_heuristic( - interp::AbstractInterpreter, @nospecialize(f), (; argtypes)::ArgInfo, +function const_prop_function_heuristic(interp::AbstractInterpreter, @nospecialize(f), arginfo::ArgInfo, nargs::Int, all_overridden::Bool, still_nothrow::Bool, _::InferenceState) - ⊑ᵢ = ⊑(typeinf_lattice(interp)) + argtypes = arginfo.argtypes if nargs > 1 + 𝕃ᵢ = typeinf_lattice(interp) if istopfunction(f, :getindex) || istopfunction(f, :setindex!) arrty = argtypes[2] # don't propagate constant index into indexing of non-constant array @@ -1218,12 +1202,12 @@ function const_prop_function_heuristic( if !still_nothrow || ismutabletype(arrty) return false end - elseif arrty ⊑ᵢ Array + elseif ⊑(𝕃ᵢ, arrty, Array) return false end elseif istopfunction(f, :iterate) itrty = argtypes[2] - if itrty ⊑ᵢ Array + if ⊑(𝕃ᵢ, itrty, Array) return false end end @@ -2294,7 +2278,7 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp end allconst &= isa(at, Const) if !anyrefine - anyrefine = has_nontrivial_const_info(𝕃ᵢ, at) || # constant information + anyrefine = has_nontrivial_extended_info(𝕃ᵢ, at) || # extended lattice information ⋤(𝕃ᵢ, at, ft) # just a type-level information, but more precise than the declared type end ats[i] = at @@ -2566,18 +2550,6 @@ struct BestguessInfo{Interp<:AbstractInterpreter} end end -""" - widenreturn(@nospecialize(rt), info::BestguessInfo) -> new_bestguess - -Appropriately converts inferred type of a return value `rt` to such a type -that we know we can store in the cache and is valid and good inter-procedurally, -E.g. if `rt isa Conditional` then `rt` should be converted to `InterConditional` -or the other cachable lattice element. - -External lattice `𝕃ₑ::ExternalLattice` may overload: -- `widenreturn(𝕃ₑ::ExternalLattice, @nospecialize(rt), info::BestguessInfo)` -- `widenreturn_noslotwrapper(𝕃ₑ::ExternalLattice, @nospecialize(rt), info::BestguessInfo)` -""" function widenreturn(@nospecialize(rt), info::BestguessInfo) return widenreturn(typeinf_lattice(info.interp), rt, info) end @@ -2691,7 +2663,7 @@ function widenreturn_partials(𝕃ᵢ::PartialsLattice, @nospecialize(rt), info: a = isvarargtype(a) ? a : widenreturn_noslotwrapper(𝕃, a, info) if !anyrefine # TODO: consider adding && const_prop_profitable(a) here? - anyrefine = has_const_info(a) || + anyrefine = has_extended_info(a) || ⊏(𝕃, a, fieldtype(rt.typ, i)) end fields[i] = a diff --git a/base/compiler/abstractlattice.jl b/base/compiler/abstractlattice.jl index 5e92aea1c81b3d..d98c2b818b6494 100644 --- a/base/compiler/abstractlattice.jl +++ b/base/compiler/abstractlattice.jl @@ -1,53 +1,54 @@ -abstract type AbstractLattice; end +# TODO add more documentations + +abstract type AbstractLattice end function widenlattice end +function is_valid_lattice_norec end """ - struct JLTypeLattice + struct JLTypeLattice <: AbstractLattice -A singleton type representing the lattice of Julia types, without any inference -extensions. +A singleton type representing the lattice of Julia types, without any inference extensions. """ struct JLTypeLattice <: AbstractLattice; end widenlattice(::JLTypeLattice) = error("Type lattice is the least-precise lattice available") -is_valid_lattice(lattice::JLTypeLattice, @nospecialize(elem)) = is_valid_lattice_norec(lattice, elem) is_valid_lattice_norec(::JLTypeLattice, @nospecialize(elem)) = isa(elem, Type) """ - struct ConstsLattice + struct ConstsLattice <: AbstractLattice A lattice extending `JLTypeLattice` and adjoining `Const` and `PartialTypeVar`. """ struct ConstsLattice <: AbstractLattice; end widenlattice(::ConstsLattice) = JLTypeLattice() -is_valid_lattice_norec(lattice::ConstsLattice, @nospecialize(elem)) = isa(elem, Const) || isa(elem, PartialTypeVar) +is_valid_lattice_norec(::ConstsLattice, @nospecialize(elem)) = isa(elem, Const) || isa(elem, PartialTypeVar) """ - struct PartialsLattice{L} + struct PartialsLattice{𝕃<:AbstractLattice} <: AbstractLattice -A lattice extending lattice `L` and adjoining `PartialStruct` and `PartialOpaque`. +A lattice extending a base lattice `𝕃` and adjoining `PartialStruct` and `PartialOpaque`. """ -struct PartialsLattice{L <: AbstractLattice} <: AbstractLattice - parent::L +struct PartialsLattice{𝕃<:AbstractLattice} <: AbstractLattice + parent::𝕃 end -widenlattice(L::PartialsLattice) = L.parent -is_valid_lattice_norec(lattice::PartialsLattice, @nospecialize(elem)) = isa(elem, PartialStruct) || isa(elem, PartialOpaque) +widenlattice(𝕃::PartialsLattice) = 𝕃.parent +is_valid_lattice_norec(::PartialsLattice, @nospecialize(elem)) = isa(elem, PartialStruct) || isa(elem, PartialOpaque) """ - struct ConditionalsLattice{L} + struct ConditionalsLattice{𝕃<:AbstractLattice} <: AbstractLattice -A lattice extending lattice `L` and adjoining `Conditional`. +A lattice extending a base lattice `𝕃` and adjoining `Conditional`. """ -struct ConditionalsLattice{L <: AbstractLattice} <: AbstractLattice - parent::L +struct ConditionalsLattice{𝕃<:AbstractLattice} <: AbstractLattice + parent::𝕃 end -widenlattice(L::ConditionalsLattice) = L.parent -is_valid_lattice_norec(lattice::ConditionalsLattice, @nospecialize(elem)) = isa(elem, Conditional) +widenlattice(𝕃::ConditionalsLattice) = 𝕃.parent +is_valid_lattice_norec(::ConditionalsLattice, @nospecialize(elem)) = isa(elem, Conditional) -struct InterConditionalsLattice{L <: AbstractLattice} <: AbstractLattice - parent::L +struct InterConditionalsLattice{𝕃<:AbstractLattice} <: AbstractLattice + parent::𝕃 end -widenlattice(L::InterConditionalsLattice) = L.parent -is_valid_lattice_norec(lattice::InterConditionalsLattice, @nospecialize(elem)) = isa(elem, InterConditional) +widenlattice(𝕃::InterConditionalsLattice) = 𝕃.parent +is_valid_lattice_norec(::InterConditionalsLattice, @nospecialize(elem)) = isa(elem, InterConditional) """ struct MustAliasesLattice{𝕃} @@ -79,37 +80,37 @@ const BaseInferenceLattice = typeof(ConditionalsLattice(SimpleInferenceLattice.i const IPOResultLattice = typeof(InterConditionalsLattice(SimpleInferenceLattice.instance)) """ - struct InferenceLattice{L} + struct InferenceLattice{𝕃<:AbstractLattice} <: AbstractLattice -The full lattice used for abstract interpretation during inference. Takes -a base lattice and adjoins `LimitedAccuracy`. +The full lattice used for abstract interpretation during inference. +Takes a base lattice `𝕃` and adjoins `LimitedAccuracy`. """ -struct InferenceLattice{L} <: AbstractLattice - parent::L +struct InferenceLattice{𝕃<:AbstractLattice} <: AbstractLattice + parent::𝕃 end -widenlattice(L::InferenceLattice) = L.parent -is_valid_lattice_norec(lattice::InferenceLattice, @nospecialize(elem)) = isa(elem, LimitedAccuracy) +widenlattice(𝕃::InferenceLattice) = 𝕃.parent +is_valid_lattice_norec(::InferenceLattice, @nospecialize(elem)) = isa(elem, LimitedAccuracy) """ - struct OptimizerLattice + struct OptimizerLattice{𝕃<:AbstractLattice} <: AbstractLattice The lattice used by the optimizer. Extends `BaseInferenceLattice` with `MaybeUndef`. """ -struct OptimizerLattice{L} <: AbstractLattice - parent::L +struct OptimizerLattice{𝕃<:AbstractLattice} <: AbstractLattice + parent::𝕃 end OptimizerLattice() = OptimizerLattice(SimpleInferenceLattice.instance) -widenlattice(L::OptimizerLattice) = L.parent -is_valid_lattice_norec(lattice::OptimizerLattice, @nospecialize(elem)) = isa(elem, MaybeUndef) +widenlattice(𝕃::OptimizerLattice) = 𝕃.parent +is_valid_lattice_norec(::OptimizerLattice, @nospecialize(elem)) = isa(elem, MaybeUndef) """ - tmeet(lattice, a, b::Type) + tmeet(𝕃::AbstractLattice, a, b::Type) -Compute the lattice meet of lattice elements `a` and `b` over the lattice -`lattice`. If `lattice` is `JLTypeLattice`, this is equivalent to type -intersection. Note that currently `b` is restricted to being a type (interpreted -as a lattice element in the JLTypeLattice sub-lattice of `lattice`). +Compute the lattice meet of lattice elements `a` and `b` over the lattice `𝕃`. +If `𝕃` is `JLTypeLattice`, this is equivalent to type intersection. +Note that currently `b` is restricted to being a type +(interpreted as a lattice element in the `JLTypeLattice` sub-lattice of `𝕃`). """ function tmeet end @@ -120,9 +121,9 @@ function tmeet(::JLTypeLattice, @nospecialize(a::Type), @nospecialize(b::Type)) end """ - tmerge(lattice, a, b) + tmerge(𝕃::AbstractLattice, a, b) -Compute a lattice join of elements `a` and `b` over the lattice `lattice`. +Compute a lattice join of elements `a` and `b` over the lattice `𝕃`. Note that the computed element need not be the least upper bound of `a` and `b`, but rather, we impose additional limitations on the complexity of the joined element, ideally without losing too much precision in common cases and @@ -131,52 +132,137 @@ remaining mostly associative and commutative. function tmerge end """ - ⊑(lattice, a, b) + ⊑(𝕃::AbstractLattice, a, b) Compute the lattice ordering (i.e. less-than-or-equal) relationship between -lattice elements `a` and `b` over the lattice `lattice`. If `lattice` is -`JLTypeLattice`, this is equivalent to subtyping. +lattice elements `a` and `b` over the lattice `𝕃`. +If `𝕃` is `JLTypeLattice`, this is equivalent to subtyping. """ function ⊑ end ⊑(::JLTypeLattice, @nospecialize(a::Type), @nospecialize(b::Type)) = a <: b """ - ⊏(lattice, a, b) -> Bool + ⊏(𝕃::AbstractLattice, a, b) -> Bool The strict partial order over the type inference lattice. This is defined as the irreflexive kernel of `⊑`. """ -⊏(lattice::AbstractLattice, @nospecialize(a), @nospecialize(b)) = ⊑(lattice, a, b) && !⊑(lattice, b, a) +⊏(𝕃::AbstractLattice, @nospecialize(a), @nospecialize(b)) = ⊑(𝕃, a, b) && !⊑(𝕃, b, a) """ - ⋤(lattice, a, b) -> Bool + ⋤(𝕃::AbstractLattice, a, b) -> Bool This order could be used as a slightly more efficient version of the strict order `⊏`, where we can safely assume `a ⊑ b` holds. """ -⋤(lattice::AbstractLattice, @nospecialize(a), @nospecialize(b)) = !⊑(lattice, b, a) +⋤(𝕃::AbstractLattice, @nospecialize(a), @nospecialize(b)) = !⊑(𝕃, b, a) """ - is_lattice_equal(lattice, a, b) -> Bool + is_lattice_equal(𝕃::AbstractLattice, a, b) -> Bool Check if two lattice elements are partial order equivalent. -This is basically `a ⊑ b && b ⊑ a` but (optionally) with extra performance optimizations. +This is basically `a ⊑ b && b ⊑ a` in the lattice of `𝕃` +but (optionally) with extra performance optimizations. """ -function is_lattice_equal(lattice::AbstractLattice, @nospecialize(a), @nospecialize(b)) +function is_lattice_equal(𝕃::AbstractLattice, @nospecialize(a), @nospecialize(b)) a === b && return true - ⊑(lattice, a, b) && ⊑(lattice, b, a) + return ⊑(𝕃, a, b) && ⊑(𝕃, b, a) +end + +""" + has_nontrivial_extended_info(𝕃::AbstractLattice, t) -> Bool + +Determines whether the given lattice element `t` of `𝕃` has non-trivial extended lattice +information that would not be available from the type itself. +""" +has_nontrivial_extended_info(𝕃::AbstractLattice, @nospecialize t) = + has_nontrivial_extended_info(widenlattice(𝕃), t) +function has_nontrivial_extended_info(𝕃::PartialsLattice, @nospecialize t) + isa(t, PartialStruct) && return true + isa(t, PartialOpaque) && return true + return has_nontrivial_extended_info(widenlattice(𝕃), t) +end +function has_nontrivial_extended_info(𝕃::ConstsLattice, @nospecialize t) + isa(t, PartialTypeVar) && return true + if isa(t, Const) + val = t.val + return !issingletontype(typeof(val)) && !(isa(val, Type) && hasuniquerep(val)) + end + return has_nontrivial_extended_info(widenlattice(𝕃), t) +end +has_nontrivial_extended_info(::JLTypeLattice, @nospecialize(t)) = false + +""" + is_const_prop_profitable_arg(𝕃::AbstractLattice, t) -> Bool + +Determines whether the given lattice element `t` of `𝕃` has new extended lattice information +that should be forwarded along with constant propagation. +""" +is_const_prop_profitable_arg(𝕃::AbstractLattice, @nospecialize t) = + is_const_prop_profitable_arg(widenlattice(𝕃), t) +function is_const_prop_profitable_arg(𝕃::PartialsLattice, @nospecialize t) + if isa(t, PartialStruct) + return true # might be a bit aggressive, may want to enable some check like follows: + # for i = 1:length(t.fields) + # fld = t.fields[i] + # isconstType(fld) && return true + # is_const_prop_profitable_arg(fld) && return true + # fld ⊏ fieldtype(t.typ, i) && return true + # end + # return false + end + isa(t, PartialOpaque) && return true + return is_const_prop_profitable_arg(widenlattice(𝕃), t) +end +function is_const_prop_profitable_arg(𝕃::ConstsLattice, @nospecialize t) + if isa(t, Const) + # don't consider mutable values useful constants + val = t.val + return isa(val, Symbol) || isa(val, Type) || !ismutable(val) + end + isa(t, PartialTypeVar) && return false # this isn't forwardable + return is_const_prop_profitable_arg(widenlattice(𝕃), t) +end +is_const_prop_profitable_arg(::JLTypeLattice, @nospecialize t) = false + +is_forwardable_argtype(𝕃::AbstractLattice, @nospecialize(x)) = + is_forwardable_argtype(widenlattice(𝕃), x) +function is_forwardable_argtype(𝕃::ConditionalsLattice, @nospecialize x) + isa(x, Conditional) && return true + return is_forwardable_argtype(widenlattice(𝕃), x) +end +function is_forwardable_argtype(𝕃::PartialsLattice, @nospecialize x) + isa(x, PartialStruct) && return true + isa(x, PartialOpaque) && return true + return is_forwardable_argtype(widenlattice(𝕃), x) +end +function is_forwardable_argtype(𝕃::ConstsLattice, @nospecialize x) + isa(x, Const) && return true + return is_forwardable_argtype(widenlattice(𝕃), x) +end +function is_forwardable_argtype(::JLTypeLattice, @nospecialize x) + return false end """ - has_nontrivial_const_info(lattice, t) -> Bool + widenreturn(𝕃ᵢ::AbstractLattice, @nospecialize(rt), info::BestguessInfo) -> new_bestguess + widenreturn_noslotwrapper(𝕃ᵢ::AbstractLattice, @nospecialize(rt), info::BestguessInfo) -> new_bestguess -Determine whether the given lattice element `t` of `lattice` has non-trivial -constant information that would not be available from the type itself. +Appropriately converts inferred type of a return value `rt` to such a type +that we know we can store in the cache and is valid and good inter-procedurally, +E.g. if `rt isa Conditional` then `rt` should be converted to `InterConditional` +or the other cachable lattice element. + +External lattice `𝕃ᵢ::ExternalLattice` may overload: +- `widenreturn(𝕃ᵢ::ExternalLattice, @nospecialize(rt), info::BestguessInfo)` +- `widenreturn_noslotwrapper(𝕃ᵢ::ExternalLattice, @nospecialize(rt), info::BestguessInfo)` """ -has_nontrivial_const_info(lattice::AbstractLattice, @nospecialize t) = - has_nontrivial_const_info(widenlattice(lattice), t) -has_nontrivial_const_info(::JLTypeLattice, @nospecialize(t)) = false +function widenreturn end, function widenreturn_noslotwrapper end + +is_valid_lattice(𝕃::AbstractLattice, @nospecialize(elem)) = + is_valid_lattice_norec(𝕃, elem) && is_valid_lattice(widenlattice(𝕃), elem) +is_valid_lattice(𝕃::JLTypeLattice, @nospecialize(elem)) = is_valid_lattice_norec(𝕃, elem) has_conditional(𝕃::AbstractLattice) = has_conditional(widenlattice(𝕃)) has_conditional(::AnyConditionalsLattice) = true @@ -202,12 +288,9 @@ tmerge(@nospecialize(a), @nospecialize(b)) = tmerge(fallback_lattice, a, b) ⋤(@nospecialize(a), @nospecialize(b)) = ⋤(fallback_lattice, a, b) is_lattice_equal(@nospecialize(a), @nospecialize(b)) = is_lattice_equal(fallback_lattice, a, b) -is_valid_lattice(lattice::AbstractLattice, @nospecialize(elem)) = is_valid_lattice_norec(lattice, elem) && - is_valid_lattice(widenlattice(lattice), elem) - # Widenlattice with argument widenlattice(::JLTypeLattice, @nospecialize(t)) = widenconst(t) -function widenlattice(lattice::AbstractLattice, @nospecialize(t)) - is_valid_lattice_norec(lattice, t) && return t - widenlattice(widenlattice(lattice), t) +function widenlattice(𝕃::AbstractLattice, @nospecialize(t)) + is_valid_lattice_norec(𝕃, t) && return t + widenlattice(widenlattice(𝕃), t) end diff --git a/base/compiler/inferenceresult.jl b/base/compiler/inferenceresult.jl index f54173c899cbc3..5001be9cc3601b 100644 --- a/base/compiler/inferenceresult.jl +++ b/base/compiler/inferenceresult.jl @@ -68,24 +68,16 @@ function pick_const_args!(cache_argtypes::Vector{Any}, overridden_by_const::BitV return cache_argtypes, overridden_by_const end -function is_argtype_match(lattice::AbstractLattice, +function is_argtype_match(𝕃::AbstractLattice, @nospecialize(given_argtype), @nospecialize(cache_argtype), overridden_by_const::Bool) - if is_forwardable_argtype(given_argtype) - return is_lattice_equal(lattice, given_argtype, cache_argtype) + if is_forwardable_argtype(𝕃, given_argtype) + return is_lattice_equal(𝕃, given_argtype, cache_argtype) end return !overridden_by_const end -# TODO MustAlias forwarding -function is_forwardable_argtype(@nospecialize x) - return isa(x, Const) || - isa(x, Conditional) || - isa(x, PartialStruct) || - isa(x, PartialOpaque) -end - va_process_argtypes(given_argtypes::Vector{Any}, linfo::MethodInstance) = va_process_argtypes(Returns(nothing), given_argtypes, linfo) function va_process_argtypes(@nospecialize(va_handler!), given_argtypes::Vector{Any}, linfo::MethodInstance) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index f804bd54610198..5dac285de6933e 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -1330,7 +1330,7 @@ function abstract_modifyfield!(interp::AbstractInterpreter, argtypes::Vector{Any TF2 = tmeet(callinfo.rt, widenconst(TF)) if TF2 === Bottom RT = Bottom - elseif isconcretetype(RT) && has_nontrivial_const_info(𝕃ᵢ, TF2) # isconcrete condition required to form a PartialStruct + elseif isconcretetype(RT) && has_nontrivial_extended_info(𝕃ᵢ, TF2) # isconcrete condition required to form a PartialStruct RT = PartialStruct(RT, Any[TF, TF2]) end info = ModifyFieldInfo(callinfo.info) @@ -1799,7 +1799,7 @@ function tuple_tfunc(𝕃::AbstractLattice, argtypes::Vector{Any}) anyinfo = false for i in 1:length(argtypes) x = argtypes[i] - if has_nontrivial_const_info(𝕃, x) + if has_nontrivial_extended_info(𝕃, x) anyinfo = true else if !isvarargtype(x) diff --git a/base/compiler/typelimits.jl b/base/compiler/typelimits.jl index 845e46ab01ed7b..e470c4711110cb 100644 --- a/base/compiler/typelimits.jl +++ b/base/compiler/typelimits.jl @@ -6,7 +6,6 @@ const MAX_TYPEUNION_COMPLEXITY = 3 const MAX_TYPEUNION_LENGTH = 3 -const MAX_INLINE_CONST_SIZE = 256 ######################### # limitation heuristics # @@ -534,7 +533,7 @@ function tmerge(lattice::PartialsLattice, @nospecialize(typea), @nospecialize(ty end fields[i] = tyi if !anyrefine - anyrefine = has_nontrivial_const_info(lattice, tyi) || # constant information + anyrefine = has_nontrivial_extended_info(lattice, tyi) || # extended information ⋤(lattice, tyi, ft) # just a type-level information, but more precise than the declared type end end @@ -542,7 +541,6 @@ function tmerge(lattice::PartialsLattice, @nospecialize(typea), @nospecialize(ty end end - # Don't widen const here - external AbstractInterpreter might insert lattice # layers between us and `ConstsLattice`. wl = widenlattice(lattice) diff --git a/base/compiler/typeutils.jl b/base/compiler/typeutils.jl index 9a282da101d022..0950150637b0a8 100644 --- a/base/compiler/typeutils.jl +++ b/base/compiler/typeutils.jl @@ -46,21 +46,7 @@ function isTypeDataType(@nospecialize t) return true end -function has_nontrivial_const_info(lattice::PartialsLattice, @nospecialize t) - isa(t, PartialStruct) && return true - isa(t, PartialOpaque) && return true - return has_nontrivial_const_info(widenlattice(lattice), t) -end -function has_nontrivial_const_info(lattice::ConstsLattice, @nospecialize t) - isa(t, PartialTypeVar) && return true - if isa(t, Const) - val = t.val - return !issingletontype(typeof(val)) && !(isa(val, Type) && hasuniquerep(val)) - end - return has_nontrivial_const_info(widenlattice(lattice), t) -end - -has_const_info(@nospecialize x) = (!isa(x, Type) && !isvarargtype(x)) || isType(x) +has_extended_info(@nospecialize x) = (!isa(x, Type) && !isvarargtype(x)) || isType(x) # Subtyping currently intentionally answers certain queries incorrectly for kind types. For # some of these queries, this check can be used to somewhat protect against making incorrect diff --git a/base/compiler/utilities.jl b/base/compiler/utilities.jl index 2915870ae2ea58..a12b8c7fb6db17 100644 --- a/base/compiler/utilities.jl +++ b/base/compiler/utilities.jl @@ -77,6 +77,12 @@ function quoted(@nospecialize(x)) return is_self_quoting(x) ? x : QuoteNode(x) end +############ +# inlining # +############ + +const MAX_INLINE_CONST_SIZE = 256 + function count_const_size(@nospecialize(x), count_self::Bool = true) (x isa Type || x isa Symbol) && return 0 ismutable(x) && return MAX_INLINE_CONST_SIZE + 1 diff --git a/test/compiler/AbstractInterpreter.jl b/test/compiler/AbstractInterpreter.jl index 6499d9d24a5182..ac1f34743e18e3 100644 --- a/test/compiler/AbstractInterpreter.jl +++ b/test/compiler/AbstractInterpreter.jl @@ -127,24 +127,21 @@ using Core: SlotNumber, Argument using Core.Compiler: slot_id, tmerge_fast_path import .CC: AbstractLattice, BaseInferenceLattice, IPOResultLattice, InferenceLattice, OptimizerLattice, - widen, is_valid_lattice, typeinf_lattice, ipo_lattice, optimizer_lattice, - widenconst, tmeet, tmerge, ⊑, abstract_eval_special_value, widenreturn, - widenlattice + widenlattice, is_valid_lattice_norec, typeinf_lattice, ipo_lattice, optimizer_lattice, + widenconst, tmeet, tmerge, ⊑, abstract_eval_special_value, widenreturn @newinterp TaintInterpreter struct TaintLattice{PL<:AbstractLattice} <: CC.AbstractLattice parent::PL end CC.widenlattice(𝕃::TaintLattice) = 𝕃.parent -CC.is_valid_lattice(𝕃::TaintLattice, @nospecialize(elm)) = - is_valid_lattice(widenlattice(𝕃), elem) || isa(elm, Taint) +CC.is_valid_lattice_norec(::TaintLattice, @nospecialize(elm)) = isa(elm, Taint) struct InterTaintLattice{PL<:AbstractLattice} <: CC.AbstractLattice parent::PL end CC.widenlattice(𝕃::InterTaintLattice) = 𝕃.parent -CC.is_valid_lattice(𝕃::InterTaintLattice, @nospecialize(elm)) = - is_valid_lattice(widenlattice(𝕃), elem) || isa(elm, InterTaint) +CC.is_valid_lattice_norec(::InterTaintLattice, @nospecialize(elm)) = isa(elm, InterTaint) const AnyTaintLattice{L} = Union{TaintLattice{L},InterTaintLattice{L}} From 4ad6aef3c13d5fe72111699e9c0736b71dcbac48 Mon Sep 17 00:00:00 2001 From: Franz Srambical <79149449+emergenz@users.noreply.github.com> Date: Mon, 12 Dec 2022 13:32:52 +0100 Subject: [PATCH 106/387] fix typo (#47875) --- doc/src/manual/functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/manual/functions.md b/doc/src/manual/functions.md index 056dfb79ae9115..7b430fa513002f 100644 --- a/doc/src/manual/functions.md +++ b/doc/src/manual/functions.md @@ -1028,7 +1028,7 @@ julia> ["a", "list", "of", "strings"] .|> [uppercase, reverse, titlecase, length 7 ``` -When combining pipes with anonymous functions, parentheses must be used if subsequent pipes are not to parsed as part of the anonymous function's body. Compare: +When combining pipes with anonymous functions, parentheses must be used if subsequent pipes are not to be parsed as part of the anonymous function's body. Compare: ```jldoctest julia> 1:3 .|> (x -> x^2) |> sum |> sqrt From cd19dc59d6bccc432ad901f16228771d4b3668ec Mon Sep 17 00:00:00 2001 From: pchintalapudi <34727397+pchintalapudi@users.noreply.github.com> Date: Mon, 12 Dec 2022 14:08:35 -0500 Subject: [PATCH 107/387] Add lock around JIT operations (#47831) This should protect against any memory manager shenanigans when the codegen lock is removed. Also, make only one lookup request instead of once per symbol. --- doc/src/devdocs/locks.md | 5 +++++ src/jitlayers.cpp | 22 +++++++++++++--------- src/jitlayers.h | 16 ++++++++++++++++ 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/doc/src/devdocs/locks.md b/doc/src/devdocs/locks.md index 9b2d992d8f5bb4..bef1419b1c8f89 100644 --- a/doc/src/devdocs/locks.md +++ b/doc/src/devdocs/locks.md @@ -33,6 +33,7 @@ The following are definitely leaf locks (level 1), and must not try to acquire a > * jl_locked_stream::mutex > * debuginfo_asyncsafe > * inference_timing_mutex +> * ExecutionEngine::SessionLock > > > flisp itself is already threadsafe, this lock only protects the `jl_ast_context_list_t` pool > > likewise, the ResourcePool::mutexes just protect the associated resource pool @@ -62,6 +63,10 @@ that TSCtx should be released prior to returning it to the pool. If multiple TSC acquired at the same time (due to recursive compilation), then locks should be acquired in the order that the TSCtxs were borrowed from the pool. +The following is a level 5 lock + +> * JuliaOJIT::EmissionMutex + The following are a level 6 lock, which can only recurse to acquire locks at lower levels: > * codegen diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index da5e8c58fdecdd..5fcafdc0f92285 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -1218,11 +1218,12 @@ JuliaOJIT::JuliaOJIT() } ), #endif + LockLayer(ObjectLayer), Pipelines{ - std::make_unique(ObjectLayer, *TM, 0), - std::make_unique(ObjectLayer, *TM, 1), - std::make_unique(ObjectLayer, *TM, 2), - std::make_unique(ObjectLayer, *TM, 3), + std::make_unique(LockLayer, *TM, 0), + std::make_unique(LockLayer, *TM, 1), + std::make_unique(LockLayer, *TM, 2), + std::make_unique(LockLayer, *TM, 3), }, OptSelLayer(Pipelines) { @@ -1332,13 +1333,14 @@ void JuliaOJIT::addModule(orc::ThreadSafeModule TSM) { JL_TIMING(LLVM_MODULE_FINISH); ++ModulesAdded; - std::vector NewExports; + orc::SymbolLookupSet NewExports; TSM.withModuleDo([&](Module &M) { jl_decorate_module(M); shareStrings(M); for (auto &F : M.global_values()) { if (!F.isDeclaration() && F.getLinkage() == GlobalValue::ExternalLinkage) { - NewExports.push_back(getMangledName(F.getName())); + auto Name = ES.intern(getMangledName(F.getName())); + NewExports.add(std::move(Name)); } } #if !defined(JL_NDEBUG) && !defined(JL_USE_JITLINK) @@ -1362,13 +1364,15 @@ void JuliaOJIT::addModule(orc::ThreadSafeModule TSM) } #endif }); + // TODO: what is the performance characteristics of this? cantFail(OptSelLayer.add(JD, std::move(TSM))); // force eager compilation (for now), due to memory management specifics // (can't handle compilation recursion) - for (auto Name : NewExports) - cantFail(ES.lookup({&JD}, Name)); - + for (auto &sym : cantFail(ES.lookup({{&JD, orc::JITDylibLookupFlags::MatchExportedSymbolsOnly}}, NewExports))) { + assert(sym.second); + (void) sym; + } } JL_JITSymbol JuliaOJIT::findSymbol(StringRef Name, bool ExportedSymbolsOnly) diff --git a/src/jitlayers.h b/src/jitlayers.h index 77ac5d64bb46d2..9e3fa6dc5711d9 100644 --- a/src/jitlayers.h +++ b/src/jitlayers.h @@ -268,6 +268,21 @@ class JuliaOJIT { #else typedef orc::RTDyldObjectLinkingLayer ObjLayerT; #endif + struct LockLayerT : public orc::ObjectLayer { + + LockLayerT(orc::ObjectLayer &BaseLayer) : orc::ObjectLayer(BaseLayer.getExecutionSession()), BaseLayer(BaseLayer) {} + + void emit(std::unique_ptr R, + std::unique_ptr O) override { +#ifndef JL_USE_JITLINK + std::lock_guard lock(EmissionMutex); +#endif + BaseLayer.emit(std::move(R), std::move(O)); + } + private: + orc::ObjectLayer &BaseLayer; + std::mutex EmissionMutex; + }; typedef orc::IRCompileLayer CompileLayerT; typedef orc::IRTransformLayer OptimizeLayerT; typedef object::OwningBinary OwningObj; @@ -492,6 +507,7 @@ class JuliaOJIT { const std::unique_ptr MemMgr; #endif ObjLayerT ObjectLayer; + LockLayerT LockLayer; const std::array, 4> Pipelines; OptSelLayerT OptSelLayer; }; From 4ff62883130802a44a5b4b3aea85c2aa0d6f98cf Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Mon, 12 Dec 2022 21:35:55 +0100 Subject: [PATCH 108/387] TOML: print: handle mixed vector of dicts and non-dicts (#47876) --- stdlib/TOML/src/print.jl | 5 ++++- stdlib/TOML/test/print.jl | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/stdlib/TOML/src/print.jl b/stdlib/TOML/src/print.jl index c9709cd7e42831..74efdfc97a05d5 100644 --- a/stdlib/TOML/src/print.jl +++ b/stdlib/TOML/src/print.jl @@ -122,7 +122,10 @@ end is_table(value) = isa(value, AbstractDict) is_array_of_tables(value) = isa(value, AbstractArray) && - length(value) > 0 && isa(value[1], AbstractDict) + length(value) > 0 && ( + isa(value, AbstractArray{<:AbstractDict}) || + all(v -> isa(v, AbstractDict), value) + ) is_tabular(value) = is_table(value) || is_array_of_tables(value) function print_table(f::MbyFunc, io::IO, a::AbstractDict, diff --git a/stdlib/TOML/test/print.jl b/stdlib/TOML/test/print.jl index 9479a14ca87962..bbfce3b7d7474e 100644 --- a/stdlib/TOML/test/print.jl +++ b/stdlib/TOML/test/print.jl @@ -80,6 +80,22 @@ loaders = ["gzip", { driver = "csv", args = {delim = "\t"}}] @test roundtrip(str) +@testset "vec with dicts and non-dicts" begin + # https://github.com/JuliaLang/julia/issues/45340 + d = Dict("b" => Any[111, Dict("a" => 222, "d" => 333)]) + @test toml_str(d) == "b = [111, {a = 222, d = 333}]\n" + + d = Dict("b" => Any[Dict("a" => 222, "d" => 333), 111]) + @test toml_str(d) == "b = [{a = 222, d = 333}, 111]\n" + + d = Dict("b" => Any[Dict("a" => 222, "d" => 333)]) + @test toml_str(d) == """ + [[b]] + a = 222 + d = 333 + """ +end + struct Foo a::Int64 b::Float64 From b03439c8e3e14d9068f8ebc0782c8a9d2f2736f2 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Mon, 12 Dec 2022 16:15:41 -0500 Subject: [PATCH 109/387] convert algorithms to SCC (#47866) These places in the code can either be more efficient O(1) or more correct using something more similar to the published SCC algorithm by Tarjan for strongly connected components. --- src/gf.c | 1 + src/jitlayers.cpp | 101 ++++++++++++------------ src/staticdata_utils.c | 172 ++++++++++++++--------------------------- 3 files changed, 111 insertions(+), 163 deletions(-) diff --git a/src/gf.c b/src/gf.c index 6d705f15a482cc..537677784c477b 100644 --- a/src/gf.c +++ b/src/gf.c @@ -3377,6 +3377,7 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, } } // then we'll merge those numbers to assign each item in the group the same number + // (similar to Kosaraju's SCC algorithm?) uint32_t groupid = 0; uint32_t grouphi = 0; for (i = 0; i < len; i++) { diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index 5fcafdc0f92285..09d05f280cbf1b 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -136,7 +136,11 @@ void jl_dump_llvm_opt_impl(void *s) **jl_ExecutionEngine->get_dump_llvm_opt_stream() = (JL_STREAM*)s; } -static void jl_add_to_ee(orc::ThreadSafeModule &M, StringMap &NewExports); +static int jl_add_to_ee( + orc::ThreadSafeModule &M, + const StringMap &NewExports, + DenseMap &Queued, + std::vector &Stack); static void jl_decorate_module(Module &M); static uint64_t getAddressForFunction(StringRef fname); @@ -228,10 +232,13 @@ static jl_callptr_t _jl_compile_codeinst( } } } + DenseMap Queued; + std::vector Stack; for (auto &def : emitted) { // Add the results to the execution engine now orc::ThreadSafeModule &M = std::get<0>(def.second); - jl_add_to_ee(M, NewExports); + jl_add_to_ee(M, NewExports, Queued, Stack); + assert(Queued.empty() && Stack.empty() && !M); } ++CompiledCodeinsts; MaxWorkqueueSize.updateMax(emitted.size()); @@ -1704,76 +1711,72 @@ static void jl_decorate_module(Module &M) { #endif } +// Implements Tarjan's SCC (strongly connected components) algorithm, simplified to remove the count variable static int jl_add_to_ee( orc::ThreadSafeModule &M, - StringMap &NewExports, + const StringMap &NewExports, DenseMap &Queued, - std::vector> &ToMerge, - int depth) + std::vector &Stack) { - // DAG-sort (post-dominator) the compile to compute the minimum - // merge-module sets for linkage + // First check if the TSM is empty (already compiled) if (!M) return 0; - // First check and record if it's on the stack somewhere + // Next check and record if it is on the stack somewhere { - auto &Cycle = Queued[&M]; - if (Cycle) - return Cycle; - ToMerge.push_back({}); - Cycle = depth; + auto &Id = Queued[&M]; + if (Id) + return Id; + Stack.push_back(&M); + Id = Stack.size(); } + // Finally work out the SCC + int depth = Stack.size(); int MergeUp = depth; - // Compute the cycle-id + std::vector Children; M.withModuleDo([&](Module &m) { for (auto &F : m.global_objects()) { if (F.isDeclaration() && F.getLinkage() == GlobalValue::ExternalLinkage) { auto Callee = NewExports.find(F.getName()); if (Callee != NewExports.end()) { - auto &CM = Callee->second; - int Down = jl_add_to_ee(*CM, NewExports, Queued, ToMerge, depth + 1); - assert(Down <= depth); - if (Down && Down < MergeUp) - MergeUp = Down; + auto *CM = Callee->second; + if (*CM && CM != &M) { + auto Down = Queued.find(CM); + if (Down != Queued.end()) + MergeUp = std::min(MergeUp, Down->second); + else + Children.push_back(CM); + } } } } }); - if (MergeUp == depth) { - // Not in a cycle (or at the top of it) - Queued.erase(&M); - for (auto &CM : ToMerge.at(depth - 1)) { - assert(Queued.find(CM)->second == depth); - Queued.erase(CM); - jl_merge_module(M, std::move(*CM)); - } - jl_ExecutionEngine->addModule(std::move(M)); - MergeUp = 0; + assert(MergeUp > 0); + for (auto *CM : Children) { + int Down = jl_add_to_ee(*CM, NewExports, Queued, Stack); + assert(Down <= (int)Stack.size()); + if (Down) + MergeUp = std::min(MergeUp, Down); } - else { - // Add our frame(s) to the top of the cycle - Queued[&M] = MergeUp; - auto &Top = ToMerge.at(MergeUp - 1); - Top.push_back(&M); - for (auto &CM : ToMerge.at(depth - 1)) { - assert(Queued.find(CM)->second == depth); - Queued[CM] = MergeUp; - Top.push_back(CM); + if (MergeUp < depth) + return MergeUp; + while (1) { + // Not in a cycle (or at the top of it) + // remove SCC state and merge every CM from the cycle into M + orc::ThreadSafeModule *CM = Stack.back(); + auto it = Queued.find(CM); + assert(it->second == (int)Stack.size()); + Queued.erase(it); + Stack.pop_back(); + if ((int)Stack.size() < depth) { + assert(&M == CM); + break; } + jl_merge_module(M, std::move(*CM)); } - ToMerge.pop_back(); - return MergeUp; -} - -static void jl_add_to_ee(orc::ThreadSafeModule &M, StringMap &NewExports) -{ - DenseMap Queued; - std::vector> ToMerge; - jl_add_to_ee(M, NewExports, Queued, ToMerge, 1); - assert(!M); + jl_ExecutionEngine->addModule(std::move(M)); + return 0; } - static uint64_t getAddressForFunction(StringRef fname) { auto addr = jl_ExecutionEngine->getFunctionAddress(fname); diff --git a/src/staticdata_utils.c b/src/staticdata_utils.c index 3d02dddbd5a708..6f673563ff3adb 100644 --- a/src/staticdata_utils.c +++ b/src/staticdata_utils.c @@ -158,31 +158,10 @@ static int type_in_worklist(jl_value_t *v) JL_NOTSAFEPOINT return 0; } -static void mark_backedges_in_worklist(jl_method_instance_t *mi, htable_t *visited, int found) -{ - int oldfound = (char*)ptrhash_get(visited, mi) - (char*)HT_NOTFOUND; - if (oldfound < 3) - return; // not in-progress - ptrhash_put(visited, mi, (void*)((char*)HT_NOTFOUND + 1 + found)); -#ifndef NDEBUG - jl_module_t *mod = mi->def.module; - if (jl_is_method(mod)) - mod = ((jl_method_t*)mod)->module; - assert(jl_is_module(mod)); - assert(!mi->precompiled && jl_object_in_image((jl_value_t*)mod)); - assert(mi->backedges); -#endif - size_t i = 0, n = jl_array_len(mi->backedges); - while (i < n) { - jl_method_instance_t *be; - i = get_next_edge(mi->backedges, i, NULL, &be); - mark_backedges_in_worklist(be, visited, found); - } -} - // When we infer external method instances, ensure they link back to the -// package. Otherwise they might be, e.g., for external macros -static int has_backedge_to_worklist(jl_method_instance_t *mi, htable_t *visited, int depth) +// package. Otherwise they might be, e.g., for external macros. +// Implements Tarjan's SCC (strongly connected components) algorithm, simplified to remove the count variable +static int has_backedge_to_worklist(jl_method_instance_t *mi, htable_t *visited, arraylist_t *stack) { jl_module_t *mod = mi->def.module; if (jl_is_method(mod)) @@ -202,14 +181,17 @@ static int has_backedge_to_worklist(jl_method_instance_t *mi, htable_t *visited, int found = (char*)*bp - (char*)HT_NOTFOUND; if (found) return found - 1; + arraylist_push(stack, (void*)mi); + int depth = stack->len; *bp = (void*)((char*)HT_NOTFOUND + 3 + depth); // preliminarily mark as in-progress size_t i = 0, n = jl_array_len(mi->backedges); int cycle = 0; while (i < n) { jl_method_instance_t *be; i = get_next_edge(mi->backedges, i, NULL, &be); - int child_found = has_backedge_to_worklist(be, visited, depth + 1); + int child_found = has_backedge_to_worklist(be, visited, stack); if (child_found == 1) { + // found what we were looking for, so terminate early found = 1; break; } @@ -221,22 +203,15 @@ static int has_backedge_to_worklist(jl_method_instance_t *mi, htable_t *visited, } if (!found && cycle && cycle != depth) return cycle + 2; - bp = ptrhash_bp(visited, mi); // re-acquire since rehashing might change the location - *bp = (void*)((char*)HT_NOTFOUND + 1 + found); - if (cycle) { - // If we are the top of the current cycle, now mark all other parts of - // our cycle by re-walking the backedges graph and marking all WIP - // items as found. - // Be careful to only re-walk as far as we had originally scanned above. - // Or if we found a backedge, also mark all of the other parts of the - // cycle as also having an backedge. - n = i; - i = 0; - while (i < n) { - jl_method_instance_t *be; - i = get_next_edge(mi->backedges, i, NULL, &be); - mark_backedges_in_worklist(be, visited, found); - } + // If we are the top of the current cycle, now mark all other parts of + // our cycle with what we found. + // Or if we found a backedge, also mark all of the other parts of the + // cycle as also having an backedge. + while (stack->len >= depth) { + void *mi = arraylist_pop(stack); + bp = ptrhash_bp(visited, mi); + assert((char*)*bp - (char*)HT_NOTFOUND == 4 + stack->len); + *bp = (void*)((char*)HT_NOTFOUND + 1 + found); } return found; } @@ -251,9 +226,11 @@ static jl_array_t *queue_external_cis(jl_array_t *list) return NULL; size_t i; htable_t visited; + arraylist_t stack; assert(jl_is_array(list)); size_t n0 = jl_array_len(list); htable_new(&visited, n0); + arraylist_new(&stack, 0); jl_array_t *new_specializations = jl_alloc_vec_any(0); JL_GC_PUSH1(&new_specializations); for (i = 0; i < n0; i++) { @@ -264,8 +241,9 @@ static jl_array_t *queue_external_cis(jl_array_t *list) if (jl_is_method(m)) { if (jl_object_in_image((jl_value_t*)m->module)) { if (ptrhash_get(&external_mis, mi) == HT_NOTFOUND) { - int found = has_backedge_to_worklist(mi, &visited, 1); + int found = has_backedge_to_worklist(mi, &visited, &stack); assert(found == 0 || found == 1); + assert(stack.len == 0); if (found == 1) { ptrhash_put(&external_mis, mi, mi); jl_array_ptr_1d_push(new_specializations, (jl_value_t*)ci); @@ -275,6 +253,7 @@ static jl_array_t *queue_external_cis(jl_array_t *list) } } htable_free(&visited); + arraylist_free(&stack); JL_GC_POP(); return new_specializations; } @@ -970,56 +949,23 @@ static void jl_verify_methods(jl_array_t *edges, jl_array_t *valids, htable_t *v } -// Propagate the result of cycle-resolution to all edges (recursively) -static int mark_edges_in_worklist(jl_array_t *edges, int idx, jl_method_instance_t *cycle, htable_t *visited, int found) -{ - jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, idx * 2); - int oldfound = (char*)ptrhash_get(visited, caller) - (char*)HT_NOTFOUND; - if (oldfound < 3) - return 0; // not in-progress - if (!found) { - ptrhash_remove(visited, (void*)caller); - } - else { - ptrhash_put(visited, (void*)caller, (void*)((char*)HT_NOTFOUND + 1 + found)); - } - jl_array_t *callee_ids = (jl_array_t*)jl_array_ptr_ref(edges, idx * 2 + 1); - assert(jl_typeis((jl_value_t*)callee_ids, jl_array_int32_type)); - int32_t *idxs = (int32_t*)jl_array_data(callee_ids); - size_t i, badidx = 0, n = jl_array_len(callee_ids); - for (i = idxs[0] + 1; i < n; i++) { - if (mark_edges_in_worklist(edges, idxs[i], cycle, visited, found) && badidx == 0) - badidx = i - idxs[0]; - } - if (_jl_debug_method_invalidation) { - jl_value_t *loctag = NULL; - JL_GC_PUSH1(&loctag); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)caller); - loctag = jl_cstr_to_string("verify_methods"); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); - jl_method_instance_t *callee = cycle; - if (badidx--) - callee = (jl_method_instance_t*)jl_array_ptr_ref(edges, 2 * badidx); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)callee); - JL_GC_POP(); - } - return 1; -} - - // Visit the entire call graph, starting from edges[idx] to determine if that method is valid -static int jl_verify_graph_edge(jl_array_t *edges, int idx, htable_t *visited, int depth) +// Implements Tarjan's SCC (strongly connected components) algorithm, simplified to remove the count variable +static int jl_verify_graph_edge(jl_array_t *edges, int idx, htable_t *visited, arraylist_t *stack) { jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, idx * 2); assert(jl_is_method_instance(caller) && jl_is_method(caller->def.method)); int found = (char*)ptrhash_get(visited, (void*)caller) - (char*)HT_NOTFOUND; if (found == 0) - return 1; // valid + return 1; // NOTFOUND == valid if (found == 1) return 0; // invalid if (found != 2) return found - 1; // depth found = 0; + jl_value_t *cause = NULL; + arraylist_push(stack, (void*)caller); + int depth = stack->len; ptrhash_put(visited, (void*)caller, (void*)((char*)HT_NOTFOUND + 3 + depth)); // change 2 to in-progress at depth jl_array_t *callee_ids = (jl_array_t*)jl_array_ptr_ref(edges, idx * 2 + 1); assert(jl_typeis((jl_value_t*)callee_ids, jl_array_int32_type)); @@ -1028,18 +974,11 @@ static int jl_verify_graph_edge(jl_array_t *edges, int idx, htable_t *visited, i size_t i, n = jl_array_len(callee_ids); for (i = idxs[0] + 1; i < n; i++) { int32_t idx = idxs[i]; - int child_found = jl_verify_graph_edge(edges, idx, visited, depth + 1); + int child_found = jl_verify_graph_edge(edges, idx, visited, stack); if (child_found == 0) { + // found what we were looking for, so terminate early found = 1; - if (_jl_debug_method_invalidation) { - jl_value_t *loctag = NULL; - JL_GC_PUSH1(&loctag); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)caller); - loctag = jl_cstr_to_string("verify_methods"); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, jl_array_ptr_ref(edges, idx * 2)); - JL_GC_POP(); - } + cause = jl_array_ptr_ref(edges, idx * 2); break; } else if (child_found >= 2 && child_found - 2 < cycle) { @@ -1048,24 +987,27 @@ static int jl_verify_graph_edge(jl_array_t *edges, int idx, htable_t *visited, i assert(cycle); } } - if (!found) { - if (cycle && cycle != depth) - return cycle + 2; - ptrhash_remove(visited, (void*)caller); - } - else { // found invalid - ptrhash_put(visited, (void*)caller, (void*)((char*)HT_NOTFOUND + 1 + found)); - } - if (cycle) { - // If we are the top of the current cycle, now mark all other parts of - // our cycle by re-walking the backedges graph and marking all WIP - // items as found. - // Be careful to only re-walk as far as we had originally scanned above. - // Or if we found a backedge, also mark all of the other parts of the - // cycle as also having an backedge. - n = i; - for (i = idxs[0] + 1; i < n; i++) { - mark_edges_in_worklist(edges, idxs[i], caller, visited, found); + if (!found && cycle && cycle != depth) + return cycle + 2; + // If we are the top of the current cycle, now mark all other parts of + // our cycle with what we found. + // Or if we found a backedge, also mark all of the other parts of the + // cycle as also having an backedge. + while (stack->len >= depth) { + void *mi = arraylist_pop(stack); + assert((char*)ptrhash_get(visited, mi) - (char*)HT_NOTFOUND == 4 + stack->len); + if (found) + ptrhash_put(visited, mi, (void*)((char*)HT_NOTFOUND + 1 + found)); + else + ptrhash_remove(visited, mi); // assign as NOTFOUND in table + if (_jl_debug_method_invalidation && found) { + jl_value_t *loctag = NULL; + JL_GC_PUSH1(&loctag); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)mi); + loctag = jl_cstr_to_string("verify_methods"); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)cause); + JL_GC_POP(); } } return found ? 0 : 1; @@ -1074,13 +1016,15 @@ static int jl_verify_graph_edge(jl_array_t *edges, int idx, htable_t *visited, i // Visit all entries in edges, verify if they are valid static jl_array_t *jl_verify_graph(jl_array_t *edges, htable_t *visited) { + arraylist_t stack; + arraylist_new(&stack, 0); size_t i, n = jl_array_len(edges) / 2; jl_array_t *valids = jl_alloc_array_1d(jl_array_uint8_type, n); JL_GC_PUSH1(&valids); int8_t *valids_data = (int8_t*)jl_array_data(valids); - for (i = 0; i < n; i++) { - valids_data[i] = jl_verify_graph_edge(edges, i, visited, 1); - } + for (i = 0; i < n; i++) + valids_data[i] = jl_verify_graph_edge(edges, i, visited, &stack); + arraylist_free(&stack); JL_GC_POP(); return valids; } @@ -1096,8 +1040,8 @@ static void jl_insert_backedges(jl_array_t *edges, jl_array_t *ext_targets, jl_a JL_GC_PUSH1(&valids); htable_t visited; htable_new(&visited, 0); - jl_verify_methods(edges, valids, &visited); - valids = jl_verify_graph(edges, &visited); + jl_verify_methods(edges, valids, &visited); // consumes valids, creates visited + valids = jl_verify_graph(edges, &visited); // consumes visited, creates valids size_t i, l = jl_array_len(edges) / 2; // next build a map from external MethodInstances to their CodeInstance for insertion From 09a6ff8cabefc4ecfa8cacb5185c2d94b026bced Mon Sep 17 00:00:00 2001 From: pchintalapudi <34727397+pchintalapudi@users.noreply.github.com> Date: Mon, 12 Dec 2022 19:07:31 -0500 Subject: [PATCH 110/387] Reduce codegen lock scope (#46836) --- src/aotcompile.cpp | 33 ++++++++++++++++++--------------- src/gf.c | 6 ++---- src/jitlayers.cpp | 2 +- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/aotcompile.cpp b/src/aotcompile.cpp index 26ba66fa967371..7325adde8b060e 100644 --- a/src/aotcompile.cpp +++ b/src/aotcompile.cpp @@ -267,7 +267,6 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm jl_method_instance_t *mi = NULL; jl_code_info_t *src = NULL; JL_GC_PUSH1(&src); - JL_LOCK(&jl_codegen_lock); auto ct = jl_current_task; ct->reentrant_codegen++; orc::ThreadSafeContext ctx; @@ -278,16 +277,18 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm } orc::ThreadSafeModule &clone = llvmmod ? *unwrap(llvmmod) : backing; auto ctxt = clone.getContext(); - jl_codegen_params_t params(ctxt); - params.params = cgparams; + uint64_t compiler_start_time = 0; uint8_t measure_compile_time_enabled = jl_atomic_load_relaxed(&jl_measure_compile_time_enabled); if (measure_compile_time_enabled) compiler_start_time = jl_hrtime(); - params.imaging = imaging; - // compile all methods for the current world and type-inference world + + JL_LOCK(&jl_codegen_lock); + jl_codegen_params_t params(ctxt); + params.params = cgparams; + params.imaging = imaging; size_t compile_for[] = { jl_typeinf_world, jl_atomic_load_acquire(&jl_world_counter) }; for (int worlds = 0; worlds < 2; worlds++) { params.world = compile_for[worlds]; @@ -332,15 +333,18 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm // finally, make sure all referenced methods also get compiled or fixed up jl_compile_workqueue(emitted, *clone.getModuleUnlocked(), params, policy); } + JL_UNLOCK(&jl_codegen_lock); // Might GC JL_GC_POP(); // process the globals array, before jl_merge_module destroys them - std::vector gvars; + std::vector gvars(params.globals.size()); data->jl_value_to_llvm.resize(params.globals.size()); + size_t idx = 0; for (auto &global : params.globals) { - data->jl_value_to_llvm.at(gvars.size()) = global.first; - gvars.push_back(std::string(global.second->getName())); + gvars[idx] = global.second->getName().str(); + data->jl_value_to_llvm[idx] = global.first; + idx++; } CreateNativeMethods += emitted.size(); @@ -423,7 +427,6 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm jl_ExecutionEngine->releaseContext(std::move(ctx)); } ct->reentrant_codegen--; - JL_UNLOCK(&jl_codegen_lock); // Might GC return (void*)data; } @@ -1013,17 +1016,18 @@ void jl_get_llvmf_defn_impl(jl_llvmf_dump_t* dump, jl_method_instance_t *mi, siz // emit this function into a new llvm module if (src && jl_is_code_info(src)) { - JL_LOCK(&jl_codegen_lock); auto ctx = jl_ExecutionEngine->getContext(); - jl_codegen_params_t output(*ctx); - output.world = world; - output.params = ¶ms; - orc::ThreadSafeModule m = jl_create_llvm_module(name_from_method_instance(mi), output.tsctx, output.imaging); + orc::ThreadSafeModule m = jl_create_llvm_module(name_from_method_instance(mi), *ctx, imaging_default()); uint64_t compiler_start_time = 0; uint8_t measure_compile_time_enabled = jl_atomic_load_relaxed(&jl_measure_compile_time_enabled); if (measure_compile_time_enabled) compiler_start_time = jl_hrtime(); + JL_LOCK(&jl_codegen_lock); + jl_codegen_params_t output(*ctx); + output.world = world; + output.params = ¶ms; auto decls = jl_emit_code(m, mi, src, jlrettype, output); + JL_UNLOCK(&jl_codegen_lock); // Might GC Function *F = NULL; if (m) { @@ -1059,7 +1063,6 @@ void jl_get_llvmf_defn_impl(jl_llvmf_dump_t* dump, jl_method_instance_t *mi, siz JL_GC_POP(); if (measure_compile_time_enabled) jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); - JL_UNLOCK(&jl_codegen_lock); // Might GC if (F) { dump->TSM = wrap(new orc::ThreadSafeModule(std::move(m))); dump->F = wrap(F); diff --git a/src/gf.c b/src/gf.c index 537677784c477b..99c482420e2f27 100644 --- a/src/gf.c +++ b/src/gf.c @@ -3539,8 +3539,6 @@ int jl_has_concrete_subtype(jl_value_t *typ) return ((jl_datatype_t*)typ)->has_concrete_subtype; } -#define typeinf_lock jl_codegen_lock - JL_DLLEXPORT void jl_typeinf_timing_begin(void) { jl_task_t *ct = jl_current_task; @@ -3563,7 +3561,7 @@ JL_DLLEXPORT void jl_typeinf_timing_end(void) JL_DLLEXPORT void jl_typeinf_lock_begin(void) { - JL_LOCK(&typeinf_lock); + JL_LOCK(&jl_codegen_lock); //Although this is claiming to be a typeinfer lock, it is actually //affecting the codegen lock count, not type inference's inferencing count jl_task_t *ct = jl_current_task; @@ -3574,7 +3572,7 @@ JL_DLLEXPORT void jl_typeinf_lock_end(void) { jl_task_t *ct = jl_current_task; ct->reentrant_codegen--; - JL_UNLOCK(&typeinf_lock); + JL_UNLOCK(&jl_codegen_lock); } #ifdef __cplusplus diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index 09d05f280cbf1b..f6ecd64e757d8b 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -551,9 +551,9 @@ jl_value_t *jl_dump_method_asm_impl(jl_method_instance_t *mi, size_t world, } JL_GC_POP(); } + JL_UNLOCK(&jl_codegen_lock); if (!--ct->reentrant_codegen && measure_compile_time_enabled) jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); - JL_UNLOCK(&jl_codegen_lock); } if (specfptr != 0) return jl_dump_fptr_asm(specfptr, raw_mc, asm_variant, debuginfo, binary); From 9fa08ccf28c821d2fadaa963d22838c17b33a7bc Mon Sep 17 00:00:00 2001 From: Oscar Smith Date: Mon, 12 Dec 2022 22:28:07 -0500 Subject: [PATCH 111/387] revert "Improve effect analysis of bitshifts #47567" (#47830) Co-authored-by: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> --- base/int.jl | 9 +++------ base/operators.jl | 17 ++++++++--------- test/int.jl | 8 +++++++- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/base/int.jl b/base/int.jl index 16fb282aec03ec..554f0a7f1a4460 100644 --- a/base/int.jl +++ b/base/int.jl @@ -504,15 +504,12 @@ trailing_ones(x::Integer) = trailing_zeros(~x) >>>(x::BitInteger, y::BitUnsigned) = lshr_int(x, y) # signed shift counts can shift in either direction # note: this early during bootstrap, `>=` is not yet available +# note: we only define Int shift counts here; the generic case is handled later >>(x::BitInteger, y::Int) = ifelse(0 <= y, x >> unsigned(y), x << unsigned(-y)) ->>>(x::BitInteger, y::Int) = - ifelse(0 <= y, x >>> unsigned(y), x << unsigned(-y)) ->>(x::BitInteger, y::BitSigned) = - ifelse(0 <= y, x >> unsigned(y), x << unsigned(-y)) -<<(x::BitInteger, y::BitSigned) = +<<(x::BitInteger, y::Int) = ifelse(0 <= y, x << unsigned(y), x >> unsigned(-y)) ->>>(x::BitInteger, y::BitSigned) = +>>>(x::BitInteger, y::Int) = ifelse(0 <= y, x >>> unsigned(y), x << unsigned(-y)) for to in BitInteger_types, from in (BitInteger_types..., Bool) diff --git a/base/operators.jl b/base/operators.jl index 2542acbde27d48..2cc36ba83c9c57 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -641,10 +641,9 @@ See also [`>>`](@ref), [`>>>`](@ref), [`exp2`](@ref), [`ldexp`](@ref). """ function <<(x::Integer, c::Integer) @inline - 0 <= c <= typemax(UInt) && return x << (c % UInt) - -c <= typemax(UInt) && return x >> (-c % UInt) - (x >= 0 || c >= 0) && return zero(x) << UInt(0) # for type stability - return oftype(x, -1) << UInt(0) + typemin(Int) <= c <= typemax(Int) && return x << (c % Int) + (x >= 0 || c >= 0) && return zero(x) << 0 # for type stability + oftype(x, -1) end function <<(x::Integer, c::Unsigned) @inline @@ -653,6 +652,7 @@ function <<(x::Integer, c::Unsigned) end c <= typemax(UInt) ? x << (c % UInt) : zero(x) << UInt(0) end +<<(x::Integer, c::Int) = c >= 0 ? x << unsigned(c) : x >> unsigned(-c) """ >>(x, n) @@ -689,11 +689,11 @@ function >>(x::Integer, c::Integer) if c isa UInt throw(MethodError(>>, (x, c))) end - 0 <= c <= typemax(UInt) && return x >> (c % UInt) - -c <= typemax(UInt) && return x << (-c % UInt) + typemin(Int) <= c <= typemax(Int) && return x >> (c % Int) (x >= 0 || c < 0) && return zero(x) >> 0 oftype(x, -1) end +>>(x::Integer, c::Int) = c >= 0 ? x >> unsigned(c) : x << unsigned(-c) """ >>>(x, n) @@ -724,9 +724,7 @@ See also [`>>`](@ref), [`<<`](@ref). """ function >>>(x::Integer, c::Integer) @inline - 0 <= c <= typemax(UInt) && return x >>> (c % UInt) - -c <= typemax(UInt) && return x << (-c % UInt) - zero(x) >>> 0 + typemin(Int) <= c <= typemax(Int) ? x >>> (c % Int) : zero(x) >>> 0 end function >>>(x::Integer, c::Unsigned) @inline @@ -735,6 +733,7 @@ function >>>(x::Integer, c::Unsigned) end c <= typemax(UInt) ? x >>> (c % UInt) : zero(x) >>> 0 end +>>>(x::Integer, c::Int) = c >= 0 ? x >>> unsigned(c) : x << unsigned(-c) # operator alias diff --git a/test/int.jl b/test/int.jl index 225f5e1987e1fb..3bfa6adc993014 100644 --- a/test/int.jl +++ b/test/int.jl @@ -201,7 +201,13 @@ end end for T2 in Base.BitInteger_types for op in (>>, <<, >>>) - @test Core.Compiler.is_total(Base.infer_effects(op, (T, T2))) + if sizeof(T2)==sizeof(Int) || T <: Signed || (op==>>>) || T2 <: Unsigned + @test Core.Compiler.is_total(Base.infer_effects(op, (T, T2))) + else + @test Core.Compiler.is_foldable(Base.infer_effects(op, (T, T2))) + # #47835, TODO implement interval arithmetic analysis + @test_broken Core.Compiler.is_nothrow(Base.infer_effects(op, (T, T2))) + end end end end From 0772eba8c7fac819ae494efc1a59cb31e16decf2 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Tue, 13 Dec 2022 19:06:19 +0900 Subject: [PATCH 112/387] inference: rm unused `empty_bitset` variable (#47886) --- base/compiler/abstractinterpretation.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 6e24f3bae2de56..766c27f271b8c6 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -30,8 +30,6 @@ function get_max_methods(@nospecialize(f), mod::Module, interp::AbstractInterpre return get_max_methods(mod, interp) end -const empty_bitset = BitSet() - function should_infer_this_call(sv::InferenceState) if sv.params.unoptimize_throw_blocks # Disable inference of calls in throw blocks, since we're unlikely to From 3d799933cc75cc25271bf97ec5c61f0efafb6ee2 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Tue, 13 Dec 2022 19:06:59 +0900 Subject: [PATCH 113/387] fix `@testset`'s docstring formatting (#47888) --- stdlib/Test/src/Test.jl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index ae3a9a57c84a3c..2e0000ff100835 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -1327,12 +1327,12 @@ also be used for any nested `@testset` invocations. The given options are only applied to the test set where they are given. The default test set type accepts three boolean options: - `verbose`: if `true`, the result summary of the nested testsets is shown even -when they all pass (the default is `false`). + when they all pass (the default is `false`). - `showtiming`: if `true`, the duration of each displayed testset is shown -(the default is `true`). + (the default is `true`). - `failfast`: if `true`, any test failure or error will cause the testset and any -child testsets to return immediately (the default is `false`). This can also be set -globally via the env var `JULIA_TEST_FAILFAST`. + child testsets to return immediately (the default is `false`). + This can also be set globally via the env var `JULIA_TEST_FAILFAST`. !!! compat "Julia 1.8" `@testset foo()` requires at least Julia 1.8. @@ -1342,7 +1342,8 @@ globally via the env var `JULIA_TEST_FAILFAST`. The description string accepts interpolation from the loop indices. If no description is provided, one is constructed based on the variables. -If a function call is provided, its name will be used. Explicit description strings override this behavior. +If a function call is provided, its name will be used. +Explicit description strings override this behavior. By default the `@testset` macro will return the testset object itself, though this behavior can be customized in other testset types. If a `for` loop is used From 965bc7d89e9f54b92a046a8488994acc41f376c4 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Tue, 13 Dec 2022 19:38:56 +0700 Subject: [PATCH 114/387] Fixups for #47383 (fixes `runbenchmarks("sort")`) (#47822) * add test demonstrating overflow in countsort * fix overflow in countsort * remove unnecessary type annotations (fixes tests) This fixes the test failure because it allows for automatic conversion. The manual for implementing the AbstractArray interface also does not recomend a type signature for the value arg in setindex!. Co-authored-by: Lilith Hafner --- base/sort.jl | 8 ++++---- test/sorting.jl | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/base/sort.jl b/base/sort.jl index 932da36b9e1d61..2dd81829312d07 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -509,12 +509,12 @@ struct WithoutMissingVector{T, U} <: AbstractVector{T} new{nonmissingtype(eltype(data)), typeof(data)}(data) end end -Base.@propagate_inbounds function Base.getindex(v::WithoutMissingVector, i::Integer) +Base.@propagate_inbounds function Base.getindex(v::WithoutMissingVector, i) out = v.data[i] @assert !(out isa Missing) out::eltype(v) end -Base.@propagate_inbounds function Base.setindex!(v::WithoutMissingVector{T}, x::T, i) where T +Base.@propagate_inbounds function Base.setindex!(v::WithoutMissingVector, x, i) v.data[i] = x v end @@ -830,7 +830,7 @@ maybe_reverse(o::ForwardOrdering, x) = x maybe_reverse(o::ReverseOrdering, x) = reverse(x) function _sort!(v::AbstractVector{<:Integer}, ::CountingSort, o::DirectOrdering, kw) @getkw lo hi mn mx scratch - range = o === Reverse ? mn-mx : mx-mn + range = maybe_unsigned(o === Reverse ? mn-mx : mx-mn) offs = 1 - (o === Reverse ? mx : mn) counts = fill(0, range+1) # TODO use scratch (but be aware of type stability) @@ -843,7 +843,7 @@ function _sort!(v::AbstractVector{<:Integer}, ::CountingSort, o::DirectOrdering, lastidx = idx + counts[i] - 1 val = i-offs for j = idx:lastidx - v[j] = val + v[j] = val isa Unsigned && eltype(v) <: Signed ? signed(val) : val end idx = lastidx + 1 end diff --git a/test/sorting.jl b/test/sorting.jl index 37bad7d23c94b0..614946a8cc4f6f 100644 --- a/test/sorting.jl +++ b/test/sorting.jl @@ -765,6 +765,7 @@ end @testset "Unions with missing" begin @test issorted(sort(shuffle!(vcat(fill(missing, 10), rand(Int, 100))))) + @test issorted(sort(vcat(rand(Int8, 600), [missing]))) end @testset "Specific algorithms" begin @@ -897,6 +898,7 @@ end @testset "Count sort near the edge of its range" begin @test issorted(sort(rand(typemin(Int):typemin(Int)+100, 1000))) @test issorted(sort(rand(typemax(Int)-100:typemax(Int), 1000))) + @test issorted(sort(rand(Int8, 600))) end # This testset is at the end of the file because it is slow. From 9fd31361ee8096f089ee823319ba803fcf2fde65 Mon Sep 17 00:00:00 2001 From: Alex Arslan Date: Tue, 13 Dec 2022 12:47:11 -0800 Subject: [PATCH 115/387] Fix the `stage` and `version-check` targets for `make -C deps` (#47881) The variable `DEP_LIBS_STAGED` was removed in PR #40998 but it's still being referenced in other places. Notably, its removal renders `make -C deps stage` and `make -C deps version-check` as no-ops. For reference, the definition of the variable prior to #40998 was ```make DEP_LIBS_STAGED := $(filter-out libsuitesparse-wrapper,$(DEP_LIBS)) ``` Since that PR removed `libsuitesparse-wrapper` entirely, we can simply initialize `DEP_LIBS_STAGED` to `DEP_LIBS`. --- deps/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deps/Makefile b/deps/Makefile index 4f0cc48b019714..244d9a2b588a04 100644 --- a/deps/Makefile +++ b/deps/Makefile @@ -168,6 +168,8 @@ DEP_LIBS += libwhich endif endif +DEP_LIBS_STAGED := $(DEP_LIBS) + # list all targets DEP_LIBS_STAGED_ALL := llvm llvm-tools clang llvmunwind unwind libuv pcre \ openlibm dsfmt blastrampoline openblas lapack gmp mpfr patchelf utf8proc \ From 0fbe56e35e98c606665edeec75412afb5d5bd2fb Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Wed, 14 Dec 2022 11:29:53 +0900 Subject: [PATCH 116/387] redirect line info of `@kwdef` constructor to the definition site (#47887) This should improve the debuggability of constructors defined by `@kwdef`. ```julia julia> @kwdef struct Test_kwdef_lineinfo a::String end julia> Test_kwdef_lineinfo(; a=42) [...] ``` Before: ``` Stacktrace: [1] Test_kwdef_lineinfo(a::Int64) @ Main ./none:2 [2] Test_kwdef_lineinfo(; a::Int64) @ Main ~/julia/julia/base/util.jl:549 [3] kwcall(::NamedTuple{(:a,), Tuple{Int64}}, ::Type{Test_kwdef_lineinfo}) @ Main ~/julia/julia/base/util.jl:549 [4] top-level scope @ none:1 ``` After: ``` Stacktrace: [1] Test_kwdef_lineinfo(a::Int64) @ Main ./none:2 [2] Test_kwdef_lineinfo(; a::Int64) @ Main ./none:1 [3] kwcall(::NamedTuple{(:a,), Tuple{Int64}}, ::Type{Test_kwdef_lineinfo}) @ Main ./none:1 [4] top-level scope @ none:1 ``` --- base/util.jl | 29 ++++++++++++++++------------- test/misc.jl | 19 +++++++++++++++++++ 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/base/util.jl b/base/util.jl index 3ae75a7f58e281..877446ab1887f9 100644 --- a/base/util.jl +++ b/base/util.jl @@ -531,8 +531,7 @@ Stacktrace: """ macro kwdef(expr) expr = macroexpand(__module__, expr) # to expand @static - expr isa Expr && expr.head === :struct || error("Invalid usage of @kwdef") - expr = expr::Expr + isexpr(expr, :struct) || error("Invalid usage of @kwdef") T = expr.args[2] if T isa Expr && T.head === :<: T = T.args[1] @@ -546,29 +545,33 @@ macro kwdef(expr) # overflow on construction if !isempty(params_ex.args) if T isa Symbol - kwdefs = :(($(esc(T)))($params_ex) = ($(esc(T)))($(call_args...))) - elseif T isa Expr && T.head === :curly - T = T::Expr + sig = :(($(esc(T)))($params_ex)) + call = :(($(esc(T)))($(call_args...))) + body = Expr(:block, __source__, call) + kwdefs = Expr(:function, sig, body) + elseif isexpr(T, :curly) # if T == S{A<:AA,B<:BB}, define two methods # S(...) = ... # S{A,B}(...) where {A<:AA,B<:BB} = ... S = T.args[1] P = T.args[2:end] - Q = Any[U isa Expr && U.head === :<: ? U.args[1] : U for U in P] + Q = Any[isexpr(U, :<:) ? U.args[1] : U for U in P] SQ = :($S{$(Q...)}) - kwdefs = quote - ($(esc(S)))($params_ex) =($(esc(S)))($(call_args...)) - ($(esc(SQ)))($params_ex) where {$(esc.(P)...)} = - ($(esc(SQ)))($(call_args...)) - end + body1 = Expr(:block, __source__, :(($(esc(S)))($(call_args...)))) + sig1 = :(($(esc(S)))($params_ex)) + def1 = Expr(:function, sig1, body1) + body2 = Expr(:block, __source__, :(($(esc(SQ)))($(call_args...)))) + sig2 = :(($(esc(SQ)))($params_ex) where {$(esc.(P)...)}) + def2 = Expr(:function, sig2, body2) + kwdefs = Expr(:block, def1, def2) else error("Invalid usage of @kwdef") end else kwdefs = nothing end - quote - Base.@__doc__($(esc(expr))) + return quote + Base.@__doc__ $(esc(expr)) $kwdefs end end diff --git a/test/misc.jl b/test/misc.jl index 8a4b2749788955..8182312f45a6ac 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -1204,6 +1204,25 @@ end end end +@kwdef struct Test_kwdef_lineinfo + a::String +end +@testset "@kwdef constructor line info" begin + for method in methods(Test_kwdef_lineinfo) + @test method.file === Symbol(@__FILE__) + @test ((@__LINE__)-6) ≤ method.line ≤ ((@__LINE__)-5) + end +end +@kwdef struct Test_kwdef_lineinfo_sparam{S<:AbstractString} + a::S +end +@testset "@kwdef constructor line info with static parameter" begin + for method in methods(Test_kwdef_lineinfo_sparam) + @test method.file === Symbol(@__FILE__) + @test ((@__LINE__)-6) ≤ method.line ≤ ((@__LINE__)-5) + end +end + @testset "exports of modules" begin for (_, mod) in Base.loaded_modules mod === Main && continue # Main exports everything From 0fcf896d861984c32f547b6f3f624c1e92f11dfc Mon Sep 17 00:00:00 2001 From: DilumAluthgeBot <43731525+DilumAluthgeBot@users.noreply.github.com> Date: Wed, 14 Dec 2022 09:38:52 -0500 Subject: [PATCH 117/387] =?UTF-8?q?=F0=9F=A4=96=20Bump=20the=20SparseArray?= =?UTF-8?q?s=20stdlib=20from=205f164a0=20to=2072827cd=20(#47894)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dilum Aluthge --- .../md5 | 1 - .../sha512 | 1 - .../md5 | 1 + .../sha512 | 1 + stdlib/SparseArrays.version | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 deps/checksums/SparseArrays-5f164a06067d3efab49f1337d0f3662bbd9960f4.tar.gz/md5 delete mode 100644 deps/checksums/SparseArrays-5f164a06067d3efab49f1337d0f3662bbd9960f4.tar.gz/sha512 create mode 100644 deps/checksums/SparseArrays-72827cd31f0aa346cfc54158f9c6db1738ecd9e4.tar.gz/md5 create mode 100644 deps/checksums/SparseArrays-72827cd31f0aa346cfc54158f9c6db1738ecd9e4.tar.gz/sha512 diff --git a/deps/checksums/SparseArrays-5f164a06067d3efab49f1337d0f3662bbd9960f4.tar.gz/md5 b/deps/checksums/SparseArrays-5f164a06067d3efab49f1337d0f3662bbd9960f4.tar.gz/md5 deleted file mode 100644 index eef5429730f5b9..00000000000000 --- a/deps/checksums/SparseArrays-5f164a06067d3efab49f1337d0f3662bbd9960f4.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -6dadec6acd00f7fc91ab5e0dd8d619ca diff --git a/deps/checksums/SparseArrays-5f164a06067d3efab49f1337d0f3662bbd9960f4.tar.gz/sha512 b/deps/checksums/SparseArrays-5f164a06067d3efab49f1337d0f3662bbd9960f4.tar.gz/sha512 deleted file mode 100644 index 6d6c0c372c9806..00000000000000 --- a/deps/checksums/SparseArrays-5f164a06067d3efab49f1337d0f3662bbd9960f4.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -b954e8e2ff1098cac9c97d5d331c3a1f722299da692ba1a44e19b2dfd7eec4cd6a062ce0bc48b698016e701d632579e663df23b0ec14fa71cf7f9c59f9351945 diff --git a/deps/checksums/SparseArrays-72827cd31f0aa346cfc54158f9c6db1738ecd9e4.tar.gz/md5 b/deps/checksums/SparseArrays-72827cd31f0aa346cfc54158f9c6db1738ecd9e4.tar.gz/md5 new file mode 100644 index 00000000000000..b3823f89e25831 --- /dev/null +++ b/deps/checksums/SparseArrays-72827cd31f0aa346cfc54158f9c6db1738ecd9e4.tar.gz/md5 @@ -0,0 +1 @@ +d98268fc078d79fa7f963d46625a605f diff --git a/deps/checksums/SparseArrays-72827cd31f0aa346cfc54158f9c6db1738ecd9e4.tar.gz/sha512 b/deps/checksums/SparseArrays-72827cd31f0aa346cfc54158f9c6db1738ecd9e4.tar.gz/sha512 new file mode 100644 index 00000000000000..dec122937cf19a --- /dev/null +++ b/deps/checksums/SparseArrays-72827cd31f0aa346cfc54158f9c6db1738ecd9e4.tar.gz/sha512 @@ -0,0 +1 @@ +ed3e4a8a8d5cd24b5de37ea9a861a4b7be9f3938d3aefafa07b0040fba64acc7b7732856bcee9518733a916e2c3839faac9df4a0f79afd97265e54a9b6bc2b41 diff --git a/stdlib/SparseArrays.version b/stdlib/SparseArrays.version index a5f9a0d71d6971..789be577f93e5e 100644 --- a/stdlib/SparseArrays.version +++ b/stdlib/SparseArrays.version @@ -1,4 +1,4 @@ SPARSEARRAYS_BRANCH = main -SPARSEARRAYS_SHA1 = 5f164a06067d3efab49f1337d0f3662bbd9960f4 +SPARSEARRAYS_SHA1 = 72827cd31f0aa346cfc54158f9c6db1738ecd9e4 SPARSEARRAYS_GIT_URL := https://github.com/JuliaSparse/SparseArrays.jl.git SPARSEARRAYS_TAR_URL = https://api.github.com/repos/JuliaSparse/SparseArrays.jl/tarball/$1 From 437ebe15df68c69d9bc64374080f6c6b7b47d52a Mon Sep 17 00:00:00 2001 From: apaz Date: Wed, 14 Dec 2022 11:25:33 -0600 Subject: [PATCH 118/387] Make jl_binding_t into a first-class object (#47592) * Make jl_binding_t into a first-class object (called Binding) * remove special handling for bindings in GC This removes a feature where all bindings were promoted to old if the module containing them was old and cleans up some odd dead code for scanparent. It could be restored explicitly by calling jl_gc_force_mark_old during construction and sweeping. Co-authored-by: Jameson Nash --- base/compiler/abstractinterpretation.jl | 2 +- base/compiler/tfuncs.jl | 4 +- src/builtins.c | 3 +- src/cgutils.cpp | 8 -- src/codegen.cpp | 2 +- src/common_symbols1.inc | 1 - src/common_symbols2.inc | 2 +- src/gc-debug.c | 4 +- src/gc.c | 103 +++--------------------- src/gc.h | 1 - src/jl_exported_data.inc | 1 + src/jltypes.c | 11 +++ src/julia.h | 6 +- src/julia_internal.h | 6 +- src/julia_threads.h | 1 - src/llvm-alloc-helpers.cpp | 3 +- src/llvm-alloc-opt.cpp | 9 +-- src/llvm-final-gc-lowering.cpp | 24 +----- src/llvm-julia-licm.cpp | 5 +- src/llvm-late-gc-lowering.cpp | 8 +- src/llvm-pass-helpers.cpp | 34 +------- src/llvm-pass-helpers.h | 7 -- src/module.c | 12 +-- src/serialize.h | 3 +- src/staticdata.c | 26 +++--- test/core.jl | 8 +- 26 files changed, 72 insertions(+), 222 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 766c27f271b8c6..50ccf9ac25e6bc 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2490,7 +2490,7 @@ function abstract_eval_globalref(g::GlobalRef) g.binding != C_NULL && return Const(ccall(:jl_binding_value, Any, (Ptr{Cvoid},), g.binding)) return Const(getglobal(g.mod, g.name)) end - ty = ccall(:jl_binding_type, Any, (Any, Any), g.mod, g.name) + ty = ccall(:jl_get_binding_type, Any, (Any, Any), g.mod, g.name) ty === nothing && return Any return ty end diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 5dac285de6933e..21b08fd8c872c2 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -2526,7 +2526,7 @@ end function global_assignment_nothrow(M::Module, s::Symbol, @nospecialize(newty)) if isdefined(M, s) && !isconst(M, s) - ty = ccall(:jl_binding_type, Any, (Any, Any), M, s) + ty = ccall(:jl_get_binding_type, Any, (Any, Any), M, s) return ty === nothing || newty ⊑ ty end return false @@ -2536,7 +2536,7 @@ end if M isa Const && s isa Const M, s = M.val, s.val if M isa Module && s isa Symbol - return ccall(:jl_binding_type, Any, (Any, Any), M, s) !== nothing + return ccall(:jl_get_binding_type, Any, (Any, Any), M, s) !== nothing end end return false diff --git a/src/builtins.c b/src/builtins.c index 824f0112d6acb5..6ebad43629c8c0 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1228,7 +1228,7 @@ JL_CALLABLE(jl_f_get_binding_type) JL_TYPECHK(get_binding_type, symbol, args[1]); jl_module_t *mod = (jl_module_t*)args[0]; jl_sym_t *sym = (jl_sym_t*)args[1]; - jl_value_t *ty = jl_binding_type(mod, sym); + jl_value_t *ty = jl_get_binding_type(mod, sym); if (ty == (jl_value_t*)jl_nothing) { jl_binding_t *b = jl_get_binding_wr(mod, sym, 0); if (b && b->owner == mod) { @@ -2057,6 +2057,7 @@ void jl_init_primitives(void) JL_GC_DISABLED add_builtin("UpsilonNode", (jl_value_t*)jl_upsilonnode_type); add_builtin("QuoteNode", (jl_value_t*)jl_quotenode_type); add_builtin("NewvarNode", (jl_value_t*)jl_newvarnode_type); + add_builtin("Binding", (jl_value_t*)jl_binding_type); add_builtin("GlobalRef", (jl_value_t*)jl_globalref_type); add_builtin("NamedTuple", (jl_value_t*)jl_namedtuple_type); diff --git a/src/cgutils.cpp b/src/cgutils.cpp index ba13e1cbe86c92..6a70171aea5f82 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -3488,14 +3488,6 @@ static void emit_write_barrier(jl_codectx_t &ctx, Value *parent, ArrayRef decay_ptrs; - decay_ptrs.push_back(maybe_decay_untracked(ctx, emit_bitcast(ctx, parent, ctx.types().T_prjlvalue))); - decay_ptrs.push_back(maybe_decay_untracked(ctx, emit_bitcast(ctx, ptr, ctx.types().T_prjlvalue))); - ctx.builder.CreateCall(prepare_call(jl_write_barrier_binding_func), decay_ptrs); -} - static void find_perm_offsets(jl_datatype_t *typ, SmallVector &res, unsigned offset) { // This is a inlined field at `offset`. diff --git a/src/codegen.cpp b/src/codegen.cpp index 024f30ad576e93..b37364ef80a272 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2604,7 +2604,7 @@ static void emit_globalset(jl_codectx_t &ctx, jl_binding_t *bnd, Value *bp, cons StoreInst *v = ctx.builder.CreateAlignedStore(rval, julia_binding_pvalue(ctx, bp), Align(sizeof(void*))); v->setOrdering(Order); tbaa_decorate(ctx.tbaa().tbaa_binding, v); - emit_write_barrier_binding(ctx, bp, rval); + emit_write_barrier(ctx, bp, rval); return; } } diff --git a/src/common_symbols1.inc b/src/common_symbols1.inc index 867961bc9a1d22..547d5d0eabede6 100644 --- a/src/common_symbols1.inc +++ b/src/common_symbols1.inc @@ -96,4 +96,3 @@ jl_symbol("structdiff"), jl_symbol("undef"), jl_symbol("sizeof"), jl_symbol("String"), -jl_symbol("namedtuple.jl"), diff --git a/src/common_symbols2.inc b/src/common_symbols2.inc index c9f4e41b83e33f..b5a334172dd76f 100644 --- a/src/common_symbols2.inc +++ b/src/common_symbols2.inc @@ -1,3 +1,4 @@ +jl_symbol("namedtuple.jl"), jl_symbol("pop"), jl_symbol("inbounds"), jl_symbol("strings/string.jl"), @@ -251,4 +252,3 @@ jl_symbol("view"), jl_symbol("GitError"), jl_symbol("zeros"), jl_symbol("InexactError"), -jl_symbol("LogLevel"), diff --git a/src/gc-debug.c b/src/gc-debug.c index 3f60ca17e0dc49..011788fbc7b2e6 100644 --- a/src/gc-debug.c +++ b/src/gc-debug.c @@ -1385,8 +1385,8 @@ NOINLINE void gc_mark_loop_unwind(jl_ptls_t ptls, jl_gc_mark_sp_t sp, int pc_off jl_safe_printf("Mark stack unwind overflow -- ABORTING !!!\n"); break; } - jl_safe_printf("%p: %s Module (bindings) %p (bits %d) -- [%p, %p)\n", - (void*)data, prefix, (void*)data->parent, (int)data->bits, + jl_safe_printf("%p: %s Module (bindings) %p -- [%p, %p)\n", + (void*)data, prefix, (void*)data->parent, (void*)data->begin, (void*)data->end); } else { diff --git a/src/gc.c b/src/gc.c index aebf3300a71a91..92e7e00a6c2810 100644 --- a/src/gc.c +++ b/src/gc.c @@ -1467,7 +1467,9 @@ static jl_taggedvalue_t **sweep_page(jl_gc_pool_t *p, jl_gc_pagemeta_t *pg, jl_t else { // marked young or old if (*ages & msk || bits == GC_OLD_MARKED) { // old enough // `!age && bits == GC_OLD_MARKED` is possible for - // non-first-class objects like `jl_binding_t` + // non-first-class objects like array buffers + // (they may get promoted by jl_gc_wb_buf for example, + // or explicitly by jl_gc_force_mark_old) if (sweep_full || bits == GC_MARKED) { bits = v->bits.gc = GC_OLD; // promote } @@ -1751,14 +1753,6 @@ void jl_gc_queue_multiroot(const jl_value_t *parent, const jl_value_t *ptr) JL_N } } -JL_DLLEXPORT void jl_gc_queue_binding(jl_binding_t *bnd) -{ - jl_ptls_t ptls = jl_current_task->ptls; - jl_taggedvalue_t *buf = jl_astaggedvalue(bnd); - buf->bits.gc = GC_MARKED; - arraylist_push(&ptls->heap.rem_bindings, bnd); -} - #ifdef JL_DEBUG_BUILD static void *volatile gc_findval; // for usage from gdb, for finding the gc-root for a value @@ -2543,61 +2537,18 @@ module_binding: { gc_mark_binding_t *binding = gc_pop_markdata(&sp, gc_mark_binding_t); jl_binding_t **begin = binding->begin; jl_binding_t **end = binding->end; - uint8_t mbits = binding->bits; for (; begin < end; begin += 2) { jl_binding_t *b = *begin; if (b == (jl_binding_t*)HT_NOTFOUND) continue; - if (jl_object_in_image((jl_value_t*)b)) { - jl_taggedvalue_t *buf = jl_astaggedvalue(b); - uintptr_t tag = buf->header; - uint8_t bits; - if (!gc_marked(tag)) - gc_setmark_tag(buf, GC_OLD_MARKED, tag, &bits); - } - else { - gc_setmark_buf_(ptls, b, mbits, sizeof(jl_binding_t)); - } - void *vb = jl_astaggedvalue(b); - verify_parent1("module", binding->parent, &vb, "binding_buff"); + verify_parent1("module", binding->parent, begin, "binding_buff"); // Record the size used for the box for non-const bindings gc_heap_snapshot_record_module_to_binding(binding->parent, b); - (void)vb; - jl_value_t *ty = jl_atomic_load_relaxed(&b->ty); - if (ty && ty != (jl_value_t*)jl_any_type) { - verify_parent2("module", binding->parent, - &b->ty, "binding(%s)", jl_symbol_name(b->name)); - if (gc_try_setmark(ty, &binding->nptr, &tag, &bits)) { - new_obj = ty; - gc_repush_markdata(&sp, gc_mark_binding_t); - goto mark; - } - } - jl_value_t *value = jl_atomic_load_relaxed(&b->value); - jl_value_t *globalref = jl_atomic_load_relaxed(&b->globalref); - if (value) { - verify_parent2("module", binding->parent, - &b->value, "binding(%s)", jl_symbol_name(b->name)); - if (gc_try_setmark(value, &binding->nptr, &tag, &bits)) { - new_obj = value; - begin += 2; - binding->begin = begin; - gc_repush_markdata(&sp, gc_mark_binding_t); - uintptr_t gr_tag; - uint8_t gr_bits; - if (gc_try_setmark(globalref, &binding->nptr, &gr_tag, &gr_bits)) { - gc_mark_marked_obj_t data = {globalref, gr_tag, gr_bits}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(marked_obj), - &data, sizeof(data), 1); - } - goto mark; - } - } - if (gc_try_setmark(globalref, &binding->nptr, &tag, &bits)) { + if (gc_try_setmark((jl_value_t*)b, &binding->nptr, &tag, &bits)) { begin += 2; binding->begin = begin; gc_repush_markdata(&sp, gc_mark_binding_t); - new_obj = globalref; + new_obj = (jl_value_t*)b; goto mark; } } @@ -2614,6 +2565,7 @@ module_binding: { gc_mark_objarray_t data = {(jl_value_t*)m, objary_begin, objary_end, 1, binding->nptr}; gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(objarray), &data, sizeof(data), 0); + // gc_mark_scan_objarray will eventually handle the remset for m if (!scanparent) { objary = (gc_mark_objarray_t*)sp.data; goto objarray_loaded; @@ -2622,6 +2574,7 @@ module_binding: { sp.pc++; } else { + // done with m gc_mark_push_remset(ptls, (jl_value_t*)m, binding->nptr); } if (scanparent) { @@ -2810,7 +2763,7 @@ mark: { jl_binding_t **table = (jl_binding_t**)m->bindings.table; size_t bsize = m->bindings.size; uintptr_t nptr = ((bsize + m->usings.len + 1) << 2) | (bits & GC_OLD); - gc_mark_binding_t markdata = {m, table + 1, table + bsize, nptr, bits}; + gc_mark_binding_t markdata = {m, table + 1, table + bsize, nptr}; gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(module_binding), &markdata, sizeof(markdata), 0); sp.data = (jl_gc_mark_data_t *)(((char*)sp.data) + sizeof(markdata)); @@ -3181,12 +3134,6 @@ static void jl_gc_premark(jl_ptls_t ptls2) objprofile_count(jl_typeof(item), 2, 0); jl_astaggedvalue(item)->bits.gc = GC_OLD_MARKED; } - len = ptls2->heap.rem_bindings.len; - items = ptls2->heap.rem_bindings.items; - for (size_t i = 0; i < len; i++) { - void *ptr = items[i]; - jl_astaggedvalue(ptr)->bits.gc = GC_OLD_MARKED; - } } static void jl_gc_queue_remset(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, jl_ptls_t ptls2) @@ -3195,29 +3142,6 @@ static void jl_gc_queue_remset(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp void **items = ptls2->heap.last_remset->items; for (size_t i = 0; i < len; i++) gc_mark_queue_scan_obj(gc_cache, sp, (jl_value_t*)items[i]); - int n_bnd_refyoung = 0; - len = ptls2->heap.rem_bindings.len; - items = ptls2->heap.rem_bindings.items; - for (size_t i = 0; i < len; i++) { - jl_binding_t *ptr = (jl_binding_t*)items[i]; - // A null pointer can happen here when the binding is cleaned up - // as an exception is thrown after it was already queued (#10221) - int bnd_refyoung = 0; - jl_value_t *v = jl_atomic_load_relaxed(&ptr->value); - if (v != NULL && gc_mark_queue_obj(gc_cache, sp, v)) - bnd_refyoung = 1; - jl_value_t *ty = jl_atomic_load_relaxed(&ptr->ty); - if (ty != NULL && gc_mark_queue_obj(gc_cache, sp, ty)) - bnd_refyoung = 1; - jl_value_t *globalref = jl_atomic_load_relaxed(&ptr->globalref); - if (globalref != NULL && gc_mark_queue_obj(gc_cache, sp, globalref)) - bnd_refyoung = 1; - if (bnd_refyoung) { - items[n_bnd_refyoung] = ptr; - n_bnd_refyoung++; - } - } - ptls2->heap.rem_bindings.len = n_bnd_refyoung; } static void jl_gc_queue_bt_buf(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, jl_ptls_t ptls2) @@ -3263,7 +3187,7 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) jl_ptls_t ptls2 = gc_all_tls_states[t_i]; if (ptls2 == NULL) continue; - // 2.1. mark every object in the `last_remsets` and `rem_binding` + // 2.1. mark every object in the `last_remsets` jl_gc_queue_remset(gc_cache, &sp, ptls2); // 2.2. mark every thread local root jl_gc_queue_thread_local(gc_cache, &sp, ptls2); @@ -3438,16 +3362,12 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) continue; if (!sweep_full) { for (int i = 0; i < ptls2->heap.remset->len; i++) { - jl_astaggedvalue(ptls2->heap.remset->items[i])->bits.gc = GC_MARKED; - } - for (int i = 0; i < ptls2->heap.rem_bindings.len; i++) { - void *ptr = ptls2->heap.rem_bindings.items[i]; + void *ptr = ptls2->heap.remset->items[i]; jl_astaggedvalue(ptr)->bits.gc = GC_MARKED; } } else { ptls2->heap.remset->len = 0; - ptls2->heap.rem_bindings.len = 0; } } @@ -3636,7 +3556,6 @@ void jl_init_thread_heap(jl_ptls_t ptls) heap->mallocarrays = NULL; heap->mafreelist = NULL; heap->big_objects = NULL; - arraylist_new(&heap->rem_bindings, 0); heap->remset = &heap->_remset[0]; heap->last_remset = &heap->_remset[1]; arraylist_new(heap->remset, 0); diff --git a/src/gc.h b/src/gc.h index 7b02df69abbc12..dfd42130898808 100644 --- a/src/gc.h +++ b/src/gc.h @@ -191,7 +191,6 @@ typedef struct { jl_binding_t **begin; // The first slot to be scanned. jl_binding_t **end; // The end address (after the last slot to be scanned) uintptr_t nptr; // See notes about `nptr` above. - uint8_t bits; // GC bits of the module (the bits to mark the binding buffer with) } gc_mark_binding_t; // Finalizer (or object) list diff --git a/src/jl_exported_data.inc b/src/jl_exported_data.inc index 6f0671ef0d6f70..a254fba5e2b283 100644 --- a/src/jl_exported_data.inc +++ b/src/jl_exported_data.inc @@ -44,6 +44,7 @@ XX(jl_float64_type) \ XX(jl_floatingpoint_type) \ XX(jl_function_type) \ + XX(jl_binding_type) \ XX(jl_globalref_type) \ XX(jl_gotoifnot_type) \ XX(jl_gotonode_type) \ diff --git a/src/jltypes.c b/src/jltypes.c index b73d5ecab82aa1..d77d711f71a3a0 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -2727,6 +2727,16 @@ void jl_init_types(void) JL_GC_DISABLED jl_value_t *pointer_void = jl_apply_type1((jl_value_t*)jl_pointer_type, (jl_value_t*)jl_nothing_type); + jl_binding_type = + jl_new_datatype(jl_symbol("Binding"), core, jl_any_type, jl_emptysvec, + jl_perm_symsvec(6, "name", "value", "globalref", "owner", "ty", "flags"), + jl_svec(6, jl_symbol_type, jl_any_type, jl_any_type/*jl_globalref_type*/, jl_module_type, jl_any_type, jl_uint8_type), + jl_emptysvec, 0, 1, 1); + const static uint32_t binding_constfields[1] = { 0x0001 }; // Set fields 1 as const + const static uint32_t binding_atomicfields[1] = { 0x0016 }; // Set fields 2, 3, 5 as atomic + jl_binding_type->name->constfields = binding_constfields; + jl_binding_type->name->atomicfields = binding_atomicfields; + jl_globalref_type = jl_new_datatype(jl_symbol("GlobalRef"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(3, "mod", "name", "binding"), @@ -2772,6 +2782,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_svecset(jl_method_instance_type->types, 6, jl_code_instance_type); jl_svecset(jl_code_instance_type->types, 13, jl_voidpointer_type); jl_svecset(jl_code_instance_type->types, 14, jl_voidpointer_type); + jl_svecset(jl_binding_type->types, 2, jl_globalref_type); jl_compute_field_offsets(jl_datatype_type); jl_compute_field_offsets(jl_typename_type); diff --git a/src/julia.h b/src/julia.h index 2c9be8ae1aa2a3..a27b66241793ae 100644 --- a/src/julia.h +++ b/src/julia.h @@ -560,7 +560,7 @@ typedef struct { } jl_weakref_t; typedef struct { - // not first-class + JL_DATA_TYPE jl_sym_t *name; _Atomic(jl_value_t*) value; _Atomic(jl_value_t*) globalref; // cached GlobalRef for this binding @@ -779,6 +779,7 @@ extern JL_DLLIMPORT jl_value_t *jl_array_symbol_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_array_int32_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_array_uint64_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_expr_type JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_datatype_t *jl_binding_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_globalref_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_linenumbernode_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_gotonode_type JL_GLOBALLY_ROOTED; @@ -1235,6 +1236,7 @@ static inline int jl_is_layout_opaque(const jl_datatype_layout_t *l) JL_NOTSAFEP #define jl_is_ssavalue(v) jl_typeis(v,jl_ssavalue_type) #define jl_is_slot(v) (jl_typeis(v,jl_slotnumber_type) || jl_typeis(v,jl_typedslot_type)) #define jl_is_expr(v) jl_typeis(v,jl_expr_type) +#define jl_is_binding(v) jl_typeis(v,jl_binding_type) #define jl_is_globalref(v) jl_typeis(v,jl_globalref_type) #define jl_is_gotonode(v) jl_typeis(v,jl_gotonode_type) #define jl_is_gotoifnot(v) jl_typeis(v,jl_gotoifnot_type) @@ -1623,7 +1625,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_ JL_DLLEXPORT jl_binding_t *jl_get_binding_or_error(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT jl_binding_t *jl_get_binding_if_bound(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT jl_value_t *jl_module_globalref(jl_module_t *m, jl_sym_t *var); -JL_DLLEXPORT jl_value_t *jl_binding_type(jl_module_t *m, jl_sym_t *var); +JL_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var); // get binding for assignment JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int alloc); JL_DLLEXPORT jl_binding_t *jl_get_binding_wr_or_error(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); diff --git a/src/julia_internal.h b/src/julia_internal.h index 1e59cf6f18b5ab..f310770bdf9b11 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -560,14 +560,11 @@ void jl_gc_run_all_finalizers(jl_task_t *ct); void jl_release_task_stack(jl_ptls_t ptls, jl_task_t *task); void jl_gc_add_finalizer_(jl_ptls_t ptls, void *v, void *f) JL_NOTSAFEPOINT; -JL_DLLEXPORT void jl_gc_queue_binding(jl_binding_t *bnd) JL_NOTSAFEPOINT; void gc_setmark_buf(jl_ptls_t ptls, void *buf, uint8_t, size_t) JL_NOTSAFEPOINT; STATIC_INLINE void jl_gc_wb_binding(jl_binding_t *bnd, void *val) JL_NOTSAFEPOINT // val isa jl_value_t* { - if (__unlikely(jl_astaggedvalue(bnd)->bits.gc == 3 && - (jl_astaggedvalue(val)->bits.gc & 1) == 0)) - jl_gc_queue_binding(bnd); + jl_gc_wb(bnd, val); } STATIC_INLINE void jl_gc_wb_buf(void *parent, void *bufptr, size_t minsz) JL_NOTSAFEPOINT // parent isa jl_value_t* @@ -1543,6 +1540,7 @@ extern JL_DLLEXPORT jl_sym_t *jl_return_sym; extern JL_DLLEXPORT jl_sym_t *jl_lineinfo_sym; extern JL_DLLEXPORT jl_sym_t *jl_lambda_sym; extern JL_DLLEXPORT jl_sym_t *jl_assign_sym; +extern JL_DLLEXPORT jl_sym_t *jl_binding_sym; extern JL_DLLEXPORT jl_sym_t *jl_globalref_sym; extern JL_DLLEXPORT jl_sym_t *jl_do_sym; extern JL_DLLEXPORT jl_sym_t *jl_method_sym; diff --git a/src/julia_threads.h b/src/julia_threads.h index 847465b363a2e1..ff28765346fbc4 100644 --- a/src/julia_threads.h +++ b/src/julia_threads.h @@ -150,7 +150,6 @@ typedef struct { struct _bigval_t *big_objects; // variables for tracking "remembered set" - arraylist_t rem_bindings; arraylist_t _remset[2]; // contains jl_value_t* // lower bound of the number of pointers inside remembered values int remset_nptr; diff --git a/src/llvm-alloc-helpers.cpp b/src/llvm-alloc-helpers.cpp index 7a80985cf02196..79aa270094d2b0 100644 --- a/src/llvm-alloc-helpers.cpp +++ b/src/llvm-alloc-helpers.cpp @@ -213,8 +213,7 @@ void jl_alloc::runEscapeAnalysis(llvm::Instruction *I, EscapeAnalysisRequiredArg assert(use->get() == I); return true; } - if (required.pass.write_barrier_func == callee || - required.pass.write_barrier_binding_func == callee) + if (required.pass.write_barrier_func == callee) return true; auto opno = use->getOperandNo(); // Uses in `jl_roots` operand bundle are not counted as escaping, everything else is. diff --git a/src/llvm-alloc-opt.cpp b/src/llvm-alloc-opt.cpp index c04a5cd3af625c..a611f71d2bc11a 100644 --- a/src/llvm-alloc-opt.cpp +++ b/src/llvm-alloc-opt.cpp @@ -662,8 +662,7 @@ void Optimizer::moveToStack(CallInst *orig_inst, size_t sz, bool has_ref) } return; } - if (pass.write_barrier_func == callee || - pass.write_barrier_binding_func == callee) { + if (pass.write_barrier_func == callee) { ++RemovedWriteBarriers; call->eraseFromParent(); return; @@ -771,8 +770,7 @@ void Optimizer::removeAlloc(CallInst *orig_inst) call->eraseFromParent(); return; } - if (pass.write_barrier_func == callee || - pass.write_barrier_binding_func == callee) { + if (pass.write_barrier_func == callee) { ++RemovedWriteBarriers; call->eraseFromParent(); return; @@ -1070,8 +1068,7 @@ void Optimizer::splitOnStack(CallInst *orig_inst) call->eraseFromParent(); return; } - if (pass.write_barrier_func == callee || - pass.write_barrier_binding_func == callee) { + if (pass.write_barrier_func == callee) { ++RemovedWriteBarriers; call->eraseFromParent(); return; diff --git a/src/llvm-final-gc-lowering.cpp b/src/llvm-final-gc-lowering.cpp index 2eb89a15692d9d..8c71c0bae58418 100644 --- a/src/llvm-final-gc-lowering.cpp +++ b/src/llvm-final-gc-lowering.cpp @@ -26,7 +26,6 @@ STATISTIC(PopGCFrameCount, "Number of lowered popGCFrameFunc intrinsics"); STATISTIC(GetGCFrameSlotCount, "Number of lowered getGCFrameSlotFunc intrinsics"); STATISTIC(GCAllocBytesCount, "Number of lowered GCAllocBytesFunc intrinsics"); STATISTIC(QueueGCRootCount, "Number of lowered queueGCRootFunc intrinsics"); -STATISTIC(QueueGCBindingCount, "Number of lowered queueGCBindingFunc intrinsics"); using namespace llvm; @@ -46,7 +45,6 @@ struct FinalLowerGC: private JuliaPassContext { private: Function *queueRootFunc; - Function *queueBindingFunc; Function *poolAllocFunc; Function *bigAllocFunc; Instruction *pgcstack; @@ -68,9 +66,6 @@ struct FinalLowerGC: private JuliaPassContext { // Lowers a `julia.queue_gc_root` intrinsic. Value *lowerQueueGCRoot(CallInst *target, Function &F); - - // Lowers a `julia.queue_gc_binding` intrinsic. - Value *lowerQueueGCBinding(CallInst *target, Function &F); }; Value *FinalLowerGC::lowerNewGCFrame(CallInst *target, Function &F) @@ -193,14 +188,6 @@ Value *FinalLowerGC::lowerQueueGCRoot(CallInst *target, Function &F) return target; } -Value *FinalLowerGC::lowerQueueGCBinding(CallInst *target, Function &F) -{ - ++QueueGCBindingCount; - assert(target->arg_size() == 1); - target->setCalledFunction(queueBindingFunc); - return target; -} - Value *FinalLowerGC::lowerGCAllocBytes(CallInst *target, Function &F) { ++GCAllocBytesCount; @@ -234,11 +221,10 @@ bool FinalLowerGC::doInitialization(Module &M) { // Initialize platform-specific references. queueRootFunc = getOrDeclare(jl_well_known::GCQueueRoot); - queueBindingFunc = getOrDeclare(jl_well_known::GCQueueBinding); poolAllocFunc = getOrDeclare(jl_well_known::GCPoolAlloc); bigAllocFunc = getOrDeclare(jl_well_known::GCBigAlloc); - GlobalValue *functionList[] = {queueRootFunc, queueBindingFunc, poolAllocFunc, bigAllocFunc}; + GlobalValue *functionList[] = {queueRootFunc, poolAllocFunc, bigAllocFunc}; unsigned j = 0; for (unsigned i = 0; i < sizeof(functionList) / sizeof(void*); i++) { if (!functionList[i]) @@ -254,8 +240,8 @@ bool FinalLowerGC::doInitialization(Module &M) { bool FinalLowerGC::doFinalization(Module &M) { - GlobalValue *functionList[] = {queueRootFunc, queueBindingFunc, poolAllocFunc, bigAllocFunc}; - queueRootFunc = queueBindingFunc = poolAllocFunc = bigAllocFunc = nullptr; + GlobalValue *functionList[] = {queueRootFunc, poolAllocFunc, bigAllocFunc}; + queueRootFunc = poolAllocFunc = bigAllocFunc = nullptr; auto used = M.getGlobalVariable("llvm.compiler.used"); if (!used) return false; @@ -320,7 +306,6 @@ bool FinalLowerGC::runOnFunction(Function &F) auto getGCFrameSlotFunc = getOrNull(jl_intrinsics::getGCFrameSlot); auto GCAllocBytesFunc = getOrNull(jl_intrinsics::GCAllocBytes); auto queueGCRootFunc = getOrNull(jl_intrinsics::queueGCRoot); - auto queueGCBindingFunc = getOrNull(jl_intrinsics::queueGCBinding); // Lower all calls to supported intrinsics. for (BasicBlock &BB : F) { @@ -353,9 +338,6 @@ bool FinalLowerGC::runOnFunction(Function &F) else if (callee == queueGCRootFunc) { replaceInstruction(CI, lowerQueueGCRoot(CI, F), it); } - else if (callee == queueGCBindingFunc) { - replaceInstruction(CI, lowerQueueGCBinding(CI, F), it); - } else { ++it; } diff --git a/src/llvm-julia-licm.cpp b/src/llvm-julia-licm.cpp index d641d61ca126b6..28dddf18c5394a 100644 --- a/src/llvm-julia-licm.cpp +++ b/src/llvm-julia-licm.cpp @@ -156,7 +156,7 @@ struct JuliaLICM : public JuliaPassContext { // `gc_preserve_end_func` is optional since the input to // `gc_preserve_end_func` must be from `gc_preserve_begin_func`. // We also hoist write barriers here, so we don't exit if write_barrier_func exists - if (!gc_preserve_begin_func && !write_barrier_func && !write_barrier_binding_func && + if (!gc_preserve_begin_func && !write_barrier_func && !alloc_obj_func) return false; auto LI = &GetLI(); @@ -235,8 +235,7 @@ struct JuliaLICM : public JuliaPassContext { createNewInstruction(CI, call, MSSAU); } } - else if (callee == write_barrier_func || - callee == write_barrier_binding_func) { + else if (callee == write_barrier_func) { bool valid = true; for (std::size_t i = 0; i < call->arg_size(); i++) { if (!makeLoopInvariant(L, call->getArgOperand(i), diff --git a/src/llvm-late-gc-lowering.cpp b/src/llvm-late-gc-lowering.cpp index 08376426b855dd..20fc78b4d742f7 100644 --- a/src/llvm-late-gc-lowering.cpp +++ b/src/llvm-late-gc-lowering.cpp @@ -1607,7 +1607,7 @@ State LateLowerGCFrame::LocalScan(Function &F) { callee == gc_preserve_end_func || callee == typeof_func || callee == pgcstack_getter || callee->getName() == XSTR(jl_egal__unboxed) || callee->getName() == XSTR(jl_lock_value) || callee->getName() == XSTR(jl_unlock_value) || - callee == write_barrier_func || callee == write_barrier_binding_func || + callee == write_barrier_func || callee->getName() == "memcmp") { continue; } @@ -2420,8 +2420,7 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S, bool *CFGModified) { typ->takeName(CI); CI->replaceAllUsesWith(typ); UpdatePtrNumbering(CI, typ, S); - } else if ((write_barrier_func && callee == write_barrier_func) || - (write_barrier_binding_func && callee == write_barrier_binding_func)) { + } else if (write_barrier_func && callee == write_barrier_func) { // The replacement for this requires creating new BasicBlocks // which messes up the loop. Queue all of them to be replaced later. assert(CI->arg_size() >= 1); @@ -2533,9 +2532,6 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S, bool *CFGModified) { if (CI->getCalledOperand() == write_barrier_func) { builder.CreateCall(getOrDeclare(jl_intrinsics::queueGCRoot), parent); } - else if (CI->getCalledOperand() == write_barrier_binding_func) { - builder.CreateCall(getOrDeclare(jl_intrinsics::queueGCBinding), parent); - } else { assert(false); } diff --git a/src/llvm-pass-helpers.cpp b/src/llvm-pass-helpers.cpp index 3b55339984516b..8e4045d14b80dd 100644 --- a/src/llvm-pass-helpers.cpp +++ b/src/llvm-pass-helpers.cpp @@ -27,8 +27,7 @@ JuliaPassContext::JuliaPassContext() gc_preserve_begin_func(nullptr), gc_preserve_end_func(nullptr), pointer_from_objref_func(nullptr), alloc_obj_func(nullptr), typeof_func(nullptr), write_barrier_func(nullptr), - write_barrier_binding_func(nullptr), call_func(nullptr), - call2_func(nullptr), module(nullptr) + call_func(nullptr), call2_func(nullptr), module(nullptr) { } @@ -51,7 +50,6 @@ void JuliaPassContext::initFunctions(Module &M) pointer_from_objref_func = M.getFunction("julia.pointer_from_objref"); typeof_func = M.getFunction("julia.typeof"); write_barrier_func = M.getFunction("julia.write_barrier"); - write_barrier_binding_func = M.getFunction("julia.write_barrier_binding"); alloc_obj_func = M.getFunction("julia.gc_alloc_obj"); call_func = M.getFunction("julia.call"); call2_func = M.getFunction("julia.call2"); @@ -118,7 +116,6 @@ namespace jl_intrinsics { static const char *PUSH_GC_FRAME_NAME = "julia.push_gc_frame"; static const char *POP_GC_FRAME_NAME = "julia.pop_gc_frame"; static const char *QUEUE_GC_ROOT_NAME = "julia.queue_gc_root"; - static const char *QUEUE_GC_BINDING_NAME = "julia.queue_gc_binding"; // Annotates a function with attributes suitable for GC allocation // functions. Specifically, the return value is marked noalias and nonnull. @@ -210,27 +207,12 @@ namespace jl_intrinsics { intrinsic->addFnAttr(Attribute::InaccessibleMemOrArgMemOnly); return intrinsic; }); - - const IntrinsicDescription queueGCBinding( - QUEUE_GC_BINDING_NAME, - [](const JuliaPassContext &context) { - auto intrinsic = Function::Create( - FunctionType::get( - Type::getVoidTy(context.getLLVMContext()), - { context.T_prjlvalue }, - false), - Function::ExternalLinkage, - QUEUE_GC_BINDING_NAME); - intrinsic->addFnAttr(Attribute::InaccessibleMemOrArgMemOnly); - return intrinsic; - }); } namespace jl_well_known { static const char *GC_BIG_ALLOC_NAME = XSTR(jl_gc_big_alloc); static const char *GC_POOL_ALLOC_NAME = XSTR(jl_gc_pool_alloc); static const char *GC_QUEUE_ROOT_NAME = XSTR(jl_gc_queue_root); - static const char *GC_QUEUE_BINDING_NAME = XSTR(jl_gc_queue_binding); using jl_intrinsics::addGCAllocAttributes; @@ -265,20 +247,6 @@ namespace jl_well_known { return addGCAllocAttributes(poolAllocFunc, context.getLLVMContext()); }); - const WellKnownFunctionDescription GCQueueBinding( - GC_QUEUE_BINDING_NAME, - [](const JuliaPassContext &context) { - auto func = Function::Create( - FunctionType::get( - Type::getVoidTy(context.getLLVMContext()), - { context.T_prjlvalue }, - false), - Function::ExternalLinkage, - GC_QUEUE_BINDING_NAME); - func->addFnAttr(Attribute::InaccessibleMemOrArgMemOnly); - return func; - }); - const WellKnownFunctionDescription GCQueueRoot( GC_QUEUE_ROOT_NAME, [](const JuliaPassContext &context) { diff --git a/src/llvm-pass-helpers.h b/src/llvm-pass-helpers.h index 68f6efe42be6de..4774f876128711 100644 --- a/src/llvm-pass-helpers.h +++ b/src/llvm-pass-helpers.h @@ -58,7 +58,6 @@ struct JuliaPassContext { llvm::Function *alloc_obj_func; llvm::Function *typeof_func; llvm::Function *write_barrier_func; - llvm::Function *write_barrier_binding_func; llvm::Function *call_func; llvm::Function *call2_func; @@ -126,9 +125,6 @@ namespace jl_intrinsics { // `julia.queue_gc_root`: an intrinsic that queues a GC root. extern const IntrinsicDescription queueGCRoot; - - // `julia.queue_gc_binding`: an intrinsic that queues a binding for GC. - extern const IntrinsicDescription queueGCBinding; } // A namespace for well-known Julia runtime function descriptions. @@ -149,9 +145,6 @@ namespace jl_well_known { // `jl_gc_queue_root`: queues a GC root. extern const WellKnownFunctionDescription GCQueueRoot; - - // `jl_gc_queue_binding`: queues a binding for GC. - extern const WellKnownFunctionDescription GCQueueBinding; } #endif diff --git a/src/module.c b/src/module.c index ec62e6d83f2aa3..f4187d23ad4625 100644 --- a/src/module.c +++ b/src/module.c @@ -160,7 +160,7 @@ static jl_binding_t *new_binding(jl_sym_t *name) { jl_task_t *ct = jl_current_task; assert(jl_is_symbol(name)); - jl_binding_t *b = (jl_binding_t*)jl_gc_alloc_buf(ct->ptls, sizeof(jl_binding_t)); + jl_binding_t *b = (jl_binding_t*)jl_gc_alloc(ct->ptls, sizeof(jl_binding_t), jl_binding_type); b->name = name; jl_atomic_store_relaxed(&b->value, NULL); b->owner = NULL; @@ -197,7 +197,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, b->owner = m; *bp = b; JL_GC_PROMISE_ROOTED(b); - jl_gc_wb_buf(m, b, sizeof(jl_binding_t)); + jl_gc_wb(m, b); } else { b = NULL; @@ -263,7 +263,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_ b->owner = m; *bp = b; JL_GC_PROMISE_ROOTED(b); - jl_gc_wb_buf(m, b, sizeof(jl_binding_t)); + jl_gc_wb(m, b); } JL_UNLOCK(&m->lock); @@ -393,7 +393,7 @@ JL_DLLEXPORT jl_value_t *jl_binding_owner(jl_module_t *m, jl_sym_t *var) } // get type of binding m.var, without resolving the binding -JL_DLLEXPORT jl_value_t *jl_binding_type(jl_module_t *m, jl_sym_t *var) +JL_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var) { JL_LOCK(&m->lock); jl_binding_t *b = _jl_get_module_binding(m, var); @@ -568,7 +568,7 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_s nb->imported = (explici!=0); nb->deprecated = b->deprecated; *bp = nb; - jl_gc_wb_buf(to, nb, sizeof(jl_binding_t)); + jl_gc_wb(to, nb); } JL_UNLOCK(&to->lock); } @@ -647,7 +647,7 @@ JL_DLLEXPORT void jl_module_export(jl_module_t *from, jl_sym_t *s) // don't yet know who the owner is b->owner = NULL; *bp = b; - jl_gc_wb_buf(from, b, sizeof(jl_binding_t)); + jl_gc_wb(from, b); } assert(*bp != HT_NOTFOUND); (*bp)->exportp = 1; diff --git a/src/serialize.h b/src/serialize.h index 020cafc74c962e..afcdcc31d66c4d 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -63,8 +63,9 @@ extern "C" { #define TAG_RETURNNODE 55 #define TAG_ARGUMENT 56 #define TAG_RELOC_METHODROOT 57 +#define TAG_BINDING 58 -#define LAST_TAG 57 +#define LAST_TAG 58 #define write_uint8(s, n) ios_putc((n), (s)) #define read_uint8(s) ((uint8_t)ios_getc((s))) diff --git a/src/staticdata.c b/src/staticdata.c index 2098596b9b612d..786d0c966693b3 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -98,7 +98,7 @@ extern "C" { // TODO: put WeakRefs on the weak_refs list during deserialization // TODO: handle finalizers -#define NUM_TAGS 157 +#define NUM_TAGS 158 // An array of references that need to be restored from the sysimg // This is a manually constructed dual of the gvars array, which would be produced by codegen for Julia code, for C. @@ -120,6 +120,7 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_array_type); INSERT_TAG(jl_typedslot_type); INSERT_TAG(jl_expr_type); + INSERT_TAG(jl_binding_type); INSERT_TAG(jl_globalref_type); INSERT_TAG(jl_string_type); INSERT_TAG(jl_module_type); @@ -383,7 +384,6 @@ enum RefTags { ConstDataRef, // constant data (e.g., layouts) TagRef, // items serialized via their tags SymbolRef, // symbols - BindingRef, // module bindings FunctionRef, // generic functions BuiltinFunctionRef, // builtin functions ExternalLinkage // items defined externally (used when serializing packages) @@ -425,11 +425,6 @@ static void write_reloc_t(ios_t *s, uintptr_t reloc_id) JL_NOTSAFEPOINT } } -static int jl_is_binding(uintptr_t v) JL_NOTSAFEPOINT -{ - return jl_typeis(v, (jl_datatype_t*)jl_buff_tag); -} - // Reporting to PkgCacheInspector typedef struct { size_t sysdata; @@ -971,11 +966,11 @@ static void write_pointerfield(jl_serializer_state *s, jl_value_t *fld) JL_NOTSA // Save blank space in stream `s` for a pointer `fld`, storing both location and target // in `gctags_list`. -static void write_gctaggedfield(jl_serializer_state *s, uintptr_t ref) JL_NOTSAFEPOINT +static void write_gctaggedfield(jl_serializer_state *s, jl_datatype_t *ref) JL_NOTSAFEPOINT { // jl_printf(JL_STDOUT, "gctaggedfield: position %p, value 0x%lx\n", (void*)(uintptr_t)ios_pos(s->s), ref); arraylist_push(&s->gctags_list, (void*)(uintptr_t)ios_pos(s->s)); - arraylist_push(&s->gctags_list, (void*)ref); + arraylist_push(&s->gctags_list, (void*)backref_id(s, ref, s->link_ids_gctags)); write_pointer(s->s); } @@ -1009,7 +1004,7 @@ static void jl_write_module(jl_serializer_state *s, uintptr_t item, jl_module_t jl_binding_t *b = (jl_binding_t*)table[i+1]; write_pointerfield(s, (jl_value_t*)table[i]); tot += sizeof(void*); - write_gctaggedfield(s, (uintptr_t)BindingRef << RELOC_TAG_OFFSET); + write_gctaggedfield(s, jl_binding_type); tot += sizeof(void*); size_t binding_reloc_offset = ios_pos(s->s); ptrhash_put(&bindings, b, (void*)(((uintptr_t)DataRef << RELOC_TAG_OFFSET) + binding_reloc_offset)); @@ -1108,7 +1103,7 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED // write header if (s->incremental && jl_needs_serialization(s, (jl_value_t*)t) && needs_uniquing((jl_value_t*)t)) arraylist_push(&s->uniquing_types, (void*)(uintptr_t)(ios_pos(s->s)|1)); - write_gctaggedfield(s, backref_id(s, t, s->link_ids_gctags)); + write_gctaggedfield(s, t); size_t reloc_offset = ios_pos(s->s); assert(item < layout_table.len && layout_table.items[item] == NULL); layout_table.items[item] = (void*)reloc_offset; // store the inverse mapping of `serialization_order` (`id` => object-as-streampos) @@ -1238,6 +1233,9 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED else if (jl_typeis(v, jl_task_type)) { jl_error("Task cannot be serialized"); } + else if (jl_typeis(v, jl_binding_type)) { + jl_error("Binding cannot be serialized"); // no way (currently) to recover its identity + } else if (jl_is_svec(v)) { ios_write(s->s, (char*)v, sizeof(void*)); size_t ii, l = jl_svec_len(v); @@ -1430,6 +1428,7 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED else if (jl_is_globalref(v)) { jl_globalref_t *newg = (jl_globalref_t*)&s->s->buf[reloc_offset]; // Don't save the cached binding reference in staticdata + // (it does not happen automatically since we declare the struct immutable) // TODO: this should be a relocation pointing to the binding in the new image newg->bnd_cache = NULL; if (s->incremental) @@ -1549,9 +1548,6 @@ static uintptr_t get_reloc_for_item(uintptr_t reloc_item, size_t reloc_offset) case TagRef: assert(offset < 2 * NBOX_C + 258 && "corrupt relocation item id"); break; - case BindingRef: - assert(offset == 0 && "corrupt relocation offset"); - break; case BuiltinFunctionRef: assert(offset < sizeof(id_to_fptrs) / sizeof(*id_to_fptrs) && "unknown function pointer id"); break; @@ -1584,8 +1580,6 @@ static inline uintptr_t get_item_for_reloc(jl_serializer_state *s, uintptr_t bas case SymbolRef: assert(offset < deser_sym.len && deser_sym.items[offset] && "corrupt relocation item id"); return (uintptr_t)deser_sym.items[offset]; - case BindingRef: - return jl_buff_tag | GC_OLD; case TagRef: if (offset == 0) return (uintptr_t)s->ptls->root_task; diff --git a/test/core.jl b/test/core.jl index bab6be0de5644c..96ec765235adb8 100644 --- a/test/core.jl +++ b/test/core.jl @@ -7870,15 +7870,15 @@ end @test methods(SpecializeModuleTest.f)[1].nospecialize & 0b11 == 0b10 let # https://github.com/JuliaLang/julia/issues/46918 - # jl_binding_type shouldn't be unstable + # jl_get_binding_type shouldn't be unstable code = quote - res1 = ccall(:jl_binding_type, Any, (Any, Any), Main, :stderr) + res1 = ccall(:jl_get_binding_type, Any, (Any, Any), Main, :stderr) stderr - res2 = ccall(:jl_binding_type, Any, (Any, Any), Main, :stderr) + res2 = ccall(:jl_get_binding_type, Any, (Any, Any), Main, :stderr) - res3 = ccall(:jl_binding_type, Any, (Any, Any), Main, :stderr) + res3 = ccall(:jl_get_binding_type, Any, (Any, Any), Main, :stderr) print(stdout, res1, " ", res2, " ", res3) end |> x->join(x.args, ';') From c8a05210d86a488ab3c41feb3774bb197ec2d482 Mon Sep 17 00:00:00 2001 From: Alex Arslan Date: Wed, 14 Dec 2022 09:33:22 -0800 Subject: [PATCH 119/387] Optimize TLS access in generated code on FreeBSD (#47891) * Optimize TLS access in generated code on FreeBSD This extends the optimization made for Linux in PR 17178 to FreeBSD. The build and all tests pass locally with this change. * Incorporate FreeBSD D31427 to support 12.2 --- src/threading.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/threading.c b/src/threading.c index dcb57cce23a794..c45143f0d01cac 100644 --- a/src/threading.c +++ b/src/threading.c @@ -13,7 +13,7 @@ // Ref https://www.uclibc.org/docs/tls.pdf // For variant 1 JL_ELF_TLS_INIT_SIZE is the size of the thread control block (TCB) // For variant 2 JL_ELF_TLS_INIT_SIZE is 0 -#ifdef _OS_LINUX_ +#if defined(_OS_LINUX_) || defined(_OS_FREEBSD_) # if defined(_CPU_X86_64_) || defined(_CPU_X86_) # define JL_ELF_TLS_VARIANT 2 # define JL_ELF_TLS_INIT_SIZE 0 @@ -30,6 +30,11 @@ # include #endif +// `ElfW` was added to FreeBSD in 12.3 but we still support 12.2 +#if defined(_OS_FREEBSD_) && !defined(ElfW) +# define ElfW(x) __ElfN(x) +#endif + #ifdef __cplusplus extern "C" { #endif From 7b10d5fe0159e21e8299681c33605f0b10dbdcfa Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Wed, 14 Dec 2022 20:15:15 +0100 Subject: [PATCH 120/387] add back wordaround for `Slot objects should not occur in an AST` in Ipython mode (#47878) --- stdlib/REPL/src/REPL.jl | 6 +++--- stdlib/REPL/test/repl.jl | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index 4c83cdf33508d0..9c8712e0d41fc1 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -1404,9 +1404,9 @@ end function out_transform(@nospecialize(x), n::Ref{Int}) return quote - let x = $x - $capture_result($n, x) - x + let __temp_val_a72df459 = $x + $capture_result($n, __temp_val_a72df459) + __temp_val_a72df459 end end end diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index ab25a565102622..edcb91defc9ab8 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -1641,6 +1641,10 @@ fake_repl() do stdin_write, stdout_read, repl s = sendrepl2("REPL\n", "In [10]") @test contains(s, "Out[9]: REPL") + # Test for https://github.com/JuliaLang/julia/issues/46451 + s = sendrepl2("x_47878 = range(-1; stop = 1)\n", "-1:1") + @test contains(s, "Out[11]: -1:1") + write(stdin_write, '\x04') Base.wait(repltask) end From e84634e3b2c7354a4ac99024ed839d3e720a40cb Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Thu, 15 Dec 2022 03:39:58 -0600 Subject: [PATCH 121/387] Reduce invalidations when loading JuliaData packages (#47889) --- base/Base.jl | 1 + base/array.jl | 7 ++++--- base/loading.jl | 3 ++- base/logging.jl | 4 ++-- base/reinterpretarray.jl | 16 +++++++--------- base/show.jl | 13 +++++++++---- base/strings/util.jl | 2 +- 7 files changed, 26 insertions(+), 20 deletions(-) diff --git a/base/Base.jl b/base/Base.jl index 0c53a8bc9124b1..07b0a2f0f0d492 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -168,6 +168,7 @@ include("idset.jl") include("iterators.jl") using .Iterators: zip, enumerate, only using .Iterators: Flatten, Filter, product # for generators +using .Iterators: Stateful # compat (was formerly used in reinterpretarray.jl) include("namedtuple.jl") diff --git a/base/array.jl b/base/array.jl index 64d0ac05fd5079..5257caabf2d454 100644 --- a/base/array.jl +++ b/base/array.jl @@ -2730,7 +2730,8 @@ keepat!(a::Vector, m::AbstractVector{Bool}) = _keepat!(a, m) # set-like operators for vectors # These are moderately efficient, preserve order, and remove dupes. -_unique_filter!(pred, update!, state) = function (x) +_unique_filter!(pred::P, update!::U, state) where {P,U} = function (x) + # P, U force specialization if pred(x, state) update!(state, x) true @@ -2756,7 +2757,7 @@ union!(v::AbstractVector{T}, itrs...) where {T} = symdiff!(v::AbstractVector{T}, itrs...) where {T} = _grow!(_shrink_filter!(symdiff!(Set{T}(), v, itrs...)), v, itrs) -function _shrink!(shrinker!, v::AbstractVector, itrs) +function _shrink!(shrinker!::F, v::AbstractVector, itrs) where F seen = Set{eltype(v)}() filter!(_grow_filter!(seen), v) shrinker!(seen, itrs...) @@ -2768,7 +2769,7 @@ setdiff!( v::AbstractVector, itrs...) = _shrink!(setdiff!, v, itrs) vectorfilter(T::Type, f, v) = T[x for x in v if f(x)] -function _shrink(shrinker!, itr, itrs) +function _shrink(shrinker!::F, itr, itrs) where F T = promote_eltype(itr, itrs...) keep = shrinker!(Set{T}(itr), itrs...) vectorfilter(T, _shrink_filter!(keep), itr) diff --git a/base/loading.jl b/base/loading.jl index ea350ff72d960d..7c71167a8c176e 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -809,7 +809,8 @@ function explicit_manifest_uuid_path(project_file::String, pkg::PkgId)::Union{No end end # Extensions - for (name, entries::Vector{Any}) in d + for (name, entries) in d + entries = entries::Vector{Any} for entry in entries uuid = get(entry, "uuid", nothing)::Union{Nothing, String} extensions = get(entry, "extensions", nothing)::Union{Nothing, Dict{String, Any}} diff --git a/base/logging.jl b/base/logging.jl index d7dc45122e0633..c670d658cdaeb9 100644 --- a/base/logging.jl +++ b/base/logging.jl @@ -378,14 +378,14 @@ function logmsg_code(_module, file, line, level, message, exs...) id = $(log_data._id) # Second chance at an early bail-out (before computing the message), # based on arbitrary logger-specific logic. - if _invoked_shouldlog(logger, level, _module, group, id) + if invokelatest(shouldlog, logger, level, _module, group, id) file = $(log_data._file) if file isa String file = Base.fixup_stdlib_path(file) end line = $(log_data._line) local msg, kwargs - $(logrecord) && handle_message( + $(logrecord) && invokelatest(handle_message, logger, level, msg, _module, group, id, file, line; kwargs...) end diff --git a/base/reinterpretarray.jl b/base/reinterpretarray.jl index f198761a095001..1fe0788a1739a0 100644 --- a/base/reinterpretarray.jl +++ b/base/reinterpretarray.jl @@ -722,25 +722,23 @@ function CyclePadding(T::DataType) CyclePadding(pad, as) end -using .Iterators: Stateful @assume_effects :total function array_subpadding(S, T) - checked_size = 0 lcm_size = lcm(sizeof(S), sizeof(T)) - s, t = Stateful{<:Any, Any}(CyclePadding(S)), - Stateful{<:Any, Any}(CyclePadding(T)) + s, t = CyclePadding(S), CyclePadding(T) isempty(t) && return true isempty(s) && return false + checked_size = 0 + ps, sstate = iterate(s) # use of Stateful harms inference and makes this vulnerable to invalidation + pad, tstate = iterate(t) while checked_size < lcm_size - # Take padding in T - pad = popfirst!(t) - # See if there's corresponding padding in S while true - ps = peek(s) + # See if there's corresponding padding in S ps.offset > pad.offset && return false intersect(ps, pad) == pad && break - popfirst!(s) + ps, sstate = iterate(s, sstate) end checked_size = pad.offset + pad.size + pad, tstate = iterate(t, tstate) end return true end diff --git a/base/show.jl b/base/show.jl index 9e6b959f24fadb..c7dbb4a46e18e7 100644 --- a/base/show.jl +++ b/base/show.jl @@ -1881,8 +1881,12 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In # . print(io, '.') # item - parens = !(field isa Symbol) || (field::Symbol in quoted_syms) - quoted = parens || isoperator(field) + if isa(field, Symbol) + parens = field in quoted_syms + quoted = parens || isoperator(field) + else + parens = quoted = true + end quoted && print(io, ':') parens && print(io, '(') show_unquoted(io, field, indent, 0, quote_level) @@ -2006,10 +2010,11 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In # binary operator (i.e. "x + y") elseif func_prec > 0 # is a binary operator + func = func::Symbol # operator_precedence returns func_prec == 0 for non-Symbol na = length(func_args) - if (na == 2 || (na > 2 && isa(func, Symbol) && func in (:+, :++, :*)) || (na == 3 && func === :(:))) && + if (na == 2 || (na > 2 && func in (:+, :++, :*)) || (na == 3 && func === :(:))) && all(a -> !isa(a, Expr) || a.head !== :..., func_args) - sep = func === :(:) ? "$func" : " " * convert(String, string(func))::String * " " # if func::Any, avoid string interpolation (invalidation) + sep = func === :(:) ? "$func" : " $func " if func_prec <= prec show_enclosed_list(io, '(', func_args, sep, ')', indent, func_prec, quote_level, true) diff --git a/base/strings/util.jl b/base/strings/util.jl index 7d48fee9b1c52c..dabb84ae656398 100644 --- a/base/strings/util.jl +++ b/base/strings/util.jl @@ -830,7 +830,7 @@ julia> hex2bytes(a) """ function hex2bytes end -hex2bytes(s) = hex2bytes!(Vector{UInt8}(undef, length(s) >> 1), s) +hex2bytes(s) = hex2bytes!(Vector{UInt8}(undef, length(s)::Int >> 1), s) # special case - valid bytes are checked in the generic implementation function hex2bytes!(dest::AbstractArray{UInt8}, s::String) From cbcae07f2cbe6902d9c4a630ca86b0deb1a0c980 Mon Sep 17 00:00:00 2001 From: Knut Andreas Meyer Date: Thu, 15 Dec 2022 19:58:24 +0100 Subject: [PATCH 122/387] doc: correct example norm -> mynorm (#47904) --- doc/src/manual/performance-tips.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/src/manual/performance-tips.md b/doc/src/manual/performance-tips.md index 6bfdce4fc411b8..1a316e5fcf347b 100644 --- a/doc/src/manual/performance-tips.md +++ b/doc/src/manual/performance-tips.md @@ -611,8 +611,8 @@ end This can be written more concisely and efficiently as: ```julia -norm(x::Vector) = sqrt(real(dot(x, x))) -norm(A::Matrix) = maximum(svdvals(A)) +mynorm(x::Vector) = sqrt(real(dot(x, x))) +mynorm(A::Matrix) = maximum(svdvals(A)) ``` It should however be noted that the compiler is quite efficient at optimizing away the dead branches in code From 847cddeb7b9ddb5d6b66bec4c19d3a711748a45b Mon Sep 17 00:00:00 2001 From: Sukera <11753998+Seelengrab@users.noreply.github.com> Date: Thu, 15 Dec 2022 22:53:30 +0100 Subject: [PATCH 123/387] Fix an impossible-to-reach `InexactError` in `parse(Int, ::String)` (#47900) * Fix an impossible-to-reach `InexactError` in `parse(Int, ::String)` Previously, even though everything was bounded by checks earlier, this function would have an error path about an `InexactError` that makes it through LLVM and into assembly. By converting to `UInt32` before the call, the inner conversion will always succeed. This is safe, since we know 2 <= `base` <= 62 from the checks at the start of `tryparse_internal`. * Add type restriction to `__convert_digit` Co-authored-by: Sukera --- base/parse.jl | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/base/parse.jl b/base/parse.jl index e5b3d2ae3bc90c..00e71e189237b7 100644 --- a/base/parse.jl +++ b/base/parse.jl @@ -89,17 +89,22 @@ function parseint_preamble(signed::Bool, base::Int, s::AbstractString, startpos: return sgn, base, j end -@inline function __convert_digit(_c::UInt32, base) +# '0':'9' -> 0:9 +# 'A':'Z' -> 10:26 +# 'a':'z' -> 10:26 if base <= 36, 36:62 otherwise +# input outside of that is mapped to base +@inline function __convert_digit(_c::UInt32, base::UInt32) _0 = UInt32('0') _9 = UInt32('9') _A = UInt32('A') _a = UInt32('a') _Z = UInt32('Z') _z = UInt32('z') - a::UInt32 = base <= 36 ? 10 : 36 + a = base <= 36 ? UInt32(10) : UInt32(36) # converting here instead of via a type assertion prevents typeassert related errors d = _0 <= _c <= _9 ? _c-_0 : _A <= _c <= _Z ? _c-_A+ UInt32(10) : - _a <= _c <= _z ? _c-_a+a : UInt32(base) + _a <= _c <= _z ? _c-_a+a : + base end @@ -110,7 +115,7 @@ function tryparse_internal(::Type{T}, s::AbstractString, startpos::Int, endpos:: return nothing end if !(2 <= base <= 62) - raise && throw(ArgumentError("invalid base: base must be 2 ≤ base ≤ 62, got $base")) + raise && throw(ArgumentError(LazyString("invalid base: base must be 2 ≤ base ≤ 62, got ", base))) return nothing end if i == 0 @@ -132,7 +137,7 @@ function tryparse_internal(::Type{T}, s::AbstractString, startpos::Int, endpos:: while n <= m # Fast path from `UInt32(::Char)`; non-ascii will be >= 0x80 _c = reinterpret(UInt32, c) >> 24 - d::T = __convert_digit(_c, base) + d::T = __convert_digit(_c, base % UInt32) # we know 2 <= base <= 62, so prevent an incorrect InexactError here if d >= base raise && throw(ArgumentError("invalid base $base digit $(repr(c)) in $(repr(SubString(s,startpos,endpos)))")) return nothing @@ -150,7 +155,7 @@ function tryparse_internal(::Type{T}, s::AbstractString, startpos::Int, endpos:: while !isspace(c) # Fast path from `UInt32(::Char)`; non-ascii will be >= 0x80 _c = reinterpret(UInt32, c) >> 24 - d::T = __convert_digit(_c, base) + d::T = __convert_digit(_c, base % UInt32) # we know 2 <= base <= 62 if d >= base raise && throw(ArgumentError("invalid base $base digit $(repr(c)) in $(repr(SubString(s,startpos,endpos)))")) return nothing From 26a7dbb8e23e4b61a75b626cae5741ff6fd30ded Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 15 Dec 2022 20:25:50 -0500 Subject: [PATCH 124/387] intersect: fix a minor soundness issue with supertypes (#47813) When doing intersection, we might end up with a value in `env` (as the only possible *value* for that parameter) without properly considering that the parameter might be a TypeVar. --- src/subtype.c | 46 ++++++---------------------------------------- test/docs.jl | 1 + test/subtype.jl | 11 ++++++++++- 3 files changed, 17 insertions(+), 41 deletions(-) diff --git a/src/subtype.c b/src/subtype.c index 1d9d3d875675da..e2b132eedc8e90 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -2882,48 +2882,14 @@ static void flip_vars(jl_stenv_t *e) // intersection where xd nominally inherits from yd static jl_value_t *intersect_sub_datatype(jl_datatype_t *xd, jl_datatype_t *yd, jl_stenv_t *e, int R, int param) { + // attempt to populate additional constraints into `e` + // if that attempt fails, then return bottom + // otherwise return xd (finish_unionall will later handle propagating those constraints) jl_value_t *isuper = R ? intersect((jl_value_t*)yd, (jl_value_t*)xd->super, e, param) : intersect((jl_value_t*)xd->super, (jl_value_t*)yd, e, param); - if (isuper == jl_bottom_type) return jl_bottom_type; - if (jl_nparams(xd) == 0 || jl_nparams(xd->super) == 0 || !jl_has_free_typevars((jl_value_t*)xd)) - return (jl_value_t*)xd; - jl_value_t *super_pattern=NULL; - JL_GC_PUSH2(&isuper, &super_pattern); - jl_value_t *wrapper = xd->name->wrapper; - super_pattern = jl_rewrap_unionall_((jl_value_t*)((jl_datatype_t*)jl_unwrap_unionall(wrapper))->super, - wrapper); - int envsz = jl_subtype_env_size(super_pattern); - jl_value_t *ii = jl_bottom_type; - { - jl_value_t **env; - JL_GC_PUSHARGS(env, envsz); - jl_stenv_t tempe; - init_stenv(&tempe, env, envsz); - tempe.intersection = tempe.ignore_free = 1; - if (subtype_in_env(isuper, super_pattern, &tempe)) { - jl_value_t *wr = wrapper; - int i; - for(i=0; ivar || - (jl_is_typevar(ei) && lookup(e, (jl_tvar_t*)ei) == NULL)) - env[i] = jl_tparam(xd,i); - wr = ((jl_unionall_t*)wr)->body; - } - JL_TRY { - ii = jl_apply_type(wrapper, env, envsz); - } - JL_CATCH { - ii = jl_bottom_type; - } - } - JL_GC_POP(); - } - JL_GC_POP(); - return ii; + if (isuper == jl_bottom_type) + return jl_bottom_type; + return (jl_value_t*)xd; } static jl_value_t *intersect_invariant(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) diff --git a/test/docs.jl b/test/docs.jl index 4399722e864c11..6707278c538475 100644 --- a/test/docs.jl +++ b/test/docs.jl @@ -970,6 +970,7 @@ abstract type $(curmod_prefix)Undocumented.at1{T>:Integer, N} ``` $(curmod_prefix)Undocumented.mt6{Integer, N} +$(curmod_prefix)Undocumented.st5{T>:Integer, N} ``` # Supertype Hierarchy diff --git a/test/subtype.jl b/test/subtype.jl index 70f3dd864cdbed..59e5b82fdc8c00 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -2188,7 +2188,16 @@ for T in (B46871{Int, N} where {N}, B46871{Int}) # intentional duplication end abstract type C38497{e,g<:Tuple,i} end struct Q38497{o,e<:NTuple{o},g} <: C38497{e,g,Array{o}} end -@testintersect(Q38497{<:Any, Tuple{Int}}, C38497, Q38497{1, Tuple{Int}, <:Tuple}) +@testintersect(Q38497{<:Any, Tuple{Int}}, C38497, Q38497{<:Any, Tuple{Int}, <:Tuple}) +# n.b. the only concrete instance of this type is Q38497{1, Tuple{Int}, <:Tuple} (since NTuple{o} also adds an ::Int constraint) +# but this abstract type is also part of the intersection abstractly + +abstract type X38497{T<:Number} end +abstract type Y38497{T>:Integer} <: X38497{T} end +struct Z38497{T>:Int} <: Y38497{T} end +@testintersect(Z38497, X38497, Z38497{T} where Int<:T<:Number) +@testintersect(Z38497, Y38497, Z38497{T} where T>:Integer) +@testintersect(X38497, Y38497, Y38497{T} where Integer<:T<:Number) #issue #33138 @test Vector{Vector{Tuple{T,T}} where Int<:T<:Int} <: Vector{Vector{Tuple{S1,S1} where S<:S1<:S}} where S From b6f32bc023ae285a9ed0e7b405b0fb86da0f2f21 Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Thu, 15 Dec 2022 20:26:25 -0500 Subject: [PATCH 125/387] make Ctrl-C during sleeping work better (#47901) fixes #46635 co-authored-by: Jameson Nash --- src/partr.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/partr.c b/src/partr.c index ec6bbe3e5720aa..f5f63f54e7d259 100644 --- a/src/partr.c +++ b/src/partr.c @@ -368,14 +368,14 @@ JL_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q, JL_UV_LOCK(); // jl_mutex_lock(&jl_uv_mutex); } if (uvlock) { - int active = 1; - // otherwise, we block until someone asks us for the lock - uv_loop_t *loop = jl_global_event_loop(); - while (active && may_sleep(ptls)) { - if (jl_atomic_load_relaxed(&jl_uv_n_waiters) != 0) - // but if we won the race against someone who actually needs - // the lock to do real work, we need to let them have it instead - break; + int enter_eventloop = may_sleep(ptls); + int active = 0; + if (jl_atomic_load_relaxed(&jl_uv_n_waiters) != 0) + // if we won the race against someone who actually needs + // the lock to do real work, we need to let them have it instead + enter_eventloop = 0; + if (enter_eventloop) { + uv_loop_t *loop = jl_global_event_loop(); loop->stop_flag = 0; JULIA_DEBUG_SLEEPWAKE( ptls->uv_run_enter = cycleclock() ); active = uv_run(loop, UV_RUN_ONCE); @@ -388,11 +388,11 @@ JL_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q, // that just wanted to steal libuv from us. We will just go // right back to sleep on the individual wake signal to let // them take it from us without conflict. - if (!may_sleep(ptls)) { + if (active || !may_sleep(ptls)) { start_cycles = 0; continue; } - if (!jl_atomic_load_relaxed(&_threadedregion) && active && ptls->tid == 0) { + if (!enter_eventloop && !jl_atomic_load_relaxed(&_threadedregion) && ptls->tid == 0) { // thread 0 is the only thread permitted to run the event loop // so it needs to stay alive, just spin-looping if necessary if (jl_atomic_load_relaxed(&ptls->sleep_check_state) != not_sleeping) { From c1a322fba63da3eda20327d4de9acac389e48414 Mon Sep 17 00:00:00 2001 From: DilumAluthgeBot <43731525+DilumAluthgeBot@users.noreply.github.com> Date: Fri, 16 Dec 2022 17:28:14 -0500 Subject: [PATCH 126/387] =?UTF-8?q?=F0=9F=A4=96=20Bump=20the=20Pkg=20stdli?= =?UTF-8?q?b=20from=205d8b9ddb8=20to=20a8a8e224e=20(#47915)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dilum Aluthge --- .../Pkg-5d8b9ddb89ef7eff7c4d032cd4a7e33778c0bbde.tar.gz/md5 | 1 - .../Pkg-5d8b9ddb89ef7eff7c4d032cd4a7e33778c0bbde.tar.gz/sha512 | 1 - .../Pkg-a8a8e224eb78352ab82102c48d3b9c46fb6ee9cd.tar.gz/md5 | 1 + .../Pkg-a8a8e224eb78352ab82102c48d3b9c46fb6ee9cd.tar.gz/sha512 | 1 + stdlib/Pkg.version | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 deps/checksums/Pkg-5d8b9ddb89ef7eff7c4d032cd4a7e33778c0bbde.tar.gz/md5 delete mode 100644 deps/checksums/Pkg-5d8b9ddb89ef7eff7c4d032cd4a7e33778c0bbde.tar.gz/sha512 create mode 100644 deps/checksums/Pkg-a8a8e224eb78352ab82102c48d3b9c46fb6ee9cd.tar.gz/md5 create mode 100644 deps/checksums/Pkg-a8a8e224eb78352ab82102c48d3b9c46fb6ee9cd.tar.gz/sha512 diff --git a/deps/checksums/Pkg-5d8b9ddb89ef7eff7c4d032cd4a7e33778c0bbde.tar.gz/md5 b/deps/checksums/Pkg-5d8b9ddb89ef7eff7c4d032cd4a7e33778c0bbde.tar.gz/md5 deleted file mode 100644 index 24682e718f7017..00000000000000 --- a/deps/checksums/Pkg-5d8b9ddb89ef7eff7c4d032cd4a7e33778c0bbde.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -e0841b6343d50524c3bf694cab48ac16 diff --git a/deps/checksums/Pkg-5d8b9ddb89ef7eff7c4d032cd4a7e33778c0bbde.tar.gz/sha512 b/deps/checksums/Pkg-5d8b9ddb89ef7eff7c4d032cd4a7e33778c0bbde.tar.gz/sha512 deleted file mode 100644 index 15829d6b80fa30..00000000000000 --- a/deps/checksums/Pkg-5d8b9ddb89ef7eff7c4d032cd4a7e33778c0bbde.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -89ed36a9e9b4b297d9480474401b2b337d736bc307684bb4d35841159400ff651d5fc57d7cd643a0d4a9dbd01d2773e86e32b3cbfb9e5a8df5dac64990ea99d0 diff --git a/deps/checksums/Pkg-a8a8e224eb78352ab82102c48d3b9c46fb6ee9cd.tar.gz/md5 b/deps/checksums/Pkg-a8a8e224eb78352ab82102c48d3b9c46fb6ee9cd.tar.gz/md5 new file mode 100644 index 00000000000000..8693795bb4ee4f --- /dev/null +++ b/deps/checksums/Pkg-a8a8e224eb78352ab82102c48d3b9c46fb6ee9cd.tar.gz/md5 @@ -0,0 +1 @@ +a6ddbc5396e099945036661980f79d3e diff --git a/deps/checksums/Pkg-a8a8e224eb78352ab82102c48d3b9c46fb6ee9cd.tar.gz/sha512 b/deps/checksums/Pkg-a8a8e224eb78352ab82102c48d3b9c46fb6ee9cd.tar.gz/sha512 new file mode 100644 index 00000000000000..b23d4955fafed8 --- /dev/null +++ b/deps/checksums/Pkg-a8a8e224eb78352ab82102c48d3b9c46fb6ee9cd.tar.gz/sha512 @@ -0,0 +1 @@ +79a9d8e3eab8fe157bf79988cd561ebc21f79ddf1c9fd58a6767a99ba985cdc03ff2018324ebce652c689d0120c60314f1cd2602cddc675d8f0bb25f10390355 diff --git a/stdlib/Pkg.version b/stdlib/Pkg.version index 9e91595d927d02..b95437cbd7a392 100644 --- a/stdlib/Pkg.version +++ b/stdlib/Pkg.version @@ -1,4 +1,4 @@ PKG_BRANCH = master -PKG_SHA1 = 5d8b9ddb89ef7eff7c4d032cd4a7e33778c0bbde +PKG_SHA1 = a8a8e224eb78352ab82102c48d3b9c46fb6ee9cd PKG_GIT_URL := https://github.com/JuliaLang/Pkg.jl.git PKG_TAR_URL = https://api.github.com/repos/JuliaLang/Pkg.jl/tarball/$1 From eb57a775ccbf05294584e203b10988d77d43da7a Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sun, 18 Dec 2022 12:31:23 +0100 Subject: [PATCH 127/387] Added hseqr! LAPACK function (#47872) * Added hseqr LAPACK function * Added API documentation * Added schur function and test * Added test with Int matrix Co-authored-by: Daniel Karrasch --- stdlib/LinearAlgebra/docs/src/index.md | 1 + stdlib/LinearAlgebra/src/lapack.jl | 98 ++++++++++++++++++++++++++ stdlib/LinearAlgebra/src/schur.jl | 3 + stdlib/LinearAlgebra/test/schur.jl | 16 +++++ 4 files changed, 118 insertions(+) diff --git a/stdlib/LinearAlgebra/docs/src/index.md b/stdlib/LinearAlgebra/docs/src/index.md index a95b622480191e..9f12af174a4ffe 100644 --- a/stdlib/LinearAlgebra/docs/src/index.md +++ b/stdlib/LinearAlgebra/docs/src/index.md @@ -745,6 +745,7 @@ LinearAlgebra.LAPACK.trexc! LinearAlgebra.LAPACK.trsen! LinearAlgebra.LAPACK.tgsen! LinearAlgebra.LAPACK.trsyl! +LinearAlgebra.LAPACK.hseqr! ``` ```@meta diff --git a/stdlib/LinearAlgebra/src/lapack.jl b/stdlib/LinearAlgebra/src/lapack.jl index 9edaf77440750c..82ce01fd8428b6 100644 --- a/stdlib/LinearAlgebra/src/lapack.jl +++ b/stdlib/LinearAlgebra/src/lapack.jl @@ -5741,6 +5741,104 @@ for (ormhr, elty) in end end +for (hseqr, elty) in + ((:zhseqr_,:ComplexF64), + (:chseqr_,:ComplexF32)) + @eval begin + # * .. Scalar Arguments .. + # CHARACTER JOB, COMPZ + # INTEGER N, ILO, IHI, LWORK, LDH, LDZ, INFO + # * .. + # * .. Array Arguments .. + # COMPLEX*16 H( LDH, * ), Z( LDZ, * ), WORK( * ) + function hseqr!(job::AbstractChar, compz::AbstractChar, ilo::Integer, ihi::Integer, + H::AbstractMatrix{$elty}, Z::AbstractMatrix{$elty}) + require_one_based_indexing(H, Z) + chkstride1(H) + n = checksquare(H) + checksquare(Z) == n || throw(DimensionMismatch()) + ldh = max(1, stride(H, 2)) + ldz = max(1, stride(Z, 2)) + w = similar(H, $elty, n) + work = Vector{$elty}(undef, 1) + lwork = BlasInt(-1) + info = Ref{BlasInt}() + for i = 1:2 # first call returns lwork as work[1] + ccall((@blasfunc($hseqr), libblastrampoline), Cvoid, + (Ref{UInt8}, Ref{UInt8}, Ref{BlasInt}, Ref{BlasInt}, + Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, + Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, + Ptr{BlasInt}), + job, compz, n, ilo, ihi, + H, ldh, w, Z, ldz, work, + lwork, info) + chklapackerror(info[]) + if i == 1 + lwork = BlasInt(real(work[1])) + resize!(work, lwork) + end + end + H, Z, w + end + end +end + +for (hseqr, elty) in + ((:dhseqr_,:Float64), + (:shseqr_,:Float32)) + @eval begin + # * .. Scalar Arguments .. + # CHARACTER JOB, COMPZ + # INTEGER N, ILO, IHI, LWORK, LDH, LDZ, INFO + # * .. + # * .. Array Arguments .. + # COMPLEX*16 H( LDH, * ), Z( LDZ, * ), WORK( * ) + function hseqr!(job::AbstractChar, compz::AbstractChar, ilo::Integer, ihi::Integer, + H::AbstractMatrix{$elty}, Z::AbstractMatrix{$elty}) + require_one_based_indexing(H, Z) + chkstride1(H) + n = checksquare(H) + checksquare(Z) == n || throw(DimensionMismatch()) + ldh = max(1, stride(H, 2)) + ldz = max(1, stride(Z, 2)) + wr = similar(H, $elty, n) + wi = similar(H, $elty, n) + work = Vector{$elty}(undef, 1) + lwork = BlasInt(-1) + info = Ref{BlasInt}() + for i = 1:2 # first call returns lwork as work[1] + ccall((@blasfunc($hseqr), libblastrampoline), Cvoid, + (Ref{UInt8}, Ref{UInt8}, Ref{BlasInt}, Ref{BlasInt}, + Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ptr{$elty}, + Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, + Ptr{BlasInt}), + job, compz, n, ilo, ihi, + H, ldh, wr, wi, Z, ldz, work, + lwork, info) + chklapackerror(info[]) + if i == 1 + lwork = BlasInt(real(work[1])) + resize!(work, lwork) + end + end + H, Z, complex.(wr, wi) + end + end +end +hseqr!(H::StridedMatrix{T}, Z::StridedMatrix{T}) where {T<:BlasFloat} = hseqr!('S', 'V', 1, size(H, 1), H, Z) +hseqr!(H::StridedMatrix{T}) where {T<:BlasFloat} = hseqr!('S', 'I', 1, size(H, 1), H, similar(H)) + +""" + hseqr!(job, compz, ilo, ihi, H, Z) -> (H, Z, w) + +Computes all eigenvalues and (optionally) the Schur factorization of a matrix +reduced to Hessenberg form. If `H` is balanced with `gebal!` +then `ilo` and `ihi` are the outputs of `gebal!`. Otherwise they should be +`ilo = 1` and `ihi = size(H,2)`. `tau` contains the elementary reflectors of +the factorization. +""" +hseqr!(job::AbstractChar, compz::AbstractChar, ilo::Integer, ihi::Integer, H::AbstractMatrix, Z::AbstractMatrix) + for (hetrd, elty) in ((:dsytrd_,Float64), (:ssytrd_,Float32), diff --git a/stdlib/LinearAlgebra/src/schur.jl b/stdlib/LinearAlgebra/src/schur.jl index 98ba9b22bb4781..53741adb48cf97 100644 --- a/stdlib/LinearAlgebra/src/schur.jl +++ b/stdlib/LinearAlgebra/src/schur.jl @@ -102,6 +102,8 @@ julia> A """ schur!(A::StridedMatrix{<:BlasFloat}) = Schur(LinearAlgebra.LAPACK.gees!('V', A)...) +schur!(A::UpperHessenberg{T}) where {T<:BlasFloat} = Schur(LinearAlgebra.LAPACK.hseqr!(parent(A))...) + """ schur(A) -> F::Schur @@ -153,6 +155,7 @@ true ``` """ schur(A::AbstractMatrix{T}) where {T} = schur!(copy_similar(A, eigtype(T))) +schur(A::UpperHessenberg{T}) where {T} = schur!(copy_similar(A, eigtype(T))) function schur(A::RealHermSymComplexHerm) F = eigen(A; sortby=nothing) return Schur(typeof(F.vectors)(Diagonal(F.values)), F.vectors, F.values) diff --git a/stdlib/LinearAlgebra/test/schur.jl b/stdlib/LinearAlgebra/test/schur.jl index d047ca12abc1f2..c9a5d92dbdae86 100644 --- a/stdlib/LinearAlgebra/test/schur.jl +++ b/stdlib/LinearAlgebra/test/schur.jl @@ -202,4 +202,20 @@ end @test A' ≈ C ≈ E end +@testset "UpperHessenberg schur" begin + A = UpperHessenberg(rand(ComplexF64, 100, 100)) + B = Array(A) + fact1 = schur(A) + fact2 = schur(B) + @test fact1.values ≈ fact2.values + @test fact1.Z * fact1.T * fact1.Z' ≈ B + + A = UpperHessenberg(rand(Int32, 50, 50)) + B = Array(A) + fact1 = schur(A) + fact2 = schur(B) + @test fact1.values ≈ fact2.values + @test fact1.Z * fact1.T * fact1.Z' ≈ B +end + end # module TestSchur From 427432e5c6ea90aa2f4616a380b4f4322ff30bbe Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Mon, 19 Dec 2022 12:53:55 +0100 Subject: [PATCH 128/387] revert promotions of abstract arrays inside other arrays (#47893) --- base/range.jl | 5 ----- stdlib/LinearAlgebra/src/adjtrans.jl | 4 ++-- test/bitarray.jl | 4 ++-- test/broadcast.jl | 8 ++++---- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/base/range.jl b/base/range.jl index 9986fa6a21defe..9d12ae1001784f 100644 --- a/base/range.jl +++ b/base/range.jl @@ -1277,11 +1277,6 @@ el_same(::Type{T}, a::Type{<:AbstractArray{T,n}}, b::Type{<:AbstractArray{S,n}}) el_same(::Type{T}, a::Type{<:AbstractArray{S,n}}, b::Type{<:AbstractArray{T,n}}) where {T,S,n} = b el_same(::Type, a, b) = promote_typejoin(a, b) -promote_result(::Type{<:AbstractArray}, ::Type{<:AbstractArray}, ::Type{T}, ::Type{S}) where {T,S} = (@inline; promote_type(T,S)) -promote_result(::Type{T}, ::Type{S}, ::Type{Bottom}, ::Type{Bottom}) where {T<:AbstractArray,S<:AbstractArray} = (@inline; promote_typejoin(T,S)) -# If no promote_rule is defined, both directions give Bottom. In that case use typejoin on the eltypes instead and give Array as the container. -promote_result(::Type{<:AbstractArray{T,n}}, ::Type{<:AbstractArray{S,n}}, ::Type{Bottom}, ::Type{Bottom}) where {T,S,n} = (@inline; Array{promote_type(T,S),n}) - promote_rule(a::Type{UnitRange{T1}}, b::Type{UnitRange{T2}}) where {T1,T2} = el_same(promote_type(T1, T2), a, b) UnitRange{T}(r::UnitRange{T}) where {T<:Real} = r diff --git a/stdlib/LinearAlgebra/src/adjtrans.jl b/stdlib/LinearAlgebra/src/adjtrans.jl index 058b1992f66258..524be86c0f8f54 100644 --- a/stdlib/LinearAlgebra/src/adjtrans.jl +++ b/stdlib/LinearAlgebra/src/adjtrans.jl @@ -237,8 +237,8 @@ julia> transpose(v) * v # compute the dot product For a matrix of matrices, the individual blocks are recursively operated on: ```jldoctest -julia> C = reshape(1:4, 2, 2) -2×2 reshape(::UnitRange{Int64}, 2, 2) with eltype Int64: +julia> C = [1 3; 2 4] +2×2 Matrix{Int64}: 1 3 2 4 diff --git a/test/bitarray.jl b/test/bitarray.jl index 05abd610682a23..dd1d0d7d6c5a44 100644 --- a/test/bitarray.jl +++ b/test/bitarray.jl @@ -98,9 +98,9 @@ end timesofar("conversions") @testset "Promotions for size $sz" for (sz, T) in allsizes - @test isequal(promote(falses(sz...), zeros(sz...)), + @test_broken isequal(promote(falses(sz...), zeros(sz...)), (zeros(sz...), zeros(sz...))) - @test isequal(promote(trues(sz...), ones(sz...)), + @test_broken isequal(promote(trues(sz...), ones(sz...)), (ones(sz...), ones(sz...))) ae = falses(1, sz...) ex = (@test_throws ErrorException promote(ae, ones(sz...))).value diff --git a/test/broadcast.jl b/test/broadcast.jl index bd9cb9e8e8fa39..1893acc8c11496 100644 --- a/test/broadcast.jl +++ b/test/broadcast.jl @@ -699,11 +699,11 @@ end @test_throws Base.CanonicalIndexError A[2] .= 0 @test_throws MethodError A[3] .= 0 A = [[1, 2, 3], 4:5] - @test A isa Vector{Vector{Int}} A[1] .= 0 - A[2] .= 0 - @test A[1] == [0, 0, 0] - @test A[2] == [0, 0] + @test A[1] isa Vector{Int} + @test A[2] isa UnitRange + @test A[1] == [0,0,0] + @test_throws Base.CanonicalIndexError A[2] .= 0 end # Issue #22180 From 9be3c85e49f51fa558a3e6522ed79fe32ff2617b Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Mon, 19 Dec 2022 22:17:13 +0100 Subject: [PATCH 129/387] only load extensions once dependencies have finished loading (#47927) --- base/loading.jl | 2 +- test/loading.jl | 4 ++-- test/project/Extensions/ExtDep.jl/Project.toml | 3 +++ test/project/Extensions/ExtDep.jl/src/ExtDep.jl | 4 ++++ .../Extensions/HasDepWithExtensions.jl/Manifest.toml | 10 ++++++++-- test/project/Extensions/SomePackage/Project.toml | 4 ++++ test/project/Extensions/SomePackage/src/SomePackage.jl | 5 +++++ 7 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 test/project/Extensions/SomePackage/Project.toml create mode 100644 test/project/Extensions/SomePackage/src/SomePackage.jl diff --git a/base/loading.jl b/base/loading.jl index 7c71167a8c176e..61c1f13a3eef3c 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -1102,7 +1102,7 @@ function run_extension_callbacks(; force::Bool=false) for extid in EXT_DORMITORY extid.succeeded && continue !force && extid.triggered && continue - if all(x -> haskey(Base.loaded_modules, x), extid.triggers) + if all(x -> haskey(Base.loaded_modules, x) && !haskey(package_locks, x), extid.triggers) ext_not_allowed_load = nothing extid.triggered = true # It is possible that some of the triggers were loaded in an environment diff --git a/test/loading.jl b/test/loading.jl index 99f39ae2375324..d52a7246abe7cc 100644 --- a/test/loading.jl +++ b/test/loading.jl @@ -998,8 +998,8 @@ end push!(empty!(DEPOT_PATH), joinpath(tmp, "depot")) proj = joinpath(@__DIR__, "project", "Extensions", "HasDepWithExtensions.jl") - for i in 1:2 # Once when requiring precomilation, once where it is already precompiled - cmd = `$(Base.julia_cmd()) --project=$proj --startup-file=no -e ' + for compile in (`--compiled-modules=no`, ``, ``) # Once when requiring precomilation, once where it is already precompiled + cmd = `$(Base.julia_cmd()) $compile --project=$proj --startup-file=no -e ' begin using HasExtensions # Base.get_extension(HasExtensions, :Extension) === nothing || error("unexpectedly got an extension") diff --git a/test/project/Extensions/ExtDep.jl/Project.toml b/test/project/Extensions/ExtDep.jl/Project.toml index 93c5e3925f06b6..d246934b7f9586 100644 --- a/test/project/Extensions/ExtDep.jl/Project.toml +++ b/test/project/Extensions/ExtDep.jl/Project.toml @@ -1,3 +1,6 @@ name = "ExtDep" uuid = "fa069be4-f60b-4d4c-8b95-f8008775090c" version = "0.1.0" + +[deps] +SomePackage = "678608ae-7bb3-42c7-98b1-82102067a3d8" diff --git a/test/project/Extensions/ExtDep.jl/src/ExtDep.jl b/test/project/Extensions/ExtDep.jl/src/ExtDep.jl index f0ca8c62d04b23..1c0022d879f51f 100644 --- a/test/project/Extensions/ExtDep.jl/src/ExtDep.jl +++ b/test/project/Extensions/ExtDep.jl/src/ExtDep.jl @@ -1,5 +1,9 @@ module ExtDep +# loading this package makes the check for loading extensions trigger +# which tests #47921 +using SomePackage + struct ExtDepStruct end end # module ExtDep diff --git a/test/project/Extensions/HasDepWithExtensions.jl/Manifest.toml b/test/project/Extensions/HasDepWithExtensions.jl/Manifest.toml index c96e3ef508ca8b..52542fc822094c 100644 --- a/test/project/Extensions/HasDepWithExtensions.jl/Manifest.toml +++ b/test/project/Extensions/HasDepWithExtensions.jl/Manifest.toml @@ -2,9 +2,10 @@ julia_version = "1.10.0-DEV" manifest_format = "2.0" -project_hash = "7cbe1857ecc6692a8cc8be428a5ad5073531ff98" +project_hash = "d523b3401f72a1ed34b7b43749fd2655c6b78542" [[deps.ExtDep]] +deps = ["SomePackage"] path = "../ExtDep.jl" uuid = "fa069be4-f60b-4d4c-8b95-f8008775090c" version = "0.1.0" @@ -15,11 +16,16 @@ uuid = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d" version = "0.1.0" [[deps.HasExtensions]] -weakdeps = ["ExtDep", "ExtDep2"] path = "../HasExtensions.jl" uuid = "4d3288b3-3afc-4bb6-85f3-489fffe514c8" version = "0.1.0" +weakdeps = ["ExtDep", "ExtDep2"] [deps.HasExtensions.extensions] Extension = "ExtDep" ExtensionFolder = ["ExtDep", "ExtDep2"] + +[[deps.SomePackage]] +path = "../SomePackage" +uuid = "678608ae-7bb3-42c7-98b1-82102067a3d8" +version = "0.1.0" diff --git a/test/project/Extensions/SomePackage/Project.toml b/test/project/Extensions/SomePackage/Project.toml new file mode 100644 index 00000000000000..b2d43340b39a86 --- /dev/null +++ b/test/project/Extensions/SomePackage/Project.toml @@ -0,0 +1,4 @@ +name = "SomePackage" +uuid = "678608ae-7bb3-42c7-98b1-82102067a3d8" +authors = ["Kristoffer "] +version = "0.1.0" diff --git a/test/project/Extensions/SomePackage/src/SomePackage.jl b/test/project/Extensions/SomePackage/src/SomePackage.jl new file mode 100644 index 00000000000000..a41e0b7482bae7 --- /dev/null +++ b/test/project/Extensions/SomePackage/src/SomePackage.jl @@ -0,0 +1,5 @@ +module SomePackage + +greet() = print("Hello World!") + +end # module SomePackage From e976206563b86734a9e5a07d2c58cafe94e3493c Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Tue, 20 Dec 2022 16:33:09 +0900 Subject: [PATCH 130/387] allow external lattice to provide its own field-merge implementation (#47910) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When merging `PartialStruct`, we are currently merging their fields a bit aggressively (#44404) in order to accelerate (or rather, guarantee) convergence. However, when `PartialStruct` wraps external lattice elements, this can be too aggressive since it does not use `tmerge(𝕃, fields...)` recursively and thus the external lattice elements are not merged as expected. This commit adds an additional lattice hook, `tmerge_field`, inside `tmerge(::PartialsLattice)` so that external lattice implementation can provide its own field-merge strategies. --- base/compiler/abstractlattice.jl | 17 +++++++++++++++++ base/compiler/typelimits.jl | 6 +++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/base/compiler/abstractlattice.jl b/base/compiler/abstractlattice.jl index d98c2b818b6494..0302b3f83e373d 100644 --- a/base/compiler/abstractlattice.jl +++ b/base/compiler/abstractlattice.jl @@ -131,6 +131,23 @@ remaining mostly associative and commutative. """ function tmerge end +""" + tmerge_field(𝕃::AbstractLattice, a, b) -> nothing or lattice element + +Compute a lattice join of elements `a` and `b` over the lattice `𝕃`, +where `a` and `b` are fields of `PartialStruct` or `Const`. +This is an opt-in interface to allow external lattice implementation to provide its own +field-merge strategy. If it returns `nothing`, `tmerge(::PartialsLattice, ...)` +will use the default aggressive type merge implementation that does not use `tmerge` +recursively to reach convergence. +""" +function tmerge_field end + +function tmerge_field(𝕃::AbstractLattice, @nospecialize(a), @nospecialize(b)) + return tmerge_field(widenlattice(𝕃), a, b) +end +tmerge_field(::JLTypeLattice, @nospecialize(a), @nospecialize(b)) = nothing + """ ⊑(𝕃::AbstractLattice, a, b) diff --git a/base/compiler/typelimits.jl b/base/compiler/typelimits.jl index e470c4711110cb..2501dd51edf6d2 100644 --- a/base/compiler/typelimits.jl +++ b/base/compiler/typelimits.jl @@ -515,8 +515,12 @@ function tmerge(lattice::PartialsLattice, @nospecialize(typea), @nospecialize(ty tyi = ai elseif is_lattice_equal(lattice, bi, ft) tyi = bi + elseif (tyi′ = tmerge_field(lattice, ai, bi); tyi′ !== nothing) + # allow external lattice implementation to provide a custom field-merge strategy + tyi = tyi′ else - # Otherwise choose between using the fieldtype or some other simple merged type. + # Otherwise use the default aggressive field-merge implementation, and + # choose between using the fieldtype or some other simple merged type. # The wrapper type never has restrictions on complexity, # so try to use that to refine the estimated type too. tni = _typename(widenconst(ai)) From 61afe7b7f0852d6d75019d7b8645e51decca46ba Mon Sep 17 00:00:00 2001 From: 2005m Date: Tue, 20 Dec 2022 08:29:38 +0000 Subject: [PATCH 131/387] Remove DelimitedFiles checksums (#47931) --- .../md5 | 1 - .../sha512 | 1 - 2 files changed, 2 deletions(-) delete mode 100644 deps/checksums/DelimitedFiles-495ebc81ef7fe2c083ca4c1d7e43a22cc1e49490.tar.gz/md5 delete mode 100644 deps/checksums/DelimitedFiles-495ebc81ef7fe2c083ca4c1d7e43a22cc1e49490.tar.gz/sha512 diff --git a/deps/checksums/DelimitedFiles-495ebc81ef7fe2c083ca4c1d7e43a22cc1e49490.tar.gz/md5 b/deps/checksums/DelimitedFiles-495ebc81ef7fe2c083ca4c1d7e43a22cc1e49490.tar.gz/md5 deleted file mode 100644 index b4cf9f1e83f384..00000000000000 --- a/deps/checksums/DelimitedFiles-495ebc81ef7fe2c083ca4c1d7e43a22cc1e49490.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -ae5ab9a7304cffb64614c49540259f62 diff --git a/deps/checksums/DelimitedFiles-495ebc81ef7fe2c083ca4c1d7e43a22cc1e49490.tar.gz/sha512 b/deps/checksums/DelimitedFiles-495ebc81ef7fe2c083ca4c1d7e43a22cc1e49490.tar.gz/sha512 deleted file mode 100644 index 38e84b1ae4e1ff..00000000000000 --- a/deps/checksums/DelimitedFiles-495ebc81ef7fe2c083ca4c1d7e43a22cc1e49490.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -eff8c61190d180248a6bcd0a4d194df223a0471f9a8200a12afb41f8df2c4dfbfb292bbe0ca5ac940e4093a041832a89ad252cd1a7b89c250500662808a6abbf From 70c1e45974c1dd2aa951b81aa05aa840bdcb7a8e Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 20 Dec 2022 04:31:52 -0500 Subject: [PATCH 132/387] add missing innervars handling to merge_env (#47877) Otherwise, we would write NULL here immediately when handling intersect_all, and may fail to restore this array correctly. --- src/subtype.c | 10 ++++- test/subtype.jl | 110 +++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 103 insertions(+), 17 deletions(-) diff --git a/src/subtype.c b/src/subtype.c index e2b132eedc8e90..10b38798cc9c67 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -3217,7 +3217,7 @@ static int merge_env(jl_stenv_t *e, jl_value_t **root, jl_savedenv_t *se, int co int n = 0; jl_varbinding_t *v = e->vars; jl_value_t *b1 = NULL, *b2 = NULL; - JL_GC_PUSH2(&b1, &b2); + JL_GC_PUSH2(&b1, &b2); // clang-sagc does not understand that *root is rooted already while (v != NULL) { b1 = jl_svecref(*root, n); b2 = v->lb; @@ -3225,6 +3225,14 @@ static int merge_env(jl_stenv_t *e, jl_value_t **root, jl_savedenv_t *se, int co b1 = jl_svecref(*root, n+1); b2 = v->ub; jl_svecset(*root, n+1, simple_join(b1, b2)); + b1 = jl_svecref(*root, n+2); + b2 = (jl_value_t*)v->innervars; + if (b2) { + if (b1) + jl_array_ptr_1d_append((jl_array_t*)b2, (jl_array_t*)b1); + else + jl_svecset(*root, n+2, b2); + } n = n + 3; v = v->prev; } diff --git a/test/subtype.jl b/test/subtype.jl index 59e5b82fdc8c00..ec771b71988b4c 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -1181,11 +1181,25 @@ ftwoparams(::TwoParams{<:Real,<:Real}) = 3 # a bunch of cases found by fuzzing let a = Tuple{Float64,T7} where T7, b = Tuple{S5,Tuple{S5}} where S5 - @test typeintersect(a, b) <: b + I1 = typeintersect(a, b) + I2 = typeintersect(b, a) + @test I1 <: I2 + @test I2 <: I1 + @test I1 <: a + @test I2 <: a + @test I1 <: b + @test I2 <: b end let a = Tuple{T1,T1} where T1, b = Tuple{Val{S2},S6} where S2 where S6 - @test typeintersect(a, b) == typeintersect(b, a) + I1 = typeintersect(a, b) + I2 = typeintersect(b, a) + @test I1 <: I2 + @test I2 <: I1 + @test I1 <: a + @test I2 <: a + @test I1 <: b + @test I2 <: b end let a = Val{Tuple{T1,T1}} where T1, b = Val{Tuple{Val{S2},S6}} where S2 where S6 @@ -1193,15 +1207,36 @@ let a = Val{Tuple{T1,T1}} where T1, end let a = Tuple{Float64,T3,T4} where T4 where T3, b = Tuple{S2,Tuple{S3},S3} where S2 where S3 - @test typeintersect(a, b) == typeintersect(b, a) + I1 = typeintersect(a, b) + I2 = typeintersect(b, a) + @test I1 <: I2 + @test I2 <: I1 + @test I1 <: a + @test I2 <: a + @test_broken I1 <: b + @test_broken I2 <: b end let a = Tuple{T1,Tuple{T1}} where T1, b = Tuple{Float64,S3} where S3 - @test typeintersect(a, b) <: a + I1 = typeintersect(a, b) + I2 = typeintersect(b, a) + @test I1 <: I2 + @test I2 <: I1 + @test I1 <: a + @test I2 <: a + @test I1 <: b + @test I2 <: b end let a = Tuple{5,T4,T5} where T4 where T5, b = Tuple{S2,S3,Tuple{S3}} where S2 where S3 - @test typeintersect(a, b) == typeintersect(b, a) + I1 = typeintersect(a, b) + I2 = typeintersect(b, a) + @test I1 <: I2 + @test I2 <: I1 + @test I1 <: a + @test I2 <: a + @test_broken I1 <: b + @test_broken I2 <: b end let a = Tuple{T2,Tuple{T4,T2}} where T4 where T2, b = Tuple{Float64,Tuple{Tuple{S3},S3}} where S3 @@ -1209,23 +1244,58 @@ let a = Tuple{T2,Tuple{T4,T2}} where T4 where T2, end let a = Tuple{Tuple{T2,4},T6} where T2 where T6, b = Tuple{Tuple{S2,S3},Tuple{S2}} where S2 where S3 - @test typeintersect(a, b) == typeintersect(b, a) + I1 = typeintersect(a, b) + I2 = typeintersect(b, a) + @test I1 <: I2 + @test I2 <: I1 + @test I1 <: a + @test I2 <: a + @test_broken I1 <: b + @test_broken I2 <: b end let a = Tuple{T3,Int64,Tuple{T3}} where T3, b = Tuple{S3,S3,S4} where S4 where S3 - @test_broken typeintersect(a, b) <: a + I1 = typeintersect(a, b) + I2 = typeintersect(b, a) + @test I1 <: I2 + @test I2 <: I1 + @test_broken I1 <: a + @test I2 <: a + @test I1 <: b + @test I2 <: b end let a = Tuple{T1,Val{T2},T2} where T2 where T1, b = Tuple{Float64,S1,S2} where S2 where S1 - @test typeintersect(a, b) == typeintersect(b, a) + I1 = typeintersect(a, b) + I2 = typeintersect(b, a) + @test I1 <: I2 + @test I2 <: I1 + @test_broken I1 <: a + @test_broken I2 <: a + @test I1 <: b + @test I2 <: b end let a = Tuple{T1,Val{T2},T2} where T2 where T1, b = Tuple{Float64,S1,S2} where S2 where S1 - @test_broken typeintersect(a, b) <: a + I1 = typeintersect(a, b) + I2 = typeintersect(b, a) + @test I1 <: I2 + @test I2 <: I1 + @test_broken I1 <: a + @test_broken I2 <: a + @test I1 <: b + @test I2 <: b end let a = Tuple{Float64,T1} where T1, b = Tuple{S1,Tuple{S1}} where S1 - @test typeintersect(a, b) <: b + I1 = typeintersect(a, b) + I2 = typeintersect(b, a) + @test I1 <: I2 + @test I2 <: I1 + @test I1 <: a + @test I2 <: a + @test I1 <: b + @test I2 <: b end let a = Tuple{Val{T1},T2,T2} where T2 where T1, b = Tuple{Val{Tuple{S2}},S3,Float64} where S2 where S3 @@ -1234,12 +1304,20 @@ end let a = Tuple{T1,T2,T2} where T1 where T2, b = Tuple{Val{S2},S2,Float64} where S2, x = Tuple{Val{Float64},Float64,Float64} - @test x <: typeintersect(a, b) -end -let a = Val{Tuple{T1,Val{T2},Val{Int64},Tuple{Tuple{T3,5,Float64},T4,T2,T5}}} where T1 where T5 where T4 where T3 where T2, - b = Val{Tuple{Tuple{S1,5,Float64},Val{S2},S3,Tuple{Tuple{Val{Float64},5,Float64},2,Float64,S4}}} where S2 where S3 where S1 where S4 - @test_skip typeintersect(b, a) -end + I1 = typeintersect(a, b) + I2 = typeintersect(b, a) + @test x <: I1 + @test x <: I2 + @test I1 <: I2 + @test I2 <: I1 + @test I1 <: a + @test I2 <: a + @test_broken I1 <: b + @test_broken I2 <: b +end +@testintersect(Val{Tuple{T1,Val{T2},Val{Int64},Tuple{Tuple{T3,5,Float64},T4,T2,T5}}} where T1 where T5 where T4 where T3 where T2, + Val{Tuple{Tuple{S1,5,Float64},Val{S2},S3,Tuple{Tuple{Val{Float64},5,Float64},2,Float64,S4}}} where S2 where S3 where S1 where S4, + Val{Tuple{Tuple{S1, 5, Float64}, Val{Float64}, Val{Int64}, Tuple{Tuple{Val{Float64}, 5, Float64}, 2, Float64, T5}}} where {T5, S1}) # issue #20992 abstract type A20992{T,D,d} end From 1f0700a29a4e0250c5c31cbc02e624009d1ed741 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Tue, 20 Dec 2022 08:30:20 -0600 Subject: [PATCH 133/387] Precompile cache: always add worklist CIs (#47924) We cache only those external CodeInstances that link back to the package being precompiled. Formerly we required a backedge; this PRs adds any whose `specTypes` could only link back to the package. This scoops up a few runtime-dispatched CodeInstances and their callees. --- src/staticdata_utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/staticdata_utils.c b/src/staticdata_utils.c index 6f673563ff3adb..dea511b1e0c98e 100644 --- a/src/staticdata_utils.c +++ b/src/staticdata_utils.c @@ -167,7 +167,7 @@ static int has_backedge_to_worklist(jl_method_instance_t *mi, htable_t *visited, if (jl_is_method(mod)) mod = ((jl_method_t*)mod)->module; assert(jl_is_module(mod)); - if (mi->precompiled || !jl_object_in_image((jl_value_t*)mod)) { + if (mi->precompiled || !jl_object_in_image((jl_value_t*)mod) || type_in_worklist(mi->specTypes)) { return 1; } if (!mi->backedges) { From d7363d894f95e7168fb490a64b65cd2c2301a11b Mon Sep 17 00:00:00 2001 From: Michael Abbott <32575566+mcabbott@users.noreply.github.com> Date: Tue, 20 Dec 2022 10:09:18 -0500 Subject: [PATCH 134/387] add bounds check to Slices indexing (#47622) Co-authored-by: Simon Byrne --- base/slicearray.jl | 14 +++++++++----- test/arrayops.jl | 9 +++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/base/slicearray.jl b/base/slicearray.jl index 506cc900ba7811..fae353dbe76904 100644 --- a/base/slicearray.jl +++ b/base/slicearray.jl @@ -85,7 +85,7 @@ the ordering of the dimensions will match those in `dims`. If `drop = false`, th `Slices` will have the same dimensionality as the underlying array, with inner dimensions having size 1. -See [`stack`](@ref)`(slices; dims)` for the inverse of `eachcol(A; dims::Integer, drop=true)`. +See [`stack`](@ref)`(slices; dims)` for the inverse of `eachslice(A; dims::Integer)`. See also [`eachrow`](@ref), [`eachcol`](@ref), [`mapslices`](@ref) and [`selectdim`](@ref). @@ -232,9 +232,13 @@ size(s::Slices) = map(length, s.axes) return map(l -> l === (:) ? (:) : c[l], s.slicemap) end -Base.@propagate_inbounds getindex(s::Slices{P,SM,AX,S,N}, I::Vararg{Int,N}) where {P,SM,AX,S,N} = - view(s.parent, _slice_index(s, I...)...) -Base.@propagate_inbounds setindex!(s::Slices{P,SM,AX,S,N}, val, I::Vararg{Int,N}) where {P,SM,AX,S,N} = - s.parent[_slice_index(s, I...)...] = val +@inline function getindex(s::Slices{P,SM,AX,S,N}, I::Vararg{Int,N}) where {P,SM,AX,S,N} + @boundscheck checkbounds(s, I...) + @inbounds view(s.parent, _slice_index(s, I...)...) +end +@inline function setindex!(s::Slices{P,SM,AX,S,N}, val, I::Vararg{Int,N}) where {P,SM,AX,S,N} + @boundscheck checkbounds(s, I...) + @inbounds s.parent[_slice_index(s, I...)...] = val +end parent(s::Slices) = s.parent diff --git a/test/arrayops.jl b/test/arrayops.jl index c2698b3c70a908..e7ac6a11325688 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -2293,6 +2293,15 @@ end f2(a) = eachslice(a, dims=2) @test (@inferred f2(a)) == eachcol(a) end + + @testset "eachslice bounds checking" begin + # https://github.com/JuliaLang/julia/pull/32310#issuecomment-1146911510 + A = eachslice(rand(2,3), dims = 2, drop = false) + @test_throws BoundsError A[2, 1] + @test_throws BoundsError A[4] + @test_throws BoundsError A[2,3] = [4,5] + @test_throws BoundsError A[2,3] .= [4,5] + end end ### From 4341b9622dcb7c10bb6f4efc7863f75b772052be Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 20 Dec 2022 10:42:36 -0500 Subject: [PATCH 135/387] compute env for typeintersect better (#47925) Avoid discarding intermediate information sooner than we must (e.g. at return). --- src/jltypes.c | 2 +- src/julia.h | 2 +- src/subtype.c | 40 ++++++++++++++++++++-------------------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/jltypes.c b/src/jltypes.c index d77d711f71a3a0..253768ad075036 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -1910,7 +1910,7 @@ jl_datatype_t *jl_wrap_Type(jl_value_t *t) jl_vararg_t *jl_wrap_vararg(jl_value_t *t, jl_value_t *n) { if (n) { - if (jl_is_typevar(n)) { + if (jl_is_typevar(n) || jl_is_uniontype(jl_unwrap_unionall(n))) { // TODO: this is disabled due to #39698; it is also inconsistent // with other similar checks, where we usually only check substituted // values and not the bounds of variables. diff --git a/src/julia.h b/src/julia.h index a27b66241793ae..0164c2e55a4f93 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1412,7 +1412,7 @@ JL_DLLEXPORT int jl_type_equality_is_identity(jl_value_t *t1, jl_value_t *t2) JL JL_DLLEXPORT int jl_has_free_typevars(jl_value_t *v) JL_NOTSAFEPOINT; JL_DLLEXPORT int jl_has_typevar(jl_value_t *t, jl_tvar_t *v) JL_NOTSAFEPOINT; JL_DLLEXPORT int jl_has_typevar_from_unionall(jl_value_t *t, jl_unionall_t *ua); -JL_DLLEXPORT int jl_subtype_env_size(jl_value_t *t); +JL_DLLEXPORT int jl_subtype_env_size(jl_value_t *t) JL_NOTSAFEPOINT; JL_DLLEXPORT int jl_subtype_env(jl_value_t *x, jl_value_t *y, jl_value_t **env, int envsz); JL_DLLEXPORT int jl_isa(jl_value_t *a, jl_value_t *t); JL_DLLEXPORT int jl_types_equal(jl_value_t *a, jl_value_t *b); diff --git a/src/subtype.c b/src/subtype.c index 10b38798cc9c67..023d8cd3dfb675 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -717,6 +717,8 @@ static jl_value_t *widen_Type(jl_value_t *t JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT // when a static parameter value is not known exactly. static jl_value_t *fix_inferred_var_bound(jl_tvar_t *var, jl_value_t *ty JL_MAYBE_UNROOTED) { + if (ty == NULL) // may happen if the user is intersecting with an incomplete type + return (jl_value_t*)var; if (!jl_is_typevar(ty) && jl_has_free_typevars(ty)) { jl_value_t *ans = ty; jl_array_t *vs = NULL; @@ -849,7 +851,7 @@ static int subtype_unionall(jl_value_t *t, jl_unionall_t *u, jl_stenv_t *e, int8 if (oldval && !jl_egal(oldval, val)) e->envout[e->envidx] = (jl_value_t*)u->var; else - e->envout[e->envidx] = fix_inferred_var_bound(u->var, val); + e->envout[e->envidx] = val; // TODO: substitute the value (if any) of this variable into previous envout entries } @@ -1897,6 +1899,16 @@ JL_DLLEXPORT int jl_subtype_env(jl_value_t *x, jl_value_t *y, jl_value_t **env, if (obvious_subtype == 0 || (obvious_subtype == 1 && envsz == 0)) subtype = obvious_subtype; // this ensures that running in a debugger doesn't change the result #endif + if (env) { + jl_unionall_t *ub = (jl_unionall_t*)y; + int i; + for (i = 0; i < envsz; i++) { + assert(jl_is_unionall(ub)); + jl_tvar_t *var = ub->var; + env[i] = fix_inferred_var_bound(var, env[i]); + ub = (jl_unionall_t*)ub->body; + } + } return subtype; } @@ -2605,7 +2617,7 @@ static jl_value_t *finish_unionall(jl_value_t *res JL_MAYBE_UNROOTED, jl_varbind if (!varval || (!is_leaf_bound(varval) && !vb->occurs_inv)) e->envout[e->envidx] = (jl_value_t*)vb->var; else if (!(oldval && jl_is_typevar(oldval) && jl_is_long(varval))) - e->envout[e->envidx] = fix_inferred_var_bound(vb->var, varval); + e->envout[e->envidx] = varval; } JL_GC_POP(); @@ -3532,17 +3544,11 @@ jl_value_t *jl_type_intersection_env_s(jl_value_t *a, jl_value_t *b, jl_svec_t * } if (penv) { jl_svec_t *e = jl_alloc_svec(sz); - *penv = e; - for (i = 0; i < sz; i++) - jl_svecset(e, i, env[i]); - jl_unionall_t *ub = (jl_unionall_t*)b; for (i = 0; i < sz; i++) { - assert(jl_is_unionall(ub)); - // TODO: assert(env[i] != NULL); - if (env[i] == NULL) - env[i] = (jl_value_t*)ub->var; - ub = (jl_unionall_t*)ub->body; + assert(env[i]); + jl_svecset(e, i, env[i]); } + *penv = e; } bot: JL_GC_POP(); @@ -3583,17 +3589,11 @@ int jl_subtype_matching(jl_value_t *a, jl_value_t *b, jl_svec_t **penv) // copy env to svec for return int i = 0; jl_svec_t *e = jl_alloc_svec(szb); - *penv = e; - for (i = 0; i < szb; i++) - jl_svecset(e, i, env[i]); - jl_unionall_t *ub = (jl_unionall_t*)b; for (i = 0; i < szb; i++) { - assert(jl_is_unionall(ub)); - // TODO: assert(env[i] != NULL); - if (env[i] == NULL) - env[i] = (jl_value_t*)ub->var; - ub = (jl_unionall_t*)ub->body; + assert(env[i]); + jl_svecset(e, i, env[i]); } + *penv = e; } JL_GC_POP(); return sub; From a3ba75710cd5aa0aa329c5b659de1905a16cb2a4 Mon Sep 17 00:00:00 2001 From: Ronan Arraes Jardim Chagas Date: Tue, 20 Dec 2022 12:53:37 -0300 Subject: [PATCH 136/387] [Markdown] use textwidth to compute string width (#47761) --- stdlib/Markdown/src/render/terminal/formatting.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/Markdown/src/render/terminal/formatting.jl b/stdlib/Markdown/src/render/terminal/formatting.jl index 87022124b9c8a8..1d9e9a55231844 100644 --- a/stdlib/Markdown/src/render/terminal/formatting.jl +++ b/stdlib/Markdown/src/render/terminal/formatting.jl @@ -3,7 +3,7 @@ # Wrapping function ansi_length(s) - replace(s, r"\e\[[0-9]+m" => "") |> length + replace(s, r"\e\[[0-9]+m" => "") |> textwidth end words(s) = split(s, " ") From 8cdb17b48a005a97889f07593c4a619add46ea76 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Wed, 21 Dec 2022 01:07:55 +0900 Subject: [PATCH 137/387] put back the old QuickSort, PartialQuickSort, and MergeSort algorithms... (#47788) ...as they were in 1.8 and rename the new PartialQuickSort to QuickerSort Also improve the documentation and API for constructing QuickerSort and test the API Co-authored-by: Lilith Hafner --- base/sort.jl | 241 ++++++++++++++++++++++++++++++++++++------------ test/sorting.jl | 43 ++++++--- 2 files changed, 212 insertions(+), 72 deletions(-) diff --git a/base/sort.jl b/base/sort.jl index 2dd81829312d07..6d9f65c61b390b 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -86,7 +86,7 @@ issorted(itr; issorted(itr, ord(lt,by,rev,order)) function partialsort!(v::AbstractVector, k::Union{Integer,OrdinalRange}, o::Ordering) - _sort!(v, _PartialQuickSort(k), o, (;)) + _sort!(v, QuickerSort(k), o, (;)) maybeview(v, k) end @@ -931,49 +931,40 @@ end """ - PartialQuickSort(lo::Union{Integer, Missing}, hi::Union{Integer, Missing}, next::Algorithm) <: Algorithm + QuickerSort(next::Algorithm=SMALL_ALGORITHM) <: Algorithm + QuickerSort(lo::Union{Integer, Missing}, hi::Union{Integer, Missing}=lo, next::Algorithm=SMALL_ALGORITHM) <: Algorithm -Indicate that a sorting function should use the partial quick sort algorithm. +Use the `QuickerSort` algorithm with the `next` algorithm as a base case. -Partial quick sort finds and sorts the elements that would end up in positions `lo:hi` using -[`QuickSort`](@ref). It is recursive and uses the `next` algorithm for small chunks +`QuickerSort` is like `QuickSort`, but utilizes scratch space to operate faster and allow +for the possibility of maintaining stability. + +If `lo` and `hi` are provided, finds and sorts the elements in the range `lo:hi`, reordering +but not necessarily sorting other elements in the process. If `lo` or `hi` is `missing`, it +is treated as the first or last index of the input, respectively. + +`lo` and `hi` may be specified together as an `AbstractUnitRange`. Characteristics: * *stable*: preserves the ordering of elements which compare equal (e.g. "a" and "A" in a sort of letters which ignores case). * *not in-place* in memory. - * *divide-and-conquer*: sort strategy similar to [`MergeSort`](@ref). + * *divide-and-conquer*: sort strategy similar to [`QuickSort`](@ref). + * *linear runtime* if `length(lo:hi)` is constant + * *quadratic worst case runtime* in pathological cases + (vanishingly rare for non-malicious input) """ -struct PartialQuickSort{L<:Union{Integer,Missing}, H<:Union{Integer,Missing}, T<:Algorithm} <: Algorithm +struct QuickerSort{L<:Union{Integer,Missing}, H<:Union{Integer,Missing}, T<:Algorithm} <: Algorithm lo::L hi::H next::T end -PartialQuickSort(k::Integer) = PartialQuickSort(missing, k, SMALL_ALGORITHM) -PartialQuickSort(k::OrdinalRange) = PartialQuickSort(first(k), last(k), SMALL_ALGORITHM) -_PartialQuickSort(k::Integer) = InitialOptimizations(PartialQuickSort(k:k)) -_PartialQuickSort(k::OrdinalRange) = InitialOptimizations(PartialQuickSort(k)) - -""" - QuickSort - -Indicate that a sorting function should use the quick sort algorithm. +QuickerSort(next::Algorithm=SMALL_ALGORITHM) = QuickerSort(missing, missing, next) +QuickerSort(lo::Union{Integer, Missing}, hi::Union{Integer, Missing}) = QuickerSort(lo, hi, SMALL_ALGORITHM) +QuickerSort(lo::Union{Integer, Missing}, next::Algorithm=SMALL_ALGORITHM) = QuickerSort(lo, lo, next) +QuickerSort(r::OrdinalRange, next::Algorithm=SMALL_ALGORITHM) = QuickerSort(first(r), last(r), next) -Quick sort picks a pivot element, partitions the array based on the pivot, -and then sorts the elements before and after the pivot recursively. - -Characteristics: - * *stable*: preserves the ordering of elements which compare equal - (e.g. "a" and "A" in a sort of letters which ignores case). - * *not in-place* in memory. - * *divide-and-conquer*: sort strategy similar to [`MergeSort`](@ref). - * *good performance* for almost all large collections. - * *quadratic worst case runtime* in pathological cases - (vanishingly rare for non-malicious input) -""" -const QuickSort = PartialQuickSort(missing, missing, SMALL_ALGORITHM) - -# select a pivot for QuickSort +# select a pivot for QuickerSort # # This method is redefined to rand(lo:hi) in Random.jl # We can't use rand here because it is not available in Core.Compiler and @@ -1013,7 +1004,7 @@ function partition!(t::AbstractVector, lo::Integer, hi::Integer, offset::Integer pivot, lo-offset end -function _sort!(v::AbstractVector, a::PartialQuickSort, o::Ordering, kw; +function _sort!(v::AbstractVector, a::QuickerSort, o::Ordering, kw; t=nothing, offset=nothing, swap=false, rev=false) @getkw lo hi scratch @@ -1029,7 +1020,7 @@ function _sort!(v::AbstractVector, a::PartialQuickSort, o::Ordering, kw; @inbounds v[j] = pivot swap = !swap - # For QuickSort, a.lo === a.hi === missing, so the first two branches get skipped + # For QuickerSort(), a.lo === a.hi === missing, so the first two branches get skipped if !ismissing(a.lo) && j <= a.lo # Skip sorting the lower part swap && copyto!(v, lo, t, lo+offset, j-lo) rev && reverse!(v, lo, j-1) @@ -1225,7 +1216,7 @@ the initial optimizations because they can change the input vector's type and or make them `UIntMappable`. If the input is not [`UIntMappable`](@ref), then we perform a presorted check and dispatch -to [`QuickSort`](@ref). +to [`QuickerSort`](@ref). Otherwise, we dispatch to [`InsertionSort`](@ref) for inputs with `length <= 40` and then perform a presorted check ([`CheckSorted`](@ref)). @@ -1257,7 +1248,7 @@ Consequently, we apply [`RadixSort`](@ref) for any reasonably long inputs that r stage. Finally, if the input has length less than 80, we dispatch to [`InsertionSort`](@ref) and -otherwise we dispatch to [`QuickSort`](@ref). +otherwise we dispatch to [`QuickerSort`](@ref). """ const DEFAULT_STABLE = InitialOptimizations( IsUIntMappable( @@ -1267,9 +1258,9 @@ const DEFAULT_STABLE = InitialOptimizations( ConsiderCountingSort( ConsiderRadixSort( Small{80}( - QuickSort)))))), + QuickerSort())))))), StableCheckSorted( - QuickSort))) + QuickerSort()))) """ DEFAULT_UNSTABLE @@ -1483,7 +1474,7 @@ function partialsortperm!(ix::AbstractVector{<:Integer}, v::AbstractVector, end # do partial quicksort - _sort!(ix, _PartialQuickSort(k), Perm(ord(lt, by, rev, order), v), (;)) + _sort!(ix, QuickerSort(k), Perm(ord(lt, by, rev, order), v), (;)) maybeview(ix, k) end @@ -1863,18 +1854,53 @@ end ### Unused constructs for backward compatibility ### -struct MergeSortAlg{T <: Algorithm} <: Algorithm - next::T +## Old algorithms ## + +struct QuickSortAlg <: Algorithm end +struct MergeSortAlg <: Algorithm end + +""" + PartialQuickSort{T <: Union{Integer,OrdinalRange}} + +Indicate that a sorting function should use the partial quick sort +algorithm. Partial quick sort returns the smallest `k` elements sorted from smallest +to largest, finding them and sorting them using [`QuickSort`](@ref). + +Characteristics: + * *not stable*: does not preserve the ordering of elements which + compare equal (e.g. "a" and "A" in a sort of letters which + ignores case). + * *in-place* in memory. + * *divide-and-conquer*: sort strategy similar to [`MergeSort`](@ref). +""" +struct PartialQuickSort{T <: Union{Integer,OrdinalRange}} <: Algorithm + k::T end """ - MergeSort + QuickSort -Indicate that a sorting function should use the merge sort algorithm. +Indicate that a sorting function should use the quick sort +algorithm, which is *not* stable. -Merge sort divides the collection into subcollections and -repeatedly merges them, sorting each subcollection at each step, -until the entire collection has been recombined in sorted form. +Characteristics: + * *not stable*: does not preserve the ordering of elements which + compare equal (e.g. "a" and "A" in a sort of letters which + ignores case). + * *in-place* in memory. + * *divide-and-conquer*: sort strategy similar to [`MergeSort`](@ref). + * *good performance* for large collections. +""" +const QuickSort = QuickSortAlg() + +""" + MergeSort + +Indicate that a sorting function should use the merge sort +algorithm. Merge sort divides the collection into +subcollections and repeatedly merges them, sorting each +subcollection at each step, until the entire +collection has been recombined in sorted form. Characteristics: * *stable*: preserves the ordering of elements which compare @@ -1883,21 +1909,94 @@ Characteristics: * *not in-place* in memory. * *divide-and-conquer* sort strategy. """ -const MergeSort = MergeSortAlg(SMALL_ALGORITHM) +const MergeSort = MergeSortAlg() -function _sort!(v::AbstractVector, a::MergeSortAlg, o::Ordering, kw; t=nothing, offset=nothing) - @getkw lo hi scratch +# selectpivot! +# +# Given 3 locations in an array (lo, mi, and hi), sort v[lo], v[mi], v[hi]) and +# choose the middle value as a pivot +# +# Upon return, the pivot is in v[lo], and v[hi] is guaranteed to be +# greater than the pivot + +@inline function selectpivot!(v::AbstractVector, lo::Integer, hi::Integer, o::Ordering) + @inbounds begin + mi = midpoint(lo, hi) + + # sort v[mi] <= v[lo] <= v[hi] such that the pivot is immediately in place + if lt(o, v[lo], v[mi]) + v[mi], v[lo] = v[lo], v[mi] + end + + if lt(o, v[hi], v[lo]) + if lt(o, v[hi], v[mi]) + v[hi], v[lo], v[mi] = v[lo], v[mi], v[hi] + else + v[hi], v[lo] = v[lo], v[hi] + end + end + + # return the pivot + return v[lo] + end +end + +# partition! +# +# select a pivot, and partition v according to the pivot + +function partition!(v::AbstractVector, lo::Integer, hi::Integer, o::Ordering) + pivot = selectpivot!(v, lo, hi, o) + # pivot == v[lo], v[hi] > pivot + i, j = lo, hi + @inbounds while true + i += 1; j -= 1 + while lt(o, v[i], pivot); i += 1; end; + while lt(o, pivot, v[j]); j -= 1; end; + i >= j && break + v[i], v[j] = v[j], v[i] + end + v[j], v[lo] = pivot, v[j] + + # v[j] == pivot + # v[k] >= pivot for k > j + # v[i] <= pivot for i < j + return j +end + +function sort!(v::AbstractVector, lo::Integer, hi::Integer, a::QuickSortAlg, o::Ordering) + @inbounds while lo < hi + hi-lo <= SMALL_THRESHOLD && return sort!(v, lo, hi, SMALL_ALGORITHM, o) + j = partition!(v, lo, hi, o) + if j-lo < hi-j + # recurse on the smaller chunk + # this is necessary to preserve O(log(n)) + # stack space in the worst case (rather than O(n)) + lo < (j-1) && sort!(v, lo, j-1, a, o) + lo = j+1 + else + j+1 < hi && sort!(v, j+1, hi, a, o) + hi = j-1 + end + end + return v +end + +sort!(v::AbstractVector{T}, lo::Integer, hi::Integer, a::MergeSortAlg, o::Ordering, t0::Vector{T}) where T = + invoke(sort!, Tuple{typeof.((v, lo, hi, a, o))..., AbstractVector{T}}, v, lo, hi, a, o, t0) # For disambiguation +function sort!(v::AbstractVector{T}, lo::Integer, hi::Integer, a::MergeSortAlg, o::Ordering, + t0::Union{AbstractVector{T}, Nothing}=nothing) where T @inbounds if lo < hi - hi-lo <= SMALL_THRESHOLD && return _sort!(v, a.next, o, kw) + hi-lo <= SMALL_THRESHOLD && return sort!(v, lo, hi, SMALL_ALGORITHM, o) m = midpoint(lo, hi) - if t === nothing - scratch, t = make_scratch(scratch, eltype(v), m-lo+1) - end + t = t0 === nothing ? similar(v, m-lo+1) : t0 + length(t) < m-lo+1 && resize!(t, m-lo+1) + Base.require_one_based_indexing(t) - _sort!(v, a, o, (;kw..., hi=m, scratch); t, offset) - _sort!(v, a, o, (;kw..., lo=m+1, scratch); t, offset) + sort!(v, lo, m, a, o, t) + sort!(v, m+1, hi, a, o, t) i, j = 1, lo while j <= m @@ -1924,9 +2023,37 @@ function _sort!(v::AbstractVector, a::MergeSortAlg, o::Ordering, kw; t=nothing, end end - scratch + return v +end + +function sort!(v::AbstractVector, lo::Integer, hi::Integer, a::PartialQuickSort, + o::Ordering) + @inbounds while lo < hi + hi-lo <= SMALL_THRESHOLD && return sort!(v, lo, hi, SMALL_ALGORITHM, o) + j = partition!(v, lo, hi, o) + + if j <= first(a.k) + lo = j+1 + elseif j >= last(a.k) + hi = j-1 + else + # recurse on the smaller chunk + # this is necessary to preserve O(log(n)) + # stack space in the worst case (rather than O(n)) + if j-lo < hi-j + lo < (j-1) && sort!(v, lo, j-1, a, o) + lo = j+1 + else + hi > (j+1) && sort!(v, j+1, hi, a, o) + hi = j-1 + end + end + end + return v end +## Old extensibility mechanisms ## + # Support 3-, 5-, and 6-argument versions of sort! for calling into the internals in the old way sort!(v::AbstractVector, a::Algorithm, o::Ordering) = sort!(v, firstindex(v), lastindex(v), a, o) function sort!(v::AbstractVector, lo::Integer, hi::Integer, a::Algorithm, o::Ordering) @@ -1952,8 +2079,4 @@ function _sort!(v::AbstractVector, a::Algorithm, o::Ordering, kw) end end -# Keep old internal types so that people can keep dispatching with -# sort!(::AbstractVector, ::Integer, ::Integer, ::Base.QuickSortAlg, ::Ordering) = ... -const QuickSortAlg = typeof(QuickSort) - end # module Sort diff --git a/test/sorting.jl b/test/sorting.jl index 614946a8cc4f6f..eb5020547c789a 100644 --- a/test/sorting.jl +++ b/test/sorting.jl @@ -79,9 +79,8 @@ end end @testset "stability" begin - for Alg in [InsertionSort, MergeSort, QuickSort, Base.DEFAULT_STABLE, - PartialQuickSort(missing, 1729, Base.Sort.SMALL_ALGORITHM), - PartialQuickSort(1729, missing, Base.Sort.SMALL_ALGORITHM)] + for Alg in [InsertionSort, MergeSort, Base.Sort.QuickerSort(), Base.DEFAULT_STABLE, + Base.Sort.QuickerSort(missing, 1729), Base.Sort.QuickerSort(1729, missing)] @test issorted(sort(1:2000, alg=Alg, by=x->0)) @test issorted(sort(1:2000, alg=Alg, by=x->x÷100)) end @@ -334,7 +333,7 @@ end @test c == v # stable algorithms - for alg in [MergeSort, QuickSort, PartialQuickSort(1:n), Base.DEFAULT_STABLE] + for alg in [MergeSort, Base.Sort.QuickerSort(), Base.Sort.QuickerSort(1:n), Base.DEFAULT_STABLE] p = sortperm(v, alg=alg, rev=rev) p2 = sortperm(float(v), alg=alg, rev=rev) @test p == p2 @@ -382,7 +381,7 @@ end end v = randn_with_nans(n,0.1) - for alg in [InsertionSort, MergeSort, QuickSort, PartialQuickSort(n), Base.DEFAULT_UNSTABLE, Base.DEFAULT_STABLE], + for alg in [InsertionSort, MergeSort, Base.Sort.QuickerSort(), Base.Sort.QuickerSort(1, n), Base.DEFAULT_UNSTABLE, Base.DEFAULT_STABLE], rev in [false,true] alg === InsertionSort && n >= 3000 && continue # test float sorting with NaNs @@ -589,7 +588,7 @@ end @testset "fallback" begin @test adaptive_sort_test(rand(1:typemax(Int32), len), by=x->x^2)# fallback - @test adaptive_sort_test(rand(Int, len), by=x->0, trusted=QuickSort) + @test adaptive_sort_test(rand(Int, len), by=x->0, trusted=Base.Sort.QuickerSort()) end @test adaptive_sort_test(rand(Int, 20)) # InsertionSort @@ -691,15 +690,16 @@ end @testset "invalid lt (#11429)" begin # lt must be a total linear order (e.g. < not <=) so this usage is # not allowed. Consequently, none of the behavior tested in this - # testset is gaurunteed to work in future minor versions of Julia. + # testset is guaranteed to work in future minor versions of Julia. + + safe_algs = [InsertionSort, MergeSort, Base.Sort.QuickerSort(), Base.DEFAULT_STABLE, Base.DEFAULT_UNSTABLE] n = 1000 v = rand(1:5, n); s = sort(v); # Nevertheless, it still works... - for alg in [InsertionSort, MergeSort, QuickSort, - Base.DEFAULT_STABLE, Base.DEFAULT_UNSTABLE] + for alg in safe_algs @test sort(v, alg=alg, lt = <=) == s end @test partialsort(v, 172, lt = <=) == s[172] @@ -709,16 +709,14 @@ end # where i < j if and only if lt(o, v[j], v[i]). This invariant holds even for # this invalid lt order. perm = reverse(sortperm(v, rev=true)) - for alg in [InsertionSort, MergeSort, QuickSort, - Base.DEFAULT_STABLE, Base.DEFAULT_UNSTABLE] + for alg in safe_algs @test sort(1:n, alg=alg, lt = (i,j) -> v[i]<=v[j]) == perm end @test partialsort(1:n, 172, lt = (i,j) -> v[i]<=v[j]) == perm[172] @test partialsort(1:n, 315:415, lt = (i,j) -> v[i]<=v[j]) == perm[315:415] # lt can be very poorly behaved and sort will still permute its input in some way. - for alg in [InsertionSort, MergeSort, QuickSort, - Base.DEFAULT_STABLE, Base.DEFAULT_UNSTABLE] + for alg in safe_algs @test sort!(sort(v, alg=alg, lt = (x,y) -> rand([false, true]))) == s end @test partialsort(v, 172, lt = (x,y) -> rand([false, true])) ∈ 1:5 @@ -901,6 +899,25 @@ end @test issorted(sort(rand(Int8, 600))) end +@testset "QuickerSort API" begin + bsqs = Base.Sort.QuickerSort + @test bsqs(1, 2, MergeSort) === bsqs(1, 2, MergeSort) + @test bsqs(missing, 2, MergeSort) === bsqs(missing, 2, MergeSort) + @test bsqs(1, missing, MergeSort) === bsqs(1, missing, MergeSort) + @test bsqs(missing, missing, MergeSort) === bsqs(missing, missing, MergeSort) + @test bsqs(1, MergeSort) === bsqs(1, 1, MergeSort) + @test bsqs(missing, MergeSort) === bsqs(missing, missing, MergeSort) + @test bsqs(MergeSort) === bsqs(missing, missing, MergeSort) + + @test bsqs(1, 2) === bsqs(1, 2, InsertionSort) + @test bsqs(missing, 2) === bsqs(missing, 2, InsertionSort) + @test bsqs(1, missing) === bsqs(1, missing, InsertionSort) + @test bsqs(missing, missing) === bsqs(missing, missing, InsertionSort) + @test bsqs(1) === bsqs(1, 1, InsertionSort) + @test bsqs(missing) === bsqs(missing, missing, InsertionSort) + @test bsqs() === bsqs(missing, missing, InsertionSort) +end + # This testset is at the end of the file because it is slow. @testset "searchsorted" begin numTypes = [ Int8, Int16, Int32, Int64, Int128, From ac77be92b55d630d3ede7389c46ac90a668d657a Mon Sep 17 00:00:00 2001 From: Sukera Date: Tue, 20 Dec 2022 21:00:19 +0100 Subject: [PATCH 138/387] [PCRE2_jll] Upgrade to 10.42 --- deps/checksums/pcre | 68 +++++++++++++++---------------- deps/pcre.version | 2 +- stdlib/PCRE2_jll/Project.toml | 2 +- stdlib/PCRE2_jll/test/runtests.jl | 2 +- 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/deps/checksums/pcre b/deps/checksums/pcre index 202265ee580609..cab79abe745bf4 100644 --- a/deps/checksums/pcre +++ b/deps/checksums/pcre @@ -1,34 +1,34 @@ -PCRE2.v10.40.0+0.aarch64-apple-darwin.tar.gz/md5/3d6b01c094c9e1adad2c1d42a3e7c3a6 -PCRE2.v10.40.0+0.aarch64-apple-darwin.tar.gz/sha512/374f9f35ae7925a6db6249850822d90c56c11b1b49971b76f016203e85bcc14ea6ab7e017b0ad5ce56c47b0715b2a396099749656e7d7291008a2dc8cb393792 -PCRE2.v10.40.0+0.aarch64-linux-gnu.tar.gz/md5/0f4c7daae3c08e5438b0af3299cbb003 -PCRE2.v10.40.0+0.aarch64-linux-gnu.tar.gz/sha512/ee9c6275019ef09a2fd7c6a649ebe184b58dae4e65a9b38159bac596e0427819e086084ca56be0f2f2ad0eb98a50a2511999cb46d5e9d1f03d39b04ade5e270d -PCRE2.v10.40.0+0.aarch64-linux-musl.tar.gz/md5/baf858fd38471dd933312079ebaf065d -PCRE2.v10.40.0+0.aarch64-linux-musl.tar.gz/sha512/3b50f6380673d30d487a3b10e6c58b76ff47fbb5c774f59f15bcc0b92e7740e73ad04c62b86e8eab0c916d4c231449f5279eae37aa401fab1a46c6e11687e806 -PCRE2.v10.40.0+0.armv6l-linux-gnueabihf.tar.gz/md5/9c582d85fe43e205679d2ed8d1ee3df7 -PCRE2.v10.40.0+0.armv6l-linux-gnueabihf.tar.gz/sha512/fb7df17fa39ac93c7af92f4afdcdd120b171682ce172561a65fae3c6e3b1c26c5715b1264007fd12713464cbff406fb19117adaf1d50bd239f0dc53e7842ca8e -PCRE2.v10.40.0+0.armv6l-linux-musleabihf.tar.gz/md5/a9c6c90c69d3de7030bd5015092a1340 -PCRE2.v10.40.0+0.armv6l-linux-musleabihf.tar.gz/sha512/7030aaaac0d275e72f3a36fe5104d11eba9bd1909c3d7126c751c9409f619d25c7735c7d3354b48786aef1ca9f1be48a60e0bd04a04c6b098915e6c4b2935e5f -PCRE2.v10.40.0+0.armv7l-linux-gnueabihf.tar.gz/md5/cc4add9c80f47ac3fb682aca3347aca3 -PCRE2.v10.40.0+0.armv7l-linux-gnueabihf.tar.gz/sha512/4a21795524d3cf8112384d133b47e87738a8c1efa71606fb55f5fabe1cc4108b2921c2efb539506552a2b630398a6770d93c9c541d5123b7a84016aad7a112f0 -PCRE2.v10.40.0+0.armv7l-linux-musleabihf.tar.gz/md5/51c54233c6e536671f2c1af74e1773d5 -PCRE2.v10.40.0+0.armv7l-linux-musleabihf.tar.gz/sha512/3889cf1faacd16779c87ac00317fbc36e54f5a99733b838920add360196edbe388c12421380105a87041d3502e5f4bea74460dedc3d797aafde5cb0f960516d0 -PCRE2.v10.40.0+0.i686-linux-gnu.tar.gz/md5/368342965b12beed2c4c92e60f7dda8f -PCRE2.v10.40.0+0.i686-linux-gnu.tar.gz/sha512/bdb3692412d0b1d07bf302fbd129755e4a53e6b39caf135df912da79088e5db29a788680b282292919c45560a795fab60d043feece63cae2296165a9909ecb57 -PCRE2.v10.40.0+0.i686-linux-musl.tar.gz/md5/79bf801c0d86614ebf95ef83016195e6 -PCRE2.v10.40.0+0.i686-linux-musl.tar.gz/sha512/d35d15ccc8b09a33088efb4bf631cbbb3ff332521f37fdaa5fc106e576a54cb57ad1243dc3db1ab17a8195fd1476889b8d548987437a195267fae7683769da38 -PCRE2.v10.40.0+0.i686-w64-mingw32.tar.gz/md5/930cbf007549542b027a1db72bab0e58 -PCRE2.v10.40.0+0.i686-w64-mingw32.tar.gz/sha512/e9bad56ca6e1871f2bf37c8b2b03ecbc77acd3f4b04c95dd6e63a4cb38487fc3349a97ca7f575c158fde8b948c363af3f7cffc4ad89af9df09e536119a1d743b -PCRE2.v10.40.0+0.powerpc64le-linux-gnu.tar.gz/md5/cebf0e67b6ae67fa841e491bf8955ae0 -PCRE2.v10.40.0+0.powerpc64le-linux-gnu.tar.gz/sha512/e04087f3e3268d389c08068ac8ae45f017e742787f20235eb6e4d32257ae3a3e445c61dc80db5a2c73d3fea5721272ec517c8b3be428d8aca097e691a14eb659 -PCRE2.v10.40.0+0.x86_64-apple-darwin.tar.gz/md5/5ed58d794f55139baac9a1ee50da3647 -PCRE2.v10.40.0+0.x86_64-apple-darwin.tar.gz/sha512/e906c6953be8a894d4cfa1792843e85aef58cf3b87baf4bcba99d19c84bd7d67dfbde85f1ddad42cbd51d2b1fa36797ce2ad79d79b19a792ca886bf52632a919 -PCRE2.v10.40.0+0.x86_64-linux-gnu.tar.gz/md5/db3fd5e855ca47b90d9a1faf58c88279 -PCRE2.v10.40.0+0.x86_64-linux-gnu.tar.gz/sha512/9082201b6519a693cf0038cf667841a0a4e4158698e1b7455ed3e0db1a7796c7303cf105975ddf059a6dbf5865eaf99f33d4e42803364935da7fa9e9c3bcb5b5 -PCRE2.v10.40.0+0.x86_64-linux-musl.tar.gz/md5/ab3456b926864ab27d5a4ce8dd42d1e7 -PCRE2.v10.40.0+0.x86_64-linux-musl.tar.gz/sha512/4b9109d9fadde86b1d76c420cb3e8b884ccba6fa08fec4fb039c384af5f040cf52b3232fbf4921cf680f36e54683b28bdb77e3b2a8943acf974f446e99f93475 -PCRE2.v10.40.0+0.x86_64-unknown-freebsd.tar.gz/md5/ee7679ad09e13f3cf9a2089e761bd718 -PCRE2.v10.40.0+0.x86_64-unknown-freebsd.tar.gz/sha512/cce31108246bdc2947865339a7cdbb7f505baf3b1b94fa6f6d825416149d8bc888a0a55961873f041cb94bba623c27f5ecaef23dda284cc57b76b30987fb6f5b -PCRE2.v10.40.0+0.x86_64-w64-mingw32.tar.gz/md5/8178c12311e6f74bc1155d6d49dfb612 -PCRE2.v10.40.0+0.x86_64-w64-mingw32.tar.gz/sha512/9d03dd7ee07fdce9af7e6995e533c59dc274417c0e39a27ccea397291b17d6865bf9c80bbc7c9aa8e908518ba33873b39b9cbfd36bc7137cb5b7432c5684e073 -pcre2-10.40.tar.bz2/md5/a5cc4e276129c177d4fffb40601019a4 -pcre2-10.40.tar.bz2/sha512/00e7b48a6554b9127cb6fe24c5cacf72783416a9754ec88f62f73c52f46ed72c86c1869e62c91a31b2ff2cbafbbedabca44b3f1eb7670bc92f49d8401c7374e8 +PCRE2.v10.42.0+0.aarch64-apple-darwin.tar.gz/md5/667a570d341396c3213749ee1e5b5fda +PCRE2.v10.42.0+0.aarch64-apple-darwin.tar.gz/sha512/c1bb99e8928efded9b0ea3f294ceb41daea7254204ca30c0ff88686110ccd58138d8ea8b20b9a9d6d16a6d8d3f34e27e74e7b57d3c8fe6b051c9d8fa6f86431a +PCRE2.v10.42.0+0.aarch64-linux-gnu.tar.gz/md5/1a758f275ff3306fbad7698df7b9b7be +PCRE2.v10.42.0+0.aarch64-linux-gnu.tar.gz/sha512/d09508c0b255366d01f1b4d1ae6748a8e47f18c451498d30715f5f968784990949dab7540cd086396abd912f61b5f7c44c8c72a27efaba0a7fc08b71a167c057 +PCRE2.v10.42.0+0.aarch64-linux-musl.tar.gz/md5/e61147579fdc9b57a61b814bdf9c84bb +PCRE2.v10.42.0+0.aarch64-linux-musl.tar.gz/sha512/eecaf4c1937fc04210b910ac65318524c02d690e8c4894c38e74eaba36d26c87a1fd9e1cc36f4307a11ff3552a79f081fa8f05085435eb34872dc2fdecce2d18 +PCRE2.v10.42.0+0.armv6l-linux-gnueabihf.tar.gz/md5/b4c484a3b87923c0e2e4d9cc5f140eb7 +PCRE2.v10.42.0+0.armv6l-linux-gnueabihf.tar.gz/sha512/5931cf13d340971356a9b87f62c9efdb3656ba649e7b25f1722127a3fd70973d94c815a37b43cecab8eb0ed8d1ae02ef1a0c0a12051852c1b9242c3eaa01c496 +PCRE2.v10.42.0+0.armv6l-linux-musleabihf.tar.gz/md5/bc7b5bb1c5b0b99c121bad5a89299ca7 +PCRE2.v10.42.0+0.armv6l-linux-musleabihf.tar.gz/sha512/86b5ad4fa6f4b5bd1a76ad68ddff4b39916d0ed0acc03a3fee8eab5256aaed53abc0ff4ce9d9d9f8b9203c087211684da92fe6aa06ff5bc331ba1b3da2cba57e +PCRE2.v10.42.0+0.armv7l-linux-gnueabihf.tar.gz/md5/3541eb26fa5a4d13e2c7d063dbd900d8 +PCRE2.v10.42.0+0.armv7l-linux-gnueabihf.tar.gz/sha512/872181f931662edaf653351486c5e2a700e94cfa0966ca90eca893fdc75dd46eb40d9d45737c198aa4b9ad8ebab33fd78697ef35906985e4e1c9748ddf58d363 +PCRE2.v10.42.0+0.armv7l-linux-musleabihf.tar.gz/md5/fe059feb18fcc9312f1033362070fe34 +PCRE2.v10.42.0+0.armv7l-linux-musleabihf.tar.gz/sha512/5a96acf3908c964ccb4f296c449499388ed447d9a094c2760c979e02ef656fa710ede3926b9626e89fb5b0545c111e6eedff21e48416e923c17fc9ff129d0519 +PCRE2.v10.42.0+0.i686-linux-gnu.tar.gz/md5/67f49cb139017109c422c51c0120823a +PCRE2.v10.42.0+0.i686-linux-gnu.tar.gz/sha512/8873d9995bdf5701fc5a24163f93eada12af76d09781a679a4ed61b66f117cf322505d291931d1c58b3b3eb560f6487a1100b0735c14abe6cb38677750b481c7 +PCRE2.v10.42.0+0.i686-linux-musl.tar.gz/md5/092af10d8182cb4240cdd975efce4d7c +PCRE2.v10.42.0+0.i686-linux-musl.tar.gz/sha512/79a48f4fd50ffdf49c8d57581e01ace38c1b3d7edd86d44db44b8efd93074d16faf035131a0d60c6631b8bf22f0fd8296acedba45908da56e8096c296122f047 +PCRE2.v10.42.0+0.i686-w64-mingw32.tar.gz/md5/2bb13db8b5d6d1a5632de3db874c2614 +PCRE2.v10.42.0+0.i686-w64-mingw32.tar.gz/sha512/7d1324696087c32d1bbbb64f5e4b8c8a220ef216d025886b3c3e6d685c3f701428c6696d7ae0bcc771d3295381ba2bdd5db040f788f8a9a58f80ad4d790dd141 +PCRE2.v10.42.0+0.powerpc64le-linux-gnu.tar.gz/md5/0de1215b2a1e9c0efd131355e9fbf2c1 +PCRE2.v10.42.0+0.powerpc64le-linux-gnu.tar.gz/sha512/69dae12627685ae665db8c91264a79aba7c60ae97eccdc79ef889f2a5f69b465fa333aba298fc90bbb95710cfc324e3630bc427a97577855e8fb6c8fe227cfec +PCRE2.v10.42.0+0.x86_64-apple-darwin.tar.gz/md5/c5c52b399921c5ab81a5f598b350d2ca +PCRE2.v10.42.0+0.x86_64-apple-darwin.tar.gz/sha512/e6c8ba3aa3fbf54b37079301ab317104c6852812b23835f52ca40f31f0831678172d32e077fbaa712a8a2cb16d62bb97d475827004353e7807922a2d6e049b28 +PCRE2.v10.42.0+0.x86_64-linux-gnu.tar.gz/md5/b074dd1f85e24e723349e566350e2c78 +PCRE2.v10.42.0+0.x86_64-linux-gnu.tar.gz/sha512/236017e02c9f32b913b772dbf22897c8460e5791f196c86f8a073e329ad8925f6859afe48f3bf18ca057c265f08fedbde255360d8f859e2303c6569ab1b0e1bb +PCRE2.v10.42.0+0.x86_64-linux-musl.tar.gz/md5/9f32ca77e79843fc9c4b5fc8ed336d11 +PCRE2.v10.42.0+0.x86_64-linux-musl.tar.gz/sha512/334a31724e9d69c6517568d922717ce76d85cf87dbc863b7262b25ab43c79734b457833cd42674eb6a004864e5c74da3ae1d0a45794b4cd459eea24d9669fac5 +PCRE2.v10.42.0+0.x86_64-unknown-freebsd.tar.gz/md5/037bf13e9a53eb90846b6643610a17df +PCRE2.v10.42.0+0.x86_64-unknown-freebsd.tar.gz/sha512/64bc9acda3d158621f442aa2e766730cc425df3795965f461b530d8152934ffaf93d75b86ebc483345b78b203b0502857683c183ec65a01da1834b55405c7f77 +PCRE2.v10.42.0+0.x86_64-w64-mingw32.tar.gz/md5/6b04c3778bf02947cb1b7e70a41f3292 +PCRE2.v10.42.0+0.x86_64-w64-mingw32.tar.gz/sha512/9b808832cc48703ed525eca06d1dd0162dae3f94a9ad72d044876edcb86a90e8443c8b169e60ccf3507d5960156c447d8f3f30e586ac2a22b6d43dbe807009d0 +pcre2-10.42.tar.bz2/md5/a8e9ab2935d428a4807461f183034abe +pcre2-10.42.tar.bz2/sha512/72fbde87fecec3aa4b47225dd919ea1d55e97f2cbcf02aba26e5a0d3b1ffb58c25a80a9ef069eb99f9cf4e41ba9604ad06a7ec159870e1e875d86820e12256d3 diff --git a/deps/pcre.version b/deps/pcre.version index 522c6a56055144..ce27921435e1d7 100644 --- a/deps/pcre.version +++ b/deps/pcre.version @@ -2,4 +2,4 @@ PCRE_JLL_NAME := PCRE2 ## source build -PCRE_VER := 10.40 +PCRE_VER := 10.42 diff --git a/stdlib/PCRE2_jll/Project.toml b/stdlib/PCRE2_jll/Project.toml index 187eddb2a55413..d630c04383bfb7 100644 --- a/stdlib/PCRE2_jll/Project.toml +++ b/stdlib/PCRE2_jll/Project.toml @@ -1,6 +1,6 @@ name = "PCRE2_jll" uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" -version = "10.40.0+0" +version = "10.42.0+0" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/stdlib/PCRE2_jll/test/runtests.jl b/stdlib/PCRE2_jll/test/runtests.jl index 21e7e7db7286be..d593b07af31ce8 100644 --- a/stdlib/PCRE2_jll/test/runtests.jl +++ b/stdlib/PCRE2_jll/test/runtests.jl @@ -6,5 +6,5 @@ using Test, Libdl, PCRE2_jll vstr = zeros(UInt8, 32) @test ccall((:pcre2_config_8, libpcre2_8), Cint, (UInt32, Ref{UInt8}), 11, vstr) > 0 vn = VersionNumber(split(unsafe_string(pointer(vstr)), " ")[1]) - @test vn == v"10.40.0" + @test vn == v"10.42.0" end From 30236cf760331eaf6fb64edc3577701d2094ac73 Mon Sep 17 00:00:00 2001 From: Sukera Date: Tue, 20 Dec 2022 23:06:05 +0100 Subject: [PATCH 139/387] Add regression tests --- stdlib/PCRE2_jll/test/runtests.jl | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/stdlib/PCRE2_jll/test/runtests.jl b/stdlib/PCRE2_jll/test/runtests.jl index d593b07af31ce8..ab7f750b203e0d 100644 --- a/stdlib/PCRE2_jll/test/runtests.jl +++ b/stdlib/PCRE2_jll/test/runtests.jl @@ -8,3 +8,14 @@ using Test, Libdl, PCRE2_jll vn = VersionNumber(split(unsafe_string(pointer(vstr)), " ")[1]) @test vn == v"10.42.0" end + +@testset "#47936" begin + tests = (r"a+[bc]+c", + r"a+[bc]{1,2}c", + r"(a)+[bc]+c", + r"a{1,2}[bc]+c", + r"(a+)[bc]+c") + for re in tests + @test !isnothing(match(re, "ababc")) + end +end From ee77ebe194afb39af1894e2e37ea543f7cac7e59 Mon Sep 17 00:00:00 2001 From: DilumAluthgeBot <43731525+DilumAluthgeBot@users.noreply.github.com> Date: Wed, 21 Dec 2022 09:26:17 -0500 Subject: [PATCH 140/387] =?UTF-8?q?=F0=9F=A4=96=20Bump=20the=20Pkg=20stdli?= =?UTF-8?q?b=20from=20a8a8e224e=20to=20a8ae3c580=20(#47950)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Pkg-a8a8e224eb78352ab82102c48d3b9c46fb6ee9cd.tar.gz/md5 | 1 - .../Pkg-a8a8e224eb78352ab82102c48d3b9c46fb6ee9cd.tar.gz/sha512 | 1 - .../Pkg-a8ae3c58078f8815718d3edc3842edf3a36adefa.tar.gz/md5 | 1 + .../Pkg-a8ae3c58078f8815718d3edc3842edf3a36adefa.tar.gz/sha512 | 1 + stdlib/Pkg.version | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 deps/checksums/Pkg-a8a8e224eb78352ab82102c48d3b9c46fb6ee9cd.tar.gz/md5 delete mode 100644 deps/checksums/Pkg-a8a8e224eb78352ab82102c48d3b9c46fb6ee9cd.tar.gz/sha512 create mode 100644 deps/checksums/Pkg-a8ae3c58078f8815718d3edc3842edf3a36adefa.tar.gz/md5 create mode 100644 deps/checksums/Pkg-a8ae3c58078f8815718d3edc3842edf3a36adefa.tar.gz/sha512 diff --git a/deps/checksums/Pkg-a8a8e224eb78352ab82102c48d3b9c46fb6ee9cd.tar.gz/md5 b/deps/checksums/Pkg-a8a8e224eb78352ab82102c48d3b9c46fb6ee9cd.tar.gz/md5 deleted file mode 100644 index 8693795bb4ee4f..00000000000000 --- a/deps/checksums/Pkg-a8a8e224eb78352ab82102c48d3b9c46fb6ee9cd.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -a6ddbc5396e099945036661980f79d3e diff --git a/deps/checksums/Pkg-a8a8e224eb78352ab82102c48d3b9c46fb6ee9cd.tar.gz/sha512 b/deps/checksums/Pkg-a8a8e224eb78352ab82102c48d3b9c46fb6ee9cd.tar.gz/sha512 deleted file mode 100644 index b23d4955fafed8..00000000000000 --- a/deps/checksums/Pkg-a8a8e224eb78352ab82102c48d3b9c46fb6ee9cd.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -79a9d8e3eab8fe157bf79988cd561ebc21f79ddf1c9fd58a6767a99ba985cdc03ff2018324ebce652c689d0120c60314f1cd2602cddc675d8f0bb25f10390355 diff --git a/deps/checksums/Pkg-a8ae3c58078f8815718d3edc3842edf3a36adefa.tar.gz/md5 b/deps/checksums/Pkg-a8ae3c58078f8815718d3edc3842edf3a36adefa.tar.gz/md5 new file mode 100644 index 00000000000000..a45343b02aee0a --- /dev/null +++ b/deps/checksums/Pkg-a8ae3c58078f8815718d3edc3842edf3a36adefa.tar.gz/md5 @@ -0,0 +1 @@ +6b0dfd893c25254e303607de4d9bda1f diff --git a/deps/checksums/Pkg-a8ae3c58078f8815718d3edc3842edf3a36adefa.tar.gz/sha512 b/deps/checksums/Pkg-a8ae3c58078f8815718d3edc3842edf3a36adefa.tar.gz/sha512 new file mode 100644 index 00000000000000..bc7943a1aac62e --- /dev/null +++ b/deps/checksums/Pkg-a8ae3c58078f8815718d3edc3842edf3a36adefa.tar.gz/sha512 @@ -0,0 +1 @@ +8fcc8f8a8166ca6941ac1be72ec71bce935860cff3fc93609af68ac54b139743d3e3d7fd67ab538fa8ca1ce3279bb451c7c3df77f6fce8d65b11134ccd2581d4 diff --git a/stdlib/Pkg.version b/stdlib/Pkg.version index b95437cbd7a392..f5db6741ee3c29 100644 --- a/stdlib/Pkg.version +++ b/stdlib/Pkg.version @@ -1,4 +1,4 @@ PKG_BRANCH = master -PKG_SHA1 = a8a8e224eb78352ab82102c48d3b9c46fb6ee9cd +PKG_SHA1 = a8ae3c58078f8815718d3edc3842edf3a36adefa PKG_GIT_URL := https://github.com/JuliaLang/Pkg.jl.git PKG_TAR_URL = https://api.github.com/repos/JuliaLang/Pkg.jl/tarball/$1 From cf2176fbbb2ad3801c0999f72be513c20536cd4a Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Wed, 21 Dec 2022 12:02:49 -0500 Subject: [PATCH 141/387] tweak extension header in manual for search-ability (#47951) --- doc/src/manual/code-loading.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/src/manual/code-loading.md b/doc/src/manual/code-loading.md index f9575b0159d8c0..bac069b2746492 100644 --- a/doc/src/manual/code-loading.md +++ b/doc/src/manual/code-loading.md @@ -348,9 +348,10 @@ The subscripted `rootsᵢ`, `graphᵢ` and `pathsᵢ` variables correspond to th 2. Packages in non-primary environments can end up using incompatible versions of their dependencies even if their own environments are entirely compatible. This can happen when one of their dependencies is shadowed by a version in an earlier environment in the stack (either by graph or path, or both). Since the primary environment is typically the environment of a project you're working on, while environments later in the stack contain additional tools, this is the right trade-off: it's better to break your development tools but keep the project working. When such incompatibilities occur, you'll typically want to upgrade your dev tools to versions that are compatible with the main project. -### "Extension"s -An "extension" is a module that is automatically loaded when a specified set of other packages (its "extension dependencies") are loaded in the current Julia session. The extension dependencies of an extension are a subset of those packages listed under the `[weakdeps]` section of a Project file. Extensions are defined under the `[extensions]` section in the project file: +### Package Extensions + +A package "extension" is a module that is automatically loaded when a specified set of other packages (its "extension dependencies") are loaded in the current Julia session. The extension dependencies of an extension are a subset of those packages listed under the `[weakdeps]` section of a Project file. Extensions are defined under the `[extensions]` section in the project file: ```toml name = "MyPackage" From 077314063b5ce58d2723bb5bb7b92532e892140b Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 21 Dec 2022 13:55:40 -0500 Subject: [PATCH 142/387] subtype: fix merge_env innervars getting copied to itself (#47941) --- src/subtype.c | 2 +- test/subtype.jl | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/subtype.c b/src/subtype.c index 023d8cd3dfb675..60c0d163524bb7 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -3239,7 +3239,7 @@ static int merge_env(jl_stenv_t *e, jl_value_t **root, jl_savedenv_t *se, int co jl_svecset(*root, n+1, simple_join(b1, b2)); b1 = jl_svecref(*root, n+2); b2 = (jl_value_t*)v->innervars; - if (b2) { + if (b2 && b1 != b2) { if (b1) jl_array_ptr_1d_append((jl_array_t*)b2, (jl_array_t*)b1); else diff --git a/test/subtype.jl b/test/subtype.jl index ec771b71988b4c..674e54ce6dc727 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -2339,3 +2339,14 @@ T46784{B<:Val, M<:AbstractMatrix} = Tuple{<:Union{B, <:Val{<:B}}, M, Union{Abstr end @test !(Tuple{Any, Any, Any} <: Tuple{Any, Vararg{T}} where T) + +abstract type MyAbstract47877{C}; end +struct MyType47877{A,B} <: MyAbstract47877{A} end +let A = Tuple{Type{T}, T} where T, + B = Tuple{Type{MyType47877{W, V} where V<:Union{Base.BitInteger, MyAbstract47877{W}}}, MyAbstract47877{<:Base.BitInteger}} where W + C = Tuple{Type{MyType47877{W, V} where V<:Union{MyAbstract47877{W1}, Base.BitInteger}}, MyType47877{W, V} where V<:Union{MyAbstract47877{W1}, Base.BitInteger}} where {W<:Base.BitInteger, W1<:Base.BitInteger} + # ensure that merge_env for innervars does not blow up (the large Unions ensure this will take excessive memory if it does) + @test typeintersect(A, B) == C # suboptimal, but acceptable + C = Tuple{Type{MyType47877{W, V} where V<:Union{MyAbstract47877{W}, Base.BitInteger}}, MyType47877{W, V} where V<:Union{MyAbstract47877{W}, Base.BitInteger}} where W<:Base.BitInteger + @test typeintersect(B, A) == C +end From 6d4132bdfa95a2834a01db94fa08fb5c1164960f Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Wed, 21 Dec 2022 15:52:42 -0500 Subject: [PATCH 143/387] Format extensions with parent in `@time_imports` report (#47945) --- base/loading.jl | 6 ++++++ doc/src/manual/code-loading.md | 2 +- stdlib/InteractiveUtils/src/macros.jl | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/base/loading.jl b/base/loading.jl index 61c1f13a3eef3c..2ce769405022aa 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -979,6 +979,12 @@ function _include_from_serialized(pkg::PkgId, path::String, depmods::Vector{Any} elapsed = round((time_ns() - t_before) / 1e6, digits = 1) comp_time, recomp_time = cumulative_compile_time_ns() .- t_comp_before print(lpad(elapsed, 9), " ms ") + for extid in EXT_DORMITORY + if extid.id == pkg + print(extid.parentid.name, " → ") + break + end + end print(pkg.name) if comp_time > 0 printstyled(" ", Ryu.writefixed(Float64(100 * comp_time / (elapsed * 1e6)), 2), "% compilation time", color = Base.info_color()) diff --git a/doc/src/manual/code-loading.md b/doc/src/manual/code-loading.md index bac069b2746492..f6e74a02b4b30b 100644 --- a/doc/src/manual/code-loading.md +++ b/doc/src/manual/code-loading.md @@ -349,7 +349,7 @@ The subscripted `rootsᵢ`, `graphᵢ` and `pathsᵢ` variables correspond to th Since the primary environment is typically the environment of a project you're working on, while environments later in the stack contain additional tools, this is the right trade-off: it's better to break your development tools but keep the project working. When such incompatibilities occur, you'll typically want to upgrade your dev tools to versions that are compatible with the main project. -### Package Extensions +### [Package Extensions](@id man-extensions) A package "extension" is a module that is automatically loaded when a specified set of other packages (its "extension dependencies") are loaded in the current Julia session. The extension dependencies of an extension are a subset of those packages listed under the `[weakdeps]` section of a Project file. Extensions are defined under the `[extensions]` section in the project file: diff --git a/stdlib/InteractiveUtils/src/macros.jl b/stdlib/InteractiveUtils/src/macros.jl index ec11d54a0c154c..cc0fa019c604a5 100644 --- a/stdlib/InteractiveUtils/src/macros.jl +++ b/stdlib/InteractiveUtils/src/macros.jl @@ -360,6 +360,8 @@ See also: [`code_native`](@ref), [`@code_llvm`](@ref), [`@code_typed`](@ref) and A macro to execute an expression and produce a report of any time spent importing packages and their dependencies. Any compilation time will be reported as a percentage, and how much of which was recompilation, if any. +On Julia 1.9+ [package extensions](@ref man-extensions) will show as Parent → Extension. + !!! note During the load process a package sequentially imports all of its dependencies, not just its direct dependencies. From b33d7c706732bf3af54b99df2717800e9a55c1ec Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Wed, 21 Dec 2022 12:56:15 -0800 Subject: [PATCH 144/387] Remove wrong `@test_broken @inferred` test This test does not fail on any Julia version I've tested going all the way back to v0.7 when it was first introduced. My assumption is that it broke on an RC version, and we didn't noticed when it was fixed because `@test_broken` ignored tests that returned non-boolean values. --- test/sets.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/sets.jl b/test/sets.jl index b52d8136232318..19f9fab6506a03 100644 --- a/test/sets.jl +++ b/test/sets.jl @@ -734,8 +734,7 @@ end x = @inferred replace([1, 2], 2=>missing) @test isequal(x, [1, missing]) && x isa Vector{Union{Int, Missing}} - @test_broken @inferred replace([1, missing], missing=>2) - x = replace([1, missing], missing=>2) + x = @inferred replace([1, missing], missing=>2) @test x == [1, 2] && x isa Vector{Int} x = @inferred replace([1, missing], missing=>2, count=1) @test x == [1, 2] && x isa Vector{Union{Int, Missing}} From 508bb92b4d5ee16202e2873eb506febb8aec8aff Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Wed, 21 Dec 2022 12:57:30 -0800 Subject: [PATCH 145/387] Fix `@test_broken` to actually assert type intersection result This test is still valid, but it was not actually asserting anything due to not returning a boolean value. --- test/subtype.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/subtype.jl b/test/subtype.jl index 23aabf38e4fa1a..aec52e8fcfef3c 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -2230,7 +2230,7 @@ T46784{B<:Val, M<:AbstractMatrix} = Tuple{<:Union{B, <:Val{<:B}}, M, Union{Abstr (Tuple{Type{T},Array{Union{T,Nothing},N}} where {T,N})) <: Any #issue 35698 - @test_broken typeintersect(Type{Tuple{Array{T,1} where T}}, UnionAll) + @test_broken typeintersect(Type{Tuple{Array{T,1} where T}}, UnionAll) != Union{} #issue 33137 @test_broken (Tuple{Q,Int} where Q<:Int) <: Tuple{T,T} where T From 5c5e4908b5a8452221b072dfc2a2200e6af5571d Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Wed, 21 Dec 2022 16:29:44 -0500 Subject: [PATCH 146/387] add `get_bool_env` for parsing bool-like env vars (#47436) --- base/env.jl | 38 ++++++++++++++++++++++++++++++++++++++ test/env.jl | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/base/env.jl b/base/env.jl index 5aeafd836b387d..066bb9d755a4f1 100644 --- a/base/env.jl +++ b/base/env.jl @@ -97,6 +97,44 @@ See also: [`withenv`](@ref), [`addenv`](@ref). """ const ENV = EnvDict() +const get_bool_env_truthy = ( + "t", "T", + "true", "True", "TRUE", + "y", "Y", + "yes", "Yes", "YES", + "1") +const get_bool_env_falsy = ( + "f", "F", + "false", "False", "FALSE", + "n", "N", + "no", "No", "NO", + "0") + +""" + Base.get_bool_env(name::String, default::Bool = false)::Union{Bool,Nothing} + +Evaluate whether the value of environnment variable `name` is a truthy or falsy string, +and return `nothing` if it is not recognized as either. If the variable is not set, or is set to "", +return `default`. + +Recognized values are the following, and their Capitalized and UPPERCASE forms: + truthy: "t", "true", "y", "yes", "1" + falsy: "f", "false", "n", "no", "0" +""" +function get_bool_env(name::String, default::Bool = false) + haskey(ENV, name) || return default + val = ENV[name] + if isempty(val) + return default + elseif val in get_bool_env_truthy + return true + elseif val in get_bool_env_falsy + return false + else + return nothing + end +end + getindex(::EnvDict, k::AbstractString) = access_env(k->throw(KeyError(k)), k) get(::EnvDict, k::AbstractString, def) = access_env(Returns(def), k) get(f::Callable, ::EnvDict, k::AbstractString) = access_env(k->f(), k) diff --git a/test/env.jl b/test/env.jl index 479536813ec47b..00557f7facf99f 100644 --- a/test/env.jl +++ b/test/env.jl @@ -122,6 +122,52 @@ if Sys.iswindows() end end +@testset "get_bool_env" begin + @testset "truthy" begin + for v in ("t", "true", "y", "yes", "1") + for _v in (v, uppercasefirst(v), uppercase(v)) + ENV["testing_gbe"] = _v + @test Base.get_bool_env("testing_gbe") == true + @test Base.get_bool_env("testing_gbe", false) == true + @test Base.get_bool_env("testing_gbe", true) == true + end + end + end + @testset "falsy" begin + for v in ("f", "false", "n", "no", "0") + for _v in (v, uppercasefirst(v), uppercase(v)) + ENV["testing_gbe"] = _v + @test Base.get_bool_env("testing_gbe") == false + @test Base.get_bool_env("testing_gbe", true) == false + @test Base.get_bool_env("testing_gbe", false) == false + end + end + end + @testset "empty" begin + ENV["testing_gbe"] = "" + @test Base.get_bool_env("testing_gbe") == false + @test Base.get_bool_env("testing_gbe", true) == true + @test Base.get_bool_env("testing_gbe", false) == false + end + @testset "undefined" begin + delete!(ENV, "testing_gbe") + @test !haskey(ENV, "testing_gbe") + @test Base.get_bool_env("testing_gbe") == false + @test Base.get_bool_env("testing_gbe", true) == true + @test Base.get_bool_env("testing_gbe", false) == false + end + @testset "unrecognized" begin + for v in ("truw", "falls") + ENV["testing_gbe"] = v + @test Base.get_bool_env("testing_gbe") === nothing + @test Base.get_bool_env("testing_gbe", true) === nothing + @test Base.get_bool_env("testing_gbe", false) === nothing + end + end + delete!(ENV, "testing_gbe") + @test !haskey(ENV, "testing_gbe") +end + # Restore the original environment for k in keys(ENV) if !haskey(original_env, k) From b89d017fc161f96c55d826e60471308522662176 Mon Sep 17 00:00:00 2001 From: DilumAluthgeBot <43731525+DilumAluthgeBot@users.noreply.github.com> Date: Wed, 21 Dec 2022 16:30:36 -0500 Subject: [PATCH 147/387] =?UTF-8?q?=F0=9F=A4=96=20Bump=20the=20Downloads?= =?UTF-8?q?=20stdlib=20from=2011b6bb7=20to=20030cfb3=20(#47953)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dilum Aluthge --- .../md5 | 1 + .../sha512 | 1 + .../md5 | 1 - .../sha512 | 1 - stdlib/Downloads.version | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 deps/checksums/Downloads-030cfb3fefd29e87405cb689fb8178613131f55c.tar.gz/md5 create mode 100644 deps/checksums/Downloads-030cfb3fefd29e87405cb689fb8178613131f55c.tar.gz/sha512 delete mode 100644 deps/checksums/Downloads-11b6bb73bff32cec1b1e3bf064420cad1335400b.tar.gz/md5 delete mode 100644 deps/checksums/Downloads-11b6bb73bff32cec1b1e3bf064420cad1335400b.tar.gz/sha512 diff --git a/deps/checksums/Downloads-030cfb3fefd29e87405cb689fb8178613131f55c.tar.gz/md5 b/deps/checksums/Downloads-030cfb3fefd29e87405cb689fb8178613131f55c.tar.gz/md5 new file mode 100644 index 00000000000000..95b4a593a88b19 --- /dev/null +++ b/deps/checksums/Downloads-030cfb3fefd29e87405cb689fb8178613131f55c.tar.gz/md5 @@ -0,0 +1 @@ +dc5f63b5cdab35d1699bed558229ec83 diff --git a/deps/checksums/Downloads-030cfb3fefd29e87405cb689fb8178613131f55c.tar.gz/sha512 b/deps/checksums/Downloads-030cfb3fefd29e87405cb689fb8178613131f55c.tar.gz/sha512 new file mode 100644 index 00000000000000..9c6e0c43c24efd --- /dev/null +++ b/deps/checksums/Downloads-030cfb3fefd29e87405cb689fb8178613131f55c.tar.gz/sha512 @@ -0,0 +1 @@ +99d5fd3a41d8e17f6955b2ff379bf7d2b9fed388b9fe22358a3abf70f743da95301d6790f6d1a3a185b61f5d7e392ef663a3cf52552da8ae69f9d943aafb2df3 diff --git a/deps/checksums/Downloads-11b6bb73bff32cec1b1e3bf064420cad1335400b.tar.gz/md5 b/deps/checksums/Downloads-11b6bb73bff32cec1b1e3bf064420cad1335400b.tar.gz/md5 deleted file mode 100644 index b968bee68a043d..00000000000000 --- a/deps/checksums/Downloads-11b6bb73bff32cec1b1e3bf064420cad1335400b.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -d02f5c45d09877258e493b61595bf3b8 diff --git a/deps/checksums/Downloads-11b6bb73bff32cec1b1e3bf064420cad1335400b.tar.gz/sha512 b/deps/checksums/Downloads-11b6bb73bff32cec1b1e3bf064420cad1335400b.tar.gz/sha512 deleted file mode 100644 index bf0bcc6dbb174d..00000000000000 --- a/deps/checksums/Downloads-11b6bb73bff32cec1b1e3bf064420cad1335400b.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -5c172f6030d0c377b04ec052e62738e3b36a2d99da5d2308b8425cf474f376a0e5d8caa4f9a4e93f871e79e491fb17a7c616190f585af62d59605dd19da14dbe diff --git a/stdlib/Downloads.version b/stdlib/Downloads.version index 8ec2124c9e06d5..16fdbd047c4dee 100644 --- a/stdlib/Downloads.version +++ b/stdlib/Downloads.version @@ -1,4 +1,4 @@ DOWNLOADS_BRANCH = master -DOWNLOADS_SHA1 = 11b6bb73bff32cec1b1e3bf064420cad1335400b +DOWNLOADS_SHA1 = 030cfb3fefd29e87405cb689fb8178613131f55c DOWNLOADS_GIT_URL := https://github.com/JuliaLang/Downloads.jl.git DOWNLOADS_TAR_URL = https://api.github.com/repos/JuliaLang/Downloads.jl/tarball/$1 From e4c064b4d4bc0afe7dd7eb6928596bff8a9c72d8 Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Wed, 21 Dec 2022 13:45:53 -0800 Subject: [PATCH 148/387] Modify `@test_broken` on `cfunction` platform support failure This `@test_broken` is meant to just register a "broken" result for these platforms, so let's ensure the test always results in a `false`. --- test/ccall.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ccall.jl b/test/ccall.jl index 1fd386fc43203b..cf5bb0e2cf9476 100644 --- a/test/ccall.jl +++ b/test/ccall.jl @@ -1020,7 +1020,7 @@ end else -@test_broken "cfunction: no support for closures on this platform" +@test_broken "cfunction: no support for closures on this platform" === nothing end From b5ae2ff96d263c792cd12299d3980e89e5962fc9 Mon Sep 17 00:00:00 2001 From: DilumAluthgeBot <43731525+DilumAluthgeBot@users.noreply.github.com> Date: Wed, 21 Dec 2022 17:46:55 -0500 Subject: [PATCH 149/387] =?UTF-8?q?=F0=9F=A4=96=20Bump=20the=20Statistics?= =?UTF-8?q?=20stdlib=20from=2020fbe57=20to=20e9ac70b=20(#47955)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dilum Aluthge --- .../md5 | 1 - .../sha512 | 1 - .../md5 | 1 + .../sha512 | 1 + stdlib/Statistics.version | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 deps/checksums/Statistics-20fbe576ec406180b1dddf4c7fbe16458a7aef21.tar.gz/md5 delete mode 100644 deps/checksums/Statistics-20fbe576ec406180b1dddf4c7fbe16458a7aef21.tar.gz/sha512 create mode 100644 deps/checksums/Statistics-e9ac70b760dcf87b77affe6c068548a3325d6e2b.tar.gz/md5 create mode 100644 deps/checksums/Statistics-e9ac70b760dcf87b77affe6c068548a3325d6e2b.tar.gz/sha512 diff --git a/deps/checksums/Statistics-20fbe576ec406180b1dddf4c7fbe16458a7aef21.tar.gz/md5 b/deps/checksums/Statistics-20fbe576ec406180b1dddf4c7fbe16458a7aef21.tar.gz/md5 deleted file mode 100644 index 5e467255c94608..00000000000000 --- a/deps/checksums/Statistics-20fbe576ec406180b1dddf4c7fbe16458a7aef21.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -85a733533f946f1183f4546b6c8e14f5 diff --git a/deps/checksums/Statistics-20fbe576ec406180b1dddf4c7fbe16458a7aef21.tar.gz/sha512 b/deps/checksums/Statistics-20fbe576ec406180b1dddf4c7fbe16458a7aef21.tar.gz/sha512 deleted file mode 100644 index e8c4c1b7dfeef8..00000000000000 --- a/deps/checksums/Statistics-20fbe576ec406180b1dddf4c7fbe16458a7aef21.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -edb6faba80e3cd5685c59a7bf7f7ad76435e1df8b65bd03b534bd5d1b605ea6610704eaa08aa99b74796cbaf2ff7e786b3ff058fd2e5f494f88e15a9bd6e8613 diff --git a/deps/checksums/Statistics-e9ac70b760dcf87b77affe6c068548a3325d6e2b.tar.gz/md5 b/deps/checksums/Statistics-e9ac70b760dcf87b77affe6c068548a3325d6e2b.tar.gz/md5 new file mode 100644 index 00000000000000..0e2d0534cd8c74 --- /dev/null +++ b/deps/checksums/Statistics-e9ac70b760dcf87b77affe6c068548a3325d6e2b.tar.gz/md5 @@ -0,0 +1 @@ +62d47cffac86df3c59b3de8dd218aa79 diff --git a/deps/checksums/Statistics-e9ac70b760dcf87b77affe6c068548a3325d6e2b.tar.gz/sha512 b/deps/checksums/Statistics-e9ac70b760dcf87b77affe6c068548a3325d6e2b.tar.gz/sha512 new file mode 100644 index 00000000000000..95e88c63f1a148 --- /dev/null +++ b/deps/checksums/Statistics-e9ac70b760dcf87b77affe6c068548a3325d6e2b.tar.gz/sha512 @@ -0,0 +1 @@ +6354b1e84d7df1fe8d7e1444181497cac87d22d10a2a21b9f7fab748c209bd9aba64f2df6489e9441624fcf27140ccffa3f7eabaf2517f4900b2661be0c74ba5 diff --git a/stdlib/Statistics.version b/stdlib/Statistics.version index 362aec5bdc1f36..22857e138655a4 100644 --- a/stdlib/Statistics.version +++ b/stdlib/Statistics.version @@ -1,4 +1,4 @@ STATISTICS_BRANCH = master -STATISTICS_SHA1 = 20fbe576ec406180b1dddf4c7fbe16458a7aef21 +STATISTICS_SHA1 = e9ac70b760dcf87b77affe6c068548a3325d6e2b STATISTICS_GIT_URL := https://github.com/JuliaStats/Statistics.jl.git STATISTICS_TAR_URL = https://api.github.com/repos/JuliaStats/Statistics.jl/tarball/$1 From b050b5d845f9a4b0a671e5af58db811b6fdab202 Mon Sep 17 00:00:00 2001 From: DilumAluthgeBot <43731525+DilumAluthgeBot@users.noreply.github.com> Date: Wed, 21 Dec 2022 17:53:07 -0500 Subject: [PATCH 150/387] =?UTF-8?q?=F0=9F=A4=96=20Bump=20the=20NetworkOpti?= =?UTF-8?q?ons=20stdlib=20from=20791fa05=20to=20f7bbeb6=20(#47954)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dilum Aluthge --- .../md5 | 1 - .../sha512 | 1 - .../md5 | 1 + .../sha512 | 1 + stdlib/NetworkOptions.version | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 deps/checksums/NetworkOptions-791fa05928f03b7f18ff5ba0d6c7fbf6c6c25e78.tar.gz/md5 delete mode 100644 deps/checksums/NetworkOptions-791fa05928f03b7f18ff5ba0d6c7fbf6c6c25e78.tar.gz/sha512 create mode 100644 deps/checksums/NetworkOptions-f7bbeb66f05fc651adb12758b650e8630a998fbd.tar.gz/md5 create mode 100644 deps/checksums/NetworkOptions-f7bbeb66f05fc651adb12758b650e8630a998fbd.tar.gz/sha512 diff --git a/deps/checksums/NetworkOptions-791fa05928f03b7f18ff5ba0d6c7fbf6c6c25e78.tar.gz/md5 b/deps/checksums/NetworkOptions-791fa05928f03b7f18ff5ba0d6c7fbf6c6c25e78.tar.gz/md5 deleted file mode 100644 index 87c4c8bdccbe1e..00000000000000 --- a/deps/checksums/NetworkOptions-791fa05928f03b7f18ff5ba0d6c7fbf6c6c25e78.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -b3f483dfbac96733424d8e306ae166b0 diff --git a/deps/checksums/NetworkOptions-791fa05928f03b7f18ff5ba0d6c7fbf6c6c25e78.tar.gz/sha512 b/deps/checksums/NetworkOptions-791fa05928f03b7f18ff5ba0d6c7fbf6c6c25e78.tar.gz/sha512 deleted file mode 100644 index 30dbd3cfef63c7..00000000000000 --- a/deps/checksums/NetworkOptions-791fa05928f03b7f18ff5ba0d6c7fbf6c6c25e78.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -0ac8d4d7b96675fdc04b9c160e28553de007fc346f46a9af8a4a775ed44b27b538b8e37f015263e7d2eafab64748c74fed66359860ff18b228c46467933e31e7 diff --git a/deps/checksums/NetworkOptions-f7bbeb66f05fc651adb12758b650e8630a998fbd.tar.gz/md5 b/deps/checksums/NetworkOptions-f7bbeb66f05fc651adb12758b650e8630a998fbd.tar.gz/md5 new file mode 100644 index 00000000000000..9e91b76f9a3c81 --- /dev/null +++ b/deps/checksums/NetworkOptions-f7bbeb66f05fc651adb12758b650e8630a998fbd.tar.gz/md5 @@ -0,0 +1 @@ +16bc9f2eefa3021e19a09ffefc84159b diff --git a/deps/checksums/NetworkOptions-f7bbeb66f05fc651adb12758b650e8630a998fbd.tar.gz/sha512 b/deps/checksums/NetworkOptions-f7bbeb66f05fc651adb12758b650e8630a998fbd.tar.gz/sha512 new file mode 100644 index 00000000000000..551f7c8da347cc --- /dev/null +++ b/deps/checksums/NetworkOptions-f7bbeb66f05fc651adb12758b650e8630a998fbd.tar.gz/sha512 @@ -0,0 +1 @@ +5b53c09343e25b5bde7ea12c2119da656040ca5f62ce934f00f57945ce73dfaf26522da6a9a007ba06ac6fd75a285cbcbdf5edaf9113faa7bba0398294fbd684 diff --git a/stdlib/NetworkOptions.version b/stdlib/NetworkOptions.version index eb61ed847a22b1..64d3fab9d7bf48 100644 --- a/stdlib/NetworkOptions.version +++ b/stdlib/NetworkOptions.version @@ -1,4 +1,4 @@ NETWORKOPTIONS_BRANCH = master -NETWORKOPTIONS_SHA1 = 791fa05928f03b7f18ff5ba0d6c7fbf6c6c25e78 +NETWORKOPTIONS_SHA1 = f7bbeb66f05fc651adb12758b650e8630a998fbd NETWORKOPTIONS_GIT_URL := https://github.com/JuliaLang/NetworkOptions.jl.git NETWORKOPTIONS_TAR_URL = https://api.github.com/repos/JuliaLang/NetworkOptions.jl/tarball/$1 From 43d9352735b26bc2aec3cc676888fda410fe8b91 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Thu, 22 Dec 2022 14:46:09 +0900 Subject: [PATCH 151/387] fix incorrect inference of `NamedTuple{(), <:Any}` (#47947) closes #47481 --- base/compiler/tfuncs.jl | 6 ------ test/compiler/inference.jl | 9 --------- 2 files changed, 15 deletions(-) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 21b08fd8c872c2..59f142343be10e 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -1766,12 +1766,6 @@ const _tvarnames = Symbol[:_A, :_B, :_C, :_D, :_E, :_F, :_G, :_H, :_I, :_J, :_K, end if istuple return Type{<:appl} - elseif isa(appl, DataType) && appl.name === _NAMEDTUPLE_NAME && length(appl.parameters) == 2 && - (appl.parameters[1] === () || appl.parameters[2] === Tuple{}) - # if the first/second parameter of `NamedTuple` is known to be empty, - # the second/first argument should also be empty tuple type, - # so refine it here - return Const(NamedTuple{(),Tuple{}}) end ans = Type{appl} for i = length(outervars):-1:1 diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 1a6f49182975c3..2a664ce6dbcc07 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -2728,15 +2728,6 @@ end |> only === Int # Equivalence of Const(T.instance) and T for singleton types @test Const(nothing) ⊑ Nothing && Nothing ⊑ Const(nothing) -# `apply_type_tfunc` should always return accurate result for empty NamedTuple case -import Core: Const -let apply_type_tfunc(@nospecialize xs...) = - Core.Compiler.apply_type_tfunc(Core.Compiler.fallback_lattice, xs...) - @test apply_type_tfunc(Const(NamedTuple), Const(()), Type{T} where T<:Tuple{}) === Const(typeof((;))) - @test apply_type_tfunc(Const(NamedTuple), Const(()), Type{T} where T<:Tuple) === Const(typeof((;))) - @test apply_type_tfunc(Const(NamedTuple), Tuple{Vararg{Symbol}}, Type{Tuple{}}) === Const(typeof((;))) -end - # Don't pessimize apply_type to anything worse than Type and yield Bottom for invalid Unions @test only(Base.return_types(Core.apply_type, Tuple{Type{Union}})) == Type{Union{}} @test only(Base.return_types(Core.apply_type, Tuple{Type{Union},Any})) == Type From 915dd8e9424816e2acc03f61c7e86e7cf3d5bf6b Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Thu, 22 Dec 2022 18:18:04 +0900 Subject: [PATCH 152/387] use parameterized lattice in various optimization passes (#47948) We may want to parameterize our optimization passes with `AbstractInterpreter` in the future, but this commit adds minimum changes to parameterize them with `AbstractInterpreter` maintained by `InliningState`. --- base/compiler/optimize.jl | 19 ++--- .../ssair/EscapeAnalysis/EscapeAnalysis.jl | 7 +- .../ssair/EscapeAnalysis/interprocedural.jl | 2 +- base/compiler/ssair/inlining.jl | 72 ++++++++++--------- base/compiler/ssair/passes.jl | 37 +++++----- base/compiler/ssair/slot2ssa.jl | 18 ++--- base/compiler/ssair/verify.jl | 7 +- 7 files changed, 86 insertions(+), 76 deletions(-) diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index f7cc2ae0268681..98d48909a5e24a 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -238,13 +238,13 @@ end Returns a tuple of `(:consistent, :effect_free_and_nothrow, :nothrow)` flags for a given statement. """ -function stmt_effect_flags(lattice::AbstractLattice, @nospecialize(stmt), @nospecialize(rt), src::Union{IRCode,IncrementalCompact}) +function stmt_effect_flags(𝕃ₒ::AbstractLattice, @nospecialize(stmt), @nospecialize(rt), src::Union{IRCode,IncrementalCompact}) # TODO: We're duplicating analysis from inference here. isa(stmt, PiNode) && return (true, true, true) isa(stmt, PhiNode) && return (true, true, true) isa(stmt, ReturnNode) && return (true, false, true) isa(stmt, GotoNode) && return (true, false, true) - isa(stmt, GotoIfNot) && return (true, false, argextype(stmt.cond, src) ⊑ₒ Bool) + isa(stmt, GotoIfNot) && return (true, false, ⊑(𝕃ₒ, argextype(stmt.cond, src), Bool)) isa(stmt, Slot) && return (true, false, false) # Slots shouldn't occur in the IR at this point, but let's be defensive here if isa(stmt, GlobalRef) nothrow = isdefined(stmt.mod, stmt.name) @@ -266,7 +266,7 @@ function stmt_effect_flags(lattice::AbstractLattice, @nospecialize(stmt), @nospe if f === UnionAll # TODO: This is a weird special case - should be determined in inference argtypes = Any[argextype(args[arg], src) for arg in 2:length(args)] - nothrow = _builtin_nothrow(lattice, f, argtypes, rt) + nothrow = _builtin_nothrow(𝕃ₒ, f, argtypes, rt) return (true, nothrow, nothrow) end if f === Intrinsics.cglobal @@ -277,7 +277,7 @@ function stmt_effect_flags(lattice::AbstractLattice, @nospecialize(stmt), @nospe # Needs to be handled in inlining to look at the callee effects f === Core._apply_iterate && return (false, false, false) argtypes = Any[argextype(args[arg], src) for arg in 2:length(args)] - effects = builtin_effects(lattice, f, argtypes, rt) + effects = builtin_effects(𝕃ₒ, f, argtypes, rt) consistent = is_consistent(effects) effect_free = is_effect_free(effects) nothrow = is_nothrow(effects) @@ -308,7 +308,7 @@ function stmt_effect_flags(lattice::AbstractLattice, @nospecialize(stmt), @nospe if !isexact && has_free_typevars(fT) return (false, false, false) end - eT ⊑ₒ fT || return (false, false, false) + ⊑(𝕃ₒ, eT, fT) || return (false, false, false) end return (false, true, true) elseif head === :foreigncall @@ -324,11 +324,11 @@ function stmt_effect_flags(lattice::AbstractLattice, @nospecialize(stmt), @nospe typ = argextype(args[1], src) typ, isexact = instanceof_tfunc(typ) isexact || return (false, false, false) - typ ⊑ₒ Tuple || return (false, false, false) + ⊑(𝕃ₒ, typ, Tuple) || return (false, false, false) rt_lb = argextype(args[2], src) rt_ub = argextype(args[3], src) source = argextype(args[4], src) - if !(rt_lb ⊑ₒ Type && rt_ub ⊑ₒ Type && source ⊑ₒ Method) + if !(⊑(𝕃ₒ, rt_lb, Type) && ⊑(𝕃ₒ, rt_ub, Type) && ⊑(𝕃ₒ, source, Method)) return (false, false, false) end return (false, true, true) @@ -580,7 +580,7 @@ function run_passes( # @timeit "verify 2" verify_ir(ir) @pass "compact 2" ir = compact!(ir) @pass "SROA" ir = sroa_pass!(ir, sv.inlining) - @pass "ADCE" ir = adce_pass!(ir) + @pass "ADCE" ir = adce_pass!(ir, sv.inlining) @pass "type lift" ir = type_lift_pass!(ir) @pass "compact 3" ir = compact!(ir) if JLOptions().debug_level == 2 @@ -700,7 +700,8 @@ function slot2reg(ir::IRCode, ci::CodeInfo, sv::OptimizationState) nargs = isa(svdef, Method) ? Int(svdef.nargs) : 0 @timeit "domtree 1" domtree = construct_domtree(ir.cfg.blocks) defuse_insts = scan_slot_def_use(nargs, ci, ir.stmts.inst) - @timeit "construct_ssa" ir = construct_ssa!(ci, ir, domtree, defuse_insts, sv.slottypes) # consumes `ir` + 𝕃ₒ = optimizer_lattice(sv.inlining.interp) + @timeit "construct_ssa" ir = construct_ssa!(ci, ir, domtree, defuse_insts, sv.slottypes, 𝕃ₒ) # consumes `ir` return ir end diff --git a/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl b/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl index 2fe364d6407327..729e9a9a49b948 100644 --- a/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl +++ b/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl @@ -42,6 +42,7 @@ end const AInfo = IdSet{Any} const LivenessSet = BitSet +const 𝕃ₒ = OptimizerLattice() """ x::EscapeInfo @@ -787,7 +788,7 @@ function compute_frameinfo(ir::IRCode, call_resolved::Bool) stmt = inst[:inst] if !call_resolved # TODO don't call `check_effect_free!` in the inlinear - check_effect_free!(ir, idx, stmt, inst[:type]) + check_effect_free!(ir, idx, stmt, inst[:type], 𝕃ₒ) end if callinfo !== nothing && isexpr(stmt, :call) callinfo[idx] = resolve_call(ir, stmt, inst[:info]) @@ -1596,10 +1597,10 @@ function escape_builtin!(::typeof(setfield!), astate::AnalysisState, pc::Int, ar add_escape_change!(astate, val, ssainfo) # compute the throwness of this setfield! call here since builtin_nothrow doesn't account for that @label add_thrown_escapes - if length(args) == 4 && setfield!_nothrow(OptimizerLattice(), + if length(args) == 4 && setfield!_nothrow(𝕃ₒ, argextype(args[2], ir), argextype(args[3], ir), argextype(args[4], ir)) return true - elseif length(args) == 3 && setfield!_nothrow(OptimizerLattice(), + elseif length(args) == 3 && setfield!_nothrow(𝕃ₒ, argextype(args[2], ir), argextype(args[3], ir)) return true else diff --git a/base/compiler/ssair/EscapeAnalysis/interprocedural.jl b/base/compiler/ssair/EscapeAnalysis/interprocedural.jl index 74a43e9b9ec8ec..d87b0edaf295e0 100644 --- a/base/compiler/ssair/EscapeAnalysis/interprocedural.jl +++ b/base/compiler/ssair/EscapeAnalysis/interprocedural.jl @@ -19,7 +19,7 @@ function resolve_call(ir::IRCode, stmt::Expr, @nospecialize(info::CallInfo)) return missing end # TODO handle _apply_iterate - if is_builtin(sig) && sig.f !== invoke + if is_builtin(𝕃ₒ, sig) && sig.f !== invoke return false end # handling corresponding to late_inline_special_case! diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 94bef6c9e7a7ec..09340d8f21637e 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -107,8 +107,6 @@ function CFGInliningState(ir::IRCode) ) end -⊑ₒ(@nospecialize(a), @nospecialize(b)) = ⊑(OptimizerLattice(), a, b) - # Tells the inliner that we're now inlining into block `block`, meaning # all previous blocks have been processed and can be added to the new cfg function inline_into_block!(state::CFGInliningState, block::Int) @@ -778,7 +776,7 @@ function rewrite_apply_exprargs!(todo::Vector{Pair{Int,Any}}, # See if we can inline this call to `iterate` handle_call!(todo, ir, state1.id, new_stmt, new_info, flag, new_sig, istate) if i != length(thisarginfo.each) - valT = getfield_tfunc(OptimizerLattice(), call.rt, Const(1)) + valT = getfield_tfunc(optimizer_lattice(istate.interp), call.rt, Const(1)) val_extracted = insert_node!(ir, idx, NewInstruction( Expr(:call, GlobalRef(Core, :getfield), state1, 1), valT)) @@ -786,7 +784,7 @@ function rewrite_apply_exprargs!(todo::Vector{Pair{Int,Any}}, push!(new_argtypes, valT) state_extracted = insert_node!(ir, idx, NewInstruction( Expr(:call, GlobalRef(Core, :getfield), state1, 2), - getfield_tfunc(OptimizerLattice(), call.rt, Const(2)))) + getfield_tfunc(optimizer_lattice(istate.interp), call.rt, Const(2)))) state = Core.svec(state_extracted) end end @@ -1038,8 +1036,8 @@ function is_valid_type_for_apply_rewrite(@nospecialize(typ), params::Optimizatio end end -function inline_splatnew!(ir::IRCode, idx::Int, stmt::Expr, @nospecialize(rt)) - 𝕃ₒ = OptimizerLattice() +function inline_splatnew!(ir::IRCode, idx::Int, stmt::Expr, @nospecialize(rt), state::InliningState) + 𝕃ₒ = optimizer_lattice(state.interp) nf = nfields_tfunc(𝕃ₒ, rt) if nf isa Const eargs = stmt.args @@ -1114,10 +1112,11 @@ function inline_apply!(todo::Vector{Pair{Int,Any}}, # if one argument is a tuple already, and the rest are empty, we can just return it # e.g. rewrite `((t::Tuple)...,)` to `t` nonempty_idx = 0 + 𝕃ₒ = optimizer_lattice(state.interp) for i = (arg_start + 1):length(argtypes) ti = argtypes[i] - ti ⊑ₒ Tuple{} && continue - if ti ⊑ₒ Tuple && nonempty_idx == 0 + ⊑(𝕃ₒ, ti, Tuple{}) && continue + if ⊑(𝕃ₒ, ti, Tuple) && nonempty_idx == 0 nonempty_idx = i continue end @@ -1156,11 +1155,13 @@ function inline_apply!(todo::Vector{Pair{Int,Any}}, end # TODO: this test is wrong if we start to handle Unions of function types later -is_builtin(s::Signature) = - isa(s.f, IntrinsicFunction) || - s.ft ⊑ₒ IntrinsicFunction || - isa(s.f, Builtin) || - s.ft ⊑ₒ Builtin +function is_builtin(𝕃ₒ::AbstractLattice, s::Signature) + isa(s.f, IntrinsicFunction) && return true + ⊑(𝕃ₒ, s.ft, IntrinsicFunction) && return true + isa(s.f, Builtin) && return true + ⊑(𝕃ₒ, s.ft, Builtin) && return true + return false +end function handle_invoke_call!(todo::Vector{Pair{Int,Any}}, ir::IRCode, idx::Int, stmt::Expr, info::InvokeCallInfo, flag::UInt8, @@ -1205,7 +1206,8 @@ function narrow_opaque_closure!(ir::IRCode, stmt::Expr, @nospecialize(info::Call ub, exact = instanceof_tfunc(ubt) exact || return # Narrow opaque closure type - newT = widenconst(tmeet(OptimizerLattice(), tmerge(OptimizerLattice(), lb, info.unspec.rt), ub)) + 𝕃ₒ = optimizer_lattice(state.interp) + newT = widenconst(tmeet(𝕃ₒ, tmerge(𝕃ₒ, lb, info.unspec.rt), ub)) if newT != ub # N.B.: Narrowing the ub requires a backedge on the mi whose type # information we're using, since a change in that function may @@ -1218,8 +1220,11 @@ end # As a matter of convenience, this pass also computes effect-freenes. # For primitives, we do that right here. For proper calls, we will # discover this when we consult the caches. -function check_effect_free!(ir::IRCode, idx::Int, @nospecialize(stmt), @nospecialize(rt)) - (consistent, effect_free_and_nothrow, nothrow) = stmt_effect_flags(OptimizerLattice(), stmt, rt, ir) +function check_effect_free!(ir::IRCode, idx::Int, @nospecialize(stmt), @nospecialize(rt), state::InliningState) + return check_effect_free!(ir, idx, stmt, rt, optimizer_lattice(state.interp)) +end +function check_effect_free!(ir::IRCode, idx::Int, @nospecialize(stmt), @nospecialize(rt), 𝕃ₒ::AbstractLattice) + (consistent, effect_free_and_nothrow, nothrow) = stmt_effect_flags(𝕃ₒ, stmt, rt, ir) if consistent ir.stmts[idx][:flag] |= IR_FLAG_CONSISTENT end @@ -1238,13 +1243,13 @@ function process_simple!(todo::Vector{Pair{Int,Any}}, ir::IRCode, idx::Int, stat stmt = ir.stmts[idx][:inst] rt = ir.stmts[idx][:type] if !(stmt isa Expr) - check_effect_free!(ir, idx, stmt, rt) + check_effect_free!(ir, idx, stmt, rt, state) return nothing end head = stmt.head if head !== :call if head === :splatnew - inline_splatnew!(ir, idx, stmt, rt) + inline_splatnew!(ir, idx, stmt, rt, state) elseif head === :new_opaque_closure narrow_opaque_closure!(ir, stmt, ir.stmts[idx][:info], state) elseif head === :invoke @@ -1252,7 +1257,7 @@ function process_simple!(todo::Vector{Pair{Int,Any}}, ir::IRCode, idx::Int, stat sig === nothing && return nothing return stmt, sig end - check_effect_free!(ir, idx, stmt, rt) + check_effect_free!(ir, idx, stmt, rt, state) return nothing end @@ -1264,30 +1269,31 @@ function process_simple!(todo::Vector{Pair{Int,Any}}, ir::IRCode, idx::Int, stat sig === nothing && return nothing # Check if we match any of the early inliners - earlyres = early_inline_special_case(ir, stmt, rt, sig, state.params) + earlyres = early_inline_special_case(ir, stmt, rt, sig, state) if isa(earlyres, SomeCase) ir.stmts[idx][:inst] = earlyres.val return nothing end - if check_effect_free!(ir, idx, stmt, rt) - if sig.f === typeassert || sig.ft ⊑ₒ typeof(typeassert) + if check_effect_free!(ir, idx, stmt, rt, state) + if sig.f === typeassert || ⊑(optimizer_lattice(state.interp), sig.ft, typeof(typeassert)) # typeassert is a no-op if effect free ir.stmts[idx][:inst] = stmt.args[2] return nothing end end - if (sig.f !== Core.invoke && sig.f !== Core.finalizer && sig.f !== modifyfield!) && is_builtin(sig) + if (sig.f !== Core.invoke && sig.f !== Core.finalizer && sig.f !== modifyfield!) && + is_builtin(optimizer_lattice(state.interp), sig) # No inlining for builtins (other invoke/apply/typeassert/finalizer) return nothing end # Special case inliners for regular functions - lateres = late_inline_special_case!(ir, idx, stmt, rt, sig, state.params) + lateres = late_inline_special_case!(ir, idx, stmt, rt, sig, state) if isa(lateres, SomeCase) ir[SSAValue(idx)][:inst] = lateres.val - check_effect_free!(ir, idx, lateres.val, rt) + check_effect_free!(ir, idx, lateres.val, rt, state) return nothing end @@ -1683,8 +1689,8 @@ end function early_inline_special_case( ir::IRCode, stmt::Expr, @nospecialize(type), sig::Signature, - params::OptimizationParams) - params.inlining || return nothing + state::InliningState) + state.params.inlining || return nothing (; f, ft, argtypes) = sig if isa(type, Const) # || isconstType(type) @@ -1697,7 +1703,7 @@ function early_inline_special_case( elseif ispuretopfunction(f) || contains_is(_PURE_BUILTINS, f) return SomeCase(quoted(val)) elseif contains_is(_EFFECT_FREE_BUILTINS, f) - if _builtin_nothrow(OptimizerLattice(), f, argtypes[2:end], type) + if _builtin_nothrow(optimizer_lattice(state.interp), f, argtypes[2:end], type) return SomeCase(quoted(val)) end elseif f === Core.get_binding_type @@ -1736,8 +1742,8 @@ end # NOTE we manually inline the method bodies, and so the logic here needs to precisely sync with their definitions function late_inline_special_case!( ir::IRCode, idx::Int, stmt::Expr, @nospecialize(type), sig::Signature, - params::OptimizationParams) - params.inlining || return nothing + state::InliningState) + state.params.inlining || return nothing (; f, ft, argtypes) = sig if length(argtypes) == 3 && istopfunction(f, :!==) # special-case inliner for !== that precedes _methods_by_ftype union splitting @@ -1752,17 +1758,17 @@ function late_inline_special_case!( elseif length(argtypes) == 3 && istopfunction(f, :(>:)) # special-case inliner for issupertype # that works, even though inference generally avoids inferring the `>:` Method - if isa(type, Const) && _builtin_nothrow(OptimizerLattice(), <:, Any[argtypes[3], argtypes[2]], type) + if isa(type, Const) && _builtin_nothrow(optimizer_lattice(state.interp), <:, Any[argtypes[3], argtypes[2]], type) return SomeCase(quoted(type.val)) end subtype_call = Expr(:call, GlobalRef(Core, :(<:)), stmt.args[3], stmt.args[2]) return SomeCase(subtype_call) - elseif f === TypeVar && 2 <= length(argtypes) <= 4 && (argtypes[2] ⊑ₒ Symbol) + elseif f === TypeVar && 2 <= length(argtypes) <= 4 && ⊑(optimizer_lattice(state.interp), argtypes[2], Symbol) typevar_call = Expr(:call, GlobalRef(Core, :_typevar), stmt.args[2], length(stmt.args) < 4 ? Bottom : stmt.args[3], length(stmt.args) == 2 ? Any : stmt.args[end]) return SomeCase(typevar_call) - elseif f === UnionAll && length(argtypes) == 3 && (argtypes[2] ⊑ₒ TypeVar) + elseif f === UnionAll && length(argtypes) == 3 && ⊑(optimizer_lattice(state.interp), argtypes[2], TypeVar) unionall_call = Expr(:foreigncall, QuoteNode(:jl_type_unionall), Any, svec(Any, Any), 0, QuoteNode(:ccall), stmt.args[2], stmt.args[3]) return SomeCase(unionall_call) diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index 974f7939d97f56..bb6de813c51eb5 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -179,11 +179,11 @@ function find_def_for_use( return def, useblock, curblock end -function collect_leaves(compact::IncrementalCompact, @nospecialize(val), @nospecialize(typeconstraint)) +function collect_leaves(compact::IncrementalCompact, @nospecialize(val), @nospecialize(typeconstraint), 𝕃ₒ::AbstractLattice) if isa(val, Union{OldSSAValue, SSAValue}) val, typeconstraint = simple_walk_constraint(compact, val, typeconstraint) end - return walk_to_defs(compact, val, typeconstraint) + return walk_to_defs(compact, val, typeconstraint, 𝕃ₒ) end function simple_walk(compact::IncrementalCompact, @nospecialize(defssa#=::AnySSAValue=#), @@ -243,7 +243,7 @@ end Starting at `val` walk use-def chains to get all the leaves feeding into this `val` (pruning those leaves rules out by path conditions). """ -function walk_to_defs(compact::IncrementalCompact, @nospecialize(defssa), @nospecialize(typeconstraint)) +function walk_to_defs(compact::IncrementalCompact, @nospecialize(defssa), @nospecialize(typeconstraint), 𝕃ₒ::AbstractLattice) visited_phinodes = AnySSAValue[] isa(defssa, AnySSAValue) || return Any[defssa], visited_phinodes def = compact[defssa][:inst] @@ -289,7 +289,7 @@ function walk_to_defs(compact::IncrementalCompact, @nospecialize(defssa), @nospe # path, with a different type constraint. We may have # to redo some work here with the wider typeconstraint push!(worklist_defs, new_def) - push!(worklist_constraints, tmerge(OptimizerLattice(), new_constraint, visited_constraints[new_def])) + push!(worklist_constraints, tmerge(𝕃ₒ, new_constraint, visited_constraints[new_def])) end continue end @@ -340,7 +340,7 @@ function is_pending(compact::IncrementalCompact, old::OldSSAValue) return old.id > length(compact.ir.stmts) + length(compact.ir.new_nodes) end -function is_getfield_captures(@nospecialize(def), compact::IncrementalCompact) +function is_getfield_captures(@nospecialize(def), compact::IncrementalCompact, 𝕃ₒ::AbstractLattice) isa(def, Expr) || return false length(def.args) >= 3 || return false is_known_call(def, getfield, compact) || return false @@ -348,7 +348,7 @@ function is_getfield_captures(@nospecialize(def), compact::IncrementalCompact) isa(which, Const) || return false which.val === :captures || return false oc = argextype(def.args[2], compact) - return oc ⊑ₒ Core.OpaqueClosure + return ⊑(𝕃ₒ, oc, Core.OpaqueClosure) end struct LiftedValue @@ -359,8 +359,8 @@ const LiftedLeaves = IdDict{Any, Union{Nothing,LiftedValue}} # try to compute lifted values that can replace `getfield(x, field)` call # where `x` is an immutable struct that are defined at any of `leaves` -function lift_leaves(compact::IncrementalCompact, - @nospecialize(result_t), field::Int, leaves::Vector{Any}) +function lift_leaves(compact::IncrementalCompact, @nospecialize(result_t), field::Int, + leaves::Vector{Any}, 𝕃ₒ::AbstractLattice) # For every leaf, the lifted value lifted_leaves = LiftedLeaves() maybe_undef = false @@ -411,7 +411,7 @@ function lift_leaves(compact::IncrementalCompact, # continue # end # return nothing - elseif is_getfield_captures(def, compact) + elseif is_getfield_captures(def, compact, 𝕃ₒ) # Walk to new_opaque_closure ocleaf = def.args[2] if isa(ocleaf, AnySSAValue) @@ -569,7 +569,7 @@ function lift_comparison_leaves!(@specialize(tfunc), val, typeconstraint = simple_walk_constraint(compact, val, typeconstraint) end isa(typeconstraint, Union) || return # bail out if there won't be a good chance for lifting - leaves, visited_phinodes = collect_leaves(compact, val, typeconstraint) + leaves, visited_phinodes = collect_leaves(compact, val, typeconstraint, 𝕃ₒ) length(leaves) ≤ 1 && return # bail out if we don't have multiple leaves # check if we can evaluate the comparison for each one of the leaves @@ -851,7 +851,7 @@ its argument). In a case when all usages are fully eliminated, `struct` allocation may also be erased as a result of succeeding dead code elimination. """ -function sroa_pass!(ir::IRCode, inlining::Union{Nothing, InliningState} = nothing) +function sroa_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) 𝕃ₒ = inlining === nothing ? OptimizerLattice() : optimizer_lattice(inlining.interp) compact = IncrementalCompact(ir) defuses = nothing # will be initialized once we encounter mutability in order to reduce dynamic allocations @@ -1017,11 +1017,11 @@ function sroa_pass!(ir::IRCode, inlining::Union{Nothing, InliningState} = nothin field = try_compute_fieldidx(struct_typ, field) field === nothing && continue - leaves, visited_phinodes = collect_leaves(compact, val, struct_typ) + leaves, visited_phinodes = collect_leaves(compact, val, struct_typ, 𝕃ₒ) isempty(leaves) && continue result_t = argextype(SSAValue(idx), compact) - lifted_result = lift_leaves(compact, result_t, field, leaves) + lifted_result = lift_leaves(compact, result_t, field, leaves, 𝕃ₒ) lifted_result === nothing && continue lifted_leaves, any_undef = lifted_result @@ -1564,7 +1564,8 @@ Also note that currently this pass _needs_ to run after `sroa_pass!`, because the `typeassert` elimination depends on the transformation by `canonicalize_typeassert!` done within `sroa_pass!` which redirects references of `typeassert`ed value to the corresponding `PiNode`. """ -function adce_pass!(ir::IRCode) +function adce_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) + 𝕃ₒ = inlining === nothing ? OptimizerLattice() : optimizer_lattice(inlining.interp) phi_uses = fill(0, length(ir.stmts) + length(ir.new_nodes)) all_phis = Int[] unionphis = Pair{Int,Any}[] # sorted @@ -1581,7 +1582,7 @@ function adce_pass!(ir::IRCode) r = searchsorted(unionphis, val.id; by = first) if !isempty(r) unionphi = unionphis[first(r)] - t = tmerge(OptimizerLattice(), unionphi[2], stmt.typ) + t = tmerge(𝕃ₒ, unionphi[2], stmt.typ) unionphis[first(r)] = Pair{Int,Any}(unionphi[1], t) end end @@ -1589,7 +1590,7 @@ function adce_pass!(ir::IRCode) if is_known_call(stmt, typeassert, compact) && length(stmt.args) == 3 # nullify safe `typeassert` calls ty, isexact = instanceof_tfunc(argextype(stmt.args[3], compact)) - if isexact && argextype(stmt.args[2], compact) ⊑ₒ ty + if isexact && ⊑(𝕃ₒ, argextype(stmt.args[2], compact), ty) compact[idx] = nothing continue end @@ -1618,7 +1619,7 @@ function adce_pass!(ir::IRCode) if !isempty(r) unionphi = unionphis[first(r)] unionphis[first(r)] = Pair{Int,Any}(unionphi[1], - tmerge(OptimizerLattice(), unionphi[2], inst[:type])) + tmerge(𝕃ₒ, unionphi[2], inst[:type])) end end end @@ -1635,7 +1636,7 @@ function adce_pass!(ir::IRCode) continue elseif t === Any continue - elseif compact.result[phi][:type] ⊑ₒ t + elseif ⊑(𝕃ₒ, compact.result[phi][:type], t) continue end to_drop = Int[] diff --git a/base/compiler/ssair/slot2ssa.jl b/base/compiler/ssair/slot2ssa.jl index dcd776b1cebdcf..62795cb7e3fd0b 100644 --- a/base/compiler/ssair/slot2ssa.jl +++ b/base/compiler/ssair/slot2ssa.jl @@ -564,7 +564,8 @@ function compute_live_ins(cfg::CFG, defs::Vector{Int}, uses::Vector{Int}) BlockLiveness(bb_defs, bb_uses) end -function recompute_type(node::Union{PhiNode, PhiCNode}, ci::CodeInfo, ir::IRCode, sptypes::Vector{Any}, slottypes::Vector{Any}, nstmts::Int) +function recompute_type(node::Union{PhiNode, PhiCNode}, ci::CodeInfo, ir::IRCode, + sptypes::Vector{Any}, slottypes::Vector{Any}, nstmts::Int, 𝕃ₒ::AbstractLattice) new_typ = Union{} for i = 1:length(node.values) if isa(node, PhiNode) && !isassigned(node.values, i) @@ -583,7 +584,7 @@ function recompute_type(node::Union{PhiNode, PhiCNode}, ci::CodeInfo, ir::IRCode while isa(typ, DelayedTyp) typ = types(ir)[new_to_regular(typ.phi::NewSSAValue, nstmts)] end - new_typ = tmerge(OptimizerLattice(), new_typ, was_maybe_undef ? MaybeUndef(typ) : typ) + new_typ = tmerge(𝕃ₒ, new_typ, was_maybe_undef ? MaybeUndef(typ) : typ) end return new_typ end @@ -603,12 +604,11 @@ struct NewPhiCNode end function construct_ssa!(ci::CodeInfo, ir::IRCode, domtree::DomTree, - defuses::Vector{SlotInfo}, slottypes::Vector{Any}) + defuses::Vector{SlotInfo}, slottypes::Vector{Any}, + 𝕃ₒ::AbstractLattice) code = ir.stmts.inst cfg = ir.cfg catch_entry_blocks = TryCatchRegion[] - lattice = OptimizerLattice() - ⊑ₒ = ⊑(lattice) for idx in 1:length(code) stmt = code[idx] if isexpr(stmt, :enter) @@ -745,7 +745,7 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, domtree::DomTree, if isa(typ, DelayedTyp) push!(type_refine_phi, ssaval.id) end - new_typ = isa(typ, DelayedTyp) ? Union{} : tmerge(lattice, old_entry[:type], typ) + new_typ = isa(typ, DelayedTyp) ? Union{} : tmerge(𝕃ₒ, old_entry[:type], typ) old_entry[:type] = new_typ old_entry[:inst] = node incoming_vals[slot] = ssaval @@ -882,7 +882,7 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, domtree::DomTree, while isa(typ, DelayedTyp) typ = types(ir)[new_to_regular(typ.phi::NewSSAValue, nstmts)] end - new_typ = tmerge(lattice, new_typ, typ) + new_typ = tmerge(𝕃ₒ, new_typ, typ) end node[:type] = new_typ end @@ -895,8 +895,8 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, domtree::DomTree, changed = false for new_idx in type_refine_phi node = new_nodes.stmts[new_idx] - new_typ = recompute_type(node[:inst]::Union{PhiNode,PhiCNode}, ci, ir, ir.sptypes, slottypes, nstmts) - if !(node[:type] ⊑ₒ new_typ) || !(new_typ ⊑ₒ node[:type]) + new_typ = recompute_type(node[:inst]::Union{PhiNode,PhiCNode}, ci, ir, ir.sptypes, slottypes, nstmts, 𝕃ₒ) + if !⊑(𝕃ₒ, node[:type], new_typ) || !⊑(𝕃ₒ, new_typ, node[:type]) node[:type] = new_typ changed = true end diff --git a/base/compiler/ssair/verify.jl b/base/compiler/ssair/verify.jl index 57d60ec2ce9804..23c76525d0fc2d 100644 --- a/base/compiler/ssair/verify.jl +++ b/base/compiler/ssair/verify.jl @@ -79,8 +79,9 @@ function count_int(val::Int, arr::Vector{Int}) n end -function verify_ir(ir::IRCode, print::Bool=true, allow_frontend_forms::Bool=false, - lattice = OptimizerLattice()) +function verify_ir(ir::IRCode, print::Bool=true, + allow_frontend_forms::Bool=false, + 𝕃ₒ::AbstractLattice = OptimizerLattice()) # For now require compact IR # @assert isempty(ir.new_nodes) # Verify CFG @@ -207,7 +208,7 @@ function verify_ir(ir::IRCode, print::Bool=true, allow_frontend_forms::Bool=fals val = stmt.values[i] phiT = ir.stmts[idx][:type] if isa(val, SSAValue) - if !⊑(lattice, types(ir)[val], phiT) + if !⊑(𝕃ₒ, types(ir)[val], phiT) #@verify_error """ # PhiNode $idx, has operand $(val.id), whose type is not a sub lattice element. # PhiNode type was $phiT From 2568160a724ef0d595312a5b150d0d7e4a0d1bbf Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Thu, 22 Dec 2022 18:18:27 +0900 Subject: [PATCH 153/387] add test case for "fix incorrect inference of `NamedTuple{(), <:Any}` (#47962) Co-Authored-By: Martin Holters Co-authored-by: Martin Holters --- test/compiler/inference.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 2a664ce6dbcc07..228e622183b9ad 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -2728,6 +2728,10 @@ end |> only === Int # Equivalence of Const(T.instance) and T for singleton types @test Const(nothing) ⊑ Nothing && Nothing ⊑ Const(nothing) +# https://github.com/JuliaLang/julia/pull/47947 +# correct `apply_type` inference of `NamedTuple{(), <:Any}` +@test (() -> NamedTuple{(), <:Any})() isa UnionAll + # Don't pessimize apply_type to anything worse than Type and yield Bottom for invalid Unions @test only(Base.return_types(Core.apply_type, Tuple{Type{Union}})) == Type{Union{}} @test only(Base.return_types(Core.apply_type, Tuple{Type{Union},Any})) == Type From 181328c9b7b7ca34982494adc90c3e49e2af1018 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Thu, 22 Dec 2022 19:15:44 +0900 Subject: [PATCH 154/387] reland refinement on instantiation of partially-known empty `NamedTuple` (#47961) This is a re-land of #47481. This commit improves inference accuracy of instantiation of partially-known, empty NamedTuple. Note that we can't do the same for inference of `apply_type` call as pointed at #47481. ```julia @test Base.return_types((Any,)) do Tpl T = NamedTuple{(),Tpl} nt = T(()) values(nt) end === Tuple{} ``` --- base/compiler/abstractinterpretation.jl | 16 ++++++++++++++++ test/compiler/inference.jl | 12 ++++++++++++ 2 files changed, 28 insertions(+) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 50ccf9ac25e6bc..f3472d30ce9cc3 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2309,6 +2309,7 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp else consistent = ALWAYS_FALSE nothrow = false + t = refine_partial_type(t) end effects = Effects(EFFECTS_TOTAL; consistent, nothrow) merge_effects!(interp, sv, effects) @@ -2327,6 +2328,8 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp nothrow = isexact t = PartialStruct(t, at.fields::Vector{Any}) end + else + t = refine_partial_type(t) end consistent = !ismutabletype(t) ? ALWAYS_TRUE : CONSISTENT_IF_NOTRETURNED effects = Effects(EFFECTS_TOTAL; consistent, nothrow) @@ -2421,6 +2424,19 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp return RTEffects(t, effects) end +# refine the result of instantiation of partially-known type `t` if some invariant can be assumed +function refine_partial_type(@nospecialize t) + t′ = unwrap_unionall(t) + if isa(t′, DataType) && t′.name === _NAMEDTUPLE_NAME && length(t′.parameters) == 2 && + (t′.parameters[1] === () || t′.parameters[2] === Tuple{}) + # if the first/second parameter of `NamedTuple` is known to be empty, + # the second/first argument should also be empty tuple type, + # so refine it here + return Const(NamedTuple(())) + end + return t +end + function abstract_eval_foreigncall(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable, Nothing}, sv::Union{InferenceState, IRCode}, mi::Union{MethodInstance, Nothing}=nothing) abstract_eval_value(interp, e.args[1], vtypes, sv) mi′ = isa(sv, InferenceState) ? sv.linfo : mi diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 228e622183b9ad..f416aa5f13a92e 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -4673,3 +4673,15 @@ bar47688() = foo47688() @test it_count47688 == 7 @test isa(foo47688(), NTuple{6, Int}) @test it_count47688 == 14 + +# refine instantiation of partially-known NamedTuple that is known to be empty +@test Base.return_types((Any,)) do Tpl + T = NamedTuple{(),Tpl} + nt = T(()) + values(nt) +end |> only === Tuple{} +@test Base.return_types((Any,)) do tpl + T = NamedTuple{tpl,Tuple{}} + nt = T(()) + keys(nt) +end |> only === Tuple{} From a074d06e2d8d645fca6c6751325f6f31bda99a2f Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Thu, 22 Dec 2022 20:30:28 +0900 Subject: [PATCH 155/387] inference: mark flag for effect-free `:call`s during abstractinterpret (#47689) So that they can be deleted during the first `compact!`-ion. This allows us to delete an inlineable and effect-free, but unused call. This is essentially an alternative of #47305, but doesn't introduce a problem like #47374. --- base/compiler/abstractinterpretation.jl | 15 +++++++++++++++ base/compiler/inferencestate.jl | 2 ++ test/compiler/inline.jl | 23 +++++++++++++++++++++-- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index f3472d30ce9cc3..96b8ded9c73a8e 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2250,6 +2250,13 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp merge_effects!(interp, sv, effects) if isa(sv, InferenceState) sv.stmt_info[sv.currpc] = info + # mark this call statement as DCE-elgible + # TODO better to do this in a single pass based on the `info` object at the end of abstractinterpret? + if is_removable_if_unused(effects) + add_curr_ssaflag!(sv, IR_FLAG_EFFECT_FREE) + else + sub_curr_ssaflag!(sv, IR_FLAG_EFFECT_FREE) + end end end t = rt @@ -2362,6 +2369,14 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp (;rt, effects) = abstract_eval_foreigncall(interp, e, vtypes, sv, mi) t = rt merge_effects!(interp, sv, effects) + if isa(sv, InferenceState) + # mark this call statement as DCE-elgible + if is_removable_if_unused(effects) + add_curr_ssaflag!(sv, IR_FLAG_EFFECT_FREE) + else + sub_curr_ssaflag!(sv, IR_FLAG_EFFECT_FREE) + end + end elseif ehead === :cfunction effects = EFFECTS_UNKNOWN merge_effects!(interp, sv, effects) diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index 61b2fe1f27c723..df65d6668df3d3 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -532,6 +532,8 @@ function print_callstack(sv::InferenceState) end get_curr_ssaflag(sv::InferenceState) = sv.src.ssaflags[sv.currpc] +add_curr_ssaflag!(sv::InferenceState, flag::UInt8) = sv.src.ssaflags[sv.currpc] |= flag +sub_curr_ssaflag!(sv::InferenceState, flag::UInt8) = sv.src.ssaflags[sv.currpc] &= ~flag function narguments(sv::InferenceState) def = sv.linfo.def diff --git a/test/compiler/inline.jl b/test/compiler/inline.jl index c8cfacd09cd9fa..5991b67b1618b0 100644 --- a/test/compiler/inline.jl +++ b/test/compiler/inline.jl @@ -1077,8 +1077,7 @@ Base.setindex!(s::SafeRef, x) = setfield!(s, 1, x) noninlined_dce_new(s) nothing end -# should be resolved once we merge https://github.com/JuliaLang/julia/pull/43923 -@test_broken fully_eliminated((Union{Symbol,String},)) do s +@test fully_eliminated((Union{Symbol,String},)) do s noninlined_dce_new(s) nothing end @@ -1820,6 +1819,26 @@ let ir = Base.code_ircode(big_tuple_test1, Tuple{})[1][1] @test length(ir.stmts) == 1 end +# inlineable but removable call should be eligible for DCE +Base.@assume_effects :removable @inline function inlineable_effect_free(a::Float64) + a == Inf && return zero(a) + return sin(a) + cos(a) +end +@test fully_eliminated((Float64,)) do a + b = inlineable_effect_free(a) + c = inlineable_effect_free(b) + nothing +end + +# https://github.com/JuliaLang/julia/issues/47374 +function f47374(x) + [f47374(i, x) for i in 1:1] +end +function f47374(i::Int, x) + return 1.0 +end +@test f47374(rand(1)) == Float64[1.0] + # compiler should recognize effectful :static_parameter # https://github.com/JuliaLang/julia/issues/45490 issue45490_1(x::Union{T, Nothing}, y::Union{T, Nothing}) where {T} = T From 12e679cabbe827d3be1869b9eaac24263415ee95 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Thu, 22 Dec 2022 19:44:35 +0800 Subject: [PATCH 156/387] Apply `InitialOptimizations` more consistently in sorting & fix dispatch bug (#47946) * Apply InitialOptimizations by default in several cases when it was previously present * fixup for MissingOptimization * fix stability in the sortperm union with missing case --- base/sort.jl | 47 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/base/sort.jl b/base/sort.jl index 6d9f65c61b390b..1266da8a8c9df7 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -86,7 +86,7 @@ issorted(itr; issorted(itr, ord(lt,by,rev,order)) function partialsort!(v::AbstractVector, k::Union{Integer,OrdinalRange}, o::Ordering) - _sort!(v, QuickerSort(k), o, (;)) + _sort!(v, InitialOptimizations(QuickerSort(k)), o, (;)) maybeview(v, k) end @@ -566,8 +566,30 @@ function _sort!(v::AbstractVector, a::MissingOptimization, o::Ordering, kw) if nonmissingtype(eltype(v)) != eltype(v) && o isa DirectOrdering lo, hi = send_to_end!(ismissing, v, o; lo, hi) _sort!(WithoutMissingVector(v, unsafe=true), a.next, o, (;kw..., lo, hi)) - elseif eltype(v) <: Integer && o isa Perm{DirectOrdering} && nonmissingtype(eltype(o.data)) != eltype(o.data) - lo, hi = send_to_end!(i -> ismissing(@inbounds o.data[i]), v, o) + elseif eltype(v) <: Integer && o isa Perm && o.order isa DirectOrdering && + nonmissingtype(eltype(o.data)) != eltype(o.data) && + all(i === j for (i,j) in zip(v, eachindex(o.data))) + # TODO make this branch known at compile time + # This uses a custom function because we need to ensure stability of both sides and + # we can assume v is equal to eachindex(o.data) which allows a copying partition + # without allocations. + lo_i, hi_i = lo, hi + for (i,x) in zip(eachindex(o.data), o.data) + if ismissing(x) == (o.order == Reverse) # should i go at the beginning? + v[lo_i] = i + lo_i += 1 + else + v[hi_i] = i + hi_i -= 1 + end + end + reverse!(v, lo_i, hi) + if o.order == Reverse + lo = lo_i + else + hi = hi_i + end + _sort!(v, a.next, Perm(o.order, WithoutMissingVector(o.data, unsafe=true)), (;kw..., lo, hi)) else _sort!(v, a.next, o, kw) @@ -1160,7 +1182,9 @@ end """ InitialOptimizations(next) <: Algorithm -Attempt to apply a suite of low-cost optimizations to the input vector before sorting. +Attempt to apply a suite of low-cost optimizations to the input vector before sorting. These +optimizations may be automatically applied by the `sort!` family of functions when +`alg=InsertionSort`, `alg=MergeSort`, or `alg=QuickSort` is passed as an argument. `InitialOptimizations` is an implementation detail and subject to change or removal in future versions of Julia. @@ -1347,7 +1371,7 @@ function sort!(v::AbstractVector{T}; rev::Union{Bool,Nothing}=nothing, order::Ordering=Forward, scratch::Union{Vector{T}, Nothing}=nothing) where T - _sort!(v, alg, ord(lt,by,rev,order), (;scratch)) + _sort!(v, maybe_apply_initial_optimizations(alg), ord(lt,by,rev,order), (;scratch)) v end @@ -1474,7 +1498,7 @@ function partialsortperm!(ix::AbstractVector{<:Integer}, v::AbstractVector, end # do partial quicksort - _sort!(ix, QuickerSort(k), Perm(ord(lt, by, rev, order), v), (;)) + _sort!(ix, InitialOptimizations(QuickerSort(k)), Perm(ord(lt, by, rev, order), v), (;)) maybeview(ix, k) end @@ -1679,11 +1703,11 @@ function sort(A::AbstractArray{T}; pdims = (dim, setdiff(1:ndims(A), dim)...) # put the selected dimension first Ap = permutedims(A, pdims) Av = vec(Ap) - sort_chunks!(Av, n, alg, order, scratch) + sort_chunks!(Av, n, maybe_apply_initial_optimizations(alg), order, scratch) permutedims(Ap, invperm(pdims)) else Av = A[:] - sort_chunks!(Av, n, alg, order, scratch) + sort_chunks!(Av, n, maybe_apply_initial_optimizations(alg), order, scratch) reshape(Av, axes(A)) end end @@ -1746,7 +1770,7 @@ function sort!(A::AbstractArray{T}; rev::Union{Bool,Nothing}=nothing, order::Ordering=Forward, # TODO stop eagerly over-allocating. scratch::Union{Vector{T}, Nothing}=similar(A, size(A, dims))) where T - __sort!(A, Val(dims), alg, ord(lt, by, rev, order), scratch) + __sort!(A, Val(dims), maybe_apply_initial_optimizations(alg), ord(lt, by, rev, order), scratch) end function __sort!(A::AbstractArray{T}, ::Val{K}, alg::Algorithm, @@ -1911,6 +1935,11 @@ Characteristics: """ const MergeSort = MergeSortAlg() +maybe_apply_initial_optimizations(alg::Algorithm) = alg +maybe_apply_initial_optimizations(alg::QuickSortAlg) = InitialOptimizations(alg) +maybe_apply_initial_optimizations(alg::MergeSortAlg) = InitialOptimizations(alg) +maybe_apply_initial_optimizations(alg::InsertionSortAlg) = InitialOptimizations(alg) + # selectpivot! # # Given 3 locations in an array (lo, mi, and hi), sort v[lo], v[mi], v[hi]) and From 3930ce1b9b36796a88c91e9e8183ecd46b800184 Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Thu, 22 Dec 2022 14:49:52 -0800 Subject: [PATCH 157/387] Add NEWS entry for #47804 (#47969) --- NEWS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS.md b/NEWS.md index 7d90c6f70ce101..6767ae24ec373c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -66,6 +66,9 @@ Standard library changes #### Test +* The `@test_broken` macro (or `@test` with `broken=true`) now complains if the test expression returns a + non-boolean value in the same way as a non-broken test. ([#47804]) + #### Dates From ebeda7a6cca43d19615cf24ce392d3e8cb218133 Mon Sep 17 00:00:00 2001 From: Stefan Karpinski Date: Thu, 22 Dec 2022 18:02:47 -0500 Subject: [PATCH 158/387] move regex tests to test/; make more precise (#47959) These tests aren't specific to PCRE; no matter how Regex matching is implemented, these tests should pass, so I'm moving them into test/regex.jl. I'm also making them more precise and testing that the match is not only something, but also that it is the exact substring that should match. Might catch some future PCRE regression. --- stdlib/PCRE2_jll/test/runtests.jl | 11 ----------- test/regex.jl | 11 +++++++++++ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/stdlib/PCRE2_jll/test/runtests.jl b/stdlib/PCRE2_jll/test/runtests.jl index ab7f750b203e0d..d593b07af31ce8 100644 --- a/stdlib/PCRE2_jll/test/runtests.jl +++ b/stdlib/PCRE2_jll/test/runtests.jl @@ -8,14 +8,3 @@ using Test, Libdl, PCRE2_jll vn = VersionNumber(split(unsafe_string(pointer(vstr)), " ")[1]) @test vn == v"10.42.0" end - -@testset "#47936" begin - tests = (r"a+[bc]+c", - r"a+[bc]{1,2}c", - r"(a)+[bc]+c", - r"a{1,2}[bc]+c", - r"(a+)[bc]+c") - for re in tests - @test !isnothing(match(re, "ababc")) - end -end diff --git a/test/regex.jl b/test/regex.jl index 37bed00ef0b975..70f620cad7141c 100644 --- a/test/regex.jl +++ b/test/regex.jl @@ -224,3 +224,14 @@ # hash @test hash(r"123"i, zero(UInt)) == hash(Regex("123", "i"), zero(UInt)) end + +@testset "#47936" begin + tests = (r"a+[bc]+c", + r"a+[bc]{1,2}c", + r"(a)+[bc]+c", + r"a{1,2}[bc]+c", + r"(a+)[bc]+c") + for re in tests + @test match(re, "ababc").match === SubString("ababc", 3:5) + end +end From 7481dcc06f66becd6cb69f6f1399bcc417df5fc4 Mon Sep 17 00:00:00 2001 From: Petr Vana Date: Fri, 23 Dec 2022 23:10:18 +0100 Subject: [PATCH 159/387] Parallelize precompilation script --- contrib/generate_precompile.jl | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index 53ca9403463b34..50da233e3433c2 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -245,15 +245,16 @@ function generate_precompile_statements() sysimg = Base.unsafe_string(Base.JLOptions().image_file) # Extract the precompile statements from the precompile file - statements = Set{String}() + statements_step1 = Channel{String}(Inf) + statements_step2 = Channel{String}(Inf) # From hardcoded statements for statement in split(hardcoded_precompile_statements::String, '\n') - push!(statements, statement) + push!(statements_step1, statement) end # Collect statements from running the script - mktempdir() do prec_path + @async mktempdir() do prec_path # Also precompile a package here pkgname = "__PackagePrecompilationStatementModule" mkpath(joinpath(prec_path, pkgname, "src")) @@ -275,12 +276,13 @@ function generate_precompile_statements() for f in (tmp_prec, tmp_proc) for statement in split(read(f, String), '\n') occursin("Main.", statement) && continue - push!(statements, statement) + push!(statements_step1, statement) end end + close(statements_step1) end - mktemp() do precompile_file, precompile_file_h + @async mktemp() do precompile_file, precompile_file_h # Collect statements from running a REPL process and replaying our REPL script pts, ptm = open_fake_pty() blackhole = Sys.isunix() ? "/dev/null" : "nul" @@ -359,8 +361,9 @@ function generate_precompile_statements() for statement in split(read(precompile_file, String), '\n') # Main should be completely clean occursin("Main.", statement) && continue - push!(statements, statement) + push!(statements_step2, statement) end + close(statements_step2) end # Create a staging area where all the loaded packages are available @@ -371,9 +374,12 @@ function generate_precompile_statements() end end + # Make statements unique + statements = Set{String}() # Execute the precompile statements n_succeeded = 0 - include_time = @elapsed for statement in statements + include_time = @elapsed for sts in [statements_step1, statements_step2], statement in sts + Base.in!(statement, statements) && continue # println(statement) # XXX: skip some that are broken. these are caused by issue #39902 occursin("Tuple{Artifacts.var\"#@artifact_str\", LineNumberNode, Module, Any, Any}", statement) && continue From 0f9186aae914b27dc69f37d43c25b33b74f15b55 Mon Sep 17 00:00:00 2001 From: Petr Vana Date: Fri, 23 Dec 2022 23:42:02 +0100 Subject: [PATCH 160/387] Clean the code --- contrib/generate_precompile.jl | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index 50da233e3433c2..b1396e206969b4 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -275,7 +275,6 @@ function generate_precompile_statements() run(`$(julia_exepath()) -O0 --sysimage $sysimg --trace-compile=$tmp_proc --startup-file=no -Cnative -e $s`) for f in (tmp_prec, tmp_proc) for statement in split(read(f, String), '\n') - occursin("Main.", statement) && continue push!(statements_step1, statement) end end @@ -359,8 +358,6 @@ function generate_precompile_statements() write(debug_output, "\n#### FINISHED ####\n") for statement in split(read(precompile_file, String), '\n') - # Main should be completely clean - occursin("Main.", statement) && continue push!(statements_step2, statement) end close(statements_step2) @@ -378,7 +375,9 @@ function generate_precompile_statements() statements = Set{String}() # Execute the precompile statements n_succeeded = 0 - include_time = @elapsed for sts in [statements_step1, statements_step2], statement in sts + for sts in [statements_step1, statements_step2], statement in sts + # Main should be completely clean + occursin("Main.", statement) && continue Base.in!(statement, statements) && continue # println(statement) # XXX: skip some that are broken. these are caused by issue #39902 @@ -422,13 +421,8 @@ function generate_precompile_statements() n_succeeded > 1200 || @warn "Only $n_succeeded precompile statements" end - include_time *= 1e9 - gen_time = (time_ns() - start_time) - include_time tot_time = time_ns() - start_time - println("Precompilation complete. Summary:") - print("Generation ── "); Base.time_print(gen_time); print(" "); show(IOContext(stdout, :compact=>true), gen_time / tot_time * 100); println("%") - print("Execution ─── "); Base.time_print(include_time); print(" "); show(IOContext(stdout, :compact=>true), include_time / tot_time * 100); println("%") print("Total ─────── "); Base.time_print(tot_time); println() return From b540315cb4bd91e6f3a3e4ab8129a58556947628 Mon Sep 17 00:00:00 2001 From: David Anthoff Date: Fri, 23 Dec 2022 20:45:00 -0800 Subject: [PATCH 161/387] Add Juliaup and versions.json to release proc instr (#47984) --- Makefile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index eb6704a1fa1e09..c31517c4f1e3a1 100644 --- a/Makefile +++ b/Makefile @@ -152,8 +152,10 @@ release-candidate: release testall @echo 10. Follow packaging instructions in doc/build/distributing.md to create binary packages for all platforms @echo 11. Upload to AWS, update https://julialang.org/downloads and http://status.julialang.org/stable links @echo 12. Update checksums on AWS for tarball and packaged binaries - @echo 13. Announce on mailing lists - @echo 14. Change master to release-0.X in base/version.jl and base/version_git.sh as in 4cb1e20 + @echo 13. Update versions.json + @echo 14. Push to Juliaup (https://github.com/JuliaLang/juliaup/wiki/Adding-a-Julia-version) + @echo 15. Announce on mailing lists + @echo 16. Change master to release-0.X in base/version.jl and base/version_git.sh as in 4cb1e20 @echo $(build_man1dir)/julia.1: $(JULIAHOME)/doc/man/julia.1 | $(build_man1dir) From 3add4cd33b99deef2136fa9d642298059020c966 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sat, 24 Dec 2022 18:17:54 -0500 Subject: [PATCH 162/387] lattice: Don't short-circuit layers between PartialStruct and Type (#47988) This catches a corner case I missed in #46881. In particular, when both arguments are `Const`, it is legal for intermediate lattice layers between the PartialsLattice and the ConstLattice to do something other than give the merged type. The current code in the PartialsLattice was short-circuiting this, leading to inference imprecision. I think technically, we'd be ok to do the short-circuiting when both are known to be PartialStruct (since we know that they widenlattice to a type), but it doesn't seem worth the extra effort to check. --- base/compiler/typelimits.jl | 118 +++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 54 deletions(-) diff --git a/base/compiler/typelimits.jl b/base/compiler/typelimits.jl index 2501dd51edf6d2..d092f1e6860ec4 100644 --- a/base/compiler/typelimits.jl +++ b/base/compiler/typelimits.jl @@ -487,69 +487,79 @@ function tmerge(𝕃::AnyMustAliasesLattice, @nospecialize(typea), @nospecialize return tmerge(widenlattice(𝕃), typea, typeb) end -function tmerge(lattice::PartialsLattice, @nospecialize(typea), @nospecialize(typeb)) - # type-lattice for Const and PartialStruct wrappers - acp = isa(typea, Const) || isa(typea, PartialStruct) - bcp = isa(typeb, Const) || isa(typeb, PartialStruct) - if acp && bcp - aty = widenconst(typea) - bty = widenconst(typeb) - if aty === bty - # must have egal here, since we do not create PartialStruct for non-concrete types - typea_nfields = nfields_tfunc(lattice, typea) - typeb_nfields = nfields_tfunc(lattice, typeb) - isa(typea_nfields, Const) || return aty - isa(typeb_nfields, Const) || return aty - type_nfields = typea_nfields.val::Int - type_nfields === typeb_nfields.val::Int || return aty - type_nfields == 0 && return aty - fields = Vector{Any}(undef, type_nfields) - anyrefine = false - for i = 1:type_nfields - ai = getfield_tfunc(lattice, typea, Const(i)) - bi = getfield_tfunc(lattice, typeb, Const(i)) - ft = fieldtype(aty, i) - if is_lattice_equal(lattice, ai, bi) || is_lattice_equal(lattice, ai, ft) - # Since ai===bi, the given type has no restrictions on complexity. - # and can be used to refine ft - tyi = ai - elseif is_lattice_equal(lattice, bi, ft) - tyi = bi - elseif (tyi′ = tmerge_field(lattice, ai, bi); tyi′ !== nothing) - # allow external lattice implementation to provide a custom field-merge strategy - tyi = tyi′ +# N.B. This can also be called with both typea::Const and typeb::Const to +# to recover PartialStruct from `Const`s with overlapping fields. +function tmerge_partial_struct(lattice::PartialsLattice, @nospecialize(typea), @nospecialize(typeb)) + aty = widenconst(typea) + bty = widenconst(typeb) + if aty === bty + # must have egal here, since we do not create PartialStruct for non-concrete types + typea_nfields = nfields_tfunc(lattice, typea) + typeb_nfields = nfields_tfunc(lattice, typeb) + isa(typea_nfields, Const) || return nothing + isa(typeb_nfields, Const) || return nothing + type_nfields = typea_nfields.val::Int + type_nfields === typeb_nfields.val::Int || return nothing + type_nfields == 0 && return nothing + fields = Vector{Any}(undef, type_nfields) + anyrefine = false + for i = 1:type_nfields + ai = getfield_tfunc(lattice, typea, Const(i)) + bi = getfield_tfunc(lattice, typeb, Const(i)) + ft = fieldtype(aty, i) + if is_lattice_equal(lattice, ai, bi) || is_lattice_equal(lattice, ai, ft) + # Since ai===bi, the given type has no restrictions on complexity. + # and can be used to refine ft + tyi = ai + elseif is_lattice_equal(lattice, bi, ft) + tyi = bi + elseif (tyi′ = tmerge_field(lattice, ai, bi); tyi′ !== nothing) + # allow external lattice implementation to provide a custom field-merge strategy + tyi = tyi′ + else + # Otherwise use the default aggressive field-merge implementation, and + # choose between using the fieldtype or some other simple merged type. + # The wrapper type never has restrictions on complexity, + # so try to use that to refine the estimated type too. + tni = _typename(widenconst(ai)) + if tni isa Const && tni === _typename(widenconst(bi)) + # A tmeet call may cause tyi to become complex, but since the inputs were + # strictly limited to being egal, this has no restrictions on complexity. + # (Otherwise, we would need to use <: and take the narrower one without + # intersection. See the similar comment in abstract_call_method.) + tyi = typeintersect(ft, (tni.val::Core.TypeName).wrapper) else - # Otherwise use the default aggressive field-merge implementation, and - # choose between using the fieldtype or some other simple merged type. - # The wrapper type never has restrictions on complexity, - # so try to use that to refine the estimated type too. - tni = _typename(widenconst(ai)) - if tni isa Const && tni === _typename(widenconst(bi)) - # A tmeet call may cause tyi to become complex, but since the inputs were - # strictly limited to being egal, this has no restrictions on complexity. - # (Otherwise, we would need to use <: and take the narrower one without - # intersection. See the similar comment in abstract_call_method.) - tyi = typeintersect(ft, (tni.val::Core.TypeName).wrapper) - else - # Since aty===bty, the fieldtype has no restrictions on complexity. - tyi = ft - end - end - fields[i] = tyi - if !anyrefine - anyrefine = has_nontrivial_extended_info(lattice, tyi) || # extended information - ⋤(lattice, tyi, ft) # just a type-level information, but more precise than the declared type + # Since aty===bty, the fieldtype has no restrictions on complexity. + tyi = ft end end - return anyrefine ? PartialStruct(aty, fields) : aty + fields[i] = tyi + if !anyrefine + anyrefine = has_nontrivial_extended_info(lattice, tyi) || # extended information + ⋤(lattice, tyi, ft) # just a type-level information, but more precise than the declared type + end end + anyrefine && return PartialStruct(aty, fields) + end + return nothing +end + +function tmerge(lattice::PartialsLattice, @nospecialize(typea), @nospecialize(typeb)) + # type-lattice for Const and PartialStruct wrappers + aps = isa(typea, PartialStruct) + bps = isa(typeb, PartialStruct) + acp = aps || isa(typea, Const) + bcp = bps || isa(typeb, Const) + if acp && bcp + psrt = tmerge_partial_struct(lattice, typea, typeb) + psrt !== nothing && return psrt end # Don't widen const here - external AbstractInterpreter might insert lattice # layers between us and `ConstsLattice`. wl = widenlattice(lattice) - isa(typea, PartialStruct) && (typea = widenlattice(wl, typea)) - isa(typeb, PartialStruct) && (typeb = widenlattice(wl, typeb)) + aps && (typea = widenlattice(wl, typea)) + bps && (typeb = widenlattice(wl, typeb)) # type-lattice for PartialOpaque wrapper apo = isa(typea, PartialOpaque) From ea13810f632341409eeddf008aef66b11f015b3d Mon Sep 17 00:00:00 2001 From: Antonio Rojas Date: Sun, 25 Dec 2022 00:57:00 +0100 Subject: [PATCH 163/387] Restore libgcc_s symlinkin in !macOS (#47986) Commit c8b72e2bf49046e8daca64214765694377277947 completely removed libgcc_s symlinking (I assume unintentionally) in !macOS. --- base/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/base/Makefile b/base/Makefile index bb79549aeea2eb..c320465a104921 100644 --- a/base/Makefile +++ b/base/Makefile @@ -227,6 +227,8 @@ else $(eval $(call symlink_system_library,CSL,libgcc_s,1)) endif endif +else +$(eval $(call symlink_system_library,CSL,libgcc_s,1)) endif ifneq (,$(LIBGFORTRAN_VERSION)) $(eval $(call symlink_system_library,CSL,libgfortran,$(LIBGFORTRAN_VERSION))) From 21529a93ee60a5499e818ba6d570f1e847e134cd Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sun, 25 Dec 2022 05:05:05 -0500 Subject: [PATCH 164/387] irinterp: Add missing widenconst into tmeet (#47993) The `fixup_slot!` code in slot2ssa.jl indiscrimnately moves lattice elements into `PiNode` `typ` fields. As a result, we cannot assume that we have `typ::Type`, so we must widenconst in irinterp to avoid errors. --- base/compiler/ssair/irinterp.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index 89e0851e84a60c..a2a5deccba838c 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -250,7 +250,7 @@ function reprocess_instruction!(interp::AbstractInterpreter, # Handled at the very end return false elseif isa(inst, PiNode) - rt = tmeet(typeinf_lattice(interp), argextype(inst.val, ir), inst.typ) + rt = tmeet(typeinf_lattice(interp), argextype(inst.val, ir), widenconst(inst.typ)) else ccall(:jl_, Cvoid, (Any,), inst) error() From 162ee48e1c34b2a2cd797395353f19a7aca21aa2 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sun, 25 Dec 2022 07:36:55 -0500 Subject: [PATCH 165/387] lattice: Don't forget to pass down lattice in Conditional tmerge (#47992) Also add some extra cases that before were taken care of by the tmerge_fast_path at the entry to the tmerge code. I briefly considered splitting an extra slow path function to avoid the one `===` in the ConstsLattice, but in the non-equality case that check should be quite fast, so it didn't seem worth it. --- base/compiler/typelimits.jl | 39 +++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/base/compiler/typelimits.jl b/base/compiler/typelimits.jl index d092f1e6860ec4..02a734cd2d5cfa 100644 --- a/base/compiler/typelimits.jl +++ b/base/compiler/typelimits.jl @@ -429,8 +429,8 @@ function tmerge(lattice::ConditionalsLattice, @nospecialize(typea), @nospecializ end if isa(typea, Conditional) && isa(typeb, Conditional) if is_same_conditionals(typea, typeb) - thentype = tmerge(typea.thentype, typeb.thentype) - elsetype = tmerge(typea.elsetype, typeb.elsetype) + thentype = tmerge(widenlattice(lattice), typea.thentype, typeb.thentype) + elsetype = tmerge(widenlattice(lattice), typea.elsetype, typeb.elsetype) if thentype !== elsetype return Conditional(typea.slot, thentype, elsetype) end @@ -464,8 +464,8 @@ function tmerge(lattice::InterConditionalsLattice, @nospecialize(typea), @nospec end if isa(typea, InterConditional) && isa(typeb, InterConditional) if is_same_conditionals(typea, typeb) - thentype = tmerge(typea.thentype, typeb.thentype) - elsetype = tmerge(typea.elsetype, typeb.elsetype) + thentype = tmerge(widenlattice(lattice), typea.thentype, typeb.thentype) + elsetype = tmerge(widenlattice(lattice), typea.elsetype, typeb.elsetype) if thentype !== elsetype return InterConditional(typea.slot, thentype, elsetype) end @@ -506,6 +506,9 @@ function tmerge_partial_struct(lattice::PartialsLattice, @nospecialize(typea), @ for i = 1:type_nfields ai = getfield_tfunc(lattice, typea, Const(i)) bi = getfield_tfunc(lattice, typeb, Const(i)) + # N.B.: We're assuming here that !isType(aty), because that case + # only arises when typea === typeb, which should have been caught + # before calling this. ft = fieldtype(aty, i) if is_lattice_equal(lattice, ai, bi) || is_lattice_equal(lattice, ai, ft) # Since ai===bi, the given type has no restrictions on complexity. @@ -551,6 +554,7 @@ function tmerge(lattice::PartialsLattice, @nospecialize(typea), @nospecialize(ty acp = aps || isa(typea, Const) bcp = bps || isa(typeb, Const) if acp && bcp + typea === typeb && return typea psrt = tmerge_partial_struct(lattice, typea, typeb) psrt !== nothing && return psrt end @@ -586,21 +590,36 @@ function tmerge(lattice::PartialsLattice, @nospecialize(typea), @nospecialize(ty return tmerge(wl, typea, typeb) end + function tmerge(lattice::ConstsLattice, @nospecialize(typea), @nospecialize(typeb)) - # the equality of the constants can be checked here, but the equivalent check is usually - # done by `tmerge_fast_path` at earlier lattice stage + acp = isa(typea, Const) || isa(typea, PartialTypeVar) + bcp = isa(typeb, Const) || isa(typeb, PartialTypeVar) + if acp && bcp + typea === typeb && return typea + end wl = widenlattice(lattice) - (isa(typea, Const) || isa(typea, PartialTypeVar)) && (typea = widenlattice(wl, typea)) - (isa(typeb, Const) || isa(typeb, PartialTypeVar)) && (typeb = widenlattice(wl, typeb)) + acp && (typea = widenlattice(wl, typea)) + bcp && (typeb = widenlattice(wl, typeb)) return tmerge(wl, typea, typeb) end function tmerge(::JLTypeLattice, @nospecialize(typea::Type), @nospecialize(typeb::Type)) - typea == typeb && return typea # it's always ok to form a Union of two concrete types - if (isconcretetype(typea) || isType(typea)) && (isconcretetype(typeb) || isType(typeb)) + act = isconcretetype(typea) + bct = isconcretetype(typeb) + if act && bct + # Extra fast path for pointer-egal concrete types + (pointer_from_objref(typea) === pointer_from_objref(typeb)) && return typea + end + if (act || isType(typea)) && (bct || isType(typeb)) return Union{typea, typeb} end + typea <: typeb && return typeb + typeb <: typea && return typea + return tmerge_types_slow(typea, typeb) +end + +@noinline function tmerge_types_slow(@nospecialize(typea::Type), @nospecialize(typeb::Type)) # collect the list of types from past tmerge calls returning Union # and then reduce over that list types = Any[] From f5eeba35d9bf20de251bb9160cc935c71e8b19ba Mon Sep 17 00:00:00 2001 From: Dilum Aluthge Date: Mon, 26 Dec 2022 03:40:51 -0500 Subject: [PATCH 166/387] Testsystem: allow skipping tests that require Internet with `--skip internet_required` (#47914) --- test/choosetests.jl | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/test/choosetests.jl b/test/choosetests.jl index 334ef051a0fe60..23b3ab8dd342a9 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -31,6 +31,19 @@ const TESTNAMES = [ "smallarrayshrink", "opaque_closure", "filesystem", "download", ] +const INTERNET_REQUIRED_LIST = [ + "Artifacts", + "Downloads", + "LazyArtifacts", + "LibCURL", + "LibGit2", + "Pkg", + "TOML", + "download", +] + +const NETWORK_REQUIRED_LIST = vcat(INTERNET_REQUIRED_LIST, ["Sockets"]) + """ `(; tests, net_on, exit_on_error, seed) = choosetests(choices)` selects a set of tests to be run. `choices` should be a vector of test names; if empty or set to @@ -149,6 +162,7 @@ function choosetests(choices = []) filtertests!(tests, "compiler/EscapeAnalysis", [ "compiler/EscapeAnalysis/local", "compiler/EscapeAnalysis/interprocedural"]) filtertests!(tests, "stdlib", STDLIBS) + filtertests!(tests, "internet_required", INTERNET_REQUIRED_LIST) # do ambiguous first to avoid failing if ambiguities are introduced by other tests filtertests!(tests, "ambiguous") @@ -164,16 +178,7 @@ function choosetests(choices = []) filter!(x -> x != "rounding", tests) end - net_required_for = filter!(in(tests), [ - "Artifacts", - "Downloads", - "LazyArtifacts", - "LibCURL", - "LibGit2", - "Sockets", - "download", - "TOML", - ]) + net_required_for = filter!(in(tests), NETWORK_REQUIRED_LIST) net_on = true JULIA_TEST_NETWORKING_AVAILABLE = get(ENV, "JULIA_TEST_NETWORKING_AVAILABLE", "") |> strip |> From 5bd016081d8688b0c68d970c8575097ee3ec9886 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Mon, 26 Dec 2022 20:45:37 +0900 Subject: [PATCH 167/387] minor lattice fix on `egal_tfunc` (#47998) --- base/compiler/tfuncs.jl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 59f142343be10e..1a0e61e3453f9a 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -295,18 +295,19 @@ end end return egal_tfunc(widenlattice(𝕃), x, y) end -@nospecs function egal_tfunc(::ConstsLattice, x, y) +@nospecs function egal_tfunc(𝕃::ConstsLattice, x, y) if isa(x, Const) && isa(y, Const) return Const(x.val === y.val) - elseif !hasintersect(widenconst(x), widenconst(y)) - return Const(false) elseif (isa(x, Const) && y === typeof(x.val) && issingletontype(x)) || (isa(y, Const) && x === typeof(y.val) && issingletontype(y)) return Const(true) end + return egal_tfunc(widenlattice(𝕃), x, y) +end +@nospecs function egal_tfunc(::JLTypeLattice, x, y) + hasintersect(widenconst(x), widenconst(y)) || return Const(false) return Bool end -@nospecs egal_tfunc(::JLTypeLattice, x, y) = Bool add_tfunc(===, 2, 2, egal_tfunc, 1) @nospecs function isdefined_nothrow(𝕃::AbstractLattice, x, name) From 5c493c5854600991d8deee52fac3cd5bae759d74 Mon Sep 17 00:00:00 2001 From: CodeReclaimers Date: Mon, 26 Dec 2022 11:58:00 -0500 Subject: [PATCH 168/387] Fix reference to the wrong parse function (#48002) Reference linked to Base.parse instead of Meta.parse --- doc/src/manual/metaprogramming.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/manual/metaprogramming.md b/doc/src/manual/metaprogramming.md index f7d84296eaeefc..2d7deae0f1c549 100644 --- a/doc/src/manual/metaprogramming.md +++ b/doc/src/manual/metaprogramming.md @@ -440,7 +440,7 @@ value 1 and the variable `b`. Note the important distinction between the way `a` As hinted above, one extremely useful feature of Julia is the capability to generate and manipulate Julia code within Julia itself. We have already seen one example of a function returning [`Expr`](@ref) -objects: the [`parse`](@ref) function, which takes a string of Julia code and returns the corresponding +objects: the [`Meta.parse`](@ref) function, which takes a string of Julia code and returns the corresponding `Expr`. A function can also take one or more `Expr` objects as arguments, and return another `Expr`. Here is a simple, motivating example: From 2d0b640369eb881eff9cc1419d479f9875e4ac7e Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Tue, 27 Dec 2022 16:03:53 +0900 Subject: [PATCH 169/387] allow external `AbstractLattice` to customize intrinsic tfuncs (#48000) This would be useful for `AbstractInterpreter`-based static analyzers that use custom `AbstractLattice`. --- base/compiler/tfuncs.jl | 110 +++++++++++++++++++++++++++------------- 1 file changed, 74 insertions(+), 36 deletions(-) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 1a0e61e3453f9a..4370b38c4a6865 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -141,25 +141,35 @@ function instanceof_tfunc(@nospecialize(t)) end return Any, false, false, false end -@nospecs bitcast_tfunc(𝕃::AbstractLattice, t, x) = instanceof_tfunc(t)[1] -@nospecs math_tfunc(𝕃::AbstractLattice, x) = widenconst(x) -@nospecs math_tfunc(𝕃::AbstractLattice, x, y) = widenconst(x) -@nospecs math_tfunc(𝕃::AbstractLattice, x, y, z) = widenconst(x) -@nospecs fptoui_tfunc(𝕃::AbstractLattice, t, x) = bitcast_tfunc(𝕃, t, x) -@nospecs fptosi_tfunc(𝕃::AbstractLattice, t, x) = bitcast_tfunc(𝕃, t, x) - - ## conversion ## + +# IntrinsicFunction +# ================= + +# conversion +# ---------- + +@nospecs bitcast_tfunc(𝕃::AbstractLattice, t, x) = bitcast_tfunc(widenlattice(𝕃), t, x) +@nospecs bitcast_tfunc(::JLTypeLattice, t, x) = instanceof_tfunc(t)[1] +@nospecs conversion_tfunc(𝕃::AbstractLattice, t, x) = conversion_tfunc(widenlattice(𝕃), t, x) +@nospecs conversion_tfunc(::JLTypeLattice, t, x) = instanceof_tfunc(t)[1] + add_tfunc(bitcast, 2, 2, bitcast_tfunc, 1) -add_tfunc(sext_int, 2, 2, bitcast_tfunc, 1) -add_tfunc(zext_int, 2, 2, bitcast_tfunc, 1) -add_tfunc(trunc_int, 2, 2, bitcast_tfunc, 1) -add_tfunc(fptoui, 2, 2, fptoui_tfunc, 1) -add_tfunc(fptosi, 2, 2, fptosi_tfunc, 1) -add_tfunc(uitofp, 2, 2, bitcast_tfunc, 1) -add_tfunc(sitofp, 2, 2, bitcast_tfunc, 1) -add_tfunc(fptrunc, 2, 2, bitcast_tfunc, 1) -add_tfunc(fpext, 2, 2, bitcast_tfunc, 1) - ## arithmetic ## +add_tfunc(sext_int, 2, 2, conversion_tfunc, 1) +add_tfunc(zext_int, 2, 2, conversion_tfunc, 1) +add_tfunc(trunc_int, 2, 2, conversion_tfunc, 1) +add_tfunc(fptoui, 2, 2, conversion_tfunc, 1) +add_tfunc(fptosi, 2, 2, conversion_tfunc, 1) +add_tfunc(uitofp, 2, 2, conversion_tfunc, 1) +add_tfunc(sitofp, 2, 2, conversion_tfunc, 1) +add_tfunc(fptrunc, 2, 2, conversion_tfunc, 1) +add_tfunc(fpext, 2, 2, conversion_tfunc, 1) + +# arithmetic +# ---------- + +@nospecs math_tfunc(𝕃::AbstractLattice, args...) = math_tfunc(widenlattice(𝕃), args...) +@nospecs math_tfunc(::JLTypeLattice, x, xs...) = widenconst(x) + add_tfunc(neg_int, 1, 1, math_tfunc, 1) add_tfunc(add_int, 2, 2, math_tfunc, 1) add_tfunc(sub_int, 2, 2, math_tfunc, 1) @@ -178,21 +188,28 @@ add_tfunc(div_float, 2, 2, math_tfunc, 20) add_tfunc(rem_float, 2, 2, math_tfunc, 20) add_tfunc(fma_float, 3, 3, math_tfunc, 5) add_tfunc(muladd_float, 3, 3, math_tfunc, 5) - ## fast arithmetic ## + +# fast arithmetic add_tfunc(neg_float_fast, 1, 1, math_tfunc, 1) add_tfunc(add_float_fast, 2, 2, math_tfunc, 1) add_tfunc(sub_float_fast, 2, 2, math_tfunc, 1) add_tfunc(mul_float_fast, 2, 2, math_tfunc, 2) add_tfunc(div_float_fast, 2, 2, math_tfunc, 10) add_tfunc(rem_float_fast, 2, 2, math_tfunc, 10) - ## bitwise operators ## + +# bitwise operators +# ----------------- + +@nospecs shift_tfunc(𝕃::AbstractLattice, x, y) = shift_tfunc(widenlattice(𝕃), x, y) +@nospecs shift_tfunc(::JLTypeLattice, x, y) = widenconst(x) + add_tfunc(and_int, 2, 2, math_tfunc, 1) add_tfunc(or_int, 2, 2, math_tfunc, 1) add_tfunc(xor_int, 2, 2, math_tfunc, 1) add_tfunc(not_int, 1, 1, math_tfunc, 0) # usually used as not_int(::Bool) to negate a condition -add_tfunc(shl_int, 2, 2, math_tfunc, 1) -add_tfunc(lshr_int, 2, 2, math_tfunc, 1) -add_tfunc(ashr_int, 2, 2, math_tfunc, 1) +add_tfunc(shl_int, 2, 2, shift_tfunc, 1) +add_tfunc(lshr_int, 2, 2, shift_tfunc, 1) +add_tfunc(ashr_int, 2, 2, shift_tfunc, 1) add_tfunc(bswap_int, 1, 1, math_tfunc, 1) add_tfunc(ctpop_int, 1, 1, math_tfunc, 1) add_tfunc(ctlz_int, 1, 1, math_tfunc, 1) @@ -201,7 +218,10 @@ add_tfunc(checked_sdiv_int, 2, 2, math_tfunc, 40) add_tfunc(checked_udiv_int, 2, 2, math_tfunc, 40) add_tfunc(checked_srem_int, 2, 2, math_tfunc, 40) add_tfunc(checked_urem_int, 2, 2, math_tfunc, 40) - ## functions ## + +# functions +# --------- + add_tfunc(abs_float, 1, 1, math_tfunc, 2) add_tfunc(copysign_float, 2, 2, math_tfunc, 2) add_tfunc(flipsign_int, 2, 2, math_tfunc, 1) @@ -211,8 +231,13 @@ add_tfunc(trunc_llvm, 1, 1, math_tfunc, 10) add_tfunc(rint_llvm, 1, 1, math_tfunc, 10) add_tfunc(sqrt_llvm, 1, 1, math_tfunc, 20) add_tfunc(sqrt_llvm_fast, 1, 1, math_tfunc, 20) - ## same-type comparisons ## -@nospecs cmp_tfunc(𝕃::AbstractLattice, x, y) = Bool + +# comparisons +# ----------- + +@nospecs cmp_tfunc(𝕃::AbstractLattice, a, b) = cmp_tfunc(widenlattice(𝕃), a, b) +@nospecs cmp_tfunc(::JLTypeLattice, a, b) = Bool + add_tfunc(eq_int, 2, 2, cmp_tfunc, 1) add_tfunc(ne_int, 2, 2, cmp_tfunc, 1) add_tfunc(slt_int, 2, 2, cmp_tfunc, 1) @@ -229,28 +254,40 @@ add_tfunc(ne_float_fast, 2, 2, cmp_tfunc, 1) add_tfunc(lt_float_fast, 2, 2, cmp_tfunc, 1) add_tfunc(le_float_fast, 2, 2, cmp_tfunc, 1) - ## checked arithmetic ## -@nospecs chk_tfunc(𝕃::AbstractLattice, x, y) = Tuple{widenconst(x), Bool} +# checked arithmetic +# ------------------ + +@nospecs chk_tfunc(𝕃::AbstractLattice, x, y) = chk_tfunc(widenlattice(𝕃), x, y) +@nospecs chk_tfunc(::JLTypeLattice, x, y) = Tuple{widenconst(x), Bool} + add_tfunc(checked_sadd_int, 2, 2, chk_tfunc, 10) add_tfunc(checked_uadd_int, 2, 2, chk_tfunc, 10) add_tfunc(checked_ssub_int, 2, 2, chk_tfunc, 10) add_tfunc(checked_usub_int, 2, 2, chk_tfunc, 10) add_tfunc(checked_smul_int, 2, 2, chk_tfunc, 10) add_tfunc(checked_umul_int, 2, 2, chk_tfunc, 10) - ## other, misc intrinsics ## + +# other, misc +# ----------- + @nospecs function llvmcall_tfunc(𝕃::AbstractLattice, fptr, rt, at, a...) return instanceof_tfunc(rt)[1] end add_tfunc(Core.Intrinsics.llvmcall, 3, INT_INF, llvmcall_tfunc, 10) + @nospecs cglobal_tfunc(𝕃::AbstractLattice, fptr) = Ptr{Cvoid} @nospecs function cglobal_tfunc(𝕃::AbstractLattice, fptr, t) isa(t, Const) && return isa(t.val, Type) ? Ptr{t.val} : Ptr return isType(t) ? Ptr{t.parameters[1]} : Ptr end add_tfunc(Core.Intrinsics.cglobal, 1, 2, cglobal_tfunc, 5) + add_tfunc(Core.Intrinsics.have_fma, 1, 1, @nospecs((𝕃::AbstractLattice, x)->Bool), 1) add_tfunc(Core.Intrinsics.arraylen, 1, 1, @nospecs((𝕃::AbstractLattice, x)->Int), 4) +# builtin functions +# ================= + @nospecs function ifelse_tfunc(𝕃::AbstractLattice, cnd, x, y) cnd = widenslotwrapper(cnd) if isa(cnd, Const) @@ -2306,16 +2343,17 @@ function intrinsic_nothrow(f::IntrinsicFunction, argtypes::Vector{Any}) f === Intrinsics.llvmcall && return false if f === Intrinsics.checked_udiv_int || f === Intrinsics.checked_urem_int || f === Intrinsics.checked_srem_int || f === Intrinsics.checked_sdiv_int # Nothrow as long as the second argument is guaranteed not to be zero - isa(argtypes[2], Const) || return false - if !isprimitivetype(widenconst(argtypes[1])) || - (widenconst(argtypes[1]) !== widenconst(argtypes[2])) - return false - end - den_val = argtypes[2].val + arg2 = argtypes[2] + isa(arg2, Const) || return false + arg1 = argtypes[1] + warg1 = widenconst(arg1) + warg2 = widenconst(arg2) + (warg1 === warg2 && isprimitivetype(warg1)) || return false + den_val = arg2.val _iszero(den_val) && return false f !== Intrinsics.checked_sdiv_int && return true # Nothrow as long as we additionally don't do typemin(T)/-1 - return !_isneg1(den_val) || (isa(argtypes[1], Const) && !_istypemin(argtypes[1].val)) + return !_isneg1(den_val) || (isa(arg1, Const) && !_istypemin(arg1.val)) end if f === Intrinsics.pointerref # Nothrow as long as the types are ok. N.B.: dereferencability is not From 3ff718650875b3783176d6bdc5e7b271ec573561 Mon Sep 17 00:00:00 2001 From: Chris Elrod Date: Tue, 27 Dec 2022 05:26:37 -0500 Subject: [PATCH 170/387] Define `gt_fast` and `ge_fast` (#47972) The important thing here is that `>` is used in `max_fast`, `min_fast`, and `minmax_fast`. These functions did not SIMD in reductions because `>` didn't have an associated fast op. This PR fixes that. --- base/fastmath.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/base/fastmath.jl b/base/fastmath.jl index 05a5ce0503e689..5f905b86554f44 100644 --- a/base/fastmath.jl +++ b/base/fastmath.jl @@ -41,6 +41,8 @@ const fast_op = :!= => :ne_fast, :< => :lt_fast, :<= => :le_fast, + :> => :gt_fast, + :>= => :ge_fast, :abs => :abs_fast, :abs2 => :abs2_fast, :cmp => :cmp_fast, @@ -182,6 +184,8 @@ eq_fast(x::T, y::T) where {T<:FloatTypes} = eq_float_fast(x, y) ne_fast(x::T, y::T) where {T<:FloatTypes} = ne_float_fast(x, y) lt_fast(x::T, y::T) where {T<:FloatTypes} = lt_float_fast(x, y) le_fast(x::T, y::T) where {T<:FloatTypes} = le_float_fast(x, y) +gt_fast(x, y) = lt_fast(y, x) +ge_fast(x, y) = le_fast(y, x) isinf_fast(x) = false isfinite_fast(x) = true From a9e0545969bb76f33fe9ad9bcf52180caa1651b9 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Tue, 27 Dec 2022 06:16:34 -0500 Subject: [PATCH 171/387] compiler: Use correct method to construct empty NamedTuple (#48005) The `NamedTuple(())` method is not available in the inference world age. Empty named tuples needs to be constructed with `NamedTuple()`. This was causing the Diffractor tests to error. --- base/compiler/abstractinterpretation.jl | 2 +- test/compiler/inference.jl | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 96b8ded9c73a8e..c3dfc0eeb22d2b 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2447,7 +2447,7 @@ function refine_partial_type(@nospecialize t) # if the first/second parameter of `NamedTuple` is known to be empty, # the second/first argument should also be empty tuple type, # so refine it here - return Const(NamedTuple(())) + return Const(NamedTuple()) end return t end diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index f416aa5f13a92e..f24124f76e3c7b 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -4675,13 +4675,17 @@ bar47688() = foo47688() @test it_count47688 == 14 # refine instantiation of partially-known NamedTuple that is known to be empty -@test Base.return_types((Any,)) do Tpl +function empty_nt_values(Tpl) T = NamedTuple{(),Tpl} nt = T(()) values(nt) -end |> only === Tuple{} -@test Base.return_types((Any,)) do tpl - T = NamedTuple{tpl,Tuple{}} +end +function empty_nt_keys(Tpl) + T = NamedTuple{(),Tpl} nt = T(()) keys(nt) -end |> only === Tuple{} +end +@test Base.return_types(empty_nt_values, (Any,)) |> only === Tuple{} +@test Base.return_types(empty_nt_keys, (Any,)) |> only === Tuple{} +g() = empty_nt_values(Base.inferencebarrier(Tuple{})) +@test g() == () # Make sure to actually run this to test this in the inference world age From a2ead37940d4650588c09b1f07ca5848ce1ba416 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Sun, 25 Dec 2022 19:00:07 +0900 Subject: [PATCH 172/387] improve semi-concrete interpretation accuracy, take 1 Currently semi-concrete interpretation can end up with more inaccurate result than usual abstract interpretation because `src.ssavaluetypes` are all widened when cached so semi-concrete interpretation can't use extended lattice information of `SSAValue`s. This commit tries to fix it by making `CodeInstance` keep extended lattice information of `SSAValue`s that are widened and allowing semi-concrete interpretation to override `src.ssavaluetypes` when it uncompressed `src` from `CodeInstance`. I found there are other chances when semi-concrete interpretation can end up with inaccurate results, but that is unrelated to this and should be fixed separately. --- base/boot.jl | 9 +++++---- base/compiler/optimize.jl | 18 ++++++++++++++---- base/compiler/ssair/irinterp.jl | 7 +++++++ base/compiler/typeinfer.jl | 12 +++++------- base/compiler/types.jl | 32 +++++++++++++++++++++----------- src/gf.c | 22 ++++++++-------------- src/jltypes.c | 12 +++++++----- src/julia.h | 7 +++++++ src/opaque_closure.c | 9 +-------- test/core.jl | 2 +- 10 files changed, 76 insertions(+), 54 deletions(-) diff --git a/base/boot.jl b/base/boot.jl index 33b2cd07688ad6..e13e4685f00475 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -425,13 +425,14 @@ eval(Core, quote function CodeInstance( mi::MethodInstance, @nospecialize(rettype), @nospecialize(inferred_const), @nospecialize(inferred), const_flags::Int32, min_world::UInt, max_world::UInt, - ipo_effects::UInt32, effects::UInt32, @nospecialize(argescapes#=::Union{Nothing,Vector{ArgEscapeInfo}}=#), + ipo_effects::UInt32, effects::UInt32, + @nospecialize(overrides#=::Union{Nothing,Vector{SSAValueTypeOverride}}=#), + @nospecialize(argescapes#=::Union{Nothing,Vector{ArgEscapeInfo}}=#), relocatability::UInt8) return ccall(:jl_new_codeinst, Ref{CodeInstance}, - (Any, Any, Any, Any, Int32, UInt, UInt, UInt32, UInt32, Any, UInt8), + (Any, Any, Any, Any, Int32, UInt, UInt, UInt32, UInt32, Any, Any, UInt8), mi, rettype, inferred_const, inferred, const_flags, min_world, max_world, - ipo_effects, effects, argescapes, - relocatability) + ipo_effects, effects, overrides, argescapes, relocatability) end Const(@nospecialize(v)) = $(Expr(:new, :Const, :v)) # NOTE the main constructor is defined within `Core.Compiler` diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index 98d48909a5e24a..e6fdef412c782d 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -179,18 +179,28 @@ function ir_to_codeinf!(opt::OptimizationState) optdef = linfo.def replace_code_newstyle!(src, opt.ir::IRCode, isa(optdef, Method) ? Int(optdef.nargs) : 0) opt.ir = nothing - widen_all_consts!(src) + overrides = widen_all_consts!(src) src.inferred = true # finish updating the result struct validate_code_in_debug_mode(linfo, src, "optimized") - return src + return src, overrides end # widen all Const elements in type annotations function widen_all_consts!(src::CodeInfo) + local overrides = nothing + ssavaluetypes = src.ssavaluetypes::Vector{Any} for i = 1:length(ssavaluetypes) - ssavaluetypes[i] = widenconst(ssavaluetypes[i]) + extended = ssavaluetypes[i] + widened = widenconst(extended) + if widened !== extended + ssavaluetypes[i] = widened + if overrides === nothing + overrides = SSAValueTypeOverride[] + end + push!(overrides, SSAValueTypeOverride(i, extended)) + end end for i = 1:length(src.code) @@ -202,7 +212,7 @@ function widen_all_consts!(src::CodeInfo) src.rettype = widenconst(src.rettype) - return src + return overrides end ######### diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index a2a5deccba838c..eb4730d3d88647 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -126,6 +126,13 @@ function codeinst_to_ir(interp::AbstractInterpreter, code::CodeInstance) else isa(src, CodeInfo) || return nothing end + # override `ssavaluetypes` with extended lattice information + overrides = code.overrides + if isa(overrides, SSAValueTypeOverrides) + for (; idx, typ) = overrides + src.ssavaluetypes[idx] = typ + end + end return inflate_ir(src, mi) end diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 29c4a7e6e477a3..c2782599e362d7 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -223,7 +223,7 @@ function finish!(interp::AbstractInterpreter, caller::InferenceResult) if opt isa OptimizationState{typeof(interp)} # implies `may_optimize(interp) === true` if opt.ir !== nothing if caller.must_be_codeinf - caller.src = ir_to_codeinf!(opt) + caller.src, caller.overrides = ir_to_codeinf!(opt) elseif is_inlineable(opt.src) # TODO: If the CFG is too big, inlining becomes more expensive and if we're going to # use this IR over and over, it's worth simplifying it. Round trips through @@ -274,7 +274,7 @@ function _typeinf(interp::AbstractInterpreter, frame::InferenceState) # we're doing it is so that code_llvm can return the code # for the `return ...::Const` (which never runs anyway). We should do this # as a post processing step instead. - ir_to_codeinf!(opt) + _, caller.overrides = ir_to_codeinf!(opt) caller.src = analyzed end caller.valid_worlds = (opt.inlining.et::EdgeTracker).valid_worlds[] @@ -336,8 +336,8 @@ function CodeInstance( widenconst(result_type), rettype_const, inferred_result, const_flags, first(valid_worlds), last(valid_worlds), # TODO: Actually do something with non-IPO effects - encode_effects(result.ipo_effects), encode_effects(result.ipo_effects), result.argescapes, - relocatability) + encode_effects(result.ipo_effects), encode_effects(result.ipo_effects), + result.overrides, result.argescapes, relocatability) end function maybe_compress_codeinfo(interp::AbstractInterpreter, linfo::MethodInstance, ci::CodeInfo) @@ -368,10 +368,8 @@ end function transform_result_for_cache(interp::AbstractInterpreter, linfo::MethodInstance, valid_worlds::WorldRange, result::InferenceResult) inferred_result = result.src - # If we decided not to optimize, drop the OptimizationState now. - # External interpreters can override as necessary to cache additional information if inferred_result isa OptimizationState{typeof(interp)} - inferred_result = ir_to_codeinf!(inferred_result) + inferred_result, result.overrides = ir_to_codeinf!(inferred_result) end if inferred_result isa CodeInfo inferred_result.min_world = first(valid_worlds) diff --git a/base/compiler/types.jl b/base/compiler/types.jl index 1514b3f101a60f..876ad902e75235 100644 --- a/base/compiler/types.jl +++ b/base/compiler/types.jl @@ -31,6 +31,15 @@ struct StmtInfo used::Bool end +struct SSAValueTypeOverride + idx::Int + typ + SSAValueTypeOverride(idx::Int, @nospecialize(typ)) = new(idx, typ) +end + +const SSAValueTypeOverrides = Vector{SSAValueTypeOverride} +const MaybeOverrides = Union{Nothing,SSAValueTypeOverrides} + abstract type ForwardableArgtypes end """ @@ -42,19 +51,20 @@ A type that represents the result of running type inference on a chunk of code. See also [`matching_cache_argtypes`](@ref). """ mutable struct InferenceResult - linfo::MethodInstance - argtypes::Vector{Any} - overridden_by_const::BitVector - result # ::Type, or InferenceState if WIP - src # ::Union{CodeInfo, IRCode, OptimizationState} if inferred copy is available, nothing otherwise - valid_worlds::WorldRange # if inference and optimization is finished - ipo_effects::Effects # if inference is finished - effects::Effects # if optimization is finished - argescapes # ::ArgEscapeCache if optimized, nothing otherwise - must_be_codeinf::Bool # if this must come out as CodeInfo or leaving it as IRCode is ok + const linfo::MethodInstance + const argtypes::Vector{Any} + const overridden_by_const::BitVector + result # ::Type, or InferenceState if WIP + src # ::Union{CodeInfo, IRCode, OptimizationState} if inferred copy is available, nothing otherwise + valid_worlds::WorldRange # if inference and optimization is finished + ipo_effects::Effects # if inference is finished + effects::Effects # if optimization is finished + overrides::MaybeOverrides # ::Vector{SSAValueTypeOverride} if optimized, nothing otherwise + argescapes # ::ArgEscapeCache if optimized, nothing otherwise + must_be_codeinf::Bool # if this must come out as CodeInfo or leaving it as IRCode is ok function InferenceResult(linfo::MethodInstance, cache_argtypes::Vector{Any}, overridden_by_const::BitVector) return new(linfo, cache_argtypes, overridden_by_const, Any, nothing, - WorldRange(), Effects(), Effects(), nothing, true) + WorldRange(), Effects(), Effects(), nothing, nothing, true) end end function InferenceResult(linfo::MethodInstance) diff --git a/src/gf.c b/src/gf.c index 99c482420e2f27..ffadac730dbdfa 100644 --- a/src/gf.c +++ b/src/gf.c @@ -216,13 +216,6 @@ JL_DLLEXPORT jl_value_t *jl_methtable_lookup(jl_methtable_t *mt, jl_value_t *typ // ----- MethodInstance specialization instantiation ----- // -JL_DLLEXPORT jl_code_instance_t* jl_new_codeinst( - jl_method_instance_t *mi, jl_value_t *rettype, - jl_value_t *inferred_const, jl_value_t *inferred, - int32_t const_flags, size_t min_world, size_t max_world, - uint32_t ipo_effects, uint32_t effects, jl_value_t *argescapes, - uint8_t relocatability); - jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_args_t fptr) JL_GC_DISABLED { jl_sym_t *sname = jl_symbol(name); @@ -256,7 +249,7 @@ jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_a jl_code_instance_t *codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, jl_nothing, jl_nothing, - 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); + 0, 1, ~(size_t)0, 0, 0, jl_nothing, jl_nothing, 0); jl_mi_cache_insert(mi, codeinst); jl_atomic_store_relaxed(&codeinst->specptr.fptr1, fptr); jl_atomic_store_relaxed(&codeinst->invoke, jl_fptr_args); @@ -383,7 +376,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_get_method_inferred( } codeinst = jl_new_codeinst( mi, rettype, NULL, NULL, - 0, min_world, max_world, 0, 0, jl_nothing, 0); + 0, min_world, max_world, 0, 0, jl_nothing, jl_nothing, 0); jl_mi_cache_insert(mi, codeinst); return codeinst; } @@ -392,8 +385,8 @@ JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( jl_method_instance_t *mi, jl_value_t *rettype, jl_value_t *inferred_const, jl_value_t *inferred, int32_t const_flags, size_t min_world, size_t max_world, - uint32_t ipo_effects, uint32_t effects, jl_value_t *argescapes, - uint8_t relocatability + uint32_t ipo_effects, uint32_t effects, + jl_value_t *overrides, jl_value_t *argescapes, uint8_t relocatability /*, jl_array_t *edges, int absolute_max*/) { jl_task_t *ct = jl_current_task; @@ -420,6 +413,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( jl_atomic_store_relaxed(&codeinst->next, NULL); codeinst->ipo_purity_bits = ipo_effects; jl_atomic_store_relaxed(&codeinst->purity_bits, effects); + codeinst->overrides = overrides; codeinst->argescapes = argescapes; codeinst->relocatability = relocatability; return codeinst; @@ -2208,7 +2202,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t if (unspec && jl_atomic_load_acquire(&unspec->invoke)) { jl_code_instance_t *codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, NULL, NULL, - 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); + 0, 1, ~(size_t)0, 0, 0, jl_nothing, jl_nothing, 0); codeinst->isspecsig = 0; codeinst->specptr = unspec->specptr; codeinst->rettype_const = unspec->rettype_const; @@ -2228,7 +2222,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t if (!jl_code_requires_compiler(src, 0)) { jl_code_instance_t *codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, NULL, NULL, - 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); + 0, 1, ~(size_t)0, 0, 0, jl_nothing, jl_nothing, 0); jl_atomic_store_relaxed(&codeinst->invoke, jl_fptr_interpret_call); jl_mi_cache_insert(mi, codeinst); record_precompile_statement(mi); @@ -2263,7 +2257,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t return ucache; } codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, NULL, NULL, - 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); + 0, 1, ~(size_t)0, 0, 0, jl_nothing, jl_nothing, 0); codeinst->isspecsig = 0; codeinst->specptr = ucache->specptr; codeinst->rettype_const = ucache->rettype_const; diff --git a/src/jltypes.c b/src/jltypes.c index 253768ad075036..6ac0b27f89eba3 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -2579,7 +2579,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_code_instance_type = jl_new_datatype(jl_symbol("CodeInstance"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(15, + jl_perm_symsvec(16, "def", "next", "min_world", @@ -2590,10 +2590,11 @@ void jl_init_types(void) JL_GC_DISABLED //"edges", //"absolute_max", "ipo_purity_bits", "purity_bits", + "overrides", "argescapes", "isspecsig", "precompile", "relocatability", "invoke", "specptr"), // function object decls - jl_svec(15, + jl_svec(16, jl_method_instance_type, jl_any_type, jl_ulong_type, @@ -2605,6 +2606,7 @@ void jl_init_types(void) JL_GC_DISABLED //jl_bool_type, jl_uint32_type, jl_uint32_type, jl_any_type, + jl_any_type, jl_bool_type, jl_bool_type, jl_uint8_type, @@ -2612,8 +2614,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_emptysvec, 0, 1, 1); jl_svecset(jl_code_instance_type->types, 1, jl_code_instance_type); - const static uint32_t code_instance_constfields[1] = { 0b000001010110001 }; // Set fields 1, 5-6, 8, 10 as const - const static uint32_t code_instance_atomicfields[1] = { 0b110100101000010 }; // Set fields 2, 7, 9, 12, 14-15 as atomic + const static uint32_t code_instance_constfields[1] = { 0b0000011010110001 }; // Set fields 1, 5-6, 8, 10-11 as const + const static uint32_t code_instance_atomicfields[1] = { 0b1101000101000010 }; // Set fields 2, 7, 9, 13, 15-16 as atomic //Fields 3-4 are only operated on by construction and deserialization, so are const at runtime //Fields 11 and 15 must be protected by locks, and thus all operations on jl_code_instance_t are threadsafe jl_code_instance_type->name->constfields = code_instance_constfields; @@ -2780,8 +2782,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_svecset(jl_methtable_type->types, 10, jl_uint8_type); jl_svecset(jl_method_type->types, 12, jl_method_instance_type); jl_svecset(jl_method_instance_type->types, 6, jl_code_instance_type); - jl_svecset(jl_code_instance_type->types, 13, jl_voidpointer_type); jl_svecset(jl_code_instance_type->types, 14, jl_voidpointer_type); + jl_svecset(jl_code_instance_type->types, 15, jl_voidpointer_type); jl_svecset(jl_binding_type->types, 2, jl_globalref_type); jl_compute_field_offsets(jl_datatype_type); diff --git a/src/julia.h b/src/julia.h index 0164c2e55a4f93..d492705ba67236 100644 --- a/src/julia.h +++ b/src/julia.h @@ -419,6 +419,7 @@ typedef struct _jl_code_instance_t { // uint8_t nonoverlayed : 1; // uint8_t notaskstate : 2; // uint8_t inaccessiblememonly : 2; + jl_value_t *overrides; // override information of `inferred`'s ssavaluetypes jl_value_t *argescapes; // escape information of call arguments // compilation state cache @@ -1474,6 +1475,12 @@ JL_DLLEXPORT jl_value_t *jl_new_structv(jl_datatype_t *type, jl_value_t **args, JL_DLLEXPORT jl_value_t *jl_new_structt(jl_datatype_t *type, jl_value_t *tup); JL_DLLEXPORT jl_value_t *jl_new_struct_uninit(jl_datatype_t *type); JL_DLLEXPORT jl_method_instance_t *jl_new_method_instance_uninit(void); +JL_DLLEXPORT jl_code_instance_t* jl_new_codeinst( + jl_method_instance_t *mi, jl_value_t *rettype, + jl_value_t *inferred_const, jl_value_t *inferred, + int32_t const_flags, size_t min_world, size_t max_world, + uint32_t ipo_effects, uint32_t effects, + jl_value_t *overrrides, jl_value_t *argescapes, uint8_t relocatability); JL_DLLEXPORT jl_svec_t *jl_svec(size_t n, ...) JL_MAYBE_UNROOTED; JL_DLLEXPORT jl_svec_t *jl_svec1(void *a); JL_DLLEXPORT jl_svec_t *jl_svec2(void *a, void *b); diff --git a/src/opaque_closure.c b/src/opaque_closure.c index db596c2bb893f0..2aa9877e435c1a 100644 --- a/src/opaque_closure.c +++ b/src/opaque_closure.c @@ -105,13 +105,6 @@ jl_opaque_closure_t *jl_new_opaque_closure(jl_tupletype_t *argt, jl_value_t *rt_ jl_method_t *jl_make_opaque_closure_method(jl_module_t *module, jl_value_t *name, int nargs, jl_value_t *functionloc, jl_code_info_t *ci, int isva); -JL_DLLEXPORT jl_code_instance_t* jl_new_codeinst( - jl_method_instance_t *mi, jl_value_t *rettype, - jl_value_t *inferred_const, jl_value_t *inferred, - int32_t const_flags, size_t min_world, size_t max_world, - uint32_t ipo_effects, uint32_t effects, jl_value_t *argescapes, - uint8_t relocatability); - JL_DLLEXPORT jl_opaque_closure_t *jl_new_opaque_closure_from_code_info(jl_tupletype_t *argt, jl_value_t *rt_lb, jl_value_t *rt_ub, jl_module_t *mod, jl_code_info_t *ci, int lineno, jl_value_t *file, int nargs, int isva, jl_value_t *env, int do_compile) { @@ -127,7 +120,7 @@ JL_DLLEXPORT jl_opaque_closure_t *jl_new_opaque_closure_from_code_info(jl_tuplet sigtype = prepend_type(jl_typeof(env), argt); jl_method_instance_t *mi = jl_specializations_get_linfo((jl_method_t*)root, sigtype, jl_emptysvec); inst = jl_new_codeinst(mi, rt_ub, NULL, (jl_value_t*)ci, - 0, ((jl_method_t*)root)->primary_world, -1, 0, 0, jl_nothing, 0); + 0, ((jl_method_t*)root)->primary_world, -1, 0, 0, jl_nothing, jl_nothing, 0); jl_mi_cache_insert(mi, inst); jl_opaque_closure_t *oc = new_opaque_closure(argt, rt_lb, rt_ub, root, env, do_compile); diff --git a/test/core.jl b/test/core.jl index 96ec765235adb8..72916ce37d7606 100644 --- a/test/core.jl +++ b/test/core.jl @@ -14,7 +14,7 @@ include("testenv.jl") # sanity tests that our built-in types are marked correctly for const fields for (T, c) in ( (Core.CodeInfo, []), - (Core.CodeInstance, [:def, :rettype, :rettype_const, :ipo_purity_bits, :argescapes]), + (Core.CodeInstance, [:def, :rettype, :rettype_const, :ipo_purity_bits, :overrides, :argescapes]), (Core.Method, [#=:name, :module, :file, :line, :primary_world, :sig, :slot_syms, :external_mt, :nargs, :called, :nospecialize, :nkw, :isva, :pure, :is_for_opaque_closure, :constprop=#]), (Core.MethodInstance, [#=:def, :specTypes, :sparam_vals=#]), (Core.MethodTable, [:module]), From b7e3da653775987346e7408778f502a9f3e80f22 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Mon, 26 Dec 2022 08:56:13 +0900 Subject: [PATCH 173/387] Revert "improve semi-concrete interpretation accuracy" This reverts commit 67d9dc57709b37c8fe17d817c109efd8e2c8188c. --- base/boot.jl | 9 ++++----- base/compiler/optimize.jl | 18 ++++-------------- base/compiler/ssair/irinterp.jl | 7 ------- base/compiler/typeinfer.jl | 12 +++++++----- base/compiler/types.jl | 32 +++++++++++--------------------- src/gf.c | 22 ++++++++++++++-------- src/jltypes.c | 12 +++++------- src/julia.h | 7 ------- src/opaque_closure.c | 9 ++++++++- test/core.jl | 2 +- 10 files changed, 54 insertions(+), 76 deletions(-) diff --git a/base/boot.jl b/base/boot.jl index e13e4685f00475..33b2cd07688ad6 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -425,14 +425,13 @@ eval(Core, quote function CodeInstance( mi::MethodInstance, @nospecialize(rettype), @nospecialize(inferred_const), @nospecialize(inferred), const_flags::Int32, min_world::UInt, max_world::UInt, - ipo_effects::UInt32, effects::UInt32, - @nospecialize(overrides#=::Union{Nothing,Vector{SSAValueTypeOverride}}=#), - @nospecialize(argescapes#=::Union{Nothing,Vector{ArgEscapeInfo}}=#), + ipo_effects::UInt32, effects::UInt32, @nospecialize(argescapes#=::Union{Nothing,Vector{ArgEscapeInfo}}=#), relocatability::UInt8) return ccall(:jl_new_codeinst, Ref{CodeInstance}, - (Any, Any, Any, Any, Int32, UInt, UInt, UInt32, UInt32, Any, Any, UInt8), + (Any, Any, Any, Any, Int32, UInt, UInt, UInt32, UInt32, Any, UInt8), mi, rettype, inferred_const, inferred, const_flags, min_world, max_world, - ipo_effects, effects, overrides, argescapes, relocatability) + ipo_effects, effects, argescapes, + relocatability) end Const(@nospecialize(v)) = $(Expr(:new, :Const, :v)) # NOTE the main constructor is defined within `Core.Compiler` diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index e6fdef412c782d..98d48909a5e24a 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -179,28 +179,18 @@ function ir_to_codeinf!(opt::OptimizationState) optdef = linfo.def replace_code_newstyle!(src, opt.ir::IRCode, isa(optdef, Method) ? Int(optdef.nargs) : 0) opt.ir = nothing - overrides = widen_all_consts!(src) + widen_all_consts!(src) src.inferred = true # finish updating the result struct validate_code_in_debug_mode(linfo, src, "optimized") - return src, overrides + return src end # widen all Const elements in type annotations function widen_all_consts!(src::CodeInfo) - local overrides = nothing - ssavaluetypes = src.ssavaluetypes::Vector{Any} for i = 1:length(ssavaluetypes) - extended = ssavaluetypes[i] - widened = widenconst(extended) - if widened !== extended - ssavaluetypes[i] = widened - if overrides === nothing - overrides = SSAValueTypeOverride[] - end - push!(overrides, SSAValueTypeOverride(i, extended)) - end + ssavaluetypes[i] = widenconst(ssavaluetypes[i]) end for i = 1:length(src.code) @@ -212,7 +202,7 @@ function widen_all_consts!(src::CodeInfo) src.rettype = widenconst(src.rettype) - return overrides + return src end ######### diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index eb4730d3d88647..a2a5deccba838c 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -126,13 +126,6 @@ function codeinst_to_ir(interp::AbstractInterpreter, code::CodeInstance) else isa(src, CodeInfo) || return nothing end - # override `ssavaluetypes` with extended lattice information - overrides = code.overrides - if isa(overrides, SSAValueTypeOverrides) - for (; idx, typ) = overrides - src.ssavaluetypes[idx] = typ - end - end return inflate_ir(src, mi) end diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index c2782599e362d7..29c4a7e6e477a3 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -223,7 +223,7 @@ function finish!(interp::AbstractInterpreter, caller::InferenceResult) if opt isa OptimizationState{typeof(interp)} # implies `may_optimize(interp) === true` if opt.ir !== nothing if caller.must_be_codeinf - caller.src, caller.overrides = ir_to_codeinf!(opt) + caller.src = ir_to_codeinf!(opt) elseif is_inlineable(opt.src) # TODO: If the CFG is too big, inlining becomes more expensive and if we're going to # use this IR over and over, it's worth simplifying it. Round trips through @@ -274,7 +274,7 @@ function _typeinf(interp::AbstractInterpreter, frame::InferenceState) # we're doing it is so that code_llvm can return the code # for the `return ...::Const` (which never runs anyway). We should do this # as a post processing step instead. - _, caller.overrides = ir_to_codeinf!(opt) + ir_to_codeinf!(opt) caller.src = analyzed end caller.valid_worlds = (opt.inlining.et::EdgeTracker).valid_worlds[] @@ -336,8 +336,8 @@ function CodeInstance( widenconst(result_type), rettype_const, inferred_result, const_flags, first(valid_worlds), last(valid_worlds), # TODO: Actually do something with non-IPO effects - encode_effects(result.ipo_effects), encode_effects(result.ipo_effects), - result.overrides, result.argescapes, relocatability) + encode_effects(result.ipo_effects), encode_effects(result.ipo_effects), result.argescapes, + relocatability) end function maybe_compress_codeinfo(interp::AbstractInterpreter, linfo::MethodInstance, ci::CodeInfo) @@ -368,8 +368,10 @@ end function transform_result_for_cache(interp::AbstractInterpreter, linfo::MethodInstance, valid_worlds::WorldRange, result::InferenceResult) inferred_result = result.src + # If we decided not to optimize, drop the OptimizationState now. + # External interpreters can override as necessary to cache additional information if inferred_result isa OptimizationState{typeof(interp)} - inferred_result, result.overrides = ir_to_codeinf!(inferred_result) + inferred_result = ir_to_codeinf!(inferred_result) end if inferred_result isa CodeInfo inferred_result.min_world = first(valid_worlds) diff --git a/base/compiler/types.jl b/base/compiler/types.jl index 876ad902e75235..1514b3f101a60f 100644 --- a/base/compiler/types.jl +++ b/base/compiler/types.jl @@ -31,15 +31,6 @@ struct StmtInfo used::Bool end -struct SSAValueTypeOverride - idx::Int - typ - SSAValueTypeOverride(idx::Int, @nospecialize(typ)) = new(idx, typ) -end - -const SSAValueTypeOverrides = Vector{SSAValueTypeOverride} -const MaybeOverrides = Union{Nothing,SSAValueTypeOverrides} - abstract type ForwardableArgtypes end """ @@ -51,20 +42,19 @@ A type that represents the result of running type inference on a chunk of code. See also [`matching_cache_argtypes`](@ref). """ mutable struct InferenceResult - const linfo::MethodInstance - const argtypes::Vector{Any} - const overridden_by_const::BitVector - result # ::Type, or InferenceState if WIP - src # ::Union{CodeInfo, IRCode, OptimizationState} if inferred copy is available, nothing otherwise - valid_worlds::WorldRange # if inference and optimization is finished - ipo_effects::Effects # if inference is finished - effects::Effects # if optimization is finished - overrides::MaybeOverrides # ::Vector{SSAValueTypeOverride} if optimized, nothing otherwise - argescapes # ::ArgEscapeCache if optimized, nothing otherwise - must_be_codeinf::Bool # if this must come out as CodeInfo or leaving it as IRCode is ok + linfo::MethodInstance + argtypes::Vector{Any} + overridden_by_const::BitVector + result # ::Type, or InferenceState if WIP + src # ::Union{CodeInfo, IRCode, OptimizationState} if inferred copy is available, nothing otherwise + valid_worlds::WorldRange # if inference and optimization is finished + ipo_effects::Effects # if inference is finished + effects::Effects # if optimization is finished + argescapes # ::ArgEscapeCache if optimized, nothing otherwise + must_be_codeinf::Bool # if this must come out as CodeInfo or leaving it as IRCode is ok function InferenceResult(linfo::MethodInstance, cache_argtypes::Vector{Any}, overridden_by_const::BitVector) return new(linfo, cache_argtypes, overridden_by_const, Any, nothing, - WorldRange(), Effects(), Effects(), nothing, nothing, true) + WorldRange(), Effects(), Effects(), nothing, true) end end function InferenceResult(linfo::MethodInstance) diff --git a/src/gf.c b/src/gf.c index ffadac730dbdfa..99c482420e2f27 100644 --- a/src/gf.c +++ b/src/gf.c @@ -216,6 +216,13 @@ JL_DLLEXPORT jl_value_t *jl_methtable_lookup(jl_methtable_t *mt, jl_value_t *typ // ----- MethodInstance specialization instantiation ----- // +JL_DLLEXPORT jl_code_instance_t* jl_new_codeinst( + jl_method_instance_t *mi, jl_value_t *rettype, + jl_value_t *inferred_const, jl_value_t *inferred, + int32_t const_flags, size_t min_world, size_t max_world, + uint32_t ipo_effects, uint32_t effects, jl_value_t *argescapes, + uint8_t relocatability); + jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_args_t fptr) JL_GC_DISABLED { jl_sym_t *sname = jl_symbol(name); @@ -249,7 +256,7 @@ jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_a jl_code_instance_t *codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, jl_nothing, jl_nothing, - 0, 1, ~(size_t)0, 0, 0, jl_nothing, jl_nothing, 0); + 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); jl_mi_cache_insert(mi, codeinst); jl_atomic_store_relaxed(&codeinst->specptr.fptr1, fptr); jl_atomic_store_relaxed(&codeinst->invoke, jl_fptr_args); @@ -376,7 +383,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_get_method_inferred( } codeinst = jl_new_codeinst( mi, rettype, NULL, NULL, - 0, min_world, max_world, 0, 0, jl_nothing, jl_nothing, 0); + 0, min_world, max_world, 0, 0, jl_nothing, 0); jl_mi_cache_insert(mi, codeinst); return codeinst; } @@ -385,8 +392,8 @@ JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( jl_method_instance_t *mi, jl_value_t *rettype, jl_value_t *inferred_const, jl_value_t *inferred, int32_t const_flags, size_t min_world, size_t max_world, - uint32_t ipo_effects, uint32_t effects, - jl_value_t *overrides, jl_value_t *argescapes, uint8_t relocatability + uint32_t ipo_effects, uint32_t effects, jl_value_t *argescapes, + uint8_t relocatability /*, jl_array_t *edges, int absolute_max*/) { jl_task_t *ct = jl_current_task; @@ -413,7 +420,6 @@ JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( jl_atomic_store_relaxed(&codeinst->next, NULL); codeinst->ipo_purity_bits = ipo_effects; jl_atomic_store_relaxed(&codeinst->purity_bits, effects); - codeinst->overrides = overrides; codeinst->argescapes = argescapes; codeinst->relocatability = relocatability; return codeinst; @@ -2202,7 +2208,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t if (unspec && jl_atomic_load_acquire(&unspec->invoke)) { jl_code_instance_t *codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, NULL, NULL, - 0, 1, ~(size_t)0, 0, 0, jl_nothing, jl_nothing, 0); + 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); codeinst->isspecsig = 0; codeinst->specptr = unspec->specptr; codeinst->rettype_const = unspec->rettype_const; @@ -2222,7 +2228,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t if (!jl_code_requires_compiler(src, 0)) { jl_code_instance_t *codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, NULL, NULL, - 0, 1, ~(size_t)0, 0, 0, jl_nothing, jl_nothing, 0); + 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); jl_atomic_store_relaxed(&codeinst->invoke, jl_fptr_interpret_call); jl_mi_cache_insert(mi, codeinst); record_precompile_statement(mi); @@ -2257,7 +2263,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t return ucache; } codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, NULL, NULL, - 0, 1, ~(size_t)0, 0, 0, jl_nothing, jl_nothing, 0); + 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); codeinst->isspecsig = 0; codeinst->specptr = ucache->specptr; codeinst->rettype_const = ucache->rettype_const; diff --git a/src/jltypes.c b/src/jltypes.c index 6ac0b27f89eba3..253768ad075036 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -2579,7 +2579,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_code_instance_type = jl_new_datatype(jl_symbol("CodeInstance"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(16, + jl_perm_symsvec(15, "def", "next", "min_world", @@ -2590,11 +2590,10 @@ void jl_init_types(void) JL_GC_DISABLED //"edges", //"absolute_max", "ipo_purity_bits", "purity_bits", - "overrides", "argescapes", "isspecsig", "precompile", "relocatability", "invoke", "specptr"), // function object decls - jl_svec(16, + jl_svec(15, jl_method_instance_type, jl_any_type, jl_ulong_type, @@ -2606,7 +2605,6 @@ void jl_init_types(void) JL_GC_DISABLED //jl_bool_type, jl_uint32_type, jl_uint32_type, jl_any_type, - jl_any_type, jl_bool_type, jl_bool_type, jl_uint8_type, @@ -2614,8 +2612,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_emptysvec, 0, 1, 1); jl_svecset(jl_code_instance_type->types, 1, jl_code_instance_type); - const static uint32_t code_instance_constfields[1] = { 0b0000011010110001 }; // Set fields 1, 5-6, 8, 10-11 as const - const static uint32_t code_instance_atomicfields[1] = { 0b1101000101000010 }; // Set fields 2, 7, 9, 13, 15-16 as atomic + const static uint32_t code_instance_constfields[1] = { 0b000001010110001 }; // Set fields 1, 5-6, 8, 10 as const + const static uint32_t code_instance_atomicfields[1] = { 0b110100101000010 }; // Set fields 2, 7, 9, 12, 14-15 as atomic //Fields 3-4 are only operated on by construction and deserialization, so are const at runtime //Fields 11 and 15 must be protected by locks, and thus all operations on jl_code_instance_t are threadsafe jl_code_instance_type->name->constfields = code_instance_constfields; @@ -2782,8 +2780,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_svecset(jl_methtable_type->types, 10, jl_uint8_type); jl_svecset(jl_method_type->types, 12, jl_method_instance_type); jl_svecset(jl_method_instance_type->types, 6, jl_code_instance_type); + jl_svecset(jl_code_instance_type->types, 13, jl_voidpointer_type); jl_svecset(jl_code_instance_type->types, 14, jl_voidpointer_type); - jl_svecset(jl_code_instance_type->types, 15, jl_voidpointer_type); jl_svecset(jl_binding_type->types, 2, jl_globalref_type); jl_compute_field_offsets(jl_datatype_type); diff --git a/src/julia.h b/src/julia.h index d492705ba67236..0164c2e55a4f93 100644 --- a/src/julia.h +++ b/src/julia.h @@ -419,7 +419,6 @@ typedef struct _jl_code_instance_t { // uint8_t nonoverlayed : 1; // uint8_t notaskstate : 2; // uint8_t inaccessiblememonly : 2; - jl_value_t *overrides; // override information of `inferred`'s ssavaluetypes jl_value_t *argescapes; // escape information of call arguments // compilation state cache @@ -1475,12 +1474,6 @@ JL_DLLEXPORT jl_value_t *jl_new_structv(jl_datatype_t *type, jl_value_t **args, JL_DLLEXPORT jl_value_t *jl_new_structt(jl_datatype_t *type, jl_value_t *tup); JL_DLLEXPORT jl_value_t *jl_new_struct_uninit(jl_datatype_t *type); JL_DLLEXPORT jl_method_instance_t *jl_new_method_instance_uninit(void); -JL_DLLEXPORT jl_code_instance_t* jl_new_codeinst( - jl_method_instance_t *mi, jl_value_t *rettype, - jl_value_t *inferred_const, jl_value_t *inferred, - int32_t const_flags, size_t min_world, size_t max_world, - uint32_t ipo_effects, uint32_t effects, - jl_value_t *overrrides, jl_value_t *argescapes, uint8_t relocatability); JL_DLLEXPORT jl_svec_t *jl_svec(size_t n, ...) JL_MAYBE_UNROOTED; JL_DLLEXPORT jl_svec_t *jl_svec1(void *a); JL_DLLEXPORT jl_svec_t *jl_svec2(void *a, void *b); diff --git a/src/opaque_closure.c b/src/opaque_closure.c index 2aa9877e435c1a..db596c2bb893f0 100644 --- a/src/opaque_closure.c +++ b/src/opaque_closure.c @@ -105,6 +105,13 @@ jl_opaque_closure_t *jl_new_opaque_closure(jl_tupletype_t *argt, jl_value_t *rt_ jl_method_t *jl_make_opaque_closure_method(jl_module_t *module, jl_value_t *name, int nargs, jl_value_t *functionloc, jl_code_info_t *ci, int isva); +JL_DLLEXPORT jl_code_instance_t* jl_new_codeinst( + jl_method_instance_t *mi, jl_value_t *rettype, + jl_value_t *inferred_const, jl_value_t *inferred, + int32_t const_flags, size_t min_world, size_t max_world, + uint32_t ipo_effects, uint32_t effects, jl_value_t *argescapes, + uint8_t relocatability); + JL_DLLEXPORT jl_opaque_closure_t *jl_new_opaque_closure_from_code_info(jl_tupletype_t *argt, jl_value_t *rt_lb, jl_value_t *rt_ub, jl_module_t *mod, jl_code_info_t *ci, int lineno, jl_value_t *file, int nargs, int isva, jl_value_t *env, int do_compile) { @@ -120,7 +127,7 @@ JL_DLLEXPORT jl_opaque_closure_t *jl_new_opaque_closure_from_code_info(jl_tuplet sigtype = prepend_type(jl_typeof(env), argt); jl_method_instance_t *mi = jl_specializations_get_linfo((jl_method_t*)root, sigtype, jl_emptysvec); inst = jl_new_codeinst(mi, rt_ub, NULL, (jl_value_t*)ci, - 0, ((jl_method_t*)root)->primary_world, -1, 0, 0, jl_nothing, jl_nothing, 0); + 0, ((jl_method_t*)root)->primary_world, -1, 0, 0, jl_nothing, 0); jl_mi_cache_insert(mi, inst); jl_opaque_closure_t *oc = new_opaque_closure(argt, rt_lb, rt_ub, root, env, do_compile); diff --git a/test/core.jl b/test/core.jl index 72916ce37d7606..96ec765235adb8 100644 --- a/test/core.jl +++ b/test/core.jl @@ -14,7 +14,7 @@ include("testenv.jl") # sanity tests that our built-in types are marked correctly for const fields for (T, c) in ( (Core.CodeInfo, []), - (Core.CodeInstance, [:def, :rettype, :rettype_const, :ipo_purity_bits, :overrides, :argescapes]), + (Core.CodeInstance, [:def, :rettype, :rettype_const, :ipo_purity_bits, :argescapes]), (Core.Method, [#=:name, :module, :file, :line, :primary_world, :sig, :slot_syms, :external_mt, :nargs, :called, :nospecialize, :nkw, :isva, :pure, :is_for_opaque_closure, :constprop=#]), (Core.MethodInstance, [#=:def, :specTypes, :sparam_vals=#]), (Core.MethodTable, [:module]), From 7ace6889fee2d6072b77b569047b77ed226fd52c Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Mon, 26 Dec 2022 09:02:02 +0900 Subject: [PATCH 174/387] improve semi-concrete interpretation accuracy, take 2 Currently semi-concrete interpretation can end up with more inaccurate result than usual abstract interpretation because `src.ssavaluetypes` are all widened when cached so semi-concrete interpretation can't use extended lattice information of `SSAValue`s. This commit tries to fix it by making the codegen system recognize extended lattice information such as `Const` and `PartialStruct` and not-widening those information from `src::CodeInfo` when caching. I found there are other chances when semi-concrete interpretation can end up with inaccurate results, but that is unrelated to this and should be fixed separately. --- base/boot.jl | 4 ++-- base/compiler/optimize.jl | 13 ++++++------- base/compiler/typelattice.jl | 29 +++++++++++++++++++++++++++++ base/opaque_closure.jl | 2 +- src/ccall.cpp | 6 ++++-- src/codegen.cpp | 15 ++++++++------- src/interpreter.c | 6 ++++-- src/jltypes.c | 22 ++++++++++++++++++++++ src/julia.h | 1 - src/julia_internal.h | 1 + test/compiler/inference.jl | 12 ++++++++++++ 11 files changed, 89 insertions(+), 22 deletions(-) diff --git a/base/boot.jl b/base/boot.jl index 33b2cd07688ad6..399fa1cdaf1ff3 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -524,12 +524,12 @@ module IR export CodeInfo, MethodInstance, CodeInstance, GotoNode, GotoIfNot, ReturnNode, NewvarNode, SSAValue, Slot, SlotNumber, TypedSlot, Argument, PiNode, PhiNode, PhiCNode, UpsilonNode, LineInfoNode, - Const, PartialStruct + Const, PartialStruct, InterConditional, PartialOpaque import Core: CodeInfo, MethodInstance, CodeInstance, GotoNode, GotoIfNot, ReturnNode, NewvarNode, SSAValue, Slot, SlotNumber, TypedSlot, Argument, PiNode, PhiNode, PhiCNode, UpsilonNode, LineInfoNode, - Const, PartialStruct + Const, PartialStruct, InterConditional, PartialOpaque end diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index 98d48909a5e24a..ddf739d104809e 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -179,29 +179,28 @@ function ir_to_codeinf!(opt::OptimizationState) optdef = linfo.def replace_code_newstyle!(src, opt.ir::IRCode, isa(optdef, Method) ? Int(optdef.nargs) : 0) opt.ir = nothing - widen_all_consts!(src) + widencompileronly!(src) + src.rettype = widenconst(src.rettype) src.inferred = true # finish updating the result struct validate_code_in_debug_mode(linfo, src, "optimized") return src end -# widen all Const elements in type annotations -function widen_all_consts!(src::CodeInfo) +# widen extended lattice elements in type annotations so that they are recognizable by the codegen system. +function widencompileronly!(src::CodeInfo) ssavaluetypes = src.ssavaluetypes::Vector{Any} for i = 1:length(ssavaluetypes) - ssavaluetypes[i] = widenconst(ssavaluetypes[i]) + ssavaluetypes[i] = widencompileronly(ssavaluetypes[i]) end for i = 1:length(src.code) x = src.code[i] if isa(x, PiNode) - src.code[i] = PiNode(x.val, widenconst(x.typ)) + src.code[i] = PiNode(x.val, widencompileronly(x.typ)) end end - src.rettype = widenconst(src.rettype) - return src end diff --git a/base/compiler/typelattice.jl b/base/compiler/typelattice.jl index 71675647e5aabf..c8429d667b43c4 100644 --- a/base/compiler/typelattice.jl +++ b/base/compiler/typelattice.jl @@ -81,6 +81,8 @@ const AnyConditional = Union{Conditional,InterConditional} Conditional(cnd::InterConditional) = Conditional(cnd.slot, cnd.thentype, cnd.elsetype) InterConditional(cnd::Conditional) = InterConditional(cnd.slot, cnd.thentype, cnd.elsetype) +# TODO make `MustAlias` and `InterMustAlias` recognizable by the codegen system + """ alias::MustAlias @@ -668,6 +670,33 @@ function tmeet(lattice::OptimizerLattice, @nospecialize(v), @nospecialize(t::Typ tmeet(widenlattice(lattice), v, t) end +""" + is_core_extended_info(t) -> Bool + +Check if extended lattice element `t` is recognizable by the runtime/codegen system. + +See also the implementation of `jl_widen_core_extended_info` in jltypes.c. +""" +function is_core_extended_info(@nospecialize t) + isa(t, Type) && return true + isa(t, Const) && return true + isa(t, PartialStruct) && return true + isa(t, InterConditional) && return true + # TODO isa(t, InterMustAlias) && return true + isa(t, PartialOpaque) && return true + return false +end + +""" + widencompileronly(t) -> wt::Any + +Widen the extended lattice element `x` so that `wt` is recognizable by the runtime/codegen system. +""" +function widencompileronly(@nospecialize t) + is_core_extended_info(t) && return t + return widenconst(t) +end + """ widenconst(x) -> t::Type diff --git a/base/opaque_closure.jl b/base/opaque_closure.jl index 2bccd613d0009f..707550cd2a444d 100644 --- a/base/opaque_closure.jl +++ b/base/opaque_closure.jl @@ -68,7 +68,7 @@ function Core.OpaqueClosure(ir::IRCode, env...; src.slotnames = fill(:none, nargs+1) src.slottypes = copy(ir.argtypes) Core.Compiler.replace_code_newstyle!(src, ir, nargs+1) - Core.Compiler.widen_all_consts!(src) + Core.Compiler.widencompileronly!(src) src.inferred = true # NOTE: we need ir.argtypes[1] == typeof(env) diff --git a/src/ccall.cpp b/src/ccall.cpp index f1fe94a0d0ede9..10f0ba0d2e8bc6 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -763,7 +763,8 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar return jl_cgval_t(); } if (jl_is_ssavalue(args[2]) && !jl_is_long(ctx.source->ssavaluetypes)) { - jl_value_t *rtt = jl_arrayref((jl_array_t*)ctx.source->ssavaluetypes, ((jl_ssavalue_t*)args[2])->id - 1); + jl_value_t *rtt = jl_widen_core_extended_info( + jl_arrayref((jl_array_t*)ctx.source->ssavaluetypes, ((jl_ssavalue_t*)args[2])->id - 1)); if (jl_is_type_type(rtt)) rt = jl_tparam0(rtt); } @@ -776,7 +777,8 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar } } if (jl_is_ssavalue(args[3]) && !jl_is_long(ctx.source->ssavaluetypes)) { - jl_value_t *att = jl_arrayref((jl_array_t*)ctx.source->ssavaluetypes, ((jl_ssavalue_t*)args[3])->id - 1); + jl_value_t *att = jl_widen_core_extended_info( + jl_arrayref((jl_array_t*)ctx.source->ssavaluetypes, ((jl_ssavalue_t*)args[3])->id - 1)); if (jl_is_type_type(att)) at = jl_tparam0(att); } diff --git a/src/codegen.cpp b/src/codegen.cpp index b37364ef80a272..5b173d39305846 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -4501,7 +4501,7 @@ static void emit_phinode_assign(jl_codectx_t &ctx, ssize_t idx, jl_value_t *r) jl_value_t *ssavalue_types = (jl_value_t*)ctx.source->ssavaluetypes; jl_value_t *phiType = NULL; if (jl_is_array(ssavalue_types)) { - phiType = jl_array_ptr_ref(ssavalue_types, idx); + phiType = jl_widen_core_extended_info(jl_array_ptr_ref(ssavalue_types, idx)); } else { phiType = (jl_value_t*)jl_any_type; } @@ -4610,7 +4610,7 @@ static void emit_ssaval_assign(jl_codectx_t &ctx, ssize_t ssaidx_0based, jl_valu // e.g. sometimes the information is inconsistent after inlining getfield on a Tuple jl_value_t *ssavalue_types = (jl_value_t*)ctx.source->ssavaluetypes; if (jl_is_array(ssavalue_types)) { - jl_value_t *declType = jl_array_ptr_ref(ssavalue_types, ssaidx_0based); + jl_value_t *declType = jl_widen_core_extended_info(jl_array_ptr_ref(ssavalue_types, ssaidx_0based)); if (declType != slot.typ) { slot = update_julia_type(ctx, slot, declType); } @@ -4949,7 +4949,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ } if (jl_is_pinode(expr)) { Value *skip = NULL; - return convert_julia_type(ctx, emit_expr(ctx, jl_fieldref_noalloc(expr, 0)), jl_fieldref_noalloc(expr, 1), &skip); + return convert_julia_type(ctx, emit_expr(ctx, jl_fieldref_noalloc(expr, 0)), jl_widen_core_extended_info(jl_fieldref_noalloc(expr, 1)), &skip); } if (!jl_is_expr(expr)) { jl_value_t *val = expr; @@ -4987,13 +4987,13 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ else if (head == jl_invoke_sym) { assert(ssaidx_0based >= 0); jl_value_t *expr_t = jl_is_long(ctx.source->ssavaluetypes) ? (jl_value_t*)jl_any_type : - jl_array_ptr_ref(ctx.source->ssavaluetypes, ssaidx_0based); + jl_widen_core_extended_info(jl_array_ptr_ref(ctx.source->ssavaluetypes, ssaidx_0based)); return emit_invoke(ctx, ex, expr_t); } else if (head == jl_invoke_modify_sym) { assert(ssaidx_0based >= 0); jl_value_t *expr_t = jl_is_long(ctx.source->ssavaluetypes) ? (jl_value_t*)jl_any_type : - jl_array_ptr_ref(ctx.source->ssavaluetypes, ssaidx_0based); + jl_widen_core_extended_info(jl_array_ptr_ref(ctx.source->ssavaluetypes, ssaidx_0based)); return emit_invoke_modify(ctx, ex, expr_t); } else if (head == jl_call_sym) { @@ -5003,7 +5003,8 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ // TODO: this case is needed for the call to emit_expr in emit_llvmcall expr_t = (jl_value_t*)jl_any_type; else { - expr_t = jl_is_long(ctx.source->ssavaluetypes) ? (jl_value_t*)jl_any_type : jl_array_ptr_ref(ctx.source->ssavaluetypes, ssaidx_0based); + expr_t = jl_is_long(ctx.source->ssavaluetypes) ? (jl_value_t*)jl_any_type : + jl_widen_core_extended_info(jl_array_ptr_ref(ctx.source->ssavaluetypes, ssaidx_0based)); is_promotable = ctx.ssavalue_usecount.at(ssaidx_0based) == 1; } jl_cgval_t res = emit_call(ctx, ex, expr_t, is_promotable); @@ -7114,7 +7115,7 @@ static jl_llvm_functions_t } jl_varinfo_t &vi = (ctx.phic_slots.emplace(i, jl_varinfo_t(ctx.builder.getContext())).first->second = jl_varinfo_t(ctx.builder.getContext())); - jl_value_t *typ = jl_array_ptr_ref(src->ssavaluetypes, i); + jl_value_t *typ = jl_widen_core_extended_info(jl_array_ptr_ref(src->ssavaluetypes, i)); vi.used = true; vi.isVolatile = true; vi.value = mark_julia_type(ctx, (Value*)NULL, false, typ); diff --git a/src/interpreter.c b/src/interpreter.c index cf9ddd5b50630d..3a3e270c3a5523 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -209,8 +209,10 @@ static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s) if (jl_is_pinode(e)) { jl_value_t *val = eval_value(jl_fieldref_noalloc(e, 0), s); #ifndef JL_NDEBUG - JL_GC_PUSH1(&val); - jl_typeassert(val, jl_fieldref_noalloc(e, 1)); + jl_value_t *typ = NULL; + JL_GC_PUSH2(&val, &typ); + typ = jl_widen_core_extended_info(jl_fieldref_noalloc(e, 1)); + jl_typeassert(val, typ); JL_GC_POP(); #endif return val; diff --git a/src/jltypes.c b/src/jltypes.c index 253768ad075036..2b83c6396aa1ee 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -2015,6 +2015,28 @@ void jl_reinstantiate_inner_types(jl_datatype_t *t) // can throw! } } +// Widens "core" extended lattice element `t` to the native `Type` representation. +// The implementation of this function should sync with those of the corresponding `widenconst`s. +JL_DLLEXPORT jl_value_t *jl_widen_core_extended_info(jl_value_t *t) +{ + jl_value_t* tt = jl_typeof(t); + if (tt == (jl_value_t*)jl_const_type) { + jl_value_t* val = jl_fieldref_noalloc(t, 0); + if (jl_isa(val, (jl_value_t*)jl_type_type)) + return (jl_value_t*)jl_wrap_Type(val); + else + return jl_typeof(val); + } + else if (tt == (jl_value_t*)jl_partial_struct_type) + return (jl_value_t*)jl_fieldref_noalloc(t, 0); + else if (tt == (jl_value_t*)jl_interconditional_type) + return (jl_value_t*)jl_bool_type; + else if (tt == (jl_value_t*)jl_partial_opaque_type) + return (jl_value_t*)jl_fieldref_noalloc(t, 0); + else + return t; +} + // initialization ------------------------------------------------------------- static jl_tvar_t *tvar(const char *name) diff --git a/src/julia.h b/src/julia.h index 0164c2e55a4f93..16f55a019d9588 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1842,7 +1842,6 @@ JL_DLLEXPORT jl_value_t *jl_compress_argnames(jl_array_t *syms); JL_DLLEXPORT jl_array_t *jl_uncompress_argnames(jl_value_t *syms); JL_DLLEXPORT jl_value_t *jl_uncompress_argname_n(jl_value_t *syms, size_t i); - JL_DLLEXPORT int jl_is_operator(char *sym); JL_DLLEXPORT int jl_is_unary_operator(char *sym); JL_DLLEXPORT int jl_is_unary_and_binary_operator(char *sym); diff --git a/src/julia_internal.h b/src/julia_internal.h index f310770bdf9b11..5710d430dc8ac1 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -722,6 +722,7 @@ void jl_init_main_module(void); JL_DLLEXPORT int jl_is_submodule(jl_module_t *child, jl_module_t *parent) JL_NOTSAFEPOINT; jl_array_t *jl_get_loaded_modules(void); JL_DLLEXPORT int jl_datatype_isinlinealloc(jl_datatype_t *ty, int pointerfree); +JL_DLLEXPORT jl_value_t *jl_widen_core_extended_info(jl_value_t *t); void jl_eval_global_expr(jl_module_t *m, jl_expr_t *ex, int set_type); jl_value_t *jl_toplevel_eval_flex(jl_module_t *m, jl_value_t *e, int fast, int expanded); diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index f24124f76e3c7b..b610b00f8462d8 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -4689,3 +4689,15 @@ end @test Base.return_types(empty_nt_keys, (Any,)) |> only === Tuple{} g() = empty_nt_values(Base.inferencebarrier(Tuple{})) @test g() == () # Make sure to actually run this to test this in the inference world age + +let # jl_widen_core_extended_info + for (extended, widened) = [(Core.Const(42), Int), + (Core.Const(Int), Type{Int}), + (Core.Const(Vector), Type{Vector}), + (Core.PartialStruct(Some{Any}, Any["julia"]), Some{Any}), + (Core.InterConditional(2, Int, Nothing), Bool)] + @test @ccall(jl_widen_core_extended_info(extended::Any)::Any) === + Core.Compiler.widenconst(extended) === + widened + end +end From 6979ff3302056d0e340b149b3488ff11711591b4 Mon Sep 17 00:00:00 2001 From: Petr Vana Date: Tue, 27 Dec 2022 13:37:44 +0100 Subject: [PATCH 175/387] Improve printing, add errormonitor, and enable serial run for debug --- contrib/generate_precompile.jl | 46 ++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index b1396e206969b4..e69e96e1753c00 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -239,6 +239,9 @@ const PKG_PROMPT = "pkg> " const SHELL_PROMPT = "shell> " const HELP_PROMPT = "help?> " +# You can disable parallel precompiles generation by setting `false` +const PARALLEL_PRECOMPILATION = true + function generate_precompile_statements() start_time = time_ns() debug_output = devnull # or stdout @@ -247,14 +250,37 @@ function generate_precompile_statements() # Extract the precompile statements from the precompile file statements_step1 = Channel{String}(Inf) statements_step2 = Channel{String}(Inf) + # Make statements unique + statements = Set{String}() + # Variables for statistics + n_step0 = n_step1 = n_step2 = 0 + n_succeeded = 0 + step1 = step2 = nothing + repl_state_global = nothing # From hardcoded statements for statement in split(hardcoded_precompile_statements::String, '\n') push!(statements_step1, statement) + n_step0 += 1 + end + + # Printing the current state + function print_state(;repl_state = nothing) + if !isnothing(repl_state) + repl_state_global = repl_state + end + step0_status = "F,$n_step0" + step1_status = (isnothing(step1) ? "W" : isopen(statements_step1) ? "R" : "F") * ",$n_step1" + step2_status = (isnothing(step2) ? "W" : isopen(statements_step2) ? "R" : "F") * ",$n_step2" + repl_status = isnothing(repl_state_global) ? "" : repl_state_global + ex_status = "$n_succeeded/$(length(statements))" + print("\rCollect(manual($step0_status), normal($step1_status), REPL $repl_status($step2_status)) => Execute $ex_status") end + println("Precompile statements (Waiting, Running, Finished)") + print_state() # Collect statements from running the script - @async mktempdir() do prec_path + step1 = @async mktempdir() do prec_path # Also precompile a package here pkgname = "__PackagePrecompilationStatementModule" mkpath(joinpath(prec_path, pkgname, "src")) @@ -276,12 +302,16 @@ function generate_precompile_statements() for f in (tmp_prec, tmp_proc) for statement in split(read(f, String), '\n') push!(statements_step1, statement) + n_step1 += 1 end end close(statements_step1) + print_state() end + errormonitor(step1) + !PARALLEL_PRECOMPILATION && wait(step1) - @async mktemp() do precompile_file, precompile_file_h + step2 = @async mktemp() do precompile_file, precompile_file_h # Collect statements from running a REPL process and replaying our REPL script pts, ptm = open_fake_pty() blackhole = Sys.isunix() ? "/dev/null" : "nul" @@ -330,7 +360,7 @@ function generate_precompile_statements() for l in precompile_lines sleep(0.1) curr += 1 - print("\rGenerating REPL precompile statements... $curr/$(length(precompile_lines))") + print_state(repl_state = "$curr/$(length(precompile_lines))") # consume any other output bytesavailable(output_copy) > 0 && readavailable(output_copy) # push our input @@ -349,7 +379,6 @@ function generate_precompile_statements() sleep(0.1) end end - println() end write(ptm, "exit()\n") wait(tee) @@ -359,9 +388,13 @@ function generate_precompile_statements() for statement in split(read(precompile_file, String), '\n') push!(statements_step2, statement) + n_step2 += 1 end close(statements_step2) + print_state() end + errormonitor(step2) + !PARALLEL_PRECOMPILATION && wait(step2) # Create a staging area where all the loaded packages are available PrecompileStagingArea = Module() @@ -371,10 +404,7 @@ function generate_precompile_statements() end end - # Make statements unique - statements = Set{String}() # Execute the precompile statements - n_succeeded = 0 for sts in [statements_step1, statements_step2], statement in sts # Main should be completely clean occursin("Main.", statement) && continue @@ -408,7 +438,7 @@ function generate_precompile_statements() ps = Core.eval(PrecompileStagingArea, ps) precompile(ps...) n_succeeded += 1 - print("\rExecuting precompile statements... $n_succeeded/$(length(statements))") + print_state() catch ex # See #28808 @warn "Failed to precompile expression" form=statement exception=ex _module=nothing _file=nothing _line=0 From a2db90fe8d9158923ebd5f45c443b12968d4e379 Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Tue, 27 Dec 2022 14:46:24 +0100 Subject: [PATCH 176/387] Implement support for object caching through pkgimages (#47184) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This implements caching native code in "package images" (pkgimages). We now write two serialization files, one ending in `*.ji` and the other with the platform dynamic library extension (e.g., `*.so`). The `*.ji` contains "extended" header information (include the source-code dump for Revise), whereas the dynamic library includes the Julia objects, including LLVM-generated native code. Native code is compiled once during precompilation and then a second time to build a "clean" module. When we find an edge to an external function (already cached in anloaded pkgimage), we emit a global variable which we will patch during loading with the address of the function to call. This allows us to leverage the standard multiversioning capabilities. Co-authored-by: Tim Holy Co-authored-by: Kristoffer Carlsson Co-authored-by: Mosè Giordano Co-authored-by: Ian Butterworth Co-authored-by: Max Horn Co-authored-by: Michael Schlottke-Lakemper Co-authored-by: Alex Ames --- Makefile | 13 + base/Base.jl | 1 + base/Makefile | 4 + base/linking.jl | 147 +++++++ base/loading.jl | 292 ++++++++++--- base/options.jl | 1 + base/util.jl | 31 +- contrib/cache_stdlibs.jl | 49 +++ contrib/print_sorted_stdlibs.jl | 11 +- deps/checksums/compilersupportlibraries | 184 ++++---- doc/make.jl | 1 + doc/src/devdocs/pkgimg.md | 48 +++ doc/src/devdocs/sysimg.md | 2 +- doc/src/manual/code-loading.md | 9 +- doc/src/manual/command-line-interface.md | 9 +- doc/src/manual/environment-variables.md | 6 +- doc/src/manual/methods.md | 36 +- doc/src/manual/modules.md | 24 +- doc/src/manual/performance-tips.md | 2 +- src/aotcompile.cpp | 121 +++++- src/codegen-stubs.c | 9 +- src/codegen.cpp | 46 +- src/coverage.cpp | 2 +- src/debug-registry.h | 16 +- src/debuginfo.cpp | 92 ++-- src/debuginfo.h | 2 +- src/disasm.cpp | 2 +- src/jitlayers.h | 4 +- src/jl_exported_data.inc | 2 + src/jl_exported_funcs.inc | 4 + src/jloptions.c | 34 +- src/jloptions.h | 1 + src/julia.h | 5 +- src/julia_internal.h | 9 +- src/llvm-multiversioning.cpp | 78 ++-- src/precompile.c | 398 ++++-------------- src/precompile_utils.c | 306 ++++++++++++++ src/processor.cpp | 4 +- src/processor.h | 9 +- src/processor_arm.cpp | 9 +- src/processor_fallback.cpp | 9 +- src/processor_x86.cpp | 9 +- src/staticdata.c | 279 +++++++----- src/staticdata_utils.c | 54 ++- .../CompilerSupportLibraries_jll/Project.toml | 2 +- test/compiler/contextual.jl | 6 +- test/loading.jl | 6 +- test/precompile.jl | 126 +++++- 48 files changed, 1750 insertions(+), 764 deletions(-) create mode 100644 base/linking.jl create mode 100644 contrib/cache_stdlibs.jl create mode 100644 doc/src/devdocs/pkgimg.md create mode 100644 src/precompile_utils.c diff --git a/Makefile b/Makefile index c31517c4f1e3a1..d41f3e50d9752a 100644 --- a/Makefile +++ b/Makefile @@ -262,13 +262,21 @@ ifeq ($(OS),WINNT) -$(INSTALL_M) $(wildcard $(build_bindir)/*.dll) $(DESTDIR)$(bindir)/ ifeq ($(JULIA_BUILD_MODE),release) -$(INSTALL_M) $(build_libdir)/libjulia.dll.a $(DESTDIR)$(libdir)/ + -$(INSTALL_M) $(build_libdir)/libjulia-internal.dll.a $(DESTDIR)$(libdir)/ else ifeq ($(JULIA_BUILD_MODE),debug) -$(INSTALL_M) $(build_libdir)/libjulia-debug.dll.a $(DESTDIR)$(libdir)/ + -$(INSTALL_M) $(build_libdir)/libjulia-internal-debug.dll.a $(DESTDIR)$(libdir)/ endif # We have a single exception; we want 7z.dll to live in libexec, not bin, so that 7z.exe can find it. -mv $(DESTDIR)$(bindir)/7z.dll $(DESTDIR)$(libexecdir)/ -$(INSTALL_M) $(build_bindir)/libopenlibm.dll.a $(DESTDIR)$(libdir)/ + -$(INSTALL_M) $(build_libdir)/libssp.dll.a $(DESTDIR)$(libdir)/ + # The rest are compiler dependencies, as an example memcpy is exported by msvcrt + # These are files from mingw32 and required for creating shared libraries like our caches. + -$(INSTALL_M) $(build_libdir)/libgcc_s.a $(DESTDIR)$(libdir)/ + -$(INSTALL_M) $(build_libdir)/libgcc.a $(DESTDIR)$(libdir)/ + -$(INSTALL_M) $(build_libdir)/libmsvcrt.a $(DESTDIR)$(libdir)/ else # Copy over .dSYM directories directly for Darwin @@ -333,6 +341,11 @@ else ifeq ($(JULIA_BUILD_MODE),debug) $(INSTALL_M) $(build_private_libdir)/sys-debug.$(SHLIB_EXT) $(DESTDIR)$(private_libdir) endif + # Cache stdlibs + @$(call PRINT_JULIA, $(call spawn,$(JULIA_EXECUTABLE)) --startup-file=no $(JULIAHOME)/contrib/cache_stdlibs.jl) + # CI uses `--check-bounds=yes` which impacts the cache flags + @$(call PRINT_JULIA, $(call spawn,$(JULIA_EXECUTABLE)) --startup-file=no --check-bounds=yes $(JULIAHOME)/contrib/cache_stdlibs.jl) + # Copy in all .jl sources as well mkdir -p $(DESTDIR)$(datarootdir)/julia/base $(DESTDIR)$(datarootdir)/julia/test cp -R -L $(JULIAHOME)/base/* $(DESTDIR)$(datarootdir)/julia/base diff --git a/base/Base.jl b/base/Base.jl index 07b0a2f0f0d492..22e8b48288efc0 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -418,6 +418,7 @@ include("threadcall.jl") include("uuid.jl") include("pkgid.jl") include("toml_parser.jl") +include("linking.jl") include("loading.jl") # misc useful functions & macros diff --git a/base/Makefile b/base/Makefile index c320465a104921..d92302b766988a 100644 --- a/base/Makefile +++ b/base/Makefile @@ -81,6 +81,10 @@ ifeq ($(DARWIN_FRAMEWORK), 1) @echo "const DARWIN_FRAMEWORK_NAME = \"$(FRAMEWORK_NAME)\"" >> $@ else @echo "const DARWIN_FRAMEWORK = false" >> $@ +endif +ifeq ($(OS), Darwin) + @echo "const MACOS_PRODUCT_VERSION = \"$(shell sw_vers -productVersion)\"" >> $@ + @echo "const MACOS_PLATFORM_VERSION = \"$(shell xcrun --show-sdk-version)\"" >> $@ endif @echo "const BUILD_TRIPLET = \"$(BB_TRIPLET_LIBGFORTRAN_CXXABI)\"" >> $@ diff --git a/base/linking.jl b/base/linking.jl new file mode 100644 index 00000000000000..288279347f1c59 --- /dev/null +++ b/base/linking.jl @@ -0,0 +1,147 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license +module Linking + +import Base.Libc: Libdl + +# inlined LLD_jll +# These get calculated in __init__() +const PATH = Ref("") +const LIBPATH = Ref("") +const PATH_list = String[] +const LIBPATH_list = String[] +const lld_path = Ref{String}() +const lld_exe = Sys.iswindows() ? "lld.exe" : "lld" + +if Sys.iswindows() + const LIBPATH_env = "PATH" + const LIBPATH_default = "" + const pathsep = ';' +elseif Sys.isapple() + const LIBPATH_env = "DYLD_FALLBACK_LIBRARY_PATH" + const LIBPATH_default = "~/lib:/usr/local/lib:/lib:/usr/lib" + const pathsep = ':' +else + const LIBPATH_env = "LD_LIBRARY_PATH" + const LIBPATH_default = "" + const pathsep = ':' +end + +function adjust_ENV!(env::Dict, PATH::String, LIBPATH::String, adjust_PATH::Bool, adjust_LIBPATH::Bool) + if adjust_LIBPATH + LIBPATH_base = get(env, LIBPATH_env, expanduser(LIBPATH_default)) + if !isempty(LIBPATH_base) + env[LIBPATH_env] = string(LIBPATH, pathsep, LIBPATH_base) + else + env[LIBPATH_env] = LIBPATH + end + end + if adjust_PATH && (LIBPATH_env != "PATH" || !adjust_LIBPATH) + if !isempty(get(env, "PATH", "")) + env["PATH"] = string(PATH, pathsep, env["PATH"]) + else + env["PATH"] = PATH + end + end + return env +end + +function __init_lld_path() + # Prefer our own bundled lld, but if we don't have one, pick it up off of the PATH + # If this is an in-tree build, `lld` will live in `tools`. Otherwise, it'll be in `libexec` + for bundled_lld_path in (joinpath(Sys.BINDIR, Base.LIBEXECDIR, lld_exe), + joinpath(Sys.BINDIR, "..", "tools", lld_exe), + joinpath(Sys.BINDIR, lld_exe)) + if isfile(bundled_lld_path) + lld_path[] = abspath(bundled_lld_path) + return + end + end + lld_path[] = something(Sys.which(lld_exe), lld_exe) + return +end + +const VERBOSE = Ref{Bool}(false) + +function __init__() + VERBOSE[] = parse(Bool, get(ENV, "JULIA_VERBOSE_LINKING", "false")) + + __init_lld_path() + PATH[] = dirname(lld_path[]) + if Sys.iswindows() + # On windows, the dynamic libraries (.dll) are in Sys.BINDIR ("usr\\bin") + append!(LIBPATH_list, [abspath(Sys.BINDIR, Base.LIBDIR, "julia"), Sys.BINDIR]) + else + append!(LIBPATH_list, [abspath(Sys.BINDIR, Base.LIBDIR, "julia"), abspath(Sys.BINDIR, Base.LIBDIR)]) + end + LIBPATH[] = join(LIBPATH_list, pathsep) + return +end + +function lld(; adjust_PATH::Bool = true, adjust_LIBPATH::Bool = true) + env = adjust_ENV!(copy(ENV), PATH[], LIBPATH[], adjust_PATH, adjust_LIBPATH) + return Cmd(Cmd([lld_path[]]); env) +end + +function ld() + default_args = `` + @static if Sys.iswindows() + # LLD supports mingw style linking + flavor = "gnu" + m = Sys.ARCH == :x86_64 ? "i386pep" : "i386pe" + default_args = `-m $m -Bdynamic --enable-auto-image-base --allow-multiple-definition` + elseif Sys.isapple() + flavor = "darwin" + arch = Sys.ARCH == :aarch64 ? :arm64 : Sys.ARCH + default_args = `-arch $arch -undefined dynamic_lookup -platform_version macos $(Base.MACOS_PRODUCT_VERSION) $(Base.MACOS_PLATFORM_VERSION)` + else + flavor = "gnu" + end + + `$(lld()) -flavor $flavor $default_args` +end + +const WHOLE_ARCHIVE = if Sys.isapple() + "-all_load" +else + "--whole-archive" +end + +const NO_WHOLE_ARCHIVE = if Sys.isapple() + "" +else + "--no-whole-archive" +end + +const SHARED = if Sys.isapple() + "-dylib" +else + "-shared" +end + +is_debug() = ccall(:jl_is_debugbuild, Cint, ()) == 1 +libdir() = abspath(Sys.BINDIR, Base.LIBDIR) +private_libdir() = abspath(Sys.BINDIR, Base.PRIVATE_LIBDIR) +if Sys.iswindows() + shlibdir() = Sys.BINDIR +else + shlibdir() = libdir() +end + +function link_image_cmd(path, out) + LIBDIR = "-L$(libdir())" + PRIVATE_LIBDIR = "-L$(private_libdir())" + SHLIBDIR = "-L$(shlibdir())" + LIBS = is_debug() ? ("-ljulia-debug", "-ljulia-internal-debug") : ("-ljulia", "-ljulia-internal") + @static if Sys.iswindows() + LIBS = (LIBS..., "-lopenlibm", "-lssp", "-lgcc_s", "-lgcc", "-lmsvcrt") + end + + V = VERBOSE[] ? "--verbose" : "" + `$(ld()) $V $SHARED -o $out $WHOLE_ARCHIVE $path $NO_WHOLE_ARCHIVE $LIBDIR $PRIVATE_LIBDIR $SHLIBDIR $LIBS` +end + +function link_image(path, out, internal_stderr::IO = stderr, internal_stdout::IO = stdout) + run(link_image_cmd(path, out), Base.DevNull(), stderr, stdout) +end + +end # module Linking diff --git a/base/loading.jl b/base/loading.jl index 2ce769405022aa..06ba6e733b6d7f 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -908,7 +908,8 @@ function find_all_in_cache_path(pkg::PkgId) isdir(path) || continue for file in readdir(path, sort = false) # no sort given we sort later if !((pkg.uuid === nothing && file == entryfile * ".ji") || - (pkg.uuid !== nothing && startswith(file, entryfile * "_"))) + (pkg.uuid !== nothing && startswith(file, entryfile * "_") && + endswith(file, ".ji"))) continue end filepath = joinpath(path, file) @@ -925,13 +926,15 @@ function find_all_in_cache_path(pkg::PkgId) end end +ocachefile_from_cachefile(cachefile) = string(chopsuffix(cachefile, ".ji"), ".", Base.Libc.dlext) + # use an Int counter so that nested @time_imports calls all remain open const TIMING_IMPORTS = Threads.Atomic{Int}(0) # these return either the array of modules loaded from the path / content given # or an Exception that describes why it couldn't be loaded # and it reconnects the Base.Docs.META -function _include_from_serialized(pkg::PkgId, path::String, depmods::Vector{Any}) +function _include_from_serialized(pkg::PkgId, path::String, ocachepath::Union{Nothing, String}, depmods::Vector{Any}) assert_havelock(require_lock) timing_imports = TIMING_IMPORTS[] > 0 try @@ -941,36 +944,18 @@ function _include_from_serialized(pkg::PkgId, path::String, depmods::Vector{Any} t_comp_before = cumulative_compile_time_ns() end - @debug "Loading cache file $path for $pkg" - sv = ccall(:jl_restore_incremental, Any, (Cstring, Any, Cint), path, depmods, false) + if ocachepath !== nothing + @debug "Loading object cache file $ocachepath for $pkg" + sv = ccall(:jl_restore_package_image_from_file, Any, (Cstring, Any, Cint), ocachepath, depmods, false) + else + @debug "Loading cache file $path for $pkg" + sv = ccall(:jl_restore_incremental, Any, (Cstring, Any, Cint), path, depmods, false) + end if isa(sv, Exception) return sv end - sv = sv::SimpleVector - restored = sv[1]::Vector{Any} - for M in restored - M = M::Module - if isdefined(M, Base.Docs.META) && getfield(M, Base.Docs.META) !== nothing - push!(Base.Docs.modules, M) - end - if parentmodule(M) === M - register_root_module(M) - end - end - - # Register this cache path now - If Requires.jl is loaded, Revise may end - # up looking at the cache path during the init callback. - get!(PkgOrigin, pkgorigins, pkg).cachepath = path - inits = sv[2]::Vector{Any} - if !isempty(inits) - unlock(require_lock) # temporarily _unlock_ during these callbacks - try - ccall(:jl_init_restored_modules, Cvoid, (Any,), inits) - finally - lock(require_lock) - end - end + restored = register_restored_modules(sv, pkg, path) for M in restored M = M::Module @@ -1005,6 +990,35 @@ function _include_from_serialized(pkg::PkgId, path::String, depmods::Vector{Any} end end +function register_restored_modules(sv::SimpleVector, pkg::PkgId, path::String) + # This function is also used by PkgCacheInspector.jl + restored = sv[1]::Vector{Any} + for M in restored + M = M::Module + if isdefined(M, Base.Docs.META) && getfield(M, Base.Docs.META) !== nothing + push!(Base.Docs.modules, M) + end + if parentmodule(M) === M + register_root_module(M) + end + end + + # Register this cache path now - If Requires.jl is loaded, Revise may end + # up looking at the cache path during the init callback. + get!(PkgOrigin, pkgorigins, pkg).cachepath = path + + inits = sv[2]::Vector{Any} + if !isempty(inits) + unlock(require_lock) # temporarily _unlock_ during these callbacks + try + ccall(:jl_init_restored_modules, Cvoid, (Any,), inits) + finally + lock(require_lock) + end + end + return restored +end + function run_package_callbacks(modkey::PkgId) assert_havelock(require_lock) unlock(require_lock) @@ -1207,7 +1221,7 @@ end # loads a precompile cache file, ignoring stale_cachefile tests # assuming all depmods are already loaded and everything is valid -function _tryrequire_from_serialized(modkey::PkgId, path::String, sourcepath::String, depmods::Vector{Any}) +function _tryrequire_from_serialized(modkey::PkgId, path::String, ocachepath::Union{Nothing, String}, sourcepath::String, depmods::Vector{Any}) assert_havelock(require_lock) loaded = nothing if root_module_exists(modkey) @@ -1229,7 +1243,7 @@ function _tryrequire_from_serialized(modkey::PkgId, path::String, sourcepath::St package_locks[modkey] = Threads.Condition(require_lock) try set_pkgorigin_version_path(modkey, sourcepath) - loaded = _include_from_serialized(modkey, path, depmods) + loaded = _include_from_serialized(modkey, path, ocachepath, depmods) finally loading = pop!(package_locks, modkey) notify(loading, loaded, all=true) @@ -1248,13 +1262,23 @@ end # loads a precompile cache file, ignoring stale_cachefile tests # load the best available (non-stale) version of all dependent modules first -function _tryrequire_from_serialized(pkg::PkgId, path::String) +function _tryrequire_from_serialized(pkg::PkgId, path::String, ocachepath::Union{Nothing, String}) assert_havelock(require_lock) local depmodnames io = open(path, "r") try iszero(isvalid_cache_header(io)) && return ArgumentError("Invalid header in cache file $path.") - depmodnames = parse_cache_header(io)[3] + _, _, depmodnames, _, _, _, clone_targets, _ = parse_cache_header(io) + pkgimage = !isempty(clone_targets) + if pkgimage + ocachepath !== nothing || return ArgumentError("Expected ocachepath to be provided") + isfile(ocachepath) || return ArgumentError("Ocachepath $ocachpath is not a file.") + ocachepath == ocachefile_from_cachefile(path) || return ArgumentError("$ocachepath is not the expected ocachefile") + # TODO: Check for valid clone_targets? + isvalid_pkgimage_crc(io, ocachepath) || return ArgumentError("Invalid checksum in cache file $ocachepath.") + else + @assert ocachepath === nothing + end isvalid_file_crc(io) || return ArgumentError("Invalid checksum in cache file $path.") finally close(io) @@ -1270,7 +1294,7 @@ function _tryrequire_from_serialized(pkg::PkgId, path::String) depmods[i] = dep end # then load the file - return _include_from_serialized(pkg, path, depmods) + return _include_from_serialized(pkg, path, ocachepath, depmods) end # returns `nothing` if require found a precompile cache for this sourcepath, but couldn't load it @@ -1278,12 +1302,13 @@ end @constprop :none function _require_search_from_serialized(pkg::PkgId, sourcepath::String, build_id::UInt128) assert_havelock(require_lock) paths = find_all_in_cache_path(pkg) + ocachefile = nothing for path_to_try in paths::Vector{String} staledeps = stale_cachefile(pkg, build_id, sourcepath, path_to_try) if staledeps === true continue end - staledeps = staledeps::Vector{Any} + staledeps, ocachefile = staledeps::Tuple{Vector{Any}, Union{Nothing, String}} # finish checking staledeps module graph for i in 1:length(staledeps) dep = staledeps[i] @@ -1296,8 +1321,8 @@ end if modstaledeps === true continue end - modstaledeps = modstaledeps::Vector{Any} - staledeps[i] = (modpath, modkey, modpath_to_try, modstaledeps) + modstaledeps, modocachepath = modstaledeps::Tuple{Vector{Any}, Union{Nothing, String}} + staledeps[i] = (modpath, modkey, modpath_to_try, modstaledeps, modocachepath) modfound = true break end @@ -1308,6 +1333,7 @@ end end end if staledeps === true + ocachefile = nothing continue end try @@ -1319,19 +1345,20 @@ end for i in 1:length(staledeps) dep = staledeps[i] dep isa Module && continue - modpath, modkey, modpath_to_try, modstaledeps = dep::Tuple{String, PkgId, String, Vector{Any}} - dep = _tryrequire_from_serialized(modkey, modpath_to_try, modpath, modstaledeps) + modpath, modkey, modcachepath, modstaledeps, modocachepath = dep::Tuple{String, PkgId, String, Vector{Any}, Union{Nothing, String}} + dep = _tryrequire_from_serialized(modkey, modcachepath, modocachepath, modpath, modstaledeps) if !isa(dep, Module) - @debug "Rejecting cache file $path_to_try because required dependency $modkey failed to load from cache file for $modpath." exception=dep + @debug "Rejecting cache file $path_to_try because required dependency $modkey failed to load from cache file for $modcachepath." exception=dep staledeps = true break end staledeps[i] = dep end if staledeps === true + ocachefile = nothing continue end - restored = _include_from_serialized(pkg, path_to_try, staledeps) + restored = _include_from_serialized(pkg, path_to_try, ocachefile, staledeps) if !isa(restored, Module) @debug "Deserialization checks failed while attempting to load cache from $path_to_try" exception=restored else @@ -1640,7 +1667,8 @@ function _require(pkg::PkgId, env=nothing) end # fall-through to loading the file locally else - m = _tryrequire_from_serialized(pkg, cachefile) + cachefile, ocachefile = cachefile::Tuple{String, Union{Nothing, String}} + m = _tryrequire_from_serialized(pkg, cachefile, ocachefile) if !isa(m, Module) @warn "The call to compilecache failed to create a usable precompiled cache file for $pkg" exception=m else @@ -1676,10 +1704,11 @@ function _require(pkg::PkgId, env=nothing) return loaded end -function _require_from_serialized(uuidkey::PkgId, path::String) +# Only used from test/precompile.jl +function _require_from_serialized(uuidkey::PkgId, path::String, ocachepath::Union{String, Nothing}) @lock require_lock begin set_pkgorigin_version_path(uuidkey, nothing) - newm = _tryrequire_from_serialized(uuidkey, path) + newm = _tryrequire_from_serialized(uuidkey, path, ocachepath) newm isa Module || throw(newm) insert_extension_triggers(uuidkey) # After successfully loading, notify downstream consumers @@ -1879,9 +1908,11 @@ function include_package_for_output(pkg::PkgId, input::String, depot_path::Vecto end const PRECOMPILE_TRACE_COMPILE = Ref{String}() -function create_expr_cache(pkg::PkgId, input::String, output::String, concrete_deps::typeof(_concrete_dependencies), internal_stderr::IO = stderr, internal_stdout::IO = stdout) +function create_expr_cache(pkg::PkgId, input::String, output::String, output_o::Union{Nothing, String}, + concrete_deps::typeof(_concrete_dependencies), internal_stderr::IO = stderr, internal_stdout::IO = stdout) @nospecialize internal_stderr internal_stdout rm(output, force=true) # Remove file if it exists + output_o === nothing || rm(output_o, force=true) depot_path = map(abspath, DEPOT_PATH) dl_load_path = map(abspath, DL_LOAD_PATH) load_path = map(abspath, Base.load_path()) @@ -1900,11 +1931,20 @@ function create_expr_cache(pkg::PkgId, input::String, output::String, concrete_d for (pkg, build_id) in concrete_deps push!(deps_strs, "$(pkg_str(pkg)) => $(repr(build_id))") end + + if output_o !== nothing + cpu_target = get(ENV, "JULIA_CPU_TARGET", nothing) + opt_level = Base.JLOptions().opt_level + opts = `-O$(opt_level) --output-o $(output_o) --output-ji $(output) --output-incremental=yes` + else + cpu_target = nothing + opts = `-O0 --output-ji $(output) --output-incremental=yes` + end + deps_eltype = sprint(show, eltype(concrete_deps); context = :module=>nothing) deps = deps_eltype * "[" * join(deps_strs, ",") * "]" trace = isassigned(PRECOMPILE_TRACE_COMPILE) ? `--trace-compile=$(PRECOMPILE_TRACE_COMPILE[])` : `` - io = open(pipeline(addenv(`$(julia_cmd()::Cmd) -O0 - --output-ji $output --output-incremental=yes + io = open(pipeline(addenv(`$(julia_cmd(;cpu_target)::Cmd) $(opts) --startup-file=no --history-file=no --warn-overwrite=yes --color=$(have_color === nothing ? "auto" : have_color ? "yes" : "no") $trace @@ -1937,6 +1977,14 @@ function compilecache_path(pkg::PkgId, prefs_hash::UInt64)::String crc = _crc32c(something(Base.active_project(), "")) crc = _crc32c(unsafe_string(JLOptions().image_file), crc) crc = _crc32c(unsafe_string(JLOptions().julia_bin), crc) + crc = _crc32c(ccall(:jl_cache_flags, UInt8, ()), crc) + + cpu_target = get(ENV, "JULIA_CPU_TARGET", nothing) + if cpu_target === nothing + cpu_target = unsafe_string(JLOptions().cpu_target) + end + crc = _crc32c(cpu_target, crc) + crc = _crc32c(prefs_hash, crc) project_precompile_slug = slug(crc, 5) abspath(cachepath, string(entryfile, "_", project_precompile_slug, ".ji")) @@ -1983,44 +2031,92 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in # create a temporary file in `cachepath` directory, write the cache in it, # write the checksum, _and then_ atomically move the file to `cachefile`. mkpath(cachepath) + cache_objects = JLOptions().use_pkgimages != 0 tmppath, tmpio = mktemp(cachepath) + + if cache_objects + tmppath_o, tmpio_o = mktemp(cachepath) + tmppath_so, tmpio_so = mktemp(cachepath) + else + tmppath_o = nothing + end local p try close(tmpio) - p = create_expr_cache(pkg, path, tmppath, concrete_deps, internal_stderr, internal_stdout) + if cache_objects + close(tmpio_o) + close(tmpio_so) + end + p = create_expr_cache(pkg, path, tmppath, tmppath_o, concrete_deps, internal_stderr, internal_stdout) + if success(p) + if cache_objects + # Run linker over tmppath_o + Linking.link_image(tmppath_o, tmppath_so) + end + + # Read preferences hash back from .ji file (we can't precompute because + # we don't actually know what the list of compile-time preferences are without compiling) + prefs_hash = preferences_hash(tmppath) + cachefile = compilecache_path(pkg, prefs_hash) + ocachefile = cache_objects ? ocachefile_from_cachefile(cachefile) : nothing + + # append checksum for so to the end of the .ji file: + crc_so = UInt32(0) + if cache_objects + crc_so = open(_crc32c, tmppath_so, "r") + end + # append extra crc to the end of the .ji file: open(tmppath, "r+") do f if iszero(isvalid_cache_header(f)) error("Invalid header for $pkg in new cache file $(repr(tmppath)).") end + seekend(f) + write(f, crc_so) seekstart(f) write(f, _crc32c(f)) end + # inherit permission from the source file (and make them writable) chmod(tmppath, filemode(path) & 0o777 | 0o200) - - # Read preferences hash back from .ji file (we can't precompute because - # we don't actually know what the list of compile-time preferences are without compiling) - prefs_hash = preferences_hash(tmppath) - cachefile = compilecache_path(pkg, prefs_hash) + if cache_objects + # Ensure that the user can execute the `.so` we're generating + # Note that on windows, `filemode(path)` typically returns `0o666`, so this + # addition of the execute bit for the user is doubly needed. + chmod(tmppath_so, filemode(path) & 0o777 | 0o300) + end # prune the directory with cache files if pkg.uuid !== nothing entrypath, entryfile = cache_file_entry(pkg) - cachefiles = filter!(x -> startswith(x, entryfile * "_"), readdir(cachepath)) + cachefiles = filter!(x -> startswith(x, entryfile * "_") && endswith(x, ".ji"), readdir(cachepath)) + if length(cachefiles) >= MAX_NUM_PRECOMPILE_FILES[] idx = findmin(mtime.(joinpath.(cachepath, cachefiles)))[2] - rm(joinpath(cachepath, cachefiles[idx]); force=true) + evicted_cachefile = joinpath(cachepath, cachefiles[idx]) + @debug "Evicting file from cache" evicted_cachefile + rm(evicted_cachefile; force=true) + try + rm(ocachefile_from_cachefile(evicted_cachefile); force=true) + catch + end end end # this is atomic according to POSIX (not Win32): rename(tmppath, cachefile; force=true) - return cachefile + if cache_objects + rename(tmppath_so, ocachefile::String; force=true) + end + return cachefile, ocachefile end finally rm(tmppath, force=true) + if cache_objects + rm(tmppath_o, force=true) + rm(tmppath_so, force=true) + end end if p.exitcode == 125 return PrecompilableError() @@ -2034,9 +2130,26 @@ function module_build_id(m::Module) return (UInt128(hi) << 64) | lo end -isvalid_cache_header(f::IOStream) = ccall(:jl_read_verify_header, UInt64, (Ptr{Cvoid},), f.ios) # returns checksum id or zero +function isvalid_cache_header(f::IOStream) + pkgimage = Ref{UInt8}() + checksum = ccall(:jl_read_verify_header, UInt64, (Ptr{Cvoid}, Ptr{UInt8}, Ptr{Int64}, Ptr{Int64}), f.ios, pkgimage, Ref{Int64}(), Ref{Int64}()) # returns checksum id or zero + + if !iszero(checksum) && pkgimage[] != 0 + @debug "Cache header was for pkgimage" + return UInt64(0) # We somehow read the header for a pkgimage and not a ji + end + return checksum +end isvalid_file_crc(f::IOStream) = (_crc32c(seekstart(f), filesize(f) - 4) == read(f, UInt32)) +function isvalid_pkgimage_crc(f::IOStream, ocachefile::String) + seekstart(f) # TODO necessary + seek(f, filesize(f) - 8) + expected_crc_so = read(f, UInt32) + crc_so = open(_crc32c, ocachefile, "r") + expected_crc_so == crc_so +end + struct CacheHeaderIncludes id::PkgId filename::String @@ -2045,6 +2158,7 @@ struct CacheHeaderIncludes end function parse_cache_header(f::IO) + flags = read(f, UInt8) modules = Vector{Pair{PkgId, UInt64}}() while true n = read(f, Int32) @@ -2118,7 +2232,10 @@ function parse_cache_header(f::IO) build_id |= read(f, UInt64) push!(required_modules, PkgId(uuid, sym) => build_id) end - return modules, (includes, requires), required_modules, srctextpos, prefs, prefs_hash + l = read(f, Int32) + clone_targets = read(f, l) + + return modules, (includes, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, flags end function parse_cache_header(cachefile::String; srcfiles_only::Bool=false) @@ -2140,8 +2257,6 @@ function parse_cache_header(cachefile::String; srcfiles_only::Bool=false) end end - - preferences_hash(f::IO) = parse_cache_header(f)[6] function preferences_hash(cachefile::String) io = open(cachefile, "r") @@ -2155,7 +2270,6 @@ function preferences_hash(cachefile::String) end end - function cache_dependencies(f::IO) _, (includes, _), modules, _... = parse_cache_header(f) return modules, map(chi -> (chi.filename, chi.mtime), includes) # return just filename and mtime @@ -2382,6 +2496,15 @@ get_compiletime_preferences(uuid::UUID) = collect(get(Vector{String}, COMPILETIM get_compiletime_preferences(m::Module) = get_compiletime_preferences(PkgId(m).uuid) get_compiletime_preferences(::Nothing) = String[] +function check_clone_targets(clone_targets) + try + ccall(:jl_check_pkgimage_clones, Cvoid, (Ptr{Cchar},), clone_targets) + return true + catch + return false + end +end + # returns true if it "cachefile.ji" is stale relative to "modpath.jl" and build_id for modkey # otherwise returns the list of dependencies to also check @constprop :none function stale_cachefile(modpath::String, cachefile::String; ignore_loaded::Bool = false) @@ -2395,10 +2518,33 @@ end @debug "Rejecting cache file $cachefile due to it containing an invalid cache header" return true # invalid cache file end - modules, (includes, requires), required_modules, srctextpos, prefs, prefs_hash = parse_cache_header(io) + modules, (includes, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, flags = parse_cache_header(io) if isempty(modules) return true # ignore empty file end + if ccall(:jl_match_cache_flags, UInt8, (UInt8,), flags) == 0 + @debug "Rejecting cache file $cachefile for $modkey since the flags are mismatched" cachefile_flags=flags current_flags=ccall(:jl_cache_flags, UInt8, ()) + return true + end + pkgimage = !isempty(clone_targets) + if pkgimage + ocachefile = ocachefile_from_cachefile(cachefile) + if JLOptions().use_pkgimages == 0 + # presence of clone_targets means native code cache + @debug "Rejecting cache file $cachefile for $modkey since it would require usage of pkgimage" + return true + end + if !check_clone_targets(clone_targets) + @debug "Rejecting cache file $cachefile for $modkey since pkgimage can't be loaded on this target" + return true + end + if !isfile(ocachefile) + @debug "Rejecting cache file $cachefile for $modkey since pkgimage $ocachefile was not found" + return true + end + else + ocachefile = nothing + end id = first(modules) if id.first != modkey && modkey != PkgId("") @debug "Rejecting cache file $cachefile for $modkey since it is for $id instead" @@ -2461,7 +2607,7 @@ end # now check if this file is fresh relative to its source files if !skip_timecheck - if !samefile(includes[1].filename, modpath) + if !samefile(includes[1].filename, modpath) && !samefile(fixup_stdlib_path(includes[1].filename), modpath) @debug "Rejecting cache file $cachefile because it is for file $(includes[1].filename) not file $modpath" return true # cache file was compiled from a different path end @@ -2474,6 +2620,16 @@ end end for chi in includes f, ftime_req = chi.filename, chi.mtime + if !isfile(f) + _f = fixup_stdlib_path(f) + if isfile(_f) && startswith(_f, Sys.STDLIB) + # mtime is changed by extraction + @debug "Skipping mtime check for file $f used by $cachefile, since it is a stdlib" + continue + end + @debug "Rejecting stale cache file $cachefile because file $f does not exist" + return true + end ftime = mtime(f) is_stale = ( ftime != ftime_req ) && ( ftime != floor(ftime_req) ) && # Issue #13606, PR #13613: compensate for Docker images rounding mtimes @@ -2493,13 +2649,20 @@ end return true end + if pkgimage + if !isvalid_pkgimage_crc(io, ocachefile::String) + @debug "Rejecting cache file $cachefile because $ocachefile has an invalid checksum" + return true + end + end + curr_prefs_hash = get_preferences_hash(id.uuid, prefs) if prefs_hash != curr_prefs_hash @debug "Rejecting cache file $cachefile because preferences hash does not match 0x$(string(prefs_hash, base=16)) != 0x$(string(curr_prefs_hash, base=16))" return true end - return depmods # fresh cachefile + return depmods, ocachefile # fresh cachefile finally close(io) end @@ -2572,4 +2735,5 @@ end precompile(include_package_for_output, (PkgId, String, Vector{String}, Vector{String}, Vector{String}, typeof(_concrete_dependencies), Nothing)) precompile(include_package_for_output, (PkgId, String, Vector{String}, Vector{String}, Vector{String}, typeof(_concrete_dependencies), String)) -precompile(create_expr_cache, (PkgId, String, String, typeof(_concrete_dependencies), IO, IO)) +precompile(create_expr_cache, (PkgId, String, String, String, typeof(_concrete_dependencies), IO, IO)) +precompile(create_expr_cache, (PkgId, String, String, Nothing, typeof(_concrete_dependencies), IO, IO)) diff --git a/base/options.jl b/base/options.jl index 48a8f7ff59d385..dda0e8b3770762 100644 --- a/base/options.jl +++ b/base/options.jl @@ -38,6 +38,7 @@ struct JLOptions handle_signals::Int8 use_sysimage_native_code::Int8 use_compiled_modules::Int8 + use_pkgimages::Int8 bindto::Ptr{UInt8} outputbc::Ptr{UInt8} outputunoptbc::Ptr{UInt8} diff --git a/base/util.jl b/base/util.jl index 877446ab1887f9..8d1a4a9fa02ef1 100644 --- a/base/util.jl +++ b/base/util.jl @@ -133,7 +133,7 @@ See also [`print`](@ref), [`println`](@ref), [`show`](@ref). printstyled(stdout, msg...; bold=bold, underline=underline, blink=blink, reverse=reverse, hidden=hidden, color=color) """ - Base.julia_cmd(juliapath=joinpath(Sys.BINDIR, julia_exename())) + Base.julia_cmd(juliapath=joinpath(Sys.BINDIR, julia_exename()); cpu_target) Return a julia command similar to the one of the running process. Propagates any of the `--cpu-target`, `--sysimage`, `--compile`, `--sysimage-native-code`, @@ -148,10 +148,15 @@ Among others, `--math-mode`, `--warn-overwrite`, and `--trace-compile` are notab !!! compat "Julia 1.5" The flags `--color` and `--startup-file` were added in Julia 1.5. + +!!! compat "Julia 1.9" + The keyword argument `cpu_target` was added. """ -function julia_cmd(julia=joinpath(Sys.BINDIR, julia_exename())) +function julia_cmd(julia=joinpath(Sys.BINDIR, julia_exename()); cpu_target::Union{Nothing,String} = nothing) opts = JLOptions() - cpu_target = unsafe_string(opts.cpu_target) + if cpu_target === nothing + cpu_target = unsafe_string(opts.cpu_target) + end image_file = unsafe_string(opts.image_file) addflags = String[] let compile = if opts.compile_enabled == 0 @@ -220,6 +225,12 @@ function julia_cmd(julia=joinpath(Sys.BINDIR, julia_exename())) if opts.use_sysimage_native_code == 0 push!(addflags, "--sysimage-native-code=no") end + if opts.use_pkgimages == 0 + push!(addflags, "--pkgimages=no") + else + # If pkgimage is set, malloc_log and code_coverage should not + @assert opts.malloc_log == 0 && opts.code_coverage == 0 + end return `$julia -C$cpu_target -J$image_file $addflags` end @@ -487,10 +498,17 @@ function _crc32c(io::IO, nb::Integer, crc::UInt32=0x00000000) end _crc32c(io::IO, crc::UInt32=0x00000000) = _crc32c(io, typemax(Int64), crc) _crc32c(io::IOStream, crc::UInt32=0x00000000) = _crc32c(io, filesize(io)-position(io), crc) -_crc32c(uuid::UUID, crc::UInt32=0x00000000) = - ccall(:jl_crc32c, UInt32, (UInt32, Ref{UInt128}, Csize_t), crc, uuid.value, 16) +_crc32c(uuid::UUID, crc::UInt32=0x00000000) = _crc32c(uuid.value, crc) +_crc32c(x::UInt128, crc::UInt32=0x00000000) = + ccall(:jl_crc32c, UInt32, (UInt32, Ref{UInt128}, Csize_t), crc, x, 16) _crc32c(x::UInt64, crc::UInt32=0x00000000) = ccall(:jl_crc32c, UInt32, (UInt32, Ref{UInt64}, Csize_t), crc, x, 8) +_crc32c(x::UInt32, crc::UInt32=0x00000000) = + ccall(:jl_crc32c, UInt32, (UInt32, Ref{UInt32}, Csize_t), crc, x, 4) +_crc32c(x::UInt16, crc::UInt32=0x00000000) = + ccall(:jl_crc32c, UInt32, (UInt32, Ref{UInt16}, Csize_t), crc, x, 2) +_crc32c(x::UInt8, crc::UInt32=0x00000000) = + ccall(:jl_crc32c, UInt32, (UInt32, Ref{UInt8}, Csize_t), crc, x, 1) """ @kwdef typedef @@ -655,7 +673,8 @@ function runtests(tests = ["all"]; ncores::Int = ceil(Int, Sys.CPU_THREADS / 2), seed !== nothing && push!(tests, "--seed=0x$(string(seed % UInt128, base=16))") # cast to UInt128 to avoid a minus sign ENV2 = copy(ENV) ENV2["JULIA_CPU_THREADS"] = "$ncores" - ENV2["JULIA_DEPOT_PATH"] = mktempdir(; cleanup = true) + pathsep = Sys.iswindows() ? ";" : ":" + ENV2["JULIA_DEPOT_PATH"] = string(mktempdir(; cleanup = true), pathsep) # make sure the default depots can be loaded delete!(ENV2, "JULIA_LOAD_PATH") delete!(ENV2, "JULIA_PROJECT") try diff --git a/contrib/cache_stdlibs.jl b/contrib/cache_stdlibs.jl new file mode 100644 index 00000000000000..bdcc3d9535fa49 --- /dev/null +++ b/contrib/cache_stdlibs.jl @@ -0,0 +1,49 @@ +# Stdlibs sorted in dependency, then alphabetical, order by contrib/print_sorted_stdlibs.jl +# Run with the `--exclude-sysimage` option to filter out all packages included in the system image +stdlibs = [ + # No dependencies + + # 1-depth packages + :GMP_jll, + :LLVMLibUnwind_jll, + :LibUV_jll, + :LibUnwind_jll, + :MbedTLS_jll, + :OpenLibm_jll, + :PCRE2_jll, + :Zlib_jll, + :dSFMT_jll, + :libLLVM_jll, + + # 2-depth packages + :LibSSH2_jll, + :MPFR_jll, + + # 3-depth packages + :LibGit2_jll, + + # 7-depth packages + :LLD_jll, + :SuiteSparse_jll, + + # 9-depth packages + :Statistics, + :SuiteSparse, +] + +depot = abspath(Sys.BINDIR, "..", "share", "julia") + +if haskey(ENV, "JULIA_CPU_TARGET") + target = ENV["JULIA_CPU_TARGET"] +else + target = "native" +end + +@info "Caching stdlibrary to" depot target +empty!(Base.DEPOT_PATH) +push!(Base.DEPOT_PATH, depot) + +for pkg in stdlibs + pkgid = Base.identify_package(string(pkg)) + Base.compilecache(pkgid) +end diff --git a/contrib/print_sorted_stdlibs.jl b/contrib/print_sorted_stdlibs.jl index 28d75f079b9dd8..6bc2023c4f1ccb 100644 --- a/contrib/print_sorted_stdlibs.jl +++ b/contrib/print_sorted_stdlibs.jl @@ -12,11 +12,12 @@ function check_flag(flag) end if check_flag("--help") || check_flag("-h") - println("Usage: julia print_sorted_stdlibs.jl [stdlib_dir] [--exclude-jlls]") + println("Usage: julia print_sorted_stdlibs.jl [stdlib_dir] [--exclude-jlls] [--exclude-sysimage]") end # Allow users to ask for JLL or no JLLs exclude_jlls = check_flag("--exclude-jlls") +exclude_sysimage = check_flag("--exclude-sysimage") # Default to the `stdlib/vX.Y` directory STDLIB_DIR = get(ARGS, 1, joinpath(@__DIR__, "..", "usr", "share", "julia", "stdlib")) @@ -80,12 +81,20 @@ if exclude_jlls filter!(p -> !endswith(p, "_jll"), sorted_projects) end +if exclude_sysimage + loaded_modules = Set(map(k->k.name, collect(keys(Base.loaded_modules)))) + filter!(p->!in(p, loaded_modules), sorted_projects) +end + # Print out sorted projects, ready to be pasted into `sysimg.jl` last_depth = 0 println(" # Stdlibs sorted in dependency, then alphabetical, order by contrib/print_sorted_stdlibs.jl") if exclude_jlls println(" # Run with the `--exclude-jlls` option to filter out all JLL packages") end +if exclude_sysimage + println(" # Run with the `--exclude-sysimage` option to filter out all packages included in the system image") +end println(" stdlibs = [") println(" # No dependencies") for p in sorted_projects diff --git a/deps/checksums/compilersupportlibraries b/deps/checksums/compilersupportlibraries index 721ad2e8a87595..098c181ca5c87a 100644 --- a/deps/checksums/compilersupportlibraries +++ b/deps/checksums/compilersupportlibraries @@ -1,92 +1,92 @@ -CompilerSupportLibraries.v1.0.1+0.aarch64-apple-darwin-libgfortran5.tar.gz/md5/20ebaad57850393b6ac9fa924e511fe4 -CompilerSupportLibraries.v1.0.1+0.aarch64-apple-darwin-libgfortran5.tar.gz/sha512/020de4d8b0ff6bedbadaa305ff8445e6849f12053762ea4aa68412d1ec763dbd86f479587a2fbb862487f1feb04d976c38099ddf3887817a3d32b3f029cf85b1 -CompilerSupportLibraries.v1.0.1+0.aarch64-linux-gnu-libgfortran3.tar.gz/md5/3908fa1a2f739b330e787468c9bfb5c8 -CompilerSupportLibraries.v1.0.1+0.aarch64-linux-gnu-libgfortran3.tar.gz/sha512/1741e3403ac7aa99e7cfd9a01222c4153ed300f47cc1b347e1af1a6cd07a82caaa54b9cfbebae8751440420551621cc6524504413446d104f9493dff2c081853 -CompilerSupportLibraries.v1.0.1+0.aarch64-linux-gnu-libgfortran4.tar.gz/md5/2444dbb7637b32cf543675cc12330878 -CompilerSupportLibraries.v1.0.1+0.aarch64-linux-gnu-libgfortran4.tar.gz/sha512/8537f0b243df8544350c884021b21c585fd302e8dd462a30a6ee84c7a36a049133262e5d1bc362f972066b8e8d6a091c32c3b746bab1feb9fccf2e7cca65756c -CompilerSupportLibraries.v1.0.1+0.aarch64-linux-gnu-libgfortran5.tar.gz/md5/d79c1434594c0c5e7d6be798bf52c99e -CompilerSupportLibraries.v1.0.1+0.aarch64-linux-gnu-libgfortran5.tar.gz/sha512/7e71accc401a45b51b298702fb4c79a2fc856c7b28f0935f6ad3a0db5381c55fe5432daff371842930d718024b7c6c1d80e2bd09d397145203673bebbe3496ae -CompilerSupportLibraries.v1.0.1+0.aarch64-linux-musl-libgfortran3.tar.gz/md5/f212059053d99558a9b0bf54b20180e1 -CompilerSupportLibraries.v1.0.1+0.aarch64-linux-musl-libgfortran3.tar.gz/sha512/5c104b1282cec8a944e5d008f44a4d60f4394fd5d797fec7d1f487d13e7328cd9c88ec4916dabf18596d87160756bda914e4f8c5a356b5577f9349d0d9e976d6 -CompilerSupportLibraries.v1.0.1+0.aarch64-linux-musl-libgfortran4.tar.gz/md5/3e3b3795ee93ef317223050e803a9875 -CompilerSupportLibraries.v1.0.1+0.aarch64-linux-musl-libgfortran4.tar.gz/sha512/85d3c955e15f66bfe8bfec2f28c9160bc03d4d531ea4ffe6bc6b51e0d69ccea3ab67a16ca752dabc870861c407381c4519d75c6be3832e8dccd6122ec8c6ed75 -CompilerSupportLibraries.v1.0.1+0.aarch64-linux-musl-libgfortran5.tar.gz/md5/cf2d1315f6a348af2e6c065e2a286e7a -CompilerSupportLibraries.v1.0.1+0.aarch64-linux-musl-libgfortran5.tar.gz/sha512/58420377bc77aa7678034ee5f708eb6be7db359faef2c2638869765453633da9bf455512bd88e95b38ae0428ecc4053561517b176b2371129bdaef9d8d5dadfd -CompilerSupportLibraries.v1.0.1+0.armv6l-linux-gnueabihf-libgfortran3.tar.gz/md5/f5c09ed7e0eeb8d345d328f950582f26 -CompilerSupportLibraries.v1.0.1+0.armv6l-linux-gnueabihf-libgfortran3.tar.gz/sha512/9c657f55c8fcdeb404be168a3a63a5e84304730fe34f25673d92cdae4b0a1fcc6a877ee1433f060e1be854c7811d66632e32510a2ed591d88330f1340b9c20de -CompilerSupportLibraries.v1.0.1+0.armv6l-linux-gnueabihf-libgfortran4.tar.gz/md5/c685518aca4721cd8621d510e2039683 -CompilerSupportLibraries.v1.0.1+0.armv6l-linux-gnueabihf-libgfortran4.tar.gz/sha512/b760468c6377dcd2b8dd50200daaabe604006afc070984d78152b2becd0680b59036c9a6e91dea490121bd85b58d285bfc1e1cf696d29af236528400101de36c -CompilerSupportLibraries.v1.0.1+0.armv6l-linux-gnueabihf-libgfortran5.tar.gz/md5/8faf5c8ad62ab10f71dd2ec9683053e2 -CompilerSupportLibraries.v1.0.1+0.armv6l-linux-gnueabihf-libgfortran5.tar.gz/sha512/921239f241a5c89710cf07272d7f6c3f10201a7533068ed1e9643f9fb2f439e1bb765a4966d913829866ee0ce4f1589d30d06e4b5c1361e3c016a9473f087177 -CompilerSupportLibraries.v1.0.1+0.armv6l-linux-musleabihf-libgfortran3.tar.gz/md5/b38fcb70691ac2621379d298eef8c79e -CompilerSupportLibraries.v1.0.1+0.armv6l-linux-musleabihf-libgfortran3.tar.gz/sha512/06c7f64257ce721f5941f6e50a0d2717cdc9394fc532ded19ce3eaacd5e92a416969534227562e4fee04d2b6340c650d8bc9779e14519b90038bc41e8d1f5ce3 -CompilerSupportLibraries.v1.0.1+0.armv6l-linux-musleabihf-libgfortran4.tar.gz/md5/cdfab2c7bc41765caf4441c3caeed761 -CompilerSupportLibraries.v1.0.1+0.armv6l-linux-musleabihf-libgfortran4.tar.gz/sha512/7109d4a7b32c00309c42685f54a86fc2cc63c0c00f65584ad296b6e44ad3320eed1aaf49684a8831841cdffa5555d72f89272fb722a780596e27ef020528026b -CompilerSupportLibraries.v1.0.1+0.armv6l-linux-musleabihf-libgfortran5.tar.gz/md5/441980ebd23d72772cbe603f1c275336 -CompilerSupportLibraries.v1.0.1+0.armv6l-linux-musleabihf-libgfortran5.tar.gz/sha512/e273d9f1af259a3080df8f173e1808a1ade976a943aba97216bf59a96178e7c052e7a048b0ceee53ab486ed577a2ecb92579857be2f7b29e76322ee1f13c9d76 -CompilerSupportLibraries.v1.0.1+0.armv7l-linux-gnueabihf-libgfortran3.tar.gz/md5/f5c09ed7e0eeb8d345d328f950582f26 -CompilerSupportLibraries.v1.0.1+0.armv7l-linux-gnueabihf-libgfortran3.tar.gz/sha512/9c657f55c8fcdeb404be168a3a63a5e84304730fe34f25673d92cdae4b0a1fcc6a877ee1433f060e1be854c7811d66632e32510a2ed591d88330f1340b9c20de -CompilerSupportLibraries.v1.0.1+0.armv7l-linux-gnueabihf-libgfortran4.tar.gz/md5/c685518aca4721cd8621d510e2039683 -CompilerSupportLibraries.v1.0.1+0.armv7l-linux-gnueabihf-libgfortran4.tar.gz/sha512/b760468c6377dcd2b8dd50200daaabe604006afc070984d78152b2becd0680b59036c9a6e91dea490121bd85b58d285bfc1e1cf696d29af236528400101de36c -CompilerSupportLibraries.v1.0.1+0.armv7l-linux-gnueabihf-libgfortran5.tar.gz/md5/8faf5c8ad62ab10f71dd2ec9683053e2 -CompilerSupportLibraries.v1.0.1+0.armv7l-linux-gnueabihf-libgfortran5.tar.gz/sha512/921239f241a5c89710cf07272d7f6c3f10201a7533068ed1e9643f9fb2f439e1bb765a4966d913829866ee0ce4f1589d30d06e4b5c1361e3c016a9473f087177 -CompilerSupportLibraries.v1.0.1+0.armv7l-linux-musleabihf-libgfortran3.tar.gz/md5/b38fcb70691ac2621379d298eef8c79e -CompilerSupportLibraries.v1.0.1+0.armv7l-linux-musleabihf-libgfortran3.tar.gz/sha512/06c7f64257ce721f5941f6e50a0d2717cdc9394fc532ded19ce3eaacd5e92a416969534227562e4fee04d2b6340c650d8bc9779e14519b90038bc41e8d1f5ce3 -CompilerSupportLibraries.v1.0.1+0.armv7l-linux-musleabihf-libgfortran4.tar.gz/md5/cdfab2c7bc41765caf4441c3caeed761 -CompilerSupportLibraries.v1.0.1+0.armv7l-linux-musleabihf-libgfortran4.tar.gz/sha512/7109d4a7b32c00309c42685f54a86fc2cc63c0c00f65584ad296b6e44ad3320eed1aaf49684a8831841cdffa5555d72f89272fb722a780596e27ef020528026b -CompilerSupportLibraries.v1.0.1+0.armv7l-linux-musleabihf-libgfortran5.tar.gz/md5/441980ebd23d72772cbe603f1c275336 -CompilerSupportLibraries.v1.0.1+0.armv7l-linux-musleabihf-libgfortran5.tar.gz/sha512/e273d9f1af259a3080df8f173e1808a1ade976a943aba97216bf59a96178e7c052e7a048b0ceee53ab486ed577a2ecb92579857be2f7b29e76322ee1f13c9d76 -CompilerSupportLibraries.v1.0.1+0.i686-linux-gnu-libgfortran3.tar.gz/md5/6decf8fd5afb50451771c761e63a8917 -CompilerSupportLibraries.v1.0.1+0.i686-linux-gnu-libgfortran3.tar.gz/sha512/4984724bcc847724b1bc005b6f760a18b68147f7d5402d0faf4e28fc0d14fa10975368a951f9caf2a8856500046dec8343043274557d58269e77492b929a9e4b -CompilerSupportLibraries.v1.0.1+0.i686-linux-gnu-libgfortran4.tar.gz/md5/39d1e8a3baa144c018d3eaf7f3806482 -CompilerSupportLibraries.v1.0.1+0.i686-linux-gnu-libgfortran4.tar.gz/sha512/fc4d429279c5a93b6c28b6e911b1e7cfd1c1cfe46f11f2e901b3832ce90d45f49d3d29f0ef18518a94af6cc8651f67c4ed81672680f9281ada390440b172a2af -CompilerSupportLibraries.v1.0.1+0.i686-linux-gnu-libgfortran5.tar.gz/md5/37dabd9cd224c9fed9633dedccb6c565 -CompilerSupportLibraries.v1.0.1+0.i686-linux-gnu-libgfortran5.tar.gz/sha512/b253149e72eef9486888fbaace66e9b6945f4477f6b818f64f3047331165b0e2bc17aa6e3fc8c88686a72e478eb62c8f53883415d5419db448d8016fa3a1da5e -CompilerSupportLibraries.v1.0.1+0.i686-linux-musl-libgfortran3.tar.gz/md5/afdd32bfadd465848e6be458817a44ae -CompilerSupportLibraries.v1.0.1+0.i686-linux-musl-libgfortran3.tar.gz/sha512/eebd679c499143014514c7c9d1875dedbbab9e3af51526c4dd445a9e3dbade95d24522da8bbad0a50ab400755e47b018828b324c4ad7705e212ccd990e34439a -CompilerSupportLibraries.v1.0.1+0.i686-linux-musl-libgfortran4.tar.gz/md5/bc4a0f0b7cea328f7e8850583774496b -CompilerSupportLibraries.v1.0.1+0.i686-linux-musl-libgfortran4.tar.gz/sha512/82285b67946212b49cddf6259f2c60ff5469f8c5263ccefe44f1d93ace98ab68e2c152e1b54434b2f075fd8d192c06d5451bc8cca26d951ad15f3453102f02b5 -CompilerSupportLibraries.v1.0.1+0.i686-linux-musl-libgfortran5.tar.gz/md5/177f0232abce8d523882530ed7a93092 -CompilerSupportLibraries.v1.0.1+0.i686-linux-musl-libgfortran5.tar.gz/sha512/db80acf0f2434f28ee7680e1beb34f564940071815d1ad89fb5913cbd9ac24da528e826d0d54be6265a7340ebd661b6d308ed79d96b67fa5d8c98dc3f1bee8d6 -CompilerSupportLibraries.v1.0.1+0.i686-w64-mingw32-libgfortran3.tar.gz/md5/c723e7d3c3038f59b9bf0cc3a65826bc -CompilerSupportLibraries.v1.0.1+0.i686-w64-mingw32-libgfortran3.tar.gz/sha512/0545561ccd7e525b6cd86935366a2724a5e013411a1c01564db21b66da5fef959cf06b0839b96f1dc2c970eb6c8fb19c012e6cd2c17bc381b55420c72fe1b9f6 -CompilerSupportLibraries.v1.0.1+0.i686-w64-mingw32-libgfortran4.tar.gz/md5/763bd82645d2f3c72b6244d68bebb40f -CompilerSupportLibraries.v1.0.1+0.i686-w64-mingw32-libgfortran4.tar.gz/sha512/700e719eeab486915a9fb0954125cb9a3e9a813d7a069eca05be3a16621f4875668918a5ed5f645e734ac62b0c2ddbaa6234adc9109e98fb88b8ca1197481ed8 -CompilerSupportLibraries.v1.0.1+0.i686-w64-mingw32-libgfortran5.tar.gz/md5/18e90d15dc6dd0a836e9aa076b342105 -CompilerSupportLibraries.v1.0.1+0.i686-w64-mingw32-libgfortran5.tar.gz/sha512/9ff61e8da2b431a8cb09818bde5daab2d7b8cf7a934f184f14ea50eccf5796ae91558e06a22137eb021c4055c54faf4a524a54dbbd718e8ea0abb5dcec844fdb -CompilerSupportLibraries.v1.0.1+0.powerpc64le-linux-gnu-libgfortran3.tar.gz/md5/4e5e4b23dc87450738da33926a07511d -CompilerSupportLibraries.v1.0.1+0.powerpc64le-linux-gnu-libgfortran3.tar.gz/sha512/fc09879d94b750e75775d8b64a41ab9924d675fb53c5700467604412928fe7f5cb21911da0f64898d2463fa77ffbaf4c96c397b9060f4746eec152747930cddc -CompilerSupportLibraries.v1.0.1+0.powerpc64le-linux-gnu-libgfortran4.tar.gz/md5/9a92138ed69aa317a932a615c6e62d69 -CompilerSupportLibraries.v1.0.1+0.powerpc64le-linux-gnu-libgfortran4.tar.gz/sha512/0b7785379936a2a209b074177b1424dd7e00b29b5165f564e799b0aa4e06a582e9d616525d97274ba2507cb88192028f1ac485d3f99bdc7ee53fc63c1a7e85de -CompilerSupportLibraries.v1.0.1+0.powerpc64le-linux-gnu-libgfortran5.tar.gz/md5/8ffee3d6de5197c7a1f354d72c8238fa -CompilerSupportLibraries.v1.0.1+0.powerpc64le-linux-gnu-libgfortran5.tar.gz/sha512/deadc4d7224c84f9b82dc956b69e815c44ae036802838365d870ab9f58c8bcf8ce0645f2f387c8ff344ac2108fc8e7e1ee907fa55e93c91aa5d9fd921bf3fdcb -CompilerSupportLibraries.v1.0.1+0.x86_64-apple-darwin-libgfortran3.tar.gz/md5/87449e72e3f33dbb69b7053cdc2649d4 -CompilerSupportLibraries.v1.0.1+0.x86_64-apple-darwin-libgfortran3.tar.gz/sha512/5ce02ad10c6f4686a476eb2a5de2988cd8b482f5e693db2880c84ad1c82f468ef03fe01b9d0feefe5d4ee741d1d16643d36b144e6261ed32311b3b6f312fac2f -CompilerSupportLibraries.v1.0.1+0.x86_64-apple-darwin-libgfortran4.tar.gz/md5/0407cde92cfa42fa89ac83217ca0ec16 -CompilerSupportLibraries.v1.0.1+0.x86_64-apple-darwin-libgfortran4.tar.gz/sha512/032c831f1166a336551138939ac40eb2c68a048ce786c0c1403b879a20c1b706caac16d22560b2c7f2b3d6373986c347188675674116005ca251336ee048d09f -CompilerSupportLibraries.v1.0.1+0.x86_64-apple-darwin-libgfortran5.tar.gz/md5/23418763b808371ee94772a90d501f4d -CompilerSupportLibraries.v1.0.1+0.x86_64-apple-darwin-libgfortran5.tar.gz/sha512/7867b843551457b11bda7821dd384c1c1cf23b80a308b2058a693de7b7da099f0b37eb0a6de2b84c04b625a68c60eea55138e200d5d6ec6f6af09bd7ce406a96 -CompilerSupportLibraries.v1.0.1+0.x86_64-linux-gnu-libgfortran3.tar.gz/md5/e3d33ae03c18affea74699bdc1fabb68 -CompilerSupportLibraries.v1.0.1+0.x86_64-linux-gnu-libgfortran3.tar.gz/sha512/42013f4921de5a69ad857195ce5c19ad1bca3c920d79699e5501f1f4534ab132fabd422362b2b5056f5d182215d6c069db5df460bafa700903faf962cc00f77b -CompilerSupportLibraries.v1.0.1+0.x86_64-linux-gnu-libgfortran4.tar.gz/md5/d40c1e8c0393213c6057c53a12f44175 -CompilerSupportLibraries.v1.0.1+0.x86_64-linux-gnu-libgfortran4.tar.gz/sha512/fe7baa4de7490065ab7b953cc12f41462a24bcb49d0a4a64b23249e98e7569b19bb1cb455af2f76090e34066a7d3cdd7a48cae6515ce6c7a5c8486b0cacc5106 -CompilerSupportLibraries.v1.0.1+0.x86_64-linux-gnu-libgfortran5.tar.gz/md5/48541b90f715c4c86ee4da0570275947 -CompilerSupportLibraries.v1.0.1+0.x86_64-linux-gnu-libgfortran5.tar.gz/sha512/7f2683fb98e80f12629f4ed3bea9fd59d32b7e7a9ed1699e782d8e238ff0915ecc61bf00adaf4597cfe41caf82cdca0f9be250f595f5f0bea6d8f77dba99eaf4 -CompilerSupportLibraries.v1.0.1+0.x86_64-linux-musl-libgfortran3.tar.gz/md5/4547059eb905995667be48bf85d49911 -CompilerSupportLibraries.v1.0.1+0.x86_64-linux-musl-libgfortran3.tar.gz/sha512/7400fdabc924434ab4a4949248c3603887ac06ffd2f205ae33e14495d86cd4f816bbd1999eeafa0257f518df1e7f7c522f596e847a71dbfbfccff4859f50acc7 -CompilerSupportLibraries.v1.0.1+0.x86_64-linux-musl-libgfortran4.tar.gz/md5/46267543cad6584d7b7b9fcc8f18f21d -CompilerSupportLibraries.v1.0.1+0.x86_64-linux-musl-libgfortran4.tar.gz/sha512/0353d7d724be48d4185d3c181692970b7996f53f6a01723072aa5c94b53a8c5055faeed30df51659c252a46f4b941dec0cb24569323e3c85c166f14c5b7c8e9e -CompilerSupportLibraries.v1.0.1+0.x86_64-linux-musl-libgfortran5.tar.gz/md5/14dba2897a6e9d370fa9091c045375fc -CompilerSupportLibraries.v1.0.1+0.x86_64-linux-musl-libgfortran5.tar.gz/sha512/10b79f9c059839f5b57fa8d2a381a034c4067262c4088bd354d14ea56bec097878069383aa9cfadaa09d73bd20fc348fb61662d863a8d62cb25d7af6b8e29858 -CompilerSupportLibraries.v1.0.1+0.x86_64-unknown-freebsd-libgfortran3.tar.gz/md5/eed836d1addeb10d0901f836724aff1e -CompilerSupportLibraries.v1.0.1+0.x86_64-unknown-freebsd-libgfortran3.tar.gz/sha512/e33eca424d1529a1fb23ba9cf7fac345ed1cfc8073c975b6b31ca44d2e8c3f5083af65433df009b22483dceb2e43149f3c1e8433681fec5fb812e1d5b4243ce4 -CompilerSupportLibraries.v1.0.1+0.x86_64-unknown-freebsd-libgfortran4.tar.gz/md5/d5ae9f9519341fdaabf62267c89461d2 -CompilerSupportLibraries.v1.0.1+0.x86_64-unknown-freebsd-libgfortran4.tar.gz/sha512/6421aa5d1bd6f08ad43f59ed4dc1bef8b9b598ebbbd3e48149730f3bec3471f8e2c02ffb338427326924290b8f52ef9e626e3313448bc931a61d866c5dc544ae -CompilerSupportLibraries.v1.0.1+0.x86_64-unknown-freebsd-libgfortran5.tar.gz/md5/fc1df521395362a5aaa2e2aeef707207 -CompilerSupportLibraries.v1.0.1+0.x86_64-unknown-freebsd-libgfortran5.tar.gz/sha512/f2e5a08e3cae171242ae6a20d2d4838c1529ce042745dc466148b7bbc06896d94476fd05c7787e6e8641bea752dfc0e6b09e95b160bede600d20d2ad68e7705f -CompilerSupportLibraries.v1.0.1+0.x86_64-w64-mingw32-libgfortran3.tar.gz/md5/80c337837a9032e4c9614f0d3218993b -CompilerSupportLibraries.v1.0.1+0.x86_64-w64-mingw32-libgfortran3.tar.gz/sha512/cf07e459ca55cb9ee3d38e6858320530c1d1ab2ffd35bfa2a33b2505d3189f13b9743a0e279d70f85d227cee8a8974448f1371a122dcbea03fb1e414f8df8337 -CompilerSupportLibraries.v1.0.1+0.x86_64-w64-mingw32-libgfortran4.tar.gz/md5/792cae36932dd53af20b7f61c80f623b -CompilerSupportLibraries.v1.0.1+0.x86_64-w64-mingw32-libgfortran4.tar.gz/sha512/805f2b64fe9d2b94fc6c966945e10458d8d1c47a8d95fcda057c03a13999d7d0f136c754e4b1e152faaf23e4949861c2ad42b4437dba19f59b3db745d7a76108 -CompilerSupportLibraries.v1.0.1+0.x86_64-w64-mingw32-libgfortran5.tar.gz/md5/063c07fcbba4b9c3bd23ab0d987f1dbb -CompilerSupportLibraries.v1.0.1+0.x86_64-w64-mingw32-libgfortran5.tar.gz/sha512/1d0344b30b5fb34a63f6844be0501c0ad08f1116b0c7b00e13d47860cc6bbdd39734416ad3b492414a28ba1744240bd05aca0d1560873f687d3f61747058626b +CompilerSupportLibraries.v1.0.2+0.aarch64-apple-darwin-libgfortran5.tar.gz/md5/20ebaad57850393b6ac9fa924e511fe4 +CompilerSupportLibraries.v1.0.2+0.aarch64-apple-darwin-libgfortran5.tar.gz/sha512/020de4d8b0ff6bedbadaa305ff8445e6849f12053762ea4aa68412d1ec763dbd86f479587a2fbb862487f1feb04d976c38099ddf3887817a3d32b3f029cf85b1 +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-gnu-libgfortran3.tar.gz/md5/3908fa1a2f739b330e787468c9bfb5c8 +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-gnu-libgfortran3.tar.gz/sha512/1741e3403ac7aa99e7cfd9a01222c4153ed300f47cc1b347e1af1a6cd07a82caaa54b9cfbebae8751440420551621cc6524504413446d104f9493dff2c081853 +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-gnu-libgfortran4.tar.gz/md5/2444dbb7637b32cf543675cc12330878 +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-gnu-libgfortran4.tar.gz/sha512/8537f0b243df8544350c884021b21c585fd302e8dd462a30a6ee84c7a36a049133262e5d1bc362f972066b8e8d6a091c32c3b746bab1feb9fccf2e7cca65756c +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-gnu-libgfortran5.tar.gz/md5/d79c1434594c0c5e7d6be798bf52c99e +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-gnu-libgfortran5.tar.gz/sha512/7e71accc401a45b51b298702fb4c79a2fc856c7b28f0935f6ad3a0db5381c55fe5432daff371842930d718024b7c6c1d80e2bd09d397145203673bebbe3496ae +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-musl-libgfortran3.tar.gz/md5/f212059053d99558a9b0bf54b20180e1 +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-musl-libgfortran3.tar.gz/sha512/5c104b1282cec8a944e5d008f44a4d60f4394fd5d797fec7d1f487d13e7328cd9c88ec4916dabf18596d87160756bda914e4f8c5a356b5577f9349d0d9e976d6 +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-musl-libgfortran4.tar.gz/md5/3e3b3795ee93ef317223050e803a9875 +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-musl-libgfortran4.tar.gz/sha512/85d3c955e15f66bfe8bfec2f28c9160bc03d4d531ea4ffe6bc6b51e0d69ccea3ab67a16ca752dabc870861c407381c4519d75c6be3832e8dccd6122ec8c6ed75 +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-musl-libgfortran5.tar.gz/md5/cf2d1315f6a348af2e6c065e2a286e7a +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-musl-libgfortran5.tar.gz/sha512/58420377bc77aa7678034ee5f708eb6be7db359faef2c2638869765453633da9bf455512bd88e95b38ae0428ecc4053561517b176b2371129bdaef9d8d5dadfd +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-gnueabihf-libgfortran3.tar.gz/md5/f5c09ed7e0eeb8d345d328f950582f26 +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-gnueabihf-libgfortran3.tar.gz/sha512/9c657f55c8fcdeb404be168a3a63a5e84304730fe34f25673d92cdae4b0a1fcc6a877ee1433f060e1be854c7811d66632e32510a2ed591d88330f1340b9c20de +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-gnueabihf-libgfortran4.tar.gz/md5/c685518aca4721cd8621d510e2039683 +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-gnueabihf-libgfortran4.tar.gz/sha512/b760468c6377dcd2b8dd50200daaabe604006afc070984d78152b2becd0680b59036c9a6e91dea490121bd85b58d285bfc1e1cf696d29af236528400101de36c +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-gnueabihf-libgfortran5.tar.gz/md5/8faf5c8ad62ab10f71dd2ec9683053e2 +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-gnueabihf-libgfortran5.tar.gz/sha512/921239f241a5c89710cf07272d7f6c3f10201a7533068ed1e9643f9fb2f439e1bb765a4966d913829866ee0ce4f1589d30d06e4b5c1361e3c016a9473f087177 +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-musleabihf-libgfortran3.tar.gz/md5/b38fcb70691ac2621379d298eef8c79e +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-musleabihf-libgfortran3.tar.gz/sha512/06c7f64257ce721f5941f6e50a0d2717cdc9394fc532ded19ce3eaacd5e92a416969534227562e4fee04d2b6340c650d8bc9779e14519b90038bc41e8d1f5ce3 +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-musleabihf-libgfortran4.tar.gz/md5/cdfab2c7bc41765caf4441c3caeed761 +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-musleabihf-libgfortran4.tar.gz/sha512/7109d4a7b32c00309c42685f54a86fc2cc63c0c00f65584ad296b6e44ad3320eed1aaf49684a8831841cdffa5555d72f89272fb722a780596e27ef020528026b +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-musleabihf-libgfortran5.tar.gz/md5/441980ebd23d72772cbe603f1c275336 +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-musleabihf-libgfortran5.tar.gz/sha512/e273d9f1af259a3080df8f173e1808a1ade976a943aba97216bf59a96178e7c052e7a048b0ceee53ab486ed577a2ecb92579857be2f7b29e76322ee1f13c9d76 +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-gnueabihf-libgfortran3.tar.gz/md5/f5c09ed7e0eeb8d345d328f950582f26 +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-gnueabihf-libgfortran3.tar.gz/sha512/9c657f55c8fcdeb404be168a3a63a5e84304730fe34f25673d92cdae4b0a1fcc6a877ee1433f060e1be854c7811d66632e32510a2ed591d88330f1340b9c20de +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-gnueabihf-libgfortran4.tar.gz/md5/c685518aca4721cd8621d510e2039683 +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-gnueabihf-libgfortran4.tar.gz/sha512/b760468c6377dcd2b8dd50200daaabe604006afc070984d78152b2becd0680b59036c9a6e91dea490121bd85b58d285bfc1e1cf696d29af236528400101de36c +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-gnueabihf-libgfortran5.tar.gz/md5/8faf5c8ad62ab10f71dd2ec9683053e2 +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-gnueabihf-libgfortran5.tar.gz/sha512/921239f241a5c89710cf07272d7f6c3f10201a7533068ed1e9643f9fb2f439e1bb765a4966d913829866ee0ce4f1589d30d06e4b5c1361e3c016a9473f087177 +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-musleabihf-libgfortran3.tar.gz/md5/b38fcb70691ac2621379d298eef8c79e +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-musleabihf-libgfortran3.tar.gz/sha512/06c7f64257ce721f5941f6e50a0d2717cdc9394fc532ded19ce3eaacd5e92a416969534227562e4fee04d2b6340c650d8bc9779e14519b90038bc41e8d1f5ce3 +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-musleabihf-libgfortran4.tar.gz/md5/cdfab2c7bc41765caf4441c3caeed761 +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-musleabihf-libgfortran4.tar.gz/sha512/7109d4a7b32c00309c42685f54a86fc2cc63c0c00f65584ad296b6e44ad3320eed1aaf49684a8831841cdffa5555d72f89272fb722a780596e27ef020528026b +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-musleabihf-libgfortran5.tar.gz/md5/441980ebd23d72772cbe603f1c275336 +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-musleabihf-libgfortran5.tar.gz/sha512/e273d9f1af259a3080df8f173e1808a1ade976a943aba97216bf59a96178e7c052e7a048b0ceee53ab486ed577a2ecb92579857be2f7b29e76322ee1f13c9d76 +CompilerSupportLibraries.v1.0.2+0.i686-linux-gnu-libgfortran3.tar.gz/md5/6decf8fd5afb50451771c761e63a8917 +CompilerSupportLibraries.v1.0.2+0.i686-linux-gnu-libgfortran3.tar.gz/sha512/4984724bcc847724b1bc005b6f760a18b68147f7d5402d0faf4e28fc0d14fa10975368a951f9caf2a8856500046dec8343043274557d58269e77492b929a9e4b +CompilerSupportLibraries.v1.0.2+0.i686-linux-gnu-libgfortran4.tar.gz/md5/39d1e8a3baa144c018d3eaf7f3806482 +CompilerSupportLibraries.v1.0.2+0.i686-linux-gnu-libgfortran4.tar.gz/sha512/fc4d429279c5a93b6c28b6e911b1e7cfd1c1cfe46f11f2e901b3832ce90d45f49d3d29f0ef18518a94af6cc8651f67c4ed81672680f9281ada390440b172a2af +CompilerSupportLibraries.v1.0.2+0.i686-linux-gnu-libgfortran5.tar.gz/md5/37dabd9cd224c9fed9633dedccb6c565 +CompilerSupportLibraries.v1.0.2+0.i686-linux-gnu-libgfortran5.tar.gz/sha512/b253149e72eef9486888fbaace66e9b6945f4477f6b818f64f3047331165b0e2bc17aa6e3fc8c88686a72e478eb62c8f53883415d5419db448d8016fa3a1da5e +CompilerSupportLibraries.v1.0.2+0.i686-linux-musl-libgfortran3.tar.gz/md5/afdd32bfadd465848e6be458817a44ae +CompilerSupportLibraries.v1.0.2+0.i686-linux-musl-libgfortran3.tar.gz/sha512/eebd679c499143014514c7c9d1875dedbbab9e3af51526c4dd445a9e3dbade95d24522da8bbad0a50ab400755e47b018828b324c4ad7705e212ccd990e34439a +CompilerSupportLibraries.v1.0.2+0.i686-linux-musl-libgfortran4.tar.gz/md5/bc4a0f0b7cea328f7e8850583774496b +CompilerSupportLibraries.v1.0.2+0.i686-linux-musl-libgfortran4.tar.gz/sha512/82285b67946212b49cddf6259f2c60ff5469f8c5263ccefe44f1d93ace98ab68e2c152e1b54434b2f075fd8d192c06d5451bc8cca26d951ad15f3453102f02b5 +CompilerSupportLibraries.v1.0.2+0.i686-linux-musl-libgfortran5.tar.gz/md5/177f0232abce8d523882530ed7a93092 +CompilerSupportLibraries.v1.0.2+0.i686-linux-musl-libgfortran5.tar.gz/sha512/db80acf0f2434f28ee7680e1beb34f564940071815d1ad89fb5913cbd9ac24da528e826d0d54be6265a7340ebd661b6d308ed79d96b67fa5d8c98dc3f1bee8d6 +CompilerSupportLibraries.v1.0.2+0.i686-w64-mingw32-libgfortran3.tar.gz/md5/756718e5eaa4547b874a71a8e3545492 +CompilerSupportLibraries.v1.0.2+0.i686-w64-mingw32-libgfortran3.tar.gz/sha512/c21c1be10ca8810f56e435b3629e2ab0678926ea9c4f4c3dd003f9e292c075493b83df04401d3bcf7738f1a44098f674f9b01bba9db4b9a9e45ad7af3497444e +CompilerSupportLibraries.v1.0.2+0.i686-w64-mingw32-libgfortran4.tar.gz/md5/65ce0024bf8fe3276addbf185ed03e48 +CompilerSupportLibraries.v1.0.2+0.i686-w64-mingw32-libgfortran4.tar.gz/sha512/5e8105a12ab04e2949e41eda50a060dea04ccd98660c7528cfc86e120fe61cca8bab878fd2c92a3858f02ac3f3c55d0e48789907e5fbd2392a8e84b183ed4636 +CompilerSupportLibraries.v1.0.2+0.i686-w64-mingw32-libgfortran5.tar.gz/md5/b7727324d550f637209db795238c46a4 +CompilerSupportLibraries.v1.0.2+0.i686-w64-mingw32-libgfortran5.tar.gz/sha512/864b1db2642e68665b9d3322563c7ce964835d0e720325ea00b193e2cbf6791760e0014710e2a79876165ab0daffa6d53d61b87a5034f956ba6e255b0144652c +CompilerSupportLibraries.v1.0.2+0.powerpc64le-linux-gnu-libgfortran3.tar.gz/md5/4e5e4b23dc87450738da33926a07511d +CompilerSupportLibraries.v1.0.2+0.powerpc64le-linux-gnu-libgfortran3.tar.gz/sha512/fc09879d94b750e75775d8b64a41ab9924d675fb53c5700467604412928fe7f5cb21911da0f64898d2463fa77ffbaf4c96c397b9060f4746eec152747930cddc +CompilerSupportLibraries.v1.0.2+0.powerpc64le-linux-gnu-libgfortran4.tar.gz/md5/9a92138ed69aa317a932a615c6e62d69 +CompilerSupportLibraries.v1.0.2+0.powerpc64le-linux-gnu-libgfortran4.tar.gz/sha512/0b7785379936a2a209b074177b1424dd7e00b29b5165f564e799b0aa4e06a582e9d616525d97274ba2507cb88192028f1ac485d3f99bdc7ee53fc63c1a7e85de +CompilerSupportLibraries.v1.0.2+0.powerpc64le-linux-gnu-libgfortran5.tar.gz/md5/8ffee3d6de5197c7a1f354d72c8238fa +CompilerSupportLibraries.v1.0.2+0.powerpc64le-linux-gnu-libgfortran5.tar.gz/sha512/deadc4d7224c84f9b82dc956b69e815c44ae036802838365d870ab9f58c8bcf8ce0645f2f387c8ff344ac2108fc8e7e1ee907fa55e93c91aa5d9fd921bf3fdcb +CompilerSupportLibraries.v1.0.2+0.x86_64-apple-darwin-libgfortran3.tar.gz/md5/87449e72e3f33dbb69b7053cdc2649d4 +CompilerSupportLibraries.v1.0.2+0.x86_64-apple-darwin-libgfortran3.tar.gz/sha512/5ce02ad10c6f4686a476eb2a5de2988cd8b482f5e693db2880c84ad1c82f468ef03fe01b9d0feefe5d4ee741d1d16643d36b144e6261ed32311b3b6f312fac2f +CompilerSupportLibraries.v1.0.2+0.x86_64-apple-darwin-libgfortran4.tar.gz/md5/0407cde92cfa42fa89ac83217ca0ec16 +CompilerSupportLibraries.v1.0.2+0.x86_64-apple-darwin-libgfortran4.tar.gz/sha512/032c831f1166a336551138939ac40eb2c68a048ce786c0c1403b879a20c1b706caac16d22560b2c7f2b3d6373986c347188675674116005ca251336ee048d09f +CompilerSupportLibraries.v1.0.2+0.x86_64-apple-darwin-libgfortran5.tar.gz/md5/23418763b808371ee94772a90d501f4d +CompilerSupportLibraries.v1.0.2+0.x86_64-apple-darwin-libgfortran5.tar.gz/sha512/7867b843551457b11bda7821dd384c1c1cf23b80a308b2058a693de7b7da099f0b37eb0a6de2b84c04b625a68c60eea55138e200d5d6ec6f6af09bd7ce406a96 +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-gnu-libgfortran3.tar.gz/md5/e3d33ae03c18affea74699bdc1fabb68 +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-gnu-libgfortran3.tar.gz/sha512/42013f4921de5a69ad857195ce5c19ad1bca3c920d79699e5501f1f4534ab132fabd422362b2b5056f5d182215d6c069db5df460bafa700903faf962cc00f77b +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-gnu-libgfortran4.tar.gz/md5/d40c1e8c0393213c6057c53a12f44175 +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-gnu-libgfortran4.tar.gz/sha512/fe7baa4de7490065ab7b953cc12f41462a24bcb49d0a4a64b23249e98e7569b19bb1cb455af2f76090e34066a7d3cdd7a48cae6515ce6c7a5c8486b0cacc5106 +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-gnu-libgfortran5.tar.gz/md5/48541b90f715c4c86ee4da0570275947 +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-gnu-libgfortran5.tar.gz/sha512/7f2683fb98e80f12629f4ed3bea9fd59d32b7e7a9ed1699e782d8e238ff0915ecc61bf00adaf4597cfe41caf82cdca0f9be250f595f5f0bea6d8f77dba99eaf4 +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-musl-libgfortran3.tar.gz/md5/4547059eb905995667be48bf85d49911 +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-musl-libgfortran3.tar.gz/sha512/7400fdabc924434ab4a4949248c3603887ac06ffd2f205ae33e14495d86cd4f816bbd1999eeafa0257f518df1e7f7c522f596e847a71dbfbfccff4859f50acc7 +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-musl-libgfortran4.tar.gz/md5/46267543cad6584d7b7b9fcc8f18f21d +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-musl-libgfortran4.tar.gz/sha512/0353d7d724be48d4185d3c181692970b7996f53f6a01723072aa5c94b53a8c5055faeed30df51659c252a46f4b941dec0cb24569323e3c85c166f14c5b7c8e9e +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-musl-libgfortran5.tar.gz/md5/14dba2897a6e9d370fa9091c045375fc +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-musl-libgfortran5.tar.gz/sha512/10b79f9c059839f5b57fa8d2a381a034c4067262c4088bd354d14ea56bec097878069383aa9cfadaa09d73bd20fc348fb61662d863a8d62cb25d7af6b8e29858 +CompilerSupportLibraries.v1.0.2+0.x86_64-unknown-freebsd-libgfortran3.tar.gz/md5/eed836d1addeb10d0901f836724aff1e +CompilerSupportLibraries.v1.0.2+0.x86_64-unknown-freebsd-libgfortran3.tar.gz/sha512/e33eca424d1529a1fb23ba9cf7fac345ed1cfc8073c975b6b31ca44d2e8c3f5083af65433df009b22483dceb2e43149f3c1e8433681fec5fb812e1d5b4243ce4 +CompilerSupportLibraries.v1.0.2+0.x86_64-unknown-freebsd-libgfortran4.tar.gz/md5/d5ae9f9519341fdaabf62267c89461d2 +CompilerSupportLibraries.v1.0.2+0.x86_64-unknown-freebsd-libgfortran4.tar.gz/sha512/6421aa5d1bd6f08ad43f59ed4dc1bef8b9b598ebbbd3e48149730f3bec3471f8e2c02ffb338427326924290b8f52ef9e626e3313448bc931a61d866c5dc544ae +CompilerSupportLibraries.v1.0.2+0.x86_64-unknown-freebsd-libgfortran5.tar.gz/md5/fc1df521395362a5aaa2e2aeef707207 +CompilerSupportLibraries.v1.0.2+0.x86_64-unknown-freebsd-libgfortran5.tar.gz/sha512/f2e5a08e3cae171242ae6a20d2d4838c1529ce042745dc466148b7bbc06896d94476fd05c7787e6e8641bea752dfc0e6b09e95b160bede600d20d2ad68e7705f +CompilerSupportLibraries.v1.0.2+0.x86_64-w64-mingw32-libgfortran3.tar.gz/md5/2338f8aa2696935f7460454e708ce308 +CompilerSupportLibraries.v1.0.2+0.x86_64-w64-mingw32-libgfortran3.tar.gz/sha512/5a4b0e97928c26eee16bbec4c3e69e55fa9c768101257c3e2f161118809c778aa0feaf21307198822c3172a58ed12ca0a49285b2941ed0b8f2b367e64ca1c51a +CompilerSupportLibraries.v1.0.2+0.x86_64-w64-mingw32-libgfortran4.tar.gz/md5/b393d2bf0d181d218130ac572c17d369 +CompilerSupportLibraries.v1.0.2+0.x86_64-w64-mingw32-libgfortran4.tar.gz/sha512/76e0f7caa24bb734c6f7542be9f834d5b912f082cb3c4c3c52a63e37d4b8c33dd94e576c43f4bee6c04bfb44af2f2b67ba70773fa52ad0de6c8c0059b3e51b83 +CompilerSupportLibraries.v1.0.2+0.x86_64-w64-mingw32-libgfortran5.tar.gz/md5/23db836e6e4142f621862971017fe61e +CompilerSupportLibraries.v1.0.2+0.x86_64-w64-mingw32-libgfortran5.tar.gz/sha512/c0b04f7fe5aabfe6af509c77a1f68e0bcfd14714758042fe502b968c4cc272156fc84c8b4c1ee574754bb2fddaa810f6a4215cbd164ddc11b697b3adaef09a81 diff --git a/doc/make.jl b/doc/make.jl index 75e3598ced6f72..04b8af595e58f0 100644 --- a/doc/make.jl +++ b/doc/make.jl @@ -142,6 +142,7 @@ DevDocs = [ "devdocs/subarrays.md", "devdocs/isbitsunionarrays.md", "devdocs/sysimg.md", + "devdocs/pkgimg.md", "devdocs/llvm.md", "devdocs/stdio.md", "devdocs/boundscheck.md", diff --git a/doc/src/devdocs/pkgimg.md b/doc/src/devdocs/pkgimg.md new file mode 100644 index 00000000000000..8230c4b91b338d --- /dev/null +++ b/doc/src/devdocs/pkgimg.md @@ -0,0 +1,48 @@ +# Package Images + +Julia package images provide object (native code) caches for Julia packages. +They are similar to Julia's [system image](@ref dev-sysimg) and support many of the same features. +In fact the underlying serialization format is the same, and the system image is the base image that the package images are build against. + +## High-level overview + +Package images are shared libraries that contain both code and data. Like `.ji` cache files, they are generated per package. The data section contains both global data (global variables in the package) as well as the necessary metadata about what methods and types are defined by the package. The code section contains native objects that cache the final output of Julia's LLVM-based compiler. + +The command line option `--pkgimages=no` can be used to turn off object caching for this session. Note that this means that cache files have to likely be regenerated. +See [`JULIA_MAX_NUM_PRECOMPILE_FILES`](@ref env-max-num-precompile-files) for the upper limit of variants Julia caches per default. + +!!! note + While the package images present themselves as native shared libraries, they are only an approximation thereof. You will not be able to link against them from a native program and they must be loaded from Julia. + + +## Linking + +Since the package images contain native code, we must run a linker over them before we can use them. You can set the environment variable `JULIA_VERBOSE_LINKING` to `true` to make the package image linking process verbose. + +Furthermore, we cannot assume that the user has a working system linker installed. Therefore, Julia ships with LLD, the LLVM linker, to provide a working out of the box experience. In `base/linking.jl`, we implement a limited interface to be able to link package images on all supported platforms. + +### Quirks +Despite LLD being a multi-platform linker, it does not provide a consistent interface across platforms. Furthermore, it is meant to be used from `clang` or +another compiler driver, we therefore reimplement some of the logic from `llvm-project/clang/lib/Driver/ToolChains`. Thankfully one can use `lld -flavor` to set lld to the right platform + +#### Windows +To avoid having to deal with `link.exe` we use `-flavor gnu`, effectively turning `lld` into a cross-linker from a mingw32 environment. Windows DLLs are required to contain a `_DllMainCRTStartup` function and to minimize our dependence on mingw32 libraries, we inject a stub definition ourselves. + +#### MacOS +Dynamic libraries on macOS need to link against `-lSystem`. On recent macOS versions, `-lSystem` is only available for linking when Xcode is available. +To that effect we link with `-undefined dynamic_lookup`. + +## Package images optimized for multiple microarchitectures +Similar to [multi-versioning](@ref sysimg-multi-versioning) for system images, package images support multi-versioning. If you are in a heterogenous environment, with a unified cache, +you can set the environment variable `JULIA_CPU_TARGET=generic` to multi-version the object caches. + +## Flags that impact package image creation and selection + +These are the Julia command line flags that impact cache selection. Package images +that were created with different flags will be rejected. + +- `-g`, `--debug-info`: Exact match required since it changes code generation. +- `--check-bounds`: Exact match required since it changes code generation. +- `--pkgimages`: To allow running without object caching enabled. +- `-O`, `--optimize`: Reject package images generated for a lower optimization level, + but allow for higher optimization levels to be loaded. diff --git a/doc/src/devdocs/sysimg.md b/doc/src/devdocs/sysimg.md index 5c976875846d3a..a21e3ba265f9b3 100644 --- a/doc/src/devdocs/sysimg.md +++ b/doc/src/devdocs/sysimg.md @@ -19,7 +19,7 @@ This operation is useful for multiple reasons. A user may: The [`PackageCompiler.jl` package](https://github.com/JuliaLang/PackageCompiler.jl) contains convenient wrapper functions to automate this process. -## System image optimized for multiple microarchitectures +## [System image optimized for multiple microarchitectures](@id sysimg-multi-versioning) The system image can be compiled simultaneously for multiple CPU microarchitectures under the same instruction set architecture (ISA). Multiple versions of the same function diff --git a/doc/src/manual/code-loading.md b/doc/src/manual/code-loading.md index f6e74a02b4b30b..6ff91282641618 100644 --- a/doc/src/manual/code-loading.md +++ b/doc/src/manual/code-loading.md @@ -389,17 +389,18 @@ When a package with extensions is added to an environment, the `weakdeps` and `e are stored in the manifest file in the section for that package. The dependency lookup rules for a package are the same as for its "parent" except that the listed extension dependencies are also considered as dependencies. -### Package/Environment Preferences + +### [Package/Environment Preferences](@id preferences) Preferences are dictionaries of metadata that influence package behavior within an environment. -The preferences system supports reading preferences at compile-time, which means that at code-loading time, we must ensure that a particular `.ji` file was built with the same preferences as the current environment before loading it. +The preferences system supports reading preferences at compile-time, which means that at code-loading time, we must ensure that the precompilation files selected by Julia were built with the same preferences as the current environment before loading them. The public API for modifying Preferences is contained within the [Preferences.jl](https://github.com/JuliaPackaging/Preferences.jl) package. Preferences are stored as TOML dictionaries within a `(Julia)LocalPreferences.toml` file next to the currently-active project. If a preference is "exported", it is instead stored within the `(Julia)Project.toml` instead. The intention is to allow shared projects to contain shared preferences, while allowing for users themselves to override those preferences with their own settings in the LocalPreferences.toml file, which should be .gitignored as the name implies. -Preferences that are accessed during compilation are automatically marked as compile-time preferences, and any change recorded to these preferences will cause the Julia compiler to recompile any cached precompilation `.ji` files for that module. -This is done by serializing the hash of all compile-time preferences during compilation, then checking that hash against the current environment when searching for the proper `.ji` file to load. +Preferences that are accessed during compilation are automatically marked as compile-time preferences, and any change recorded to these preferences will cause the Julia compiler to recompile any cached precompilation file(s) (`.ji` and corresponding `.so`, `.dll`, or `.dylib` files) for that module. +This is done by serializing the hash of all compile-time preferences during compilation, then checking that hash against the current environment when searching for the proper file(s) to load. Preferences can be set with depot-wide defaults; if package Foo is installed within your global environment and it has preferences set, these preferences will apply as long as your global environment is part of your `LOAD_PATH`. Preferences in environments higher up in the environment stack get overridden by the more proximal entries in the load path, ending with the currently active project. diff --git a/doc/src/manual/command-line-interface.md b/doc/src/manual/command-line-interface.md index 4af3c05d51eb67..e35cbf5e313e77 100644 --- a/doc/src/manual/command-line-interface.md +++ b/doc/src/manual/command-line-interface.md @@ -88,7 +88,7 @@ There are various ways to run Julia code and provide options, similar to those a julia [switches] -- [programfile] [args...] ``` -The following is a complete list of command-line switches available when launching julia (a '*' marks the default value, if applicable): +The following is a complete list of command-line switches available when launching julia (a '*' marks the default value, if applicable; settings marked '($)' may trigger package precompilation): |Switch |Description| |:--- |:---| @@ -102,6 +102,7 @@ The following is a complete list of command-line switches available when launchi |`--handle-signals={yes*\|no}` |Enable or disable Julia's default signal handlers| |`--sysimage-native-code={yes*\|no}` |Use native code from system image if available| |`--compiled-modules={yes*\|no}` |Enable or disable incremental precompilation of modules| +|`--pkgimages={yes*\|no}` |Enable or disable usage of native code caching in the form of pkgimages| |`-e`, `--eval ` |Evaluate ``| |`-E`, `--print ` |Evaluate `` and display the result| |`-L`, `--load ` |Load `` immediately on all processors| @@ -117,11 +118,11 @@ The following is a complete list of command-line switches available when launchi |`--warn-overwrite={yes\|no*}` |Enable or disable method overwrite warnings| |`--warn-scope={yes*\|no}` |Enable or disable warning for ambiguous top-level scope| |`-C`, `--cpu-target ` |Limit usage of CPU features up to ``; set to `help` to see the available options| -|`-O`, `--optimize={0,1,2*,3}` |Set the optimization level (level is 3 if `-O` is used without a level)| +|`-O`, `--optimize={0,1,2*,3}` |Set the optimization level (level is 3 if `-O` is used without a level) ($)| |`--min-optlevel={0*,1,2,3}` |Set the lower bound on per-module optimization| -|`-g {0,1*,2}` |Set the level of debug info generation (level is 2 if `-g` is used without a level)| +|`-g`, `--debug-info={0,1*,2}` |Set the level of debug info generation (level is 2 if `-g` is used without a level) ($)| |`--inline={yes\|no}` |Control whether inlining is permitted, including overriding `@inline` declarations| -|`--check-bounds={yes\|no\|auto*}` |Emit bounds checks always, never, or respect `@inbounds` declarations| +|`--check-bounds={yes\|no\|auto*}` |Emit bounds checks always, never, or respect `@inbounds` declarations ($)| |`--math-mode={ieee,fast}` |Disallow or enable unsafe floating point optimizations (overrides `@fastmath` declaration)| |`--code-coverage[={none*\|user\|all}]` |Count executions of source lines (omitting setting is equivalent to `user`)| |`--code-coverage=tracefile.info` |Append coverage information to the LCOV tracefile (filename supports format tokens).| diff --git a/doc/src/manual/environment-variables.md b/doc/src/manual/environment-variables.md index bc4a742365d692..f29e5b7aaf8f78 100644 --- a/doc/src/manual/environment-variables.md +++ b/doc/src/manual/environment-variables.md @@ -162,10 +162,14 @@ The absolute path `REPL.find_hist_file()` of the REPL's history file. If $(DEPOT_PATH[1])/logs/repl_history.jl ``` -### `JULIA_MAX_NUM_PRECOMPILE_FILES` +### [`JULIA_MAX_NUM_PRECOMPILE_FILES`](@id env-max-num-precompile-files) Sets the maximum number of different instances of a single package that are to be stored in the precompile cache (default = 10). +### `JULIA_VERBOSE_LINKING` + +If set to true, linker commands will be displayed during precompilation. + ## Pkg.jl ### `JULIA_CI` diff --git a/doc/src/manual/methods.md b/doc/src/manual/methods.md index 6cbcc4fad6a65d..a504f8e3511b23 100644 --- a/doc/src/manual/methods.md +++ b/doc/src/manual/methods.md @@ -265,8 +265,40 @@ julia> methods(+) ``` Multiple dispatch together with the flexible parametric type system give Julia its ability to -abstractly express high-level algorithms decoupled from implementation details, yet generate efficient, -specialized code to handle each case at run time. +abstractly express high-level algorithms decoupled from implementation details. + +## [Method specializations](@id man-method-specializations) + +When you create multiple methods of the same function, this is sometimes called +"specialization." In this case, you're specializing the *function* by adding additional +methods to it: each new method is a new specialization of the function. +As shown above, these specializations are returned by `methods`. + +There's another kind of specialization that occurs without programmer intervention: +Julia's compiler can automatically specialize the *method* for the specific argument types used. +Such specializations are *not* listed by `methods`, as this doesn't create new `Method`s, but tools like [`@code_typed`](@ref) allow you to inspect such specializations. + +For example, if you create a method + +``` +mysum(x::Real, y::Real) = x + y +``` + +you've given the function `mysum` one new method (possibly its only method), and that method takes any pair of `Real` number inputs. But if you then execute + +```julia-repl +julia> mysum(1, 2) +3 + +julia> mysum(1.0, 2.0) +3.0 +``` + +Julia will compile `mysum` twice, once for `x::Int, y::Int` and again for `x::Float64, y::Float64`. +The point of compiling twice is performance: the methods that get called for `+` (which `mysum` uses) vary depending on the specific types of `x` and `y`, and by compiling different specializations Julia can do all the method lookup ahead of time. This allows the program to run much more quickly, since it does not have to bother with method lookup while it is running. +Julia's automatic specialization allows you to write generic algorithms and expect that the compiler will generate efficient, specialized code to handle each case you need. + +In cases where the number of potential specializations might be effectively unlimited, Julia may avoid this default specialization. See [Be aware of when Julia avoids specializing](@ref) for more information. ## [Method Ambiguities](@id man-ambiguities) diff --git a/doc/src/manual/modules.md b/doc/src/manual/modules.md index f0a9a5110ded41..90680828d2bc22 100644 --- a/doc/src/manual/modules.md +++ b/doc/src/manual/modules.md @@ -9,7 +9,7 @@ Modules in Julia help organize code into coherent units. They are delimited synt 2. Modules have facilities for detailed namespace management: each defines a set of names it `export`s, and can import names from other modules with `using` and `import` (we explain these below). -3. Modules can be precompiled for faster loading, and contain code for runtime initialization. +3. Modules can be precompiled for faster loading, and may contain code for runtime initialization. Typically, in larger Julia packages you will see module code organized into files, eg @@ -429,11 +429,14 @@ Large modules can take several seconds to load because executing all of the stat often involves compiling a large amount of code. Julia creates precompiled caches of the module to reduce this time. -The incremental precompiled module file are created and used automatically when using `import` -or `using` to load a module. This will cause it to be automatically compiled the first time -it is imported. Alternatively, you can manually call [`Base.compilecache(Base.identify_package("modulename"))`](@ref). The resulting -cache files will be stored in `DEPOT_PATH[1]/compiled/`. Subsequently, the module is automatically -recompiled upon `using` or `import` whenever any of its dependencies change; dependencies are modules it +Precompiled module files (sometimes called "cache files") are created and used automatically when `import` or `using` loads a module. If the cache file(s) do not yet exist, the module will be compiled and saved for future reuse. You can also manually call [`Base.compilecache(Base.identify_package("modulename"))`](@ref) to create these files without loading the module. The resulting +cache files will be stored in the `compiled` subfolder of `DEPOT_PATH[1]`. If nothing about your system changes, +such cache files will be used when you load the module with `import` or `using`. + +Precompilation cache files store definitions of modules, types, methods, and constants. They may also store method specializations and the code generated for them, but this typically requires that the developer add explicit [`precompile`](@ref) directives or execute workloads that force compilation during the package build. + +However, if you update the module's dependencies or change its source code, the module is automatically +recompiled upon `using` or `import`. Dependencies are modules it imports, the Julia build, files it includes, or explicit dependencies declared by [`include_dependency(path)`](@ref) in the module file(s). @@ -445,6 +448,7 @@ by the search logic in `require` matches the path that had created the precompil into account the set of dependencies already loaded into the current process and won't recompile those modules, even if their files change or disappear, in order to avoid creating incompatibilities between the running system and the precompile cache. +Finally, it takes account of changes in any [compile-time preferences](@ref preferences). If you know that a module is *not* safe to precompile (for example, for one of the reasons described below), you should @@ -589,6 +593,12 @@ A few other points to be aware of: It is sometimes helpful during module development to turn off incremental precompilation. The command line flag `--compiled-modules={yes|no}` enables you to toggle module precompilation on and off. When Julia is started with `--compiled-modules=no` the serialized modules in the compile cache -are ignored when loading modules and module dependencies. `Base.compilecache` can still be called +are ignored when loading modules and module dependencies. +More fine-grained control is available with `--pkgimages=no`, which suppresses only +native-code storage during precompilation. `Base.compilecache` can still be called manually. The state of this command line flag is passed to `Pkg.build` to disable automatic precompilation triggering when installing, updating, and explicitly building packages. + +You can also debug some precompilation failures with environment variables. Setting +`JULIA_VERBOSE_LINKING=true` may help resolve failures in linking shared libraries of compiled +native code. See the **Developer Documentation** part of the Julia manual, where you will find further details in the section documenting Julia's internals under "Package Images". diff --git a/doc/src/manual/performance-tips.md b/doc/src/manual/performance-tips.md index 1a316e5fcf347b..1eee23e163a77e 100644 --- a/doc/src/manual/performance-tips.md +++ b/doc/src/manual/performance-tips.md @@ -525,7 +525,7 @@ at the time `k` is compiled. ### Be aware of when Julia avoids specializing -As a heuristic, Julia avoids automatically specializing on argument type parameters in three +As a heuristic, Julia avoids automatically [specializing](@ref man-method-specializations) on argument type parameters in three specific cases: `Type`, `Function`, and `Vararg`. Julia will always specialize when the argument is used within the method, but not if the argument is just passed through to another function. This usually has no performance impact at runtime and diff --git a/src/aotcompile.cpp b/src/aotcompile.cpp index 7325adde8b060e..6f9345ee18f829 100644 --- a/src/aotcompile.cpp +++ b/src/aotcompile.cpp @@ -61,7 +61,9 @@ using namespace llvm; #include "jitlayers.h" +#include "serialize.h" #include "julia_assert.h" +#include "codegen_shared.h" #define DEBUG_TYPE "julia_aotcompile" @@ -93,6 +95,7 @@ typedef struct { std::vector jl_sysimg_gvars; std::map> jl_fvar_map; std::vector jl_value_to_llvm; + std::vector jl_external_to_llvm; } jl_native_code_desc_t; extern "C" JL_DLLEXPORT @@ -118,6 +121,15 @@ void jl_get_llvm_gvs_impl(void *native_code, arraylist_t *gvs) memcpy(gvs->items, data->jl_value_to_llvm.data(), gvs->len * sizeof(void*)); } +extern "C" JL_DLLEXPORT +void jl_get_llvm_external_fns_impl(void *native_code, arraylist_t *external_fns) +{ + jl_native_code_desc_t *data = (jl_native_code_desc_t*)native_code; + arraylist_grow(external_fns, data->jl_external_to_llvm.size()); + memcpy(external_fns->items, data->jl_external_to_llvm.data(), + external_fns->len * sizeof(jl_code_instance_t*)); +} + extern "C" JL_DLLEXPORT LLVMOrcThreadSafeModuleRef jl_get_llvm_module_impl(void *native_code) { @@ -248,13 +260,17 @@ static void jl_ci_cache_lookup(const jl_cgparams_t &cgparams, jl_method_instance *ci_out = codeinst; } +void replaceUsesWithLoad(Function &F, function_ref should_replace, MDNode *tbaa_const); + // takes the running content that has collected in the shadow module and dump it to disk // this builds the object file portion of the sysimage files for fast startup, and can // also be used be extern consumers like GPUCompiler.jl to obtain a module containing -// all reachable & inferrrable functions. The `policy` flag switches between the default -// mode `0`, the extern mode `1`. +// all reachable & inferrrable functions. +// The `policy` flag switches between the default mode `0` and the extern mode `1` used by GPUCompiler. +// `_imaging_mode` controls if raw pointers can be embedded (e.g. the code will be loaded into the same session). +// `_external_linkage` create linkages between pkgimages. extern "C" JL_DLLEXPORT -void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvmmod, const jl_cgparams_t *cgparams, int _policy, int _imaging_mode) +void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvmmod, const jl_cgparams_t *cgparams, int _policy, int _imaging_mode, int _external_linkage) { ++CreateNativeCalls; CreateNativeMax.updateMax(jl_array_len(methods)); @@ -289,6 +305,7 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm jl_codegen_params_t params(ctxt); params.params = cgparams; params.imaging = imaging; + params.external_linkage = _external_linkage; size_t compile_for[] = { jl_typeinf_world, jl_atomic_load_acquire(&jl_world_counter) }; for (int worlds = 0; worlds < 2; worlds++) { params.world = compile_for[worlds]; @@ -348,6 +365,39 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm } CreateNativeMethods += emitted.size(); + size_t offset = gvars.size(); + data->jl_external_to_llvm.resize(params.external_fns.size()); + + auto tbaa_const = tbaa_make_child_with_context(*ctxt.getContext(), "jtbaa_const", nullptr, true).first; + for (auto &extern_fn : params.external_fns) { + jl_code_instance_t *this_code = std::get<0>(extern_fn.first); + bool specsig = std::get<1>(extern_fn.first); + assert(specsig && "Error external_fns doesn't handle non-specsig yet"); + (void)specsig; + Function *F = extern_fn.second; + Module *M = F->getParent(); + + Type *T_funcp = F->getFunctionType()->getPointerTo(); + // Can't create a GC with type FunctionType. Alias also doesn't work + GlobalVariable *GV = new GlobalVariable(*M, T_funcp, false, + GlobalVariable::ExternalLinkage, + Constant::getNullValue(T_funcp), + F->getName()); + + + // Need to insert load instruction, thus we can't use replace all uses with + replaceUsesWithLoad(*F, [GV](Instruction &) { return GV; }, tbaa_const); + + assert(F->getNumUses() == 0); // declaration counts as use + GV->takeName(F); + F->eraseFromParent(); + + size_t idx = gvars.size() - offset; + assert(idx >= 0); + data->jl_external_to_llvm.at(idx) = this_code; + gvars.push_back(std::string(GV->getName())); + } + // clones the contents of the module `m` to the shadow_output collector // while examining and recording what kind of function pointer we have for (auto &def : emitted) { @@ -459,7 +509,7 @@ static void injectCRTAlias(Module &M, StringRef name, StringRef alias, FunctionT if (!target) { target = Function::Create(FT, Function::ExternalLinkage, alias, M); } - Function *interposer = Function::Create(FT, Function::WeakAnyLinkage, name, M); + Function *interposer = Function::Create(FT, Function::InternalLinkage, name, M); appendToCompilerUsed(M, {interposer}); llvm::IRBuilder<> builder(BasicBlock::Create(M.getContext(), "top", interposer)); @@ -477,7 +527,7 @@ extern "C" JL_DLLEXPORT void jl_dump_native_impl(void *native_code, const char *bc_fname, const char *unopt_bc_fname, const char *obj_fname, const char *asm_fname, - const char *sysimg_data, size_t sysimg_len) + const char *sysimg_data, size_t sysimg_len, ios_t *s) { JL_TIMING(NATIVE_DUMP); jl_native_code_desc_t *data = (jl_native_code_desc_t*)native_code; @@ -589,7 +639,7 @@ void jl_dump_native_impl(void *native_code, } // do the actual work - auto add_output = [&] (Module &M, StringRef unopt_bc_Name, StringRef bc_Name, StringRef obj_Name, StringRef asm_Name) { + auto add_output = [&] (Module &M, StringRef unopt_bc_Name, StringRef bc_Name, StringRef obj_Name, StringRef asm_Name, bool inject_crt) { preopt.run(M, empty.MAM); if (bc_fname || obj_fname || asm_fname) { assert(!verifyModule(M, &errs())); @@ -597,22 +647,49 @@ void jl_dump_native_impl(void *native_code, assert(!verifyModule(M, &errs())); } - // We would like to emit an alias or an weakref alias to redirect these symbols - // but LLVM doesn't let us emit a GlobalAlias to a declaration... - // So for now we inject a definition of these functions that calls our runtime - // functions. We do so after optimization to avoid cloning these functions. - injectCRTAlias(M, "__gnu_h2f_ieee", "julia__gnu_h2f_ieee", - FunctionType::get(Type::getFloatTy(Context), { Type::getHalfTy(Context) }, false)); - injectCRTAlias(M, "__extendhfsf2", "julia__gnu_h2f_ieee", - FunctionType::get(Type::getFloatTy(Context), { Type::getHalfTy(Context) }, false)); - injectCRTAlias(M, "__gnu_f2h_ieee", "julia__gnu_f2h_ieee", - FunctionType::get(Type::getHalfTy(Context), { Type::getFloatTy(Context) }, false)); - injectCRTAlias(M, "__truncsfhf2", "julia__gnu_f2h_ieee", - FunctionType::get(Type::getHalfTy(Context), { Type::getFloatTy(Context) }, false)); - injectCRTAlias(M, "__truncdfhf2", "julia__truncdfhf2", - FunctionType::get(Type::getHalfTy(Context), { Type::getDoubleTy(Context) }, false)); + if (inject_crt) { + // We would like to emit an alias or an weakref alias to redirect these symbols + // but LLVM doesn't let us emit a GlobalAlias to a declaration... + // So for now we inject a definition of these functions that calls our runtime + // functions. We do so after optimization to avoid cloning these functions. + injectCRTAlias(M, "__gnu_h2f_ieee", "julia__gnu_h2f_ieee", + FunctionType::get(Type::getFloatTy(Context), { Type::getHalfTy(Context) }, false)); + injectCRTAlias(M, "__extendhfsf2", "julia__gnu_h2f_ieee", + FunctionType::get(Type::getFloatTy(Context), { Type::getHalfTy(Context) }, false)); + injectCRTAlias(M, "__gnu_f2h_ieee", "julia__gnu_f2h_ieee", + FunctionType::get(Type::getHalfTy(Context), { Type::getFloatTy(Context) }, false)); + injectCRTAlias(M, "__truncsfhf2", "julia__gnu_f2h_ieee", + FunctionType::get(Type::getHalfTy(Context), { Type::getFloatTy(Context) }, false)); + injectCRTAlias(M, "__truncdfhf2", "julia__truncdfhf2", + FunctionType::get(Type::getHalfTy(Context), { Type::getDoubleTy(Context) }, false)); + +#if defined(_OS_WINDOWS_) + // Windows expect that the function `_DllMainStartup` is present in an dll. + // Normal compilers use something like Zig's crtdll.c instead we provide a + // a stub implementation. + auto T_pvoid = Type::getInt8Ty(Context)->getPointerTo(); + auto T_int32 = Type::getInt32Ty(Context); + auto FT = FunctionType::get(T_int32, {T_pvoid, T_int32, T_pvoid}, false); + auto F = Function::Create(FT, Function::ExternalLinkage, "_DllMainCRTStartup", M); + F->setCallingConv(CallingConv::X86_StdCall); + + llvm::IRBuilder<> builder(BasicBlock::Create(M.getContext(), "top", F)); + builder.CreateRet(ConstantInt::get(T_int32, 1)); +#endif + } postopt.run(M, empty.MAM); + + // Get target by snooping on multiversioning + GlobalVariable *target_ids = M.getNamedGlobal("jl_dispatch_target_ids"); + if (s && target_ids) { + if(auto targets = dyn_cast(target_ids->getInitializer())) { + auto rawTargets = targets->getRawDataValues(); + write_int32(s, rawTargets.size()); + ios_write(s, rawTargets.data(), rawTargets.size()); + }; + } + emitter.run(M); if (unopt_bc_fname) @@ -625,7 +702,7 @@ void jl_dump_native_impl(void *native_code, emit_result(asm_Archive, asm_Buffer, asm_Name, outputs); }; - add_output(*dataM, "unopt.bc", "text.bc", "text.o", "text.s"); + add_output(*dataM, "unopt.bc", "text.bc", "text.o", "text.s", true); orc::ThreadSafeModule sysimage(std::make_unique("sysimage", Context), TSCtx); auto sysimageM = sysimage.getModuleUnlocked(); @@ -648,7 +725,7 @@ void jl_dump_native_impl(void *native_code, GlobalVariable::ExternalLinkage, len, "jl_system_image_size")); } - add_output(*sysimageM, "data.bc", "data.bc", "data.o", "data.s"); + add_output(*sysimageM, "data.bc", "data.bc", "data.o", "data.s", false); object::Archive::Kind Kind = getDefaultForHost(TheTriple); if (unopt_bc_fname) diff --git a/src/codegen-stubs.c b/src/codegen-stubs.c index 01324e349f08f6..e7b7d1fb791a5e 100644 --- a/src/codegen-stubs.c +++ b/src/codegen-stubs.c @@ -12,8 +12,9 @@ JL_DLLEXPORT void jl_dump_native_fallback(void *native_code, const char *bc_fname, const char *unopt_bc_fname, const char *obj_fname, const char *asm_fname, - const char *sysimg_data, size_t sysimg_len) UNAVAILABLE + const char *sysimg_data, size_t sysimg_len, ios_t *s) UNAVAILABLE JL_DLLEXPORT void jl_get_llvm_gvs_fallback(void *native_code, arraylist_t *gvs) UNAVAILABLE +JL_DLLEXPORT void jl_get_llvm_external_fns_fallback(void *native_code, arraylist_t *gvs) UNAVAILABLE JL_DLLEXPORT void jl_extern_c_fallback(jl_function_t *f, jl_value_t *rt, jl_value_t *argt, char *name) UNAVAILABLE JL_DLLEXPORT jl_value_t *jl_dump_method_asm_fallback(jl_method_instance_t *linfo, size_t world, @@ -31,10 +32,10 @@ JL_DLLEXPORT int jl_getFunctionInfo_fallback(jl_frame_t **frames, uintptr_t poin return 0; } -JL_DLLEXPORT void jl_register_fptrs_fallback(uint64_t sysimage_base, const struct _jl_sysimg_fptrs_t *fptrs, +JL_DLLEXPORT void jl_register_fptrs_fallback(uint64_t image_base, const struct _jl_image_fptrs_t *fptrs, jl_method_instance_t **linfos, size_t n) { - (void)sysimage_base; (void)fptrs; (void)linfos; (void)n; + (void)image_base; (void)fptrs; (void)linfos; (void)n; } JL_DLLEXPORT jl_code_instance_t *jl_generate_fptr_fallback(jl_method_instance_t *mi JL_PROPAGATES_ROOT, size_t world) @@ -66,7 +67,7 @@ JL_DLLEXPORT size_t jl_jit_total_bytes_fallback(void) return 0; } -JL_DLLEXPORT void *jl_create_native_fallback(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvmmod, const jl_cgparams_t *cgparams, int _policy, int _imaging_mode) UNAVAILABLE +JL_DLLEXPORT void *jl_create_native_fallback(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvmmod, const jl_cgparams_t *cgparams, int _policy, int _imaging_mode, int _external_linkage) UNAVAILABLE JL_DLLEXPORT void jl_dump_compiles_fallback(void *s) { diff --git a/src/codegen.cpp b/src/codegen.cpp index 5b173d39305846..c01e340431e5f0 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1419,6 +1419,7 @@ class jl_codectx_t { jl_codegen_params_t &emission_context; llvm::MapVector call_targets; std::map &global_targets; + std::map, Function*> &external_calls; Function *f = NULL; // local var info. globals are not in here. std::vector slots; @@ -1455,6 +1456,7 @@ class jl_codectx_t { bool debug_enabled = false; bool use_cache = false; + bool external_linkage = false; const jl_cgparams_t *params = NULL; std::vector llvmcall_modules; @@ -1464,8 +1466,10 @@ class jl_codectx_t { emission_context(params), call_targets(), global_targets(params.globals), + external_calls(params.external_fns), world(params.world), use_cache(params.cache), + external_linkage(params.external_linkage), params(params.params) { } jl_typecache_t &types() { @@ -4028,9 +4032,17 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, const std::string name; StringRef protoname; bool need_to_emit = true; - // TODO: We should check if the code is available externally - // and then emit a trampoline. - if (ctx.use_cache) { + bool cache_valid = ctx.use_cache; + bool external = false; + if (ctx.external_linkage) { + if (jl_object_in_image((jl_value_t*)codeinst)) { + // Target is present in another pkgimage + cache_valid = true; + external = true; + } + } + + if (cache_valid) { // optimization: emit the correct name immediately, if we know it // TODO: use `emitted` map here too to try to consolidate names? // WARNING: isspecsig is protected by the codegen-lock. If that lock is removed, then the isspecsig load needs to be properly atomically sequenced with this. @@ -4058,6 +4070,13 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, const result = emit_call_specfun_other(ctx, mi, codeinst->rettype, protoname, argv, nargs, &cc, &return_roots, rt); else result = emit_call_specfun_boxed(ctx, codeinst->rettype, protoname, argv, nargs, rt); + if (external) { + assert(!need_to_emit); + auto calledF = jl_Module->getFunction(protoname); + assert(calledF); + // TODO: Check if already present? + ctx.external_calls[std::make_tuple(codeinst, specsig)] = calledF; + } handled = true; if (need_to_emit) { Function *trampoline_decl = cast(jl_Module->getNamedValue(protoname)); @@ -5376,7 +5395,16 @@ static Function *emit_tojlinvoke(jl_code_instance_t *codeinst, Module *M, jl_cod Function *theFunc; Value *theFarg; auto invoke = jl_atomic_load_relaxed(&codeinst->invoke); - if (params.cache && invoke != NULL) { + + bool cache_valid = params.cache; + if (params.external_linkage) { + if (jl_object_in_image((jl_value_t*)codeinst)) { + // Target is present in another pkgimage + cache_valid = true; + } + } + + if (cache_valid && invoke != NULL) { StringRef theFptrName = jl_ExecutionEngine->getFunctionAtAddress((uintptr_t)invoke, codeinst); theFunc = cast( M->getOrInsertFunction(theFptrName, jlinvoke_func->_type(ctx.builder.getContext())).getCallee()); @@ -8273,12 +8301,12 @@ void jl_compile_workqueue( StringRef preal_decl = ""; bool preal_specsig = false; auto invoke = jl_atomic_load_relaxed(&codeinst->invoke); - // TODO: available_extern - // We need to emit a trampoline that loads the target address in an extern_module from a GV - // Right now we will unecessarily emit a function we have already compiled in a native module - // again in a calling module. + bool cache_valid = params.cache; + if (params.external_linkage) { + cache_valid = jl_object_in_image((jl_value_t*)codeinst); + } // WARNING: isspecsig is protected by the codegen-lock. If that lock is removed, then the isspecsig load needs to be properly atomically sequenced with this. - if (params.cache && invoke != NULL) { + if (cache_valid && invoke != NULL) { auto fptr = jl_atomic_load_relaxed(&codeinst->specptr.fptr); if (invoke == jl_fptr_args_addr) { preal_decl = jl_ExecutionEngine->getFunctionAtAddress((uintptr_t)fptr, codeinst); diff --git a/src/coverage.cpp b/src/coverage.cpp index 46363a7e9ac01d..0dfb903798bfac 100644 --- a/src/coverage.cpp +++ b/src/coverage.cpp @@ -17,7 +17,7 @@ using namespace llvm; static int codegen_imaging_mode(void) { - return jl_options.image_codegen || (jl_generating_output() && !jl_options.incremental); + return jl_options.image_codegen || jl_generating_output(); } // Logging for code coverage and memory allocation diff --git a/src/debug-registry.h b/src/debug-registry.h index 3780bbee33718c..165f0efa479e30 100644 --- a/src/debug-registry.h +++ b/src/debug-registry.h @@ -81,11 +81,11 @@ class JITDebugInfoRegistry ~Locked() JL_NOTSAFEPOINT = default; }; - struct sysimg_info_t { - uint64_t jl_sysimage_base; - jl_sysimg_fptrs_t sysimg_fptrs; - jl_method_instance_t **sysimg_fvars_linfo; - size_t sysimg_fvars_n; + struct image_info_t { + uint64_t base; + jl_image_fptrs_t fptrs; + jl_method_instance_t **fvars_linfo; + size_t fvars_n; }; struct libc_frames_t { @@ -122,7 +122,7 @@ class JITDebugInfoRegistry // that it came from (providing name, type signature, file info, etc.) Locked> codeinst_in_flight{}; - Locked sysimg_info{}; + Locked> image_info{}; Locked objfilemap{}; @@ -141,7 +141,7 @@ class JITDebugInfoRegistry std::function getLoadAddress, std::function lookupWriteAddress) JL_NOTSAFEPOINT; objectmap_t& getObjectMap() JL_NOTSAFEPOINT; - void set_sysimg_info(sysimg_info_t info) JL_NOTSAFEPOINT; - Locked::ConstLockT get_sysimg_info() const JL_NOTSAFEPOINT; + void add_image_info(image_info_t info) JL_NOTSAFEPOINT; + bool get_image_info(uint64_t base, image_info_t *info) const JL_NOTSAFEPOINT; Locked::LockT get_objfile_map() JL_NOTSAFEPOINT; }; diff --git a/src/debuginfo.cpp b/src/debuginfo.cpp index fe5614100f9e32..997c04aff6445a 100644 --- a/src/debuginfo.cpp +++ b/src/debuginfo.cpp @@ -109,13 +109,19 @@ JITDebugInfoRegistry::getObjectMap() JL_NOTSAFEPOINT return objectmap; } -void JITDebugInfoRegistry::set_sysimg_info(sysimg_info_t info) JL_NOTSAFEPOINT { - (**this->sysimg_info) = info; +void JITDebugInfoRegistry::add_image_info(image_info_t info) JL_NOTSAFEPOINT { + (**this->image_info)[info.base] = info; } -JITDebugInfoRegistry::Locked::ConstLockT -JITDebugInfoRegistry::get_sysimg_info() const JL_NOTSAFEPOINT { - return *this->sysimg_info; + +bool JITDebugInfoRegistry::get_image_info(uint64_t base, JITDebugInfoRegistry::image_info_t *info) const JL_NOTSAFEPOINT { + auto infos = *this->image_info; + auto it = infos->find(base); + if (it != infos->end()) { + *info = it->second; + return true; + } + return false; } JITDebugInfoRegistry::Locked::LockT @@ -680,10 +686,10 @@ openDebugInfo(StringRef debuginfopath, const debug_link_info &info) std::move(SplitFile.get())); } extern "C" JL_DLLEXPORT -void jl_register_fptrs_impl(uint64_t sysimage_base, const jl_sysimg_fptrs_t *fptrs, +void jl_register_fptrs_impl(uint64_t image_base, const jl_image_fptrs_t *fptrs, jl_method_instance_t **linfos, size_t n) { - getJITDebugRegistry().set_sysimg_info({(uintptr_t) sysimage_base, *fptrs, linfos, n}); + getJITDebugRegistry().add_image_info({(uintptr_t) image_base, *fptrs, linfos, n}); } template @@ -694,12 +700,9 @@ static inline void ignoreError(T &err) JL_NOTSAFEPOINT #endif } -static void get_function_name_and_base(llvm::object::SectionRef Section, size_t pointer, int64_t slide, bool insysimage, +static void get_function_name_and_base(llvm::object::SectionRef Section, size_t pointer, int64_t slide, bool inimage, void **saddr, char **name, bool untrusted_dladdr) JL_NOTSAFEPOINT { - // Assume we only need base address for sysimg for now - if (!insysimage || !getJITDebugRegistry().get_sysimg_info()->sysimg_fptrs.base) - saddr = nullptr; bool needs_saddr = saddr && (!*saddr || untrusted_dladdr); bool needs_name = name && (!*name || untrusted_dladdr); // Try platform specific methods first since they are usually faster @@ -780,7 +783,7 @@ static void get_function_name_and_base(llvm::object::SectionRef Section, size_t } #ifdef _OS_WINDOWS_ // For ntdll and msvcrt since we are currently only parsing DWARF debug info through LLVM - if (!insysimage && needs_name) { + if (!inimage && needs_name) { static char frame_info_func[ sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; @@ -1012,7 +1015,7 @@ static object::SectionRef getModuleSectionForAddress(const object::ObjectFile *o bool jl_dylib_DI_for_fptr(size_t pointer, object::SectionRef *Section, int64_t *slide, llvm::DIContext **context, - bool onlySysImg, bool *isSysImg, void **saddr, char **name, char **filename) JL_NOTSAFEPOINT + bool onlyImage, bool *isImage, uint64_t *_fbase, void **saddr, char **name, char **filename) JL_NOTSAFEPOINT { *Section = object::SectionRef(); *context = NULL; @@ -1046,10 +1049,11 @@ bool jl_dylib_DI_for_fptr(size_t pointer, object::SectionRef *Section, int64_t * if (fname.empty()) // empirically, LoadedImageName might be missing fname = ModuleInfo.ImageName; DWORD64 fbase = ModuleInfo.BaseOfImage; - bool insysimage = (fbase == getJITDebugRegistry().get_sysimg_info()->jl_sysimage_base); - if (isSysImg) - *isSysImg = insysimage; - if (onlySysImg && !insysimage) + JITDebugInfoRegistry::image_info_t image_info; + bool inimage = getJITDebugRegistry().get_image_info(fbase, &image_info); + if (isImage) + *isImage = inimage; + if (onlyImage && !inimage) return false; // If we didn't find the filename before in the debug // info, use the dll name @@ -1057,6 +1061,8 @@ bool jl_dylib_DI_for_fptr(size_t pointer, object::SectionRef *Section, int64_t * jl_copy_str(filename, fname.data()); if (saddr) *saddr = NULL; + if (_fbase) + *_fbase = fbase; #else // ifdef _OS_WINDOWS_ Dl_info dlinfo; @@ -1095,16 +1101,19 @@ bool jl_dylib_DI_for_fptr(size_t pointer, object::SectionRef *Section, int64_t * fbase = (uintptr_t)dlinfo.dli_fbase; #endif StringRef fname; - bool insysimage = (fbase == getJITDebugRegistry().get_sysimg_info()->jl_sysimage_base); - if (saddr && !(insysimage && untrusted_dladdr)) + JITDebugInfoRegistry::image_info_t image_info; + bool inimage = getJITDebugRegistry().get_image_info(fbase, &image_info); + if (saddr && !(inimage && untrusted_dladdr)) *saddr = dlinfo.dli_saddr; - if (isSysImg) - *isSysImg = insysimage; - if (onlySysImg && !insysimage) + if (isImage) + *isImage = inimage; + if (onlyImage && !inimage) return false; + if (_fbase) + *_fbase = fbase; // In case we fail with the debug info lookup, we at least still // have the function name, even if we don't have line numbers - if (name && !(insysimage && untrusted_dladdr)) + if (name && !(inimage && untrusted_dladdr)) jl_copy_str(name, dlinfo.dli_sname); if (filename) jl_copy_str(filename, dlinfo.dli_fname); @@ -1115,7 +1124,10 @@ bool jl_dylib_DI_for_fptr(size_t pointer, object::SectionRef *Section, int64_t * *context = entry.ctx; if (entry.obj) *Section = getModuleSectionForAddress(entry.obj, pointer + entry.slide); - get_function_name_and_base(*Section, pointer, entry.slide, insysimage, saddr, name, untrusted_dladdr); + // Assume we only need base address for sysimg for now + if (!inimage || !image_info.fptrs.base) + saddr = nullptr; + get_function_name_and_base(*Section, pointer, entry.slide, inimage, saddr, name, untrusted_dladdr); return true; } @@ -1144,34 +1156,36 @@ static int jl_getDylibFunctionInfo(jl_frame_t **frames, size_t pointer, int skip object::SectionRef Section; llvm::DIContext *context = NULL; int64_t slide; - bool isSysImg; + bool isImage; void *saddr; - if (!jl_dylib_DI_for_fptr(pointer, &Section, &slide, &context, skipC, &isSysImg, &saddr, &frame0->func_name, &frame0->file_name)) { + uint64_t fbase; + if (!jl_dylib_DI_for_fptr(pointer, &Section, &slide, &context, skipC, &isImage, &fbase, &saddr, &frame0->func_name, &frame0->file_name)) { frame0->fromC = 1; return 1; } - frame0->fromC = !isSysImg; + frame0->fromC = !isImage; { - auto sysimg_locked = getJITDebugRegistry().get_sysimg_info(); - if (isSysImg && sysimg_locked->sysimg_fptrs.base && saddr) { - intptr_t diff = (uintptr_t)saddr - (uintptr_t)sysimg_locked->sysimg_fptrs.base; - for (size_t i = 0; i < sysimg_locked->sysimg_fptrs.nclones; i++) { - if (diff == sysimg_locked->sysimg_fptrs.clone_offsets[i]) { - uint32_t idx = sysimg_locked->sysimg_fptrs.clone_idxs[i] & jl_sysimg_val_mask; - if (idx < sysimg_locked->sysimg_fvars_n) // items after this were cloned but not referenced directly by a method (such as our ccall PLT thunks) - frame0->linfo = sysimg_locked->sysimg_fvars_linfo[idx]; + JITDebugInfoRegistry::image_info_t image; + bool inimage = getJITDebugRegistry().get_image_info(fbase, &image); + if (isImage && saddr && inimage) { + intptr_t diff = (uintptr_t)saddr - (uintptr_t)image.fptrs.base; + for (size_t i = 0; i < image.fptrs.nclones; i++) { + if (diff == image.fptrs.clone_offsets[i]) { + uint32_t idx = image.fptrs.clone_idxs[i] & jl_sysimg_val_mask; + if (idx < image.fvars_n) // items after this were cloned but not referenced directly by a method (such as our ccall PLT thunks) + frame0->linfo = image.fvars_linfo[idx]; break; } } - for (size_t i = 0; i < sysimg_locked->sysimg_fvars_n; i++) { - if (diff == sysimg_locked->sysimg_fptrs.offsets[i]) { - frame0->linfo = sysimg_locked->sysimg_fvars_linfo[i]; + for (size_t i = 0; i < image.fvars_n; i++) { + if (diff == image.fptrs.offsets[i]) { + frame0->linfo = image.fvars_linfo[i]; break; } } } } - return lookup_pointer(Section, context, frames, pointer, slide, isSysImg, noInline); + return lookup_pointer(Section, context, frames, pointer, slide, isImage, noInline); } int jl_DI_for_fptr(uint64_t fptr, uint64_t *symsize, int64_t *slide, diff --git a/src/debuginfo.h b/src/debuginfo.h index 5ea34350ac1fb2..5b5cdcb82d534f 100644 --- a/src/debuginfo.h +++ b/src/debuginfo.h @@ -6,7 +6,7 @@ int jl_DI_for_fptr(uint64_t fptr, uint64_t *symsize, int64_t *slide, llvm::object::SectionRef *Section, llvm::DIContext **context) JL_NOTSAFEPOINT; bool jl_dylib_DI_for_fptr(size_t pointer, llvm::object::SectionRef *Section, int64_t *slide, llvm::DIContext **context, - bool onlySysImg, bool *isSysImg, void **saddr, char **name, char **filename) JL_NOTSAFEPOINT; + bool onlyImage, bool *isImage, uint64_t* fbase, void **saddr, char **name, char **filename) JL_NOTSAFEPOINT; static object::SectionedAddress makeAddress( llvm::object::SectionRef Section, uint64_t address) JL_NOTSAFEPOINT diff --git a/src/disasm.cpp b/src/disasm.cpp index cfc030f649fd67..5b510a24b33da8 100644 --- a/src/disasm.cpp +++ b/src/disasm.cpp @@ -592,7 +592,7 @@ jl_value_t *jl_dump_fptr_asm_impl(uint64_t fptr, char raw_mc, const char* asm_va llvm::DIContext *context = NULL; if (!jl_DI_for_fptr(fptr, &symsize, &slide, &Section, &context)) { if (!jl_dylib_DI_for_fptr(fptr, &Section, &slide, &context, - false, NULL, NULL, NULL, NULL)) { + false, NULL, NULL, NULL, NULL, NULL)) { jl_printf(JL_STDERR, "WARNING: Unable to find function pointer\n"); return jl_pchar_to_string("", 0); } diff --git a/src/jitlayers.h b/src/jitlayers.h index 9e3fa6dc5711d9..8edeec8929014c 100644 --- a/src/jitlayers.h +++ b/src/jitlayers.h @@ -73,7 +73,7 @@ GlobalVariable *jl_emit_RTLD_DEFAULT_var(Module *M); DataLayout jl_create_datalayout(TargetMachine &TM); static inline bool imaging_default() { - return jl_options.image_codegen || (jl_generating_output() && !jl_options.incremental); + return jl_options.image_codegen || jl_generating_output(); } struct OptimizationOptions { @@ -173,6 +173,7 @@ typedef struct _jl_codegen_params_t { // outputs std::vector> workqueue; std::map globals; + std::map, Function*> external_fns; std::map ditypes; std::map llvmtypes; DenseMap mergedConstants; @@ -200,6 +201,7 @@ typedef struct _jl_codegen_params_t { size_t world = 0; const jl_cgparams_t *params = &jl_default_cgparams; bool cache = false; + bool external_linkage = false; bool imaging; _jl_codegen_params_t(orc::ThreadSafeContext ctx) : tsctx(std::move(ctx)), tsctx_lock(tsctx.getLock()), imaging(imaging_default()) {} } jl_codegen_params_t; diff --git a/src/jl_exported_data.inc b/src/jl_exported_data.inc index a254fba5e2b283..dd38560af14140 100644 --- a/src/jl_exported_data.inc +++ b/src/jl_exported_data.inc @@ -128,6 +128,8 @@ XX(jl_voidpointer_type) \ XX(jl_void_type) \ XX(jl_weakref_type) \ + XX(jl_build_ids) \ + XX(jl_linkage_blobs) \ // Data symbols that are defined inside the public libjulia #define JL_EXPORTED_DATA_SYMBOLS(XX) \ diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index 2e5df94dc72007..8ea77bfe12be35 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -118,6 +118,7 @@ XX(jl_dlopen) \ XX(jl_dlsym) \ XX(jl_dump_host_cpu) \ + XX(jl_check_pkgimage_clones) \ XX(jl_egal) \ XX(jl_egal__bits) \ XX(jl_egal__special) \ @@ -394,6 +395,8 @@ XX(jl_queue_work) \ XX(jl_raise_debugger) \ XX(jl_readuntil) \ + XX(jl_cache_flags) \ + XX(jl_match_cache_flags) \ XX(jl_read_verify_header) \ XX(jl_realloc) \ XX(jl_register_newmeth_tracer) \ @@ -536,6 +539,7 @@ YY(jl_get_LLVM_VERSION) \ YY(jl_dump_native) \ YY(jl_get_llvm_gvs) \ + YY(jl_get_llvm_external_fns) \ YY(jl_dump_function_asm) \ YY(jl_LLVMCreateDisasm) \ YY(jl_LLVMDisasmInstruction) \ diff --git a/src/jloptions.c b/src/jloptions.c index da362e0054b3ee..90bb39955ee42f 100644 --- a/src/jloptions.c +++ b/src/jloptions.c @@ -71,6 +71,7 @@ JL_DLLEXPORT void jl_init_options(void) JL_OPTIONS_HANDLE_SIGNALS_ON, JL_OPTIONS_USE_SYSIMAGE_NATIVE_CODE_YES, JL_OPTIONS_USE_COMPILED_MODULES_YES, + JL_OPTIONS_USE_PKGIMAGES_YES, NULL, // bind-to NULL, // output-bc NULL, // output-unopt-bc @@ -92,7 +93,7 @@ JL_DLLEXPORT void jl_init_options(void) static const char usage[] = "\n julia [switches] -- [programfile] [args...]\n\n"; static const char opts[] = - "Switches (a '*' marks the default value, if applicable):\n\n" + "Switches (a '*' marks the default value, if applicable; settings marked '($)' may trigger package precompilation):\n\n" " -v, --version Display version information\n" " -h, --help Print this message (--help-hidden for more)\n" " --help-hidden Uncommon options not shown by `-h`\n\n" @@ -107,7 +108,9 @@ static const char opts[] = " --sysimage-native-code={yes*|no}\n" " Use native code from system image if available\n" " --compiled-modules={yes*|no}\n" - " Enable or disable incremental precompilation of modules\n\n" + " Enable or disable incremental precompilation of modules\n" + " --pkgimages={yes*|no}\n" + " Enable or disable usage of native code caching in the form of pkgimages ($)\n\n" // actions " -e, --eval Evaluate \n" @@ -143,16 +146,16 @@ static const char opts[] = // code generation options " -C, --cpu-target Limit usage of CPU features up to ; set to `help` to see the available options\n" - " -O, --optimize={0,1,2*,3} Set the optimization level (level 3 if `-O` is used without a level)\n" + " -O, --optimize={0,1,2*,3} Set the optimization level (level 3 if `-O` is used without a level) ($)\n" " --min-optlevel={0*,1,2,3} Set a lower bound on the optimization level\n" #ifdef JL_DEBUG_BUILD - " -g, --debug-info=[{0,1,2*}] Set the level of debug info generation in the julia-debug build\n" + " -g, --debug-info=[{0,1,2*}] Set the level of debug info generation in the julia-debug build ($)\n" #else - " -g, --debug-info=[{0,1*,2}] Set the level of debug info generation (level 2 if `-g` is used without a level)\n" + " -g, --debug-info=[{0,1*,2}] Set the level of debug info generation (level 2 if `-g` is used without a level) ($)\n" #endif " --inline={yes*|no} Control whether inlining is permitted, including overriding @inline declarations\n" " --check-bounds={yes|no|auto*}\n" - " Emit bounds checks always, never, or respect @inbounds declarations\n" + " Emit bounds checks always, never, or respect @inbounds declarations ($)\n" #ifdef USE_POLLY " --polly={yes*|no} Enable or disable the polyhedral optimizer Polly (overrides @polly declaration)\n" #endif @@ -239,6 +242,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) opt_banner, opt_sysimage_native_code, opt_compiled_modules, + opt_pkgimages, opt_machine_file, opt_project, opt_bug_report, @@ -267,6 +271,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) { "sysimage", required_argument, 0, 'J' }, { "sysimage-native-code", required_argument, 0, opt_sysimage_native_code }, { "compiled-modules",required_argument, 0, opt_compiled_modules }, + { "pkgimages", required_argument, 0, opt_pkgimages }, { "cpu-target", required_argument, 0, 'C' }, { "procs", required_argument, 0, 'p' }, { "threads", required_argument, 0, 't' }, @@ -317,6 +322,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) const char **cmds = NULL; int codecov = JL_LOG_NONE; int malloclog = JL_LOG_NONE; + int pkgimage_explicit = 0; int argc = *argcp; char **argv = *argvp; char *endptr; @@ -444,6 +450,15 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) else jl_errorf("julia: invalid argument to --compiled-modules={yes|no} (%s)", optarg); break; + case opt_pkgimages: + pkgimage_explicit = 1; + if (!strcmp(optarg,"yes")) + jl_options.use_pkgimages = JL_OPTIONS_USE_PKGIMAGES_YES; + else if (!strcmp(optarg,"no")) + jl_options.use_pkgimages = JL_OPTIONS_USE_PKGIMAGES_NO; + else + jl_errorf("julia: invalid argument to --pkgimage={yes|no} (%s)", optarg); + break; case 'C': // cpu-target jl_options.cpu_target = strdup(optarg); if (!jl_options.cpu_target) @@ -803,6 +818,13 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) "This is a bug, please report it.", c); } } + if (codecov || malloclog) { + if (pkgimage_explicit && jl_options.use_pkgimages) { + jl_errorf("julia: Can't use --pkgimages=yes together " + "with --track-allocation or --code-coverage."); + } + jl_options.use_pkgimages = 0; + } jl_options.code_coverage = codecov; jl_options.malloc_log = malloclog; int proc_args = *argcp < optind ? *argcp : optind; diff --git a/src/jloptions.h b/src/jloptions.h index d7be95348f01f1..d0aba777027e7d 100644 --- a/src/jloptions.h +++ b/src/jloptions.h @@ -42,6 +42,7 @@ typedef struct { int8_t handle_signals; int8_t use_sysimage_native_code; int8_t use_compiled_modules; + int8_t use_pkgimages; const char *bindto; const char *outputbc; const char *outputunoptbc; diff --git a/src/julia.h b/src/julia.h index 16f55a019d9588..84fecd5fe28c4a 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1762,7 +1762,7 @@ JL_DLLEXPORT jl_gcframe_t **jl_adopt_thread(void); JL_DLLEXPORT int jl_deserialize_verify_header(ios_t *s); JL_DLLEXPORT void jl_preload_sysimg_so(const char *fname); JL_DLLEXPORT void jl_set_sysimg_so(void *handle); -JL_DLLEXPORT ios_t *jl_create_system_image(void *, jl_array_t *worklist); +JL_DLLEXPORT void jl_create_system_image(void **, jl_array_t *worklist, bool_t emit_split, ios_t **s, ios_t **z, jl_array_t **udeps, int64_t *srctextpos); JL_DLLEXPORT void jl_restore_system_image(const char *fname); JL_DLLEXPORT void jl_restore_system_image_data(const char *buf, size_t len); JL_DLLEXPORT jl_value_t *jl_restore_incremental(const char *fname, jl_array_t *depmods, int complete); @@ -2182,6 +2182,9 @@ JL_DLLEXPORT int jl_generating_output(void) JL_NOTSAFEPOINT; #define JL_OPTIONS_USE_COMPILED_MODULES_YES 1 #define JL_OPTIONS_USE_COMPILED_MODULES_NO 0 +#define JL_OPTIONS_USE_PKGIMAGES_YES 1 +#define JL_OPTIONS_USE_PKGIMAGES_NO 0 + // Version information #include // Generated file diff --git a/src/julia_internal.h b/src/julia_internal.h index 5710d430dc8ac1..a13e548b13b163 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -992,11 +992,12 @@ JL_DLLEXPORT jl_value_t *jl_dump_fptr_asm(uint64_t fptr, char raw_mc, const char JL_DLLEXPORT jl_value_t *jl_dump_function_ir(jl_llvmf_dump_t *dump, char strip_ir_metadata, char dump_module, const char *debuginfo); JL_DLLEXPORT jl_value_t *jl_dump_function_asm(jl_llvmf_dump_t *dump, char raw_mc, const char* asm_variant, const char *debuginfo, char binary); -void *jl_create_native(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvmmod, const jl_cgparams_t *cgparams, int policy, int imaging_mode); +void *jl_create_native(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvmmod, const jl_cgparams_t *cgparams, int policy, int imaging_mode, int cache); void jl_dump_native(void *native_code, const char *bc_fname, const char *unopt_bc_fname, const char *obj_fname, const char *asm_fname, - const char *sysimg_data, size_t sysimg_len); + const char *sysimg_data, size_t sysimg_len, ios_t *s); void jl_get_llvm_gvs(void *native_code, arraylist_t *gvs); +void jl_get_llvm_external_fns(void *native_code, arraylist_t *gvs); JL_DLLEXPORT void jl_get_function_id(void *native_code, jl_code_instance_t *ncode, int32_t *func_idx, int32_t *specfunc_idx); @@ -1619,9 +1620,9 @@ extern JL_DLLEXPORT jl_sym_t *jl_sequentially_consistent_sym; JL_DLLEXPORT enum jl_memory_order jl_get_atomic_order(jl_sym_t *order, char loading, char storing); JL_DLLEXPORT enum jl_memory_order jl_get_atomic_order_checked(jl_sym_t *order, char loading, char storing); -struct _jl_sysimg_fptrs_t; +struct _jl_image_fptrs_t; -void jl_register_fptrs(uint64_t sysimage_base, const struct _jl_sysimg_fptrs_t *fptrs, +void jl_register_fptrs(uint64_t image_base, const struct _jl_image_fptrs_t *fptrs, jl_method_instance_t **linfos, size_t n); void jl_write_coverage_data(const char*); void jl_write_malloc_log(void); diff --git a/src/llvm-multiversioning.cpp b/src/llvm-multiversioning.cpp index 815ebfe7ed1011..97867f1d9f471d 100644 --- a/src/llvm-multiversioning.cpp +++ b/src/llvm-multiversioning.cpp @@ -47,6 +47,8 @@ extern Optional always_have_fma(Function&); extern Optional always_have_fp16(); +void replaceUsesWithLoad(Function &F, function_ref should_replace, MDNode *tbaa_const); + namespace { constexpr uint32_t clone_mask = JL_TARGET_CLONE_LOOP | JL_TARGET_CLONE_SIMD | JL_TARGET_CLONE_MATH | JL_TARGET_CLONE_CPU; @@ -266,8 +268,6 @@ struct CloneCtx { uint32_t get_func_id(Function *F); template Constant *rewrite_gv_init(const Stack& stack); - template - Value *rewrite_inst_use(const Stack& stack, Value *replace, Instruction *insert_before); std::pair get_reloc_slot(Function *F); Constant *get_ptrdiff32(Constant *ptr, Constant *base) const; template @@ -820,7 +820,7 @@ std::pair CloneCtx::get_reloc_slot(Function *F) } template -Value *CloneCtx::rewrite_inst_use(const Stack& stack, Value *replace, Instruction *insert_before) +static Value *rewrite_inst_use(const Stack& stack, Value *replace, Instruction *insert_before) { SmallVector args; uint32_t nlevel = stack.size(); @@ -879,40 +879,24 @@ void CloneCtx::fix_inst_uses() continue; auto orig_f = orig_funcs[i]; auto F = grp.base_func(orig_f); - bool changed; - do { - changed = false; - for (auto uses = ConstantUses(F, M); !uses.done(); uses.next()) { - auto info = uses.get_info(); - auto use_i = info.val; - auto use_f = use_i->getFunction(); - if (!use_f->getName().endswith(suffix)) + replaceUsesWithLoad(*F, [&](Instruction &I) -> GlobalVariable * { + uint32_t id; + GlobalVariable *slot; + auto use_f = I.getFunction(); + if (!use_f->getName().endswith(suffix)) + return nullptr; + std::tie(id, slot) = get_reloc_slot(orig_f); + + grp.relocs.insert(id); + for (auto &tgt: grp.clones) { + // The enclosing function of the use is cloned, + // no need to deal with this use on this target. + if (map_get(*tgt.vmap, use_f)) continue; - Instruction *insert_before = use_i; - if (auto phi = dyn_cast(use_i)) - insert_before = phi->getIncomingBlock(*info.use)->getTerminator(); - uint32_t id; - GlobalVariable *slot; - std::tie(id, slot) = get_reloc_slot(orig_f); - Instruction *ptr = new LoadInst(orig_f->getType(), slot, "", false, insert_before); - ptr->setMetadata(llvm::LLVMContext::MD_tbaa, tbaa_const); - ptr->setMetadata(llvm::LLVMContext::MD_invariant_load, MDNode::get(ptr->getContext(), None)); - use_i->setOperand(info.use->getOperandNo(), - rewrite_inst_use(uses.get_stack(), ptr, - insert_before)); - - grp.relocs.insert(id); - for (auto &tgt: grp.clones) { - // The enclosing function of the use is cloned, - // no need to deal with this use on this target. - if (map_get(*tgt.vmap, use_f)) - continue; - tgt.relocs.insert(id); - } - - changed = true; + tgt.relocs.insert(id); } - } while (changed); + return slot; + }, tbaa_const); } } } @@ -1202,6 +1186,30 @@ static RegisterPass X("JuliaMultiVersioning", "JuliaMulti } // anonymous namespace +void replaceUsesWithLoad(Function &F, function_ref should_replace, MDNode *tbaa_const) { + bool changed; + do { + changed = false; + for (auto uses = ConstantUses(&F, *F.getParent()); !uses.done(); uses.next()) { + auto info = uses.get_info(); + auto use_i = info.val; + GlobalVariable *slot = should_replace(*use_i); + if (!slot) + continue; + Instruction *insert_before = use_i; + if (auto phi = dyn_cast(use_i)) + insert_before = phi->getIncomingBlock(*info.use)->getTerminator(); + Instruction *ptr = new LoadInst(F.getType(), slot, "", false, insert_before); + ptr->setMetadata(llvm::LLVMContext::MD_tbaa, tbaa_const); + ptr->setMetadata(llvm::LLVMContext::MD_invariant_load, MDNode::get(ptr->getContext(), None)); + use_i->setOperand(info.use->getOperandNo(), + rewrite_inst_use(uses.get_stack(), ptr, + insert_before)); + changed = true; + } + } while (changed); +} + PreservedAnalyses MultiVersioning::run(Module &M, ModuleAnalysisManager &AM) { auto &FAM = AM.getResult(M).getManager(); diff --git a/src/precompile.c b/src/precompile.c index ebe7afae69f641..bfc123cf3fda83 100644 --- a/src/precompile.c +++ b/src/precompile.c @@ -10,6 +10,7 @@ #include "julia.h" #include "julia_internal.h" #include "julia_assert.h" +#include "serialize.h" #ifdef __cplusplus extern "C" { @@ -20,8 +21,53 @@ JL_DLLEXPORT int jl_generating_output(void) return jl_options.outputo || jl_options.outputbc || jl_options.outputunoptbc || jl_options.outputji || jl_options.outputasm; } -static void *jl_precompile(int all); -static void *jl_precompile_worklist(jl_array_t *worklist); +void write_srctext(ios_t *f, jl_array_t *udeps, int64_t srctextpos) { + // Write the source-text for the dependent files + if (udeps) { + // Go back and update the source-text position to point to the current position + int64_t posfile = ios_pos(f); + ios_seek(f, srctextpos); + write_uint64(f, posfile); + ios_seek_end(f); + // Each source-text file is written as + // int32: length of abspath + // char*: abspath + // uint64: length of src text + // char*: src text + // At the end we write int32(0) as a terminal sentinel. + size_t len = jl_array_len(udeps); + ios_t srctext; + for (size_t i = 0; i < len; i++) { + jl_value_t *deptuple = jl_array_ptr_ref(udeps, i); + jl_value_t *depmod = jl_fieldref(deptuple, 0); // module + // Dependencies declared with `include_dependency` are excluded + // because these may not be Julia code (and could be huge) + if (depmod != (jl_value_t*)jl_main_module) { + jl_value_t *dep = jl_fieldref(deptuple, 1); // file abspath + const char *depstr = jl_string_data(dep); + if (!depstr[0]) + continue; + ios_t *srctp = ios_file(&srctext, depstr, 1, 0, 0, 0); + if (!srctp) { + jl_printf(JL_STDERR, "WARNING: could not cache source text for \"%s\".\n", + jl_string_data(dep)); + continue; + } + size_t slen = jl_string_len(dep); + write_int32(f, slen); + ios_write(f, depstr, slen); + posfile = ios_pos(f); + write_uint64(f, 0); // placeholder for length of this file in bytes + uint64_t filelen = (uint64_t) ios_copyall(f, &srctext); + ios_close(&srctext); + ios_seek(f, posfile); + write_uint64(f, filelen); + ios_seek_end(f); + } + } + } + write_int32(f, 0); // mark the end of the source text +} JL_DLLEXPORT void jl_write_compiler_output(void) { @@ -35,7 +81,8 @@ JL_DLLEXPORT void jl_write_compiler_output(void) } jl_array_t *worklist = jl_module_init_order; - JL_GC_PUSH1(&worklist); + jl_array_t *udeps = NULL; + JL_GC_PUSH2(&worklist, &udeps); jl_module_init_order = jl_alloc_vec_any(0); int i, l = jl_array_len(worklist); for (i = 0; i < l; i++) { @@ -59,49 +106,54 @@ JL_DLLEXPORT void jl_write_compiler_output(void) assert(jl_precompile_toplevel_module == NULL); void *native_code = NULL; - if (jl_options.outputo || jl_options.outputbc || jl_options.outputunoptbc || jl_options.outputasm) { - if (jl_options.incremental) - jl_precompile_toplevel_module = (jl_module_t*)jl_array_ptr_ref(worklist, jl_array_len(worklist)-1); - native_code = jl_options.incremental ? jl_precompile_worklist(worklist) : jl_precompile(jl_options.compile_enabled == JL_OPTIONS_COMPILE_ALL); - if (jl_options.incremental) - jl_precompile_toplevel_module = NULL; - } - if (jl_options.incremental) { - if (jl_options.outputbc || jl_options.outputunoptbc) - jl_printf(JL_STDERR, "WARNING: incremental output to a .bc file is not implemented\n"); - if (jl_options.outputasm) - jl_printf(JL_STDERR, "WARNING: incremental output to a .s file is not implemented\n"); - if (jl_options.outputo) { - jl_printf(JL_STDERR, "WARNING: incremental output to a .o file is not implemented\n"); - } - } + bool_t emit_native = jl_options.outputo || jl_options.outputbc || jl_options.outputunoptbc || jl_options.outputasm; - ios_t *s = jl_create_system_image(native_code, jl_options.incremental ? worklist : NULL); + bool_t emit_split = jl_options.outputji && emit_native; - if (jl_options.outputji) { - ios_t f; - if (ios_file(&f, jl_options.outputji, 1, 1, 1, 1) == NULL) - jl_errorf("cannot open system image file \"%s\" for writing", jl_options.outputji); - ios_write(&f, (const char*)s->buf, (size_t)s->size); - ios_close(&f); - } + ios_t *s = NULL; + ios_t *z = NULL; + int64_t srctextpos = 0 ; + jl_create_system_image(&native_code, jl_options.incremental ? worklist : NULL, emit_split, + &s, &z, &udeps, &srctextpos); + if (!emit_split) + z = s; + + // jl_dump_native writes the clone_targets into `s` + // We need to postpone the srctext writing after that. if (native_code) { jl_dump_native(native_code, jl_options.outputbc, jl_options.outputunoptbc, jl_options.outputo, jl_options.outputasm, - (const char*)s->buf, (size_t)s->size); + (const char*)z->buf, (size_t)z->size, s); jl_postoutput_hook(); } + if ((jl_options.outputji || emit_native) && jl_options.incremental) { + write_srctext(s, udeps, srctextpos); + } + + if (jl_options.outputji) { + ios_t f; + if (ios_file(&f, jl_options.outputji, 1, 1, 1, 1) == NULL) + jl_errorf("cannot open system image file \"%s\" for writing", jl_options.outputji); + ios_write(&f, (const char*)s->buf, (size_t)s->size); + ios_close(&f); + } + if (s) { ios_close(s); free(s); } + if (emit_split) { + ios_close(z); + free(z); + } + for (size_t i = 0; i < jl_current_modules.size; i += 2) { if (jl_current_modules.table[i + 1] != HT_NOTFOUND) { jl_printf(JL_STDERR, "\nWARNING: detected unclosed module: "); @@ -112,296 +164,6 @@ JL_DLLEXPORT void jl_write_compiler_output(void) JL_GC_POP(); } -// f{<:Union{...}}(...) is a common pattern -// and expanding the Union may give a leaf function -static void _compile_all_tvar_union(jl_value_t *methsig) -{ - int tvarslen = jl_subtype_env_size(methsig); - jl_value_t *sigbody = methsig; - jl_value_t **roots; - JL_GC_PUSHARGS(roots, 1 + 2 * tvarslen); - jl_value_t **env = roots + 1; - int *idx = (int*)alloca(sizeof(int) * tvarslen); - int i; - for (i = 0; i < tvarslen; i++) { - assert(jl_is_unionall(sigbody)); - idx[i] = 0; - env[2 * i] = (jl_value_t*)((jl_unionall_t*)sigbody)->var; - env[2 * i + 1] = jl_bottom_type; // initialize the list with Union{}, since T<:Union{} is always a valid option - sigbody = ((jl_unionall_t*)sigbody)->body; - } - - for (i = 0; i < tvarslen; /* incremented by inner loop */) { - jl_value_t **sig = &roots[0]; - JL_TRY { - // TODO: wrap in UnionAll for each tvar in env[2*i + 1] ? - // currently doesn't matter much, since jl_compile_hint doesn't work on abstract types - *sig = (jl_value_t*)jl_instantiate_type_with(sigbody, env, tvarslen); - } - JL_CATCH { - goto getnext; // sigh, we found an invalid type signature. should we warn the user? - } - if (!jl_has_concrete_subtype(*sig)) - goto getnext; // signature wouldn't be callable / is invalid -- skip it - if (jl_is_concrete_type(*sig)) { - if (jl_compile_hint((jl_tupletype_t *)*sig)) - goto getnext; // success - } - - getnext: - for (i = 0; i < tvarslen; i++) { - jl_tvar_t *tv = (jl_tvar_t*)env[2 * i]; - if (jl_is_uniontype(tv->ub)) { - size_t l = jl_count_union_components(tv->ub); - size_t j = idx[i]; - if (j == l) { - env[2 * i + 1] = jl_bottom_type; - idx[i] = 0; - } - else { - jl_value_t *ty = jl_nth_union_component(tv->ub, j); - if (!jl_is_concrete_type(ty)) - ty = (jl_value_t*)jl_new_typevar(tv->name, tv->lb, ty); - env[2 * i + 1] = ty; - idx[i] = j + 1; - break; - } - } - else { - env[2 * i + 1] = (jl_value_t*)tv; - } - } - } - JL_GC_POP(); -} - -// f(::Union{...}, ...) is a common pattern -// and expanding the Union may give a leaf function -static void _compile_all_union(jl_value_t *sig) -{ - jl_tupletype_t *sigbody = (jl_tupletype_t*)jl_unwrap_unionall(sig); - size_t count_unions = 0; - size_t i, l = jl_svec_len(sigbody->parameters); - jl_svec_t *p = NULL; - jl_value_t *methsig = NULL; - - for (i = 0; i < l; i++) { - jl_value_t *ty = jl_svecref(sigbody->parameters, i); - if (jl_is_uniontype(ty)) - ++count_unions; - else if (ty == jl_bottom_type) - return; // why does this method exist? - else if (jl_is_datatype(ty) && !jl_has_free_typevars(ty) && - ((!jl_is_kind(ty) && ((jl_datatype_t*)ty)->isconcretetype) || - ((jl_datatype_t*)ty)->name == jl_type_typename)) - return; // no amount of union splitting will make this a leaftype signature - } - - if (count_unions == 0 || count_unions >= 6) { - _compile_all_tvar_union(sig); - return; - } - - int *idx = (int*)alloca(sizeof(int) * count_unions); - for (i = 0; i < count_unions; i++) { - idx[i] = 0; - } - - JL_GC_PUSH2(&p, &methsig); - int idx_ctr = 0, incr = 0; - while (!incr) { - p = jl_alloc_svec_uninit(l); - for (i = 0, idx_ctr = 0, incr = 1; i < l; i++) { - jl_value_t *ty = jl_svecref(sigbody->parameters, i); - if (jl_is_uniontype(ty)) { - assert(idx_ctr < count_unions); - size_t l = jl_count_union_components(ty); - size_t j = idx[idx_ctr]; - jl_svecset(p, i, jl_nth_union_component(ty, j)); - ++j; - if (incr) { - if (j == l) { - idx[idx_ctr] = 0; - } - else { - idx[idx_ctr] = j; - incr = 0; - } - } - ++idx_ctr; - } - else { - jl_svecset(p, i, ty); - } - } - methsig = (jl_value_t*)jl_apply_tuple_type(p); - methsig = jl_rewrap_unionall(methsig, sig); - _compile_all_tvar_union(methsig); - } - - JL_GC_POP(); -} - -static int compile_all_collect__(jl_typemap_entry_t *ml, void *env) -{ - jl_array_t *allmeths = (jl_array_t*)env; - jl_method_t *m = ml->func.method; - if (m->source) { - // method has a non-generated definition; can be compiled generically - jl_array_ptr_1d_push(allmeths, (jl_value_t*)m); - } - return 1; -} - -static int compile_all_collect_(jl_methtable_t *mt, void *env) -{ - jl_typemap_visitor(jl_atomic_load_relaxed(&mt->defs), compile_all_collect__, env); - return 1; -} - -static void jl_compile_all_defs(jl_array_t *mis) -{ - jl_array_t *allmeths = jl_alloc_vec_any(0); - JL_GC_PUSH1(&allmeths); - - jl_foreach_reachable_mtable(compile_all_collect_, allmeths); - - size_t i, l = jl_array_len(allmeths); - for (i = 0; i < l; i++) { - jl_method_t *m = (jl_method_t*)jl_array_ptr_ref(allmeths, i); - if (jl_is_datatype(m->sig) && jl_isa_compileable_sig((jl_tupletype_t*)m->sig, jl_emptysvec, m)) { - // method has a single compilable specialization, e.g. its definition - // signature is concrete. in this case we can just hint it. - jl_compile_hint((jl_tupletype_t*)m->sig); - } - else { - // first try to create leaf signatures from the signature declaration and compile those - _compile_all_union(m->sig); - - // finally, compile a fully generic fallback that can work for all arguments - jl_method_instance_t *unspec = jl_get_unspecialized(m); - if (unspec) - jl_array_ptr_1d_push(mis, (jl_value_t*)unspec); - } - } - - JL_GC_POP(); -} - -static int precompile_enq_specialization_(jl_method_instance_t *mi, void *closure) -{ - assert(jl_is_method_instance(mi)); - jl_code_instance_t *codeinst = jl_atomic_load_relaxed(&mi->cache); - while (codeinst) { - int do_compile = 0; - if (jl_atomic_load_relaxed(&codeinst->invoke) != jl_fptr_const_return) { - jl_value_t *inferred = jl_atomic_load_relaxed(&codeinst->inferred); - if (inferred && - inferred != jl_nothing && - jl_ir_flag_inferred((jl_array_t*)inferred) && - (jl_ir_inlining_cost((jl_array_t*)inferred) == UINT16_MAX)) { - do_compile = 1; - } - else if (jl_atomic_load_relaxed(&codeinst->invoke) != NULL || jl_atomic_load_relaxed(&codeinst->precompile)) { - do_compile = 1; - } - } - if (do_compile) { - jl_array_ptr_1d_push((jl_array_t*)closure, (jl_value_t*)mi); - return 1; - } - codeinst = jl_atomic_load_relaxed(&codeinst->next); - } - return 1; -} - -static int precompile_enq_all_specializations__(jl_typemap_entry_t *def, void *closure) -{ - jl_method_t *m = def->func.method; - if ((m->name == jl_symbol("__init__") || m->ccallable) && jl_is_dispatch_tupletype(m->sig)) { - // ensure `__init__()` and @ccallables get strongly-hinted, specialized, and compiled - jl_method_instance_t *mi = jl_specializations_get_linfo(m, m->sig, jl_emptysvec); - jl_array_ptr_1d_push((jl_array_t*)closure, (jl_value_t*)mi); - } - else { - jl_svec_t *specializations = jl_atomic_load_relaxed(&def->func.method->specializations); - size_t i, l = jl_svec_len(specializations); - for (i = 0; i < l; i++) { - jl_value_t *mi = jl_svecref(specializations, i); - if (mi != jl_nothing) - precompile_enq_specialization_((jl_method_instance_t*)mi, closure); - } - } - if (m->ccallable) - jl_array_ptr_1d_push((jl_array_t*)closure, (jl_value_t*)m->ccallable); - return 1; -} - -static int precompile_enq_all_specializations_(jl_methtable_t *mt, void *env) -{ - return jl_typemap_visitor(jl_atomic_load_relaxed(&mt->defs), precompile_enq_all_specializations__, env); -} - -static void *jl_precompile_(jl_array_t *m) -{ - jl_array_t *m2 = NULL; - jl_method_instance_t *mi = NULL; - JL_GC_PUSH2(&m2, &mi); - m2 = jl_alloc_vec_any(0); - for (size_t i = 0; i < jl_array_len(m); i++) { - jl_value_t *item = jl_array_ptr_ref(m, i); - if (jl_is_method_instance(item)) { - mi = (jl_method_instance_t*)item; - size_t min_world = 0; - size_t max_world = ~(size_t)0; - if (mi != jl_atomic_load_relaxed(&mi->def.method->unspecialized) && !jl_isa_compileable_sig((jl_tupletype_t*)mi->specTypes, mi->sparam_vals, mi->def.method)) - mi = jl_get_specialization1((jl_tupletype_t*)mi->specTypes, jl_atomic_load_acquire(&jl_world_counter), &min_world, &max_world, 0); - if (mi) - jl_array_ptr_1d_push(m2, (jl_value_t*)mi); - } - else { - assert(jl_is_simplevector(item)); - assert(jl_svec_len(item) == 2); - jl_array_ptr_1d_push(m2, item); - } - } - void *native_code = jl_create_native(m2, NULL, NULL, 0, 1); - JL_GC_POP(); - return native_code; -} - -static void *jl_precompile(int all) -{ - // array of MethodInstances and ccallable aliases to include in the output - jl_array_t *m = jl_alloc_vec_any(0); - JL_GC_PUSH1(&m); - if (all) - jl_compile_all_defs(m); - jl_foreach_reachable_mtable(precompile_enq_all_specializations_, m); - void *native_code = jl_precompile_(m); - JL_GC_POP(); - return native_code; -} - -static void *jl_precompile_worklist(jl_array_t *worklist) -{ - if (!worklist) - return NULL; - // this "found" array will contain function - // type signatures that were inferred but haven't been compiled - jl_array_t *m = jl_alloc_vec_any(0); - JL_GC_PUSH1(&m); - size_t i, nw = jl_array_len(worklist); - for (i = 0; i < nw; i++) { - jl_module_t *mod = (jl_module_t*)jl_array_ptr_ref(worklist, i); - assert(jl_is_module(mod)); - foreach_mtable_in_module(mod, precompile_enq_all_specializations_, m); - } - void *native_code = jl_precompile_(m); - JL_GC_POP(); - return native_code; -} - #ifdef __cplusplus } #endif diff --git a/src/precompile_utils.c b/src/precompile_utils.c new file mode 100644 index 00000000000000..f251d00f76cfd5 --- /dev/null +++ b/src/precompile_utils.c @@ -0,0 +1,306 @@ +// f{<:Union{...}}(...) is a common pattern +// and expanding the Union may give a leaf function +static void _compile_all_tvar_union(jl_value_t *methsig) +{ + int tvarslen = jl_subtype_env_size(methsig); + jl_value_t *sigbody = methsig; + jl_value_t **roots; + JL_GC_PUSHARGS(roots, 1 + 2 * tvarslen); + jl_value_t **env = roots + 1; + int *idx = (int*)alloca(sizeof(int) * tvarslen); + int i; + for (i = 0; i < tvarslen; i++) { + assert(jl_is_unionall(sigbody)); + idx[i] = 0; + env[2 * i] = (jl_value_t*)((jl_unionall_t*)sigbody)->var; + env[2 * i + 1] = jl_bottom_type; // initialize the list with Union{}, since T<:Union{} is always a valid option + sigbody = ((jl_unionall_t*)sigbody)->body; + } + + for (i = 0; i < tvarslen; /* incremented by inner loop */) { + jl_value_t **sig = &roots[0]; + JL_TRY { + // TODO: wrap in UnionAll for each tvar in env[2*i + 1] ? + // currently doesn't matter much, since jl_compile_hint doesn't work on abstract types + *sig = (jl_value_t*)jl_instantiate_type_with(sigbody, env, tvarslen); + } + JL_CATCH { + goto getnext; // sigh, we found an invalid type signature. should we warn the user? + } + if (!jl_has_concrete_subtype(*sig)) + goto getnext; // signature wouldn't be callable / is invalid -- skip it + if (jl_is_concrete_type(*sig)) { + if (jl_compile_hint((jl_tupletype_t *)*sig)) + goto getnext; // success + } + + getnext: + for (i = 0; i < tvarslen; i++) { + jl_tvar_t *tv = (jl_tvar_t*)env[2 * i]; + if (jl_is_uniontype(tv->ub)) { + size_t l = jl_count_union_components(tv->ub); + size_t j = idx[i]; + if (j == l) { + env[2 * i + 1] = jl_bottom_type; + idx[i] = 0; + } + else { + jl_value_t *ty = jl_nth_union_component(tv->ub, j); + if (!jl_is_concrete_type(ty)) + ty = (jl_value_t*)jl_new_typevar(tv->name, tv->lb, ty); + env[2 * i + 1] = ty; + idx[i] = j + 1; + break; + } + } + else { + env[2 * i + 1] = (jl_value_t*)tv; + } + } + } + JL_GC_POP(); +} + +// f(::Union{...}, ...) is a common pattern +// and expanding the Union may give a leaf function +static void _compile_all_union(jl_value_t *sig) +{ + jl_tupletype_t *sigbody = (jl_tupletype_t*)jl_unwrap_unionall(sig); + size_t count_unions = 0; + size_t i, l = jl_svec_len(sigbody->parameters); + jl_svec_t *p = NULL; + jl_value_t *methsig = NULL; + + for (i = 0; i < l; i++) { + jl_value_t *ty = jl_svecref(sigbody->parameters, i); + if (jl_is_uniontype(ty)) + ++count_unions; + else if (ty == jl_bottom_type) + return; // why does this method exist? + else if (jl_is_datatype(ty) && !jl_has_free_typevars(ty) && + ((!jl_is_kind(ty) && ((jl_datatype_t*)ty)->isconcretetype) || + ((jl_datatype_t*)ty)->name == jl_type_typename)) + return; // no amount of union splitting will make this a leaftype signature + } + + if (count_unions == 0 || count_unions >= 6) { + _compile_all_tvar_union(sig); + return; + } + + int *idx = (int*)alloca(sizeof(int) * count_unions); + for (i = 0; i < count_unions; i++) { + idx[i] = 0; + } + + JL_GC_PUSH2(&p, &methsig); + int idx_ctr = 0, incr = 0; + while (!incr) { + p = jl_alloc_svec_uninit(l); + for (i = 0, idx_ctr = 0, incr = 1; i < l; i++) { + jl_value_t *ty = jl_svecref(sigbody->parameters, i); + if (jl_is_uniontype(ty)) { + assert(idx_ctr < count_unions); + size_t l = jl_count_union_components(ty); + size_t j = idx[idx_ctr]; + jl_svecset(p, i, jl_nth_union_component(ty, j)); + ++j; + if (incr) { + if (j == l) { + idx[idx_ctr] = 0; + } + else { + idx[idx_ctr] = j; + incr = 0; + } + } + ++idx_ctr; + } + else { + jl_svecset(p, i, ty); + } + } + methsig = (jl_value_t*)jl_apply_tuple_type(p); + methsig = jl_rewrap_unionall(methsig, sig); + _compile_all_tvar_union(methsig); + } + + JL_GC_POP(); +} + +static int compile_all_collect__(jl_typemap_entry_t *ml, void *env) +{ + jl_array_t *allmeths = (jl_array_t*)env; + jl_method_t *m = ml->func.method; + if (m->source) { + // method has a non-generated definition; can be compiled generically + jl_array_ptr_1d_push(allmeths, (jl_value_t*)m); + } + return 1; +} + +static int compile_all_collect_(jl_methtable_t *mt, void *env) +{ + jl_typemap_visitor(jl_atomic_load_relaxed(&mt->defs), compile_all_collect__, env); + return 1; +} + +static void jl_compile_all_defs(jl_array_t *mis) +{ + jl_array_t *allmeths = jl_alloc_vec_any(0); + JL_GC_PUSH1(&allmeths); + + jl_foreach_reachable_mtable(compile_all_collect_, allmeths); + + size_t i, l = jl_array_len(allmeths); + for (i = 0; i < l; i++) { + jl_method_t *m = (jl_method_t*)jl_array_ptr_ref(allmeths, i); + if (jl_is_datatype(m->sig) && jl_isa_compileable_sig((jl_tupletype_t*)m->sig, jl_emptysvec, m)) { + // method has a single compilable specialization, e.g. its definition + // signature is concrete. in this case we can just hint it. + jl_compile_hint((jl_tupletype_t*)m->sig); + } + else { + // first try to create leaf signatures from the signature declaration and compile those + _compile_all_union(m->sig); + + // finally, compile a fully generic fallback that can work for all arguments + jl_method_instance_t *unspec = jl_get_unspecialized(m); + if (unspec) + jl_array_ptr_1d_push(mis, (jl_value_t*)unspec); + } + } + + JL_GC_POP(); +} + +static int precompile_enq_specialization_(jl_method_instance_t *mi, void *closure) +{ + assert(jl_is_method_instance(mi)); + jl_code_instance_t *codeinst = jl_atomic_load_relaxed(&mi->cache); + while (codeinst) { + int do_compile = 0; + if (jl_atomic_load_relaxed(&codeinst->invoke) != jl_fptr_const_return) { + jl_value_t *inferred = jl_atomic_load_relaxed(&codeinst->inferred); + if (inferred && + inferred != jl_nothing && + jl_ir_flag_inferred((jl_array_t*)inferred) && + (jl_ir_inlining_cost((jl_array_t*)inferred) == UINT16_MAX)) { + do_compile = 1; + } + else if (jl_atomic_load_relaxed(&codeinst->invoke) != NULL || jl_atomic_load_relaxed(&codeinst->precompile)) { + do_compile = 1; + } + } + if (do_compile) { + jl_array_ptr_1d_push((jl_array_t*)closure, (jl_value_t*)mi); + return 1; + } + codeinst = jl_atomic_load_relaxed(&codeinst->next); + } + return 1; +} + +static int precompile_enq_all_specializations__(jl_typemap_entry_t *def, void *closure) +{ + jl_method_t *m = def->func.method; + if ((m->name == jl_symbol("__init__") || m->ccallable) && jl_is_dispatch_tupletype(m->sig)) { + // ensure `__init__()` and @ccallables get strongly-hinted, specialized, and compiled + jl_method_instance_t *mi = jl_specializations_get_linfo(m, m->sig, jl_emptysvec); + jl_array_ptr_1d_push((jl_array_t*)closure, (jl_value_t*)mi); + } + else { + jl_svec_t *specializations = jl_atomic_load_relaxed(&def->func.method->specializations); + size_t i, l = jl_svec_len(specializations); + for (i = 0; i < l; i++) { + jl_value_t *mi = jl_svecref(specializations, i); + if (mi != jl_nothing) + precompile_enq_specialization_((jl_method_instance_t*)mi, closure); + } + } + if (m->ccallable) + jl_array_ptr_1d_push((jl_array_t*)closure, (jl_value_t*)m->ccallable); + return 1; +} + +static int precompile_enq_all_specializations_(jl_methtable_t *mt, void *env) +{ + return jl_typemap_visitor(jl_atomic_load_relaxed(&mt->defs), precompile_enq_all_specializations__, env); +} + +static void *jl_precompile_(jl_array_t *m, int external_linkage) +{ + jl_array_t *m2 = NULL; + jl_method_instance_t *mi = NULL; + JL_GC_PUSH2(&m2, &mi); + m2 = jl_alloc_vec_any(0); + for (size_t i = 0; i < jl_array_len(m); i++) { + jl_value_t *item = jl_array_ptr_ref(m, i); + if (jl_is_method_instance(item)) { + mi = (jl_method_instance_t*)item; + size_t min_world = 0; + size_t max_world = ~(size_t)0; + if (mi != jl_atomic_load_relaxed(&mi->def.method->unspecialized) && !jl_isa_compileable_sig((jl_tupletype_t*)mi->specTypes, mi->sparam_vals, mi->def.method)) + mi = jl_get_specialization1((jl_tupletype_t*)mi->specTypes, jl_atomic_load_acquire(&jl_world_counter), &min_world, &max_world, 0); + if (mi) + jl_array_ptr_1d_push(m2, (jl_value_t*)mi); + } + else { + assert(jl_is_simplevector(item)); + assert(jl_svec_len(item) == 2); + jl_array_ptr_1d_push(m2, item); + } + } + void *native_code = jl_create_native(m2, NULL, NULL, 0, 1, external_linkage); + JL_GC_POP(); + return native_code; +} + +static void *jl_precompile(int all) +{ + // array of MethodInstances and ccallable aliases to include in the output + jl_array_t *m = jl_alloc_vec_any(0); + JL_GC_PUSH1(&m); + if (all) + jl_compile_all_defs(m); + jl_foreach_reachable_mtable(precompile_enq_all_specializations_, m); + void *native_code = jl_precompile_(m, 0); + JL_GC_POP(); + return native_code; +} + +static void *jl_precompile_worklist(jl_array_t *worklist, jl_array_t *extext_methods, jl_array_t *new_specializations) +{ + if (!worklist) + return NULL; + // this "found" array will contain function + // type signatures that were inferred but haven't been compiled + jl_array_t *m = jl_alloc_vec_any(0); + JL_GC_PUSH1(&m); + size_t i, n = jl_array_len(worklist); + for (i = 0; i < n; i++) { + jl_module_t *mod = (jl_module_t*)jl_array_ptr_ref(worklist, i); + assert(jl_is_module(mod)); + foreach_mtable_in_module(mod, precompile_enq_all_specializations_, m); + } + n = jl_array_len(extext_methods); + for (i = 0; i < n; i++) { + jl_method_t *method = (jl_method_t*)jl_array_ptr_ref(extext_methods, i); + assert(jl_is_method(method)); + jl_svec_t *specializations = jl_atomic_load_relaxed(&method->specializations); + size_t j, l = jl_svec_len(specializations); + for (j = 0; j < l; j++) { + jl_value_t *mi = jl_svecref(specializations, j); + if (mi != jl_nothing) + precompile_enq_specialization_((jl_method_instance_t*)mi, m); + } + } + n = jl_array_len(new_specializations); + for (i = 0; i < n; i++) { + jl_code_instance_t *ci = (jl_code_instance_t*)jl_array_ptr_ref(new_specializations, i); + precompile_enq_specialization_(ci->def, m); + } + void *native_code = jl_precompile_(m, 1); + JL_GC_POP(); + return native_code; +} diff --git a/src/processor.cpp b/src/processor.cpp index df114b4d802575..13b40ec4f73636 100644 --- a/src/processor.cpp +++ b/src/processor.cpp @@ -621,9 +621,9 @@ static inline std::vector> &get_cmdline_targets(F &&feature_cb) // Load sysimg, use the `callback` for dispatch and perform all relocations // for the selected target. template -static inline jl_sysimg_fptrs_t parse_sysimg(void *hdl, F &&callback) +static inline jl_image_fptrs_t parse_sysimg(void *hdl, F &&callback) { - jl_sysimg_fptrs_t res = {nullptr, 0, nullptr, 0, nullptr, nullptr}; + jl_image_fptrs_t res = {nullptr, 0, nullptr, 0, nullptr, nullptr}; // .data base char *data_base; diff --git a/src/processor.h b/src/processor.h index ac00f8874141bd..d1b18cb72b0849 100644 --- a/src/processor.h +++ b/src/processor.h @@ -135,7 +135,7 @@ JL_DLLEXPORT int jl_test_cpu_feature(jl_cpu_feature_t feature); static const uint32_t jl_sysimg_tag_mask = 0x80000000u; static const uint32_t jl_sysimg_val_mask = ~((uint32_t)0x80000000u); -typedef struct _jl_sysimg_fptrs_t { +typedef struct _jl_image_fptrs_t { // base function pointer const char *base; // number of functions @@ -153,7 +153,7 @@ typedef struct _jl_sysimg_fptrs_t { const int32_t *clone_offsets; // sorted indices of the cloned functions (including the tag bit) const uint32_t *clone_idxs; -} jl_sysimg_fptrs_t; +} jl_image_fptrs_t; /** * Initialize the processor dispatch system with sysimg `hdl` (also initialize the sysimg itself). @@ -165,14 +165,15 @@ typedef struct _jl_sysimg_fptrs_t { * * Return the data about the function pointers selected. */ -jl_sysimg_fptrs_t jl_init_processor_sysimg(void *hdl); -jl_sysimg_fptrs_t jl_init_processor_pkgimg(void *hdl); +jl_image_fptrs_t jl_init_processor_sysimg(void *hdl); +jl_image_fptrs_t jl_init_processor_pkgimg(void *hdl); // Return the name of the host CPU as a julia string. JL_DLLEXPORT jl_value_t *jl_get_cpu_name(void); // Dump the name and feature set of the host CPU // For debugging only JL_DLLEXPORT void jl_dump_host_cpu(void); +JL_DLLEXPORT void jl_check_pkgimage_clones(char* data); JL_DLLEXPORT int32_t jl_set_zero_subnormals(int8_t isZero); JL_DLLEXPORT int32_t jl_get_zero_subnormals(void); diff --git a/src/processor_arm.cpp b/src/processor_arm.cpp index f7a112993e3e54..3e7b22caf00d40 100644 --- a/src/processor_arm.cpp +++ b/src/processor_arm.cpp @@ -1802,14 +1802,14 @@ JL_DLLEXPORT jl_value_t *jl_get_cpu_name(void) return jl_cstr_to_string(host_cpu_name().c_str()); } -jl_sysimg_fptrs_t jl_init_processor_sysimg(void *hdl) +jl_image_fptrs_t jl_init_processor_sysimg(void *hdl) { if (!jit_targets.empty()) jl_error("JIT targets already initialized"); return parse_sysimg(hdl, sysimg_init_cb); } -jl_sysimg_fptrs_t jl_init_processor_pkgimg(void *hdl) +jl_image_fptrs_t jl_init_processor_pkgimg(void *hdl) { if (jit_targets.empty()) jl_error("JIT targets not initialized"); @@ -1818,6 +1818,11 @@ jl_sysimg_fptrs_t jl_init_processor_pkgimg(void *hdl) return parse_sysimg(hdl, pkgimg_init_cb); } +JL_DLLEXPORT void jl_check_pkgimage_clones(char *data) +{ + pkgimg_init_cb(data); +} + std::pair> jl_get_llvm_target(bool imaging, uint32_t &flags) { ensure_jit_target(imaging); diff --git a/src/processor_fallback.cpp b/src/processor_fallback.cpp index 3160bd0ba67506..c1353e1bb43b09 100644 --- a/src/processor_fallback.cpp +++ b/src/processor_fallback.cpp @@ -112,14 +112,14 @@ get_llvm_target_str(const TargetData<1> &data) using namespace Fallback; -jl_sysimg_fptrs_t jl_init_processor_sysimg(void *hdl) +jl_image_fptrs_t jl_init_processor_sysimg(void *hdl) { if (!jit_targets.empty()) jl_error("JIT targets already initialized"); return parse_sysimg(hdl, sysimg_init_cb); } -jl_sysimg_fptrs_t jl_init_processor_pkgimg(void *hdl) +jl_image_fptrs_t jl_init_processor_pkgimg(void *hdl) { if (jit_targets.empty()) jl_error("JIT targets not initialized"); @@ -170,6 +170,11 @@ JL_DLLEXPORT void jl_dump_host_cpu(void) jl_safe_printf("Features: %s\n", jl_get_cpu_features_llvm().c_str()); } +JL_DLLEXPORT void jl_check_pkgimage_clones(char *data) +{ + pkgimg_init_cb(data); +} + extern "C" int jl_test_cpu_feature(jl_cpu_feature_t) { return 0; diff --git a/src/processor_x86.cpp b/src/processor_x86.cpp index b73838a55777e6..6f064ddd47d19e 100644 --- a/src/processor_x86.cpp +++ b/src/processor_x86.cpp @@ -1019,19 +1019,24 @@ JL_DLLEXPORT void jl_dump_host_cpu(void) cpus, ncpu_names); } +JL_DLLEXPORT void jl_check_pkgimage_clones(char *data) +{ + pkgimg_init_cb(data); +} + JL_DLLEXPORT jl_value_t *jl_get_cpu_name(void) { return jl_cstr_to_string(host_cpu_name().c_str()); } -jl_sysimg_fptrs_t jl_init_processor_sysimg(void *hdl) +jl_image_fptrs_t jl_init_processor_sysimg(void *hdl) { if (!jit_targets.empty()) jl_error("JIT targets already initialized"); return parse_sysimg(hdl, sysimg_init_cb); } -jl_sysimg_fptrs_t jl_init_processor_pkgimg(void *hdl) +jl_image_fptrs_t jl_init_processor_pkgimg(void *hdl) { if (jit_targets.empty()) jl_error("JIT targets not initialized"); diff --git a/src/staticdata.c b/src/staticdata.c index 786d0c966693b3..eea59a8cffceb2 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -90,6 +90,7 @@ External links: #include "julia_assert.h" #include "staticdata_utils.c" +#include "precompile_utils.c" #ifdef __cplusplus extern "C" { @@ -322,7 +323,7 @@ typedef struct { uint64_t base; uintptr_t *gvars_base; int32_t *gvars_offsets; - jl_sysimg_fptrs_t fptrs; + jl_image_fptrs_t fptrs; } jl_image_t; // array of definitions for the predefined function pointers @@ -365,6 +366,7 @@ typedef struct { jl_array_t *link_ids_relocs; jl_array_t *link_ids_gctags; jl_array_t *link_ids_gvars; + jl_array_t *link_ids_external_fnvars; jl_ptls_t ptls; htable_t callers_with_edges; jl_image_t *image; @@ -851,7 +853,6 @@ static void write_padding(ios_t *s, size_t nb) JL_NOTSAFEPOINT ios_write(s, zeros, nb); } - static void write_pointer(ios_t *s) JL_NOTSAFEPOINT { assert((ios_pos(s) & (sizeof(void*) - 1)) == 0 && "stream misaligned for writing a word-sized value"); @@ -1077,6 +1078,24 @@ static void record_gvars(jl_serializer_state *s, arraylist_t *globals) JL_NOTSAF } } +static void record_external_fns(jl_serializer_state *s, arraylist_t *external_fns) JL_NOTSAFEPOINT +{ + if (!s->incremental) { + assert(external_fns->len == 0); + (void) external_fns; + return; + } + + // We could call jl_queue_for_serialization here, but that should + // always be a no-op. +#ifndef JL_NDEBUG + for (size_t i = 0; i < external_fns->len; i++) { + jl_code_instance_t *ci = (jl_code_instance_t*)external_fns->items[i]; + assert(jl_object_in_image((jl_value_t*)ci)); + } +#endif +} + jl_value_t *jl_find_ptr = NULL; // The main function for serializing all the items queued in `serialization_order` // (They are also stored in `serialization_queue` which is order-preserving, unlike the hash table used @@ -1567,7 +1586,7 @@ static uintptr_t get_reloc_for_item(uintptr_t reloc_item, size_t reloc_offset) } // Compute target location at deserialization -static inline uintptr_t get_item_for_reloc(jl_serializer_state *s, uintptr_t base, size_t size, uintptr_t reloc_id, jl_array_t *link_ids, int *link_index) +static inline uintptr_t get_item_for_reloc(jl_serializer_state *s, uintptr_t base, size_t size, uintptr_t reloc_id, jl_array_t *link_ids, int *link_index) JL_NOTSAFEPOINT { enum RefTags tag = (enum RefTags)(reloc_id >> RELOC_TAG_OFFSET); size_t offset = (reloc_id & (((uintptr_t)1 << RELOC_TAG_OFFSET) - 1)); @@ -1829,20 +1848,20 @@ static jl_value_t *jl_delayed_reloc(jl_serializer_state *s, uintptr_t offset) JL static void jl_update_all_fptrs(jl_serializer_state *s, jl_image_t *image) { - jl_sysimg_fptrs_t fvars = image->fptrs; + jl_image_fptrs_t fvars = image->fptrs; // make these NULL now so we skip trying to restore GlobalVariable pointers later image->gvars_base = NULL; image->fptrs.base = NULL; if (fvars.base == NULL) return; - int sysimg_fvars_max = s->fptr_record->size / sizeof(void*); + int img_fvars_max = s->fptr_record->size / sizeof(void*); size_t i; uintptr_t base = (uintptr_t)&s->s->buf[0]; // These will become MethodInstance references, but they start out as a list of // offsets into `s` for CodeInstances jl_method_instance_t **linfos = (jl_method_instance_t**)&s->fptr_record->buf[0]; uint32_t clone_idx = 0; - for (i = 0; i < sysimg_fvars_max; i++) { + for (i = 0; i < img_fvars_max; i++) { reloc_t offset = *(reloc_t*)&linfos[i]; linfos[i] = NULL; if (offset != 0) { @@ -1877,12 +1896,13 @@ static void jl_update_all_fptrs(jl_serializer_state *s, jl_image_t *image) } } // Tell LLVM about the native code - jl_register_fptrs(image->base, &fvars, linfos, sysimg_fvars_max); + jl_register_fptrs(image->base, &fvars, linfos, img_fvars_max); } -static void write_gvars(jl_serializer_state *s, arraylist_t *globals) JL_NOTSAFEPOINT +static uint32_t write_gvars(jl_serializer_state *s, arraylist_t *globals, arraylist_t *external_fns) JL_NOTSAFEPOINT { - ios_ensureroom(s->gvar_record, globals->len * sizeof(reloc_t)); + size_t len = globals->len + external_fns->len; + ios_ensureroom(s->gvar_record, len * sizeof(reloc_t)); for (size_t i = 0; i < globals->len; i++) { void *g = globals->items[i]; if (jl_is_binding((uintptr_t)g)) { @@ -1903,10 +1923,17 @@ static void write_gvars(jl_serializer_state *s, arraylist_t *globals) JL_NOTSAFE write_reloc_t(s->gvar_record, reloc); record_uniquing(s, (jl_value_t*)g, ((i << 2) | 2)); // mark as gvar && !tag } + for (size_t i = 0; i < external_fns->len; i++) { + jl_code_instance_t *ci = (jl_code_instance_t*)external_fns->items[i]; + uintptr_t item = backref_id(s, (void*)ci, s->link_ids_external_fnvars); + uintptr_t reloc = get_reloc_for_item(item, 0); + write_reloc_t(s->gvar_record, reloc); + } + return globals->len + 1; } // Pointer relocation for native-code referenced global variables -static void jl_update_all_gvars(jl_serializer_state *s, jl_image_t *image) +static void jl_update_all_gvars(jl_serializer_state *s, jl_image_t *image, uint32_t external_fns_begin) { if (image->gvars_base == NULL) return; @@ -1915,17 +1942,24 @@ static void jl_update_all_gvars(jl_serializer_state *s, jl_image_t *image) uintptr_t base = (uintptr_t)&s->s->buf[0]; size_t size = s->s->size; reloc_t *gvars = (reloc_t*)&s->gvar_record->buf[0]; - int link_index = 0; + int gvar_link_index = 0; + int external_fns_link_index = 0; for (i = 0; i < l; i++) { uintptr_t offset = gvars[i]; - uintptr_t v = get_item_for_reloc(s, base, size, offset, s->link_ids_gvars, &link_index); + uintptr_t v = 0; + if (i < external_fns_begin) { + v = get_item_for_reloc(s, base, size, offset, s->link_ids_gvars, &gvar_link_index); + } else { + v = get_item_for_reloc(s, base, size, offset, s->link_ids_external_fnvars, &external_fns_link_index); + } uintptr_t *gv = sysimg_gvars(image->gvars_base, image->gvars_offsets, i); *gv = v; } - assert(!s->link_ids_gvars || link_index == jl_array_len(s->link_ids_gvars)); + assert(!s->link_ids_gvars || gvar_link_index == jl_array_len(s->link_ids_gvars)); + assert(!s->link_ids_external_fnvars || external_fns_link_index == jl_array_len(s->link_ids_external_fnvars)); } -static void jl_root_new_gvars(jl_serializer_state *s, jl_image_t *image) +static void jl_root_new_gvars(jl_serializer_state *s, jl_image_t *image, uint32_t external_fns_begin) { if (image->gvars_base == NULL) return; @@ -1934,8 +1968,14 @@ static void jl_root_new_gvars(jl_serializer_state *s, jl_image_t *image) for (i = 0; i < l; i++) { uintptr_t *gv = sysimg_gvars(image->gvars_base, image->gvars_offsets, i); uintptr_t v = *gv; - if (!jl_is_binding(v)) - v = (uintptr_t)jl_as_global_root((jl_value_t*)v); + if (i < external_fns_begin) { + if (!jl_is_binding(v)) + v = (uintptr_t)jl_as_global_root((jl_value_t*)v); + } else { + jl_code_instance_t *codeinst = (jl_code_instance_t*) v; + assert(codeinst && codeinst->isspecsig); + v = (uintptr_t)codeinst->specptr.fptr; + } *gv = v; } } @@ -2287,13 +2327,18 @@ static void jl_save_system_image_to_stream(ios_t *f, s.link_ids_relocs = jl_alloc_array_1d(jl_array_uint64_type, 0); s.link_ids_gctags = jl_alloc_array_1d(jl_array_uint64_type, 0); s.link_ids_gvars = jl_alloc_array_1d(jl_array_uint64_type, 0); + s.link_ids_external_fnvars = jl_alloc_array_1d(jl_array_uint64_type, 0); htable_new(&s.callers_with_edges, 0); jl_value_t **const*const tags = get_tags(); // worklist == NULL ? get_tags() : NULL; arraylist_t gvars; + arraylist_t external_fns; arraylist_new(&gvars, 0); - if (native_functions) + arraylist_new(&external_fns, 0); + if (native_functions) { jl_get_llvm_gvs(native_functions, &gvars); + jl_get_llvm_external_fns(native_functions, &external_fns); + } if (worklist == NULL) { // empty!(Core.ARGS) @@ -2359,6 +2404,7 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_serialize_reachable(&s); // step 1.2: now that we have marked all bindings (badly), ensure all gvars are part of the sysimage record_gvars(&s, &gvars); + record_external_fns(&s, &external_fns); jl_serialize_reachable(&s); // step 1.3: prune (garbage collect) some special weak references from // built-in type caches @@ -2372,10 +2418,11 @@ static void jl_save_system_image_to_stream(ios_t *f, } } + uint32_t external_fns_begin = 0; { // step 2: build all the sysimg sections write_padding(&sysimg, sizeof(uintptr_t)); jl_write_values(&s); - write_gvars(&s, &gvars); + external_fns_begin = write_gvars(&s, &gvars, &external_fns); jl_write_relocations(&s); } @@ -2475,6 +2522,9 @@ static void jl_save_system_image_to_stream(ios_t *f, ios_write(f, (char*)jl_array_data(s.link_ids_relocs), jl_array_len(s.link_ids_relocs)*sizeof(uint64_t)); write_uint32(f, jl_array_len(s.link_ids_gvars)); ios_write(f, (char*)jl_array_data(s.link_ids_gvars), jl_array_len(s.link_ids_gvars)*sizeof(uint64_t)); + write_uint32(f, jl_array_len(s.link_ids_external_fnvars)); + ios_write(f, (char*)jl_array_data(s.link_ids_external_fnvars), jl_array_len(s.link_ids_external_fnvars)*sizeof(uint64_t)); + write_uint32(f, external_fns_begin); jl_write_arraylist(s.s, &s.ccallable_list); } // Write the build_id key @@ -2492,6 +2542,7 @@ static void jl_save_system_image_to_stream(ios_t *f, arraylist_free(&s.relocs_list); arraylist_free(&s.gctags_list); arraylist_free(&gvars); + arraylist_free(&external_fns); htable_free(&field_replace); if (worklist) htable_free(&external_objects); @@ -2512,9 +2563,8 @@ static void jl_write_header_for_incremental(ios_t *f, jl_array_t *worklist, jl_a assert(jl_precompile_toplevel_module == NULL); jl_precompile_toplevel_module = (jl_module_t*)jl_array_ptr_ref(worklist, jl_array_len(worklist)-1); - write_header(f); - // last word of the header is the checksumpos - *checksumpos = ios_pos(f) - sizeof(uint64_t); + *checksumpos = write_header(f, 0); + write_uint8(f, jl_cache_flags()); // write description of contents (name, uuid, buildid) write_worklist_for_header(f, worklist); // Determine unique (module, abspath, mtime) dependencies for the files defining modules in the worklist @@ -2528,88 +2578,96 @@ static void jl_write_header_for_incremental(ios_t *f, jl_array_t *worklist, jl_a write_mod_list(f, *mod_array); } - -JL_DLLEXPORT ios_t *jl_create_system_image(void *_native_data, jl_array_t *worklist) +JL_DLLEXPORT void jl_create_system_image(void **_native_data, jl_array_t *worklist, bool_t emit_split, + ios_t **s, ios_t **z, jl_array_t **udeps, int64_t *srctextpos) { jl_gc_collect(JL_GC_FULL); jl_gc_collect(JL_GC_INCREMENTAL); // sweep finalizers JL_TIMING(SYSIMG_DUMP); + // iff emit_split + // write header and src_text to one file f/s + // write systemimg to a second file ff/z jl_task_t *ct = jl_current_task; ios_t *f = (ios_t*)malloc_s(sizeof(ios_t)); ios_mem(f, 0); - jl_array_t *mod_array = NULL, *udeps = NULL, *extext_methods = NULL, *new_specializations = NULL; + + ios_t *ff = NULL; + if (emit_split) { + ff = (ios_t*)malloc_s(sizeof(ios_t)); + ios_mem(ff, 0); + } else { + ff = f; + } + + jl_array_t *mod_array = NULL, *extext_methods = NULL, *new_specializations = NULL; jl_array_t *method_roots_list = NULL, *ext_targets = NULL, *edges = NULL; - JL_GC_PUSH7(&mod_array, &udeps, &extext_methods, &new_specializations, &method_roots_list, &ext_targets, &edges); - int64_t srctextpos = 0; int64_t checksumpos = 0; + int64_t checksumpos_ff = 0; int64_t datastartpos = 0; + JL_GC_PUSH6(&mod_array, &extext_methods, &new_specializations, &method_roots_list, &ext_targets, &edges); if (worklist) { - jl_write_header_for_incremental(f, worklist, &mod_array, &udeps, &srctextpos, &checksumpos); + jl_write_header_for_incremental(f, worklist, &mod_array, udeps, srctextpos, &checksumpos); + if (emit_split) { + checksumpos_ff = write_header(ff, 1); + write_uint8(ff, jl_cache_flags()); + write_mod_list(ff, mod_array); + } else { + checksumpos_ff = checksumpos; + } jl_gc_enable_finalizers(ct, 0); // make sure we don't run any Julia code concurrently after this point jl_prepare_serialization_data(mod_array, newly_inferred, jl_worklist_key(worklist), &extext_methods, &new_specializations, &method_roots_list, &ext_targets, &edges); - write_padding(f, LLT_ALIGN(ios_pos(f), JL_CACHE_BYTE_ALIGNMENT) - ios_pos(f)); - datastartpos = ios_pos(f); + + // Generate _native_data` + if (jl_options.outputo || jl_options.outputbc || jl_options.outputunoptbc || jl_options.outputasm) { + jl_precompile_toplevel_module = (jl_module_t*)jl_array_ptr_ref(worklist, jl_array_len(worklist)-1); + *_native_data = jl_precompile_worklist(worklist, extext_methods, new_specializations); + jl_precompile_toplevel_module = NULL; + } + + if (!emit_split) { + write_int32(f, 0); // No clone_targets + write_padding(f, LLT_ALIGN(ios_pos(f), JL_CACHE_BYTE_ALIGNMENT) - ios_pos(f)); + } else { + write_padding(ff, LLT_ALIGN(ios_pos(ff), JL_CACHE_BYTE_ALIGNMENT) - ios_pos(ff)); + } + datastartpos = ios_pos(ff); + } else { + *_native_data = jl_precompile(jl_options.compile_enabled == JL_OPTIONS_COMPILE_ALL); } - native_functions = _native_data; - jl_save_system_image_to_stream(f, worklist, extext_methods, new_specializations, method_roots_list, ext_targets, edges); + native_functions = *_native_data; + jl_save_system_image_to_stream(ff, worklist, extext_methods, new_specializations, method_roots_list, ext_targets, edges); native_functions = NULL; if (worklist) { jl_gc_enable_finalizers(ct, 1); // make sure we don't run any Julia code concurrently before this point + jl_precompile_toplevel_module = NULL; + } + + if (worklist) { // Go back and update the checksum in the header - int64_t dataendpos = ios_pos(f); - uint32_t checksum = jl_crc32c(0, &f->buf[datastartpos], dataendpos - datastartpos); - ios_seek(f, checksumpos); - write_uint64(f, checksum | ((uint64_t)0xfafbfcfd << 32)); - ios_seek(f, srctextpos); - write_uint64(f, dataendpos); - // Write the source-text for the dependent files - // Go back and update the source-text position to point to the current position - if (udeps) { - ios_seek_end(f); - // Each source-text file is written as - // int32: length of abspath - // char*: abspath - // uint64: length of src text - // char*: src text - // At the end we write int32(0) as a terminal sentinel. - size_t len = jl_array_len(udeps); - ios_t srctext; - for (size_t i = 0; i < len; i++) { - jl_value_t *deptuple = jl_array_ptr_ref(udeps, i); - jl_value_t *depmod = jl_fieldref(deptuple, 0); // module - // Dependencies declared with `include_dependency` are excluded - // because these may not be Julia code (and could be huge) - if (depmod != (jl_value_t*)jl_main_module) { - jl_value_t *dep = jl_fieldref(deptuple, 1); // file abspath - const char *depstr = jl_string_data(dep); - if (!depstr[0]) - continue; - ios_t *srctp = ios_file(&srctext, depstr, 1, 0, 0, 0); - if (!srctp) { - jl_printf(JL_STDERR, "WARNING: could not cache source text for \"%s\".\n", - jl_string_data(dep)); - continue; - } - size_t slen = jl_string_len(dep); - write_int32(f, slen); - ios_write(f, depstr, slen); - int64_t posfile = ios_pos(f); - write_uint64(f, 0); // placeholder for length of this file in bytes - uint64_t filelen = (uint64_t) ios_copyall(f, &srctext); - ios_close(&srctext); - ios_seek(f, posfile); - write_uint64(f, filelen); - ios_seek_end(f); - } - } + int64_t dataendpos = ios_pos(ff); + uint32_t checksum = jl_crc32c(0, &ff->buf[datastartpos], dataendpos - datastartpos); + ios_seek(ff, checksumpos_ff); + write_uint64(ff, checksum | ((uint64_t)0xfafbfcfd << 32)); + write_uint64(ff, datastartpos); + write_uint64(ff, dataendpos); + ios_seek(ff, dataendpos); + + // Write the checksum to the split header if necessary + if (emit_split) { + int64_t cur = ios_pos(f); + ios_seek(f, checksumpos); + write_uint64(f, checksum | ((uint64_t)0xfafbfcfd << 32)); + ios_seek(f, cur); + // Next we will write the clone_targets and afterwards the srctext } - write_int32(f, 0); // mark the end of the source text - jl_precompile_toplevel_module = NULL; } JL_GC_POP(); - return f; + *s = f; + if (emit_split) + *z = ff; + return; } JL_DLLEXPORT size_t ios_write_direct(ios_t *dest, ios_t *src); @@ -2672,7 +2730,7 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl s.ptls = jl_current_task->ptls; arraylist_new(&s.relocs_list, 0); arraylist_new(&s.gctags_list, 0); - s.link_ids_relocs = s.link_ids_gctags = s.link_ids_gvars = NULL; + s.link_ids_relocs = s.link_ids_gctags = s.link_ids_gvars = s.link_ids_external_fnvars = NULL; jl_value_t **const*const tags = get_tags(); htable_t new_dt_objs; htable_new(&new_dt_objs, 0); @@ -2764,6 +2822,12 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl s.link_ids_gvars = jl_alloc_array_1d(jl_array_uint64_type, nlinks_gvars); ios_read(f, (char*)jl_array_data(s.link_ids_gvars), nlinks_gvars * sizeof(uint64_t)); } + size_t nlinks_external_fnvars = read_uint32(f); + if (nlinks_external_fnvars > 0) { + s.link_ids_external_fnvars = jl_alloc_array_1d(jl_array_uint64_type, nlinks_external_fnvars); + ios_read(f, (char*)jl_array_data(s.link_ids_external_fnvars), nlinks_external_fnvars * sizeof(uint64_t)); + } + uint32_t external_fns_begin = read_uint32(f); jl_read_arraylist(s.s, ccallable_list ? ccallable_list : &s.ccallable_list); if (s.incremental) { assert(restored && init_order && extext_methods && new_specializations && method_roots_list && ext_targets && edges); @@ -2777,6 +2841,7 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl } s.s = NULL; + // step 3: apply relocations assert(!ios_eof(f)); jl_read_symbols(&s); @@ -2793,7 +2858,8 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl (void)sizeof_tags; jl_read_reloclist(&s, s.link_ids_relocs, 0); // general relocs // s.link_ids_gvars will be processed in `jl_update_all_gvars` - jl_update_all_gvars(&s, image); // gvars relocs + // s.link_ids_external_fns will be processed in `jl_update_all_gvars` + jl_update_all_gvars(&s, image, external_fns_begin); // gvars relocs if (s.incremental) { jl_read_arraylist(s.relocs, &s.uniquing_types); jl_read_arraylist(s.relocs, &s.uniquing_objs); @@ -3102,7 +3168,7 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl arraylist_free(&s.fixup_objs); if (s.incremental) - jl_root_new_gvars(&s, image); + jl_root_new_gvars(&s, image, external_fns_begin); ios_close(&relocs); ios_close(&const_data); ios_close(&gvar_record); @@ -3172,21 +3238,26 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl jl_gc_enable(en); } -static jl_value_t *jl_validate_cache_file(ios_t *f, jl_array_t *depmods, uint64_t *checksum, int64_t *dataendpos) +static jl_value_t *jl_validate_cache_file(ios_t *f, jl_array_t *depmods, uint64_t *checksum, int64_t *dataendpos, int64_t *datastartpos) { - if (ios_eof(f) || 0 == (*checksum = jl_read_verify_header(f)) || (*checksum >> 32 != 0xfafbfcfd)) { + uint8_t pkgimage = 0; + if (ios_eof(f) || 0 == (*checksum = jl_read_verify_header(f, &pkgimage, dataendpos, datastartpos)) || (*checksum >> 32 != 0xfafbfcfd)) { return jl_get_exceptionf(jl_errorexception_type, "Precompile file header verification checks failed."); } - { // skip past the mod list + uint8_t flags = read_uint8(f); + if (pkgimage && !jl_match_cache_flags(flags)) { + return jl_get_exceptionf(jl_errorexception_type, "Pkgimage flags mismatch"); + } + if (!pkgimage) { + // skip past the worklist size_t len; while ((len = read_int32(f))) ios_skip(f, len + 3 * sizeof(uint64_t)); - } - { // skip past the dependency list + // skip past the dependency list size_t deplen = read_uint64(f); ios_skip(f, deplen - sizeof(uint64_t)); - *dataendpos = read_uint64(f); + read_uint64(f); // where is this write coming from? } // verify that the system state is valid @@ -3198,10 +3269,14 @@ static jl_value_t *jl_restore_package_image_from_stream(ios_t *f, jl_image_t *im { uint64_t checksum = 0; int64_t dataendpos = 0; - jl_value_t *verify_fail = jl_validate_cache_file(f, depmods, &checksum, &dataendpos); + int64_t datastartpos = 0; + jl_value_t *verify_fail = jl_validate_cache_file(f, depmods, &checksum, &dataendpos, &datastartpos); + if (verify_fail) return verify_fail; + assert(datastartpos > 0 && datastartpos < dataendpos); + jl_value_t *restored = NULL; jl_array_t *init_order = NULL, *extext_methods = NULL, *new_specializations = NULL, *method_roots_list = NULL, *ext_targets = NULL, *edges = NULL; jl_svec_t *cachesizes_sv = NULL; @@ -3212,11 +3287,9 @@ static jl_value_t *jl_restore_package_image_from_stream(ios_t *f, jl_image_t *im { // make a permanent in-memory copy of f (excluding the header) ios_bufmode(f, bm_none); JL_SIGATOMIC_BEGIN(); - size_t len_begin = LLT_ALIGN(ios_pos(f), JL_CACHE_BYTE_ALIGNMENT); - assert(len_begin > 0 && len_begin < dataendpos); - size_t len = dataendpos - len_begin; + size_t len = dataendpos - datastartpos; char *sysimg = (char*)jl_gc_perm_alloc(len, 0, 64, 0); - ios_seek(f, len_begin); + ios_seek(f, datastartpos); if (ios_readall(f, sysimg, len) != len || jl_crc32c(0, sysimg, len) != (uint32_t)checksum) { restored = jl_get_exceptionf(jl_errorexception_type, "Error reading system image file."); JL_SIGATOMIC_END(); @@ -3333,7 +3406,7 @@ JL_DLLEXPORT void jl_restore_system_image_data(const char *buf, size_t len) JL_SIGATOMIC_END(); } -JL_DLLEXPORT jl_value_t *jl_restore_package_image_from_file(const char *fname, jl_array_t *depmods) +JL_DLLEXPORT jl_value_t *jl_restore_package_image_from_file(const char *fname, jl_array_t *depmods, int complete) { void *pkgimg_handle = jl_dlopen(fname, JL_RTLD_LAZY); if (!pkgimg_handle) { @@ -3357,9 +3430,9 @@ JL_DLLEXPORT jl_value_t *jl_restore_package_image_from_file(const char *fname, j if (!jl_dlsym(pkgimg_handle, "jl_sysimg_gvars_base", (void **)&pkgimage.gvars_base, 0)) { pkgimage.gvars_base = NULL; } + jl_dlsym(pkgimg_handle, "jl_sysimg_gvars_offsets", (void **)&pkgimage.gvars_offsets, 1); pkgimage.gvars_offsets += 1; - jl_value_t* mod = jl_restore_incremental_from_buf(pkgimg_data, &pkgimage, *plen, depmods, 0); void *pgcstack_func_slot; jl_dlsym(pkgimg_handle, "jl_pgcstack_func_slot", &pgcstack_func_slot, 0); @@ -3373,6 +3446,20 @@ JL_DLLEXPORT jl_value_t *jl_restore_package_image_from_file(const char *fname, j *tls_offset_idx = (uintptr_t)(jl_tls_offset == -1 ? 0 : jl_tls_offset); } + #ifdef _OS_WINDOWS_ + pkgimage.base = (intptr_t)pkgimg_handle; + #else + Dl_info dlinfo; + if (dladdr((void*)pkgimage.gvars_base, &dlinfo) != 0) { + pkgimage.base = (intptr_t)dlinfo.dli_fbase; + } + else { + pkgimage.base = 0; + } + #endif + + jl_value_t* mod = jl_restore_incremental_from_buf(pkgimg_data, &pkgimage, *plen, depmods, complete); + return mod; } diff --git a/src/staticdata_utils.c b/src/staticdata_utils.c index dea511b1e0c98e..220a354847dfec 100644 --- a/src/staticdata_utils.c +++ b/src/staticdata_utils.c @@ -611,11 +611,46 @@ static void write_mod_list(ios_t *s, jl_array_t *a) write_int32(s, 0); } +JL_DLLEXPORT uint8_t jl_cache_flags(void) +{ + // ??OOCDDP + uint8_t flags = 0; + flags |= (jl_options.use_pkgimages & 1); + flags |= (jl_options.debug_level & 3) << 1; + flags |= (jl_options.check_bounds & 1) << 2; + flags |= (jl_options.opt_level & 3) << 4; + // NOTES: + // In contrast to check-bounds, inline has no "observable effect" + return flags; +} + +JL_DLLEXPORT uint8_t jl_match_cache_flags(uint8_t flags) +{ + // 1. Check which flags are relevant + uint8_t current_flags = jl_cache_flags(); + uint8_t supports_pkgimage = (current_flags & 1); + uint8_t is_pkgimage = (flags & 1); + + // For .ji packages ignore other flags + if (!supports_pkgimage && !is_pkgimage) { + return 1; + } + + // 2. Check all flags that must be exact + uint8_t mask = (1 << 4)-1; + if ((flags & mask) != (current_flags & mask)) + return 0; + // 3. allow for higher optimization flags in cache + flags >>= 4; + current_flags >>= 4; + return flags >= current_flags; +} + // "magic" string and version header of .ji file static const int JI_FORMAT_VERSION = 12; static const char JI_MAGIC[] = "\373jli\r\n\032\n"; // based on PNG signature static const uint16_t BOM = 0xFEFF; // byte-order marker -static void write_header(ios_t *s) +static int64_t write_header(ios_t *s, uint8_t pkgimage) { ios_write(s, JI_MAGIC, strlen(JI_MAGIC)); write_uint16(s, JI_FORMAT_VERSION); @@ -627,7 +662,12 @@ static void write_header(ios_t *s) const char *branch = jl_git_branch(), *commit = jl_git_commit(); ios_write(s, branch, strlen(branch)+1); ios_write(s, commit, strlen(commit)+1); + write_uint8(s, pkgimage); + int64_t checksumpos = ios_pos(s); write_uint64(s, 0); // eventually will hold checksum for the content portion of this (build_id.hi) + write_uint64(s, 0); // eventually will hold dataendpos + write_uint64(s, 0); // eventually will hold datastartpos + return checksumpos; } // serialize information about the result of deserializing this file @@ -1206,9 +1246,10 @@ static int readstr_verify(ios_t *s, const char *str, int include_null) return 1; } -JL_DLLEXPORT uint64_t jl_read_verify_header(ios_t *s) +JL_DLLEXPORT uint64_t jl_read_verify_header(ios_t *s, uint8_t *pkgimage, int64_t *dataendpos, int64_t *datastartpos) { uint16_t bom; + uint64_t checksum = 0; if (readstr_verify(s, JI_MAGIC, 0) && read_uint16(s) == JI_FORMAT_VERSION && ios_read(s, (char *) &bom, 2) == 2 && bom == BOM && @@ -1218,6 +1259,11 @@ JL_DLLEXPORT uint64_t jl_read_verify_header(ios_t *s) readstr_verify(s, JULIA_VERSION_STRING, 1) && readstr_verify(s, jl_git_branch(), 1) && readstr_verify(s, jl_git_commit(), 1)) - return read_uint64(s); - return 0; + { + *pkgimage = read_uint8(s); + checksum = read_uint64(s); + *datastartpos = (int64_t)read_uint64(s); + *dataendpos = (int64_t)read_uint64(s); + } + return checksum; } diff --git a/stdlib/CompilerSupportLibraries_jll/Project.toml b/stdlib/CompilerSupportLibraries_jll/Project.toml index b072831326627d..fc5883cc79802d 100644 --- a/stdlib/CompilerSupportLibraries_jll/Project.toml +++ b/stdlib/CompilerSupportLibraries_jll/Project.toml @@ -4,7 +4,7 @@ uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" # NOTE: When updating this, also make sure to update the value # `CSL_NEXT_GLIBCXX_VERSION` in `deps/csl.mk`, to properly disable # automatic usage of BB-built CSLs on extremely up-to-date systems! -version = "1.0.1+0" +version = "1.0.2+0" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/test/compiler/contextual.jl b/test/compiler/contextual.jl index 75cbcf2de37fc0..79285f62b09472 100644 --- a/test/compiler/contextual.jl +++ b/test/compiler/contextual.jl @@ -209,7 +209,11 @@ try @test length(Bar.mt) == 1 finally rm(load_path, recursive=true, force=true) - rm(depot_path, recursive=true, force=true) + try + rm(depot_path, force=true, recursive=true) + catch err + @show err + end filter!((≠)(load_path), LOAD_PATH) filter!((≠)(depot_path), DEPOT_PATH) end diff --git a/test/loading.jl b/test/loading.jl index d52a7246abe7cc..497dfaed4af187 100644 --- a/test/loading.jl +++ b/test/loading.jl @@ -746,7 +746,11 @@ for env in keys(envs) rm(env, force=true, recursive=true) end for depot in depots - rm(depot, force=true, recursive=true) + try + rm(depot, force=true, recursive=true) + catch err + @show err + end end append!(empty!(LOAD_PATH), saved_load_path) diff --git a/test/precompile.jl b/test/precompile.jl index 688286a113b550..8aa09efe3f417c 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -28,8 +28,18 @@ function precompile_test_harness(@nospecialize(f), separate::Bool) pushfirst!(DEPOT_PATH, load_cache_path) f(load_path) finally - rm(load_path, recursive=true, force=true) - separate && rm(load_cache_path, recursive=true, force=true) + try + rm(load_path, force=true, recursive=true) + catch err + @show err + end + if separate + try + rm(load_cache_path, force=true, recursive=true) + catch err + @show err + end + end filter!((≠)(load_path), LOAD_PATH) separate && filter!((≠)(load_cache_path), DEPOT_PATH) end @@ -318,11 +328,16 @@ precompile_test_harness(false) do dir cachedir = joinpath(dir, "compiled", "v$(VERSION.major).$(VERSION.minor)") cachedir2 = joinpath(dir2, "compiled", "v$(VERSION.major).$(VERSION.minor)") cachefile = joinpath(cachedir, "$Foo_module.ji") + if Base.JLOptions().use_pkgimages == 1 + ocachefile = Base.ocachefile_from_cachefile(cachefile) + else + ocachefile = nothing + end # use _require_from_serialized to ensure that the test fails if # the module doesn't reload from the image: @test_warn "@ccallable was already defined for this method name" begin @test_logs (:warn, "Replacing module `$Foo_module`") begin - m = Base._require_from_serialized(Base.PkgId(Foo), cachefile) + m = Base._require_from_serialized(Base.PkgId(Foo), cachefile, ocachefile) @test isa(m, Module) end end @@ -343,7 +358,7 @@ precompile_test_harness(false) do dir @test string(Base.Docs.doc(Foo.Bar.bar)) == "bar function\n" @test string(Base.Docs.doc(Foo.Bar)) == "Bar module\n" - modules, (deps, requires), required_modules = Base.parse_cache_header(cachefile) + modules, (deps, requires), required_modules, _... = Base.parse_cache_header(cachefile) discard_module = mod_fl_mt -> (mod_fl_mt.filename, mod_fl_mt.mtime) @test modules == [ Base.PkgId(Foo) => Base.module_build_id(Foo) % UInt64 ] @test map(x -> x.filename, deps) == [ Foo_file, joinpath(dir, "foo.jl"), joinpath(dir, "bar.jl") ] @@ -378,7 +393,7 @@ precompile_test_harness(false) do dir ), ) @test discard_module.(deps) == deps1 - modules, (deps, requires), required_modules = Base.parse_cache_header(cachefile; srcfiles_only=true) + modules, (deps, requires), required_modules, _... = Base.parse_cache_header(cachefile; srcfiles_only=true) @test map(x -> x.filename, deps) == [Foo_file] @test current_task()(0x01, 0x4000, 0x30031234) == 2 @@ -441,7 +456,7 @@ precompile_test_harness(false) do dir """) Nest = Base.require(Main, Nest_module) cachefile = joinpath(cachedir, "$Nest_module.ji") - modules, (deps, requires), required_modules = Base.parse_cache_header(cachefile) + modules, (deps, requires), required_modules, _... = Base.parse_cache_header(cachefile) @test last(deps).modpath == ["NestInner"] UsesB_module = :UsesB4b3a94a1a081a8cb @@ -463,7 +478,7 @@ precompile_test_harness(false) do dir """) UsesB = Base.require(Main, UsesB_module) cachefile = joinpath(cachedir, "$UsesB_module.ji") - modules, (deps, requires), required_modules = Base.parse_cache_header(cachefile) + modules, (deps, requires), required_modules, _... = Base.parse_cache_header(cachefile) id1, id2 = only(requires) @test Base.pkgorigins[id1].cachepath == cachefile @test Base.pkgorigins[id2].cachepath == joinpath(cachedir, "$B_module.ji") @@ -497,18 +512,19 @@ precompile_test_harness(false) do dir end """) - cachefile = Base.compilecache(Base.PkgId("FooBar")) + cachefile, _ = Base.compilecache(Base.PkgId("FooBar")) empty_prefs_hash = Base.get_preferences_hash(nothing, String[]) @test cachefile == Base.compilecache_path(Base.PkgId("FooBar"), empty_prefs_hash) @test isfile(joinpath(cachedir, "FooBar.ji")) - @test Base.stale_cachefile(FooBar_file, joinpath(cachedir, "FooBar.ji")) isa Vector + Tsc = Bool(Base.JLOptions().use_pkgimages) ? Tuple{<:Vector, String} : Tuple{<:Vector, Nothing} + @test Base.stale_cachefile(FooBar_file, joinpath(cachedir, "FooBar.ji")) isa Tsc @test !isdefined(Main, :FooBar) @test !isdefined(Main, :FooBar1) relFooBar_file = joinpath(dir, "subfolder", "..", "FooBar.jl") - @test Base.stale_cachefile(relFooBar_file, joinpath(cachedir, "FooBar.ji")) isa (Sys.iswindows() ? Vector : Bool) # `..` is not a symlink on Windows + @test Base.stale_cachefile(relFooBar_file, joinpath(cachedir, "FooBar.ji")) isa (Sys.iswindows() ? Tuple{<:Vector, String} : Bool) # `..` is not a symlink on Windows mkdir(joinpath(dir, "subfolder")) - @test Base.stale_cachefile(relFooBar_file, joinpath(cachedir, "FooBar.ji")) isa Vector + @test Base.stale_cachefile(relFooBar_file, joinpath(cachedir, "FooBar.ji")) isa Tsc @eval using FooBar fb_uuid = Base.module_build_id(FooBar) @@ -520,7 +536,7 @@ precompile_test_harness(false) do dir @test !isfile(joinpath(cachedir, "FooBar1.ji")) @test isfile(joinpath(cachedir2, "FooBar1.ji")) @test Base.stale_cachefile(FooBar_file, joinpath(cachedir, "FooBar.ji")) === true - @test Base.stale_cachefile(FooBar1_file, joinpath(cachedir2, "FooBar1.ji")) isa Vector + @test Base.stale_cachefile(FooBar1_file, joinpath(cachedir2, "FooBar1.ji")) isa Tsc @test fb_uuid == Base.module_build_id(FooBar) fb_uuid1 = Base.module_build_id(FooBar1) @test fb_uuid != fb_uuid1 @@ -1260,7 +1276,11 @@ end end finally cd(save_cwd) - rm(temp_path, recursive=true) + try + rm(temp_path, recursive=true) + catch err + @show err + end pop!(test_workers) # remove myid rmprocs(test_workers) end @@ -1400,13 +1420,13 @@ precompile_test_harness("Issue #25971") do load_path sourcefile = joinpath(load_path, "Foo25971.jl") write(sourcefile, "module Foo25971 end") chmod(sourcefile, 0o666) - cachefile = Base.compilecache(Base.PkgId("Foo25971")) + cachefile, _ = Base.compilecache(Base.PkgId("Foo25971")) @test filemode(sourcefile) == filemode(cachefile) chmod(sourcefile, 0o600) - cachefile = Base.compilecache(Base.PkgId("Foo25971")) + cachefile, _ = Base.compilecache(Base.PkgId("Foo25971")) @test filemode(sourcefile) == filemode(cachefile) chmod(sourcefile, 0o444) - cachefile = Base.compilecache(Base.PkgId("Foo25971")) + cachefile, _ = Base.compilecache(Base.PkgId("Foo25971")) # Check writable @test touch(cachefile) == cachefile end @@ -1597,6 +1617,80 @@ precompile_test_harness("Module tparams") do load_path @test ModuleTparams.the_struct === Base.invokelatest(ModuleTparams.ParamStruct{ModuleTparams.TheTParam}) end +precompile_test_harness("PkgCacheInspector") do load_path + # Test functionality needed by PkgCacheInspector.jl + write(joinpath(load_path, "PCI.jl"), + """ + module PCI + Base.repl_cmd() = 55 # external method + f() = Base.repl_cmd(7, "hello") # external specialization (should never exist otherwise) + try + f() + catch + end + end + """) + cachefile, ocachefile = Base.compilecache(Base.PkgId("PCI")) + + # Get the depmods + local depmods + @lock Base.require_lock begin + local depmodnames + io = open(cachefile, "r") + try + # isvalid_cache_header returns checksum id or zero + Base.isvalid_cache_header(io) == 0 && throw(ArgumentError("Invalid header in cache file $cachefile.")) + depmodnames = Base.parse_cache_header(io)[3] + Base.isvalid_file_crc(io) || throw(ArgumentError("Invalid checksum in cache file $cachefile.")) + finally + close(io) + end + ndeps = length(depmodnames) + depmods = Vector{Any}(undef, ndeps) + for i in 1:ndeps + modkey, build_id = depmodnames[i] + dep = Base._tryrequire_from_serialized(modkey, build_id) + if !isa(dep, Module) + return dep + end + depmods[i] = dep + end + end + + if ocachefile !== nothing + sv = ccall(:jl_restore_package_image_from_file, Any, (Cstring, Any, Cint), ocachefile, depmods, true) + else + sv = ccall(:jl_restore_incremental, Any, (Cstring, Any, Cint), cachefile, depmods, true) + end + + modules, init_order, external_methods, new_specializations, new_method_roots, external_targets, edges = sv + m = only(external_methods) + @test m.name == :repl_cmd && m.nargs < 2 + @test any(new_specializations) do ci + mi = ci.def + mi.specTypes == Tuple{typeof(Base.repl_cmd), Int, String} + end +end + +precompile_test_harness("DynamicExpressions") do load_path + # https://github.com/JuliaLang/julia/pull/47184#issuecomment-1364716312 + write(joinpath(load_path, "Float16MWE.jl"), + """ + module Float16MWE + struct Node{T} + val::T + end + doconvert(::Type{<:Node}, val) = convert(Float16, val) + precompile(Tuple{typeof(doconvert), Type{Node{Float16}}, Float64}) + end # module Float16MWE + """) + Base.compilecache(Base.PkgId("Float16MWE")) + (@eval (using Float16MWE)) + Base.invokelatest() do + @test Float16MWE.doconvert(Float16MWE.Node{Float16}, -1.2) === Float16(-1.2) + end +end + empty!(Base.DEPOT_PATH) append!(Base.DEPOT_PATH, original_depot_path) empty!(Base.LOAD_PATH) From 1fb6ec8fce46b7f36771fb31ef5fc5d44b9af090 Mon Sep 17 00:00:00 2001 From: Petr Vana Date: Tue, 27 Dec 2022 16:51:56 +0100 Subject: [PATCH 177/387] Fix printing --- contrib/generate_precompile.jl | 44 ++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index e69e96e1753c00..ef971a17381b07 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -250,37 +250,33 @@ function generate_precompile_statements() # Extract the precompile statements from the precompile file statements_step1 = Channel{String}(Inf) statements_step2 = Channel{String}(Inf) - # Make statements unique - statements = Set{String}() - # Variables for statistics - n_step0 = n_step1 = n_step2 = 0 - n_succeeded = 0 - step1 = step2 = nothing - repl_state_global = nothing # From hardcoded statements for statement in split(hardcoded_precompile_statements::String, '\n') push!(statements_step1, statement) - n_step0 += 1 end # Printing the current state - function print_state(;repl_state = nothing) - if !isnothing(repl_state) - repl_state_global = repl_state + print_lk = ReentrantLock() + status = Dict{String, String}( + "step1" => "W", + "step2" => "W", + "repl" => "0/0", + "execute" => "0/0", + ) + function print_state(args::Pair{String,String}...) + lock(print_lk) do + isempty(args) || push!(status, args...) + t1, t2, t3, t4 = (get(status, x, "") for x in ["step1", "repl", "step2", "execute"]) + print("\rCollect (normal($t1), REPL $t2 ($t3)) => Execute $t4") end - step0_status = "F,$n_step0" - step1_status = (isnothing(step1) ? "W" : isopen(statements_step1) ? "R" : "F") * ",$n_step1" - step2_status = (isnothing(step2) ? "W" : isopen(statements_step2) ? "R" : "F") * ",$n_step2" - repl_status = isnothing(repl_state_global) ? "" : repl_state_global - ex_status = "$n_succeeded/$(length(statements))" - print("\rCollect(manual($step0_status), normal($step1_status), REPL $repl_status($step2_status)) => Execute $ex_status") end println("Precompile statements (Waiting, Running, Finished)") print_state() # Collect statements from running the script step1 = @async mktempdir() do prec_path + print_state("step1" => "R") # Also precompile a package here pkgname = "__PackagePrecompilationStatementModule" mkpath(joinpath(prec_path, pkgname, "src")) @@ -299,6 +295,7 @@ function generate_precompile_statements() $precompile_script """ run(`$(julia_exepath()) -O0 --sysimage $sysimg --trace-compile=$tmp_proc --startup-file=no -Cnative -e $s`) + n_step1 = 0 for f in (tmp_prec, tmp_proc) for statement in split(read(f, String), '\n') push!(statements_step1, statement) @@ -306,12 +303,13 @@ function generate_precompile_statements() end end close(statements_step1) - print_state() + print_state("step1" => "F,$n_step1") end errormonitor(step1) !PARALLEL_PRECOMPILATION && wait(step1) step2 = @async mktemp() do precompile_file, precompile_file_h + print_state("step2" => "R") # Collect statements from running a REPL process and replaying our REPL script pts, ptm = open_fake_pty() blackhole = Sys.isunix() ? "/dev/null" : "nul" @@ -360,7 +358,7 @@ function generate_precompile_statements() for l in precompile_lines sleep(0.1) curr += 1 - print_state(repl_state = "$curr/$(length(precompile_lines))") + print_state("repl" => "$curr/$(length(precompile_lines))") # consume any other output bytesavailable(output_copy) > 0 && readavailable(output_copy) # push our input @@ -386,12 +384,13 @@ function generate_precompile_statements() close(ptm) write(debug_output, "\n#### FINISHED ####\n") + n_step2 = 0 for statement in split(read(precompile_file, String), '\n') push!(statements_step2, statement) n_step2 += 1 end close(statements_step2) - print_state() + print_state("step2" => "F,$n_step2") end errormonitor(step2) !PARALLEL_PRECOMPILATION && wait(step2) @@ -404,6 +403,9 @@ function generate_precompile_statements() end end + n_succeeded = 0 + # Make statements unique + statements = Set{String}() # Execute the precompile statements for sts in [statements_step1, statements_step2], statement in sts # Main should be completely clean @@ -438,7 +440,7 @@ function generate_precompile_statements() ps = Core.eval(PrecompileStagingArea, ps) precompile(ps...) n_succeeded += 1 - print_state() + print_state("execute" => "$n_succeeded/$(length(statements))") catch ex # See #28808 @warn "Failed to precompile expression" form=statement exception=ex _module=nothing _file=nothing _line=0 From 1794d64eaf9b19e1037dc646aa30fbc3fa873bbd Mon Sep 17 00:00:00 2001 From: Petr Vana Date: Tue, 27 Dec 2022 19:43:09 +0100 Subject: [PATCH 178/387] Make printing thread-safe --- contrib/generate_precompile.jl | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index ef971a17381b07..7628a517479a1d 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -242,21 +242,9 @@ const HELP_PROMPT = "help?> " # You can disable parallel precompiles generation by setting `false` const PARALLEL_PRECOMPILATION = true -function generate_precompile_statements() - start_time = time_ns() - debug_output = devnull # or stdout - sysimg = Base.unsafe_string(Base.JLOptions().image_file) - - # Extract the precompile statements from the precompile file - statements_step1 = Channel{String}(Inf) - statements_step2 = Channel{String}(Inf) - - # From hardcoded statements - for statement in split(hardcoded_precompile_statements::String, '\n') - push!(statements_step1, statement) - end - - # Printing the current state +# Printing the current state +let + global print_state print_lk = ReentrantLock() status = Dict{String, String}( "step1" => "W", @@ -271,6 +259,22 @@ function generate_precompile_statements() print("\rCollect (normal($t1), REPL $t2 ($t3)) => Execute $t4") end end +end + +function generate_precompile_statements() + start_time = time_ns() + debug_output = devnull # or stdout + sysimg = Base.unsafe_string(Base.JLOptions().image_file) + + # Extract the precompile statements from the precompile file + statements_step1 = Channel{String}(Inf) + statements_step2 = Channel{String}(Inf) + + # From hardcoded statements + for statement in split(hardcoded_precompile_statements::String, '\n') + push!(statements_step1, statement) + end + println("Precompile statements (Waiting, Running, Finished)") print_state() From a8151920f26905d875f0fe9b56cafebd42a584f5 Mon Sep 17 00:00:00 2001 From: Petr Vana Date: Tue, 27 Dec 2022 20:07:41 +0100 Subject: [PATCH 179/387] Wait for both steps --- contrib/generate_precompile.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index 7628a517479a1d..cddb17d92ba35d 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -457,6 +457,9 @@ function generate_precompile_statements() n_succeeded > 1200 || @warn "Only $n_succeeded precompile statements" end + PARALLEL_PRECOMPILATION && wait(step1) + PARALLEL_PRECOMPILATION && wait(step2) + tot_time = time_ns() - start_time println("Precompilation complete. Summary:") print("Total ─────── "); Base.time_print(tot_time); println() From 4c2a431083ca77dc5b2c5a554b1936ec248ca63e Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Tue, 27 Dec 2022 17:00:22 -0300 Subject: [PATCH 180/387] Initialize the rng of external threads (#47956) * Initialize the rng of external threads with uv_random --- src/threading.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/threading.c b/src/threading.c index c45143f0d01cac..150fbacbc421b4 100644 --- a/src/threading.c +++ b/src/threading.c @@ -413,7 +413,7 @@ JL_DLLEXPORT jl_gcframe_t **jl_adopt_thread(void) // warning: this changes `jl_current_task`, so be careful not to call that from this function jl_task_t *ct = jl_init_root_task(ptls, stack_lo, stack_hi); JL_GC_PROMISE_ROOTED(ct); - + uv_random(NULL, NULL, &ct->rngState, sizeof(ct->rngState), 0, NULL); return &ct->gcstack; } From a40e24e1cd8546728169eac89a7aedb8c5618240 Mon Sep 17 00:00:00 2001 From: Adrian Hill Date: Tue, 27 Dec 2022 22:01:05 +0100 Subject: [PATCH 181/387] Fix zero-length Markdown headers (#47725) * Fix zero-length Markdown headers * Add test on empty header --- stdlib/Markdown/src/render/terminal/render.jl | 3 ++- stdlib/Markdown/test/runtests.jl | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/stdlib/Markdown/src/render/terminal/render.jl b/stdlib/Markdown/src/render/terminal/render.jl index f3764bf1e84431..a7421b13660a09 100644 --- a/stdlib/Markdown/src/render/terminal/render.jl +++ b/stdlib/Markdown/src/render/terminal/render.jl @@ -88,7 +88,8 @@ function _term_header(io::IO, md, char, columns) if line_no > 1 line_width = max(line_width, div(columns, 3)) end - char != ' ' && print(io, '\n', ' '^(margin), char^(line_width-margin)) + header_width = max(0, line_width-margin) + char != ' ' && header_width > 0 && print(io, '\n', ' '^(margin), char^header_width) end end diff --git a/stdlib/Markdown/test/runtests.jl b/stdlib/Markdown/test/runtests.jl index b85d7604378ead..52bcf07ad89424 100644 --- a/stdlib/Markdown/test/runtests.jl +++ b/stdlib/Markdown/test/runtests.jl @@ -377,6 +377,7 @@ table = md""" let out = @test sprint(show, "text/plain", book) == " Title\n ≡≡≡≡≡\n\n Some discussion\n\n │ A quote\n\n Section important\n =================\n\n Some bolded\n\n • list1\n\n • list2" + @test sprint(show, "text/plain", md"#") == " " # edge case of empty header @test sprint(show, "text/markdown", book) == """ # Title From 79bee809b75182faae2c8f37146fccd4c7ffd6cc Mon Sep 17 00:00:00 2001 From: Petr Vana Date: Tue, 27 Dec 2022 23:39:17 +0100 Subject: [PATCH 182/387] Disable errormonitor temporarily --- contrib/generate_precompile.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index cddb17d92ba35d..62c23bfd834712 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -309,7 +309,7 @@ function generate_precompile_statements() close(statements_step1) print_state("step1" => "F,$n_step1") end - errormonitor(step1) + #errormonitor(step1) !PARALLEL_PRECOMPILATION && wait(step1) step2 = @async mktemp() do precompile_file, precompile_file_h @@ -396,7 +396,7 @@ function generate_precompile_statements() close(statements_step2) print_state("step2" => "F,$n_step2") end - errormonitor(step2) + #errormonitor(step2) !PARALLEL_PRECOMPILATION && wait(step2) # Create a staging area where all the loaded packages are available From 9448c8965298a5b31ce3fbc782f60f3cf1482025 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Wed, 28 Dec 2022 09:22:40 +0900 Subject: [PATCH 183/387] minor NFC improvements on abstractlattice.jl (#48008) --- base/compiler/abstractlattice.jl | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/base/compiler/abstractlattice.jl b/base/compiler/abstractlattice.jl index 0302b3f83e373d..7e4fd3e59f1534 100644 --- a/base/compiler/abstractlattice.jl +++ b/base/compiler/abstractlattice.jl @@ -44,6 +44,11 @@ end widenlattice(𝕃::ConditionalsLattice) = 𝕃.parent is_valid_lattice_norec(::ConditionalsLattice, @nospecialize(elem)) = isa(elem, Conditional) +""" + struct InterConditionalsLattice{𝕃<:AbstractLattice} <: AbstractLattice + +A lattice extending a base lattice `𝕃` and adjoining `InterConditional`. +""" struct InterConditionalsLattice{𝕃<:AbstractLattice} <: AbstractLattice parent::𝕃 end @@ -51,29 +56,29 @@ widenlattice(𝕃::InterConditionalsLattice) = 𝕃.parent is_valid_lattice_norec(::InterConditionalsLattice, @nospecialize(elem)) = isa(elem, InterConditional) """ - struct MustAliasesLattice{𝕃} + struct MustAliasesLattice{𝕃<:AbstractLattice} A lattice extending lattice `𝕃` and adjoining `MustAlias`. """ -struct MustAliasesLattice{𝕃 <: AbstractLattice} <: AbstractLattice +struct MustAliasesLattice{𝕃<:AbstractLattice} <: AbstractLattice parent::𝕃 end widenlattice(𝕃::MustAliasesLattice) = 𝕃.parent -is_valid_lattice_norec(𝕃::MustAliasesLattice, @nospecialize(elem)) = isa(elem, MustAlias) +is_valid_lattice_norec(::MustAliasesLattice, @nospecialize(elem)) = isa(elem, MustAlias) """ - struct InterMustAliasesLattice{𝕃} + struct InterMustAliasesLattice{𝕃<:AbstractLattice} A lattice extending lattice `𝕃` and adjoining `InterMustAlias`. """ -struct InterMustAliasesLattice{𝕃 <: AbstractLattice} <: AbstractLattice +struct InterMustAliasesLattice{𝕃<:AbstractLattice} <: AbstractLattice parent::𝕃 end widenlattice(𝕃::InterMustAliasesLattice) = 𝕃.parent -is_valid_lattice_norec(𝕃::InterMustAliasesLattice, @nospecialize(elem)) = isa(elem, InterMustAlias) +is_valid_lattice_norec(::InterMustAliasesLattice, @nospecialize(elem)) = isa(elem, InterMustAlias) -const AnyConditionalsLattice{𝕃} = Union{ConditionalsLattice{𝕃}, InterConditionalsLattice{𝕃}} -const AnyMustAliasesLattice{𝕃} = Union{MustAliasesLattice{𝕃}, InterMustAliasesLattice{𝕃}} +const AnyConditionalsLattice{𝕃<:AbstractLattice} = Union{ConditionalsLattice{𝕃}, InterConditionalsLattice{𝕃}} +const AnyMustAliasesLattice{𝕃<:AbstractLattice} = Union{MustAliasesLattice{𝕃}, InterMustAliasesLattice{𝕃}} const SimpleInferenceLattice = typeof(PartialsLattice(ConstsLattice())) const BaseInferenceLattice = typeof(ConditionalsLattice(SimpleInferenceLattice.instance)) @@ -83,7 +88,7 @@ const IPOResultLattice = typeof(InterConditionalsLattice(SimpleInferenceLattice. struct InferenceLattice{𝕃<:AbstractLattice} <: AbstractLattice The full lattice used for abstract interpretation during inference. -Takes a base lattice `𝕃` and adjoins `LimitedAccuracy`. +Extends a base lattice `𝕃` and adjoins `LimitedAccuracy`. """ struct InferenceLattice{𝕃<:AbstractLattice} <: AbstractLattice parent::𝕃 @@ -94,8 +99,8 @@ is_valid_lattice_norec(::InferenceLattice, @nospecialize(elem)) = isa(elem, Limi """ struct OptimizerLattice{𝕃<:AbstractLattice} <: AbstractLattice -The lattice used by the optimizer. Extends -`BaseInferenceLattice` with `MaybeUndef`. +The lattice used by the optimizer. +Extends a base lattice `𝕃` and adjoins `MaybeUndef`. """ struct OptimizerLattice{𝕃<:AbstractLattice} <: AbstractLattice parent::𝕃 From 8b2b7f55696c8422ac237482cb6de2459f67a069 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Wed, 28 Dec 2022 11:11:08 +0900 Subject: [PATCH 184/387] minor fixes on `[getfield|setfield!]_tfunc`s (#48009) --- base/compiler/tfuncs.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 4370b38c4a6865..ad24636add1a49 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -412,7 +412,7 @@ end end elseif isa(a1, Union) # Results can only be `Const` or `Bool` - return tmerge(fallback_lattice, + return tmerge(𝕃, isdefined_tfunc(𝕃, rewrap_unionall(a1.a, arg1t), sym), isdefined_tfunc(𝕃, rewrap_unionall(a1.b, arg1t), sym)) end @@ -1087,8 +1087,7 @@ end end @nospecs function _getfield_tfunc(𝕃::AnyMustAliasesLattice, s00, name, setfield::Bool) - s00 = widenmustalias(s00) - return _getfield_tfunc(widenlattice(𝕃), s00, name, setfield) + return _getfield_tfunc(widenlattice(𝕃), widenmustalias(s00), name, setfield) end @nospecs function _getfield_tfunc(𝕃::PartialsLattice, s00, name, setfield::Bool) @@ -1284,7 +1283,7 @@ end end @nospecs function setfield!_tfunc(𝕃::AbstractLattice, o, f, v) mutability_errorcheck(o) || return Bottom - ft = _getfield_tfunc(fallback_lattice, o, f, true) + ft = _getfield_tfunc(𝕃, o, f, true) ft === Bottom && return Bottom hasintersect(widenconst(v), widenconst(ft)) || return Bottom return v From fc4c666d672a45d9f38693cc6b41ca22d30e5f77 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Wed, 28 Dec 2022 09:05:46 +0100 Subject: [PATCH 185/387] Update calling-c-and-fortran-code.md (#47976) * Update calling-c-and-fortran-code.md Co-authored-by: Alex Arslan --- doc/src/manual/calling-c-and-fortran-code.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/manual/calling-c-and-fortran-code.md b/doc/src/manual/calling-c-and-fortran-code.md index 0ebed7db009c9f..d8ec8110b6d183 100644 --- a/doc/src/manual/calling-c-and-fortran-code.md +++ b/doc/src/manual/calling-c-and-fortran-code.md @@ -817,7 +817,7 @@ end ## Garbage Collection Safety When passing data to a `@ccall`, it is best to avoid using the [`pointer`](@ref) function. -Instead define a convert method and pass the variables directly to the `@ccall`. `@ccall` +Instead define a [`Base.cconvert`](@ref) method and pass the variables directly to the `@ccall`. `@ccall` automatically arranges that all of its arguments will be preserved from garbage collection until the call returns. If a C API will store a reference to memory allocated by Julia, after the `@ccall` returns, you must ensure that the object remains visible to the garbage collector. The suggested From dd8d58e6048464d83597114eb65539ec8c8d71ab Mon Sep 17 00:00:00 2001 From: Sukera <11753998+Seelengrab@users.noreply.github.com> Date: Wed, 28 Dec 2022 09:07:01 +0100 Subject: [PATCH 186/387] Fix doc comment in internal `__convert_digit` (#47997) Co-authored-by: Sukera --- base/parse.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/parse.jl b/base/parse.jl index 00e71e189237b7..6e616004a47afc 100644 --- a/base/parse.jl +++ b/base/parse.jl @@ -90,8 +90,8 @@ function parseint_preamble(signed::Bool, base::Int, s::AbstractString, startpos: end # '0':'9' -> 0:9 -# 'A':'Z' -> 10:26 -# 'a':'z' -> 10:26 if base <= 36, 36:62 otherwise +# 'A':'Z' -> 10:35 +# 'a':'z' -> 10:35 if base <= 36, 36:61 otherwise # input outside of that is mapped to base @inline function __convert_digit(_c::UInt32, base::UInt32) _0 = UInt32('0') From 68d4ee32b01aa824bfdd10174b8b841be311a1eb Mon Sep 17 00:00:00 2001 From: Petr Vana Date: Wed, 28 Dec 2022 09:29:58 +0100 Subject: [PATCH 187/387] Use istaskfailed instead of errormonitor --- contrib/generate_precompile.jl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index 62c23bfd834712..645d3abf6360db 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -256,7 +256,7 @@ let lock(print_lk) do isempty(args) || push!(status, args...) t1, t2, t3, t4 = (get(status, x, "") for x in ["step1", "repl", "step2", "execute"]) - print("\rCollect (normal($t1), REPL $t2 ($t3)) => Execute $t4") + print("\rCollect (normal ($t1), REPL $t2 ($t3)) => Execute $t4") end end end @@ -396,7 +396,6 @@ function generate_precompile_statements() close(statements_step2) print_state("step2" => "F,$n_step2") end - #errormonitor(step2) !PARALLEL_PRECOMPILATION && wait(step2) # Create a staging area where all the loaded packages are available @@ -457,8 +456,10 @@ function generate_precompile_statements() n_succeeded > 1200 || @warn "Only $n_succeeded precompile statements" end - PARALLEL_PRECOMPILATION && wait(step1) - PARALLEL_PRECOMPILATION && wait(step2) + wait(step1) + istaskfailed(step1) && throw("Step 1 of collecting precompiles failed.") + wait(step2) + istaskfailed(step2) && throw("Step 2 of collecting precompiles failed.") tot_time = time_ns() - start_time println("Precompilation complete. Summary:") From 03b9cc63c0dac0195448793c6907d45dc124d9e8 Mon Sep 17 00:00:00 2001 From: Petr Vana Date: Wed, 28 Dec 2022 11:00:56 +0100 Subject: [PATCH 188/387] Use fetch instead of errormonitor --- contrib/generate_precompile.jl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index 645d3abf6360db..fcd6777ccc245d 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -308,8 +308,8 @@ function generate_precompile_statements() end close(statements_step1) print_state("step1" => "F,$n_step1") + return :ok end - #errormonitor(step1) !PARALLEL_PRECOMPILATION && wait(step1) step2 = @async mktemp() do precompile_file, precompile_file_h @@ -395,6 +395,7 @@ function generate_precompile_statements() end close(statements_step2) print_state("step2" => "F,$n_step2") + return :ok end !PARALLEL_PRECOMPILATION && wait(step2) @@ -456,10 +457,8 @@ function generate_precompile_statements() n_succeeded > 1200 || @warn "Only $n_succeeded precompile statements" end - wait(step1) - istaskfailed(step1) && throw("Step 1 of collecting precompiles failed.") - wait(step2) - istaskfailed(step2) && throw("Step 2 of collecting precompiles failed.") + fetch(step1) == :ok || throw("Step 1 of collecting precompiles failed.") + fetch(step2) == :ok || throw("Step 2 of collecting precompiles failed.") tot_time = time_ns() - start_time println("Precompilation complete. Summary:") From 02492f625f6c89e596e5bed6ce8ec74df5261c12 Mon Sep 17 00:00:00 2001 From: TacHawkes Date: Wed, 28 Dec 2022 15:12:33 +0100 Subject: [PATCH 189/387] Update README.md (#48018) Fixed the version number of the current stable release --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a2593c0830c7d6..4c8d93f70bf725 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ and then use the command prompt to change into the resulting julia directory. By Julia. However, most users should use the [most recent stable version](https://github.com/JuliaLang/julia/releases) of Julia. You can get this version by running: - git checkout v1.8.3 + git checkout v1.8.4 To build the `julia` executable, run `make` from within the julia directory. From 6ea5ad5698faefdf0ffeabed80fa95369350bb6c Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Wed, 28 Dec 2022 09:49:11 -0500 Subject: [PATCH 190/387] replace broken NFC link in manual (#48017) --- doc/src/manual/variables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/manual/variables.md b/doc/src/manual/variables.md index cf391ff86976cf..8f8c58d319edaa 100644 --- a/doc/src/manual/variables.md +++ b/doc/src/manual/variables.md @@ -137,7 +137,7 @@ ERROR: syntax: unexpected "=" Some Unicode characters are considered to be equivalent in identifiers. Different ways of entering Unicode combining characters (e.g., accents) -are treated as equivalent (specifically, Julia identifiers are [NFC](https://www.macchiato.com/unicode-intl-sw/nfc-faq)-normalized). +are treated as equivalent (specifically, Julia identifiers are [NFC](https://en.wikipedia.org/wiki/Unicode_equivalence). Julia also includes a few non-standard equivalences for characters that are visually similar and are easily entered by some input methods. The Unicode characters `ɛ` (U+025B: Latin small letter open e) and `µ` (U+00B5: micro sign) From 83245120b7cb79b10a6a21c18fae48c7e7ae2c88 Mon Sep 17 00:00:00 2001 From: Nathan Daly Date: Wed, 28 Dec 2022 08:34:41 -0700 Subject: [PATCH 191/387] Add option to `jl_print_task_backtraces(bool)` to control whether or not to print DONE tasks (#47933) Add option to `jl_print_task_backtraces(bool)` to control whether or not to print DONE tasks This lets you print all live tasks, with less noise in the logs, in case it's been a while since the last GC. In some cases, we are seeing many thousands of DONE Tasks, which greatly increase the noise in the logs, and can overwhelm the DataDog logging. The API now takes a bool, which can allow us to control this setting, letting us print the DONE tasks if the user wants to, but also hide them if desired. --- src/stackwalk.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/stackwalk.c b/src/stackwalk.c index 481b0abf9d701d..4965e46931016b 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -1126,7 +1126,7 @@ JL_DLLEXPORT void jl_print_backtrace(void) JL_NOTSAFEPOINT // Print backtraces for all live tasks, for all threads. // WARNING: this is dangerous and can crash if used outside of gdb, if // all of Julia's threads are not stopped! -JL_DLLEXPORT void jl_print_task_backtraces(void) JL_NOTSAFEPOINT +JL_DLLEXPORT void jl_print_task_backtraces(int show_done) JL_NOTSAFEPOINT { size_t nthreads = jl_atomic_load_acquire(&jl_n_threads); jl_ptls_t *allstates = jl_atomic_load_relaxed(&jl_all_tls_states); @@ -1146,9 +1146,13 @@ JL_DLLEXPORT void jl_print_task_backtraces(void) JL_NOTSAFEPOINT void **lst = live_tasks->items; for (size_t j = 0; j < live_tasks->len; j++) { jl_task_t *t = (jl_task_t *)lst[j]; + int t_state = jl_atomic_load_relaxed(&t->_state); + if (!show_done && t_state == JL_TASK_STATE_DONE) { + continue; + } jl_safe_printf(" ---- Task %zu (%p)\n", j + 1, t); jl_safe_printf(" (sticky: %d, started: %d, state: %d, tid: %d)\n", - t->sticky, t->started, jl_atomic_load_relaxed(&t->_state), + t->sticky, t->started, t_state, jl_atomic_load_relaxed(&t->tid) + 1); if (t->stkbuf != NULL) jlbacktracet(t); From b838067dca2e1a5fa896c3a7c9ccbaef64be023e Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Wed, 28 Dec 2022 13:19:24 -0300 Subject: [PATCH 192/387] Add dereferenceable LLVM attribute to some GC functions (#46256) --- src/cgutils.cpp | 4 ++++ src/codegen.cpp | 7 ++++++- src/llvm-final-gc-lowering.cpp | 4 ++++ src/llvm-pass-helpers.cpp | 7 +++---- test/llvmpasses/alloc-opt-gcframe.jl | 2 +- test/llvmpasses/final-lower-gc.ll | 2 +- 6 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/cgutils.cpp b/src/cgutils.cpp index 6a70171aea5f82..84c917e70b5cb3 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -3455,6 +3455,10 @@ static Value *emit_allocobj(jl_codectx_t &ctx, size_t static_size, Value *jt) Function *F = prepare_call(jl_alloc_obj_func); auto call = ctx.builder.CreateCall(F, {current_task, ConstantInt::get(getSizeTy(ctx.builder.getContext()), static_size), maybe_decay_untracked(ctx, jt)}); call->setAttributes(F->getAttributes()); + if (static_size > 0) + { + call->addRetAttr(Attribute::getWithDereferenceableBytes(ctx.builder.getContext(),static_size)); + } return call; } diff --git a/src/codegen.cpp b/src/codegen.cpp index c01e340431e5f0..31bfb4b5ca51d1 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -842,7 +842,12 @@ static const auto jl_alloc_obj_func = new JuliaFunction{ }, [](LLVMContext &C) { return AttributeList::get(C, AttributeSet::get(C, makeArrayRef({Attribute::getWithAllocSizeArgs(C, 1, None)})), // returns %1 bytes - Attributes(C, {Attribute::NoAlias, Attribute::NonNull}), + + Attributes(C, {Attribute::NoAlias, Attribute::NonNull, +#if JL_LLVM_VERSION >= 150000 + Attribute::get(C, Attribute::AllocKind, AllocFnKind::Alloc | AllocFnKind::Uninitialized | AllocFnKind::Aligned), +#endif + }), None); }, }; static const auto jl_newbits_func = new JuliaFunction{ diff --git a/src/llvm-final-gc-lowering.cpp b/src/llvm-final-gc-lowering.cpp index 8c71c0bae58418..7adb25c7af08e4 100644 --- a/src/llvm-final-gc-lowering.cpp +++ b/src/llvm-final-gc-lowering.cpp @@ -200,17 +200,21 @@ Value *FinalLowerGC::lowerGCAllocBytes(CallInst *target, Function &F) builder.SetCurrentDebugLocation(target->getDebugLoc()); auto ptls = target->getArgOperand(0); CallInst *newI; + Attribute derefAttr; if (offset < 0) { newI = builder.CreateCall( bigAllocFunc, { ptls, ConstantInt::get(getSizeTy(F.getContext()), sz + sizeof(void*)) }); + derefAttr = Attribute::getWithDereferenceableBytes(F.getContext(), sz + sizeof(void*)); } else { auto pool_offs = ConstantInt::get(Type::getInt32Ty(F.getContext()), offset); auto pool_osize = ConstantInt::get(Type::getInt32Ty(F.getContext()), osize); newI = builder.CreateCall(poolAllocFunc, { ptls, pool_offs, pool_osize }); + derefAttr = Attribute::getWithDereferenceableBytes(F.getContext(), osize); } newI->setAttributes(newI->getCalledFunction()->getAttributes()); + newI->addRetAttr(derefAttr); newI->takeName(target); return newI; } diff --git a/src/llvm-pass-helpers.cpp b/src/llvm-pass-helpers.cpp index 8e4045d14b80dd..2ddfbf1e2af680 100644 --- a/src/llvm-pass-helpers.cpp +++ b/src/llvm-pass-helpers.cpp @@ -124,7 +124,6 @@ namespace jl_intrinsics { { addRetAttr(target, Attribute::NoAlias); addRetAttr(target, Attribute::NonNull); - target->addFnAttr(Attribute::getWithAllocSizeArgs(context, 1, None)); // returns %1 bytes return target; } @@ -153,7 +152,7 @@ namespace jl_intrinsics { false), Function::ExternalLinkage, GC_ALLOC_BYTES_NAME); - + intrinsic->addFnAttr(Attribute::getWithAllocSizeArgs(context.getLLVMContext(), 1, None)); return addGCAllocAttributes(intrinsic, context.getLLVMContext()); }); @@ -229,7 +228,7 @@ namespace jl_well_known { false), Function::ExternalLinkage, GC_BIG_ALLOC_NAME); - + bigAllocFunc->addFnAttr(Attribute::getWithAllocSizeArgs(context.getLLVMContext(), 1, None)); return addGCAllocAttributes(bigAllocFunc, context.getLLVMContext()); }); @@ -243,7 +242,7 @@ namespace jl_well_known { false), Function::ExternalLinkage, GC_POOL_ALLOC_NAME); - + poolAllocFunc->addFnAttr(Attribute::getWithAllocSizeArgs(context.getLLVMContext(), 2, None)); return addGCAllocAttributes(poolAllocFunc, context.getLLVMContext()); }); diff --git a/test/llvmpasses/alloc-opt-gcframe.jl b/test/llvmpasses/alloc-opt-gcframe.jl index 3b5fc3a51a606f..c5a5b2be866142 100644 --- a/test/llvmpasses/alloc-opt-gcframe.jl +++ b/test/llvmpasses/alloc-opt-gcframe.jl @@ -18,7 +18,7 @@ target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" # CHECK-NEXT: [[ptls_load:%.*]] = load {}*, {}** [[ptls_field]], align 8, !tbaa !0 # CHECK-NEXT: [[ppjl_ptls:%.*]] = bitcast {}* [[ptls_load]] to {}** # CHECK-NEXT: [[ptls_i8:%.*]] = bitcast {}** [[ppjl_ptls]] to i8* -# CHECK-NEXT: %v = call noalias nonnull {} addrspace(10)* @ijl_gc_pool_alloc(i8* [[ptls_i8]], i32 [[SIZE_T:[0-9]+]], i32 16) +# CHECK-NEXT: %v = call noalias nonnull dereferenceable({{[0-9]+}}) {} addrspace(10)* @ijl_gc_pool_alloc(i8* [[ptls_i8]], i32 [[SIZE_T:[0-9]+]], i32 16) # CHECK: store atomic {} addrspace(10)* @tag, {} addrspace(10)* addrspace(10)* {{.*}} unordered, align 8, !tbaa !4 println(""" define {} addrspace(10)* @return_obj() { diff --git a/test/llvmpasses/final-lower-gc.ll b/test/llvmpasses/final-lower-gc.ll index 176b695ba918b1..95e88f9feac9e0 100644 --- a/test/llvmpasses/final-lower-gc.ll +++ b/test/llvmpasses/final-lower-gc.ll @@ -59,7 +59,7 @@ top: %pgcstack = call {}*** @julia.get_pgcstack() %ptls = call {}*** @julia.ptls_states() %ptls_i8 = bitcast {}*** %ptls to i8* -; CHECK: %v = call noalias nonnull {} addrspace(10)* @ijl_gc_pool_alloc +; CHECK: %v = call noalias nonnull dereferenceable({{[0-9]+}}) {} addrspace(10)* @ijl_gc_pool_alloc %v = call {} addrspace(10)* @julia.gc_alloc_bytes(i8* %ptls_i8, i64 8) %0 = bitcast {} addrspace(10)* %v to {} addrspace(10)* addrspace(10)* %1 = getelementptr {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %0, i64 -1 From 82fbf54131fae6dbb74c0a125c68eecf46285154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mos=C3=A8=20Giordano?= Date: Wed, 28 Dec 2022 17:19:51 +0100 Subject: [PATCH 193/387] [CompilerSupportLibraries_jll] Add `libssp` as product on Windows (#48012) --- .../src/CompilerSupportLibraries_jll.jl | 7 +++++++ stdlib/CompilerSupportLibraries_jll/test/runtests.jl | 3 +++ 2 files changed, 10 insertions(+) diff --git a/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl b/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl index 604c7ae89dd5eb..e58e16a46df35b 100644 --- a/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl +++ b/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl @@ -21,6 +21,8 @@ libstdcxx_handle = C_NULL libstdcxx_path = "" libgomp_handle = C_NULL libgomp_path = "" +libssp_handle = C_NULL +libssp_path = "" if Sys.iswindows() if arch(HostPlatform()) == "x86_64" @@ -31,6 +33,7 @@ if Sys.iswindows() const libgfortran = string("libgfortran-", libgfortran_version(HostPlatform()).major, ".dll") const libstdcxx = "libstdc++-6.dll" const libgomp = "libgomp-1.dll" + const libssp = "libssp-0.dll" elseif Sys.isapple() if arch(HostPlatform()) == "aarch64" || libgfortran_version(HostPlatform()) == v"5" const libgcc_s = "@rpath/libgcc_s.1.1.dylib" @@ -56,6 +59,10 @@ function __init__() global libstdcxx_path = dlpath(libstdcxx_handle) global libgomp_handle = dlopen(libgomp) global libgomp_path = dlpath(libgomp_handle) + if Sys.iswindows() + global libssp_handle = dlopen(libssp) + global libssp_path = dlpath(libssp_handle) + end global artifact_dir = dirname(Sys.BINDIR) LIBPATH[] = dirname(libgcc_s_path) push!(LIBPATH_list, LIBPATH[]) diff --git a/stdlib/CompilerSupportLibraries_jll/test/runtests.jl b/stdlib/CompilerSupportLibraries_jll/test/runtests.jl index 85cf132c3a5bd5..de823c7f666541 100644 --- a/stdlib/CompilerSupportLibraries_jll/test/runtests.jl +++ b/stdlib/CompilerSupportLibraries_jll/test/runtests.jl @@ -7,4 +7,7 @@ using Test, CompilerSupportLibraries_jll @test isfile(CompilerSupportLibraries_jll.libgfortran_path) @test isfile(CompilerSupportLibraries_jll.libstdcxx_path) @test isfile(CompilerSupportLibraries_jll.libgomp_path) + if Sys.iswindows() + @test isfile(CompilerSupportLibraries_jll.libssp_path) + end end From 58d9c87fc5274f4b5227a8ae4309db6ed0ba8eba Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Wed, 28 Dec 2022 14:52:46 -0300 Subject: [PATCH 194/387] Change permissions of stdlib pkgimgs (#48019) * Change permissions of pkgimgs * Update base/loading.jl Co-authored-by: Elliot Saba Co-authored-by: Elliot Saba --- base/loading.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/loading.jl b/base/loading.jl index 06ba6e733b6d7f..f7ff11f1ae165a 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -2084,7 +2084,7 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in # Ensure that the user can execute the `.so` we're generating # Note that on windows, `filemode(path)` typically returns `0o666`, so this # addition of the execute bit for the user is doubly needed. - chmod(tmppath_so, filemode(path) & 0o777 | 0o300) + chmod(tmppath_so, filemode(path) & 0o777 | 0o333) end # prune the directory with cache files From 6ac5159a0b4c6754a996e092c4c345efafba36fb Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Wed, 28 Dec 2022 13:47:15 -0600 Subject: [PATCH 195/387] Replace dynamic dispatch with runtime branch on `rev` keyword in sortperm (#47966) --- base/sort.jl | 21 ++++++++++++++++----- test/sorting.jl | 22 ++++++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/base/sort.jl b/base/sort.jl index 1266da8a8c9df7..669d2d97b2ac1e 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -1560,8 +1560,14 @@ function sortperm(A::AbstractArray; order::Ordering=Forward, scratch::Union{Vector{<:Integer}, Nothing}=nothing, dims...) #to optionally specify dims argument - ordr = ord(lt,by,rev,order) - if ordr === Forward && isa(A,Vector) && eltype(A)<:Integer + if rev === true + _sortperm(A; alg, order=ord(lt, by, true, order), scratch, dims...) + else + _sortperm(A; alg, order=ord(lt, by, nothing, order), scratch, dims...) + end +end +function _sortperm(A::AbstractArray; alg, order, scratch, dims...) + if order === Forward && isa(A,Vector) && eltype(A)<:Integer n = length(A) if n > 1 min, max = extrema(A) @@ -1573,7 +1579,7 @@ function sortperm(A::AbstractArray; end end ix = copymutable(LinearIndices(A)) - sort!(ix; alg, order = Perm(ordr, vec(A)), scratch, dims...) + sort!(ix; alg, order = Perm(order, vec(A)), scratch, dims...) end @@ -1615,7 +1621,7 @@ julia> sortperm!(p, A; dims=2); p 2 4 ``` """ -function sortperm!(ix::AbstractArray{T}, A::AbstractArray; +@inline function sortperm!(ix::AbstractArray{T}, A::AbstractArray; alg::Algorithm=DEFAULT_UNSTABLE, lt=isless, by=identity, @@ -1630,7 +1636,12 @@ function sortperm!(ix::AbstractArray{T}, A::AbstractArray; if !initialized ix .= LinearIndices(A) end - sort!(ix; alg, order = Perm(ord(lt, by, rev, order), vec(A)), scratch, dims...) + + if rev === true + sort!(ix; alg, order=Perm(ord(lt, by, true, order), vec(A)), scratch, dims...) + else + sort!(ix; alg, order=Perm(ord(lt, by, nothing, order), vec(A)), scratch, dims...) + end end # sortperm for vectors of few unique integers diff --git a/test/sorting.jl b/test/sorting.jl index eb5020547c789a..c4e324a61cde9a 100644 --- a/test/sorting.jl +++ b/test/sorting.jl @@ -918,6 +918,28 @@ end @test bsqs() === bsqs(missing, missing, InsertionSort) end +function test_allocs() + v = rand(10) + i = randperm(length(v)) + @test 1 == @allocations sort(v) + @test 0 == @allocations sortperm!(i, v) + @test 0 == @allocations sort!(i) + @test 0 == @allocations sortperm!(i, v, rev=true) + @test 1 == @allocations sortperm(v, rev=true) + @test 1 == @allocations sortperm(v, rev=false) + @test 0 == @allocations sortperm!(i, v, order=Base.Reverse) + @test 1 == @allocations sortperm(v) + @test 1 == @allocations sortperm(i, by=sqrt) + @test 0 == @allocations sort!(v, lt=(a, b) -> hash(a) < hash(b)) + sort!(Int[], rev=false) # compile + @test 0 == @allocations sort!(i, rev=false) + rand!(i) + @test 0 == @allocations sort!(i, order=Base.Reverse) +end +@testset "Small calls do not unnecessarily allocate" begin + test_allocs() +end + # This testset is at the end of the file because it is slow. @testset "searchsorted" begin numTypes = [ Int8, Int16, Int32, Int64, Int128, From 1fda4bb4bace782cff71ebdae7a1fad43771ddb7 Mon Sep 17 00:00:00 2001 From: pchintalapudi <34727397+pchintalapudi@users.noreply.github.com> Date: Wed, 28 Dec 2022 22:52:10 -0800 Subject: [PATCH 196/387] Don't double-count inference time (#48033) * Use the same timing reentrancy counter for both inference and codegen * Add some timing tests * Add macro-based timer test --- src/aotcompile.cpp | 15 +++++++----- src/gf.c | 10 ++------ src/jitlayers.cpp | 31 +++++++++++++++---------- src/julia.h | 2 +- src/task.c | 4 ++-- test/misc.jl | 57 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/aotcompile.cpp b/src/aotcompile.cpp index 6f9345ee18f829..bd4c896a39e110 100644 --- a/src/aotcompile.cpp +++ b/src/aotcompile.cpp @@ -284,7 +284,7 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm jl_code_info_t *src = NULL; JL_GC_PUSH1(&src); auto ct = jl_current_task; - ct->reentrant_codegen++; + ct->reentrant_timing++; orc::ThreadSafeContext ctx; orc::ThreadSafeModule backing; if (!llvmmod) { @@ -471,12 +471,13 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm } data->M = std::move(clone); - if (measure_compile_time_enabled) - jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); + if (!ct->reentrant_timing-- && measure_compile_time_enabled) { + auto end = jl_hrtime(); + jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, end - compiler_start_time); + } if (ctx.getContext()) { jl_ExecutionEngine->releaseContext(std::move(ctx)); } - ct->reentrant_codegen--; return (void*)data; } @@ -1138,8 +1139,10 @@ void jl_get_llvmf_defn_impl(jl_llvmf_dump_t* dump, jl_method_instance_t *mi, siz F = cast(m.getModuleUnlocked()->getNamedValue(*fname)); } JL_GC_POP(); - if (measure_compile_time_enabled) - jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); + if (measure_compile_time_enabled) { + auto end = jl_hrtime(); + jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, end - compiler_start_time); + } if (F) { dump->TSM = wrap(new orc::ThreadSafeModule(std::move(m))); dump->F = wrap(F); diff --git a/src/gf.c b/src/gf.c index 99c482420e2f27..2828d0e22e2d9e 100644 --- a/src/gf.c +++ b/src/gf.c @@ -3542,7 +3542,7 @@ int jl_has_concrete_subtype(jl_value_t *typ) JL_DLLEXPORT void jl_typeinf_timing_begin(void) { jl_task_t *ct = jl_current_task; - if (ct->reentrant_inference == 1) { + if (!ct->reentrant_timing++) { ct->inference_start_time = jl_hrtime(); } } @@ -3550,7 +3550,7 @@ JL_DLLEXPORT void jl_typeinf_timing_begin(void) JL_DLLEXPORT void jl_typeinf_timing_end(void) { jl_task_t *ct = jl_current_task; - if (ct->reentrant_inference == 1) { + if (!--ct->reentrant_timing) { if (jl_atomic_load_relaxed(&jl_measure_compile_time_enabled)) { uint64_t inftime = jl_hrtime() - ct->inference_start_time; jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, inftime); @@ -3562,16 +3562,10 @@ JL_DLLEXPORT void jl_typeinf_timing_end(void) JL_DLLEXPORT void jl_typeinf_lock_begin(void) { JL_LOCK(&jl_codegen_lock); - //Although this is claiming to be a typeinfer lock, it is actually - //affecting the codegen lock count, not type inference's inferencing count - jl_task_t *ct = jl_current_task; - ct->reentrant_codegen++; } JL_DLLEXPORT void jl_typeinf_lock_end(void) { - jl_task_t *ct = jl_current_task; - ct->reentrant_codegen--; JL_UNLOCK(&jl_codegen_lock); } diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index f6ecd64e757d8b..fecbc28cf8ea71 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -303,7 +303,7 @@ extern "C" JL_DLLEXPORT int jl_compile_extern_c_impl(LLVMOrcThreadSafeModuleRef llvmmod, void *p, void *sysimg, jl_value_t *declrt, jl_value_t *sigt) { auto ct = jl_current_task; - ct->reentrant_codegen++; + ct->reentrant_timing++; uint64_t compiler_start_time = 0; uint8_t measure_compile_time_enabled = jl_atomic_load_relaxed(&jl_measure_compile_time_enabled); if (measure_compile_time_enabled) @@ -340,8 +340,10 @@ int jl_compile_extern_c_impl(LLVMOrcThreadSafeModuleRef llvmmod, void *p, void * jl_ExecutionEngine->addModule(std::move(*into)); } JL_UNLOCK(&jl_codegen_lock); - if (!--ct->reentrant_codegen && measure_compile_time_enabled) - jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); + if (!--ct->reentrant_timing && measure_compile_time_enabled) { + auto end = jl_hrtime(); + jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, end - compiler_start_time); + } if (ctx.getContext()) { jl_ExecutionEngine->releaseContext(std::move(ctx)); } @@ -396,7 +398,7 @@ extern "C" JL_DLLEXPORT jl_code_instance_t *jl_generate_fptr_impl(jl_method_instance_t *mi JL_PROPAGATES_ROOT, size_t world) { auto ct = jl_current_task; - ct->reentrant_codegen++; + ct->reentrant_timing++; uint64_t compiler_start_time = 0; uint8_t measure_compile_time_enabled = jl_atomic_load_relaxed(&jl_measure_compile_time_enabled); bool is_recompile = false; @@ -449,10 +451,11 @@ jl_code_instance_t *jl_generate_fptr_impl(jl_method_instance_t *mi JL_PROPAGATES codeinst = NULL; } JL_UNLOCK(&jl_codegen_lock); - if (!--ct->reentrant_codegen && measure_compile_time_enabled) { + if (!--ct->reentrant_timing && measure_compile_time_enabled) { uint64_t t_comp = jl_hrtime() - compiler_start_time; - if (is_recompile) + if (is_recompile) { jl_atomic_fetch_add_relaxed(&jl_cumulative_recompile_time, t_comp); + } jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, t_comp); } JL_GC_POP(); @@ -466,7 +469,7 @@ void jl_generate_fptr_for_unspecialized_impl(jl_code_instance_t *unspec) return; } auto ct = jl_current_task; - ct->reentrant_codegen++; + ct->reentrant_timing++; uint64_t compiler_start_time = 0; uint8_t measure_compile_time_enabled = jl_atomic_load_relaxed(&jl_measure_compile_time_enabled); if (measure_compile_time_enabled) @@ -500,8 +503,10 @@ void jl_generate_fptr_for_unspecialized_impl(jl_code_instance_t *unspec) JL_GC_POP(); } JL_UNLOCK(&jl_codegen_lock); // Might GC - if (!--ct->reentrant_codegen && measure_compile_time_enabled) - jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); + if (!--ct->reentrant_timing && measure_compile_time_enabled) { + auto end = jl_hrtime(); + jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, end - compiler_start_time); + } } @@ -522,7 +527,7 @@ jl_value_t *jl_dump_method_asm_impl(jl_method_instance_t *mi, size_t world, // (using sentinel value `1` instead) // so create an exception here so we can print pretty our lies auto ct = jl_current_task; - ct->reentrant_codegen++; + ct->reentrant_timing++; uint64_t compiler_start_time = 0; uint8_t measure_compile_time_enabled = jl_atomic_load_relaxed(&jl_measure_compile_time_enabled); if (measure_compile_time_enabled) @@ -552,8 +557,10 @@ jl_value_t *jl_dump_method_asm_impl(jl_method_instance_t *mi, size_t world, JL_GC_POP(); } JL_UNLOCK(&jl_codegen_lock); - if (!--ct->reentrant_codegen && measure_compile_time_enabled) - jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); + if (!--ct->reentrant_timing && measure_compile_time_enabled) { + auto end = jl_hrtime(); + jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, end - compiler_start_time); + } } if (specfptr != 0) return jl_dump_fptr_asm(specfptr, raw_mc, asm_variant, debuginfo, binary); diff --git a/src/julia.h b/src/julia.h index 84fecd5fe28c4a..1395df45013290 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1938,7 +1938,7 @@ typedef struct _jl_task_t { size_t bufsz; // actual sizeof stkbuf uint64_t inference_start_time; // time when inference started uint16_t reentrant_inference; // How many times we've reentered inference - uint16_t reentrant_codegen; // How many times we've reentered codegen + uint16_t reentrant_timing; // How many times we've reentered timing unsigned int copy_stack:31; // sizeof stack for copybuf unsigned int started:1; } jl_task_t; diff --git a/src/task.c b/src/task.c index a5ebc1ce26005d..1772127391183d 100644 --- a/src/task.c +++ b/src/task.c @@ -938,7 +938,7 @@ JL_DLLEXPORT jl_task_t *jl_new_task(jl_function_t *start, jl_value_t *completion t->threadpoolid = ct->threadpoolid; t->ptls = NULL; t->world_age = ct->world_age; - t->reentrant_codegen = 0; + t->reentrant_timing = 0; t->reentrant_inference = 0; t->inference_start_time = 0; @@ -1526,7 +1526,7 @@ jl_task_t *jl_init_root_task(jl_ptls_t ptls, void *stack_lo, void *stack_hi) ct->sticky = 1; ct->ptls = ptls; ct->world_age = 1; // OK to run Julia code on this task - ct->reentrant_codegen = 0; + ct->reentrant_timing = 0; ct->reentrant_inference = 0; ct->inference_start_time = 0; ptls->root_task = ct; diff --git a/test/misc.jl b/test/misc.jl index 8182312f45a6ac..480334fdaf0ae6 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -353,6 +353,8 @@ end after_comp, after_recomp = Base.cumulative_compile_time_ns() # no need to turn timing off, @time will do that @test after_comp >= before_comp; +@test after_recomp >= before_recomp; +@test after_recomp - before_recomp <= after_comp - before_comp; # should be approximately 60,000,000 ns, we definitely shouldn't exceed 100x that value # failing this probably means an uninitialized variable somewhere @@ -360,6 +362,40 @@ after_comp, after_recomp = Base.cumulative_compile_time_ns() # no need to turn t end # redirect_stdout +# issue #48024, avoid overcounting timers +begin + double(x::Real) = 2x; + calldouble(container) = double(container[1]); + calldouble2(container) = calldouble(container); + + Base.Experimental.@force_compile; + local elapsed = Base.time_ns(); + Base.cumulative_compile_timing(true); + local compiles = Base.cumulative_compile_time_ns(); + @eval calldouble([1.0]); + Base.cumulative_compile_timing(false); + compiles = Base.cumulative_compile_time_ns() .- compiles; + elapsed = Base.time_ns() - elapsed; + + # compile time should be at most total time + @test compiles[1] <= elapsed + # recompile time should be at most compile time + @test compiles[2] <= compiles[1] + + elapsed = Base.time_ns(); + Base.cumulative_compile_timing(true); + compiles = Base.cumulative_compile_time_ns(); + @eval calldouble(1.0); + Base.cumulative_compile_timing(false); + compiles = Base.cumulative_compile_time_ns() .- compiles; + elapsed = Base.time_ns() - elapsed; + + # compile time should be at most total time + @test compiles[1] <= elapsed + # recompile time should be at most compile time + @test compiles[2] <= compiles[1] +end + macro capture_stdout(ex) quote mktemp() do fname, f @@ -372,6 +408,27 @@ macro capture_stdout(ex) end end +# issue #48024, but with the time macro itself +begin + double(x::Real) = 2x; + calldouble(container) = double(container[1]); + calldouble2(container) = calldouble(container); + + local first = @capture_stdout @time @eval calldouble([1.0]) + local second = @capture_stdout @time @eval calldouble2(1.0) + + # these functions were not recompiled + local matches = collect(eachmatch(r"(\d+(?:\.\d+)?)%", first)) + @test length(matches) == 1 + @test parse(Float64, matches[1][1]) > 0.0 + @test parse(Float64, matches[1][1]) <= 100.0 + + matches = collect(eachmatch(r"(\d+(?:\.\d+)?)%", second)) + @test length(matches) == 1 + @test parse(Float64, matches[1][1]) > 0.0 + @test parse(Float64, matches[1][1]) <= 100.0 +end + # compilation reports in @time, @timev let f = gensym("f"), callf = gensym("callf"), call2f = gensym("call2f") @eval begin From 0f2665f2ee66483afd7ec3ca94ab994efc93833c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mos=C3=A8=20Giordano?= Date: Thu, 29 Dec 2022 08:30:43 +0100 Subject: [PATCH 197/387] [CompilerSupportLibraries_jll] Add libssp for more platforms (#48027) --- .../src/CompilerSupportLibraries_jll.jl | 6 +++++- stdlib/CompilerSupportLibraries_jll/test/runtests.jl | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl b/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl index e58e16a46df35b..0068414f942e87 100644 --- a/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl +++ b/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl @@ -43,11 +43,15 @@ elseif Sys.isapple() const libgfortran = string("@rpath/", "libgfortran.", libgfortran_version(HostPlatform()).major, ".dylib") const libstdcxx = "@rpath/libstdc++.6.dylib" const libgomp = "@rpath/libgomp.1.dylib" + const libssp = "@rpath/libssp.0.dylib" else const libgcc_s = "libgcc_s.so.1" const libgfortran = string("libgfortran.so.", libgfortran_version(HostPlatform()).major) const libstdcxx = "libstdc++.so.6" const libgomp = "libgomp.so.1" + if libc(HostPlatform()) != "musl" + const libssp = "libssp.so.0" + end end function __init__() @@ -59,7 +63,7 @@ function __init__() global libstdcxx_path = dlpath(libstdcxx_handle) global libgomp_handle = dlopen(libgomp) global libgomp_path = dlpath(libgomp_handle) - if Sys.iswindows() + @static if libc(HostPlatform()) != "musl" global libssp_handle = dlopen(libssp) global libssp_path = dlpath(libssp_handle) end diff --git a/stdlib/CompilerSupportLibraries_jll/test/runtests.jl b/stdlib/CompilerSupportLibraries_jll/test/runtests.jl index de823c7f666541..840a36bdd8d495 100644 --- a/stdlib/CompilerSupportLibraries_jll/test/runtests.jl +++ b/stdlib/CompilerSupportLibraries_jll/test/runtests.jl @@ -1,13 +1,13 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -using Test, CompilerSupportLibraries_jll +using Test, CompilerSupportLibraries_jll, Base.BinaryPlatforms @testset "CompilerSupportLibraries_jll" begin @test isfile(CompilerSupportLibraries_jll.libgcc_s_path) @test isfile(CompilerSupportLibraries_jll.libgfortran_path) @test isfile(CompilerSupportLibraries_jll.libstdcxx_path) @test isfile(CompilerSupportLibraries_jll.libgomp_path) - if Sys.iswindows() + if libc(HostPlatform()) != "musl" @test isfile(CompilerSupportLibraries_jll.libssp_path) end end From 9ded051e9f80871bb6192a8f473e3de9142e0978 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Thu, 29 Dec 2022 05:05:45 -0500 Subject: [PATCH 198/387] Add remarks for more cases of failed method lookup (#48023) * Add remarks for more cases of failed method lookup * Update base/compiler/typeinfer.jl Co-authored-by: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Co-authored-by: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> --- base/compiler/typeinfer.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 29c4a7e6e477a3..9fcb7ea539d224 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -914,6 +914,7 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize cache = :global # cache edge targets by default end if ccall(:jl_get_module_infer, Cint, (Any,), method.module) == 0 && !generating_sysimg() + add_remark!(interp, caller, "Inference is disabled for the target module") return EdgeCallResult(Any, nothing, Effects()) end if !caller.cached && caller.parent === nothing @@ -929,6 +930,7 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize result = InferenceResult(mi) frame = InferenceState(result, cache, interp) # always use the cache for edge targets if frame === nothing + add_remark!(interp, caller, "Failed to retrieve source") # can't get the source for this, so we know nothing unlock_mi_inference(interp, mi) return EdgeCallResult(Any, nothing, Effects()) From f49f537afae0ac016817fcf785df0f360f0222eb Mon Sep 17 00:00:00 2001 From: Petr Vana Date: Thu, 29 Dec 2022 13:08:52 +0100 Subject: [PATCH 199/387] Fancy printing --- contrib/generate_precompile.jl | 53 +++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index fcd6777ccc245d..994a81dec1113b 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -242,6 +242,9 @@ const HELP_PROMPT = "help?> " # You can disable parallel precompiles generation by setting `false` const PARALLEL_PRECOMPILATION = true +# TODO use can_fancyprint(io) from Pkg +const fancyprint = true + # Printing the current state let global print_state @@ -250,13 +253,30 @@ let "step1" => "W", "step2" => "W", "repl" => "0/0", - "execute" => "0/0", + "step3" => "W", + "clock" => "◐", ) + function print_status(key::String) + txt = status[key] + if startswith(txt, "W") # Waiting + printstyled("?", color=Base.warn_color()); print(txt[2:end]) + elseif startswith(txt, "R") # Running + printstyled(status["clock"], color=:magenta); print(txt[2:end]) + elseif startswith(txt, "F") # Finished + printstyled("✓", color=:green); print(txt[2:end]) + else + print(txt) + end + end function print_state(args::Pair{String,String}...) lock(print_lk) do isempty(args) || push!(status, args...) - t1, t2, t3, t4 = (get(status, x, "") for x in ["step1", "repl", "step2", "execute"]) - print("\rCollect (normal ($t1), REPL $t2 ($t3)) => Execute $t4") + print("\rCollect (Basic: ") + print_status("step1") + print(", REPL ", status["repl"], ": ") + print_status("step2") + print(") => Execute ") + print_status("step3") end end end @@ -275,8 +295,20 @@ function generate_precompile_statements() push!(statements_step1, statement) end - println("Precompile statements (Waiting, Running, Finished)") + println("Collect and execute precompile statements") print_state() + clock = @async begin + t = Timer(0; interval=1/10) + anim_chars = ["◐","◓","◑","◒"] + current = 1 + if fancyprint + while isopen(statements_step2) || !isempty(statements_step2) + print_state("clock" => anim_chars[current]) + wait(t) + current = current == 4 ? 1 : current + 1 + end + end + end # Collect statements from running the script step1 = @async mktempdir() do prec_path @@ -301,13 +333,14 @@ function generate_precompile_statements() run(`$(julia_exepath()) -O0 --sysimage $sysimg --trace-compile=$tmp_proc --startup-file=no -Cnative -e $s`) n_step1 = 0 for f in (tmp_prec, tmp_proc) + isfile(f) || continue for statement in split(read(f, String), '\n') push!(statements_step1, statement) n_step1 += 1 end end close(statements_step1) - print_state("step1" => "F,$n_step1") + print_state("step1" => "F$n_step1") return :ok end !PARALLEL_PRECOMPILATION && wait(step1) @@ -394,7 +427,7 @@ function generate_precompile_statements() n_step2 += 1 end close(statements_step2) - print_state("step2" => "F,$n_step2") + print_state("step2" => "F$n_step2") return :ok end !PARALLEL_PRECOMPILATION && wait(step2) @@ -444,17 +477,21 @@ function generate_precompile_statements() ps = Core.eval(PrecompileStagingArea, ps) precompile(ps...) n_succeeded += 1 - print_state("execute" => "$n_succeeded/$(length(statements))") + failed = length(statements) - n_succeeded + print_state("step3" => "R$n_succeeded ($failed failed)") catch ex # See #28808 @warn "Failed to precompile expression" form=statement exception=ex _module=nothing _file=nothing _line=0 end end + wait(clock) # Stop asynchronous printing + failed = length(statements) - n_succeeded + print_state("step3" => "F$n_succeeded ($failed failed)") println() if have_repl # Seems like a reasonable number right now, adjust as needed # comment out if debugging script - n_succeeded > 1200 || @warn "Only $n_succeeded precompile statements" + n_succeeded > 1500 || @warn "Only $n_succeeded precompile statements" end fetch(step1) == :ok || throw("Step 1 of collecting precompiles failed.") From 5083d8e9cadf438b77c2381b3d1153e5a8760346 Mon Sep 17 00:00:00 2001 From: Petr Vana Date: Thu, 29 Dec 2022 13:22:42 +0100 Subject: [PATCH 200/387] Make clock spinning --- contrib/generate_precompile.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index 994a81dec1113b..baed8884d56268 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -478,6 +478,7 @@ function generate_precompile_statements() precompile(ps...) n_succeeded += 1 failed = length(statements) - n_succeeded + yield() # Make clock spinning print_state("step3" => "R$n_succeeded ($failed failed)") catch ex # See #28808 From c0f763517f0924cb0d6f7f6fdc6b78ff8484360d Mon Sep 17 00:00:00 2001 From: Petr Vana Date: Thu, 29 Dec 2022 13:30:27 +0100 Subject: [PATCH 201/387] Fix spacing --- contrib/generate_precompile.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index baed8884d56268..811764ed8dbba4 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -259,11 +259,11 @@ let function print_status(key::String) txt = status[key] if startswith(txt, "W") # Waiting - printstyled("?", color=Base.warn_color()); print(txt[2:end]) + printstyled("? ", color=Base.warn_color()); print(txt[2:end]) elseif startswith(txt, "R") # Running - printstyled(status["clock"], color=:magenta); print(txt[2:end]) + printstyled(status["clock"], " ", color=:magenta); print(txt[2:end]) elseif startswith(txt, "F") # Finished - printstyled("✓", color=:green); print(txt[2:end]) + printstyled("✓ ", color=:green); print(txt[2:end]) else print(txt) end From 5c1b99a5c33476443160e32463a916fb8f502543 Mon Sep 17 00:00:00 2001 From: Petr Vana Date: Thu, 29 Dec 2022 13:41:49 +0100 Subject: [PATCH 202/387] Improve wording based on the review --- contrib/generate_precompile.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index 811764ed8dbba4..4aace615924272 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -271,7 +271,7 @@ let function print_state(args::Pair{String,String}...) lock(print_lk) do isempty(args) || push!(status, args...) - print("\rCollect (Basic: ") + print("\r└ Collect (Basic: ") print_status("step1") print(", REPL ", status["repl"], ": ") print_status("step2") @@ -295,7 +295,7 @@ function generate_precompile_statements() push!(statements_step1, statement) end - println("Collect and execute precompile statements") + println("Collecting and executing precompile statements") print_state() clock = @async begin t = Timer(0; interval=1/10) From f0daa0d0e4f0470ddd322840cff9441b18b6ab71 Mon Sep 17 00:00:00 2001 From: Petr Vana Date: Thu, 29 Dec 2022 14:39:13 +0100 Subject: [PATCH 203/387] Detect if stdout is TTY or CI --- contrib/generate_precompile.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index 4aace615924272..2f8251642abea0 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -242,8 +242,8 @@ const HELP_PROMPT = "help?> " # You can disable parallel precompiles generation by setting `false` const PARALLEL_PRECOMPILATION = true -# TODO use can_fancyprint(io) from Pkg -const fancyprint = true +# You can disable fancy printing +const fancyprint = (io isa Base.TTY) && (get(ENV, "CI", nothing) != "true") # Printing the current state let From 86ae9e9583ccc9efed03e53310e313c108314228 Mon Sep 17 00:00:00 2001 From: Petr Vana Date: Thu, 29 Dec 2022 14:50:54 +0100 Subject: [PATCH 204/387] Fix to stdout --- contrib/generate_precompile.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index 2f8251642abea0..85b51e1b39adee 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -243,7 +243,7 @@ const HELP_PROMPT = "help?> " const PARALLEL_PRECOMPILATION = true # You can disable fancy printing -const fancyprint = (io isa Base.TTY) && (get(ENV, "CI", nothing) != "true") +const fancyprint = (stdout isa Base.TTY) && (get(ENV, "CI", nothing) != "true") # Printing the current state let From 338b5627d007e4163897597a7096af8581bb5147 Mon Sep 17 00:00:00 2001 From: Petr Vana Date: Thu, 29 Dec 2022 15:10:35 +0100 Subject: [PATCH 205/387] White spinner + disable cursor --- contrib/generate_precompile.jl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index 85b51e1b39adee..6ec4c282e6168b 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -261,7 +261,7 @@ let if startswith(txt, "W") # Waiting printstyled("? ", color=Base.warn_color()); print(txt[2:end]) elseif startswith(txt, "R") # Running - printstyled(status["clock"], " ", color=:magenta); print(txt[2:end]) + print(status["clock"], " ", txt[2:end]) elseif startswith(txt, "F") # Finished printstyled("✓ ", color=:green); print(txt[2:end]) else @@ -296,6 +296,11 @@ function generate_precompile_statements() end println("Collecting and executing precompile statements") + + ansi_enablecursor = "\e[?25h" + ansi_disablecursor = "\e[?25l" + + fancyprint && print(ansi_disablecursor) print_state() clock = @async begin t = Timer(0; interval=1/10) @@ -488,6 +493,7 @@ function generate_precompile_statements() wait(clock) # Stop asynchronous printing failed = length(statements) - n_succeeded print_state("step3" => "F$n_succeeded ($failed failed)") + fancyprint && print(ansi_enablecursor) println() if have_repl # Seems like a reasonable number right now, adjust as needed From f1ec0cd4199552397c8f678133c95fc9a17f2a96 Mon Sep 17 00:00:00 2001 From: Petr Vana Date: Thu, 29 Dec 2022 16:08:33 +0100 Subject: [PATCH 206/387] Use try-catch not to loose cursor --- contrib/generate_precompile.jl | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index 6ec4c282e6168b..c728e62c61bdfe 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -281,7 +281,10 @@ let end end -function generate_precompile_statements() +ansi_enablecursor = "\e[?25h" +ansi_disablecursor = "\e[?25l" + +generate_precompile_statements() = try # Make sure `ansi_enablecursor` is printed start_time = time_ns() debug_output = devnull # or stdout sysimg = Base.unsafe_string(Base.JLOptions().image_file) @@ -296,10 +299,6 @@ function generate_precompile_statements() end println("Collecting and executing precompile statements") - - ansi_enablecursor = "\e[?25h" - ansi_disablecursor = "\e[?25l" - fancyprint && print(ansi_disablecursor) print_state() clock = @async begin @@ -493,7 +492,6 @@ function generate_precompile_statements() wait(clock) # Stop asynchronous printing failed = length(statements) - n_succeeded print_state("step3" => "F$n_succeeded ($failed failed)") - fancyprint && print(ansi_enablecursor) println() if have_repl # Seems like a reasonable number right now, adjust as needed @@ -507,7 +505,10 @@ function generate_precompile_statements() tot_time = time_ns() - start_time println("Precompilation complete. Summary:") print("Total ─────── "); Base.time_print(tot_time); println() - +catch e + rethrow() +finally + fancyprint && print(ansi_enablecursor) return end From 8c5e490fa35f96c883cf293c5bd9e64f3119734f Mon Sep 17 00:00:00 2001 From: Petr Vana Date: Thu, 29 Dec 2022 16:30:27 +0100 Subject: [PATCH 207/387] Remove redundant chatch --- contrib/generate_precompile.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index c728e62c61bdfe..de79fac756420f 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -505,8 +505,6 @@ generate_precompile_statements() = try # Make sure `ansi_enablecursor` is printe tot_time = time_ns() - start_time println("Precompilation complete. Summary:") print("Total ─────── "); Base.time_print(tot_time); println() -catch e - rethrow() finally fancyprint && print(ansi_enablecursor) return From a1ee9af134685c54e7b4fab1d18c475b6ec284d7 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Thu, 29 Dec 2022 12:27:58 -0500 Subject: [PATCH 208/387] don't report skipped comments as failures (and only show failed when > 0) --- contrib/generate_precompile.jl | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index de79fac756420f..89bb1d3bb1c061 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -465,7 +465,12 @@ generate_precompile_statements() = try # Make sure `ansi_enablecursor` is printe occursin(", Core.Compiler.AbstractInterpreter, ", statement) && continue try ps = Meta.parse(statement) - isexpr(ps, :call) || continue + if !isexpr(ps, :call) + # these are typically comments + @debug "skipping statement because it does not parse as an expression" statement + delete!(statements, statement) + continue + end popfirst!(ps.args) # precompile(...) ps.head = :tuple l = ps.args[end] @@ -483,7 +488,7 @@ generate_precompile_statements() = try # Make sure `ansi_enablecursor` is printe n_succeeded += 1 failed = length(statements) - n_succeeded yield() # Make clock spinning - print_state("step3" => "R$n_succeeded ($failed failed)") + print_state("step3" => string("R$n_succeeded", failed > 0 ? " ($failed failed)" : "")) catch ex # See #28808 @warn "Failed to precompile expression" form=statement exception=ex _module=nothing _file=nothing _line=0 @@ -491,7 +496,7 @@ generate_precompile_statements() = try # Make sure `ansi_enablecursor` is printe end wait(clock) # Stop asynchronous printing failed = length(statements) - n_succeeded - print_state("step3" => "F$n_succeeded ($failed failed)") + print_state("step3" => string("F$n_succeeded", failed > 0 ? " ($failed failed)" : "")) println() if have_repl # Seems like a reasonable number right now, adjust as needed From 6740224b94a28dab629767ccfb8fe55e415361bd Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Thu, 29 Dec 2022 13:13:15 -0500 Subject: [PATCH 209/387] Stop renumbering statements in inference proper (#48022) I don't think there's any good reason to try to delete the statements here. The very next thing we do is to convert to IRCode which drops dead code anyway, so this just seems redundant. In addition, it complicates Cthulhu-like analysis, which now has to track an extra set of statement numbers. --- base/compiler/ssair/irinterp.jl | 2 ++ base/compiler/ssair/slot2ssa.jl | 8 +++++--- base/compiler/typeinfer.jl | 24 ++++-------------------- 3 files changed, 11 insertions(+), 23 deletions(-) diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index a2a5deccba838c..717a4eec102c23 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -251,6 +251,8 @@ function reprocess_instruction!(interp::AbstractInterpreter, return false elseif isa(inst, PiNode) rt = tmeet(typeinf_lattice(interp), argextype(inst.val, ir), widenconst(inst.typ)) + elseif inst === nothing + return false else ccall(:jl_, Cvoid, (Any,), inst) error() diff --git a/base/compiler/ssair/slot2ssa.jl b/base/compiler/ssair/slot2ssa.jl index 62795cb7e3fd0b..79bdf817dc8667 100644 --- a/base/compiler/ssair/slot2ssa.jl +++ b/base/compiler/ssair/slot2ssa.jl @@ -338,7 +338,9 @@ function iterated_dominance_frontier(cfg::CFG, liveness::BlockLiveness, domtree: end function rename_incoming_edge(old_edge::Int, old_to::Int, result_order::Vector{Int}, bb_rename::Vector{Int}) + old_edge == 0 && return 0 new_edge_from = bb_rename[old_edge] + new_edge_from < 0 && return new_edge_from if old_edge == old_to - 1 # Could have been a crit edge break if new_edge_from < length(result_order) && result_order[new_edge_from + 1] == 0 @@ -364,7 +366,7 @@ function rename_phinode_edges(node::PhiNode, bb::Int, result_order::Vector{Int}, new_edges = Int32[] for (idx, edge) in pairs(node.edges) edge = Int(edge) - (edge == 0 || bb_rename[edge] != 0) || continue + (edge == 0 || bb_rename[edge] != -1) || continue new_edge_from = edge == 0 ? 0 : rename_incoming_edge(edge, bb, result_order, bb_rename) push!(new_edges, new_edge_from) if isassigned(node.values, idx) @@ -387,7 +389,7 @@ function domsort_ssa!(ir::IRCode, domtree::DomTree) # First compute the new order of basic blocks result_order = Int[] stack = Int[] - bb_rename = zeros(Int, length(ir.cfg.blocks)) + bb_rename = fill(-1, length(ir.cfg.blocks)) node = 1 ncritbreaks = 0 nnewfallthroughs = 0 @@ -498,7 +500,7 @@ function domsort_ssa!(ir::IRCode, domtree::DomTree) bb_start_off += length(inst_range) local new_preds, new_succs let bb = bb, bb_rename = bb_rename, result_order = result_order - new_preds = Int[i == 0 ? 0 : rename_incoming_edge(i, bb, result_order, bb_rename) for i in ir.cfg.blocks[bb].preds] + new_preds = Int[bb for bb in (rename_incoming_edge(i, bb, result_order, bb_rename) for i in ir.cfg.blocks[bb].preds) if bb != -1] new_succs = Int[ rename_outgoing_edge(i, bb, result_order, bb_rename) for i in ir.cfg.blocks[bb].succs] end new_bbs[new_bb] = BasicBlock(inst_range, new_preds, new_succs) diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 9fcb7ea539d224..d9443045e9e89e 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -556,8 +556,7 @@ function finish(me::InferenceState, interp::AbstractInterpreter) # annotate fulltree with type information, # either because we are the outermost code, or we might use this later doopt = (me.cached || me.parent !== nothing) - changemap = type_annotate!(interp, me, doopt) - recompute_cfg = changemap !== nothing + recompute_cfg = type_annotate!(interp, me, doopt) if doopt && may_optimize(interp) me.result.src = OptimizationState(me, OptimizationParams(interp), interp, recompute_cfg) else @@ -715,6 +714,7 @@ function type_annotate!(interp::AbstractInterpreter, sv::InferenceState, run_opt slotflags = src.slotflags nslots = length(slotflags) undefs = fill(false, nslots) + any_unreachable = false # this statement traversal does five things: # 1. introduce temporary `TypedSlot`s that are supposed to be replaced with π-nodes later @@ -742,13 +742,9 @@ function type_annotate!(interp::AbstractInterpreter, sv::InferenceState, run_opt body[i] = annotate_slot_load!(undefs, i, sv, expr) # 1&2 ssavaluetypes[i] = widenslotwrapper(ssavaluetypes[i]) # 4 else # i.e. any runtime execution will never reach this statement + any_unreachable = true if is_meta_expr(expr) # keep any lexically scoped expressions ssavaluetypes[i] = Any # 4 - elseif run_optimizer - if changemap === nothing - changemap = fill(0, nexpr) - end - changemap[i] = -1 # 3&4: mark for the bulk deletion else ssavaluetypes[i] = Bottom # 4 body[i] = Const(expr) # annotate that this statement actually is dead @@ -763,19 +759,7 @@ function type_annotate!(interp::AbstractInterpreter, sv::InferenceState, run_opt end end - # do the bulk deletion of unreached statements - if changemap !== nothing - inds = Int[i for (i,v) in enumerate(changemap) if v == -1] - deleteat!(body, inds) - deleteat!(ssavaluetypes, inds) - deleteat!(codelocs, inds) - deleteat!(stmt_info, inds) - deleteat!(ssaflags, inds) - renumber_ir_elements!(body, changemap) - return changemap - else - return nothing - end + return any_unreachable end # at the end, all items in b's cycle From 4d206c97c78a1abdd53f0108e17d243f0e4dadff Mon Sep 17 00:00:00 2001 From: Jeremie Knuesel Date: Fri, 30 Dec 2022 16:07:56 +0100 Subject: [PATCH 210/387] Fix typo in ComposedFunction docstring (#48044) --- base/operators.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/operators.jl b/base/operators.jl index 2cc36ba83c9c57..9922305e14a3e9 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -997,7 +997,7 @@ Represents the composition of two callable objects `outer::Outer` and `inner::In ```julia ComposedFunction(outer, inner)(args...; kw...) === outer(inner(args...; kw...)) ``` -The preferred way to construct instance of `ComposedFunction` is to use the composition operator [`∘`](@ref): +The preferred way to construct an instance of `ComposedFunction` is to use the composition operator [`∘`](@ref): ```jldoctest julia> sin ∘ cos === ComposedFunction(sin, cos) true From cc5bc87b005ee193bd22b2907d591f120371a26b Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Fri, 30 Dec 2022 23:38:25 +0100 Subject: [PATCH 211/387] Adjust assertion for pkgimage & code-coverage (#48036) --- src/coverage.cpp | 2 +- src/jitlayers.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coverage.cpp b/src/coverage.cpp index 0dfb903798bfac..2be064726b1fe9 100644 --- a/src/coverage.cpp +++ b/src/coverage.cpp @@ -17,7 +17,7 @@ using namespace llvm; static int codegen_imaging_mode(void) { - return jl_options.image_codegen || jl_generating_output(); + return jl_options.image_codegen || (jl_generating_output() && jl_options.use_pkgimages); } // Logging for code coverage and memory allocation diff --git a/src/jitlayers.h b/src/jitlayers.h index 8edeec8929014c..f2a6c284649a4a 100644 --- a/src/jitlayers.h +++ b/src/jitlayers.h @@ -73,7 +73,7 @@ GlobalVariable *jl_emit_RTLD_DEFAULT_var(Module *M); DataLayout jl_create_datalayout(TargetMachine &TM); static inline bool imaging_default() { - return jl_options.image_codegen || jl_generating_output(); + return jl_options.image_codegen || (jl_generating_output() && jl_options.use_pkgimages); } struct OptimizationOptions { From d020702dbbc627abfdf0b880dfbd185b0904715a Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sat, 31 Dec 2022 10:56:12 -0500 Subject: [PATCH 212/387] Add missing Makefile dependency link (#48052) --- src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index 11d5afa963a8cf..65987ddf53fa4d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -319,7 +319,7 @@ $(BUILDDIR)/llvm-remove-addrspaces.o $(BUILDDIR)/llvm-remove-addrspaces.dbg.obj: $(BUILDDIR)/llvm-ptls.o $(BUILDDIR)/llvm-ptls.dbg.obj: $(SRCDIR)/codegen_shared.h $(BUILDDIR)/processor.o $(BUILDDIR)/processor.dbg.obj: $(addprefix $(SRCDIR)/,processor_*.cpp processor.h features_*.h) $(BUILDDIR)/signal-handling.o $(BUILDDIR)/signal-handling.dbg.obj: $(addprefix $(SRCDIR)/,signals-*.c) -$(BUILDDIR)/staticdata.o $(BUILDDIR)/staticdata.dbg.obj: $(SRCDIR)/staticdata_utils.c $(SRCDIR)/processor.h $(SRCDIR)/builtin_proto.h +$(BUILDDIR)/staticdata.o $(BUILDDIR)/staticdata.dbg.obj: $(SRCDIR)/staticdata_utils.c $(SRCDIR)/precompile_utils.c $(SRCDIR)/processor.h $(SRCDIR)/builtin_proto.h $(BUILDDIR)/toplevel.o $(BUILDDIR)/toplevel.dbg.obj: $(SRCDIR)/builtin_proto.h $(BUILDDIR)/ircode.o $(BUILDDIR)/ircode.dbg.obj: $(SRCDIR)/serialize.h $(BUILDDIR)/pipeline.o $(BUILDDIR)/pipeline.dbg.obj: $(SRCDIR)/passes.h $(SRCDIR)/jitlayers.h From e5d044010ce0210796fce6823925286e94fa08a5 Mon Sep 17 00:00:00 2001 From: Shawn LeMaster Date: Sat, 31 Dec 2022 10:18:03 -0600 Subject: [PATCH 213/387] isassigned: check bounds on multidimensional indexes (#48003) * isassigned: check bounds on multidimensional indexes * isassigned: remove isempty check This check was added in the previous commit to preserve existing behavior when calling isassigned with an empty splat for the indexes. But it turns out different version of isassigned treat this case differently (e.g. AbstractArray vs Array). So the check is being removed to make isassigned behavior consistent and more in line with checkbounds and expectations of the [] operator. --- base/array.jl | 2 +- test/core.jl | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/base/array.jl b/base/array.jl index 5257caabf2d454..694a3913cacf46 100644 --- a/base/array.jl +++ b/base/array.jl @@ -215,8 +215,8 @@ sizeof(a::Array) = Core.sizeof(a) function isassigned(a::Array, i::Int...) @inline + @boundscheck checkbounds(Bool, a, i...) || return false ii = (_sub2ind(size(a), i...) % UInt) - 1 - @boundscheck ii < length(a) % UInt || return false ccall(:jl_array_isassigned, Cint, (Any, UInt), a, ii) == 1 end diff --git a/test/core.jl b/test/core.jl index 96ec765235adb8..e6ad8705d4e2ba 100644 --- a/test/core.jl +++ b/test/core.jl @@ -780,11 +780,15 @@ let @test isassigned(a,1) && !isassigned(a,2) a = Vector{Float64}(undef,1) @test isassigned(a,1) + @test isassigned(a,1,1) @test isassigned(a) @test !isassigned(a,2) a = Array{Float64}(undef, 2, 2, 2) @test isassigned(a,1) - @test isassigned(a) + @test isassigned(a,8) + @test isassigned(a,2,2,2) + @test isassigned(a,2,2,2,1) + @test !isassigned(a) @test !isassigned(a,9) a = Array{Float64}(undef, 1) @test isassigned(a,1) @@ -792,8 +796,15 @@ let @test !isassigned(a,2) a = Array{Float64}(undef, 2, 2, 2, 2) @test isassigned(a,1) - @test isassigned(a) + @test isassigned(a,2,2,2,2) + @test isassigned(a,2,2,2,2,1) + @test isassigned(a,16) + @test !isassigned(a) @test !isassigned(a,17) + @test !isassigned(a,3,1,1,1) + @test !isassigned(a,1,3,1,1) + @test !isassigned(a,1,1,3,1) + @test !isassigned(a,1,1,1,3) end # isassigned, issue #11167 From 516b097bd705faf628a3346c39279a595ab633d3 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Sat, 31 Dec 2022 11:57:24 -0600 Subject: [PATCH 214/387] Remove unsafe initialized=true option in sortperm! (#47979) * remove unsafe initialized=true option in sortperm! and partialsortperm! * Update NEWS.md --- NEWS.md | 3 ++- base/sort.jl | 34 +++++++++++----------------------- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/NEWS.md b/NEWS.md index 6767ae24ec373c..d3b9f618ddceef 100644 --- a/NEWS.md +++ b/NEWS.md @@ -30,7 +30,8 @@ New library functions New library features -------------------- - +The `initialized=true` keyword assignment for `sortperm!` and `partialsortperm!` +is now a no-op ([#47979]). It previously exposed unsafe behavior ([#47977]). Standard library changes ------------------------ diff --git a/base/sort.jl b/base/sort.jl index 669d2d97b2ac1e..043322dd25b7bd 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -1433,25 +1433,18 @@ julia> v[p] ``` """ partialsortperm(v::AbstractVector, k::Union{Integer,OrdinalRange}; kwargs...) = - partialsortperm!(similar(Vector{eltype(k)}, axes(v,1)), v, k; kwargs..., initialized=false) + partialsortperm!(similar(Vector{eltype(k)}, axes(v,1)), v, k; kwargs...) """ - partialsortperm!(ix, v, k; by=, lt=, rev=false, initialized=false) + partialsortperm!(ix, v, k; by=, lt=, rev=false) Like [`partialsortperm`](@ref), but accepts a preallocated index vector `ix` the same size as `v`, which is used to store (a permutation of) the indices of `v`. -If the index vector `ix` is initialized with the indices of `v` (or a permutation thereof), `initialized` should be set to -`true`. - -If `initialized` is `false` (the default), then `ix` is initialized to contain the indices of `v`. - -If `initialized` is `true`, but `ix` does not contain (a permutation of) the indices of `v`, the behavior of -`partialsortperm!` is undefined. +`ix` is initialized to contain the indices of `v`. (Typically, the indices of `v` will be `1:length(v)`, although if `v` has an alternative array type -with non-one-based indices, such as an `OffsetArray`, `ix` must also be an `OffsetArray` with the same -indices, and must contain as values (a permutation of) these same indices.) +with non-one-based indices, such as an `OffsetArray`, `ix` must share those same indices) Upon return, `ix` is guaranteed to have the indices `k` in their sorted positions, such that @@ -1474,7 +1467,7 @@ julia> partialsortperm!(ix, v, 1) julia> ix = [1:4;]; -julia> partialsortperm!(ix, v, 2:3, initialized=true) +julia> partialsortperm!(ix, v, 2:3) 2-element view(::Vector{Int64}, 2:3) with eltype Int64: 4 3 @@ -1491,10 +1484,8 @@ function partialsortperm!(ix::AbstractVector{<:Integer}, v::AbstractVector, throw(ArgumentError("The index vector is used as scratch space and must have the " * "same length/indices as the source vector, $(axes(ix,1)) != $(axes(v,1))")) end - if !initialized - @inbounds for i in eachindex(ix) - ix[i] = i - end + @inbounds for i in eachindex(ix) + ix[i] = i end # do partial quicksort @@ -1584,10 +1575,10 @@ end """ - sortperm!(ix, A; alg::Algorithm=DEFAULT_UNSTABLE, lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward, initialized::Bool=false, [dims::Integer]) + sortperm!(ix, A; alg::Algorithm=DEFAULT_UNSTABLE, lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward, [dims::Integer]) -Like [`sortperm`](@ref), but accepts a preallocated index vector or array `ix` with the same `axes` as `A`. If `initialized` is `false` -(the default), `ix` is initialized to contain the values `LinearIndices(A)`. +Like [`sortperm`](@ref), but accepts a preallocated index vector or array `ix` with the same `axes` as `A`. +`ix` is initialized to contain the values `LinearIndices(A)`. !!! compat "Julia 1.9" The method accepting `dims` requires at least Julia 1.9. @@ -1633,10 +1624,7 @@ julia> sortperm!(p, A; dims=2); p (typeof(A) <: AbstractVector) == (:dims in keys(dims)) && throw(ArgumentError("Dims argument incorrect for type $(typeof(A))")) axes(ix) == axes(A) || throw(ArgumentError("index array must have the same size/axes as the source array, $(axes(ix)) != $(axes(A))")) - if !initialized - ix .= LinearIndices(A) - end - + ix .= LinearIndices(A) if rev === true sort!(ix; alg, order=Perm(ord(lt, by, true, order), vec(A)), scratch, dims...) else From adf755801f024e326b8aace76115b38debbfd5f0 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sat, 31 Dec 2022 20:26:34 -0500 Subject: [PATCH 215/387] Pass down lattice in `ifelse_tfunc` (#48065) --- base/compiler/tfuncs.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index ad24636add1a49..eb06657dde1f7f 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -301,7 +301,7 @@ add_tfunc(Core.Intrinsics.arraylen, 1, 1, @nospecs((𝕃::AbstractLattice, x)->I elseif !hasintersect(widenconst(cnd), Bool) return Bottom end - return tmerge(x, y) + return tmerge(𝕃, x, y) end add_tfunc(Core.ifelse, 3, 3, ifelse_tfunc, 1) From 266341e731e28adda0d41e953e0c461fb3a52053 Mon Sep 17 00:00:00 2001 From: Jeff Fessler Date: Sat, 31 Dec 2022 20:28:50 -0500 Subject: [PATCH 216/387] Make the `getproperty` example type stable (#48004) The example as written is not type stable, as one can verify with`@code_warntype`. Seems worth this small change to illustrate the preferable approach. On a more general note, I just recently figured out that `getproperty` is not type stable in general. (Simply replace the `obj.x + 1` with something like `obj.x / 2`.) Probably whoever reviews this PR will know that already but I wish that I had been warned long ago about that. I would recommend that a warning be put right here in these docs for it. I could draft the warning if there is openness to including it, but I figured that might be more debatable than the type annotation... Co-authored-by: Shuhei Kadowaki --- base/docs/basedocs.jl | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index 033d0fcb0ec5ef..bb00edbef41ebe 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -2850,8 +2850,8 @@ the syntax `@atomic a.b` calls `getproperty(a, :b, :sequentially_consistent)`. # Examples ```jldoctest -julia> struct MyType - x +julia> struct MyType{T <: Number} + x::T end julia> function Base.getproperty(obj::MyType, sym::Symbol) @@ -2871,6 +2871,11 @@ julia> obj.x 1 ``` +One should overload `getproperty` only when necessary, as it can be confusing if +the behavior of the syntax `obj.f` is unusual. +Also note that using methods is often preferable. See also this style guide documentation +for more information: [Prefer exported methods over direct field access](@ref). + See also [`getfield`](@ref Core.getfield), [`propertynames`](@ref Base.propertynames) and [`setproperty!`](@ref Base.setproperty!). From 47950049236473b59e12880c2446e45b78a281cb Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sat, 31 Dec 2022 20:38:19 -0500 Subject: [PATCH 217/387] Remove suplicated sqrt_fast in inconsistent intrinsics list (#48060) --- base/compiler/tfuncs.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index eb06657dde1f7f..d5d2591fd2b6a4 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -2131,7 +2131,6 @@ const _ARGMEM_BUILTINS = Any[ const _INCONSISTENT_INTRINSICS = Any[ Intrinsics.pointerref, # this one is volatile Intrinsics.arraylen, # this one is volatile - Intrinsics.sqrt_llvm_fast, # this one may differ at runtime (by a few ulps) Intrinsics.have_fma, # this one depends on the runtime environment Intrinsics.cglobal, # cglobal lookup answer changes at runtime # ... and list fastmath intrinsics: From 75bc5ee590dbf60ebd167669c05076ab7955fafa Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Sun, 1 Jan 2023 21:19:56 +0100 Subject: [PATCH 218/387] use the correct env variable name to set default openblas num threads (#48064) This was add to OpenBLAS in https://github.com/xianyi/OpenBLAS/pull/3773 and was supposed to be used in https://github.com/JuliaLang/julia/pull/46844/ but was likely typod --- stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl b/stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl index c57dd15bb19307..2684a6b635cb41 100644 --- a/stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl +++ b/stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl @@ -47,7 +47,7 @@ function __init__() !haskey(ENV, "OMP_NUM_THREADS") # We set this to `1` here, and then LinearAlgebra will update # to the true value in its `__init__()` function. - ENV["OPENBLAS_NUM_THREADS"] = "1" + ENV["OPENBLAS_DEFAULT_NUM_THREADS"] = "1" end global libopenblas_handle = dlopen(libopenblas) From b5b16fc4359edc70a19745fa7419469f638f8c48 Mon Sep 17 00:00:00 2001 From: Carsten Bauer Date: Mon, 2 Jan 2023 11:58:23 +0100 Subject: [PATCH 219/387] Fix documentation in non-toplevel blocks (#48016) --- doc/src/manual/documentation.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/src/manual/documentation.md b/doc/src/manual/documentation.md index 68bd1141000319..4c724e1deaaebe 100644 --- a/doc/src/manual/documentation.md +++ b/doc/src/manual/documentation.md @@ -310,18 +310,18 @@ end @doc "`subtract(a,b)` subtracts `b` from `a`" subtract ``` -Documentation written in non-toplevel blocks, such as `begin`, `if`, `for`, and `let`, is -added to the documentation system as blocks are evaluated. For example: +Documentation in non-toplevel blocks, such as `begin`, `if`, `for`, and `let`, should be +added to the documentation system via `@doc` as well. For example: ```julia if condition() - "..." + @doc "..." f(x) = x end ``` will add documentation to `f(x)` when `condition()` is `true`. Note that even if `f(x)` goes -out of scope at the end of the block, its documentation will remain. +out of scope at the end of a block, its documentation will remain. It is possible to make use of metaprogramming to assist in the creation of documentation. When using string-interpolation within the docstring you will need to use an extra `$` as From 8184b07c28fe50fa553a1a30fa9b6de5e2c89fd6 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Mon, 2 Jan 2023 05:17:45 -0600 Subject: [PATCH 220/387] turn a comment into a test in sorting tests (#47965) Co-authored-by: Lilith Hafner --- test/sorting.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/sorting.jl b/test/sorting.jl index c4e324a61cde9a..d909b30ee8646a 100644 --- a/test/sorting.jl +++ b/test/sorting.jl @@ -1083,7 +1083,7 @@ end @testset "issue #34408" begin r = 1f8-10:1f8 - # collect(r) = Float32[9.999999e7, 9.999999e7, 9.999999e7, 9.999999e7, 1.0e8, 1.0e8, 1.0e8, 1.0e8, 1.0e8] + @test collect(r) == Float32[9.999999e7, 9.999999e7, 9.999999e7, 9.999999e7, 1.0e8, 1.0e8, 1.0e8, 1.0e8, 1.0e8] for i in r @test_broken searchsorted(collect(r), i) == searchsorted(r, i) end From 16f4f05ac0ddb54c9a319fa8e72033886bec2448 Mon Sep 17 00:00:00 2001 From: Weijia Wang <9713184+wegank@users.noreply.github.com> Date: Mon, 2 Jan 2023 12:18:03 +0100 Subject: [PATCH 221/387] Disable further FileWatching tests (#47920) --- stdlib/FileWatching/test/runtests.jl | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/stdlib/FileWatching/test/runtests.jl b/stdlib/FileWatching/test/runtests.jl index 65e0433d559b08..dd5187ec524349 100644 --- a/stdlib/FileWatching/test/runtests.jl +++ b/stdlib/FileWatching/test/runtests.jl @@ -12,8 +12,6 @@ using Base: uv_error, Experimental # Odd numbered pipes are tested for reads # Even numbered pipes are tested for timeouts # Writable ends are always tested for write-ability before a write -ismacos_arm = ((Sys.ARCH == :aarch64) && (Sys.isapple())) #https://github.com/JuliaLang/julia/issues/46185 -ismacos_x86 = ((Sys.ARCH == :x86_64) && (Sys.isapple())) #Used to disable the unreliable macos tests n = 20 intvls = [2, .2, .1, .005, .00001] @@ -34,7 +32,7 @@ for i in 1:n if !fd_in_limits && Sys.islinux() run(`ls -la /proc/$(getpid())/fd`) end - if !ismacos_arm + if !Sys.isapple() @test fd_in_limits end end @@ -187,7 +185,7 @@ function test_init_afile() @test(watch_folder(dir) == (F_PATH => FileWatching.FileEvent(FileWatching.UV_RENAME))) @test close(open(file, "w")) === nothing sleep(3) - if !ismacos_x86 + if !Sys.isapple() let c c = watch_folder(dir, 0) @@ -377,7 +375,7 @@ test_monitor_wait_poll() test_watch_file_timeout(0.2) test_watch_file_change(6) -if !ismacos_x86 +if !Sys.isapple() test_dirmonitor_wait2(0.2) test_dirmonitor_wait2(0.2) From 7a561bd6f046de661741568e0b6eb6f1161bc0e6 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Mon, 2 Jan 2023 06:18:09 -0500 Subject: [PATCH 222/387] Hook up effect modeling for `TypeVar` and `UnionAll` (#48063) * Hook up effect modeling for `TypeVar` and `UnionAll` We already had effect modeling for `_typevar`, which `TypeVar` should just defer to. Add a simple model for `UnionAll` as well, though in the future we can add the Vararg case also. * Apply suggestions from code review Co-authored-by: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Co-authored-by: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> --- base/compiler/abstractinterpretation.jl | 23 ++++++++++++++--------- base/compiler/tfuncs.jl | 1 + 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index c3dfc0eeb22d2b..6cf40748ca6f35 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1834,35 +1834,38 @@ end function abstract_call_unionall(argtypes::Vector{Any}) if length(argtypes) == 3 canconst = true + a2 = argtypes[2] a3 = argtypes[3] + nothrow = a2 ⊑ TypeVar && ( + a3 ⊑ Type || + a3 ⊑ TypeVar) if isa(a3, Const) body = a3.val elseif isType(a3) body = a3.parameters[1] canconst = false else - return Any + return CallMeta(Any, Effects(EFFECTS_TOTAL; nothrow), NoCallInfo()) end if !isa(body, Type) && !isa(body, TypeVar) - return Any + return CallMeta(Any, EFFECTS_THROWS, NoCallInfo()) end if has_free_typevars(body) - a2 = argtypes[2] if isa(a2, Const) tv = a2.val elseif isa(a2, PartialTypeVar) tv = a2.tv canconst = false else - return Any + return CallMeta(Any, EFFECTS_THROWS, NoCallInfo()) end - !isa(tv, TypeVar) && return Any + !isa(tv, TypeVar) && return CallMeta(Any, EFFECTS_THROWS, NoCallInfo()) body = UnionAll(tv, body) end ret = canconst ? Const(body) : Type{body} - return ret + return CallMeta(ret, Effects(EFFECTS_TOTAL; nothrow), NoCallInfo()) end - return Any + return CallMeta(Any, EFFECTS_UNKNOWN, NoCallInfo()) end function abstract_invoke(interp::AbstractInterpreter, (; fargs, argtypes)::ArgInfo, si::StmtInfo, sv::InferenceState) @@ -1974,9 +1977,11 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), elseif la == 3 ub_var = argtypes[3] end - return CallMeta(typevar_tfunc(𝕃ᵢ, n, lb_var, ub_var), EFFECTS_UNKNOWN, NoCallInfo()) + pT = typevar_tfunc(𝕃ᵢ, n, lb_var, ub_var) + return CallMeta(pT, + builtin_effects(𝕃ᵢ, Core._typevar, Any[n, lb_var, ub_var], pT), NoCallInfo()) elseif f === UnionAll - return CallMeta(abstract_call_unionall(argtypes), EFFECTS_UNKNOWN, NoCallInfo()) + return abstract_call_unionall(argtypes) elseif f === Tuple && la == 2 aty = argtypes[2] ty = isvarargtype(aty) ? unwrapva(aty) : widenconst(aty) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index d5d2591fd2b6a4..1450be754a7bbb 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -2117,6 +2117,7 @@ const _INACCESSIBLEMEM_BUILTINS = Any[ typeassert, typeof, compilerbarrier, + Core._typevar ] const _ARGMEM_BUILTINS = Any[ From fe45f010dbf81fcefe1aaddcc0a1bfbc7af0c4be Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Mon, 2 Jan 2023 13:56:34 -0500 Subject: [PATCH 223/387] =?UTF-8?q?Refine=20LimitedAccuracy's=20=E2=8A=91?= =?UTF-8?q?=20semantics=20(#48045)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refine LimitedAccuracy's ⊑ semantics As discussed in #48030, this is a different attempt to fix the semantics of LimitedAccuracy. This fixes the same test case as #48030, but keeps `LimitedAccuracy` ε smaller than its wrapped lattice element. The primary change here is that now all lattice elements that are strictly `⊑ T` are now also `⊑ LimitedAccuracy(T)`, whereas before that was only true for other `LimitedAccuracy` elements. Quoting the still relevant parts of #48030's commit message: ``` I was investigating some suboptimal inference in Diffractor (which due to its recursive structure over the order of the taken derivative likes to tickle recursion limiting) and noticed that inference was performing some constant propagation, but then discarding the result. Upon further investigation, it turned out that inference had determined the function to be `LimitedAccuracy(...)`, but constprop found out it actually returned `Const`. Now, ordinarily, we don't constprop functions that inference determined to be `LimitedAccuracy`, but this function happened to have `@constprop :aggressive` annotated. Of course, if constprop determines that the function actually terminates, we do want to use that information. We could hardcode this in abstract_call_gf_by_type, but it made me take a closer look at the lattice operations for `LimitedAccuracy`, since in theory `abstract_call_gf_by_type` should prefer a more precise result. ``` * Apply suggestions from code review Co-authored-by: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Co-authored-by: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> --- base/compiler/abstractinterpretation.jl | 8 ++- base/compiler/typelattice.jl | 77 +++++++++++++++----- base/compiler/typelimits.jl | 94 ++++++++++++++++++++----- test/compiler/inference.jl | 8 +++ 4 files changed, 151 insertions(+), 36 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 6cf40748ca6f35..7ca6a19eaf9670 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -135,6 +135,8 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), if const_call_result.rt ⊑ₚ rt rt = const_call_result.rt (; effects, const_result, edge) = const_call_result + else + add_remark!(interp, sv, "[constprop] Discarded because the result was wider than inference") end end all_effects = merge_effects(all_effects, effects) @@ -169,6 +171,8 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), this_conditional = this_const_conditional this_rt = this_const_rt (; effects, const_result, edge) = const_call_result + else + add_remark!(interp, sv, "[constprop] Discarded because the result was wider than inference") end end all_effects = merge_effects(all_effects, effects) @@ -535,6 +539,7 @@ end const RECURSION_UNUSED_MSG = "Bounded recursion detected with unused result. Annotated return type may be wider than true result." const RECURSION_MSG = "Bounded recursion detected. Call was widened to force convergence." +const RECURSION_MSG_HARDLIMIT = "Bounded recursion detected under hardlimit. Call was widened to force convergence." function abstract_call_method(interp::AbstractInterpreter, method::Method, @nospecialize(sig), sparams::SimpleVector, hardlimit::Bool, si::StmtInfo, sv::InferenceState) if method.name === :depwarn && isdefined(Main, :Base) && method.module === Main.Base @@ -573,6 +578,7 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp end end end + washardlimit = hardlimit if topmost !== nothing sigtuple = unwrap_unionall(sig)::DataType @@ -611,7 +617,7 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp # (non-typically, this means that we lose the ability to detect a guaranteed StackOverflow in some cases) return MethodCallResult(Any, true, true, nothing, Effects()) end - add_remark!(interp, sv, RECURSION_MSG) + add_remark!(interp, sv, washardlimit ? RECURSION_MSG_HARDLIMIT : RECURSION_MSG) topmost = topmost::InferenceState parentframe = topmost.parent poison_callstack(sv, parentframe === nothing ? topmost : parentframe) diff --git a/base/compiler/typelattice.jl b/base/compiler/typelattice.jl index c8429d667b43c4..5439ec8654f31e 100644 --- a/base/compiler/typelattice.jl +++ b/base/compiler/typelattice.jl @@ -171,9 +171,44 @@ struct StateUpdate conditional::Bool end -# Represent that the type estimate has been approximated, due to "causes" -# (only used in abstract interpretation, doesn't appear in optimization) -# N.B. in the lattice, this is epsilon smaller than `typ` (except Union{}) +""" + struct LimitedAccuracy + +A `LimitedAccuracy` lattice element is used to indicate that the true inference +result was approximate due to heuristic termination of a recursion. For example, +consider two call stacks starting from `A` and `B` that look like: + + A -> C -> A -> D + B -> C -> A -> D + +In the first case, inference may have decided that `A->C->A` constitutes a cycle, +widening the result it obtained for `C`, even if it might otherwise have been +able to obtain a result. In this case, the result inferred for `C` will be +annotated with this lattice type to indicate that the obtained result is an +upper bound for the non-limited inference. In particular, this means that the +call stack originating at `B` will re-perform inference without being poisoned +by the potentially inaccurate result obtained during the inference of `A`. + +N.B.: We do *not* take any efforts to ensure the reverse. For example, if `B` +is inferred first, then we may cache a precise result for `C` and re-use this +result while inferring `A`, even if inference of `A` would have not been able +to obtain this result due to limiting. This is undesirable, because it makes +some inference results order dependent, but there it is unclear how this situation +could be avoided. + +A `LimitedAccuracy` element wraps another lattice element (let's call it `T`) +and additionally tracks the `causes` due to which limitation occurred. As a +lattice element, `LimitedAccuracy(T)` is considered ε smaller than the +corresponding lattice element `T`, but in particular, all lattice elements that +are `⊑ T` (but not equal `T`) are also `⊑ LimitedAccuracy(T)`. + +The `causes` list is used to determine whether a particular cause of limitation is +inevitable and if so, widening `LimitedAccuracy(T)` back to `T`. For example, +in the call stacks above, if any call to `A` always leads back to `A`, then +it does not matter whether we start at `A` or reach it via `B`: Any inference +that reaches `A` will always hit the same limitation and the result may thus +be cached. +""" struct LimitedAccuracy typ causes::IdSet{InferenceState} @@ -182,6 +217,7 @@ struct LimitedAccuracy return new(typ, causes) end end +LimitedAccuracy(@nospecialize(T), ::Nothing) = T """ struct NotFound end @@ -366,17 +402,22 @@ ignorelimited(typ::LimitedAccuracy) = typ.typ # ============= function ⊑(lattice::InferenceLattice, @nospecialize(a), @nospecialize(b)) - if isa(b, LimitedAccuracy) - if !isa(a, LimitedAccuracy) - return false - end - if b.causes ⊈ a.causes - return false - end - b = b.typ + r = ⊑(widenlattice(lattice), ignorelimited(a), ignorelimited(b)) + r || return false + isa(b, LimitedAccuracy) || return true + + # We've found that ignorelimited(a) ⊑ ignorelimited(b). + # Now perform the reverse query to check for equality. + ab_eq = ⊑(widenlattice(lattice), b.typ, ignorelimited(a)) + + if !ab_eq + # a's unlimited type is strictly smaller than b's + return true end - isa(a, LimitedAccuracy) && (a = a.typ) - return ⊑(widenlattice(lattice), a, b) + + # a and b's unlimited types are equal. + isa(a, LimitedAccuracy) || return false # b is limited, so ε smaller + return a.causes ⊆ b.causes end function ⊑(lattice::OptimizerLattice, @nospecialize(a), @nospecialize(b)) @@ -508,9 +549,13 @@ function ⊑(lattice::ConstsLattice, @nospecialize(a), @nospecialize(b)) end function is_lattice_equal(lattice::InferenceLattice, @nospecialize(a), @nospecialize(b)) - if isa(a, LimitedAccuracy) || isa(b, LimitedAccuracy) - # TODO: Unwrap these and recurse to is_lattice_equal - return ⊑(lattice, a, b) && ⊑(lattice, b, a) + if isa(a, LimitedAccuracy) + isa(b, LimitedAccuracy) || return false + a.causes == b.causes || return false + a = a.typ + b = b.typ + elseif isa(b, LimitedAccuracy) + return false end return is_lattice_equal(widenlattice(lattice), a, b) end diff --git a/base/compiler/typelimits.jl b/base/compiler/typelimits.jl index 02a734cd2d5cfa..c09cf4e5d0f91b 100644 --- a/base/compiler/typelimits.jl +++ b/base/compiler/typelimits.jl @@ -304,8 +304,6 @@ end # A simplified type_more_complex query over the extended lattice # (assumes typeb ⊑ typea) function issimplertype(𝕃::AbstractLattice, @nospecialize(typea), @nospecialize(typeb)) - typea = ignorelimited(typea) - typeb = ignorelimited(typeb) typea isa MaybeUndef && (typea = typea.typ) # n.b. does not appear in inference typeb isa MaybeUndef && (typeb = typeb.typ) # n.b. does not appear in inference typea === typeb && return true @@ -385,29 +383,87 @@ function tmerge(lattice::OptimizerLattice, @nospecialize(typea), @nospecialize(t return tmerge(widenlattice(lattice), typea, typeb) end -function tmerge(lattice::InferenceLattice, @nospecialize(typea), @nospecialize(typeb)) - r = tmerge_fast_path(lattice, typea, typeb) - r !== nothing && return r +function union_causes(causesa::IdSet{InferenceState}, causesb::IdSet{InferenceState}) + if causesa ⊆ causesb + return causesb + elseif causesb ⊆ causesa + return causesa + else + return union!(copy(causesa), causesb) + end +end + +function merge_causes(causesa::IdSet{InferenceState}, causesb::IdSet{InferenceState}) + # TODO: When lattice elements are equal, we're allowed to discard one or the + # other set, but we'll need to come up with a consistent rule. For now, we + # just check the length, but other heuristics may be applicable. + if length(causesa) < length(causesb) + return causesa + elseif length(causesb) < length(causesa) + return causesb + else + return union!(copy(causesa), causesb) + end +end + +@noinline function tmerge_limited(lattice::InferenceLattice, @nospecialize(typea), @nospecialize(typeb)) + typea === Union{} && return typeb + typeb === Union{} && return typea - # type-lattice for LimitedAccuracy wrapper - # the merge create a slightly narrower type than needed, but we can't - # represent the precise intersection of causes and don't attempt to - # enumerate some of these cases where we could + # Like tmerge_fast_path, but tracking which causes need to be preserved at + # the same time. if isa(typea, LimitedAccuracy) && isa(typeb, LimitedAccuracy) - if typea.causes ⊆ typeb.causes - causes = typeb.causes - elseif typeb.causes ⊆ typea.causes - causes = typea.causes + causesa = typea.causes + causesb = typeb.causes + typea = typea.typ + typeb = typeb.typ + suba = ⊑(lattice, typea, typeb) + subb = ⊑(lattice, typeb, typea) + + # Approximated types are lattice equal. Merge causes. + if suba && subb + causes = merge_causes(causesa, causesb) + issimplertype(lattice, typeb, typea) && return LimitedAccuracy(typeb, causesb) + elseif suba + issimplertype(lattice, typeb, typea) && return LimitedAccuracy(typeb, causesb) + causes = causesb + # `a`'s causes may be discarded + elseif subb + causes = causesa else - causes = union!(copy(typea.causes), typeb.causes) + causes = union_causes(causesa, causesb) + end + else + if isa(typeb, LimitedAccuracy) + (typea, typeb) = (typeb, typea) + end + typea = typea::LimitedAccuracy + + causes = typea.causes + typea = typea.typ + + suba = ⊑(lattice, typea, typeb) + if suba + issimplertype(lattice, typeb, typea) && return typeb + # `typea` was narrower than `typeb`. Whatever tmerge produces, + # we know it must be wider than `typeb`, so we may drop the + # causes. + causes = nothing end - return LimitedAccuracy(tmerge(widenlattice(lattice), typea.typ, typeb.typ), causes) - elseif isa(typea, LimitedAccuracy) - return LimitedAccuracy(tmerge(widenlattice(lattice), typea.typ, typeb), typea.causes) - elseif isa(typeb, LimitedAccuracy) - return LimitedAccuracy(tmerge(widenlattice(lattice), typea, typeb.typ), typeb.causes) + subb = ⊑(lattice, typeb, typea) end + subb && issimplertype(lattice, typea, typeb) && return LimitedAccuracy(typea, causes) + return LimitedAccuracy(tmerge(widenlattice(lattice), typea, typeb), causes) +end + +function tmerge(lattice::InferenceLattice, @nospecialize(typea), @nospecialize(typeb)) + if isa(typea, LimitedAccuracy) || isa(typeb, LimitedAccuracy) + return tmerge_limited(lattice, typea, typeb) + end + + r = tmerge_fast_path(widenlattice(lattice), typea, typeb) + r !== nothing && return r return tmerge(widenlattice(lattice), typea, typeb) end diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index b610b00f8462d8..5adc1a0dc6c4c3 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -4701,3 +4701,11 @@ let # jl_widen_core_extended_info widened end end + +# This is somewhat sensitive to the exact recursion level that inference is willing to do, but the intention +# is to test the case where inference limited a recursion, but then a forced constprop nevertheless managed +# to terminate the call. +@Base.constprop :aggressive type_level_recurse1(x...) = x[1] == 2 ? 1 : (length(x) > 100 ? x : type_level_recurse2(x[1] + 1, x..., x...)) +@Base.constprop :aggressive type_level_recurse2(x...) = type_level_recurse1(x...) +type_level_recurse_entry() = Val{type_level_recurse1(1)}() +@test Base.return_types(type_level_recurse_entry, ()) |> only == Val{1} From 79e29e3cb9ef34f7ab613657c0f480d81b1402b7 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Mon, 2 Jan 2023 23:17:19 +0100 Subject: [PATCH 224/387] use invokelatest to prevent invalidations in TOML (#48083) --- stdlib/TOML/src/print.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/TOML/src/print.jl b/stdlib/TOML/src/print.jl index 74efdfc97a05d5..61d13a8f4853ef 100644 --- a/stdlib/TOML/src/print.jl +++ b/stdlib/TOML/src/print.jl @@ -126,7 +126,7 @@ is_array_of_tables(value) = isa(value, AbstractArray) && isa(value, AbstractArray{<:AbstractDict}) || all(v -> isa(v, AbstractDict), value) ) -is_tabular(value) = is_table(value) || is_array_of_tables(value) +is_tabular(value) = is_table(value) || @invokelatest(is_array_of_tables(value)) function print_table(f::MbyFunc, io::IO, a::AbstractDict, ks::Vector{String} = String[]; @@ -176,7 +176,7 @@ function print_table(f::MbyFunc, io::IO, a::AbstractDict, # Use runtime dispatch here since the type of value seems not to be enforced other than as AbstractDict @invokelatest print_table(f, io, value, ks; indent = indent + header, first_block = header, sorted=sorted, by=by) pop!(ks) - elseif is_array_of_tables(value) + elseif @invokelatest(is_array_of_tables(value)) # print array of tables first_block || println(io) first_block = false From e3c62cb5df3331e7488132081972476cff7373df Mon Sep 17 00:00:00 2001 From: N5N3 <2642243996@qq.com> Date: Wed, 28 Dec 2022 20:15:22 +0800 Subject: [PATCH 225/387] Make sure `env` is restored between 2 adjacent `subtype_in_env_existential` --- src/subtype.c | 13 ++++++++++--- test/subtype.jl | 14 ++++++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/subtype.c b/src/subtype.c index 60c0d163524bb7..9356f0d943e9bb 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -2361,8 +2361,12 @@ static jl_value_t *intersect_var(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int JL_GC_PUSH2(&ub, &root); if (!jl_has_free_typevars(a)) { save_env(e, &root, &se); - int issub = subtype_in_env_existential(bb->lb, a, e, 0, d) && subtype_in_env_existential(a, bb->ub, e, 1, d); + int issub = subtype_in_env_existential(bb->lb, a, e, 0, d); restore_env(e, root, &se); + if (issub) { + issub = subtype_in_env_existential(a, bb->ub, e, 1, d); + restore_env(e, root, &se); + } free_env(&se); if (!issub) { JL_GC_POP(); @@ -2938,8 +2942,11 @@ static jl_value_t *intersect_invariant(jl_value_t *x, jl_value_t *y, jl_stenv_t save_env(e, &root, &se); if (!subtype_in_env_existential(x, y, e, 0, e->invdepth)) ii = NULL; - else if (!subtype_in_env_existential(y, x, e, 0, e->invdepth)) - ii = NULL; + else { + restore_env(e, root, &se); + if (!subtype_in_env_existential(y, x, e, 0, e->invdepth)) + ii = NULL; + } restore_env(e, root, &se); free_env(&se); JL_GC_POP(); diff --git a/test/subtype.jl b/test/subtype.jl index 34ff59bc62580d..9239c66decff1e 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -2286,6 +2286,16 @@ T46784{B<:Val, M<:AbstractMatrix} = Tuple{<:Union{B, <:Val{<:B}}, M, Union{Abstr @testintersect(T46784{T,S} where {T,S}, T46784, !Union{}) @test_broken T46784 <: T46784{T,S} where {T,S} +#issue 36185 +let S = Tuple{Type{T},Array{Union{T,Missing},N}} where {T,N}, + T = Tuple{Type{T},Array{Union{T,Nothing},N}} where {T,N} + @testintersect(S, T, !Union{}) + I = typeintersect(S, T) + @test (Tuple{Type{Any},Array{Any,N}} where {N}) <: I + @test_broken I <: S + @test_broken I <: T +end + @testset "known subtype/intersect issue" begin #issue 45874 # Causes a hang due to jl_critical_error calling back into malloc... @@ -2313,10 +2323,6 @@ T46784{B<:Val, M<:AbstractMatrix} = Tuple{<:Union{B, <:Val{<:B}}, M, Union{Abstr B = Tuple{NTuple{N, Int}, NTuple{N, Int}, NTuple{N, Int}} where N @test_broken !(A <: B) - #issue 36185 - @test_broken typeintersect((Tuple{Type{T},Array{Union{T,Missing},N}} where {T,N}), - (Tuple{Type{T},Array{Union{T,Nothing},N}} where {T,N})) <: Any - #issue 35698 @test_broken typeintersect(Type{Tuple{Array{T,1} where T}}, UnionAll) != Union{} From 2901f0e353c5e5fdb341caa9abce92b042248627 Mon Sep 17 00:00:00 2001 From: N5N3 <2642243996@qq.com> Date: Thu, 29 Dec 2022 06:04:09 +0800 Subject: [PATCH 226/387] Avoid stack-overflow in #46736 When we perform re-`intersection_unionall`, the `Union` bounds might be generated from `simple_join and thus not identical to the src `Union`. This commit adds a fast-path to skip the following `intersect_all. --- src/subtype.c | 3 +++ test/subtype.jl | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/src/subtype.c b/src/subtype.c index 9356f0d943e9bb..c4207840ae5190 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -2150,6 +2150,9 @@ static jl_value_t *intersect_aside(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, return y; if (y == (jl_value_t*)jl_any_type && !jl_is_typevar(x)) return x; + // band-aid for #46736 + if (jl_egal(x, y)) + return x; jl_saved_unionstate_t oldRunions; push_unionstate(&oldRunions, &e->Runions); int savedepth = e->invdepth, Rsavedepth = e->Rinvdepth; diff --git a/test/subtype.jl b/test/subtype.jl index 9239c66decff1e..d5f07f722f5cae 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -2296,6 +2296,14 @@ let S = Tuple{Type{T},Array{Union{T,Missing},N}} where {T,N}, @test_broken I <: T end +#issue 46736 +let S = Tuple{Val{T}, T} where {S1,T<:Val{Union{Nothing,S1}}}, + T = Tuple{Val{Val{Union{Nothing, S2}}}, Any} where S2 + @testintersect(S, T, !Union{}) + # not ideal (`S1` should be unbounded) + @test_broken testintersect(S, T) == Tuple{Val{Val{Union{Nothing, S1}}}, Val{Union{Nothing, S1}}} where S1<:(Union{Nothing, S2} where S2) +end + @testset "known subtype/intersect issue" begin #issue 45874 # Causes a hang due to jl_critical_error calling back into malloc... From 36cd9c62f921abf700bb2f30d750b6a5d859a428 Mon Sep 17 00:00:00 2001 From: N5N3 <2642243996@qq.com> Date: Sat, 31 Dec 2022 02:07:44 +0800 Subject: [PATCH 227/387] Avoid setting circular var's bounds during intersection. This fixes the MWE reported in https://github.com/JuliaLang/julia/issues/47874#issue-1491378956 And this fixes the remaining internal error in `Healpix.jl`'s test. gc fix --- src/subtype.c | 12 ++++++++---- test/subtype.jl | 10 ++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/subtype.c b/src/subtype.c index c4207840ae5190..3c9db8f1677cbc 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -605,6 +605,8 @@ static int var_outside(jl_stenv_t *e, jl_tvar_t *x, jl_tvar_t *y) static jl_value_t *intersect_aside(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int R, int d); +static int reachable_var(jl_value_t *x, jl_tvar_t *y, jl_stenv_t *e); + // check that type var `b` is <: `a`, and update b's upper bound. static int var_lt(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int param) { @@ -622,8 +624,10 @@ static int var_lt(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int param) // since otherwise the issub(a, bb.ub) check in var_gt becomes vacuous. if (e->intersection) { jl_value_t *ub = intersect_aside(bb->ub, a, e, 0, bb->depth0); - if (ub != (jl_value_t*)b) + JL_GC_PUSH1(&ub); + if (ub != (jl_value_t*)b && (!jl_is_typevar(ub) || !reachable_var(ub, b, e))) bb->ub = ub; + JL_GC_POP(); } else { bb->ub = simple_meet(bb->ub, a); @@ -640,8 +644,6 @@ static int var_lt(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int param) return 1; } -static int subtype_by_bounds(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) JL_NOTSAFEPOINT; - // check that type var `b` is >: `a`, and update b's lower bound. static int var_gt(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int param) { @@ -660,8 +662,10 @@ static int var_gt(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int param) if (!((bb->ub == (jl_value_t*)jl_any_type && !jl_is_type(a) && !jl_is_typevar(a)) || subtype_ccheck(a, bb->ub, e))) return 0; jl_value_t *lb = simple_join(bb->lb, a); - if (!e->intersection || !subtype_by_bounds(lb, (jl_value_t*)b, e)) + JL_GC_PUSH1(&lb); + if (!e->intersection || !jl_is_typevar(lb) || !reachable_var(lb, b, e)) bb->lb = lb; + JL_GC_POP(); // this bound should not be directly circular assert(bb->lb != (jl_value_t*)b); if (jl_is_typevar(a)) { diff --git a/test/subtype.jl b/test/subtype.jl index d5f07f722f5cae..14ea362abd7951 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -2304,6 +2304,16 @@ let S = Tuple{Val{T}, T} where {S1,T<:Val{Union{Nothing,S1}}}, @test_broken testintersect(S, T) == Tuple{Val{Val{Union{Nothing, S1}}}, Val{Union{Nothing, S1}}} where S1<:(Union{Nothing, S2} where S2) end +#issue #47874:case1 +let S1 = Tuple{Int, Any, Union{Val{C1}, C1}} where {R1<:Real, C1<:Union{Complex{R1}, R1}}, + S2 = Tuple{Int, Any, Union{Val{C1}, C1} where {R1<:Real, C1<:Union{Complex{R1}, R1}}}, + T1 = Tuple{Any, Int, Union{Val{C2}, C2}} where {R2<:Real, C2<:Union{Complex{R2}, R2}}, + T2 = Tuple{Any, Int, V} where {R2<:Real, C2<:Union{Complex{R2}, R2}, V<:Union{Val{C2}, C2}} + for S in (S1, S2), T in (T1, T2) + @testintersect(S, T, !Union{}) + end +end + @testset "known subtype/intersect issue" begin #issue 45874 # Causes a hang due to jl_critical_error calling back into malloc... From 87b1f2f5d76a48f0b13d342c1201cfaed4cec603 Mon Sep 17 00:00:00 2001 From: DilumAluthgeBot <43731525+DilumAluthgeBot@users.noreply.github.com> Date: Tue, 3 Jan 2023 04:21:28 -0500 Subject: [PATCH 228/387] =?UTF-8?q?=F0=9F=A4=96=20Bump=20the=20SparseArray?= =?UTF-8?q?s=20stdlib=20from=2072827cd=20to=2031b491e=20(#48084)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dilum Aluthge --- .../md5 | 1 + .../sha512 | 1 + .../md5 | 1 - .../sha512 | 1 - stdlib/SparseArrays.version | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 deps/checksums/SparseArrays-31b491e431d0b9c4e5e17fdcbbc9f71579e7c79c.tar.gz/md5 create mode 100644 deps/checksums/SparseArrays-31b491e431d0b9c4e5e17fdcbbc9f71579e7c79c.tar.gz/sha512 delete mode 100644 deps/checksums/SparseArrays-72827cd31f0aa346cfc54158f9c6db1738ecd9e4.tar.gz/md5 delete mode 100644 deps/checksums/SparseArrays-72827cd31f0aa346cfc54158f9c6db1738ecd9e4.tar.gz/sha512 diff --git a/deps/checksums/SparseArrays-31b491e431d0b9c4e5e17fdcbbc9f71579e7c79c.tar.gz/md5 b/deps/checksums/SparseArrays-31b491e431d0b9c4e5e17fdcbbc9f71579e7c79c.tar.gz/md5 new file mode 100644 index 00000000000000..f67e2fb240bd80 --- /dev/null +++ b/deps/checksums/SparseArrays-31b491e431d0b9c4e5e17fdcbbc9f71579e7c79c.tar.gz/md5 @@ -0,0 +1 @@ +5d31e7c74a00b630e1faef7b02642219 diff --git a/deps/checksums/SparseArrays-31b491e431d0b9c4e5e17fdcbbc9f71579e7c79c.tar.gz/sha512 b/deps/checksums/SparseArrays-31b491e431d0b9c4e5e17fdcbbc9f71579e7c79c.tar.gz/sha512 new file mode 100644 index 00000000000000..804f8002b437ff --- /dev/null +++ b/deps/checksums/SparseArrays-31b491e431d0b9c4e5e17fdcbbc9f71579e7c79c.tar.gz/sha512 @@ -0,0 +1 @@ +88ee08b50fd84b83288d7facbb2887f23d71278a8b976e4d5b3e867b3bad726b24d561d31aee435513263cf34d2f3fb50a3a20be62052f117ae46a37b1940157 diff --git a/deps/checksums/SparseArrays-72827cd31f0aa346cfc54158f9c6db1738ecd9e4.tar.gz/md5 b/deps/checksums/SparseArrays-72827cd31f0aa346cfc54158f9c6db1738ecd9e4.tar.gz/md5 deleted file mode 100644 index b3823f89e25831..00000000000000 --- a/deps/checksums/SparseArrays-72827cd31f0aa346cfc54158f9c6db1738ecd9e4.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -d98268fc078d79fa7f963d46625a605f diff --git a/deps/checksums/SparseArrays-72827cd31f0aa346cfc54158f9c6db1738ecd9e4.tar.gz/sha512 b/deps/checksums/SparseArrays-72827cd31f0aa346cfc54158f9c6db1738ecd9e4.tar.gz/sha512 deleted file mode 100644 index dec122937cf19a..00000000000000 --- a/deps/checksums/SparseArrays-72827cd31f0aa346cfc54158f9c6db1738ecd9e4.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -ed3e4a8a8d5cd24b5de37ea9a861a4b7be9f3938d3aefafa07b0040fba64acc7b7732856bcee9518733a916e2c3839faac9df4a0f79afd97265e54a9b6bc2b41 diff --git a/stdlib/SparseArrays.version b/stdlib/SparseArrays.version index 789be577f93e5e..5f3276dfcc279b 100644 --- a/stdlib/SparseArrays.version +++ b/stdlib/SparseArrays.version @@ -1,4 +1,4 @@ SPARSEARRAYS_BRANCH = main -SPARSEARRAYS_SHA1 = 72827cd31f0aa346cfc54158f9c6db1738ecd9e4 +SPARSEARRAYS_SHA1 = 31b491e431d0b9c4e5e17fdcbbc9f71579e7c79c SPARSEARRAYS_GIT_URL := https://github.com/JuliaSparse/SparseArrays.jl.git SPARSEARRAYS_TAR_URL = https://api.github.com/repos/JuliaSparse/SparseArrays.jl/tarball/$1 From 4831361c0c452f8ae1ecdc6cd297f0e77a9f3a06 Mon Sep 17 00:00:00 2001 From: Petr Vana Date: Tue, 3 Jan 2023 16:07:01 +0100 Subject: [PATCH 229/387] Parallelize precompiles generation II (#48049) Co-authored-by: Ian Butterworth --- contrib/generate_precompile.jl | 107 ++++++++++++++++++++------------- 1 file changed, 65 insertions(+), 42 deletions(-) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index 89bb1d3bb1c061..cd357eaf556d89 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -15,6 +15,17 @@ Base.include(@__MODULE__, joinpath(Sys.BINDIR, "..", "share", "julia", "test", " import .FakePTYs: open_fake_pty using Base.Meta +## Debugging options +# Disable parallel precompiles generation by setting `false` +const PARALLEL_PRECOMPILATION = true + +# View the code sent to the repl by setting this to `stdout` +const debug_output = devnull # or stdout + +# Disable fancy printing +const fancyprint = (stdout isa Base.TTY) && (get(ENV, "CI", nothing) != "true") +## + CTRL_C = '\x03' UP_ARROW = "\e[A" DOWN_ARROW = "\e[B" @@ -163,7 +174,7 @@ Pkg = get(Base.loaded_modules, if Pkg !== nothing # TODO: Split Pkg precompile script into REPL and script part - repl_script *= Pkg.precompile_script + repl_script = Pkg.precompile_script * repl_script # do larger workloads first for better parallelization end FileWatching = get(Base.loaded_modules, @@ -224,7 +235,7 @@ Profile = get(Base.loaded_modules, Base.PkgId(Base.UUID("9abbd945-dff8-562f-b5e8-e1ebf5ef1b79"), "Profile"), nothing) if Profile !== nothing - repl_script *= Profile.precompile_script + repl_script = Profile.precompile_script * repl_script # do larger workloads first for better parallelization hardcoded_precompile_statements *= """ precompile(Tuple{typeof(Profile.tree!), Profile.StackFrameTree{UInt64}, Vector{UInt64}, Dict{UInt64, Vector{Base.StackTraces.StackFrame}}, Bool, Symbol, Int, UInt}) precompile(Tuple{typeof(Profile.tree!), Profile.StackFrameTree{UInt64}, Vector{UInt64}, Dict{UInt64, Vector{Base.StackTraces.StackFrame}}, Bool, Symbol, Int, UnitRange{UInt}}) @@ -239,12 +250,6 @@ const PKG_PROMPT = "pkg> " const SHELL_PROMPT = "shell> " const HELP_PROMPT = "help?> " -# You can disable parallel precompiles generation by setting `false` -const PARALLEL_PRECOMPILATION = true - -# You can disable fancy printing -const fancyprint = (stdout isa Base.TTY) && (get(ENV, "CI", nothing) != "true") - # Printing the current state let global print_state @@ -286,7 +291,6 @@ ansi_disablecursor = "\e[?25l" generate_precompile_statements() = try # Make sure `ansi_enablecursor` is printed start_time = time_ns() - debug_output = devnull # or stdout sysimg = Base.unsafe_string(Base.JLOptions().image_file) # Extract the precompile statements from the precompile file @@ -352,6 +356,7 @@ generate_precompile_statements() = try # Make sure `ansi_enablecursor` is printe step2 = @async mktemp() do precompile_file, precompile_file_h print_state("step2" => "R") # Collect statements from running a REPL process and replaying our REPL script + touch(precompile_file) pts, ptm = open_fake_pty() blackhole = Sys.isunix() ? "/dev/null" : "nul" if have_repl @@ -388,48 +393,66 @@ generate_precompile_statements() = try # Make sure `ansi_enablecursor` is printe close(output_copy) close(ptm) end - # wait for the definitive prompt before start writing to the TTY - readuntil(output_copy, JULIA_PROMPT) - sleep(0.1) - readavailable(output_copy) - # Input our script - if have_repl - precompile_lines = split(repl_script::String, '\n'; keepempty=false) - curr = 0 - for l in precompile_lines - sleep(0.1) - curr += 1 - print_state("repl" => "$curr/$(length(precompile_lines))") - # consume any other output - bytesavailable(output_copy) > 0 && readavailable(output_copy) - # push our input - write(debug_output, "\n#### inputting statement: ####\n$(repr(l))\n####\n") - write(ptm, l, "\n") - readuntil(output_copy, "\n") - # wait for the next prompt-like to appear - readuntil(output_copy, "\n") - strbuf = "" - while !eof(output_copy) - strbuf *= String(readavailable(output_copy)) - occursin(JULIA_PROMPT, strbuf) && break - occursin(PKG_PROMPT, strbuf) && break - occursin(SHELL_PROMPT, strbuf) && break - occursin(HELP_PROMPT, strbuf) && break + repl_inputter = @async begin + # wait for the definitive prompt before start writing to the TTY + readuntil(output_copy, JULIA_PROMPT) + sleep(0.1) + readavailable(output_copy) + # Input our script + if have_repl + precompile_lines = split(repl_script::String, '\n'; keepempty=false) + curr = 0 + for l in precompile_lines sleep(0.1) + curr += 1 + print_state("repl" => "$curr/$(length(precompile_lines))") + # consume any other output + bytesavailable(output_copy) > 0 && readavailable(output_copy) + # push our input + write(debug_output, "\n#### inputting statement: ####\n$(repr(l))\n####\n") + write(ptm, l, "\n") + readuntil(output_copy, "\n") + # wait for the next prompt-like to appear + readuntil(output_copy, "\n") + strbuf = "" + while !eof(output_copy) + strbuf *= String(readavailable(output_copy)) + occursin(JULIA_PROMPT, strbuf) && break + occursin(PKG_PROMPT, strbuf) && break + occursin(SHELL_PROMPT, strbuf) && break + occursin(HELP_PROMPT, strbuf) && break + sleep(0.1) + end end end + write(ptm, "exit()\n") + wait(tee) + success(p) || Base.pipeline_error(p) + close(ptm) + write(debug_output, "\n#### FINISHED ####\n") end - write(ptm, "exit()\n") - wait(tee) - success(p) || Base.pipeline_error(p) - close(ptm) - write(debug_output, "\n#### FINISHED ####\n") n_step2 = 0 - for statement in split(read(precompile_file, String), '\n') + precompile_copy = Base.BufferStream() + buffer_reader = @async for statement in eachline(precompile_copy) + print_state("step2" => "R$n_step2") push!(statements_step2, statement) n_step2 += 1 end + + open(precompile_file, "r") do io + while true + # We need to allways call eof(io) for bytesavailable(io) to work + eof(io) && istaskdone(repl_inputter) && eof(io) && break + if bytesavailable(io) == 0 + sleep(0.1) + continue + end + write(precompile_copy, readavailable(io)) + end + end + close(precompile_copy) + wait(buffer_reader) close(statements_step2) print_state("step2" => "F$n_step2") return :ok From 52af407745f23512d48f489658bbe07cb21b5808 Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Tue, 3 Jan 2023 18:00:26 -0300 Subject: [PATCH 230/387] Use native fmin/fmax in aarch64 (#47814) * Use native fmin in aarch64 * Small cleanup + effects * Cleanup * Update base/math.jl Co-authored-by: Valentin Churavy * Some more cleanup Co-authored-by: Valentin Churavy --- base/math.jl | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/base/math.jl b/base/math.jl index c2eda1526c0629..f41057c76cfc2c 100644 --- a/base/math.jl +++ b/base/math.jl @@ -851,26 +851,45 @@ minmax(x::T, y::T) where {T<:AbstractFloat} = min(x, y), max(x, y) _isless(x::Float16, y::Float16) = signbit(widen(x) - widen(y)) +const has_native_fminmax = Sys.ARCH === :aarch64 +@static if has_native_fminmax + @eval begin + Base.@assume_effects :total @inline llvm_min(x::Float64, y::Float64) = ccall("llvm.minimum.f64", llvmcall, Float64, (Float64, Float64), x, y) + Base.@assume_effects :total @inline llvm_min(x::Float32, y::Float32) = ccall("llvm.minimum.f32", llvmcall, Float32, (Float32, Float32), x, y) + Base.@assume_effects :total @inline llvm_max(x::Float64, y::Float64) = ccall("llvm.maximum.f64", llvmcall, Float64, (Float64, Float64), x, y) + Base.@assume_effects :total @inline llvm_max(x::Float32, y::Float32) = ccall("llvm.maximum.f32", llvmcall, Float32, (Float32, Float32), x, y) + end +end + function min(x::T, y::T) where {T<:Union{Float32,Float64}} + @static if has_native_fminmax + return llvm_min(x,y) + end diff = x - y argmin = ifelse(signbit(diff), x, y) anynan = isnan(x)|isnan(y) - ifelse(anynan, diff, argmin) + return ifelse(anynan, diff, argmin) end function max(x::T, y::T) where {T<:Union{Float32,Float64}} + @static if has_native_fminmax + return llvm_max(x,y) + end diff = x - y argmax = ifelse(signbit(diff), y, x) anynan = isnan(x)|isnan(y) - ifelse(anynan, diff, argmax) + return ifelse(anynan, diff, argmax) end function minmax(x::T, y::T) where {T<:Union{Float32,Float64}} + @static if has_native_fminmax + return llvm_min(x, y), llvm_max(x, y) + end diff = x - y sdiff = signbit(diff) min, max = ifelse(sdiff, x, y), ifelse(sdiff, y, x) anynan = isnan(x)|isnan(y) - ifelse(anynan, diff, min), ifelse(anynan, diff, max) + return ifelse(anynan, diff, min), ifelse(anynan, diff, max) end """ From cdcb07f13c10611f3a2fe4d491a8d0342ad11d12 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Wed, 4 Jan 2023 17:54:32 +0900 Subject: [PATCH 231/387] optimizer: keep statements that may throw from being optimized by SROA (#48068) SROA should verify a statement won't throw, otherwise it can't be eliminated safely. Note that this commit skips the nothrow-ness verification on `getfield` statement. This is acceptable because currently we are unable to prove it in the presence of potentially undefined fields. This is okay because our SROA pass will eliminate such a `getfield` statement only if it determines that the forwarded value safely defines the potentially undefined field. --- base/compiler/ssair/passes.jl | 35 ++++++++++++++++------------------- test/compiler/irpasses.jl | 16 ++++++++++++++++ 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index bb6de813c51eb5..24293586e06290 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -46,10 +46,6 @@ function compute_live_ins(cfg::CFG, du::SSADefUse) compute_live_ins(cfg, sort!(du.defs), uses) end -# assume `stmt == getfield(obj, field, ...)` or `stmt == setfield!(obj, field, val, ...)` -try_compute_field_stmt(ir::Union{IncrementalCompact,IRCode}, stmt::Expr) = - try_compute_field(ir, stmt.args[3]) - function try_compute_field(ir::Union{IncrementalCompact,IRCode}, @nospecialize(field)) # fields are usually literals, handle them manually if isa(field, QuoteNode) @@ -67,8 +63,9 @@ function try_compute_field(ir::Union{IncrementalCompact,IRCode}, @nospecialize(f return isa(field, Union{Int, Symbol}) ? field : nothing end +# assume `stmt` is a call of `getfield`/`setfield!`/`isdefined` function try_compute_fieldidx_stmt(ir::Union{IncrementalCompact,IRCode}, stmt::Expr, typ::DataType) - field = try_compute_field_stmt(ir, stmt) + field = try_compute_field(ir, stmt.args[3]) return try_compute_fieldidx(typ, field) end @@ -953,14 +950,11 @@ function sroa_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) continue end - # analyze this `getfield` / `isdefined` / `setfield!` call - - if !is_finalizer - field = try_compute_field_stmt(compact, stmt) - field === nothing && continue - val = stmt.args[2] - else + if is_finalizer val = stmt.args[3] + else + # analyze `getfield` / `isdefined` / `setfield!` call + val = stmt.args[2] end struct_typ = unwrap_unionall(widenconst(argextype(val, compact))) @@ -1014,7 +1008,7 @@ function sroa_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) # perform SROA on immutable structs here on - field = try_compute_fieldidx(struct_typ, field) + field = try_compute_fieldidx_stmt(compact, stmt, struct_typ) field === nothing && continue leaves, visited_phinodes = collect_leaves(compact, val, struct_typ, 𝕃ₒ) @@ -1116,7 +1110,7 @@ function try_inline_finalizer!(ir::IRCode, argexprs::Vector{Any}, idx::Int, return true end -is_nothrow(ir::IRCode, pc::Int) = (ir.stmts[pc][:flag] & IR_FLAG_NOTHROW) ≠ 0 +is_nothrow(ir::IRCode, ssa::SSAValue) = (ir[ssa][:flag] & IR_FLAG_NOTHROW) ≠ 0 function reachable_blocks(cfg::CFG, from_bb::Int, to_bb::Union{Nothing,Int} = nothing) worklist = Int[from_bb] @@ -1210,7 +1204,7 @@ function try_resolve_finalizer!(ir::IRCode, idx::Int, finalizer_idx::Int, defuse return all(s:e) do sidx::Int sidx == finalizer_idx && return true sidx == idx && return true - return is_nothrow(ir, sidx) + return is_nothrow(ir, SSAValue(sidx)) end end for bb in blocks @@ -1418,11 +1412,14 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse end all_eliminated || continue # all "usages" (i.e. `getfield` and `isdefined` calls) are eliminated, - # now eliminate "definitions" (`setfield!`) calls + # now eliminate "definitions" (i.e. `setfield!`) calls # (NOTE the allocation itself will be eliminated by DCE pass later) - for stmt in du.defs - stmt == newidx && continue - ir[SSAValue(stmt)][:inst] = nothing + for idx in du.defs + idx == newidx && continue # this is allocation + # verify this statement won't throw, otherwise it can't be eliminated safely + ssa = SSAValue(idx) + is_nothrow(ir, ssa) || continue + ir[ssa][:inst] = nothing end end preserve_uses === nothing && continue diff --git a/test/compiler/irpasses.jl b/test/compiler/irpasses.jl index 26ce3e3c807a49..2db01c4b85444e 100644 --- a/test/compiler/irpasses.jl +++ b/test/compiler/irpasses.jl @@ -387,6 +387,22 @@ let # should work with constant globals @test count(isnew, src.code) == 0 end +# don't SROA statement that may throw +# https://github.com/JuliaLang/julia/issues/48067 +function issue48067(a::Int, b) + r = Ref(a) + try + setfield!(r, :x, b) + nothing + catch err + getfield(r, :x) + end +end +let src = code_typed1(issue48067, (Int,String)) + @test any(iscall((src, setfield!)), src.code) +end +@test issue48067(42, "julia") == 42 + # should work nicely with inlining to optimize away a complicated case # adapted from http://wiki.luajit.org/Allocation-Sinking-Optimization#implementation%5B struct Point From e73c261d4ab5a356e6cc03602844afd8f381537e Mon Sep 17 00:00:00 2001 From: Jeremie Knuesel Date: Wed, 4 Jan 2023 16:33:31 +0100 Subject: [PATCH 232/387] Document array literals equivalent to hvncat (#48118) --- doc/src/manual/functions.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/doc/src/manual/functions.md b/doc/src/manual/functions.md index 7b430fa513002f..6d0263776233b0 100644 --- a/doc/src/manual/functions.md +++ b/doc/src/manual/functions.md @@ -264,16 +264,20 @@ Under the name `f`, the function does not support infix notation, however. A few special expressions correspond to calls to functions with non-obvious names. These are: -| Expression | Calls | -|:----------------- |:----------------------- | -| `[A B C ...]` | [`hcat`](@ref) | -| `[A; B; C; ...]` | [`vcat`](@ref) | -| `[A B; C D; ...]` | [`hvcat`](@ref) | -| `A'` | [`adjoint`](@ref) | -| `A[i]` | [`getindex`](@ref) | -| `A[i] = x` | [`setindex!`](@ref) | -| `A.n` | [`getproperty`](@ref Base.getproperty) | -| `A.n = x` | [`setproperty!`](@ref Base.setproperty!) | +| Expression | Calls | +|:--------------------- |:----------------------- | +| `[A B C ...]` | [`hcat`](@ref) | +| `[A; B; C; ...]` | [`vcat`](@ref) | +| `[A B; C D; ...]` | [`hvcat`](@ref) | +| `[A; B;; C; D;; ...]` | [`hvncat`](@ref) | +| `A'` | [`adjoint`](@ref) | +| `A[i]` | [`getindex`](@ref) | +| `A[i] = x` | [`setindex!`](@ref) | +| `A.n` | [`getproperty`](@ref Base.getproperty) | +| `A.n = x` | [`setproperty!`](@ref Base.setproperty!) | + +Note that expressions similar to `[A; B;; C; D;; ...]` but with more than two +consecutive `;` also correspond to `hvncat` calls. ## [Anonymous Functions](@id man-anonymous-functions) From 7b92f386af3013313a527c6c2f61df94282e137d Mon Sep 17 00:00:00 2001 From: Jeremie Knuesel Date: Wed, 4 Jan 2023 16:44:18 +0100 Subject: [PATCH 233/387] Add example for now(::Type{UTC}) and mention TimeZones.jl (#48117) --- stdlib/Dates/src/conversions.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/stdlib/Dates/src/conversions.jl b/stdlib/Dates/src/conversions.jl index 161dc3791afabf..8493218cc4086d 100644 --- a/stdlib/Dates/src/conversions.jl +++ b/stdlib/Dates/src/conversions.jl @@ -80,6 +80,13 @@ today() = Date(now()) now(::Type{UTC}) -> DateTime Return a `DateTime` corresponding to the user's system time as UTC/GMT. +For other time zones, see the TimeZones.jl package. + +# Example +```julia +julia> now(UTC) +2023-01-04T10:52:24.864 +``` """ now(::Type{UTC}) = unix2datetime(time()) From b604fc9b12a99e1ad216d22016ced637d77b3311 Mon Sep 17 00:00:00 2001 From: mikmoore <95002244+mikmoore@users.noreply.github.com> Date: Wed, 4 Jan 2023 12:12:07 -0700 Subject: [PATCH 234/387] Specialize isinf(::IEEEFloat) (#48109) * specialize isinf(::IEEEFloat) Co-authored-by: mikmoore --- base/float.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/base/float.jl b/base/float.jl index 6109710d7a851d..eda0865c9fbac7 100644 --- a/base/float.jl +++ b/base/float.jl @@ -631,6 +631,7 @@ Test whether a number is infinite. See also: [`Inf`](@ref), [`iszero`](@ref), [`isfinite`](@ref), [`isnan`](@ref). """ isinf(x::Real) = !isnan(x) & !isfinite(x) +isinf(x::IEEEFloat) = abs(x) === oftype(x, Inf) const hx_NaN = hash_uint64(reinterpret(UInt64, NaN)) let Tf = Float64, Tu = UInt64, Ti = Int64 From b6794f99b2f9ac2fd7ce87cd151e0a93daab159d Mon Sep 17 00:00:00 2001 From: Petr Vana Date: Wed, 4 Jan 2023 20:21:14 +0100 Subject: [PATCH 235/387] Add test for invperm for incorrect permutation (#47869) --- test/combinatorics.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/combinatorics.jl b/test/combinatorics.jl index 8a27ad6da99713..b04259f3973048 100644 --- a/test/combinatorics.jl +++ b/test/combinatorics.jl @@ -34,6 +34,7 @@ end @test invperm((1,2)) == (1,2) @test invperm((2,1)) == (2,1) @test_throws ArgumentError invperm((1,3)) + @test_throws ArgumentError invperm((1,1)) push!(p, 1) @test !isperm(p) From 4a42367c2e9c1f98c30a4106472130da383e8315 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 4 Jan 2023 15:05:55 -0500 Subject: [PATCH 236/387] [GCChecker] add support for tracking JL_NOTSAFEPOINT regions (#47978) We have regions of code that we either cannot (gcsafe_enter) or should not (locks) enter GC since it would cause data races or deadlocks. Teach the GCChecker how to analyze those state transitions. Additionally, mark all functions inside files named 'llvm-*.*' as not safepoints. This saves us some manual annotation effort. --- src/Makefile | 33 ++- src/aotcompile.cpp | 2 +- src/clangsa/GCChecker.cpp | 172 ++++++++++----- src/codegen.cpp | 4 +- src/debuginfo.cpp | 50 ++--- src/disasm.cpp | 82 +++---- src/dlload.c | 4 - src/gc.c | 18 +- src/gc.h | 2 +- src/gf.c | 2 +- src/init.c | 4 +- src/jitlayers.cpp | 135 ++++++------ src/jitlayers.h | 201 ++++++++++-------- src/julia_internal.h | 22 +- src/julia_locks.h | 6 +- src/julia_threads.h | 8 +- src/llvm-alloc-helpers.cpp | 2 +- src/llvm-alloc-opt.cpp | 2 +- ...codegen_shared.h => llvm-codegen-shared.h} | 0 src/llvm-cpufeatures.cpp | 12 +- src/llvm-final-gc-lowering.cpp | 2 +- src/llvm-gc-invariant-verifier.cpp | 2 +- src/llvm-julia-licm.cpp | 2 +- src/llvm-late-gc-lowering.cpp | 2 +- src/llvm-lower-handlers.cpp | 2 +- src/llvm-muladd.cpp | 6 +- src/llvm-multiversioning.cpp | 2 +- src/llvm-pass-helpers.cpp | 2 +- src/llvm-pass-helpers.h | 3 +- src/llvm-propagate-addrspaces.cpp | 2 +- src/llvm-ptls.cpp | 2 +- src/llvm-remove-addrspaces.cpp | 2 +- src/llvm-remove-ni.cpp | 2 +- src/llvm-simdloop.cpp | 8 +- src/llvmcalltest.cpp | 2 +- src/module.c | 2 - src/passes.h | 42 ++-- src/pipeline.cpp | 66 +++--- src/processor.h | 10 +- src/staticdata.c | 5 +- src/support/analyzer_annotations.h | 6 +- src/support/ios.h | 4 +- src/task.c | 8 +- src/threading.c | 4 +- src/threading.h | 2 +- test/clangsa/GCPushPop.cpp | 1 - 46 files changed, 528 insertions(+), 424 deletions(-) rename src/{codegen_shared.h => llvm-codegen-shared.h} (100%) diff --git a/src/Makefile b/src/Makefile index 65987ddf53fa4d..5cb346c0fa95ad 100644 --- a/src/Makefile +++ b/src/Makefile @@ -286,11 +286,11 @@ $(BUILDDIR)/julia_flisp.boot: $(addprefix $(SRCDIR)/,jlfrontend.scm flisp/aliase # additional dependency links $(BUILDDIR)/codegen-stubs.o $(BUILDDIR)/codegen-stubs.dbg.obj: $(SRCDIR)/intrinsics.h -$(BUILDDIR)/aotcompile.o $(BUILDDIR)/aotcompile.dbg.obj: $(SRCDIR)/jitlayers.h $(SRCDIR)/codegen_shared.h $(SRCDIR)/debug-registry.h +$(BUILDDIR)/aotcompile.o $(BUILDDIR)/aotcompile.dbg.obj: $(SRCDIR)/jitlayers.h $(SRCDIR)/llvm-codegen-shared.h $(SRCDIR)/debug-registry.h $(BUILDDIR)/ast.o $(BUILDDIR)/ast.dbg.obj: $(BUILDDIR)/julia_flisp.boot.inc $(SRCDIR)/flisp/*.h $(BUILDDIR)/builtins.o $(BUILDDIR)/builtins.dbg.obj: $(SRCDIR)/iddict.c $(SRCDIR)/builtin_proto.h $(BUILDDIR)/codegen.o $(BUILDDIR)/codegen.dbg.obj: $(addprefix $(SRCDIR)/,\ - intrinsics.cpp jitlayers.h debug-registry.h intrinsics.h codegen_shared.h cgutils.cpp ccall.cpp abi_*.cpp processor.h builtin_proto.h) + intrinsics.cpp jitlayers.h debug-registry.h intrinsics.h llvm-codegen-shared.h cgutils.cpp ccall.cpp abi_*.cpp processor.h builtin_proto.h) $(BUILDDIR)/datatype.o $(BUILDDIR)/datatype.dbg.obj: $(SRCDIR)/support/htable.h $(SRCDIR)/support/htable.inc $(BUILDDIR)/debuginfo.o $(BUILDDIR)/debuginfo.dbg.obj: $(addprefix $(SRCDIR)/,debuginfo.h processor.h jitlayers.h debug-registry.h) $(BUILDDIR)/disasm.o $(BUILDDIR)/disasm.dbg.obj: $(SRCDIR)/debuginfo.h $(SRCDIR)/processor.h @@ -301,22 +301,22 @@ $(BUILDDIR)/gc-heap-snapshot.o $(BUILDDIR)/gc-heap-snapshot.dbg.obj: $(SRCDIR)/g $(BUILDDIR)/gc-alloc-profiler.o $(BUILDDIR)/gc-alloc-profiler.dbg.obj: $(SRCDIR)/gc.h $(SRCDIR)/gc-alloc-profiler.h $(BUILDDIR)/init.o $(BUILDDIR)/init.dbg.obj: $(SRCDIR)/builtin_proto.h $(BUILDDIR)/interpreter.o $(BUILDDIR)/interpreter.dbg.obj: $(SRCDIR)/builtin_proto.h -$(BUILDDIR)/jitlayers.o $(BUILDDIR)/jitlayers.dbg.obj: $(SRCDIR)/jitlayers.h $(SRCDIR)/codegen_shared.h $(SRCDIR)/debug-registry.h +$(BUILDDIR)/jitlayers.o $(BUILDDIR)/jitlayers.dbg.obj: $(SRCDIR)/jitlayers.h $(SRCDIR)/llvm-codegen-shared.h $(SRCDIR)/debug-registry.h $(BUILDDIR)/jltypes.o $(BUILDDIR)/jltypes.dbg.obj: $(SRCDIR)/builtin_proto.h -$(build_shlibdir)/libllvmcalltest.$(SHLIB_EXT): $(SRCDIR)/codegen_shared.h $(BUILDDIR)/julia_version.h -$(BUILDDIR)/llvm-alloc-helpers.o $(BUILDDIR)/llvm-alloc-helpers.dbg.obj: $(SRCDIR)/codegen_shared.h $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-alloc-helpers.h -$(BUILDDIR)/llvm-alloc-opt.o $(BUILDDIR)/llvm-alloc-opt.dbg.obj: $(SRCDIR)/codegen_shared.h $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-alloc-helpers.h +$(build_shlibdir)/libllvmcalltest.$(SHLIB_EXT): $(SRCDIR)/llvm-codegen-shared.h $(BUILDDIR)/julia_version.h +$(BUILDDIR)/llvm-alloc-helpers.o $(BUILDDIR)/llvm-alloc-helpers.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-alloc-helpers.h +$(BUILDDIR)/llvm-alloc-opt.o $(BUILDDIR)/llvm-alloc-opt.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-alloc-helpers.h $(BUILDDIR)/llvm-cpufeatures.o $(BUILDDIR)/llvm-cpufeatures.dbg.obj: $(SRCDIR)/jitlayers.h $(SRCDIR)/debug-registry.h -$(BUILDDIR)/llvm-final-gc-lowering.o $(BUILDDIR)/llvm-final-gc-lowering.dbg.obj: $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/codegen_shared.h -$(BUILDDIR)/llvm-gc-invariant-verifier.o $(BUILDDIR)/llvm-gc-invariant-verifier.dbg.obj: $(SRCDIR)/codegen_shared.h -$(BUILDDIR)/llvm-julia-licm.o $(BUILDDIR)/llvm-julia-licm.dbg.obj: $(SRCDIR)/codegen_shared.h $(SRCDIR)/llvm-alloc-helpers.h $(SRCDIR)/llvm-pass-helpers.h -$(BUILDDIR)/llvm-late-gc-lowering.o $(BUILDDIR)/llvm-late-gc-lowering.dbg.obj: $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/codegen_shared.h -$(BUILDDIR)/llvm-lower-handlers.o $(BUILDDIR)/llvm-lower-handlers.dbg.obj: $(SRCDIR)/codegen_shared.h -$(BUILDDIR)/llvm-multiversioning.o $(BUILDDIR)/llvm-multiversioning.dbg.obj: $(SRCDIR)/codegen_shared.h $(SRCDIR)/processor.h -$(BUILDDIR)/llvm-pass-helpers.o $(BUILDDIR)/llvm-pass-helpers.dbg.obj: $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/codegen_shared.h -$(BUILDDIR)/llvm-propagate-addrspaces.o $(BUILDDIR)/llvm-propagate-addrspaces.dbg.obj: $(SRCDIR)/codegen_shared.h -$(BUILDDIR)/llvm-remove-addrspaces.o $(BUILDDIR)/llvm-remove-addrspaces.dbg.obj: $(SRCDIR)/codegen_shared.h -$(BUILDDIR)/llvm-ptls.o $(BUILDDIR)/llvm-ptls.dbg.obj: $(SRCDIR)/codegen_shared.h +$(BUILDDIR)/llvm-final-gc-lowering.o $(BUILDDIR)/llvm-final-gc-lowering.dbg.obj: $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-codegen-shared.h +$(BUILDDIR)/llvm-gc-invariant-verifier.o $(BUILDDIR)/llvm-gc-invariant-verifier.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h +$(BUILDDIR)/llvm-julia-licm.o $(BUILDDIR)/llvm-julia-licm.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h $(SRCDIR)/llvm-alloc-helpers.h $(SRCDIR)/llvm-pass-helpers.h +$(BUILDDIR)/llvm-late-gc-lowering.o $(BUILDDIR)/llvm-late-gc-lowering.dbg.obj: $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-codegen-shared.h +$(BUILDDIR)/llvm-lower-handlers.o $(BUILDDIR)/llvm-lower-handlers.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h +$(BUILDDIR)/llvm-multiversioning.o $(BUILDDIR)/llvm-multiversioning.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h $(SRCDIR)/processor.h +$(BUILDDIR)/llvm-pass-helpers.o $(BUILDDIR)/llvm-pass-helpers.dbg.obj: $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-codegen-shared.h +$(BUILDDIR)/llvm-propagate-addrspaces.o $(BUILDDIR)/llvm-propagate-addrspaces.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h +$(BUILDDIR)/llvm-remove-addrspaces.o $(BUILDDIR)/llvm-remove-addrspaces.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h +$(BUILDDIR)/llvm-ptls.o $(BUILDDIR)/llvm-ptls.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h $(BUILDDIR)/processor.o $(BUILDDIR)/processor.dbg.obj: $(addprefix $(SRCDIR)/,processor_*.cpp processor.h features_*.h) $(BUILDDIR)/signal-handling.o $(BUILDDIR)/signal-handling.dbg.obj: $(addprefix $(SRCDIR)/,signals-*.c) $(BUILDDIR)/staticdata.o $(BUILDDIR)/staticdata.dbg.obj: $(SRCDIR)/staticdata_utils.c $(SRCDIR)/precompile_utils.c $(SRCDIR)/processor.h $(SRCDIR)/builtin_proto.h @@ -498,7 +498,6 @@ clang-tidy-%: $(SRCDIR)/%.cpp $(build_shlibdir)/libImplicitAtomicsPlugin.$(SHLIB tidysrc: $(addprefix clang-tidy-,$(filter-out $(basename $(SKIP_IMPLICIT_ATOMICS)),$(CODEGEN_SRCS) $(SRCS))) analyzesrc: $(addprefix clang-sa-,$(CODEGEN_SRCS) $(SRCS)) analyzegc: $(addprefix clang-sagc-,$(filter-out $(basename $(SKIP_GC_CHECK)),$(CODEGEN_SRCS) $(SRCS))) -analyzegc: analyzesrc tidysrc # TODO: remove me (depended on by CI currently) analyze: analyzesrc analyzegc tidysrc clean-analyzegc: diff --git a/src/aotcompile.cpp b/src/aotcompile.cpp index bd4c896a39e110..41292c58486f43 100644 --- a/src/aotcompile.cpp +++ b/src/aotcompile.cpp @@ -63,7 +63,7 @@ using namespace llvm; #include "jitlayers.h" #include "serialize.h" #include "julia_assert.h" -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #define DEBUG_TYPE "julia_aotcompile" diff --git a/src/clangsa/GCChecker.cpp b/src/clangsa/GCChecker.cpp index 513e6db606eb89..9f7a5e22d22b48 100644 --- a/src/clangsa/GCChecker.cpp +++ b/src/clangsa/GCChecker.cpp @@ -37,6 +37,16 @@ static const Stmt *getStmtForDiagnostics(const ExplodedNode *N) return N->getStmtForDiagnostics(); } +static unsigned getStackFrameHeight(const LocationContext *stack) +{ + // TODO: or use getID ? + unsigned depth = 0; + while (stack) { + depth++; + stack = stack->getParent(); + } + return depth; +} class GCChecker : public Checker< @@ -124,8 +134,8 @@ class GCChecker return ValueState(Rooted, Root, Depth); } static ValueState getForArgument(const FunctionDecl *FD, - const ParmVarDecl *PVD) { - bool isFunctionSafepoint = !isFDAnnotatedNotSafepoint(FD); + const ParmVarDecl *PVD, + bool isFunctionSafepoint) { bool maybeUnrooted = declHasAnnotation(PVD, "julia_maybe_unrooted"); if (!isFunctionSafepoint || maybeUnrooted) { ValueState VS = getAllocated(); @@ -199,8 +209,9 @@ class GCChecker bool isGloballyRootedType(QualType Type) const; static void dumpState(const ProgramStateRef &State); static bool declHasAnnotation(const clang::Decl *D, const char *which); - static bool isFDAnnotatedNotSafepoint(const clang::FunctionDecl *FD); - bool isSafepoint(const CallEvent &Call) const; + static bool isFDAnnotatedNotSafepoint(const clang::FunctionDecl *FD, const SourceManager &SM); + static const SourceManager &getSM(CheckerContext &C) { return C.getSourceManager(); } + bool isSafepoint(const CallEvent &Call, CheckerContext &C) const; bool processPotentialSafepoint(const CallEvent &Call, CheckerContext &C, ProgramStateRef &State) const; bool processAllocationOfResult(const CallEvent &Call, CheckerContext &C, @@ -214,7 +225,9 @@ class GCChecker const MemRegion *R, bool Debug = false); bool gcEnabledHere(CheckerContext &C) const; + bool gcEnabledHere(ProgramStateRef State) const; bool safepointEnabledHere(CheckerContext &C) const; + bool safepointEnabledHere(ProgramStateRef State) const; bool propagateArgumentRootedness(CheckerContext &C, ProgramStateRef &State) const; SymbolRef getSymbolForResult(const Expr *Result, const ValueState *OldValS, @@ -463,7 +476,7 @@ PDP GCChecker::GCValueBugVisitor::VisitNode(const ExplodedNode *N, } else { if (NewValueState->FD) { bool isFunctionSafepoint = - !isFDAnnotatedNotSafepoint(NewValueState->FD); + !isFDAnnotatedNotSafepoint(NewValueState->FD, BRC.getSourceManager()); bool maybeUnrooted = declHasAnnotation(NewValueState->PVD, "julia_maybe_unrooted"); assert(isFunctionSafepoint || maybeUnrooted); @@ -544,12 +557,20 @@ void GCChecker::report_value_error(CheckerContext &C, SymbolRef Sym, } bool GCChecker::gcEnabledHere(CheckerContext &C) const { - unsigned disabledAt = C.getState()->get(); + return gcEnabledHere(C.getState()); +} + +bool GCChecker::gcEnabledHere(ProgramStateRef State) const { + unsigned disabledAt = State->get(); return disabledAt == (unsigned)-1; } bool GCChecker::safepointEnabledHere(CheckerContext &C) const { - unsigned disabledAt = C.getState()->get(); + return safepointEnabledHere(C.getState()); +} + +bool GCChecker::safepointEnabledHere(ProgramStateRef State) const { + unsigned disabledAt = State->get(); return disabledAt == (unsigned)-1; } @@ -617,8 +638,8 @@ void GCChecker::checkBeginFunction(CheckerContext &C) const { // otherwise const auto *LCtx = C.getLocationContext(); const auto *FD = dyn_cast(LCtx->getDecl()); - if (!FD) - return; + assert(FD); + unsigned CurrentHeight = getStackFrameHeight(C.getStackFrame()); ProgramStateRef State = C.getState(); bool Change = false; if (C.inTopFrame()) { @@ -626,15 +647,14 @@ void GCChecker::checkBeginFunction(CheckerContext &C) const { State = State->set((unsigned)-1); Change = true; } - if (State->get() == (unsigned)-1) { - if (declHasAnnotation(FD, "julia_gc_disabled")) { - State = State->set(C.getStackFrame()->getIndex()); - Change = true; - } + if (gcEnabledHere(State) && declHasAnnotation(FD, "julia_gc_disabled")) { + State = State->set(CurrentHeight); + Change = true; } - if (State->get() == (unsigned)-1 && - isFDAnnotatedNotSafepoint(FD)) { - State = State->set(C.getStackFrame()->getIndex()); + bool isFunctionSafepoint = !isFDAnnotatedNotSafepoint(FD, getSM(C)); + if (safepointEnabledHere(State) && + (!isFunctionSafepoint || declHasAnnotation(FD, "julia_notsafepoint_leave"))) { + State = State->set(CurrentHeight); Change = true; } if (!C.inTopFrame()) { @@ -654,7 +674,7 @@ void GCChecker::checkBeginFunction(CheckerContext &C) const { continue; assert(AssignedSym); State = State->set(AssignedSym, - ValueState::getForArgument(FD, P)); + ValueState::getForArgument(FD, P, isFunctionSafepoint)); Change = true; } } @@ -666,8 +686,10 @@ void GCChecker::checkBeginFunction(CheckerContext &C) const { void GCChecker::checkEndFunction(const clang::ReturnStmt *RS, CheckerContext &C) const { ProgramStateRef State = C.getState(); + const auto *LCtx = C.getLocationContext(); + const auto *FD = dyn_cast(LCtx->getDecl()); - if (RS && gcEnabledHere(C) && RS->getRetValue() && isGCTracked(RS->getRetValue())) { + if (RS && gcEnabledHere(State) && RS->getRetValue() && isGCTracked(RS->getRetValue())) { auto ResultVal = C.getSVal(RS->getRetValue()); SymbolRef Sym = ResultVal.getAsSymbol(true); const ValueState *ValS = Sym ? State->get(Sym) : nullptr; @@ -676,12 +698,16 @@ void GCChecker::checkEndFunction(const clang::ReturnStmt *RS, } } + unsigned CurrentHeight = getStackFrameHeight(C.getStackFrame()); bool Changed = false; - if (State->get() == C.getStackFrame()->getIndex()) { + if (State->get() == CurrentHeight) { State = State->set((unsigned)-1); Changed = true; } - if (State->get() == C.getStackFrame()->getIndex()) { + if (State->get() == CurrentHeight) { + if (!isFDAnnotatedNotSafepoint(FD, getSM(C)) && !(FD && declHasAnnotation(FD, "julia_notsafepoint_enter"))) { + report_error(C, "Safepoints disabled at end of function"); + } State = State->set((unsigned)-1); Changed = true; } @@ -689,8 +715,10 @@ void GCChecker::checkEndFunction(const clang::ReturnStmt *RS, C.addTransition(State); if (!C.inTopFrame()) return; - if (C.getState()->get() > 0) + unsigned CurrentDepth = C.getState()->get(); + if (CurrentDepth != 0) { report_error(C, "Non-popped GC frame present at end of function"); + } } bool GCChecker::declHasAnnotation(const clang::Decl *D, const char *which) { @@ -701,8 +729,38 @@ bool GCChecker::declHasAnnotation(const clang::Decl *D, const char *which) { return false; } -bool GCChecker::isFDAnnotatedNotSafepoint(const clang::FunctionDecl *FD) { - return declHasAnnotation(FD, "julia_not_safepoint"); +bool GCChecker::isFDAnnotatedNotSafepoint(const clang::FunctionDecl *FD, const SourceManager &SM) { + if (declHasAnnotation(FD, "julia_not_safepoint")) + return true; + SourceLocation Loc = FD->getLocation(); + StringRef Name = SM.getFilename(Loc); + Name = llvm::sys::path::filename(Name); + if (Name.startswith("llvm-")) + return true; + return false; +} + +static bool isMutexLock(StringRef name) { + return name == "uv_mutex_lock" || + //name == "uv_mutex_trylock" || + name == "pthread_mutex_lock" || + //name == "pthread_mutex_trylock" || + name == "pthread_spin_lock" || + //name == "pthread_spin_trylock" || + name == "uv_rwlock_rdlock" || + //name == "uv_rwlock_tryrdlock" || + name == "uv_rwlock_wrlock" || + //name == "uv_rwlock_trywrlock" || + false; +} + +static bool isMutexUnlock(StringRef name) { + return name == "uv_mutex_unlock" || + name == "pthread_mutex_unlock" || + name == "pthread_spin_unlock" || + name == "uv_rwlock_rdunlock" || + name == "uv_rwlock_wrunlock" || + false; } #if LLVM_VERSION_MAJOR >= 13 @@ -779,14 +837,20 @@ bool GCChecker::isGloballyRootedType(QualType QT) const { [](StringRef Name) { return Name.endswith("jl_sym_t"); }, QT); } -bool GCChecker::isSafepoint(const CallEvent &Call) const { +bool GCChecker::isSafepoint(const CallEvent &Call, CheckerContext &C) const { bool isCalleeSafepoint = true; if (Call.isInSystemHeader()) { // defined by -isystem per // https://clang.llvm.org/docs/UsersManual.html#controlling-diagnostics-in-system-headers isCalleeSafepoint = false; } else { - auto *Decl = Call.getDecl(); + const clang::Decl *Decl = Call.getDecl(); // we might not have a simple call, or we might have an SVal + const clang::Expr *Callee = nullptr; + if (auto CE = dyn_cast_or_null(Call.getOriginExpr())) { + Callee = CE->getCallee(); + if (Decl == nullptr) + Decl = CE->getCalleeDecl(); // ignores dyn_cast, so it could also be a MemberDecl, etc. + } const DeclContext *DC = Decl ? Decl->getDeclContext() : nullptr; while (DC) { // Anything in llvm or std is not a safepoint @@ -797,9 +861,9 @@ bool GCChecker::isSafepoint(const CallEvent &Call) const { } const FunctionDecl *FD = Decl ? Decl->getAsFunction() : nullptr; if (!Decl || !FD) { - const clang::Expr *Callee = - dyn_cast(Call.getOriginExpr())->getCallee(); - if (const TypedefType *TDT = dyn_cast(Callee->getType())) { + if (Callee == nullptr) { + isCalleeSafepoint = true; + } else if (const TypedefType *TDT = dyn_cast(Callee->getType())) { isCalleeSafepoint = !declHasAnnotation(TDT->getDecl(), "julia_not_safepoint"); } else if (const CXXPseudoDestructorExpr *PDE = @@ -820,7 +884,7 @@ bool GCChecker::isSafepoint(const CallEvent &Call) const { FD->getName() != "uv_run") isCalleeSafepoint = false; else - isCalleeSafepoint = !isFDAnnotatedNotSafepoint(FD); + isCalleeSafepoint = !isFDAnnotatedNotSafepoint(FD, getSM(C)); } } return isCalleeSafepoint; @@ -829,7 +893,7 @@ bool GCChecker::isSafepoint(const CallEvent &Call) const { bool GCChecker::processPotentialSafepoint(const CallEvent &Call, CheckerContext &C, ProgramStateRef &State) const { - if (!isSafepoint(Call)) + if (!isSafepoint(Call, C)) return false; bool DidChange = false; if (!gcEnabledHere(C)) @@ -1113,8 +1177,9 @@ void GCChecker::checkDerivingExpr(const Expr *Result, const Expr *Parent, dyn_cast(C.getLocationContext()->getDecl()); if (FD) { inheritedState = true; + bool isFunctionSafepoint = !isFDAnnotatedNotSafepoint(FD, getSM(C)); Updated = - ValueState::getForArgument(FD, cast(VR->getDecl())); + ValueState::getForArgument(FD, cast(VR->getDecl()), isFunctionSafepoint); } } else { VR = Helpers::walk_back_to_global_VR(Region); @@ -1222,10 +1287,21 @@ void GCChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { return; unsigned NumArgs = Call.getNumArgs(); ProgramStateRef State = C.getState(); - bool isCalleeSafepoint = isSafepoint(Call); + bool isCalleeSafepoint = isSafepoint(Call, C); auto *Decl = Call.getDecl(); const FunctionDecl *FD = Decl ? Decl->getAsFunction() : nullptr; - if (!safepointEnabledHere(C) && isCalleeSafepoint) { + StringRef FDName = + FD && FD->getDeclName().isIdentifier() ? FD->getName() : ""; + if (isMutexUnlock(FDName) || (FD && declHasAnnotation(FD, "julia_notsafepoint_leave"))) { + const auto *LCtx = C.getLocationContext(); + const auto *FD = dyn_cast(LCtx->getDecl()); + if (State->get() == getStackFrameHeight(C.getStackFrame()) && + !isFDAnnotatedNotSafepoint(FD, getSM(C))) { + State = State->set((unsigned)-1); + C.addTransition(State); + } + } + if (!safepointEnabledHere(State) && isCalleeSafepoint) { // Suppress this warning if the function is noreturn. // We could separate out "not safepoint, except for noreturn functions", // but that seems like a lot of effort with little benefit. @@ -1448,7 +1524,7 @@ bool GCChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { } else { cast(Arg.getAsSymbol())->getStmt()->dump(); } - bool EnabledNow = State->get() == (unsigned)-1; + bool EnabledNow = gcEnabledHere(State); if (!EnabledAfter) { State = State->set((unsigned)-2); } else { @@ -1460,22 +1536,16 @@ bool GCChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { C.addTransition(State->BindExpr(CE, C.getLocationContext(), Result)); return true; } - else if (name == "uv_mutex_lock") { - ProgramStateRef State = C.getState(); - if (State->get() == (unsigned)-1) { - C.addTransition(State->set(C.getStackFrame()->getIndex())); - return true; - } - } - else if (name == "uv_mutex_unlock") { - ProgramStateRef State = C.getState(); - const auto *LCtx = C.getLocationContext(); - const auto *FD = dyn_cast(LCtx->getDecl()); - if (State->get() == (unsigned)C.getStackFrame()->getIndex() && - !isFDAnnotatedNotSafepoint(FD)) { - C.addTransition(State->set(-1)); - return true; - } + { + auto *Decl = Call.getDecl(); + const FunctionDecl *FD = Decl ? Decl->getAsFunction() : nullptr; + if (isMutexLock(name) || (FD && declHasAnnotation(FD, "julia_notsafepoint_enter"))) { + ProgramStateRef State = C.getState(); + if (State->get() == (unsigned)-1) { + C.addTransition(State->set(getStackFrameHeight(C.getStackFrame()))); + return true; + } + } } return false; } diff --git a/src/codegen.cpp b/src/codegen.cpp index 31bfb4b5ca51d1..4ca5795b3e95be 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -156,7 +156,7 @@ typedef Instruction TerminatorInst; #endif #include "jitlayers.h" -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #include "processor.h" #include "julia_assert.h" @@ -188,7 +188,7 @@ STATISTIC(EmittedFunctions, "Number of functions emitted"); extern "C" JL_DLLEXPORT void jl_dump_emitted_mi_name_impl(void *s) { - **jl_ExecutionEngine->get_dump_emitted_mi_name_stream() = (JL_STREAM*)s; + **jl_ExecutionEngine->get_dump_emitted_mi_name_stream() = (ios_t*)s; } extern "C" { diff --git a/src/debuginfo.cpp b/src/debuginfo.cpp index 997c04aff6445a..b654846ee113af 100644 --- a/src/debuginfo.cpp +++ b/src/debuginfo.cpp @@ -51,8 +51,8 @@ struct debug_link_info { }; #if (defined(_OS_LINUX_) || defined(_OS_FREEBSD_) || (defined(_OS_DARWIN_) && defined(LLVM_SHLIB))) -extern "C" void __register_frame(void*); -extern "C" void __deregister_frame(void*); +extern "C" void __register_frame(void*) JL_NOTSAFEPOINT; +extern "C" void __deregister_frame(void*) JL_NOTSAFEPOINT; template static void processFDEs(const char *EHFrameAddr, size_t EHFrameSize, callback f) @@ -77,7 +77,7 @@ static void processFDEs(const char *EHFrameAddr, size_t EHFrameSize, callback f) } #endif -std::string JITDebugInfoRegistry::mangle(StringRef Name, const DataLayout &DL) JL_NOTSAFEPOINT +std::string JITDebugInfoRegistry::mangle(StringRef Name, const DataLayout &DL) { std::string MangledName; { @@ -87,11 +87,11 @@ std::string JITDebugInfoRegistry::mangle(StringRef Name, const DataLayout &DL) J return MangledName; } -void JITDebugInfoRegistry::add_code_in_flight(StringRef name, jl_code_instance_t *codeinst, const DataLayout &DL) JL_NOTSAFEPOINT { +void JITDebugInfoRegistry::add_code_in_flight(StringRef name, jl_code_instance_t *codeinst, const DataLayout &DL) { (**codeinst_in_flight)[mangle(name, DL)] = codeinst; } -jl_method_instance_t *JITDebugInfoRegistry::lookupLinfo(size_t pointer) JL_NOTSAFEPOINT +jl_method_instance_t *JITDebugInfoRegistry::lookupLinfo(size_t pointer) { jl_lock_profile(); auto region = linfomap.lower_bound(pointer); @@ -104,17 +104,17 @@ jl_method_instance_t *JITDebugInfoRegistry::lookupLinfo(size_t pointer) JL_NOTSA //Protected by debuginfo_asyncsafe (profile) lock JITDebugInfoRegistry::objectmap_t & -JITDebugInfoRegistry::getObjectMap() JL_NOTSAFEPOINT +JITDebugInfoRegistry::getObjectMap() { return objectmap; } -void JITDebugInfoRegistry::add_image_info(image_info_t info) JL_NOTSAFEPOINT { +void JITDebugInfoRegistry::add_image_info(image_info_t info) { (**this->image_info)[info.base] = info; } -bool JITDebugInfoRegistry::get_image_info(uint64_t base, JITDebugInfoRegistry::image_info_t *info) const JL_NOTSAFEPOINT { +bool JITDebugInfoRegistry::get_image_info(uint64_t base, JITDebugInfoRegistry::image_info_t *info) const { auto infos = *this->image_info; auto it = infos->find(base); if (it != infos->end()) { @@ -125,11 +125,11 @@ bool JITDebugInfoRegistry::get_image_info(uint64_t base, JITDebugInfoRegistry::i } JITDebugInfoRegistry::Locked::LockT -JITDebugInfoRegistry::get_objfile_map() JL_NOTSAFEPOINT { +JITDebugInfoRegistry::get_objfile_map() { return *this->objfilemap; } -JITDebugInfoRegistry::JITDebugInfoRegistry() JL_NOTSAFEPOINT { } +JITDebugInfoRegistry::JITDebugInfoRegistry() { } struct unw_table_entry { @@ -140,7 +140,7 @@ struct unw_table_entry // some actions aren't signal (especially profiler) safe so we acquire a lock // around them to establish a mutual exclusion with unwinding from a signal template -static void jl_profile_atomic(T f) +static void jl_profile_atomic(T f) JL_NOTSAFEPOINT { assert(0 == jl_lock_profile_rd_held()); jl_lock_profile_wr(); @@ -187,7 +187,7 @@ static void create_PRUNTIME_FUNCTION(uint8_t *Code, size_t Size, StringRef fnnam if (mod_size && !SymLoadModuleEx(GetCurrentProcess(), NULL, NULL, NULL, (DWORD64)Section, mod_size, NULL, SLMFLAG_VIRTUAL)) { static int warned = 0; if (!warned) { - jl_printf(JL_STDERR, "WARNING: failed to insert module info for backtrace: %lu\n", GetLastError()); + jl_safe_printf("WARNING: failed to insert module info for backtrace: %lu\n", GetLastError()); warned = 1; } } @@ -200,17 +200,17 @@ static void create_PRUNTIME_FUNCTION(uint8_t *Code, size_t Size, StringRef fnnam name[len-1] = 0; if (!SymAddSymbol(GetCurrentProcess(), (ULONG64)Section, name, (DWORD64)Code, (DWORD)Size, 0)) { - jl_printf(JL_STDERR, "WARNING: failed to insert function name %s into debug info: %lu\n", name, GetLastError()); + jl_safe_printf("WARNING: failed to insert function name %s into debug info: %lu\n", name, GetLastError()); } } uv_mutex_unlock(&jl_in_stackwalk); } #if defined(_CPU_X86_64_) - jl_profile_atomic([&]() { + jl_profile_atomic([&]() JL_NOTSAFEPOINT { if (!RtlAddFunctionTable(tbl, 1, (DWORD64)Section)) { static int warned = 0; if (!warned) { - jl_printf(JL_STDERR, "WARNING: failed to insert function stack unwind info: %lu\n", GetLastError()); + jl_safe_printf("WARNING: failed to insert function stack unwind info: %lu\n", GetLastError()); warned = 1; } } @@ -268,7 +268,7 @@ void JITDebugInfoRegistry::registerJITObject(const object::ObjectFile &Object, di->u.rti.name_ptr = 0; di->u.rti.table_data = arm_exidx_addr; di->u.rti.table_len = arm_exidx_len; - jl_profile_atomic([&]() { + jl_profile_atomic([&]() JL_NOTSAFEPOINT { _U_dyn_register(di); }); break; @@ -370,7 +370,7 @@ void JITDebugInfoRegistry::registerJITObject(const object::ObjectFile &Object, codeinst_in_flight.erase(codeinst_it); } } - jl_profile_atomic([&]() { + jl_profile_atomic([&]() JL_NOTSAFEPOINT { if (codeinst) linfomap[Addr] = std::make_pair(Size, codeinst->def); if (first) { @@ -388,7 +388,7 @@ void JITDebugInfoRegistry::registerJITObject(const object::ObjectFile &Object, void jl_register_jit_object(const object::ObjectFile &Object, std::function getLoadAddress, - std::function lookupWriteAddress) + std::function lookupWriteAddress) JL_NOTSAFEPOINT { getJITDebugRegistry().registerJITObject(Object, getLoadAddress, lookupWriteAddress); } @@ -544,7 +544,7 @@ void JITDebugInfoRegistry::libc_frames_t::libc_register_frame(const char *Entry) jl_atomic_store_release(&this->libc_register_frame_, libc_register_frame_); } assert(libc_register_frame_); - jl_profile_atomic([&]() { + jl_profile_atomic([&]() JL_NOTSAFEPOINT { libc_register_frame_(const_cast(Entry)); __register_frame(const_cast(Entry)); }); @@ -557,7 +557,7 @@ void JITDebugInfoRegistry::libc_frames_t::libc_deregister_frame(const char *Entr jl_atomic_store_release(&this->libc_deregister_frame_, libc_deregister_frame_); } assert(libc_deregister_frame_); - jl_profile_atomic([&]() { + jl_profile_atomic([&]() JL_NOTSAFEPOINT { libc_deregister_frame_(const_cast(Entry)); __deregister_frame(const_cast(Entry)); }); @@ -601,7 +601,7 @@ static debug_link_info getDebuglink(const object::ObjectFile &Obj) JL_NOTSAFEPOI * code or tables extracted from it, as desired without restriction. */ static uint32_t -calc_gnu_debuglink_crc32(const void *buf, size_t size) +calc_gnu_debuglink_crc32(const void *buf, size_t size) JL_NOTSAFEPOINT { static const uint32_t g_crc32_tab[] = { @@ -659,7 +659,7 @@ calc_gnu_debuglink_crc32(const void *buf, size_t size) } static Expected> -openDebugInfo(StringRef debuginfopath, const debug_link_info &info) +openDebugInfo(StringRef debuginfopath, const debug_link_info &info) JL_NOTSAFEPOINT { auto SplitFile = MemoryBuffer::getFile(debuginfopath); if (std::error_code EC = SplitFile.getError()) { @@ -1450,7 +1450,7 @@ static DW_EH_PE parseCIE(const uint8_t *Addr, const uint8_t *End) void register_eh_frames(uint8_t *Addr, size_t Size) { // System unwinder - jl_profile_atomic([&]() { + jl_profile_atomic([&]() JL_NOTSAFEPOINT { __register_frame(Addr); }); @@ -1578,14 +1578,14 @@ void register_eh_frames(uint8_t *Addr, size_t Size) di->start_ip = start_ip; di->end_ip = end_ip; - jl_profile_atomic([&]() { + jl_profile_atomic([&]() JL_NOTSAFEPOINT { _U_dyn_register(di); }); } void deregister_eh_frames(uint8_t *Addr, size_t Size) { - jl_profile_atomic([&]() { + jl_profile_atomic([&]() JL_NOTSAFEPOINT { __deregister_frame(Addr); }); // Deregistering with our unwinder (_U_dyn_cancel) requires a lookup table diff --git a/src/disasm.cpp b/src/disasm.cpp index 5b510a24b33da8..e693fe74275709 100644 --- a/src/disasm.cpp +++ b/src/disasm.cpp @@ -133,10 +133,11 @@ class DILineInfoPrinter { output_source = 1, } verbosity = output_source; public: - DILineInfoPrinter(const char *LineStart, bool bracket_outer) + DILineInfoPrinter(const char *LineStart, bool bracket_outer) JL_NOTSAFEPOINT : LineStart(LineStart), bracket_outer(bracket_outer) {}; - void SetVerbosity(const char *c) + ~DILineInfoPrinter() JL_NOTSAFEPOINT = default; + void SetVerbosity(const char *c) JL_NOTSAFEPOINT { if (StringRef("default") == c) { verbosity = output_source; @@ -149,14 +150,14 @@ class DILineInfoPrinter { } } - void emit_finish(raw_ostream &Out); - void emit_lineinfo(raw_ostream &Out, std::vector &DI); + void emit_finish(raw_ostream &Out) JL_NOTSAFEPOINT; + void emit_lineinfo(raw_ostream &Out, std::vector &DI) JL_NOTSAFEPOINT; struct repeat { size_t times; const char *c; }; - struct repeat inlining_indent(const char *c) + struct repeat inlining_indent(const char *c) JL_NOTSAFEPOINT { return repeat{ std::max(inline_depth + bracket_outer, (uint32_t)1) - 1, @@ -164,20 +165,20 @@ class DILineInfoPrinter { } template - void emit_lineinfo(std::string &Out, T &DI) + void emit_lineinfo(std::string &Out, T &DI) JL_NOTSAFEPOINT { raw_string_ostream OS(Out); emit_lineinfo(OS, DI); } - void emit_lineinfo(raw_ostream &Out, DILineInfo &DI) + void emit_lineinfo(raw_ostream &Out, DILineInfo &DI) JL_NOTSAFEPOINT { std::vector DIvec(1); DIvec[0] = DI; emit_lineinfo(Out, DIvec); } - void emit_lineinfo(raw_ostream &Out, DIInliningInfo &DI) + void emit_lineinfo(raw_ostream &Out, DIInliningInfo &DI) JL_NOTSAFEPOINT { uint32_t nframes = DI.getNumberOfFrames(); std::vector DIvec(nframes); @@ -187,14 +188,14 @@ class DILineInfoPrinter { emit_lineinfo(Out, DIvec); } - void emit_finish(std::string &Out) + void emit_finish(std::string &Out) JL_NOTSAFEPOINT { raw_string_ostream OS(Out); emit_finish(OS); } }; -static raw_ostream &operator<<(raw_ostream &Out, struct DILineInfoPrinter::repeat i) +static raw_ostream &operator<<(raw_ostream &Out, struct DILineInfoPrinter::repeat i) JL_NOTSAFEPOINT { while (i.times-- > 0) Out << i.c; @@ -336,27 +337,28 @@ class LineNumberAnnotatedWriter : public AssemblyAnnotationWriter { DenseMap DebugLoc; DenseMap Subprogram; public: - LineNumberAnnotatedWriter(const char *LineStart, bool bracket_outer, const char *debuginfo) + LineNumberAnnotatedWriter(const char *LineStart, bool bracket_outer, const char *debuginfo) JL_NOTSAFEPOINT : LinePrinter(LineStart, bracket_outer) { LinePrinter.SetVerbosity(debuginfo); } - virtual void emitFunctionAnnot(const Function *, formatted_raw_ostream &); - virtual void emitInstructionAnnot(const Instruction *, formatted_raw_ostream &); - virtual void emitInstructionAnnot(const DILocation *, formatted_raw_ostream &); - virtual void emitBasicBlockEndAnnot(const BasicBlock *, formatted_raw_ostream &); - // virtual void printInfoComment(const Value &, formatted_raw_ostream &) {} - - void emitEnd(formatted_raw_ostream &Out) { + ~LineNumberAnnotatedWriter() JL_NOTSAFEPOINT = default; + virtual void emitFunctionAnnot(const Function *, formatted_raw_ostream &) JL_NOTSAFEPOINT; + virtual void emitInstructionAnnot(const Instruction *, formatted_raw_ostream &) JL_NOTSAFEPOINT; + virtual void emitInstructionAnnot(const DILocation *, formatted_raw_ostream &) JL_NOTSAFEPOINT; + virtual void emitBasicBlockEndAnnot(const BasicBlock *, formatted_raw_ostream &) JL_NOTSAFEPOINT; + // virtual void printInfoComment(const Value &, formatted_raw_ostream &) JL_NOTSAFEPOINT {} + + void emitEnd(formatted_raw_ostream &Out) JL_NOTSAFEPOINT { LinePrinter.emit_finish(Out); InstrLoc = nullptr; } - void addSubprogram(const Function *F, DISubprogram *SP) + void addSubprogram(const Function *F, DISubprogram *SP) JL_NOTSAFEPOINT { Subprogram[F] = SP; } - void addDebugLoc(const Instruction *I, DILocation *Loc) + void addDebugLoc(const Instruction *I, DILocation *Loc) JL_NOTSAFEPOINT { DebugLoc[I] = Loc; } @@ -422,7 +424,7 @@ void LineNumberAnnotatedWriter::emitBasicBlockEndAnnot( emitEnd(Out); } -static void jl_strip_llvm_debug(Module *m, bool all_meta, LineNumberAnnotatedWriter *AAW) +static void jl_strip_llvm_debug(Module *m, bool all_meta, LineNumberAnnotatedWriter *AAW) JL_NOTSAFEPOINT { // strip metadata from all instructions in all functions in the module Instruction *deletelast = nullptr; // can't actually delete until the iterator advances @@ -473,18 +475,16 @@ static void jl_strip_llvm_debug(Module *m, bool all_meta, LineNumberAnnotatedWri // m->eraseNamedMetadata(md); } -void jl_strip_llvm_debug(Module *m) +void jl_strip_llvm_debug(Module *m) JL_NOTSAFEPOINT { jl_strip_llvm_debug(m, false, NULL); } -void jl_strip_llvm_addrspaces(Module *m) +void jl_strip_llvm_addrspaces(Module *m) JL_NOTSAFEPOINT { PassBuilder PB; AnalysisManagers AM(PB); - ModulePassManager MPM; - MPM.addPass(RemoveJuliaAddrspacesPass()); - MPM.run(*m, AM.MAM); + RemoveJuliaAddrspacesPass().run(*m, AM.MAM); } // print an llvm IR acquired from jl_get_llvmf @@ -543,7 +543,7 @@ static void jl_dump_asm_internal( raw_ostream &rstream, const char* asm_variant, const char* debuginfo, - bool binary); + bool binary) JL_NOTSAFEPOINT; // This isn't particularly fast, but neither is printing assembly, and they're only used for interactive mode static uint64_t compute_obj_symsize(object::SectionRef Section, uint64_t offset) @@ -642,20 +642,21 @@ class SymbolTable { uint64_t ip; // virtual instruction pointer of the current instruction int64_t slide; public: - SymbolTable(MCContext &Ctx, const object::ObjectFile *object, int64_t slide, const FuncMCView &MemObj): - Ctx(Ctx), MemObj(MemObj), object(object), ip(0), slide(slide) {} - const FuncMCView &getMemoryObject() const { return MemObj; } - void setPass(int Pass) { this->Pass = Pass; } - int getPass() const { return Pass; } - void insertAddress(uint64_t addr); + SymbolTable(MCContext &Ctx, const object::ObjectFile *object, int64_t slide, const FuncMCView &MemObj) JL_NOTSAFEPOINT + : Ctx(Ctx), MemObj(MemObj), object(object), ip(0), slide(slide) {} + ~SymbolTable() JL_NOTSAFEPOINT = default; + const FuncMCView &getMemoryObject() const JL_NOTSAFEPOINT { return MemObj; } + void setPass(int Pass) JL_NOTSAFEPOINT { this->Pass = Pass; } + int getPass() const JL_NOTSAFEPOINT { return Pass; } + void insertAddress(uint64_t addr) JL_NOTSAFEPOINT; // void createSymbol(const char *name, uint64_t addr); - void createSymbols(); - const char *lookupSymbolName(uint64_t addr); - MCSymbol *lookupSymbol(uint64_t addr); - StringRef getSymbolNameAt(uint64_t offset) const; - const char *lookupLocalPC(size_t addr); - void setIP(uint64_t addr); - uint64_t getIP() const; + void createSymbols() JL_NOTSAFEPOINT; + const char *lookupSymbolName(uint64_t addr) JL_NOTSAFEPOINT; + MCSymbol *lookupSymbol(uint64_t addr) JL_NOTSAFEPOINT; + StringRef getSymbolNameAt(uint64_t offset) const JL_NOTSAFEPOINT; + const char *lookupLocalPC(size_t addr) JL_NOTSAFEPOINT; + void setIP(uint64_t addr) JL_NOTSAFEPOINT; + uint64_t getIP() const JL_NOTSAFEPOINT; }; void SymbolTable::setIP(uint64_t addr) @@ -1173,6 +1174,7 @@ class LineNumberPrinterHandler : public AsmPrinterHandler { LinePrinter("; ", true, debuginfo), RawStream(Buffer), Stream(RawStream) {} + ~LineNumberPrinterHandler() JL_NOTSAFEPOINT = default; void emitAndReset() { Stream.flush(); diff --git a/src/dlload.c b/src/dlload.c index dd5d75da31a34e..c1a8e02268ba23 100644 --- a/src/dlload.c +++ b/src/dlload.c @@ -409,11 +409,7 @@ JL_DLLEXPORT int jl_dlsym(void *handle, const char *symbol, void ** value, int t char err[256]; win32_formatmessage(GetLastError(), err, sizeof(err)); #endif -#ifndef __clang_gcanalyzer__ - // Hide the error throwing from the analyser since there isn't a way to express - // "safepoint only when throwing error" currently. jl_errorf("could not load symbol \"%s\":\n%s", symbol, err); -#endif } return symbol_found; } diff --git a/src/gc.c b/src/gc.c index 92e7e00a6c2810..9c1301eb1c938e 100644 --- a/src/gc.c +++ b/src/gc.c @@ -360,7 +360,7 @@ static void finalize_object(arraylist_t *list, jl_value_t *o, // The first two entries are assumed to be empty and the rest are assumed to // be pointers to `jl_value_t` objects -static void jl_gc_push_arraylist(jl_task_t *ct, arraylist_t *list) +static void jl_gc_push_arraylist(jl_task_t *ct, arraylist_t *list) JL_NOTSAFEPOINT { void **items = list->items; items[0] = (void*)JL_GC_ENCODE_PUSHARGS(list->len - 2); @@ -371,7 +371,7 @@ static void jl_gc_push_arraylist(jl_task_t *ct, arraylist_t *list) // Same assumption as `jl_gc_push_arraylist`. Requires the finalizers lock // to be hold for the current thread and will release the lock when the // function returns. -static void jl_gc_run_finalizers_in_list(jl_task_t *ct, arraylist_t *list) +static void jl_gc_run_finalizers_in_list(jl_task_t *ct, arraylist_t *list) JL_NOTSAFEPOINT_LEAVE { // Avoid marking `ct` as non-migratable via an `@async` task (as noted in the docstring // of `finalizer`) in a finalizer: @@ -395,7 +395,7 @@ static void jl_gc_run_finalizers_in_list(jl_task_t *ct, arraylist_t *list) static uint64_t finalizer_rngState[4]; -void jl_rng_split(uint64_t to[4], uint64_t from[4]); +void jl_rng_split(uint64_t to[4], uint64_t from[4]) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_gc_init_finalizer_rng_state(void) { @@ -3490,13 +3490,15 @@ JL_DLLEXPORT void jl_gc_collect(jl_gc_collection_t collection) gc_cblist_pre_gc, (collection)); if (!jl_atomic_load_relaxed(&jl_gc_disable_counter)) { - JL_LOCK_NOGC(&finalizers_lock); + JL_LOCK_NOGC(&finalizers_lock); // all the other threads are stopped, so this does not make sense, right? otherwise, failing that, this seems like plausibly a deadlock +#ifndef __clang_gcanalyzer__ if (_jl_gc_collect(ptls, collection)) { // recollect int ret = _jl_gc_collect(ptls, JL_GC_AUTO); (void)ret; assert(!ret); } +#endif JL_UNLOCK_NOGC(&finalizers_lock); } @@ -3889,8 +3891,8 @@ static void *gc_perm_alloc_large(size_t sz, int zero, unsigned align, unsigned o #ifdef _OS_WINDOWS_ DWORD last_error = GetLastError(); #endif - uintptr_t base = (uintptr_t)(zero ? calloc(1, sz) : malloc(sz)); - if (base == 0) + void *base = zero ? calloc(1, sz) : malloc(sz); + if (base == NULL) jl_throw(jl_memory_exception); #ifdef _OS_WINDOWS_ SetLastError(last_error); @@ -3898,8 +3900,8 @@ static void *gc_perm_alloc_large(size_t sz, int zero, unsigned align, unsigned o errno = last_errno; jl_may_leak(base); assert(align > 0); - unsigned diff = (offset - base) % align; - return (void*)(base + diff); + unsigned diff = (offset - (uintptr_t)base) % align; + return (void*)((char*)base + diff); } STATIC_INLINE void *gc_try_perm_alloc_pool(size_t sz, unsigned align, unsigned offset) JL_NOTSAFEPOINT diff --git a/src/gc.h b/src/gc.h index dfd42130898808..35767b75ea3f83 100644 --- a/src/gc.h +++ b/src/gc.h @@ -378,7 +378,7 @@ typedef struct { int ub; } pagetable_t; -#ifdef __clang_gcanalyzer__ +#ifdef __clang_gcanalyzer__ /* clang may not have __builtin_ffs */ unsigned ffs_u32(uint32_t bitvec) JL_NOTSAFEPOINT; #else STATIC_INLINE unsigned ffs_u32(uint32_t bitvec) diff --git a/src/gf.c b/src/gf.c index 2828d0e22e2d9e..481297765ff6c2 100644 --- a/src/gf.c +++ b/src/gf.c @@ -1678,7 +1678,7 @@ static int typemap_search(jl_typemap_entry_t *entry, void *closure) static jl_typemap_entry_t *do_typemap_search(jl_methtable_t *mt JL_PROPAGATES_ROOT, jl_method_t *method) JL_NOTSAFEPOINT; -#ifndef __clang_gcanalyzer__ +#ifndef __clang_gcanalyzer__ /* in general, jl_typemap_visitor could be a safepoint, but not for typemap_search */ static jl_typemap_entry_t *do_typemap_search(jl_methtable_t *mt JL_PROPAGATES_ROOT, jl_method_t *method) JL_NOTSAFEPOINT { jl_value_t *closure = (jl_value_t*)(method); if (jl_typemap_visitor(jl_atomic_load_relaxed(&mt->defs), typemap_search, &closure)) diff --git a/src/init.c b/src/init.c index 19e4dafef44d38..18e4d41eb6d792 100644 --- a/src/init.c +++ b/src/init.c @@ -237,7 +237,7 @@ JL_DLLEXPORT void jl_raise(int signo) #endif } -JL_DLLEXPORT void jl_atexit_hook(int exitcode) +JL_DLLEXPORT void jl_atexit_hook(int exitcode) JL_NOTSAFEPOINT_ENTER { uv_tty_reset_mode(); @@ -807,13 +807,13 @@ static NOINLINE void _finish_julia_init(JL_IMAGE_SEARCH rel, jl_ptls_t ptls, jl_ jl_preload_sysimg_so(jl_options.image_file); if (jl_options.cpu_target == NULL) jl_options.cpu_target = "native"; + jl_init_codegen(); if (jl_options.image_file) { jl_restore_system_image(jl_options.image_file); } else { jl_init_types(); jl_global_roots_table = jl_alloc_vec_any(0); - jl_init_codegen(); } jl_init_common_symbols(); diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index fecbc28cf8ea71..8c9637d72d3943 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -37,7 +37,7 @@ using namespace llvm; -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #include "jitlayers.h" #include "julia_assert.h" #include "processor.h" @@ -128,23 +128,23 @@ static void *getTLSAddress(void *control) extern "C" JL_DLLEXPORT void jl_dump_compiles_impl(void *s) { - **jl_ExecutionEngine->get_dump_compiles_stream() = (JL_STREAM*)s; + **jl_ExecutionEngine->get_dump_compiles_stream() = (ios_t*)s; } extern "C" JL_DLLEXPORT void jl_dump_llvm_opt_impl(void *s) { - **jl_ExecutionEngine->get_dump_llvm_opt_stream() = (JL_STREAM*)s; + **jl_ExecutionEngine->get_dump_llvm_opt_stream() = (ios_t*)s; } static int jl_add_to_ee( orc::ThreadSafeModule &M, const StringMap &NewExports, DenseMap &Queued, - std::vector &Stack); -static void jl_decorate_module(Module &M); -static uint64_t getAddressForFunction(StringRef fname); + std::vector &Stack) JL_NOTSAFEPOINT; +static void jl_decorate_module(Module &M) JL_NOTSAFEPOINT; +static uint64_t getAddressForFunction(StringRef fname) JL_NOTSAFEPOINT; -void jl_link_global(GlobalVariable *GV, void *addr) +void jl_link_global(GlobalVariable *GV, void *addr) JL_NOTSAFEPOINT { ++LinkedGlobals; Constant *P = literal_static_pointer_val(addr, GV->getValueType()); @@ -162,7 +162,7 @@ void jl_link_global(GlobalVariable *GV, void *addr) } } -void jl_jit_globals(std::map &globals) +void jl_jit_globals(std::map &globals) JL_NOTSAFEPOINT { for (auto &global : globals) { jl_link_global(global.second, global.first); @@ -288,9 +288,9 @@ static jl_callptr_t _jl_compile_codeinst( if (jl_is_method(mi->def.method)) { auto stream = *jl_ExecutionEngine->get_dump_compiles_stream(); if (stream) { - jl_printf(stream, "%" PRIu64 "\t\"", end_time - start_time); - jl_static_show(stream, mi->specTypes); - jl_printf(stream, "\"\n"); + ios_printf(stream, "%" PRIu64 "\t\"", end_time - start_time); + jl_static_show((JL_STREAM*)stream, mi->specTypes); + ios_printf(stream, "\"\n"); } } return fptr; @@ -585,7 +585,7 @@ CodeGenOpt::Level CodeGenOptLevelFor(int optlevel) #endif } -static auto countBasicBlocks(const Function &F) +static auto countBasicBlocks(const Function &F) JL_NOTSAFEPOINT { return std::distance(F.begin(), F.end()); } @@ -620,7 +620,7 @@ void JuliaOJIT::OptSelLayerT::emit(std::unique_ptr getLoadAddress, - std::function lookupWriteAddress); + std::function lookupWriteAddress) JL_NOTSAFEPOINT; #ifdef JL_USE_JITLINK @@ -940,8 +940,7 @@ void registerRTDyldJITObject(const object::ObjectFile &Object, } #endif namespace { - std::unique_ptr createTargetMachine() { - + static std::unique_ptr createTargetMachine() JL_NOTSAFEPOINT { TargetOptions options = TargetOptions(); #if defined(_OS_WINDOWS_) // use ELF because RuntimeDyld COFF i686 support didn't exist @@ -976,18 +975,15 @@ namespace { std::string errorstr; const Target *TheTarget = TargetRegistry::lookupTarget("", TheTriple, errorstr); if (!TheTarget) { - // Note we are explicitly not using `jl_errorf()` here, as it will attempt to - // collect a backtrace, but we're too early in LLVM initialization for that. - jl_printf(JL_STDERR, "ERROR: %s", errorstr.c_str()); - exit(1); + jl_errorf("Internal problem with process triple %s lookup: %s", TheTriple.str().c_str(), errorstr.c_str()); + return nullptr; } if (jl_processor_print_help || (target_flags & JL_TARGET_UNKNOWN_NAME)) { std::unique_ptr MSTI( TheTarget->createMCSubtargetInfo(TheTriple.str(), "", "")); if (!MSTI->isCPUStringValid(TheCPU)) { - // Same as above, we are too early to use `jl_errorf()` here. - jl_printf(JL_STDERR, "ERROR: Invalid CPU name \"%s\".", TheCPU.c_str()); - exit(1); + jl_errorf("Invalid CPU name \"%s\".", TheCPU.c_str()); + return nullptr; } if (jl_processor_print_help) { // This is the only way I can find to print the help message once. @@ -1023,7 +1019,7 @@ namespace { true // JIT ); assert(TM && "Failed to select target machine -" - " Is the LLVM backend for this CPU enabled?"); + " Is the LLVM backend for this CPU enabled?"); #if (!defined(_CPU_ARM_) && !defined(_CPU_PPC64_)) // FastISel seems to be buggy for ARM. Ref #13321 if (jl_options.opt_level < 2) @@ -1041,22 +1037,23 @@ namespace { typedef NewPM PassManager; #endif - orc::JITTargetMachineBuilder createJTMBFromTM(TargetMachine &TM, int optlevel) { + orc::JITTargetMachineBuilder createJTMBFromTM(TargetMachine &TM, int optlevel) JL_NOTSAFEPOINT { return orc::JITTargetMachineBuilder(TM.getTargetTriple()) - .setCPU(TM.getTargetCPU().str()) - .setFeatures(TM.getTargetFeatureString()) - .setOptions(TM.Options) - .setRelocationModel(Reloc::Static) - .setCodeModel(TM.getCodeModel()) - .setCodeGenOptLevel(CodeGenOptLevelFor(optlevel)); + .setCPU(TM.getTargetCPU().str()) + .setFeatures(TM.getTargetFeatureString()) + .setOptions(TM.Options) + .setRelocationModel(Reloc::Static) + .setCodeModel(TM.getCodeModel()) + .setCodeGenOptLevel(CodeGenOptLevelFor(optlevel)); } struct TMCreator { orc::JITTargetMachineBuilder JTMB; - TMCreator(TargetMachine &TM, int optlevel) : JTMB(createJTMBFromTM(TM, optlevel)) {} + TMCreator(TargetMachine &TM, int optlevel) JL_NOTSAFEPOINT + : JTMB(createJTMBFromTM(TM, optlevel)) {} - std::unique_ptr operator()() { + std::unique_ptr operator()() JL_NOTSAFEPOINT { return cantFail(JTMB.createTargetMachine()); } }; @@ -1065,19 +1062,22 @@ namespace { struct PMCreator { std::unique_ptr TM; int optlevel; - PMCreator(TargetMachine &TM, int optlevel) : TM(cantFail(createJTMBFromTM(TM, optlevel).createTargetMachine())), optlevel(optlevel) {} - PMCreator(const PMCreator &other) : PMCreator(*other.TM, other.optlevel) {} - PMCreator(PMCreator &&other) : TM(std::move(other.TM)), optlevel(other.optlevel) {} - friend void swap(PMCreator &self, PMCreator &other) { + PMCreator(TargetMachine &TM, int optlevel) JL_NOTSAFEPOINT + : TM(cantFail(createJTMBFromTM(TM, optlevel).createTargetMachine())), optlevel(optlevel) {} + PMCreator(const PMCreator &other) JL_NOTSAFEPOINT + : PMCreator(*other.TM, other.optlevel) {} + PMCreator(PMCreator &&other) JL_NOTSAFEPOINT + : TM(std::move(other.TM)), optlevel(other.optlevel) {} + friend void swap(PMCreator &self, PMCreator &other) JL_NOTSAFEPOINT { using std::swap; swap(self.TM, other.TM); swap(self.optlevel, other.optlevel); } - PMCreator &operator=(PMCreator other) { + PMCreator &operator=(PMCreator other) JL_NOTSAFEPOINT { swap(*this, other); return *this; } - auto operator()() { + auto operator()() JL_NOTSAFEPOINT { auto PM = std::make_unique(); addTargetPasses(PM.get(), TM->getTargetTriple(), TM->getTargetIRAnalysis()); addOptimizationPasses(PM.get(), optlevel); @@ -1089,37 +1089,41 @@ namespace { struct PMCreator { orc::JITTargetMachineBuilder JTMB; OptimizationLevel O; - PMCreator(TargetMachine &TM, int optlevel) : JTMB(createJTMBFromTM(TM, optlevel)), O(getOptLevel(optlevel)) {} + PMCreator(TargetMachine &TM, int optlevel) JL_NOTSAFEPOINT + : JTMB(createJTMBFromTM(TM, optlevel)), O(getOptLevel(optlevel)) {} - auto operator()() { + auto operator()() JL_NOTSAFEPOINT { return std::make_unique(cantFail(JTMB.createTargetMachine()), O); } }; #endif struct OptimizerT { - OptimizerT(TargetMachine &TM, int optlevel) : optlevel(optlevel), PMs(PMCreator(TM, optlevel)) {} + OptimizerT(TargetMachine &TM, int optlevel) JL_NOTSAFEPOINT + : optlevel(optlevel), PMs(PMCreator(TM, optlevel)) {} + OptimizerT(OptimizerT&) JL_NOTSAFEPOINT = delete; + OptimizerT(OptimizerT&&) JL_NOTSAFEPOINT = default; - OptimizerResultT operator()(orc::ThreadSafeModule TSM, orc::MaterializationResponsibility &R) { - TSM.withModuleDo([&](Module &M) { + OptimizerResultT operator()(orc::ThreadSafeModule TSM, orc::MaterializationResponsibility &R) JL_NOTSAFEPOINT { + TSM.withModuleDo([&](Module &M) JL_NOTSAFEPOINT { uint64_t start_time = 0; { auto stream = *jl_ExecutionEngine->get_dump_llvm_opt_stream(); if (stream) { // Print LLVM function statistics _before_ optimization // Print all the information about this invocation as a YAML object - jl_printf(stream, "- \n"); + ios_printf(stream, "- \n"); // We print the name and some statistics for each function in the module, both // before optimization and again afterwards. - jl_printf(stream, " before: \n"); + ios_printf(stream, " before: \n"); for (auto &F : M.functions()) { if (F.isDeclaration() || F.getName().startswith("jfptr_")) { continue; } // Each function is printed as a YAML object with several attributes - jl_printf(stream, " \"%s\":\n", F.getName().str().c_str()); - jl_printf(stream, " instructions: %u\n", F.getInstructionCount()); - jl_printf(stream, " basicblocks: %zd\n", countBasicBlocks(F)); + ios_printf(stream, " \"%s\":\n", F.getName().str().c_str()); + ios_printf(stream, " instructions: %u\n", F.getInstructionCount()); + ios_printf(stream, " basicblocks: %zd\n", countBasicBlocks(F)); } start_time = jl_hrtime(); @@ -1138,18 +1142,18 @@ namespace { auto stream = *jl_ExecutionEngine->get_dump_llvm_opt_stream(); if (stream) { end_time = jl_hrtime(); - jl_printf(stream, " time_ns: %" PRIu64 "\n", end_time - start_time); - jl_printf(stream, " optlevel: %d\n", optlevel); + ios_printf(stream, " time_ns: %" PRIu64 "\n", end_time - start_time); + ios_printf(stream, " optlevel: %d\n", optlevel); // Print LLVM function statistics _after_ optimization - jl_printf(stream, " after: \n"); + ios_printf(stream, " after: \n"); for (auto &F : M.functions()) { if (F.isDeclaration() || F.getName().startswith("jfptr_")) { continue; } - jl_printf(stream, " \"%s\":\n", F.getName().str().c_str()); - jl_printf(stream, " instructions: %u\n", F.getInstructionCount()); - jl_printf(stream, " basicblocks: %zd\n", countBasicBlocks(F)); + ios_printf(stream, " \"%s\":\n", F.getName().str().c_str()); + ios_printf(stream, " instructions: %u\n", F.getInstructionCount()); + ios_printf(stream, " basicblocks: %zd\n", countBasicBlocks(F)); } } } @@ -1179,8 +1183,8 @@ namespace { struct CompilerT : orc::IRCompileLayer::IRCompiler { - CompilerT(orc::IRSymbolMapper::ManglingOptions MO, TargetMachine &TM, int optlevel) - : orc::IRCompileLayer::IRCompiler(MO), TMs(TMCreator(TM, optlevel)) {} + CompilerT(orc::IRSymbolMapper::ManglingOptions MO, TargetMachine &TM, int optlevel) JL_NOTSAFEPOINT + : orc::IRCompileLayer::IRCompiler(MO), TMs(TMCreator(TM, optlevel)) {} Expected> operator()(Module &M) override { return orc::SimpleCompiler(***TMs)(M); @@ -1198,9 +1202,10 @@ llvm::DataLayout jl_create_datalayout(TargetMachine &TM) { } JuliaOJIT::PipelineT::PipelineT(orc::ObjectLayer &BaseLayer, TargetMachine &TM, int optlevel) -: CompileLayer(BaseLayer.getExecutionSession(), BaseLayer, - std::make_unique(orc::irManglingOptionsFromTargetOptions(TM.Options), TM, optlevel)), - OptimizeLayer(CompileLayer.getExecutionSession(), CompileLayer, OptimizerT(TM, optlevel)) {} + : CompileLayer(BaseLayer.getExecutionSession(), BaseLayer, + std::make_unique(orc::irManglingOptionsFromTargetOptions(TM.Options), TM, optlevel)), + OptimizeLayer(CompileLayer.getExecutionSession(), CompileLayer, + llvm::orc::IRTransformLayer::TransformFunction(OptimizerT(TM, optlevel))) {} JuliaOJIT::JuliaOJIT() : TM(createTargetMachine()), @@ -1332,6 +1337,8 @@ JuliaOJIT::JuliaOJIT() #endif } +JuliaOJIT::~JuliaOJIT() = default; + orc::SymbolStringPtr JuliaOJIT::mangle(StringRef Name) { std::string MangleName = getMangledName(Name); @@ -1348,7 +1355,7 @@ void JuliaOJIT::addModule(orc::ThreadSafeModule TSM) JL_TIMING(LLVM_MODULE_FINISH); ++ModulesAdded; orc::SymbolLookupSet NewExports; - TSM.withModuleDo([&](Module &M) { + TSM.withModuleDo([&](Module &M) JL_NOTSAFEPOINT { jl_decorate_module(M); shareStrings(M); for (auto &F : M.global_values()) { @@ -1510,7 +1517,7 @@ size_t JuliaOJIT::getTotalBytes() const return total_size.load(std::memory_order_relaxed); } #else -size_t getRTDyldMemoryManagerTotalBytes(RTDyldMemoryManager *mm); +size_t getRTDyldMemoryManagerTotalBytes(RTDyldMemoryManager *mm) JL_NOTSAFEPOINT; size_t JuliaOJIT::getTotalBytes() const { @@ -1528,8 +1535,8 @@ JuliaOJIT *jl_ExecutionEngine; void jl_merge_module(orc::ThreadSafeModule &destTSM, orc::ThreadSafeModule srcTSM) { ++ModulesMerged; - destTSM.withModuleDo([&](Module &dest) { - srcTSM.withModuleDo([&](Module &src) { + destTSM.withModuleDo([&](Module &dest) JL_NOTSAFEPOINT { + srcTSM.withModuleDo([&](Module &src) JL_NOTSAFEPOINT { assert(&dest != &src && "Cannot merge module with itself!"); assert(&dest.getContext() == &src.getContext() && "Cannot merge modules with different contexts!"); assert(dest.getDataLayout() == src.getDataLayout() && "Cannot merge modules with different data layouts!"); @@ -1740,7 +1747,7 @@ static int jl_add_to_ee( int depth = Stack.size(); int MergeUp = depth; std::vector Children; - M.withModuleDo([&](Module &m) { + M.withModuleDo([&](Module &m) JL_NOTSAFEPOINT { for (auto &F : m.global_objects()) { if (F.isDeclaration() && F.getLinkage() == GlobalValue::ExternalLinkage) { auto Callee = NewExports.find(F.getName()); diff --git a/src/jitlayers.h b/src/jitlayers.h index f2a6c284649a4a..044d5aacaa2877 100644 --- a/src/jitlayers.h +++ b/src/jitlayers.h @@ -64,15 +64,15 @@ extern "C" jl_cgparams_t jl_default_cgparams; DEFINE_SIMPLE_CONVERSION_FUNCTIONS(orc::ThreadSafeContext, LLVMOrcThreadSafeContextRef) DEFINE_SIMPLE_CONVERSION_FUNCTIONS(orc::ThreadSafeModule, LLVMOrcThreadSafeModuleRef) -void addTargetPasses(legacy::PassManagerBase *PM, const Triple &triple, TargetIRAnalysis analysis); -void addOptimizationPasses(legacy::PassManagerBase *PM, int opt_level, bool lower_intrinsics=true, bool dump_native=false, bool external_use=false); -void addMachinePasses(legacy::PassManagerBase *PM, int optlevel); -void jl_finalize_module(orc::ThreadSafeModule m); -void jl_merge_module(orc::ThreadSafeModule &dest, orc::ThreadSafeModule src); -GlobalVariable *jl_emit_RTLD_DEFAULT_var(Module *M); -DataLayout jl_create_datalayout(TargetMachine &TM); - -static inline bool imaging_default() { +void addTargetPasses(legacy::PassManagerBase *PM, const Triple &triple, TargetIRAnalysis analysis) JL_NOTSAFEPOINT; +void addOptimizationPasses(legacy::PassManagerBase *PM, int opt_level, bool lower_intrinsics=true, bool dump_native=false, bool external_use=false) JL_NOTSAFEPOINT; +void addMachinePasses(legacy::PassManagerBase *PM, int optlevel) JL_NOTSAFEPOINT; +void jl_finalize_module(orc::ThreadSafeModule m) JL_NOTSAFEPOINT; +void jl_merge_module(orc::ThreadSafeModule &dest, orc::ThreadSafeModule src) JL_NOTSAFEPOINT; +GlobalVariable *jl_emit_RTLD_DEFAULT_var(Module *M) JL_NOTSAFEPOINT; +DataLayout jl_create_datalayout(TargetMachine &TM) JL_NOTSAFEPOINT; + +static inline bool imaging_default() JL_NOTSAFEPOINT { return jl_options.image_codegen || (jl_generating_output() && jl_options.use_pkgimages); } @@ -99,9 +99,10 @@ struct NewPM { ModulePassManager MPM; OptimizationLevel O; - NewPM(std::unique_ptr TM, OptimizationLevel O, OptimizationOptions options = OptimizationOptions::defaults()); + NewPM(std::unique_ptr TM, OptimizationLevel O, OptimizationOptions options = OptimizationOptions::defaults()) JL_NOTSAFEPOINT; + ~NewPM() JL_NOTSAFEPOINT; - void run(Module &M); + void run(Module &M) JL_NOTSAFEPOINT; }; struct AnalysisManagers { @@ -110,36 +111,48 @@ struct AnalysisManagers { CGSCCAnalysisManager CGAM; ModuleAnalysisManager MAM; - AnalysisManagers(PassBuilder &PB); - AnalysisManagers(TargetMachine &TM, PassBuilder &PB, OptimizationLevel O); + AnalysisManagers(PassBuilder &PB) JL_NOTSAFEPOINT; + AnalysisManagers(TargetMachine &TM, PassBuilder &PB, OptimizationLevel O) JL_NOTSAFEPOINT; + ~AnalysisManagers() JL_NOTSAFEPOINT; }; -OptimizationLevel getOptLevel(int optlevel); +OptimizationLevel getOptLevel(int optlevel) JL_NOTSAFEPOINT; struct jl_locked_stream { - JL_STREAM *stream = nullptr; + ios_t *stream = nullptr; std::mutex mutex; struct lock { std::unique_lock lck; - JL_STREAM *&stream; + ios_t *&stream; - lock(std::mutex &mutex, JL_STREAM *&stream) : lck(mutex), stream(stream) {} + lock(std::mutex &mutex, ios_t *&stream) JL_NOTSAFEPOINT + : lck(mutex), stream(stream) {} + lock(lock&) = delete; + lock(lock&&) JL_NOTSAFEPOINT = default; + ~lock() JL_NOTSAFEPOINT = default; - JL_STREAM *&operator*() { + ios_t *&operator*() JL_NOTSAFEPOINT { return stream; } - explicit operator bool() { + explicit operator bool() JL_NOTSAFEPOINT { return !!stream; } - operator JL_STREAM *() { + operator ios_t *() JL_NOTSAFEPOINT { return stream; } + + operator JL_STREAM *() JL_NOTSAFEPOINT { + return (JL_STREAM*)stream; + } }; - lock operator*() { + jl_locked_stream() JL_NOTSAFEPOINT = default; + ~jl_locked_stream() JL_NOTSAFEPOINT = default; + + lock operator*() JL_NOTSAFEPOINT { return lock(mutex, stream); } }; @@ -235,9 +248,9 @@ void jl_compile_workqueue( Function *jl_cfunction_object(jl_function_t *f, jl_value_t *rt, jl_tupletype_t *argt, jl_codegen_params_t ¶ms); -void add_named_global(StringRef name, void *addr); +void add_named_global(StringRef name, void *addr) JL_NOTSAFEPOINT; -static inline Constant *literal_static_pointer_val(const void *p, Type *T) +static inline Constant *literal_static_pointer_val(const void *p, Type *T) JL_NOTSAFEPOINT { // this function will emit a static pointer into the generated code // the generated code will only be valid during the current session, @@ -272,7 +285,8 @@ class JuliaOJIT { #endif struct LockLayerT : public orc::ObjectLayer { - LockLayerT(orc::ObjectLayer &BaseLayer) : orc::ObjectLayer(BaseLayer.getExecutionSession()), BaseLayer(BaseLayer) {} + LockLayerT(orc::ObjectLayer &BaseLayer) JL_NOTSAFEPOINT : orc::ObjectLayer(BaseLayer.getExecutionSession()), BaseLayer(BaseLayer) {} + ~LockLayerT() JL_NOTSAFEPOINT = default; void emit(std::unique_ptr R, std::unique_ptr O) override { @@ -299,44 +313,49 @@ class JuliaOJIT { > struct ResourcePool { public: - ResourcePool(std::function creator) : creator(std::move(creator)), mutex(std::make_unique()) {} + ResourcePool(std::function creator) JL_NOTSAFEPOINT : creator(std::move(creator)), mutex(std::make_unique()) {} + ResourcePool(ResourcePool&) = delete; + ResourcePool(ResourcePool&&) JL_NOTSAFEPOINT = default; + ~ResourcePool() JL_NOTSAFEPOINT = default; class OwningResource { public: - OwningResource(ResourcePool &pool, ResourceT resource) : pool(pool), resource(std::move(resource)) {} + OwningResource(ResourcePool &pool, ResourceT resource) JL_NOTSAFEPOINT // _ENTER + : pool(pool), resource(std::move(resource)) {} OwningResource(const OwningResource &) = delete; OwningResource &operator=(const OwningResource &) = delete; - OwningResource(OwningResource &&) = default; - OwningResource &operator=(OwningResource &&) = default; - ~OwningResource() { - if (resource) pool.release(std::move(*resource)); + OwningResource(OwningResource &&) JL_NOTSAFEPOINT = default; + OwningResource &operator=(OwningResource &&) JL_NOTSAFEPOINT = default; + ~OwningResource() JL_NOTSAFEPOINT { // _LEAVE + if (resource) + pool.release(std::move(*resource)); } - ResourceT release() { + ResourceT release() JL_NOTSAFEPOINT { ResourceT res(std::move(*resource)); resource.reset(); return res; } - void reset(ResourceT res) { + void reset(ResourceT res) JL_NOTSAFEPOINT { *resource = std::move(res); } - ResourceT &operator*() { + ResourceT &operator*() JL_NOTSAFEPOINT { return *resource; } - ResourceT *operator->() { + ResourceT *operator->() JL_NOTSAFEPOINT { return get(); } - ResourceT *get() { + ResourceT *get() JL_NOTSAFEPOINT { return resource.getPointer(); } - const ResourceT &operator*() const { + const ResourceT &operator*() const JL_NOTSAFEPOINT { return *resource; } - const ResourceT *operator->() const { + const ResourceT *operator->() const JL_NOTSAFEPOINT { return get(); } - const ResourceT *get() const { + const ResourceT *get() const JL_NOTSAFEPOINT { return resource.getPointer(); } - explicit operator bool() const { + explicit operator bool() const JL_NOTSAFEPOINT { return resource; } private: @@ -344,7 +363,7 @@ class JuliaOJIT { llvm::Optional resource; }; - OwningResource operator*() { + OwningResource operator*() JL_NOTSAFEPOINT { return OwningResource(*this, acquire()); } @@ -352,7 +371,7 @@ class JuliaOJIT { return **this; } - ResourceT acquire() { + ResourceT acquire() JL_NOTSAFEPOINT { // _ENTER std::unique_lock lock(mutex->mutex); if (!pool.empty()) { return pop(pool); @@ -365,20 +384,20 @@ class JuliaOJIT { assert(!pool.empty() && "Expected resource pool to have a value!"); return pop(pool); } - void release(ResourceT &&resource) { + void release(ResourceT &&resource) JL_NOTSAFEPOINT { // _LEAVE std::lock_guard lock(mutex->mutex); pool.push(std::move(resource)); mutex->empty.notify_one(); } private: template - static ResourceT pop(std::queue &pool) { + static ResourceT pop(std::queue &pool) JL_NOTSAFEPOINT { ResourceT top = std::move(pool.front()); pool.pop(); return top; } template - static ResourceT pop(PoolT &pool) { + static ResourceT pop(PoolT &pool) JL_NOTSAFEPOINT { ResourceT top = std::move(pool.top()); pool.pop(); return top; @@ -402,13 +421,14 @@ class JuliaOJIT { struct OptSelLayerT : orc::IRLayer { template - OptSelLayerT(const std::array, N> &optimizers) + OptSelLayerT(const std::array, N> &optimizers) JL_NOTSAFEPOINT : orc::IRLayer(optimizers[0]->OptimizeLayer.getExecutionSession(), optimizers[0]->OptimizeLayer.getManglingOptions()), optimizers(optimizers.data()), count(N) { static_assert(N > 0, "Expected array with at least one optimizer!"); } + ~OptSelLayerT() JL_NOTSAFEPOINT = default; void emit(std::unique_ptr R, orc::ThreadSafeModule TSM) override; @@ -424,44 +444,45 @@ class JuliaOJIT { public: - JuliaOJIT(); + JuliaOJIT() JL_NOTSAFEPOINT; + ~JuliaOJIT() JL_NOTSAFEPOINT; - void enableJITDebuggingSupport(); + void enableJITDebuggingSupport() JL_NOTSAFEPOINT; #ifndef JL_USE_JITLINK // JITLink doesn't support old JITEventListeners (yet). - void RegisterJITEventListener(JITEventListener *L); + void RegisterJITEventListener(JITEventListener *L) JL_NOTSAFEPOINT; #endif - orc::SymbolStringPtr mangle(StringRef Name); - void addGlobalMapping(StringRef Name, uint64_t Addr); - void addModule(orc::ThreadSafeModule M); + orc::SymbolStringPtr mangle(StringRef Name) JL_NOTSAFEPOINT; + void addGlobalMapping(StringRef Name, uint64_t Addr) JL_NOTSAFEPOINT; + void addModule(orc::ThreadSafeModule M) JL_NOTSAFEPOINT; - JL_JITSymbol findSymbol(StringRef Name, bool ExportedSymbolsOnly); - JL_JITSymbol findUnmangledSymbol(StringRef Name); - uint64_t getGlobalValueAddress(StringRef Name); - uint64_t getFunctionAddress(StringRef Name); - StringRef getFunctionAtAddress(uint64_t Addr, jl_code_instance_t *codeinst); - auto getContext() { + JL_JITSymbol findSymbol(StringRef Name, bool ExportedSymbolsOnly) JL_NOTSAFEPOINT; + JL_JITSymbol findUnmangledSymbol(StringRef Name) JL_NOTSAFEPOINT; + uint64_t getGlobalValueAddress(StringRef Name) JL_NOTSAFEPOINT; + uint64_t getFunctionAddress(StringRef Name) JL_NOTSAFEPOINT; + StringRef getFunctionAtAddress(uint64_t Addr, jl_code_instance_t *codeinst) JL_NOTSAFEPOINT; + auto getContext() JL_NOTSAFEPOINT { return *ContextPool; } - orc::ThreadSafeContext acquireContext() { + orc::ThreadSafeContext acquireContext() { // JL_NOTSAFEPOINT_ENTER? return ContextPool.acquire(); } - void releaseContext(orc::ThreadSafeContext &&ctx) { + void releaseContext(orc::ThreadSafeContext &&ctx) { // JL_NOTSAFEPOINT_LEAVE? ContextPool.release(std::move(ctx)); } - const DataLayout& getDataLayout() const; + const DataLayout& getDataLayout() const JL_NOTSAFEPOINT; // TargetMachine pass-through methods - std::unique_ptr cloneTargetMachine() const; - const Triple& getTargetTriple() const; - StringRef getTargetFeatureString() const; - StringRef getTargetCPU() const; - const TargetOptions &getTargetOptions() const; - const Target &getTarget() const; - TargetIRAnalysis getTargetIRAnalysis() const; + std::unique_ptr cloneTargetMachine() const JL_NOTSAFEPOINT; + const Triple& getTargetTriple() const JL_NOTSAFEPOINT; + StringRef getTargetFeatureString() const JL_NOTSAFEPOINT; + StringRef getTargetCPU() const JL_NOTSAFEPOINT; + const TargetOptions &getTargetOptions() const JL_NOTSAFEPOINT; + const Target &getTarget() const JL_NOTSAFEPOINT; + TargetIRAnalysis getTargetIRAnalysis() const JL_NOTSAFEPOINT; - size_t getTotalBytes() const; + size_t getTotalBytes() const JL_NOTSAFEPOINT; JITDebugInfoRegistry &getDebugInfoRegistry() JL_NOTSAFEPOINT { return DebugRegistry; @@ -477,9 +498,9 @@ class JuliaOJIT { return dump_llvm_opt_stream; } private: - std::string getMangledName(StringRef Name); - std::string getMangledName(const GlobalValue *GV); - void shareStrings(Module &M); + std::string getMangledName(StringRef Name) JL_NOTSAFEPOINT; + std::string getMangledName(const GlobalValue *GV) JL_NOTSAFEPOINT; + void shareStrings(Module &M) JL_NOTSAFEPOINT; const std::unique_ptr TM; const DataLayout DL; @@ -514,9 +535,9 @@ class JuliaOJIT { OptSelLayerT OptSelLayer; }; extern JuliaOJIT *jl_ExecutionEngine; -orc::ThreadSafeModule jl_create_llvm_module(StringRef name, orc::ThreadSafeContext ctx, bool imaging_mode, const DataLayout &DL = jl_ExecutionEngine->getDataLayout(), const Triple &triple = jl_ExecutionEngine->getTargetTriple()); +orc::ThreadSafeModule jl_create_llvm_module(StringRef name, orc::ThreadSafeContext ctx, bool imaging_mode, const DataLayout &DL = jl_ExecutionEngine->getDataLayout(), const Triple &triple = jl_ExecutionEngine->getTargetTriple()) JL_NOTSAFEPOINT; -orc::ThreadSafeModule &jl_codegen_params_t::shared_module(Module &from) { +orc::ThreadSafeModule &jl_codegen_params_t::shared_module(Module &from) JL_NOTSAFEPOINT { if (!_shared_module) { _shared_module = jl_create_llvm_module("globals", tsctx, imaging, from.getDataLayout(), Triple(from.getTargetTriple())); assert(&from.getContext() == tsctx.getContext() && "Module context differs from codegen_params context!"); @@ -528,29 +549,29 @@ orc::ThreadSafeModule &jl_codegen_params_t::shared_module(Module &from) { return _shared_module; } -Pass *createLowerPTLSPass(bool imaging_mode); -Pass *createCombineMulAddPass(); -Pass *createFinalLowerGCPass(); -Pass *createLateLowerGCFramePass(); -Pass *createLowerExcHandlersPass(); -Pass *createGCInvariantVerifierPass(bool Strong); -Pass *createPropagateJuliaAddrspaces(); -Pass *createRemoveJuliaAddrspacesPass(); -Pass *createRemoveNIPass(); -Pass *createJuliaLICMPass(); -Pass *createMultiVersioningPass(bool external_use); -Pass *createAllocOptPass(); -Pass *createDemoteFloat16Pass(); -Pass *createCPUFeaturesPass(); -Pass *createLowerSimdLoopPass(); +Pass *createLowerPTLSPass(bool imaging_mode) JL_NOTSAFEPOINT; +Pass *createCombineMulAddPass() JL_NOTSAFEPOINT; +Pass *createFinalLowerGCPass() JL_NOTSAFEPOINT; +Pass *createLateLowerGCFramePass() JL_NOTSAFEPOINT; +Pass *createLowerExcHandlersPass() JL_NOTSAFEPOINT; +Pass *createGCInvariantVerifierPass(bool Strong) JL_NOTSAFEPOINT; +Pass *createPropagateJuliaAddrspaces() JL_NOTSAFEPOINT; +Pass *createRemoveJuliaAddrspacesPass() JL_NOTSAFEPOINT; +Pass *createRemoveNIPass() JL_NOTSAFEPOINT; +Pass *createJuliaLICMPass() JL_NOTSAFEPOINT; +Pass *createMultiVersioningPass(bool external_use) JL_NOTSAFEPOINT; +Pass *createAllocOptPass() JL_NOTSAFEPOINT; +Pass *createDemoteFloat16Pass() JL_NOTSAFEPOINT; +Pass *createCPUFeaturesPass() JL_NOTSAFEPOINT; +Pass *createLowerSimdLoopPass() JL_NOTSAFEPOINT; // NewPM #include "passes.h" // Whether the Function is an llvm or julia intrinsic. -static inline bool isIntrinsicFunction(Function *F) +static inline bool isIntrinsicFunction(Function *F) JL_NOTSAFEPOINT { return F->isIntrinsic() || F->getName().startswith("julia."); } -CodeGenOpt::Level CodeGenOptLevelFor(int optlevel); +CodeGenOpt::Level CodeGenOptLevelFor(int optlevel) JL_NOTSAFEPOINT; diff --git a/src/julia_internal.h b/src/julia_internal.h index a13e548b13b163..b839d5eda1650c 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -195,10 +195,10 @@ JL_DLLEXPORT void jl_set_profile_peek_duration(double); JL_DLLEXPORT void jl_init_profile_lock(void); JL_DLLEXPORT uintptr_t jl_lock_profile_rd_held(void) JL_NOTSAFEPOINT; -JL_DLLEXPORT void jl_lock_profile(void) JL_NOTSAFEPOINT; -JL_DLLEXPORT void jl_unlock_profile(void) JL_NOTSAFEPOINT; -JL_DLLEXPORT void jl_lock_profile_wr(void) JL_NOTSAFEPOINT; -JL_DLLEXPORT void jl_unlock_profile_wr(void) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_lock_profile(void) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_ENTER; +JL_DLLEXPORT void jl_unlock_profile(void) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_LEAVE; +JL_DLLEXPORT void jl_lock_profile_wr(void) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_ENTER; +JL_DLLEXPORT void jl_unlock_profile_wr(void) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_LEAVE; // number of cycles since power-on static inline uint64_t cycleclock(void) JL_NOTSAFEPOINT @@ -329,7 +329,7 @@ JL_DLLEXPORT extern const char *jl_filename; jl_value_t *jl_gc_pool_alloc_noinline(jl_ptls_t ptls, int pool_offset, int osize); jl_value_t *jl_gc_big_alloc_noinline(jl_ptls_t ptls, size_t allocsz); -JL_DLLEXPORT int jl_gc_classify_pools(size_t sz, int *osize); +JL_DLLEXPORT int jl_gc_classify_pools(size_t sz, int *osize) JL_NOTSAFEPOINT; extern uv_mutex_t gc_perm_lock; void *jl_gc_perm_alloc_nolock(size_t sz, int zero, unsigned align, unsigned offset) JL_NOTSAFEPOINT; @@ -378,7 +378,7 @@ static const int jl_gc_sizeclasses[] = { }; static_assert(sizeof(jl_gc_sizeclasses) / sizeof(jl_gc_sizeclasses[0]) == JL_GC_N_POOLS, ""); -STATIC_INLINE int jl_gc_alignment(size_t sz) +STATIC_INLINE int jl_gc_alignment(size_t sz) JL_NOTSAFEPOINT { if (sz == 0) return sizeof(void*); @@ -398,14 +398,14 @@ STATIC_INLINE int jl_gc_alignment(size_t sz) return 16; #endif } -JL_DLLEXPORT int jl_alignment(size_t sz); +JL_DLLEXPORT int jl_alignment(size_t sz) JL_NOTSAFEPOINT; // the following table is computed as: // [searchsortedfirst(jl_gc_sizeclasses, i) - 1 for i = 0:16:jl_gc_sizeclasses[end]] static const uint8_t szclass_table[] = {0, 1, 3, 5, 7, 9, 11, 13, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 28, 29, 29, 30, 30, 31, 31, 31, 32, 32, 32, 33, 33, 33, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 37, 37, 38, 38, 38, 38, 38, 39, 39, 39, 39, 39, 40, 40, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41, 42, 42, 42, 42, 42, 43, 43, 43, 43, 43, 44, 44, 44, 44, 44, 44, 44, 45, 45, 45, 45, 45, 45, 45, 45, 46, 46, 46, 46, 46, 46, 46, 46, 46, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48}; static_assert(sizeof(szclass_table) == 128, ""); -STATIC_INLINE uint8_t JL_CONST_FUNC jl_gc_szclass(unsigned sz) +STATIC_INLINE uint8_t JL_CONST_FUNC jl_gc_szclass(unsigned sz) JL_NOTSAFEPOINT { assert(sz <= 2032); #ifdef _P64 @@ -425,7 +425,7 @@ STATIC_INLINE uint8_t JL_CONST_FUNC jl_gc_szclass(unsigned sz) return klass + N; } -STATIC_INLINE uint8_t JL_CONST_FUNC jl_gc_szclass_align8(unsigned sz) +STATIC_INLINE uint8_t JL_CONST_FUNC jl_gc_szclass_align8(unsigned sz) JL_NOTSAFEPOINT { if (sz >= 16 && sz <= 152) { #ifdef _P64 @@ -858,12 +858,12 @@ void jl_init_runtime_ccall(void); void jl_init_intrinsic_functions(void); void jl_init_intrinsic_properties(void); void jl_init_tasks(void) JL_GC_DISABLED; -void jl_init_stack_limits(int ismaster, void **stack_hi, void **stack_lo); +void jl_init_stack_limits(int ismaster, void **stack_hi, void **stack_lo) JL_NOTSAFEPOINT; jl_task_t *jl_init_root_task(jl_ptls_t ptls, void *stack_lo, void *stack_hi); void jl_init_serializer(void); void jl_gc_init(void); void jl_init_uv(void); -void jl_init_thread_heap(jl_ptls_t ptls); +void jl_init_thread_heap(jl_ptls_t ptls) JL_NOTSAFEPOINT; void jl_init_int32_int64_cache(void); JL_DLLEXPORT void jl_init_options(void); diff --git a/src/julia_locks.h b/src/julia_locks.h index 234ff1fa8c0db9..7db37b03f0bed7 100644 --- a/src/julia_locks.h +++ b/src/julia_locks.h @@ -29,7 +29,7 @@ static inline void jl_mutex_wait(jl_mutex_t *lock, int safepoint) _jl_mutex_wait(jl_current_task, lock, safepoint); } -static inline void jl_mutex_lock_nogc(jl_mutex_t *lock) JL_NOTSAFEPOINT +static inline void jl_mutex_lock_nogc(jl_mutex_t *lock) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_ENTER { #ifndef __clang_gcanalyzer__ // Hide this body from the analyzer, otherwise it complains that we're calling @@ -66,7 +66,7 @@ static inline void jl_mutex_lock(jl_mutex_t *lock) _jl_mutex_lock(jl_current_task, lock); } -static inline int jl_mutex_trylock_nogc(jl_mutex_t *lock) JL_NOTSAFEPOINT +static inline int jl_mutex_trylock_nogc(jl_mutex_t *lock) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_ENTER { return _jl_mutex_trylock_nogc(jl_current_task, lock); } @@ -81,7 +81,7 @@ static inline void jl_mutex_unlock(jl_mutex_t *lock) _jl_mutex_unlock(jl_current_task, lock); } -static inline void jl_mutex_unlock_nogc(jl_mutex_t *lock) JL_NOTSAFEPOINT +static inline void jl_mutex_unlock_nogc(jl_mutex_t *lock) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_LEAVE { _jl_mutex_unlock_nogc(lock); } diff --git a/src/julia_threads.h b/src/julia_threads.h index ff28765346fbc4..5874225c12eac2 100644 --- a/src/julia_threads.h +++ b/src/julia_threads.h @@ -354,10 +354,10 @@ STATIC_INLINE int8_t jl_gc_state_save_and_set(jl_ptls_t ptls, return jl_gc_state_set(ptls, state, jl_atomic_load_relaxed(&ptls->gc_state)); } #ifdef __clang_gcanalyzer__ -int8_t jl_gc_unsafe_enter(jl_ptls_t ptls); // Can be a safepoint -int8_t jl_gc_unsafe_leave(jl_ptls_t ptls, int8_t state) JL_NOTSAFEPOINT; -int8_t jl_gc_safe_enter(jl_ptls_t ptls) JL_NOTSAFEPOINT; -int8_t jl_gc_safe_leave(jl_ptls_t ptls, int8_t state); // Can be a safepoint +int8_t jl_gc_unsafe_enter(jl_ptls_t ptls) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_LEAVE; // this could be a safepoint, but we will assume it is not +void jl_gc_unsafe_leave(jl_ptls_t ptls, int8_t state) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_ENTER; +int8_t jl_gc_safe_enter(jl_ptls_t ptls) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_ENTER; +void jl_gc_safe_leave(jl_ptls_t ptls, int8_t state) JL_NOTSAFEPOINT_LEAVE; // this might not be a safepoint, but we have to assume it could be (statically) #else #define jl_gc_unsafe_enter(ptls) jl_gc_state_save_and_set(ptls, 0) #define jl_gc_unsafe_leave(ptls, state) ((void)jl_gc_state_set(ptls, (state), 0)) diff --git a/src/llvm-alloc-helpers.cpp b/src/llvm-alloc-helpers.cpp index 79aa270094d2b0..6ba0c73c14785c 100644 --- a/src/llvm-alloc-helpers.cpp +++ b/src/llvm-alloc-helpers.cpp @@ -2,7 +2,7 @@ #include "llvm-version.h" #include "llvm-alloc-helpers.h" -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #include "julia_assert.h" #include diff --git a/src/llvm-alloc-opt.cpp b/src/llvm-alloc-opt.cpp index a611f71d2bc11a..f6a2593724f57b 100644 --- a/src/llvm-alloc-opt.cpp +++ b/src/llvm-alloc-opt.cpp @@ -28,7 +28,7 @@ #include #include "passes.h" -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #include "julia.h" #include "julia_internal.h" #include "llvm-pass-helpers.h" diff --git a/src/codegen_shared.h b/src/llvm-codegen-shared.h similarity index 100% rename from src/codegen_shared.h rename to src/llvm-codegen-shared.h diff --git a/src/llvm-cpufeatures.cpp b/src/llvm-cpufeatures.cpp index 45b393151581c5..0f39bf06d2101e 100644 --- a/src/llvm-cpufeatures.cpp +++ b/src/llvm-cpufeatures.cpp @@ -38,7 +38,7 @@ STATISTIC(LoweredWithoutFMA, "Number of have_fma's that were lowered to false"); extern JuliaOJIT *jl_ExecutionEngine; // whether this platform unconditionally (i.e. without needing multiversioning) supports FMA -Optional always_have_fma(Function &intr) { +Optional always_have_fma(Function &intr) JL_NOTSAFEPOINT { auto intr_name = intr.getName(); auto typ = intr_name.substr(strlen("julia.cpu.have_fma.")); @@ -50,7 +50,7 @@ Optional always_have_fma(Function &intr) { #endif } -bool have_fma(Function &intr, Function &caller) { +bool have_fma(Function &intr, Function &caller) JL_NOTSAFEPOINT { auto unconditional = always_have_fma(intr); if (unconditional.hasValue()) return unconditional.getValue(); @@ -67,7 +67,7 @@ bool have_fma(Function &intr, Function &caller) { for (StringRef Feature : Features) #if defined _CPU_ARM_ if (Feature == "+vfp4") - return typ == "f32" || typ == "f64"; + return typ == "f32" || typ == "f64";lowerCPUFeatures else if (Feature == "+vfp4sp") return typ == "f32"; #else @@ -78,7 +78,7 @@ bool have_fma(Function &intr, Function &caller) { return false; } -void lowerHaveFMA(Function &intr, Function &caller, CallInst *I) { +void lowerHaveFMA(Function &intr, Function &caller, CallInst *I) JL_NOTSAFEPOINT { if (have_fma(intr, caller)) { ++LoweredWithFMA; I->replaceAllUsesWith(ConstantInt::get(I->getType(), 1)); @@ -89,7 +89,7 @@ void lowerHaveFMA(Function &intr, Function &caller, CallInst *I) { return; } -bool lowerCPUFeatures(Module &M) +bool lowerCPUFeatures(Module &M) JL_NOTSAFEPOINT { SmallVector Materialized; @@ -130,7 +130,7 @@ PreservedAnalyses CPUFeatures::run(Module &M, ModuleAnalysisManager &AM) namespace { struct CPUFeaturesLegacy : public ModulePass { static char ID; - CPUFeaturesLegacy() : ModulePass(ID) {}; + CPUFeaturesLegacy() JL_NOTSAFEPOINT : ModulePass(ID) {}; bool runOnModule(Module &M) { diff --git a/src/llvm-final-gc-lowering.cpp b/src/llvm-final-gc-lowering.cpp index 7adb25c7af08e4..6b416b05a002e1 100644 --- a/src/llvm-final-gc-lowering.cpp +++ b/src/llvm-final-gc-lowering.cpp @@ -14,7 +14,7 @@ #include #include -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #include "julia.h" #include "julia_internal.h" #include "llvm-pass-helpers.h" diff --git a/src/llvm-gc-invariant-verifier.cpp b/src/llvm-gc-invariant-verifier.cpp index f2dd821c9551b0..af9a1862089e44 100644 --- a/src/llvm-gc-invariant-verifier.cpp +++ b/src/llvm-gc-invariant-verifier.cpp @@ -26,7 +26,7 @@ #include #include -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #include "julia.h" #define DEBUG_TYPE "verify_gc_invariants" diff --git a/src/llvm-julia-licm.cpp b/src/llvm-julia-licm.cpp index 28dddf18c5394a..553d091ef4c6fe 100644 --- a/src/llvm-julia-licm.cpp +++ b/src/llvm-julia-licm.cpp @@ -19,7 +19,7 @@ #include "llvm-pass-helpers.h" #include "julia.h" #include "llvm-alloc-helpers.h" -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #define DEBUG_TYPE "julia-licm" diff --git a/src/llvm-late-gc-lowering.cpp b/src/llvm-late-gc-lowering.cpp index 20fc78b4d742f7..6837dc505a5031 100644 --- a/src/llvm-late-gc-lowering.cpp +++ b/src/llvm-late-gc-lowering.cpp @@ -32,7 +32,7 @@ #include -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #include "julia.h" #include "julia_internal.h" #include "julia_assert.h" diff --git a/src/llvm-lower-handlers.cpp b/src/llvm-lower-handlers.cpp index c8a77e2edc22f8..a5b05cb4f90668 100644 --- a/src/llvm-lower-handlers.cpp +++ b/src/llvm-lower-handlers.cpp @@ -24,7 +24,7 @@ #include "julia.h" #include "julia_assert.h" -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #include #define DEBUG_TYPE "lower_handlers" diff --git a/src/llvm-muladd.cpp b/src/llvm-muladd.cpp index a554c32b5e6578..b66ea33e57384d 100644 --- a/src/llvm-muladd.cpp +++ b/src/llvm-muladd.cpp @@ -41,7 +41,7 @@ STATISTIC(TotalContracted, "Total number of multiplies marked for FMA"); */ // Return true if we changed the mulOp -static bool checkCombine(Value *maybeMul) +static bool checkCombine(Value *maybeMul) JL_NOTSAFEPOINT { auto mulOp = dyn_cast(maybeMul); if (!mulOp || mulOp->getOpcode() != Instruction::FMul) @@ -59,7 +59,7 @@ static bool checkCombine(Value *maybeMul) return false; } -static bool combineMulAdd(Function &F) +static bool combineMulAdd(Function &F) JL_NOTSAFEPOINT { bool modified = false; for (auto &BB: F) { @@ -90,7 +90,7 @@ static bool combineMulAdd(Function &F) return modified; } -PreservedAnalyses CombineMulAdd::run(Function &F, FunctionAnalysisManager &AM) +PreservedAnalyses CombineMulAdd::run(Function &F, FunctionAnalysisManager &AM) JL_NOTSAFEPOINT { if (combineMulAdd(F)) { return PreservedAnalyses::allInSet(); diff --git a/src/llvm-multiversioning.cpp b/src/llvm-multiversioning.cpp index 97867f1d9f471d..9ec7982a6d975a 100644 --- a/src/llvm-multiversioning.cpp +++ b/src/llvm-multiversioning.cpp @@ -35,7 +35,7 @@ #include #include -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #include "julia_assert.h" #define DEBUG_TYPE "julia_multiversioning" diff --git a/src/llvm-pass-helpers.cpp b/src/llvm-pass-helpers.cpp index 2ddfbf1e2af680..e12fa7ad90fed1 100644 --- a/src/llvm-pass-helpers.cpp +++ b/src/llvm-pass-helpers.cpp @@ -12,7 +12,7 @@ #include #include -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #include "julia_assert.h" #include "llvm-pass-helpers.h" diff --git a/src/llvm-pass-helpers.h b/src/llvm-pass-helpers.h index 4774f876128711..3c06440f369d89 100644 --- a/src/llvm-pass-helpers.h +++ b/src/llvm-pass-helpers.h @@ -10,6 +10,7 @@ #include #include #include +#include "analyzer_annotations.h" struct JuliaPassContext; @@ -19,7 +20,7 @@ namespace jl_intrinsics { // intrinsics and declare new intrinsics if necessary. struct IntrinsicDescription final { // The type of function that declares an intrinsic. - typedef llvm::Function *(*DeclarationFunction)(const JuliaPassContext&); + typedef llvm::Function *(*DeclarationFunction)(const JuliaPassContext&) JL_NOTSAFEPOINT; // Creates an intrinsic description with a particular // name and declaration function. diff --git a/src/llvm-propagate-addrspaces.cpp b/src/llvm-propagate-addrspaces.cpp index 53b3fce090c238..91bec48bca8616 100644 --- a/src/llvm-propagate-addrspaces.cpp +++ b/src/llvm-propagate-addrspaces.cpp @@ -23,7 +23,7 @@ #include #include "passes.h" -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #define DEBUG_TYPE "propagate_julia_addrspaces" diff --git a/src/llvm-ptls.cpp b/src/llvm-ptls.cpp index c8d7ffbf0240bb..ebd7d10a2a130a 100644 --- a/src/llvm-ptls.cpp +++ b/src/llvm-ptls.cpp @@ -23,7 +23,7 @@ #include "julia.h" #include "julia_internal.h" -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #include "julia_assert.h" #define DEBUG_TYPE "lower_ptls" diff --git a/src/llvm-remove-addrspaces.cpp b/src/llvm-remove-addrspaces.cpp index 1cc09018958afa..e263467ba600c0 100644 --- a/src/llvm-remove-addrspaces.cpp +++ b/src/llvm-remove-addrspaces.cpp @@ -14,7 +14,7 @@ #include #include "passes.h" -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #define DEBUG_TYPE "remove_addrspaces" diff --git a/src/llvm-remove-ni.cpp b/src/llvm-remove-ni.cpp index c252905dc75f95..d9e3357524a9a4 100644 --- a/src/llvm-remove-ni.cpp +++ b/src/llvm-remove-ni.cpp @@ -17,7 +17,7 @@ using namespace llvm; namespace { -static bool removeNI(Module &M) +static bool removeNI(Module &M) JL_NOTSAFEPOINT { auto dlstr = M.getDataLayoutStr(); auto nistart = dlstr.find("-ni:"); diff --git a/src/llvm-simdloop.cpp b/src/llvm-simdloop.cpp index ae3065bc70b5fd..2f0375e39e1a30 100644 --- a/src/llvm-simdloop.cpp +++ b/src/llvm-simdloop.cpp @@ -44,7 +44,7 @@ STATISTIC(MulChains, "Multiply reduction chains"); namespace { -static unsigned getReduceOpcode(Instruction *J, Instruction *operand) +static unsigned getReduceOpcode(Instruction *J, Instruction *operand) JL_NOTSAFEPOINT { switch (J->getOpcode()) { case Instruction::FSub: @@ -67,7 +67,7 @@ static unsigned getReduceOpcode(Instruction *J, Instruction *operand) /// If Phi is part of a reduction cycle of FAdd, FSub, FMul or FDiv, /// mark the ops as permitting reassociation/commuting. /// As of LLVM 4.0, FDiv is not handled by the loop vectorizer -static void enableUnsafeAlgebraIfReduction(PHINode *Phi, Loop *L) +static void enableUnsafeAlgebraIfReduction(PHINode *Phi, Loop *L) JL_NOTSAFEPOINT { typedef SmallVector chainVector; chainVector chain; @@ -130,7 +130,7 @@ static void enableUnsafeAlgebraIfReduction(PHINode *Phi, Loop *L) MaxChainLength.updateMax(length); } -static bool markLoopInfo(Module &M, Function *marker, function_ref GetLI) +static bool markLoopInfo(Module &M, Function *marker, function_ref GetLI) JL_NOTSAFEPOINT { bool Changed = false; std::vector ToDelete; @@ -284,7 +284,7 @@ class LowerSIMDLoopLegacy : public ModulePass { Function *loopinfo_marker = M.getFunction("julia.loopinfo_marker"); - auto GetLI = [this](Function &F) -> LoopInfo & { + auto GetLI = [this](Function &F) JL_NOTSAFEPOINT -> LoopInfo & { return getAnalysis(F).getLoopInfo(); }; diff --git a/src/llvmcalltest.cpp b/src/llvmcalltest.cpp index b225111520c394..352c4695f2f20e 100644 --- a/src/llvmcalltest.cpp +++ b/src/llvmcalltest.cpp @@ -9,7 +9,7 @@ #include #include "julia.h" -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" using namespace llvm; diff --git a/src/module.c b/src/module.c index f4187d23ad4625..11e92cb9e2a189 100644 --- a/src/module.c +++ b/src/module.c @@ -880,10 +880,8 @@ JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_value_t *rhs) if (jl_egal(rhs, old)) return; if (jl_typeof(rhs) != jl_typeof(old) || jl_is_type(rhs) || jl_is_module(rhs)) { -#ifndef __clang_gcanalyzer__ jl_errorf("invalid redefinition of constant %s", jl_symbol_name(b->name)); -#endif } jl_safe_printf("WARNING: redefinition of constant %s. This may fail, cause incorrect answers, or produce other errors.\n", jl_symbol_name(b->name)); diff --git a/src/passes.h b/src/passes.h index 82922a95db565c..acbfcd95381065 100644 --- a/src/passes.h +++ b/src/passes.h @@ -3,6 +3,7 @@ #ifndef JL_PASSES_H #define JL_PASSES_H +#include "analyzer_annotations.h" #include #include @@ -10,93 +11,94 @@ using namespace llvm; // Function Passes struct DemoteFloat16 : PassInfoMixin { - PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM); + PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) JL_NOTSAFEPOINT; static bool isRequired() { return true; } }; struct CombineMulAdd : PassInfoMixin { - PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM); + PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) JL_NOTSAFEPOINT; }; struct LateLowerGC : PassInfoMixin { - PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM); + PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) JL_NOTSAFEPOINT; static bool isRequired() { return true; } }; struct AllocOptPass : PassInfoMixin { - PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM); + PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) JL_NOTSAFEPOINT; }; struct PropagateJuliaAddrspacesPass : PassInfoMixin { - PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM); + PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) JL_NOTSAFEPOINT; static bool isRequired() { return true; } }; struct LowerExcHandlers : PassInfoMixin { - PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM); + PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) JL_NOTSAFEPOINT; static bool isRequired() { return true; } }; struct GCInvariantVerifierPass : PassInfoMixin { bool Strong; - GCInvariantVerifierPass(bool Strong = false) : Strong(Strong) {} + GCInvariantVerifierPass(bool Strong = false) JL_NOTSAFEPOINT : Strong(Strong) {} - PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM); + PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) JL_NOTSAFEPOINT; static bool isRequired() { return true; } }; // Module Passes struct CPUFeatures : PassInfoMixin { - PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) JL_NOTSAFEPOINT; static bool isRequired() { return true; } }; struct RemoveNI : PassInfoMixin { - PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) JL_NOTSAFEPOINT; }; struct LowerSIMDLoop : PassInfoMixin { - PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) JL_NOTSAFEPOINT; }; struct FinalLowerGCPass : PassInfoMixin { - PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) JL_NOTSAFEPOINT; static bool isRequired() { return true; } }; struct MultiVersioning : PassInfoMixin { bool external_use; MultiVersioning(bool external_use = false) : external_use(external_use) {} - PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) JL_NOTSAFEPOINT; static bool isRequired() { return true; } }; struct RemoveJuliaAddrspacesPass : PassInfoMixin { - PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) JL_NOTSAFEPOINT; static bool isRequired() { return true; } }; struct RemoveAddrspacesPass : PassInfoMixin { std::function ASRemapper; - RemoveAddrspacesPass(); - RemoveAddrspacesPass(std::function ASRemapper) : ASRemapper(std::move(ASRemapper)) {} + RemoveAddrspacesPass() JL_NOTSAFEPOINT; + RemoveAddrspacesPass(std::function ASRemapper) JL_NOTSAFEPOINT : ASRemapper(std::move(ASRemapper)) {} + ~RemoveAddrspacesPass() JL_NOTSAFEPOINT = default; - PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) JL_NOTSAFEPOINT; static bool isRequired() { return true; } }; struct LowerPTLSPass : PassInfoMixin { bool imaging_mode; - LowerPTLSPass(bool imaging_mode=false) : imaging_mode(imaging_mode) {} + LowerPTLSPass(bool imaging_mode=false) JL_NOTSAFEPOINT : imaging_mode(imaging_mode) {} - PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) JL_NOTSAFEPOINT; static bool isRequired() { return true; } }; // Loop Passes struct JuliaLICMPass : PassInfoMixin { PreservedAnalyses run(Loop &L, LoopAnalysisManager &AM, - LoopStandardAnalysisResults &AR, LPMUpdater &U); + LoopStandardAnalysisResults &AR, LPMUpdater &U) JL_NOTSAFEPOINT; }; #endif diff --git a/src/pipeline.cpp b/src/pipeline.cpp index 10709989f55b00..ae2b1c3202f042 100644 --- a/src/pipeline.cpp +++ b/src/pipeline.cpp @@ -72,21 +72,21 @@ #pragma GCC diagnostic pop #endif -#include "passes.h" - #include #include "julia.h" #include "julia_internal.h" #include "jitlayers.h" #include "julia_assert.h" +#include "passes.h" + using namespace llvm; namespace { //Shamelessly stolen from Clang's approach to sanitizers //TODO do we want to enable other sanitizers? - static void addSanitizerPasses(ModulePassManager &MPM, OptimizationLevel O) { + static void addSanitizerPasses(ModulePassManager &MPM, OptimizationLevel O) JL_NOTSAFEPOINT { // Coverage sanitizer // if (CodeGenOpts.hasSanitizeCoverage()) { // auto SancovOpts = getSancovOptsFromCGOpts(CodeGenOpts); @@ -96,12 +96,12 @@ namespace { // } #ifdef _COMPILER_MSAN_ENABLED_ - auto MSanPass = [&](/*SanitizerMask Mask, */bool CompileKernel) { + auto MSanPass = [&](/*SanitizerMask Mask, */bool CompileKernel) JL_NOTSAFEPOINT { // if (LangOpts.Sanitize.has(Mask)) { // int TrackOrigins = CodeGenOpts.SanitizeMemoryTrackOrigins; // bool Recover = CodeGenOpts.SanitizeRecover.has(Mask); - // MemorySanitizerOptions options(TrackOrigins, Recover, CompileKernel, + // MemorySanitizerOptions options(TrackOrigins, Recover, CompileKernel,{ // CodeGenOpts.SanitizeMemoryParamRetval); MemorySanitizerOptions options; MPM.addPass(ModuleMemorySanitizerPass(options)); @@ -135,7 +135,7 @@ namespace { #ifdef _COMPILER_ASAN_ENABLED_ - auto ASanPass = [&](/*SanitizerMask Mask, */bool CompileKernel) { + auto ASanPass = [&](/*SanitizerMask Mask, */bool CompileKernel) JL_NOTSAFEPOINT { // if (LangOpts.Sanitize.has(Mask)) { // bool UseGlobalGC = asanUseGlobalsGC(TargetTriple, CodeGenOpts); // bool UseOdrIndicator = CodeGenOpts.SanitizeAddressUseOdrIndicator; @@ -173,20 +173,20 @@ namespace { // } } - void addVerificationPasses(ModulePassManager &MPM, bool llvm_only) { + void addVerificationPasses(ModulePassManager &MPM, bool llvm_only) JL_NOTSAFEPOINT { if (!llvm_only) MPM.addPass(llvm::createModuleToFunctionPassAdaptor(GCInvariantVerifierPass())); MPM.addPass(VerifierPass()); } - auto basicSimplifyCFGOptions() { + auto basicSimplifyCFGOptions() JL_NOTSAFEPOINT { return SimplifyCFGOptions() .convertSwitchRangeToICmp(true) .convertSwitchToLookupTable(true) .forwardSwitchCondToPhi(true); } - auto aggressiveSimplifyCFGOptions() { + auto aggressiveSimplifyCFGOptions() JL_NOTSAFEPOINT { return SimplifyCFGOptions() .convertSwitchRangeToICmp(true) .convertSwitchToLookupTable(true) @@ -208,16 +208,16 @@ namespace { //For now we'll maintain the insertion points even though they don't do anything //for the sake of documentation //If PB is a nullptr, don't invoke anything (this happens when running julia from opt) - void invokePipelineStartCallbacks(ModulePassManager &MPM, PassBuilder *PB, OptimizationLevel O) {} - void invokePeepholeEPCallbacks(FunctionPassManager &MPM, PassBuilder *PB, OptimizationLevel O) {} - void invokeEarlySimplificationCallbacks(ModulePassManager &MPM, PassBuilder *PB, OptimizationLevel O) {} - void invokeCGSCCCallbacks(CGSCCPassManager &MPM, PassBuilder *PB, OptimizationLevel O) {} - void invokeOptimizerEarlyCallbacks(ModulePassManager &MPM, PassBuilder *PB, OptimizationLevel O) {} - void invokeLateLoopOptimizationCallbacks(LoopPassManager &MPM, PassBuilder *PB, OptimizationLevel O) {} - void invokeLoopOptimizerEndCallbacks(LoopPassManager &MPM, PassBuilder *PB, OptimizationLevel O) {} - void invokeScalarOptimizerCallbacks(FunctionPassManager &MPM, PassBuilder *PB, OptimizationLevel O) {} - void invokeVectorizerCallbacks(FunctionPassManager &MPM, PassBuilder *PB, OptimizationLevel O) {} - void invokeOptimizerLastCallbacks(ModulePassManager &MPM, PassBuilder *PB, OptimizationLevel O) {} + void invokePipelineStartCallbacks(ModulePassManager &MPM, PassBuilder *PB, OptimizationLevel O) JL_NOTSAFEPOINT {} + void invokePeepholeEPCallbacks(FunctionPassManager &MPM, PassBuilder *PB, OptimizationLevel O) JL_NOTSAFEPOINT {} + void invokeEarlySimplificationCallbacks(ModulePassManager &MPM, PassBuilder *PB, OptimizationLevel O) JL_NOTSAFEPOINT {} + void invokeCGSCCCallbacks(CGSCCPassManager &MPM, PassBuilder *PB, OptimizationLevel O) JL_NOTSAFEPOINT {} + void invokeOptimizerEarlyCallbacks(ModulePassManager &MPM, PassBuilder *PB, OptimizationLevel O) JL_NOTSAFEPOINT {} + void invokeLateLoopOptimizationCallbacks(LoopPassManager &MPM, PassBuilder *PB, OptimizationLevel O) JL_NOTSAFEPOINT {} + void invokeLoopOptimizerEndCallbacks(LoopPassManager &MPM, PassBuilder *PB, OptimizationLevel O) JL_NOTSAFEPOINT {} + void invokeScalarOptimizerCallbacks(FunctionPassManager &MPM, PassBuilder *PB, OptimizationLevel O) JL_NOTSAFEPOINT {} + void invokeVectorizerCallbacks(FunctionPassManager &MPM, PassBuilder *PB, OptimizationLevel O) JL_NOTSAFEPOINT {} + void invokeOptimizerLastCallbacks(ModulePassManager &MPM, PassBuilder *PB, OptimizationLevel O) JL_NOTSAFEPOINT {} } //The actual pipelines @@ -240,10 +240,10 @@ namespace { //? loop sink pass //? hot-cold splitting pass -#define JULIA_PASS(ADD_PASS) if (!options.llvm_only) { ADD_PASS; } else do;while (0) +#define JULIA_PASS(ADD_PASS) if (!options.llvm_only) { ADD_PASS; } else do { } while (0) //Use for O1 and below -void buildBasicPipeline(ModulePassManager &MPM, PassBuilder *PB, OptimizationLevel O, OptimizationOptions options) { +static void buildBasicPipeline(ModulePassManager &MPM, PassBuilder *PB, OptimizationLevel O, OptimizationOptions options) JL_NOTSAFEPOINT { // #ifdef JL_DEBUG_BUILD addVerificationPasses(MPM, options.llvm_only); // #endif @@ -319,7 +319,7 @@ void buildBasicPipeline(ModulePassManager &MPM, PassBuilder *PB, OptimizationLev } //Use for O2 and above -void buildFullPipeline(ModulePassManager &MPM, PassBuilder *PB, OptimizationLevel O, OptimizationOptions options) { +static void buildFullPipeline(ModulePassManager &MPM, PassBuilder *PB, OptimizationLevel O, OptimizationOptions options) JL_NOTSAFEPOINT { // #ifdef JL_DEBUG_BUILD addVerificationPasses(MPM, options.llvm_only); // #endif @@ -485,7 +485,7 @@ void buildFullPipeline(ModulePassManager &MPM, PassBuilder *PB, OptimizationLeve #undef JULIA_PASS namespace { - auto createPIC(StandardInstrumentations &SI) { + auto createPIC(StandardInstrumentations &SI) JL_NOTSAFEPOINT { auto PIC = std::make_unique(); //Borrowed from LLVM PassBuilder.cpp:386 #define MODULE_PASS(NAME, CREATE_PASS) \ @@ -535,11 +535,11 @@ PIC->addClassToPassName(decltype(CREATE_PASS)::name(), NAME); return PIC; } - FunctionAnalysisManager createFAM(OptimizationLevel O, TargetIRAnalysis analysis, const Triple &triple) { + FunctionAnalysisManager createFAM(OptimizationLevel O, TargetIRAnalysis analysis, const Triple &triple) JL_NOTSAFEPOINT { FunctionAnalysisManager FAM; // Register the AA manager first so that our version is the one used. - FAM.registerPass([&] { + FAM.registerPass([&] JL_NOTSAFEPOINT { AAManager AA; // TODO: Why are we only doing this for -O3? if (O.getSpeedupLevel() >= 3) { @@ -553,12 +553,12 @@ PIC->addClassToPassName(decltype(CREATE_PASS)::name(), NAME); return AA; }); // Register our TargetLibraryInfoImpl. - FAM.registerPass([&] { return llvm::TargetIRAnalysis(analysis); }); - FAM.registerPass([&] { return llvm::TargetLibraryAnalysis(llvm::TargetLibraryInfoImpl(triple)); }); + FAM.registerPass([&] JL_NOTSAFEPOINT { return llvm::TargetIRAnalysis(analysis); }); + FAM.registerPass([&] JL_NOTSAFEPOINT { return llvm::TargetLibraryAnalysis(llvm::TargetLibraryInfoImpl(triple)); }); return FAM; } - ModulePassManager createMPM(PassBuilder &PB, OptimizationLevel O, OptimizationOptions options) { + ModulePassManager createMPM(PassBuilder &PB, OptimizationLevel O, OptimizationOptions options) JL_NOTSAFEPOINT { ModulePassManager MPM; if (O.getSpeedupLevel() < 2) buildBasicPipeline(MPM, &PB, O, options); @@ -573,6 +573,8 @@ NewPM::NewPM(std::unique_ptr TM, OptimizationLevel O, Optimizatio PB(this->TM.get(), PipelineTuningOptions(), None, PIC.get()), MPM(createMPM(PB, O, options)), O(O) {} +NewPM::~NewPM() = default; + AnalysisManagers::AnalysisManagers(TargetMachine &TM, PassBuilder &PB, OptimizationLevel O) : LAM(), FAM(createFAM(O, TM.getTargetIRAnalysis(), TM.getTargetTriple())), CGAM(), MAM() { PB.registerLoopAnalyses(LAM); PB.registerFunctionAnalyses(FAM); @@ -589,12 +591,16 @@ AnalysisManagers::AnalysisManagers(PassBuilder &PB) : LAM(), FAM(), CGAM(), MAM( PB.crossRegisterProxies(LAM, FAM, CGAM, MAM); } +AnalysisManagers::~AnalysisManagers() = default; + void NewPM::run(Module &M) { //We must recreate the analysis managers every time //so that analyses from previous runs of the pass manager //do not hang around for the next run AnalysisManagers AM{*TM, PB, O}; +#ifndef __clang_gcanalyzer__ /* the analyzer cannot prove we have not added instrumentation callbacks with safepoints */ MPM.run(M, AM.MAM); +#endif } OptimizationLevel getOptLevel(int optlevel) { @@ -670,7 +676,7 @@ static llvm::Optional> parseJu // NOTE: Instead of exporting all the constructors in passes.h we could // forward the callbacks to the respective passes. LLVM seems to prefer this, // and when we add the full pass builder having them directly will be helpful. -void registerCallbacks(PassBuilder &PB) { +void registerCallbacks(PassBuilder &PB) JL_NOTSAFEPOINT { PB.registerPipelineParsingCallback( [](StringRef Name, FunctionPassManager &PM, ArrayRef InnerPipeline) { @@ -712,6 +718,6 @@ void registerCallbacks(PassBuilder &PB) { } extern "C" JL_DLLEXPORT ::llvm::PassPluginLibraryInfo -llvmGetPassPluginInfo() { +llvmGetPassPluginInfo() JL_NOTSAFEPOINT { return {LLVM_PLUGIN_API_VERSION, "Julia", "1", registerCallbacks}; } diff --git a/src/processor.h b/src/processor.h index d1b18cb72b0849..e3f3bd512c9105 100644 --- a/src/processor.h +++ b/src/processor.h @@ -194,14 +194,14 @@ extern JL_DLLEXPORT bool jl_processor_print_help; * If the detected/specified CPU name is not available on the LLVM version specified, * a fallback CPU name will be used. Unsupported features will be ignored. */ -extern "C" JL_DLLEXPORT std::pair> jl_get_llvm_target(bool imaging, uint32_t &flags); +extern "C" JL_DLLEXPORT std::pair> jl_get_llvm_target(bool imaging, uint32_t &flags) JL_NOTSAFEPOINT; /** * Returns the CPU name and feature string to be used by LLVM disassembler. * * This will return a generic CPU name and a full feature string. */ -extern "C" JL_DLLEXPORT const std::pair &jl_get_llvm_disasm_target(void); +extern "C" JL_DLLEXPORT const std::pair &jl_get_llvm_disasm_target(void) JL_NOTSAFEPOINT; struct jl_target_spec_t { // LLVM target name @@ -218,9 +218,9 @@ struct jl_target_spec_t { /** * Return the list of targets to clone */ -extern "C" JL_DLLEXPORT std::vector jl_get_llvm_clone_targets(void); -std::string jl_get_cpu_name_llvm(void); -std::string jl_get_cpu_features_llvm(void); +extern "C" JL_DLLEXPORT std::vector jl_get_llvm_clone_targets(void) JL_NOTSAFEPOINT; +std::string jl_get_cpu_name_llvm(void) JL_NOTSAFEPOINT; +std::string jl_get_cpu_features_llvm(void) JL_NOTSAFEPOINT; #endif #endif diff --git a/src/staticdata.c b/src/staticdata.c index eea59a8cffceb2..7439386483f9b5 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -1,4 +1,3 @@ - // This file is a part of Julia. License is MIT: https://julialang.org/license /* @@ -499,7 +498,7 @@ static void jl_load_sysimg_so(void) #define NBOX_C 1024 -static int jl_needs_serialization(jl_serializer_state *s, jl_value_t *v) +static int jl_needs_serialization(jl_serializer_state *s, jl_value_t *v) JL_NOTSAFEPOINT { // ignore items that are given a special relocation representation if (s->incremental && jl_object_in_image(v)) @@ -3204,8 +3203,6 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl cachesizes->fptrlist = sizeof_fptr_record; } - if (!s.incremental) - jl_init_codegen(); s.s = &sysimg; jl_update_all_fptrs(&s, image); // fptr relocs and registration if (!ccallable_list) { diff --git a/src/support/analyzer_annotations.h b/src/support/analyzer_annotations.h index 70b5a273953f14..3e577e6b45483f 100644 --- a/src/support/analyzer_annotations.h +++ b/src/support/analyzer_annotations.h @@ -12,6 +12,8 @@ #define JL_PROPAGATES_ROOT __attribute__((annotate("julia_propagates_root"))) #define JL_NOTSAFEPOINT __attribute__((annotate("julia_not_safepoint"))) +#define JL_NOTSAFEPOINT_ENTER __attribute__((annotate("julia_notsafepoint_enter"))) +#define JL_NOTSAFEPOINT_LEAVE __attribute__((annotate("julia_notsafepoint_leave"))) #define JL_MAYBE_UNROOTED __attribute__((annotate("julia_maybe_unrooted"))) #define JL_GLOBALLY_ROOTED __attribute__((annotate("julia_globally_rooted"))) #define JL_ROOTING_ARGUMENT __attribute__((annotate("julia_rooting_argument"))) @@ -25,7 +27,7 @@ extern "C" { #endif void JL_GC_PROMISE_ROOTED(void *v) JL_NOTSAFEPOINT; - void jl_may_leak(uintptr_t) JL_NOTSAFEPOINT; + void jl_may_leak(void *v) JL_NOTSAFEPOINT; #ifdef __cplusplus } #endif @@ -34,6 +36,8 @@ extern "C" { #define JL_PROPAGATES_ROOT #define JL_NOTSAFEPOINT +#define JL_NOTSAFEPOINT_ENTER +#define JL_NOTSAFEPOINT_LEAVE #define JL_MAYBE_UNROOTED #define JL_GLOBALLY_ROOTED #define JL_ROOTING_ARGUMENT diff --git a/src/support/ios.h b/src/support/ios.h index 9d0f42d6d1bc40..046edfae0556f4 100644 --- a/src/support/ios.h +++ b/src/support/ios.h @@ -128,8 +128,8 @@ void ios_init_stdstreams(void); /* high-level functions - output */ JL_DLLEXPORT int ios_pututf8(ios_t *s, uint32_t wc); -JL_DLLEXPORT int ios_printf(ios_t *s, const char *format, ...); -JL_DLLEXPORT int ios_vprintf(ios_t *s, const char *format, va_list args); +JL_DLLEXPORT int ios_printf(ios_t *s, const char *format, ...) JL_NOTSAFEPOINT; +JL_DLLEXPORT int ios_vprintf(ios_t *s, const char *format, va_list args) JL_NOTSAFEPOINT; /* high-level stream functions - input */ JL_DLLEXPORT int ios_getutf8(ios_t *s, uint32_t *pwc); diff --git a/src/task.c b/src/task.c index 1772127391183d..d97cadffa55c62 100644 --- a/src/task.c +++ b/src/task.c @@ -620,7 +620,7 @@ JL_NO_ASAN static void ctx_switch(jl_task_t *lastt) sanitizer_finish_switch_fiber(ptls->previous_task, jl_atomic_load_relaxed(&ptls->current_task)); } -JL_DLLEXPORT void jl_switch(void) +JL_DLLEXPORT void jl_switch(void) JL_NOTSAFEPOINT_LEAVE JL_NOTSAFEPOINT_ENTER { jl_task_t *ct = jl_current_task; jl_ptls_t ptls = ct->ptls; @@ -628,6 +628,7 @@ JL_DLLEXPORT void jl_switch(void) if (t == ct) { return; } + int8_t gc_state = jl_gc_unsafe_enter(ptls); if (t->started && t->stkbuf == NULL) jl_error("attempt to switch to exited task"); if (ptls->in_finalizer) @@ -641,7 +642,6 @@ JL_DLLEXPORT void jl_switch(void) // Store old values on the stack and reset sig_atomic_t defer_signal = ptls->defer_signal; - int8_t gc_state = jl_gc_unsafe_enter(ptls); int finalizers_inhibited = ptls->finalizers_inhibited; ptls->finalizers_inhibited = 0; @@ -681,16 +681,16 @@ JL_DLLEXPORT void jl_switch(void) (void)ct; #endif - jl_gc_unsafe_leave(ptls, gc_state); sig_atomic_t other_defer_signal = ptls->defer_signal; ptls->defer_signal = defer_signal; if (other_defer_signal && !defer_signal) jl_sigint_safepoint(ptls); JL_PROBE_RT_RUN_TASK(ct); + jl_gc_unsafe_leave(ptls, gc_state); } -JL_DLLEXPORT void jl_switchto(jl_task_t **pt) +JL_DLLEXPORT void jl_switchto(jl_task_t **pt) JL_NOTSAFEPOINT_ENTER // n.b. this does not actually enter a safepoint { jl_set_next_task(*pt); jl_switch(); diff --git a/src/threading.c b/src/threading.c index 150fbacbc421b4..e825870edc4ea8 100644 --- a/src/threading.c +++ b/src/threading.c @@ -402,7 +402,7 @@ jl_ptls_t jl_init_threadtls(int16_t tid) return ptls; } -JL_DLLEXPORT jl_gcframe_t **jl_adopt_thread(void) +JL_DLLEXPORT jl_gcframe_t **jl_adopt_thread(void) JL_NOTSAFEPOINT_LEAVE { // initialize this thread (assign tid, create heap, set up root task) jl_ptls_t ptls = jl_init_threadtls(-1); @@ -417,7 +417,7 @@ JL_DLLEXPORT jl_gcframe_t **jl_adopt_thread(void) return &ct->gcstack; } -static void jl_delete_thread(void *value) +static void jl_delete_thread(void *value) JL_NOTSAFEPOINT_ENTER { jl_ptls_t ptls = (jl_ptls_t)value; // Acquire the profile write lock, to ensure we are not racing with the `kill` diff --git a/src/threading.h b/src/threading.h index 9fd63f0fd188dd..4df6815124eb9c 100644 --- a/src/threading.h +++ b/src/threading.h @@ -21,7 +21,7 @@ typedef struct _jl_threadarg_t { } jl_threadarg_t; // each thread must initialize its TLS -jl_ptls_t jl_init_threadtls(int16_t tid); +jl_ptls_t jl_init_threadtls(int16_t tid) JL_NOTSAFEPOINT; // provided by a threading infrastructure void jl_init_threadinginfra(void); diff --git a/test/clangsa/GCPushPop.cpp b/test/clangsa/GCPushPop.cpp index ac9f3bfb354b8a..a62c1501bf323c 100644 --- a/test/clangsa/GCPushPop.cpp +++ b/test/clangsa/GCPushPop.cpp @@ -36,7 +36,6 @@ void jl_gc_run_finalizers_in_list(jl_ptls_t ptls, arraylist_t *list) JL_GC_POP(); } -void safepoint(void); bool testfunc1() JL_NOTSAFEPOINT { struct implied_struct1 { // expected-note{{Tried to call method defined here}} From a77774a3d54cc1de1765e6697a11c23c7a2a706c Mon Sep 17 00:00:00 2001 From: Jeremie Knuesel Date: Thu, 5 Jan 2023 03:57:26 +0100 Subject: [PATCH 237/387] Improve documentation on tuples and named tuples (#47981) * Improve documentation on semicolon, tuples and named tuples Co-authored-by: Stefan Karpinski --- base/docs/basedocs.jl | 84 +++++++++++++++++++++++++++++++++---- base/namedtuple.jl | 34 ++++++++++++--- base/tuple.jl | 2 + doc/src/manual/functions.md | 8 ++-- 4 files changed, 111 insertions(+), 17 deletions(-) diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index bb00edbef41ebe..3e86095f181a98 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -1141,8 +1141,16 @@ Adding `;` at the end of a line in the REPL will suppress printing the result of In function declarations, and optionally in calls, `;` separates regular arguments from keywords. -While constructing arrays, if the arguments inside the square brackets are separated by `;` -then their contents are vertically concatenated together. +In array literals, arguments separated by semicolons have their contents +concatenated together. A separator made of a single `;` concatenates vertically +(i.e. along the first dimension), `;;` concatenates horizontally (second +dimension), `;;;` concatenates along the third dimension, etc. Such a separator +can also be used in last position in the square brackets to add trailing +dimensions of length 1. + +A `;` in first position inside of parentheses can be used to construct a named +tuple. The same `(; ...)` syntax on the left side of an assignment allows for +property destructuring. In the standard REPL, typing `;` on an empty line will switch to shell mode. @@ -1166,11 +1174,40 @@ julia> function plot(x, y; style="solid", width=1, color="black") ### end -julia> [1 2; 3 4] +julia> A = [1 2; 3 4] 2×2 Matrix{Int64}: 1 2 3 4 +julia> [1; 3;; 2; 4;;; 10*A] +2×2×2 Array{Int64, 3}: +[:, :, 1] = + 1 2 + 3 4 + +[:, :, 2] = + 10 20 + 30 40 + +julia> [2; 3;;;] +2×1×1 Array{Int64, 3}: +[:, :, 1] = + 2 + 3 + +julia> nt = (; x=1) # without the ; or a trailing comma this would assign to x +(x = 1,) + +julia> key = :a; c = 3; + +julia> nt2 = (; key => 1, b=2, c, nt.x) +(a = 1, b = 2, c = 3, x = 1) + +julia> (; b, x) = nt2; # set variables b and x using property destructuring + +julia> b, x +(2, 1) + julia> ; # upon typing ;, the prompt changes (in place) to: shell> shell> echo hello hello @@ -2078,7 +2115,7 @@ Symbol(x...) Construct a tuple of the given objects. -See also [`Tuple`](@ref), [`NamedTuple`](@ref). +See also [`Tuple`](@ref), [`ntuple`](@ref), [`NamedTuple`](@ref). # Examples ```jldoctest @@ -2776,17 +2813,48 @@ Vararg """ Tuple{Types...} -Tuples are an abstraction of the arguments of a function – without the function itself. The salient aspects of -a function's arguments are their order and their types. Therefore a tuple type is similar to a parameterized -immutable type where each parameter is the type of one field. Tuple types may have any number of parameters. +A tuple is a fixed-length container that can hold any values of different +types, but cannot be modified (it is immutable). The values can be accessed via +indexing. Tuple literals are written with commas and parentheses: + +```jldoctest +julia> (1, 1+1) +(1, 2) + +julia> (1,) +(1,) + +julia> x = (0.0, "hello", 6*7) +(0.0, "hello", 42) + +julia> x[2] +"hello" + +julia> typeof(x) +Tuple{Float64, String, Int64} +``` + +A length-1 tuple must be written with a comma, `(1,)`, since `(1)` would just +be a parenthesized value. `()` represents the empty (length-0) tuple. + +A tuple can be constructed from an iterator by using a `Tuple` type as constructor: + +```jldoctest +julia> Tuple(["a", 1]) +("a", 1) + +julia> Tuple{String, Float64}(["a", 1]) +("a", 1.0) +``` Tuple types are covariant in their parameters: `Tuple{Int}` is a subtype of `Tuple{Any}`. Therefore `Tuple{Any}` is considered an abstract type, and tuple types are only concrete if their parameters are. Tuples do not have field names; fields are only accessed by index. +Tuple types may have any number of parameters. See the manual section on [Tuple Types](@ref). -See also [`Vararg`](@ref), [`NTuple`](@ref), [`tuple`](@ref), [`NamedTuple`](@ref). +See also [`Vararg`](@ref), [`NTuple`](@ref), [`ntuple`](@ref), [`tuple`](@ref), [`NamedTuple`](@ref). """ Tuple diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 6ed09a99e11ec0..7b58ccce1b3041 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -8,6 +8,12 @@ tuple-like collection of values, where each entry has a unique name, represented [`Symbol`](@ref). Like `Tuple`s, `NamedTuple`s are immutable; neither the names nor the values can be modified in place after construction. +A named tuple can be created as a tuple literal with keys, e.g. `(a=1, b=2)`, +or as a tuple literal with semicolon after the opening parenthesis, e.g. `(; +a=1, b=2)` (this form also accepts programmatically generated names as +described below), or using a `NamedTuple` type as constructor, e.g. +`NamedTuple{(:a, :b)}((1,2))`. + Accessing the value associated with a name in a named tuple can be done using field access syntax, e.g. `x.a`, or using [`getindex`](@ref), e.g. `x[:a]` or `x[(:a, :b)]`. A tuple of the names can be obtained using [`keys`](@ref), and a tuple of the values @@ -51,16 +57,34 @@ julia> collect(pairs(x)) ``` In a similar fashion as to how one can define keyword arguments programmatically, -a named tuple can be created by giving a pair `name::Symbol => value` or splatting -an iterator yielding such pairs after a semicolon inside a tuple literal: +a named tuple can be created by giving pairs `name::Symbol => value` after a +semicolon inside a tuple literal. This and the `name=value` syntax can be mixed: ```jldoctest -julia> (; :a => 1) -(a = 1,) +julia> (; :a => 1, :b => 2, c=3) +(a = 1, b = 2, c = 3) +``` + +The name-value pairs can also be provided by splatting a named tuple or any +iterator that yields two-value collections holding each a symbol as first +value: julia> keys = (:a, :b, :c); values = (1, 2, 3); -julia> (; zip(keys, values)...) +julia> NamedTuple{keys}(values) +(a = 1, b = 2, c = 3) + +julia> (; (keys .=> values)...) +(a = 1, b = 2, c = 3) + +julia> nt1 = (a=1, b=2); + +julia> nt2 = (c=3, d=4); + +julia> (; nt1..., nt2..., b=20) # the final b overwrites the value from nt1 +(a = 1, b = 20, c = 3, d = 4) + +julia> (; zip(keys, values)...) # zip yields tuples such as (:a, 1) (a = 1, b = 2, c = 3) ``` diff --git a/base/tuple.jl b/base/tuple.jl index d59c9239217e33..962a26d8fa789f 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -11,6 +11,8 @@ A compact way of representing the type for a tuple of length `N` where all eleme julia> isa((1, 2, 3, 4, 5, 6), NTuple{6, Int}) true ``` + +See also [`ntuple`](@ref). """ NTuple diff --git a/doc/src/manual/functions.md b/doc/src/manual/functions.md index 6d0263776233b0..4b223d5ec0d908 100644 --- a/doc/src/manual/functions.md +++ b/doc/src/manual/functions.md @@ -392,9 +392,8 @@ julia> x.a 2 ``` -Named tuples are very similar to tuples, except that fields can additionally be accessed by name -using dot syntax (`x.a`) in addition to the regular indexing syntax -(`x[1]`). +The fields of named tuples can be accessed by name using dot syntax (`x.a`) in +addition to the regular indexing syntax (`x[1]` or `x[:a]`). ## [Destructuring Assignment and Multiple Return Values](@id destructuring-assignment) @@ -854,7 +853,8 @@ end ``` Inside `f`, `kwargs` will be an immutable key-value iterator over a named tuple. -Named tuples (as well as dictionaries with keys of `Symbol`) can be passed as +Named tuples (as well as dictionaries with keys of `Symbol`, and other iterators +yielding two-value collections with symbol as first values) can be passed as keyword arguments using a semicolon in a call, e.g. `f(x, z=1; kwargs...)`. If a keyword argument is not assigned a default value in the method definition, From a9506f542d7ccd228bd664132dc73401c97f6997 Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Thu, 5 Jan 2023 09:05:58 +0100 Subject: [PATCH 238/387] Avoid allocations in reduction over adjoints (#48120) --- stdlib/LinearAlgebra/src/adjtrans.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/stdlib/LinearAlgebra/src/adjtrans.jl b/stdlib/LinearAlgebra/src/adjtrans.jl index 524be86c0f8f54..1e9687ef0f31a2 100644 --- a/stdlib/LinearAlgebra/src/adjtrans.jl +++ b/stdlib/LinearAlgebra/src/adjtrans.jl @@ -406,6 +406,7 @@ Base.mapreducedim!(f::typeof(identity), op::Union{typeof(*),typeof(Base.mul_prod (Base.mapreducedim!(f∘adjoint, op, switch_dim12(B), parent(A)); B) switch_dim12(B::AbstractVector) = permutedims(B) +switch_dim12(B::AbstractVector{<:Number}) = transpose(B) # avoid allocs due to permutedims switch_dim12(B::AbstractArray{<:Any,0}) = B switch_dim12(B::AbstractArray) = PermutedDimsArray(B, (2, 1, ntuple(Base.Fix1(+,2), ndims(B) - 2)...)) From 80aeebe032a89455482134e8a9f1d46b1f5f2b40 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Thu, 5 Jan 2023 07:47:42 -0500 Subject: [PATCH 239/387] Don't perform extra inference during incremental image creation (#48054) As noted in #48047, we're currently attempting to infer extra methods during incremental image saving, which causes us to miss edges in the image. In particular, in the case of #48047, Cthulhu had the `compile=min` option set, which caused the code instance for `do_typeinf!` to not be infered. However, later it was nevertheless queued for precompilation, causing inference to occur at an inopportune time. This PR simply prevents code instances that don't explicitly have the `->precompile` flag set (e.g. the guard instance created for the interpreter) from being enqueued for precompilation. It is not clear that this is necessarily the correct behavior - we may in fact want to infer these method instances, just before we set up the serializer state, but for now this fixes #48047 for me. I also included an appropriate test and a warning message if we attempt to enter inference when this is not legal, so any revisit of what should be happening here can hopefully make use of those. --- base/Base.jl | 4 ++++ src/gf.c | 7 +++++++ src/staticdata.c | 11 +++++++++-- test/precompile.jl | 17 +++++++++++++++++ 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/base/Base.jl b/base/Base.jl index 22e8b48288efc0..20e729256664f7 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -447,6 +447,10 @@ for m in methods(include) delete_method(m) end +# This method is here only to be overwritten during the test suite to test +# various sysimg related invalidation scenarios. +a_method_to_overwrite_in_test() = inferencebarrier(1) + # These functions are duplicated in client.jl/include(::String) for # nicer stacktraces. Modifications here have to be backported there include(mod::Module, _path::AbstractString) = _include(identity, mod, _path) diff --git a/src/gf.c b/src/gf.c index 481297765ff6c2..af9061737b3196 100644 --- a/src/gf.c +++ b/src/gf.c @@ -280,6 +280,13 @@ jl_code_info_t *jl_type_infer(jl_method_instance_t *mi, size_t world, int force) if (jl_typeinf_func == NULL) return NULL; jl_task_t *ct = jl_current_task; + if (ct->reentrant_inference == (uint16_t)-1) { + // TODO: We should avoid attempting to re-inter inference here at all + // and turn on this warning, but that requires further refactoring + // of the precompile code, so for now just catch that case here. + //jl_printf(JL_STDERR, "ERROR: Attempted to enter inference while writing out image."); + return NULL; + } if (ct->reentrant_inference > 2) return NULL; diff --git a/src/staticdata.c b/src/staticdata.c index 7439386483f9b5..929c44273a6ef3 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -2614,7 +2614,12 @@ JL_DLLEXPORT void jl_create_system_image(void **_native_data, jl_array_t *workli } else { checksumpos_ff = checksumpos; } - jl_gc_enable_finalizers(ct, 0); // make sure we don't run any Julia code concurrently after this point + { + // make sure we don't run any Julia code concurrently after this point + jl_gc_enable_finalizers(ct, 0); + assert(ct->reentrant_inference == 0); + ct->reentrant_inference = (uint16_t)-1; + } jl_prepare_serialization_data(mod_array, newly_inferred, jl_worklist_key(worklist), &extext_methods, &new_specializations, &method_roots_list, &ext_targets, &edges); // Generate _native_data` @@ -2638,7 +2643,9 @@ JL_DLLEXPORT void jl_create_system_image(void **_native_data, jl_array_t *workli jl_save_system_image_to_stream(ff, worklist, extext_methods, new_specializations, method_roots_list, ext_targets, edges); native_functions = NULL; if (worklist) { - jl_gc_enable_finalizers(ct, 1); // make sure we don't run any Julia code concurrently before this point + // Re-enable running julia code for postoutput hooks, atexit, etc. + jl_gc_enable_finalizers(ct, 1); + ct->reentrant_inference = 0; jl_precompile_toplevel_module = NULL; } diff --git a/test/precompile.jl b/test/precompile.jl index 8aa09efe3f417c..8f8c8202832ee0 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -1691,6 +1691,23 @@ precompile_test_harness("DynamicExpressions") do load_path end end +precompile_test_harness("BadInvalidations") do load_path + write(joinpath(load_path, "BadInvalidations.jl"), + """ + module BadInvalidations + Base.Experimental.@compiler_options compile=min optimize=1 + getval() = Base.a_method_to_overwrite_in_test() + getval() + end # module BadInvalidations + """) + Base.compilecache(Base.PkgId("BadInvalidations")) + (@eval Base a_method_to_overwrite_in_test() = inferencebarrier(2)) + (@eval (using BadInvalidations)) + Base.invokelatest() do + @test BadInvalidations.getval() === 2 + end +end + empty!(Base.DEPOT_PATH) append!(Base.DEPOT_PATH, original_depot_path) empty!(Base.LOAD_PATH) From b984c4e198d313a14889f1c33b85ccac98e2d1a0 Mon Sep 17 00:00:00 2001 From: matthias314 <56549971+matthias314@users.noreply.github.com> Date: Thu, 5 Jan 2023 10:18:23 -0500 Subject: [PATCH 240/387] make get! type-stable for AbstractDict (#48128) --- base/abstractdict.jl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/base/abstractdict.jl b/base/abstractdict.jl index 39de4c441a9668..fcadeab76de839 100644 --- a/base/abstractdict.jl +++ b/base/abstractdict.jl @@ -551,10 +551,12 @@ setindex!(t::AbstractDict, v, k1, k2, ks...) = setindex!(t, v, tuple(k1,k2,ks... get!(t::AbstractDict, key, default) = get!(() -> default, t, key) function get!(default::Callable, t::AbstractDict{K,V}, key) where K where V - haskey(t, key) && return t[key] - val = default() - t[key] = val - return val + key = convert(K, key) + if haskey(t, key) + return t[key] + else + return t[key] = convert(V, default()) + end end push!(t::AbstractDict, p::Pair) = setindex!(t, p.second, p.first) From 35d1840447f0230e874a52c92e1a505148844850 Mon Sep 17 00:00:00 2001 From: pchintalapudi <34727397+pchintalapudi@users.noreply.github.com> Date: Thu, 5 Jan 2023 07:52:54 -0800 Subject: [PATCH 241/387] Switch back to LLVM's IR linker (#48106) Co-authored-by: Tim Besard --- src/Makefile | 3 ++- src/aotcompile.cpp | 12 ++++++++---- src/ccall.cpp | 9 +++------ src/cgutils.cpp | 2 +- src/codegen.cpp | 40 ++++++++++++++++++++-------------------- src/jitlayers.cpp | 12 ++++++------ src/jitlayers.h | 27 +++++++++++++++++---------- test/llvmcall.jl | 17 +++++++++++++++++ 8 files changed, 74 insertions(+), 48 deletions(-) diff --git a/src/Makefile b/src/Makefile index 5cb346c0fa95ad..6b914cd53da5b9 100644 --- a/src/Makefile +++ b/src/Makefile @@ -307,6 +307,7 @@ $(build_shlibdir)/libllvmcalltest.$(SHLIB_EXT): $(SRCDIR)/llvm-codegen-shared.h $(BUILDDIR)/llvm-alloc-helpers.o $(BUILDDIR)/llvm-alloc-helpers.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-alloc-helpers.h $(BUILDDIR)/llvm-alloc-opt.o $(BUILDDIR)/llvm-alloc-opt.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-alloc-helpers.h $(BUILDDIR)/llvm-cpufeatures.o $(BUILDDIR)/llvm-cpufeatures.dbg.obj: $(SRCDIR)/jitlayers.h $(SRCDIR)/debug-registry.h +$(BUILDDIR)/llvm-demote-float16.o $(BUILDDIR)/llvm-demote-float16.dbg.obj: $(SRCDIR)/jitlayers.h $(SRCDIR)/debug-registry.h $(BUILDDIR)/llvm-final-gc-lowering.o $(BUILDDIR)/llvm-final-gc-lowering.dbg.obj: $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-codegen-shared.h $(BUILDDIR)/llvm-gc-invariant-verifier.o $(BUILDDIR)/llvm-gc-invariant-verifier.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h $(BUILDDIR)/llvm-julia-licm.o $(BUILDDIR)/llvm-julia-licm.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h $(SRCDIR)/llvm-alloc-helpers.h $(SRCDIR)/llvm-pass-helpers.h @@ -322,7 +323,7 @@ $(BUILDDIR)/signal-handling.o $(BUILDDIR)/signal-handling.dbg.obj: $(addprefix $ $(BUILDDIR)/staticdata.o $(BUILDDIR)/staticdata.dbg.obj: $(SRCDIR)/staticdata_utils.c $(SRCDIR)/precompile_utils.c $(SRCDIR)/processor.h $(SRCDIR)/builtin_proto.h $(BUILDDIR)/toplevel.o $(BUILDDIR)/toplevel.dbg.obj: $(SRCDIR)/builtin_proto.h $(BUILDDIR)/ircode.o $(BUILDDIR)/ircode.dbg.obj: $(SRCDIR)/serialize.h -$(BUILDDIR)/pipeline.o $(BUILDDIR)/pipeline.dbg.obj: $(SRCDIR)/passes.h $(SRCDIR)/jitlayers.h +$(BUILDDIR)/pipeline.o $(BUILDDIR)/pipeline.dbg.obj: $(SRCDIR)/passes.h $(SRCDIR)/jitlayers.h $(SRCDIR)/debug-registry.h $(addprefix $(BUILDDIR)/,threading.o threading.dbg.obj gc.o gc.dbg.obj init.c init.dbg.obj task.o task.dbg.obj): $(addprefix $(SRCDIR)/,threading.h) $(addprefix $(BUILDDIR)/,APInt-C.o APInt-C.dbg.obj runtime_intrinsics.o runtime_intrinsics.dbg.obj): $(SRCDIR)/APInt-C.h diff --git a/src/aotcompile.cpp b/src/aotcompile.cpp index 41292c58486f43..3f986cbbc489d0 100644 --- a/src/aotcompile.cpp +++ b/src/aotcompile.cpp @@ -56,6 +56,7 @@ #include #include +#include using namespace llvm; @@ -289,7 +290,7 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm orc::ThreadSafeModule backing; if (!llvmmod) { ctx = jl_ExecutionEngine->acquireContext(); - backing = jl_create_llvm_module("text", ctx, imaging); + backing = jl_create_ts_module("text", ctx, imaging); } orc::ThreadSafeModule &clone = llvmmod ? *unwrap(llvmmod) : backing; auto ctxt = clone.getContext(); @@ -336,7 +337,7 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm if (src && !emitted.count(codeinst)) { // now add it to our compilation results JL_GC_PROMISE_ROOTED(codeinst->rettype); - orc::ThreadSafeModule result_m = jl_create_llvm_module(name_from_method_instance(codeinst->def), + orc::ThreadSafeModule result_m = jl_create_ts_module(name_from_method_instance(codeinst->def), params.tsctx, params.imaging, clone.getModuleUnlocked()->getDataLayout(), Triple(clone.getModuleUnlocked()->getTargetTriple())); @@ -400,6 +401,7 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm // clones the contents of the module `m` to the shadow_output collector // while examining and recording what kind of function pointer we have + Linker L(*clone.getModuleUnlocked()); for (auto &def : emitted) { jl_merge_module(clone, std::move(std::get<0>(def.second))); jl_code_instance_t *this_code = def.first; @@ -427,7 +429,9 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm data->jl_fvar_map[this_code] = std::make_tuple(func_id, cfunc_id); } if (params._shared_module) { - jl_merge_module(clone, std::move(params._shared_module)); + bool error = L.linkInModule(std::move(params._shared_module)); + assert(!error && "Error linking in shared module"); + (void)error; } // now get references to the globals in the merged module @@ -1095,7 +1099,7 @@ void jl_get_llvmf_defn_impl(jl_llvmf_dump_t* dump, jl_method_instance_t *mi, siz // emit this function into a new llvm module if (src && jl_is_code_info(src)) { auto ctx = jl_ExecutionEngine->getContext(); - orc::ThreadSafeModule m = jl_create_llvm_module(name_from_method_instance(mi), *ctx, imaging_default()); + orc::ThreadSafeModule m = jl_create_ts_module(name_from_method_instance(mi), *ctx, imaging_default()); uint64_t compiler_start_time = 0; uint8_t measure_compile_time_enabled = jl_atomic_load_relaxed(&jl_measure_compile_time_enabled); if (measure_compile_time_enabled) diff --git a/src/ccall.cpp b/src/ccall.cpp index 10f0ba0d2e8bc6..ed3992a78091c2 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -57,9 +57,7 @@ GlobalVariable *jl_emit_RTLD_DEFAULT_var(Module *M) static bool runtime_sym_gvs(jl_codectx_t &ctx, const char *f_lib, const char *f_name, GlobalVariable *&lib, GlobalVariable *&sym) { - auto &TSM = ctx.emission_context.shared_module(*jl_Module); - //Safe b/c emission context holds context lock - auto M = TSM.getModuleUnlocked(); + auto M = &ctx.emission_context.shared_module(*jl_Module); bool runtime_lib = false; GlobalVariable *libptrgv; jl_codegen_params_t::SymMapGV *symMap; @@ -238,8 +236,7 @@ static GlobalVariable *emit_plt_thunk( bool runtime_lib) { ++PLTThunks; - auto &TSM = ctx.emission_context.shared_module(*jl_Module); - Module *M = TSM.getModuleUnlocked(); + auto M = &ctx.emission_context.shared_module(*jl_Module); PointerType *funcptype = PointerType::get(functype, 0); libptrgv = prepare_global_in(M, libptrgv); llvmgv = prepare_global_in(M, llvmgv); @@ -987,7 +984,7 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar // save the module to be linked later. // we cannot do this right now, because linking mutates the destination module, // which might invalidate LLVM values cached in cgval_t's (specifically constant arrays) - ctx.llvmcall_modules.push_back(orc::ThreadSafeModule(std::move(Mod), ctx.emission_context.tsctx)); + ctx.llvmcall_modules.push_back(std::move(Mod)); JL_GC_POP(); diff --git a/src/cgutils.cpp b/src/cgutils.cpp index 84c917e70b5cb3..d2862e55a32413 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -310,7 +310,7 @@ static Value *julia_pgv(jl_codectx_t &ctx, const char *cname, void *addr) } if (gv == nullptr) gv = new GlobalVariable(*M, ctx.types().T_pjlvalue, - false, GlobalVariable::PrivateLinkage, + false, GlobalVariable::ExternalLinkage, NULL, localname); // LLVM passes sometimes strip metadata when moving load around // since the load at the new location satisfy the same condition as the original one. diff --git a/src/codegen.cpp b/src/codegen.cpp index 4ca5795b3e95be..1caafd8e64330a 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1464,7 +1464,7 @@ class jl_codectx_t { bool external_linkage = false; const jl_cgparams_t *params = NULL; - std::vector llvmcall_modules; + std::vector> llvmcall_modules; jl_codectx_t(LLVMContext &llvmctx, jl_codegen_params_t ¶ms) : builder(llvmctx), @@ -2079,22 +2079,21 @@ static jl_cgval_t convert_julia_type(jl_codectx_t &ctx, const jl_cgval_t &v, jl_ return jl_cgval_t(v, typ, new_tindex); } -orc::ThreadSafeModule jl_create_llvm_module(StringRef name, orc::ThreadSafeContext context, bool imaging_mode, const DataLayout &DL, const Triple &triple) +std::unique_ptr jl_create_llvm_module(StringRef name, LLVMContext &context, bool imaging_mode, const DataLayout &DL, const Triple &triple) { ++ModulesCreated; - auto lock = context.getLock(); - Module *m = new Module(name, *context.getContext()); - orc::ThreadSafeModule TSM(std::unique_ptr(m), std::move(context)); + auto m = std::make_unique(name, context); // Some linkers (*cough* OS X) don't understand DWARF v4, so we use v2 in // imaging mode. The structure of v4 is slightly nicer for debugging JIT // code. if (!m->getModuleFlag("Dwarf Version")) { int dwarf_version = 4; -#ifdef _OS_DARWIN_ - if (imaging_mode) + if (triple.isOSDarwin()) { + if (imaging_mode) { dwarf_version = 2; -#endif - m->addModuleFlag(llvm::Module::Warning, "Dwarf Version", dwarf_version); + } + } + m->addModuleFlag(llvm::Module::Warning, "Dwarf Version", dwarf_version); } if (!m->getModuleFlag("Debug Info Version")) m->addModuleFlag(llvm::Module::Warning, "Debug Info Version", @@ -2111,7 +2110,7 @@ orc::ThreadSafeModule jl_create_llvm_module(StringRef name, orc::ThreadSafeConte #if defined(JL_DEBUG_BUILD) && JL_LLVM_VERSION >= 130000 m->setStackProtectorGuard("global"); #endif - return TSM; + return m; } static void jl_init_function(Function *F) @@ -4898,7 +4897,7 @@ static std::pair get_oc_function(jl_codectx_t &ctx, jl_met ir = jl_uncompress_ir(closure_method, ci, (jl_array_t*)inferred); // TODO: Emit this inline and outline it late using LLVM's coroutine support. - orc::ThreadSafeModule closure_m = jl_create_llvm_module( + orc::ThreadSafeModule closure_m = jl_create_ts_module( name_from_method_instance(mi), ctx.emission_context.tsctx, ctx.emission_context.imaging, jl_Module->getDataLayout(), Triple(jl_Module->getTargetTriple())); @@ -8125,14 +8124,15 @@ static jl_llvm_functions_t // link the dependent llvmcall modules, but switch their function's linkage to internal // so that they don't conflict when they show up in the execution engine. - for (auto &TSMod : ctx.llvmcall_modules) { + Linker L(*jl_Module); + for (auto &Mod : ctx.llvmcall_modules) { SmallVector Exports; - TSMod.withModuleDo([&](Module &Mod) { - for (const auto &F: Mod.functions()) - if (!F.isDeclaration()) - Exports.push_back(F.getName().str()); - }); - jl_merge_module(TSM, std::move(TSMod)); + for (const auto &F: Mod->functions()) + if (!F.isDeclaration()) + Exports.push_back(F.getName().str()); + bool error = L.linkInModule(std::move(Mod)); + assert(!error && "linking llvmcall modules failed"); + (void)error; for (auto FN: Exports) jl_Module->getFunction(FN)->setLinkage(GlobalVariable::InternalLinkage); } @@ -8335,7 +8335,7 @@ void jl_compile_workqueue( src = jl_type_infer(codeinst->def, jl_atomic_load_acquire(&jl_world_counter), 0); if (src) { orc::ThreadSafeModule result_m = - jl_create_llvm_module(name_from_method_instance(codeinst->def), + jl_create_ts_module(name_from_method_instance(codeinst->def), params.tsctx, params.imaging, original.getDataLayout(), Triple(original.getTargetTriple())); result.second = jl_emit_code(result_m, codeinst->def, src, src->rettype, params); @@ -8344,7 +8344,7 @@ void jl_compile_workqueue( } else { orc::ThreadSafeModule result_m = - jl_create_llvm_module(name_from_method_instance(codeinst->def), + jl_create_ts_module(name_from_method_instance(codeinst->def), params.tsctx, params.imaging, original.getDataLayout(), Triple(original.getTargetTriple())); result.second = jl_emit_codeinst(result_m, codeinst, NULL, params); diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index 8c9637d72d3943..98c24dfcdf9413 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -199,17 +199,17 @@ static jl_callptr_t _jl_compile_codeinst( jl_workqueue_t emitted; { orc::ThreadSafeModule result_m = - jl_create_llvm_module(name_from_method_instance(codeinst->def), params.tsctx, params.imaging); + jl_create_ts_module(name_from_method_instance(codeinst->def), params.tsctx, params.imaging); jl_llvm_functions_t decls = jl_emit_codeinst(result_m, codeinst, src, params); if (result_m) emitted[codeinst] = {std::move(result_m), std::move(decls)}; { - auto temp_module = jl_create_llvm_module(name_from_method_instance(codeinst->def), params.tsctx, params.imaging); - jl_compile_workqueue(emitted, *temp_module.getModuleUnlocked(), params, CompilationPolicy::Default); + auto temp_module = jl_create_llvm_module(name_from_method_instance(codeinst->def), params.getContext(), params.imaging); + jl_compile_workqueue(emitted, *temp_module, params, CompilationPolicy::Default); } if (params._shared_module) - jl_ExecutionEngine->addModule(std::move(params._shared_module)); + jl_ExecutionEngine->addModule(orc::ThreadSafeModule(std::move(params._shared_module), params.tsctx)); StringMap NewExports; StringMap NewGlobals; for (auto &global : params.globals) { @@ -316,7 +316,7 @@ int jl_compile_extern_c_impl(LLVMOrcThreadSafeModuleRef llvmmod, void *p, void * if (!pparams) { ctx = jl_ExecutionEngine->acquireContext(); } - backing = jl_create_llvm_module("cextern", pparams ? pparams->tsctx : ctx, pparams ? pparams->imaging : imaging_default()); + backing = jl_create_ts_module("cextern", pparams ? pparams->tsctx : ctx, pparams ? pparams->imaging : imaging_default()); into = &backing; } JL_LOCK(&jl_codegen_lock); @@ -334,7 +334,7 @@ int jl_compile_extern_c_impl(LLVMOrcThreadSafeModuleRef llvmmod, void *p, void * jl_jit_globals(params.globals); assert(params.workqueue.empty()); if (params._shared_module) - jl_ExecutionEngine->addModule(std::move(params._shared_module)); + jl_ExecutionEngine->addModule(orc::ThreadSafeModule(std::move(params._shared_module), params.tsctx)); } if (success && llvmmod == NULL) jl_ExecutionEngine->addModule(std::move(*into)); diff --git a/src/jitlayers.h b/src/jitlayers.h index 044d5aacaa2877..e7dfa8959c3766 100644 --- a/src/jitlayers.h +++ b/src/jitlayers.h @@ -67,7 +67,6 @@ DEFINE_SIMPLE_CONVERSION_FUNCTIONS(orc::ThreadSafeModule, LLVMOrcThreadSafeModul void addTargetPasses(legacy::PassManagerBase *PM, const Triple &triple, TargetIRAnalysis analysis) JL_NOTSAFEPOINT; void addOptimizationPasses(legacy::PassManagerBase *PM, int opt_level, bool lower_intrinsics=true, bool dump_native=false, bool external_use=false) JL_NOTSAFEPOINT; void addMachinePasses(legacy::PassManagerBase *PM, int optlevel) JL_NOTSAFEPOINT; -void jl_finalize_module(orc::ThreadSafeModule m) JL_NOTSAFEPOINT; void jl_merge_module(orc::ThreadSafeModule &dest, orc::ThreadSafeModule src) JL_NOTSAFEPOINT; GlobalVariable *jl_emit_RTLD_DEFAULT_var(Module *M) JL_NOTSAFEPOINT; DataLayout jl_create_datalayout(TargetMachine &TM) JL_NOTSAFEPOINT; @@ -182,6 +181,10 @@ typedef std::tuple SymMapGV; // outputs std::vector> workqueue; @@ -208,8 +211,8 @@ typedef struct _jl_codegen_params_t { DenseMap, GlobalVariable*>> allPltMap; - orc::ThreadSafeModule _shared_module; - inline orc::ThreadSafeModule &shared_module(Module &from); + std::unique_ptr _shared_module; + inline Module &shared_module(Module &from); // inputs size_t world = 0; const jl_cgparams_t *params = &jl_default_cgparams; @@ -535,18 +538,22 @@ class JuliaOJIT { OptSelLayerT OptSelLayer; }; extern JuliaOJIT *jl_ExecutionEngine; -orc::ThreadSafeModule jl_create_llvm_module(StringRef name, orc::ThreadSafeContext ctx, bool imaging_mode, const DataLayout &DL = jl_ExecutionEngine->getDataLayout(), const Triple &triple = jl_ExecutionEngine->getTargetTriple()) JL_NOTSAFEPOINT; +std::unique_ptr jl_create_llvm_module(StringRef name, LLVMContext &ctx, bool imaging_mode, const DataLayout &DL = jl_ExecutionEngine->getDataLayout(), const Triple &triple = jl_ExecutionEngine->getTargetTriple()) JL_NOTSAFEPOINT; +inline orc::ThreadSafeModule jl_create_ts_module(StringRef name, orc::ThreadSafeContext ctx, bool imaging_mode, const DataLayout &DL = jl_ExecutionEngine->getDataLayout(), const Triple &triple = jl_ExecutionEngine->getTargetTriple()) JL_NOTSAFEPOINT { + auto lock = ctx.getLock(); + return orc::ThreadSafeModule(jl_create_llvm_module(name, *ctx.getContext(), imaging_mode, DL, triple), ctx); +} -orc::ThreadSafeModule &jl_codegen_params_t::shared_module(Module &from) JL_NOTSAFEPOINT { +Module &jl_codegen_params_t::shared_module(Module &from) JL_NOTSAFEPOINT { if (!_shared_module) { - _shared_module = jl_create_llvm_module("globals", tsctx, imaging, from.getDataLayout(), Triple(from.getTargetTriple())); + _shared_module = jl_create_llvm_module("globals", getContext(), imaging, from.getDataLayout(), Triple(from.getTargetTriple())); assert(&from.getContext() == tsctx.getContext() && "Module context differs from codegen_params context!"); } else { - assert(&from.getContext() == _shared_module.getContext().getContext() && "Module context differs from shared module context!"); - assert(from.getDataLayout() == _shared_module.getModuleUnlocked()->getDataLayout() && "Module data layout differs from shared module data layout!"); - assert(from.getTargetTriple() == _shared_module.getModuleUnlocked()->getTargetTriple() && "Module target triple differs from shared module target triple!"); + assert(&from.getContext() == &getContext() && "Module context differs from shared module context!"); + assert(from.getDataLayout() == _shared_module->getDataLayout() && "Module data layout differs from shared module data layout!"); + assert(from.getTargetTriple() == _shared_module->getTargetTriple() && "Module target triple differs from shared module target triple!"); } - return _shared_module; + return *_shared_module; } Pass *createLowerPTLSPass(bool imaging_mode) JL_NOTSAFEPOINT; diff --git a/test/llvmcall.jl b/test/llvmcall.jl index b7f78205ec8564..a89696ed9c6c26 100644 --- a/test/llvmcall.jl +++ b/test/llvmcall.jl @@ -205,6 +205,23 @@ module CcallableRetTypeTest @test do_the_call() === 42.0 end +# Issue #48093 - test that non-external globals are not deduplicated +function kernel() + Base.llvmcall((""" + @shmem = internal global i8 0, align 8 + define void @entry() { + store i8 1, i8* @shmem + ret void + }""", "entry"), Cvoid, Tuple{}) + Base.llvmcall((""" + @shmem = internal global i8 0, align 8 + define i8 @entry() { + %1 = load i8, i8* @shmem + ret i8 %1 + }""", "entry"), UInt8, Tuple{}) +end +@test kernel() == 0x00 + # If this test breaks, you've probably broken Cxx.jl - please check module LLVMCallFunctionTest using Base: llvmcall From 321c5f55165643cff299ae7d270cfb3d5df73779 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Thu, 5 Jan 2023 16:59:45 +0100 Subject: [PATCH 242/387] Avoid a couple of InexactErrors in the IdDict code. (#48116) --- base/iddict.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/base/iddict.jl b/base/iddict.jl index 7247a85c9afc80..dc7af461b09ec2 100644 --- a/base/iddict.jl +++ b/base/iddict.jl @@ -68,7 +68,7 @@ end empty(d::IdDict, ::Type{K}, ::Type{V}) where {K, V} = IdDict{K,V}() -function rehash!(d::IdDict, newsz = length(d.ht)) +function rehash!(d::IdDict, newsz = length(d.ht)%UInt) d.ht = ccall(:jl_idtable_rehash, Vector{Any}, (Any, Csize_t), d.ht, newsz) d end @@ -89,7 +89,7 @@ function setindex!(d::IdDict{K,V}, @nospecialize(val), @nospecialize(key)) where val = convert(V, val) end if d.ndel >= ((3*length(d.ht))>>2) - rehash!(d, max(length(d.ht)>>1, 32)) + rehash!(d, max((length(d.ht)%UInt)>>1, 32)) d.ndel = 0 end inserted = RefValue{Cint}(0) @@ -143,7 +143,7 @@ end _oidd_nextind(a, i) = reinterpret(Int, ccall(:jl_eqtable_nextind, Csize_t, (Any, Csize_t), a, i)) function iterate(d::IdDict{K,V}, idx=0) where {K, V} - idx = _oidd_nextind(d.ht, idx) + idx = _oidd_nextind(d.ht, idx%UInt) idx == -1 && return nothing return (Pair{K, V}(d.ht[idx + 1]::K, d.ht[idx + 2]::V), idx + 2) end From dc2b4d95a8618f5f391e1cc1875b929bda11e2d0 Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Thu, 5 Jan 2023 19:32:40 +0100 Subject: [PATCH 243/387] Deprecate AMD's LWP extension (#48131) --- src/features_x86.h | 2 +- src/processor_x86.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features_x86.h b/src/features_x86.h index 93cef3d8ce30e0..acacaa68751d3c 100644 --- a/src/features_x86.h +++ b/src/features_x86.h @@ -89,7 +89,7 @@ JL_FEATURE_DEF(lzcnt, 32 * 5 + 5, 0) JL_FEATURE_DEF(sse4a, 32 * 5 + 6, 0) JL_FEATURE_DEF(prfchw, 32 * 5 + 8, 0) JL_FEATURE_DEF(xop, 32 * 5 + 11, 0) -JL_FEATURE_DEF(lwp, 32 * 5 + 15, 0) +// JL_FEATURE_DEF(lwp, 32 * 5 + 15, 0) Deprecated JL_FEATURE_DEF(fma4, 32 * 5 + 16, 0) JL_FEATURE_DEF(tbm, 32 * 5 + 21, 0) JL_FEATURE_DEF(mwaitx, 32 * 5 + 29, 0) diff --git a/src/processor_x86.cpp b/src/processor_x86.cpp index 6f064ddd47d19e..c61712ada787af 100644 --- a/src/processor_x86.cpp +++ b/src/processor_x86.cpp @@ -219,7 +219,7 @@ constexpr auto btver2 = btver1 | get_feature_masks(sse41, sse42, avx, aes, pclmu movbe, xsave, xsaveopt); constexpr auto bdver1 = amdfam10 | get_feature_masks(xop, fma4, avx, ssse3, sse41, sse42, aes, - prfchw, pclmul, xsave, lwp); + prfchw, pclmul, xsave); constexpr auto bdver2 = bdver1 | get_feature_masks(f16c, bmi, tbm, fma); constexpr auto bdver3 = bdver2 | get_feature_masks(xsaveopt, fsgsbase); constexpr auto bdver4 = bdver3 | get_feature_masks(avx2, bmi2, mwaitx, movbe, rdrnd); From 0913cbca8fd298374109b001adc3669274c847ee Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Thu, 5 Jan 2023 14:03:05 -0600 Subject: [PATCH 244/387] Fix invalidations in `finish_show_ir` (#48134) The whole module is under `@nospecialize`, so inference needs us to annotate the argtypes. --- base/compiler/ssair/show.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/compiler/ssair/show.jl b/base/compiler/ssair/show.jl index 71a075f30f8216..d2291404f11c6d 100644 --- a/base/compiler/ssair/show.jl +++ b/base/compiler/ssair/show.jl @@ -858,7 +858,7 @@ function show_ir_stmts(io::IO, ir::Union{IRCode, CodeInfo, IncrementalCompact}, return bb_idx end -function finish_show_ir(io::IO, cfg, config::IRShowConfig) +function finish_show_ir(io::IO, cfg::CFG, config::IRShowConfig) max_bb_idx_size = length(string(length(cfg.blocks))) config.line_info_preprinter(io, " "^(max_bb_idx_size + 2), 0) return nothing From 54aa57c78a403329fbaa82d13f8601132a473efe Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Thu, 5 Jan 2023 14:15:01 -0600 Subject: [PATCH 245/387] Make QuickerSort efficient for non-homogonous eltype (#47973) * set `v[j] = pivot` in partition rather than returning pivot to caller to make partition! type stable for non-concrete eltype --- base/sort.jl | 23 ++++++++++++++--------- test/sorting.jl | 8 ++++++++ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/base/sort.jl b/base/sort.jl index 043322dd25b7bd..276d3f5d29727b 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -998,7 +998,8 @@ select_pivot(lo::Integer, hi::Integer) = typeof(hi-lo)(hash(lo) % (hi-lo+1)) + l # # returns (pivot, pivot_index) where pivot_index is the location the pivot # should end up, but does not set t[pivot_index] = pivot -function partition!(t::AbstractVector, lo::Integer, hi::Integer, offset::Integer, o::Ordering, v::AbstractVector, rev::Bool) +function partition!(t::AbstractVector, lo::Integer, hi::Integer, offset::Integer, o::Ordering, + v::AbstractVector, rev::Bool, pivot_dest::AbstractVector, pivot_index_offset::Integer) pivot_index = select_pivot(lo, hi) @inbounds begin pivot = v[pivot_index] @@ -1016,14 +1017,16 @@ function partition!(t::AbstractVector, lo::Integer, hi::Integer, offset::Integer offset += fx lo += 1 end + pivot_index = lo-offset + pivot_index_offset + pivot_dest[pivot_index] = pivot end - # pivot_index = lo-offset - # t[pivot_index] is whatever it was before - # t[pivot_index] >* pivot, reverse stable + # t_pivot_index = lo-offset (i.e. without pivot_index_offset) + # t[t_pivot_index] is whatever it was before unless t is the pivot_dest + # t[t_pivot_index] >* pivot, reverse stable - pivot, lo-offset + pivot_index end function _sort!(v::AbstractVector, a::QuickerSort, o::Ordering, kw; @@ -1037,9 +1040,11 @@ function _sort!(v::AbstractVector, a::QuickerSort, o::Ordering, kw; end while lo < hi && hi - lo > SMALL_THRESHOLD - pivot, j = swap ? partition!(v, lo+offset, hi+offset, offset, o, t, rev) : partition!(t, lo, hi, -offset, o, v, rev) - j -= !swap*offset - @inbounds v[j] = pivot + j = if swap + partition!(v, lo+offset, hi+offset, offset, o, t, rev, v, 0) + else + partition!(t, lo, hi, -offset, o, v, rev, v, -offset) + end swap = !swap # For QuickerSort(), a.lo === a.hi === missing, so the first two branches get skipped diff --git a/test/sorting.jl b/test/sorting.jl index d909b30ee8646a..1b79070d7e06ef 100644 --- a/test/sorting.jl +++ b/test/sorting.jl @@ -918,6 +918,14 @@ end @test bsqs() === bsqs(missing, missing, InsertionSort) end +@testset "QuickerSort allocations on non-concrete eltype" begin + v = Vector{Union{Nothing, Bool}}(rand(Bool, 10000)) + @test 4 == @allocations sort(v) + @test 4 == @allocations sort(v; alg=Base.Sort.QuickerSort()) + # it would be nice if these numbers were lower (1 or 2), but these + # test that we don't have O(n) allocations due to type instability +end + function test_allocs() v = rand(10) i = randperm(length(v)) From 4562cfae36499a656ccfecfceb59bd9348d46e93 Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Thu, 5 Jan 2023 17:17:59 -0300 Subject: [PATCH 246/387] Fix small nits in multiversioning (#47675) --- src/llvm-multiversioning.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/llvm-multiversioning.cpp b/src/llvm-multiversioning.cpp index 9ec7982a6d975a..bb1f6590a3207f 100644 --- a/src/llvm-multiversioning.cpp +++ b/src/llvm-multiversioning.cpp @@ -51,7 +51,7 @@ void replaceUsesWithLoad(Function &F, function_ref Date: Thu, 5 Jan 2023 20:57:07 -0500 Subject: [PATCH 247/387] Math tests: if fma is not available, relax some tests from exact equality to approximate equality (#48102) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Math tests: if fma is not available, relax some tests from exact equality to approximate equality * Apply suggestions from code review Co-authored-by: Mosè Giordano * `has_fma` has no runtime support * Add `Rational{Int}` * Put the FMA support info in the testset context * Fix whitespace * Remove inaccurate testset name Co-authored-by: Mosè Giordano --- test/math.jl | 54 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/test/math.jl b/test/math.jl index 055f143cea39da..93fcf1a8e3150a 100644 --- a/test/math.jl +++ b/test/math.jl @@ -8,6 +8,19 @@ function isnan_type(::Type{T}, x) where T isa(x, T) && isnan(x) end +# has_fma has no runtime support. +# So we need function wrappers to make this work. +has_fma_Int() = Core.Compiler.have_fma(Int) +has_fma_Float32() = Core.Compiler.have_fma(Float32) +has_fma_Float64() = Core.Compiler.have_fma(Float64) + +has_fma = Dict( + Int => has_fma_Int(), + Rational{Int} => has_fma_Int(), + Float32 => has_fma_Float32(), + Float64 => has_fma_Float64(), +) + @testset "clamp" begin @test clamp(0, 1, 3) == 1 @test clamp(1, 1, 3) == 1 @@ -481,18 +494,29 @@ end @test cospi(convert(T,-1.5))::fT === zero(fT) @test_throws DomainError cospi(convert(T,Inf)) end - @testset "Check exact values" begin - @test sind(convert(T,30)) == 0.5 - @test cosd(convert(T,60)) == 0.5 - @test sind(convert(T,150)) == 0.5 - @test sinpi(one(T)/convert(T,6)) == 0.5 - @test sincospi(one(T)/convert(T,6))[1] == 0.5 - @test_throws DomainError sind(convert(T,Inf)) - @test_throws DomainError cosd(convert(T,Inf)) - T != Float32 && @test cospi(one(T)/convert(T,3)) == 0.5 - T != Float32 && @test sincospi(one(T)/convert(T,3))[2] == 0.5 - T == Rational{Int} && @test sinpi(5//6) == 0.5 - T == Rational{Int} && @test sincospi(5//6)[1] == 0.5 + @testset begin + # If the machine supports fma (fused multiply add), we require exact equality. + # Otherwise, we only require approximate equality. + if has_fma[T] + my_eq = (==) + @debug "On this machine, FMA is supported for $(T), so we will test for exact equality" my_eq + else + my_eq = isapprox + @debug "On this machine, FMA is not supported for $(T), so we will test for approximate equality" my_eq + end + @testset let context=(T, has_fma[T], my_eq) + @test sind(convert(T,30)) == 0.5 + @test cosd(convert(T,60)) == 0.5 + @test sind(convert(T,150)) == 0.5 + @test my_eq(sinpi(one(T)/convert(T,6)), 0.5) + @test my_eq(sincospi(one(T)/convert(T,6))[1], 0.5) + @test_throws DomainError sind(convert(T,Inf)) + @test_throws DomainError cosd(convert(T,Inf)) + T != Float32 && @test my_eq(cospi(one(T)/convert(T,3)), 0.5) + T != Float32 && @test my_eq(sincospi(one(T)/convert(T,3))[2], 0.5) + T == Rational{Int} && @test my_eq(sinpi(5//6), 0.5) + T == Rational{Int} && @test my_eq(sincospi(5//6)[1], 0.5) + end end end scdm = sincosd(missing) @@ -546,7 +570,11 @@ end @test sinc(0.00099) ≈ 0.9999983878009009 rtol=1e-15 @test sinc(0.05f0) ≈ 0.9958927352435614 rtol=1e-7 @test sinc(0.0499f0) ≈ 0.9959091277049384 rtol=1e-7 - @test cosc(0.14) ≈ -0.4517331883801308 rtol=1e-15 + if has_fma[Float64] + @test cosc(0.14) ≈ -0.4517331883801308 rtol=1e-15 + else + @test cosc(0.14) ≈ -0.4517331883801308 rtol=1e-14 + end @test cosc(0.1399) ≈ -0.45142306168781854 rtol=1e-14 @test cosc(0.26f0) ≈ -0.7996401373462212 rtol=5e-7 @test cosc(0.2599f0) ≈ -0.7993744054401625 rtol=5e-7 From 463e5f0fef2665497438b06bd5ad0e326015530e Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Fri, 6 Jan 2023 04:43:23 +0100 Subject: [PATCH 248/387] add a note to `ismutable` docstring about `String` and `Symbol` (#48149) --- base/reflection.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/base/reflection.jl b/base/reflection.jl index 2b559b73261c65..a559ed63d58411 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -527,6 +527,10 @@ Return `true` if and only if value `v` is mutable. See [Mutable Composite Types for a discussion of immutability. Note that this function works on values, so if you give it a `DataType`, it will tell you that a value of the type is mutable. +!!! note + For technical reasons, `ismutable` returns `true` for values of certain special types + (for example `String` and `Symbol`) even though they cannot be mutated in a permissible way. + See also [`isbits`](@ref), [`isstructtype`](@ref). # Examples From f056c3436c04bd67b04696f85cc71fcddb9a41e4 Mon Sep 17 00:00:00 2001 From: Jakob Nybo Nissen Date: Fri, 6 Jan 2023 09:19:13 +0100 Subject: [PATCH 249/387] Improve type stability of array_subpadding slightly (#48136) This should be slightly more efficient as the compiler now only tries to call `iterate` on `t` and `s` once, and will not try to destructure the result if the `iterate` call returns `nothing`. This change reduce spurious JET warnings. --- base/reinterpretarray.jl | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/base/reinterpretarray.jl b/base/reinterpretarray.jl index 1fe0788a1739a0..ebcb7437291606 100644 --- a/base/reinterpretarray.jl +++ b/base/reinterpretarray.jl @@ -725,11 +725,18 @@ end @assume_effects :total function array_subpadding(S, T) lcm_size = lcm(sizeof(S), sizeof(T)) s, t = CyclePadding(S), CyclePadding(T) - isempty(t) && return true - isempty(s) && return false checked_size = 0 - ps, sstate = iterate(s) # use of Stateful harms inference and makes this vulnerable to invalidation - pad, tstate = iterate(t) + # use of Stateful harms inference and makes this vulnerable to invalidation + (pad, tstate) = let + it = iterate(t) + it === nothing && return true + it + end + (ps, sstate) = let + it = iterate(s) + it === nothing && return false + it + end while checked_size < lcm_size while true # See if there's corresponding padding in S From 46365eae3043f2e3af47d28952a6524436bbeaf6 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Fri, 6 Jan 2023 23:28:41 +0900 Subject: [PATCH 250/387] improve the effects of `Base._tuple_unique_fieldtypes` (#48112) Discovered during inspecting #48097. --- base/essentials.jl | 6 ++++-- base/tuple.jl | 5 ++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/base/essentials.jl b/base/essentials.jl index 2093c792dd9b44..61e9b1b25800d8 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -386,8 +386,10 @@ function isvatuple(@nospecialize(t)) return false end -unwrapva(t::Core.TypeofVararg) = isdefined(t, :T) ? t.T : Any -unwrapva(@nospecialize(t)) = t +function unwrapva(@nospecialize(t)) + isa(t, Core.TypeofVararg) || return t + return isdefined(t, :T) ? t.T : Any +end function unconstrain_vararg_length(va::Core.TypeofVararg) # construct a new Vararg type where its length is unconstrained, diff --git a/base/tuple.jl b/base/tuple.jl index 962a26d8fa789f..219315e77944d9 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -213,13 +213,12 @@ function _tuple_unique_fieldtypes(@nospecialize t) t´ = unwrap_unionall(t) # Given t = Tuple{Vararg{S}} where S<:Real, the various # unwrapping/wrapping/va-handling here will return Real - if t isa Union + if t´ isa Union union!(types, _tuple_unique_fieldtypes(rewrap_unionall(t´.a, t))) union!(types, _tuple_unique_fieldtypes(rewrap_unionall(t´.b, t))) else - r = Union{} for ti in (t´::DataType).parameters - r = push!(types, rewrap_unionall(unwrapva(ti), t)) + push!(types, rewrap_unionall(unwrapva(ti), t)) end end return Core.svec(types...) From 0eafda842d5565cedf2719d1f23ee76ee3253395 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Fri, 6 Jan 2023 10:29:58 -0500 Subject: [PATCH 251/387] doc: clarify `let x` without an assignment (#48122) --- base/docs/basedocs.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index 3e86095f181a98..7d792a401fdab7 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -499,7 +499,8 @@ sense to write something like `let x = x`, since the two `x` variables are disti the left-hand side locally shadowing the `x` from the outer scope. This can even be a useful idiom as new local variables are freshly created each time local scopes are entered, but this is only observable in the case of variables that outlive their -scope via closures. +scope via closures. A `let` variable without an assignment, such as `var2` in the +example above, declares a new local variable that is not yet bound to a value. By contrast, [`begin`](@ref) blocks also group multiple expressions together but do not introduce scope or have the special assignment syntax. From fd41b59cc9af12cf06d97b03dfa457b0da2a1893 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Fri, 6 Jan 2023 17:49:43 -0500 Subject: [PATCH 252/387] Allow irinterp to refine nothrow effect (#48066) This addresses a remaining todo in the irinterp code to allow it to compute whether its particular evaluation refined `nothrow`. As a result, we can re-enable it for a larger class of ir (we had previously disabled it to avoid regressing cases where regular constprop was able to prove a `nothrow` refinement, but irinterp was not). --- base/compiler/abstractinterpretation.jl | 9 +++--- base/compiler/optimize.jl | 2 +- base/compiler/ssair/irinterp.jl | 40 ++++++++++++++++++------- base/compiler/ssair/slot2ssa.jl | 2 +- base/reflection.jl | 2 ++ base/tuple.jl | 1 + test/broadcast.jl | 2 +- 7 files changed, 40 insertions(+), 18 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 7ca6a19eaf9670..4700e72ccf8744 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -836,9 +836,7 @@ function concrete_eval_eligible(interp::AbstractInterpreter, if is_all_const_arg(arginfo, #=start=#2) return true else - # TODO: `is_nothrow` is not an actual requirement here, this is just a hack - # to avoid entering semi concrete eval while it doesn't properly override effects - return is_nothrow(result.effects) ? false : nothing + return false end end return nothing @@ -1010,10 +1008,11 @@ function abstract_call_method_with_const_args(interp::AbstractInterpreter, ir = codeinst_to_ir(interp, code) if isa(ir, IRCode) irsv = IRInterpretationState(interp, ir, mi, sv.world, arginfo.argtypes) - rt = ir_abstract_constant_propagation(interp, irsv) + rt, nothrow = ir_abstract_constant_propagation(interp, irsv) @assert !(rt isa Conditional || rt isa MustAlias) "invalid lattice element returned from IR interpretation" if !isa(rt, Type) || typeintersect(rt, Bool) === Union{} - return ConstCallResults(rt, SemiConcreteResult(mi, ir, result.effects), result.effects, mi) + new_effects = Effects(result.effects; nothrow=nothrow) + return ConstCallResults(rt, SemiConcreteResult(mi, ir, new_effects), new_effects, mi) end end end diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index ddf739d104809e..c00cfd8d0c26a6 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -649,7 +649,7 @@ function convert_to_ircode(ci::CodeInfo, sv::OptimizationState) insert!(codelocs, idx + 1, codelocs[idx]) insert!(ssavaluetypes, idx + 1, Union{}) insert!(stmtinfo, idx + 1, NoCallInfo()) - insert!(ssaflags, idx + 1, ssaflags[idx]) + insert!(ssaflags, idx + 1, IR_FLAG_NOTHROW) if ssachangemap === nothing ssachangemap = fill(0, nstmts) end diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index 717a4eec102c23..92e130a65cc756 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -144,9 +144,9 @@ function concrete_eval_invoke(interp::AbstractInterpreter, inst::Expr, mi::MethodInstance, irsv::IRInterpretationState) mi_cache = WorldView(code_cache(interp), irsv.world) code = get(mi_cache, mi, nothing) - code === nothing && return nothing + code === nothing && return Pair{Any, Bool}(nothing, false) argtypes = collect_argtypes(interp, inst.args[2:end], nothing, irsv.ir) - argtypes === nothing && return Union{} + argtypes === nothing && return Pair{Any, Bool}(Union{}, false) effects = decode_effects(code.ipo_purity_bits) if is_foldable(effects) && is_all_const_arg(argtypes, #=start=#1) args = collect_const_args(argtypes, #=start=#1) @@ -154,10 +154,10 @@ function concrete_eval_invoke(interp::AbstractInterpreter, value = try Core._call_in_world_total(world, args...) catch - return Union{} + return Pair{Any, Bool}(Union{}, false) end if is_inlineable_constant(value) - return Const(value) + return Pair{Any, Bool}(Const(value), true) end else ir′ = codeinst_to_ir(interp, code) @@ -166,7 +166,7 @@ function concrete_eval_invoke(interp::AbstractInterpreter, return _ir_abstract_constant_propagation(interp, irsv′) end end - return nothing + return Pair{Any, Bool}(nothing, is_nothrow(effects)) end function abstract_eval_phi_stmt(interp::AbstractInterpreter, phi::PhiNode, ::Int, irsv::IRInterpretationState) @@ -183,6 +183,12 @@ function reprocess_instruction!(interp::AbstractInterpreter, if condval isa Bool function update_phi!(from::Int, to::Int) if length(ir.cfg.blocks[to].preds) == 0 + # Kill the entire block + for idx in ir.cfg.blocks[to].stmts + ir.stmts[idx][:inst] = nothing + ir.stmts[idx][:type] = Union{} + ir.stmts[idx][:flag] = IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW + end return end for idx in ir.cfg.blocks[to].stmts @@ -205,6 +211,7 @@ function reprocess_instruction!(interp::AbstractInterpreter, if bb === nothing bb = block_for_inst(ir, idx) end + ir.stmts[idx][:flag] |= IR_FLAG_NOTHROW if condval ir.stmts[idx][:inst] = nothing ir.stmts[idx][:type] = Any @@ -221,20 +228,25 @@ function reprocess_instruction!(interp::AbstractInterpreter, rt = nothing if isa(inst, Expr) head = inst.head - if head === :call || head === :foreigncall || head === :new + if head === :call || head === :foreigncall || head === :new || head === :splatnew (; rt, effects) = abstract_eval_statement_expr(interp, inst, nothing, ir, irsv.mi) # All other effects already guaranteed effect free by construction if is_nothrow(effects) + ir.stmts[idx][:flag] |= IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW if isa(rt, Const) && is_inlineable_constant(rt.val) ir.stmts[idx][:inst] = quoted(rt.val) - else - ir.stmts[idx][:flag] |= IR_FLAG_EFFECT_FREE end end elseif head === :invoke mi′ = inst.args[1]::MethodInstance if mi′ !== irsv.mi # prevent infinite loop - rt = concrete_eval_invoke(interp, inst, mi′, irsv) + rt, nothrow = concrete_eval_invoke(interp, inst, mi′, irsv) + if nothrow + ir.stmts[idx][:flag] |= IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW + if isa(rt, Const) && is_inlineable_constant(rt.val) + ir.stmts[idx][:inst] = quoted(rt.val) + end + end end elseif head === :throw_undef_if_not || # TODO: Terminate interpretation early if known false? head === :gc_preserve_begin || @@ -416,7 +428,15 @@ function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IR end end - return maybe_singleton_const(ultimate_rt) + nothrow = true + for i = 1:length(ir.stmts) + if (ir.stmts[i][:flag] & IR_FLAG_NOTHROW) == 0 + nothrow = false + break + end + end + + return Pair{Any, Bool}(maybe_singleton_const(ultimate_rt), nothrow) end function ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IRInterpretationState) diff --git a/base/compiler/ssair/slot2ssa.jl b/base/compiler/ssair/slot2ssa.jl index 79bdf817dc8667..289cea14dc01d9 100644 --- a/base/compiler/ssair/slot2ssa.jl +++ b/base/compiler/ssair/slot2ssa.jl @@ -203,7 +203,7 @@ function strip_trailing_junk!(ci::CodeInfo, code::Vector{Any}, info::Vector{Call push!(ssavaluetypes, Union{}) push!(codelocs, 0) push!(info, NoCallInfo()) - push!(ssaflags, IR_FLAG_NULL) + push!(ssaflags, IR_FLAG_NOTHROW) end nothing end diff --git a/base/reflection.jl b/base/reflection.jl index a559ed63d58411..d54d31a3d626aa 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -790,6 +790,7 @@ julia> Base.fieldindex(Foo, :z, false) """ function fieldindex(T::DataType, name::Symbol, err::Bool=true) @_foldable_meta + @noinline return Int(ccall(:jl_field_index, Cint, (Any, Any, Cint), T, name, err)+1) end @@ -804,6 +805,7 @@ end function argument_datatype(@nospecialize t) @_total_meta + @noinline return ccall(:jl_argument_datatype, Any, (Any,), t)::Union{Nothing,DataType} end diff --git a/base/tuple.jl b/base/tuple.jl index 219315e77944d9..e0adbfe6d20cc2 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -582,6 +582,7 @@ any(x::Tuple{Bool, Bool, Bool}) = x[1]|x[2]|x[3] # a version of `in` esp. for NamedTuple, to make it pure, and not compiled for each tuple length function sym_in(x::Symbol, @nospecialize itr::Tuple{Vararg{Symbol}}) + @noinline @_total_meta for y in itr y === x && return true diff --git a/test/broadcast.jl b/test/broadcast.jl index 1893acc8c11496..5afc60b9c55122 100644 --- a/test/broadcast.jl +++ b/test/broadcast.jl @@ -1104,7 +1104,7 @@ end end arr = rand(1000) @allocated test(arr) - @test (@allocated test(arr)) == 0 + @test (@allocated test(arr)) <= 16 end @testset "Fix type unstable .&& #43470" begin From 1508425368171c6d6b1da98d50095da5b8e7e42a Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Fri, 6 Jan 2023 17:50:26 -0500 Subject: [PATCH 253/387] Slightly generalize _compute_sparam elision (#48144) To catch a case that occurs in FuncPipelines.jl and was causing precision issues in #48066. --- base/compiler/ssair/passes.jl | 25 ++++++++++++++++++------- test/compiler/irpasses.jl | 8 ++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index 24293586e06290..82ba6ddf062d79 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -796,7 +796,6 @@ end end && return nothing arg = sig.parameters[i] - isa(arg, DataType) || return nothing rarg = def.args[2 + i] isa(rarg, SSAValue) || return nothing @@ -805,6 +804,10 @@ end rarg = argdef.args[1] isa(rarg, SSAValue) || return nothing argdef = compact[rarg][:inst] + else + isa(arg, DataType) || return nothing + isType(arg) || return nothing + arg = arg.parameters[1] end is_known_call(argdef, Core.apply_type, compact) || return nothing @@ -815,15 +818,23 @@ end applyT = applyT.val isa(applyT, UnionAll) || return nothing + # N.B.: At the moment we only lift the valI == 1 case, so we + # only need to look at the outermost tvar. applyTvar = applyT.var applyTbody = applyT.body - isa(applyTbody, DataType) || return nothing - applyTbody.name == arg.name || return nothing - length(applyTbody.parameters) == length(arg.parameters) == 1 || return nothing - applyTbody.parameters[1] === applyTvar || return nothing - arg.parameters[1] === tvar || return nothing - return LiftedValue(argdef.args[3]) + arg = unwrap_unionall(arg) + applyTbody = unwrap_unionall(applyTbody) + + (isa(arg, DataType) && isa(applyTbody, DataType)) || return nothing + applyTbody.name === arg.name || return nothing + length(applyTbody.parameters) == length(arg.parameters) || return nothing + for i = 1:length(applyTbody.parameters) + if applyTbody.parameters[i] === applyTvar && arg.parameters[i] === tvar + return LiftedValue(argdef.args[3]) + end + end + return nothing end # NOTE we use `IdSet{Int}` instead of `BitSet` for in these passes since they work on IR after inlining, diff --git a/test/compiler/irpasses.jl b/test/compiler/irpasses.jl index 2db01c4b85444e..bc2cb0d3507f33 100644 --- a/test/compiler/irpasses.jl +++ b/test/compiler/irpasses.jl @@ -1221,3 +1221,11 @@ function a47180(b; stdout ) c end @test isa(a47180(``; stdout), Base.AbstractCmd) + +# Test that _compute_sparams can be eliminated for NamedTuple +named_tuple_elim(name::Symbol, result) = NamedTuple{(name,)}(result) +let src = code_typed1(named_tuple_elim, Tuple{Symbol, Tuple}) + @test count(iscall((src, Core._compute_sparams)), src.code) == 0 && + count(iscall((src, Core._svec_ref)), src.code) == 0 && + count(iscall(x->!isa(argextype(x, src).val, Core.Builtin)), src.code) == 0 +end From b76fdcc3de4183a4dab019aacd335a5f944542ea Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Fri, 6 Jan 2023 17:50:40 -0500 Subject: [PATCH 254/387] ir: Respect GlobalRef lattice elements (#48151) Currently IncrementalCompact recomputes the type of globals on every iteration. There is not much reason to do this - the type of a global cannot change. In addition, external abstract interpreters may want to inject custom, more precise lattice elements for globals, which should be respected. Overall, this should be both faster and better for external absint, though of course GlobalRefs now need to be inserted into the IR with the correct type. If there's any callsites that don't do that, those would have to be updated. --- base/compiler/ssair/ir.jl | 1 - base/compiler/ssair/irinterp.jl | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index 0f86945b15b88a..5a5d98ac4539c9 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -1244,7 +1244,6 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr ssa_rename[idx] = stmt else result[result_idx][:inst] = stmt - result[result_idx][:type] = argextype(stmt, compact) result[result_idx][:flag] = flag result_idx += 1 end diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index 92e130a65cc756..856cd3b9eff14e 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -265,6 +265,8 @@ function reprocess_instruction!(interp::AbstractInterpreter, rt = tmeet(typeinf_lattice(interp), argextype(inst.val, ir), widenconst(inst.typ)) elseif inst === nothing return false + elseif isa(inst, GlobalRef) + # GlobalRef is not refinable else ccall(:jl_, Cvoid, (Any,), inst) error() From 98d5a0a38742deff0dd53439ab65bf1cb6c877af Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Fri, 6 Jan 2023 21:45:27 -0500 Subject: [PATCH 255/387] ir/passes: Various followups from previous PRs (#48164) Rolls up individual review comments from #48066, #48144, #48151. --- base/compiler/ssair/ir.jl | 1 - base/compiler/ssair/irinterp.jl | 4 ++-- base/compiler/ssair/passes.jl | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index 5a5d98ac4539c9..1dfcc7365ac1ea 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -1244,7 +1244,6 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr ssa_rename[idx] = stmt else result[result_idx][:inst] = stmt - result[result_idx][:flag] = flag result_idx += 1 end elseif isa(stmt, GotoNode) diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index 856cd3b9eff14e..e5d5c5834301e3 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -232,7 +232,7 @@ function reprocess_instruction!(interp::AbstractInterpreter, (; rt, effects) = abstract_eval_statement_expr(interp, inst, nothing, ir, irsv.mi) # All other effects already guaranteed effect free by construction if is_nothrow(effects) - ir.stmts[idx][:flag] |= IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW + ir.stmts[idx][:flag] |= IR_FLAG_NOTHROW if isa(rt, Const) && is_inlineable_constant(rt.val) ir.stmts[idx][:inst] = quoted(rt.val) end @@ -242,7 +242,7 @@ function reprocess_instruction!(interp::AbstractInterpreter, if mi′ !== irsv.mi # prevent infinite loop rt, nothrow = concrete_eval_invoke(interp, inst, mi′, irsv) if nothrow - ir.stmts[idx][:flag] |= IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW + ir.stmts[idx][:flag] |= IR_FLAG_NOTHROW if isa(rt, Const) && is_inlineable_constant(rt.val) ir.stmts[idx][:inst] = quoted(rt.val) end diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index 82ba6ddf062d79..4e93d6364749a6 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -805,7 +805,6 @@ end isa(rarg, SSAValue) || return nothing argdef = compact[rarg][:inst] else - isa(arg, DataType) || return nothing isType(arg) || return nothing arg = arg.parameters[1] end From 8dbf7a15170ce529fbabc72a11a6f7ca2df57fee Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Sat, 7 Jan 2023 01:17:30 -0600 Subject: [PATCH 256/387] Remove redundant definition of `UIntType` (#48157) --- base/sort.jl | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/base/sort.jl b/base/sort.jl index 276d3f5d29727b..1cf6a970e728b5 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -4,7 +4,7 @@ module Sort using Base.Order -using Base: copymutable, midpoint, require_one_based_indexing, +using Base: copymutable, midpoint, require_one_based_indexing, uinttype, sub_with_overflow, add_with_overflow, OneTo, BitSigned, BitIntegerType import Base: @@ -611,9 +611,6 @@ struct IEEEFloatOptimization{T <: Algorithm} <: Algorithm next::T end -UIntType(::Type{Float16}) = UInt16 -UIntType(::Type{Float32}) = UInt32 -UIntType(::Type{Float64}) = UInt64 after_zero(::ForwardOrdering, x) = !signbit(x) after_zero(::ReverseOrdering, x) = signbit(x) is_concrete_IEEEFloat(T::Type) = T <: Base.IEEEFloat && isconcretetype(T) @@ -621,7 +618,7 @@ function _sort!(v::AbstractVector, a::IEEEFloatOptimization, o::Ordering, kw) @getkw lo hi if is_concrete_IEEEFloat(eltype(v)) && o isa DirectOrdering lo, hi = send_to_end!(isnan, v, o, true; lo, hi) - iv = reinterpret(UIntType(eltype(v)), v) + iv = reinterpret(uinttype(eltype(v)), v) j = send_to_end!(x -> after_zero(o, x), v; lo, hi) scratch = _sort!(iv, a.next, Reverse, (;kw..., lo, hi=j)) if scratch === nothing # Union split @@ -631,7 +628,7 @@ function _sort!(v::AbstractVector, a::IEEEFloatOptimization, o::Ordering, kw) end elseif eltype(v) <: Integer && o isa Perm && o.order isa DirectOrdering && is_concrete_IEEEFloat(eltype(o.data)) lo, hi = send_to_end!(i -> isnan(@inbounds o.data[i]), v, o.order, true; lo, hi) - ip = reinterpret(UIntType(eltype(o.data)), o.data) + ip = reinterpret(uinttype(eltype(o.data)), o.data) j = send_to_end!(i -> after_zero(o.order, @inbounds o.data[i]), v; lo, hi) scratch = _sort!(v, a.next, Perm(Reverse, ip), (;kw..., lo, hi=j)) if scratch === nothing # Union split From de73c26fbff61d07a38c9653525b530a56630831 Mon Sep 17 00:00:00 2001 From: N5N3 <2642243996@qq.com> Date: Sat, 7 Jan 2023 18:39:46 +0800 Subject: [PATCH 257/387] Make sure `reachable_var` not falls into infinite recusion. (#48135) --- src/subtype.c | 41 ++++++++++++++++++++++++++++++++++++++--- test/subtype.jl | 5 +++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/subtype.c b/src/subtype.c index 3c9db8f1677cbc..4f2cf7a5c60570 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -216,6 +216,17 @@ static void restore_env(jl_stenv_t *e, jl_value_t *root, jl_savedenv_t *se) JL_N memset(&e->envout[e->envidx], 0, (e->envsz - e->envidx)*sizeof(void*)); } +static int current_env_length(jl_stenv_t *e) +{ + jl_varbinding_t *v = e->vars; + int len = 0; + while (v) { + len++; + v = v->prev; + } + return len; +} + // type utilities // quickly test that two types are identical @@ -2302,16 +2313,40 @@ static int subtype_in_env_existential(jl_value_t *x, jl_value_t *y, jl_stenv_t * } // See if var y is reachable from x via bounds; used to avoid cycles. -static int reachable_var(jl_value_t *x, jl_tvar_t *y, jl_stenv_t *e) +static int _reachable_var(jl_value_t *x, jl_tvar_t *y, jl_stenv_t *e) { if (in_union(x, (jl_value_t*)y)) return 1; if (!jl_is_typevar(x)) return 0; jl_varbinding_t *xv = lookup(e, (jl_tvar_t*)x); - if (xv == NULL) + if (xv == NULL || xv->right) return 0; - return reachable_var(xv->ub, y, e) || reachable_var(xv->lb, y, e); + xv->right = 1; + return _reachable_var(xv->ub, y, e) || _reachable_var(xv->lb, y, e); +} + +static int reachable_var(jl_value_t *x, jl_tvar_t *y, jl_stenv_t *e) +{ + int len = current_env_length(e); + int8_t *rs = (int8_t*)malloc_s(len); + int n = 0; + jl_varbinding_t *v = e->vars; + while (n < len) { + assert(v != NULL); + rs[n++] = v->right; + v->right = 0; + v = v->prev; + } + int res = _reachable_var(x, y, e); + n = 0; v = e->vars; + while (n < len) { + assert(v != NULL); + v->right = rs[n++]; + v = v->prev; + } + free(rs); + return res; } // check whether setting v == t implies v == SomeType{v}, which is unsatisfiable. diff --git a/test/subtype.jl b/test/subtype.jl index 14ea362abd7951..b6e30ce6771466 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -2314,6 +2314,11 @@ let S1 = Tuple{Int, Any, Union{Val{C1}, C1}} where {R1<:Real, C1<:Union{Complex{ end end +let S = Tuple{T2, V2} where {T2, N2, V2<:(Array{S2, N2} where {S2 <: T2})}, + T = Tuple{V1, T1} where {T1, N1, V1<:(Array{S1, N1} where {S1 <: T1})} + @testintersect(S, T, !Union{}) +end + @testset "known subtype/intersect issue" begin #issue 45874 # Causes a hang due to jl_critical_error calling back into malloc... From db7d7625d3a5fbddded2728b365eb5ed76380559 Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Sun, 8 Jan 2023 16:03:08 +0100 Subject: [PATCH 258/387] Move safepoint emission to llvm-final-gc-lowering (#47393) --- src/llvm-codegen-shared.h | 48 ++++++++++++++++++++++++---------- src/llvm-final-gc-lowering.cpp | 32 ++++++++++++++++++++--- src/llvm-pass-helpers.cpp | 17 ++++++++++++ src/llvm-pass-helpers.h | 3 +++ src/llvm-ptls.cpp | 4 +-- 5 files changed, 85 insertions(+), 19 deletions(-) diff --git a/src/llvm-codegen-shared.h b/src/llvm-codegen-shared.h index 329cc567e8c5f1..e0edb792d76459 100644 --- a/src/llvm-codegen-shared.h +++ b/src/llvm-codegen-shared.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -233,20 +234,39 @@ static inline void emit_signal_fence(llvm::IRBuilder<> &builder) builder.CreateFence(AtomicOrdering::SequentiallyConsistent, SyncScope::SingleThread); } -static inline void emit_gc_safepoint(llvm::IRBuilder<> &builder, llvm::Value *ptls, llvm::MDNode *tbaa) +static inline void emit_gc_safepoint(llvm::IRBuilder<> &builder, llvm::Value *ptls, llvm::MDNode *tbaa, bool final = false) { + using namespace llvm; + llvm::Value *signal_page = get_current_signal_page_from_ptls(builder, ptls, tbaa); emit_signal_fence(builder); - builder.CreateLoad(getSizeTy(builder.getContext()), get_current_signal_page_from_ptls(builder, ptls, tbaa), true); + Module *M = builder.GetInsertBlock()->getModule(); + LLVMContext &C = builder.getContext(); + // inline jlsafepoint_func->realize(M) + if (final) { + auto T_size = getSizeTy(builder.getContext()); + builder.CreateLoad(T_size, signal_page, true); + } + else { + Function *F = M->getFunction("julia.safepoint"); + if (!F) { + auto T_size = getSizeTy(builder.getContext()); + auto T_psize = T_size->getPointerTo(); + FunctionType *FT = FunctionType::get(Type::getVoidTy(C), {T_psize}, false); + F = Function::Create(FT, Function::ExternalLinkage, "julia.safepoint", M); + F->addFnAttr(Attribute::InaccessibleMemOrArgMemOnly); + } + builder.CreateCall(F, {signal_page}); + } emit_signal_fence(builder); } -static inline llvm::Value *emit_gc_state_set(llvm::IRBuilder<> &builder, llvm::Value *ptls, llvm::Value *state, llvm::Value *old_state) +static inline llvm::Value *emit_gc_state_set(llvm::IRBuilder<> &builder, llvm::Value *ptls, llvm::Value *state, llvm::Value *old_state, bool final) { using namespace llvm; Type *T_int8 = state->getType(); - ptls = emit_bitcast_with_builder(builder, ptls, builder.getInt8PtrTy()); + llvm::Value *ptls_i8 = emit_bitcast_with_builder(builder, ptls, builder.getInt8PtrTy()); Constant *offset = ConstantInt::getSigned(builder.getInt32Ty(), offsetof(jl_tls_states_t, gc_state)); - Value *gc_state = builder.CreateInBoundsGEP(T_int8, ptls, ArrayRef(offset), "gc_state"); + Value *gc_state = builder.CreateInBoundsGEP(T_int8, ptls_i8, ArrayRef(offset), "gc_state"); if (old_state == nullptr) { old_state = builder.CreateLoad(T_int8, gc_state); cast(old_state)->setOrdering(AtomicOrdering::Monotonic); @@ -266,38 +286,38 @@ static inline llvm::Value *emit_gc_state_set(llvm::IRBuilder<> &builder, llvm::V passBB, exitBB); builder.SetInsertPoint(passBB); MDNode *tbaa = get_tbaa_const(builder.getContext()); - emit_gc_safepoint(builder, ptls, tbaa); + emit_gc_safepoint(builder, ptls, tbaa, final); builder.CreateBr(exitBB); builder.SetInsertPoint(exitBB); return old_state; } -static inline llvm::Value *emit_gc_unsafe_enter(llvm::IRBuilder<> &builder, llvm::Value *ptls) +static inline llvm::Value *emit_gc_unsafe_enter(llvm::IRBuilder<> &builder, llvm::Value *ptls, bool final) { using namespace llvm; Value *state = builder.getInt8(0); - return emit_gc_state_set(builder, ptls, state, nullptr); + return emit_gc_state_set(builder, ptls, state, nullptr, final); } -static inline llvm::Value *emit_gc_unsafe_leave(llvm::IRBuilder<> &builder, llvm::Value *ptls, llvm::Value *state) +static inline llvm::Value *emit_gc_unsafe_leave(llvm::IRBuilder<> &builder, llvm::Value *ptls, llvm::Value *state, bool final) { using namespace llvm; Value *old_state = builder.getInt8(0); - return emit_gc_state_set(builder, ptls, state, old_state); + return emit_gc_state_set(builder, ptls, state, old_state, final); } -static inline llvm::Value *emit_gc_safe_enter(llvm::IRBuilder<> &builder, llvm::Value *ptls) +static inline llvm::Value *emit_gc_safe_enter(llvm::IRBuilder<> &builder, llvm::Value *ptls, bool final) { using namespace llvm; Value *state = builder.getInt8(JL_GC_STATE_SAFE); - return emit_gc_state_set(builder, ptls, state, nullptr); + return emit_gc_state_set(builder, ptls, state, nullptr, final); } -static inline llvm::Value *emit_gc_safe_leave(llvm::IRBuilder<> &builder, llvm::Value *ptls, llvm::Value *state) +static inline llvm::Value *emit_gc_safe_leave(llvm::IRBuilder<> &builder, llvm::Value *ptls, llvm::Value *state, bool final) { using namespace llvm; Value *old_state = builder.getInt8(JL_GC_STATE_SAFE); - return emit_gc_state_set(builder, ptls, state, old_state); + return emit_gc_state_set(builder, ptls, state, old_state, final); } // Compatibility shims for LLVM attribute APIs that were renamed in LLVM 14. diff --git a/src/llvm-final-gc-lowering.cpp b/src/llvm-final-gc-lowering.cpp index 6b416b05a002e1..3b8533c6d01152 100644 --- a/src/llvm-final-gc-lowering.cpp +++ b/src/llvm-final-gc-lowering.cpp @@ -26,6 +26,7 @@ STATISTIC(PopGCFrameCount, "Number of lowered popGCFrameFunc intrinsics"); STATISTIC(GetGCFrameSlotCount, "Number of lowered getGCFrameSlotFunc intrinsics"); STATISTIC(GCAllocBytesCount, "Number of lowered GCAllocBytesFunc intrinsics"); STATISTIC(QueueGCRootCount, "Number of lowered queueGCRootFunc intrinsics"); +STATISTIC(SafepointCount, "Number of lowered safepoint intrinsics"); using namespace llvm; @@ -66,6 +67,9 @@ struct FinalLowerGC: private JuliaPassContext { // Lowers a `julia.queue_gc_root` intrinsic. Value *lowerQueueGCRoot(CallInst *target, Function &F); + + // Lowers a `julia.safepoint` intrinsic. + Value *lowerSafepoint(CallInst *target, Function &F); }; Value *FinalLowerGC::lowerNewGCFrame(CallInst *target, Function &F) @@ -188,6 +192,18 @@ Value *FinalLowerGC::lowerQueueGCRoot(CallInst *target, Function &F) return target; } +Value *FinalLowerGC::lowerSafepoint(CallInst *target, Function &F) +{ + ++SafepointCount; + assert(target->arg_size() == 1); + IRBuilder<> builder(target->getContext()); + builder.SetInsertPoint(target); + auto T_size = getSizeTy(builder.getContext()); + Value* signal_page = target->getOperand(0); + Value* load = builder.CreateLoad(T_size, signal_page, true); + return load; +} + Value *FinalLowerGC::lowerGCAllocBytes(CallInst *target, Function &F) { ++GCAllocBytesCount; @@ -292,16 +308,20 @@ static void replaceInstruction( bool FinalLowerGC::runOnFunction(Function &F) { - LLVM_DEBUG(dbgs() << "FINAL GC LOWERING: Processing function " << F.getName() << "\n"); // Check availability of functions again since they might have been deleted. initFunctions(*F.getParent()); - if (!pgcstack_getter && !adoptthread_func) + if (!pgcstack_getter && !adoptthread_func) { + LLVM_DEBUG(dbgs() << "FINAL GC LOWERING: Skipping function " << F.getName() << "\n"); return false; + } // Look for a call to 'julia.get_pgcstack'. pgcstack = getPGCstack(F); - if (!pgcstack) + if (!pgcstack) { + LLVM_DEBUG(dbgs() << "FINAL GC LOWERING: Skipping function " << F.getName() << " no pgcstack\n"); return false; + } + LLVM_DEBUG(dbgs() << "FINAL GC LOWERING: Processing function " << F.getName() << "\n"); // Acquire intrinsic functions. auto newGCFrameFunc = getOrNull(jl_intrinsics::newGCFrame); @@ -310,6 +330,7 @@ bool FinalLowerGC::runOnFunction(Function &F) auto getGCFrameSlotFunc = getOrNull(jl_intrinsics::getGCFrameSlot); auto GCAllocBytesFunc = getOrNull(jl_intrinsics::GCAllocBytes); auto queueGCRootFunc = getOrNull(jl_intrinsics::queueGCRoot); + auto safepointFunc = getOrNull(jl_intrinsics::safepoint); // Lower all calls to supported intrinsics. for (BasicBlock &BB : F) { @@ -321,6 +342,7 @@ bool FinalLowerGC::runOnFunction(Function &F) } Value *callee = CI->getCalledOperand(); + assert(callee); if (callee == newGCFrameFunc) { replaceInstruction(CI, lowerNewGCFrame(CI, F), it); @@ -342,6 +364,10 @@ bool FinalLowerGC::runOnFunction(Function &F) else if (callee == queueGCRootFunc) { replaceInstruction(CI, lowerQueueGCRoot(CI, F), it); } + else if (callee == safepointFunc) { + lowerSafepoint(CI, F); + it = CI->eraseFromParent(); + } else { ++it; } diff --git a/src/llvm-pass-helpers.cpp b/src/llvm-pass-helpers.cpp index e12fa7ad90fed1..ea390f01010fd8 100644 --- a/src/llvm-pass-helpers.cpp +++ b/src/llvm-pass-helpers.cpp @@ -116,6 +116,7 @@ namespace jl_intrinsics { static const char *PUSH_GC_FRAME_NAME = "julia.push_gc_frame"; static const char *POP_GC_FRAME_NAME = "julia.pop_gc_frame"; static const char *QUEUE_GC_ROOT_NAME = "julia.queue_gc_root"; + static const char *SAFEPOINT_NAME = "julia.safepoint"; // Annotates a function with attributes suitable for GC allocation // functions. Specifically, the return value is marked noalias and nonnull. @@ -206,6 +207,22 @@ namespace jl_intrinsics { intrinsic->addFnAttr(Attribute::InaccessibleMemOrArgMemOnly); return intrinsic; }); + + const IntrinsicDescription safepoint( + SAFEPOINT_NAME, + [](const JuliaPassContext &context) { + auto T_size = getSizeTy(context.getLLVMContext()); + auto T_psize = T_size->getPointerTo(); + auto intrinsic = Function::Create( + FunctionType::get( + Type::getVoidTy(context.getLLVMContext()), + {T_psize}, + false), + Function::ExternalLinkage, + SAFEPOINT_NAME); + intrinsic->addFnAttr(Attribute::InaccessibleMemOrArgMemOnly); + return intrinsic; + }); } namespace jl_well_known { diff --git a/src/llvm-pass-helpers.h b/src/llvm-pass-helpers.h index 3c06440f369d89..2b2bd50cd0e4dc 100644 --- a/src/llvm-pass-helpers.h +++ b/src/llvm-pass-helpers.h @@ -126,6 +126,9 @@ namespace jl_intrinsics { // `julia.queue_gc_root`: an intrinsic that queues a GC root. extern const IntrinsicDescription queueGCRoot; + + // `julia.safepoint`: an intrinsic that triggers a GC safepoint. + extern const IntrinsicDescription safepoint; } // A namespace for well-known Julia runtime function descriptions. diff --git a/src/llvm-ptls.cpp b/src/llvm-ptls.cpp index ebd7d10a2a130a..ea92e1709c5974 100644 --- a/src/llvm-ptls.cpp +++ b/src/llvm-ptls.cpp @@ -207,7 +207,7 @@ void LowerPTLS::fix_pgcstack_use(CallInst *pgcstack, Function *pgcstack_getter, IRBuilder<> builder(fastTerm->getParent()); fastTerm->removeFromParent(); MDNode *tbaa = tbaa_gcframe; - Value *prior = emit_gc_unsafe_enter(builder, get_current_ptls_from_task(builder, get_current_task_from_pgcstack(builder, pgcstack), tbaa)); + Value *prior = emit_gc_unsafe_enter(builder, get_current_ptls_from_task(builder, get_current_task_from_pgcstack(builder, pgcstack), tbaa), true); builder.Insert(fastTerm); phi->addIncoming(pgcstack, fastTerm->getParent()); // emit pre-return cleanup @@ -219,7 +219,7 @@ void LowerPTLS::fix_pgcstack_use(CallInst *pgcstack, Function *pgcstack_getter, for (auto &BB : *pgcstack->getParent()->getParent()) { if (isa(BB.getTerminator())) { IRBuilder<> builder(BB.getTerminator()); - emit_gc_unsafe_leave(builder, get_current_ptls_from_task(builder, get_current_task_from_pgcstack(builder, phi), tbaa), last_gc_state); + emit_gc_unsafe_leave(builder, get_current_ptls_from_task(builder, get_current_task_from_pgcstack(builder, phi), tbaa), last_gc_state, true); } } } From 0847a7fa3baa2a43b081b9ea1a10a96148e19500 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Sun, 8 Jan 2023 12:42:39 -0500 Subject: [PATCH 259/387] Make cache mismatch log more informative (#48168) --- base/loading.jl | 29 ++++++++++++++++++++++++++++- src/staticdata_utils.c | 1 + 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/base/loading.jl b/base/loading.jl index f7ff11f1ae165a..9137651007c30a 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -2505,6 +2505,29 @@ function check_clone_targets(clone_targets) end end +struct CacheFlags + # ??OOCDDP - see jl_cache_flags + use_pkgimages::Bool + debug_level::Int + check_bounds::Bool + opt_level::Int + + CacheFlags(f::Int) = CacheFlags(UInt8(f)) + function CacheFlags(f::UInt8) + use_pkgimages = Bool(f & 1) + debug_level = Int((f >> 1) & 3) + check_bounds = Bool((f >> 2) & 1) + opt_level = Int((f >> 4) & 3) + new(use_pkgimages, debug_level, check_bounds, opt_level) + end +end +function show(io::IO, cf::CacheFlags) + print(io, "use_pkgimages = ", cf.use_pkgimages) + print(io, ", debug_level = ", cf.debug_level) + print(io, ", check_bounds = ", cf.check_bounds) + print(io, ", opt_level = ", cf.opt_level) +end + # returns true if it "cachefile.ji" is stale relative to "modpath.jl" and build_id for modkey # otherwise returns the list of dependencies to also check @constprop :none function stale_cachefile(modpath::String, cachefile::String; ignore_loaded::Bool = false) @@ -2523,7 +2546,11 @@ end return true # ignore empty file end if ccall(:jl_match_cache_flags, UInt8, (UInt8,), flags) == 0 - @debug "Rejecting cache file $cachefile for $modkey since the flags are mismatched" cachefile_flags=flags current_flags=ccall(:jl_cache_flags, UInt8, ()) + @debug """ + Rejecting cache file $cachefile for $modkey since the flags are mismatched + current session: $(CacheFlags(ccall(:jl_cache_flags, UInt8, ()))) + cache file: $(CacheFlags(flags)) + """ return true end pkgimage = !isempty(clone_targets) diff --git a/src/staticdata_utils.c b/src/staticdata_utils.c index 220a354847dfec..cb107b06a95850 100644 --- a/src/staticdata_utils.c +++ b/src/staticdata_utils.c @@ -621,6 +621,7 @@ JL_DLLEXPORT uint8_t jl_cache_flags(void) flags |= (jl_options.opt_level & 3) << 4; // NOTES: // In contrast to check-bounds, inline has no "observable effect" + // CacheFlags in loading.jl should be kept in-sync with this return flags; } From 2f51851041a11664a5e208d463c9bd7b6546fe71 Mon Sep 17 00:00:00 2001 From: DilumAluthgeBot <43731525+DilumAluthgeBot@users.noreply.github.com> Date: Sun, 8 Jan 2023 13:14:00 -0500 Subject: [PATCH 260/387] =?UTF-8?q?=F0=9F=A4=96=20Bump=20the=20SparseArray?= =?UTF-8?q?s=20stdlib=20from=2031b491e=20to=20a3116b9=20(#48175)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dilum Aluthge --- .../md5 | 1 - .../sha512 | 1 - .../md5 | 1 + .../sha512 | 1 + stdlib/SparseArrays.version | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 deps/checksums/SparseArrays-31b491e431d0b9c4e5e17fdcbbc9f71579e7c79c.tar.gz/md5 delete mode 100644 deps/checksums/SparseArrays-31b491e431d0b9c4e5e17fdcbbc9f71579e7c79c.tar.gz/sha512 create mode 100644 deps/checksums/SparseArrays-a3116b95add064055c1f737abd98b5a5839c4147.tar.gz/md5 create mode 100644 deps/checksums/SparseArrays-a3116b95add064055c1f737abd98b5a5839c4147.tar.gz/sha512 diff --git a/deps/checksums/SparseArrays-31b491e431d0b9c4e5e17fdcbbc9f71579e7c79c.tar.gz/md5 b/deps/checksums/SparseArrays-31b491e431d0b9c4e5e17fdcbbc9f71579e7c79c.tar.gz/md5 deleted file mode 100644 index f67e2fb240bd80..00000000000000 --- a/deps/checksums/SparseArrays-31b491e431d0b9c4e5e17fdcbbc9f71579e7c79c.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -5d31e7c74a00b630e1faef7b02642219 diff --git a/deps/checksums/SparseArrays-31b491e431d0b9c4e5e17fdcbbc9f71579e7c79c.tar.gz/sha512 b/deps/checksums/SparseArrays-31b491e431d0b9c4e5e17fdcbbc9f71579e7c79c.tar.gz/sha512 deleted file mode 100644 index 804f8002b437ff..00000000000000 --- a/deps/checksums/SparseArrays-31b491e431d0b9c4e5e17fdcbbc9f71579e7c79c.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -88ee08b50fd84b83288d7facbb2887f23d71278a8b976e4d5b3e867b3bad726b24d561d31aee435513263cf34d2f3fb50a3a20be62052f117ae46a37b1940157 diff --git a/deps/checksums/SparseArrays-a3116b95add064055c1f737abd98b5a5839c4147.tar.gz/md5 b/deps/checksums/SparseArrays-a3116b95add064055c1f737abd98b5a5839c4147.tar.gz/md5 new file mode 100644 index 00000000000000..e5d8b0a66dd7b6 --- /dev/null +++ b/deps/checksums/SparseArrays-a3116b95add064055c1f737abd98b5a5839c4147.tar.gz/md5 @@ -0,0 +1 @@ +a278371a01cb77cf360812b8b31461e1 diff --git a/deps/checksums/SparseArrays-a3116b95add064055c1f737abd98b5a5839c4147.tar.gz/sha512 b/deps/checksums/SparseArrays-a3116b95add064055c1f737abd98b5a5839c4147.tar.gz/sha512 new file mode 100644 index 00000000000000..d9f99472954486 --- /dev/null +++ b/deps/checksums/SparseArrays-a3116b95add064055c1f737abd98b5a5839c4147.tar.gz/sha512 @@ -0,0 +1 @@ +2c55fa70940414e9c0fee86b5a23b9e33c6d839683307941e32bdb915d8845410e63d28b7e1b627fce43ebffec2b746d4c7d3eb5df3f2ef14634eedb1ba8c77d diff --git a/stdlib/SparseArrays.version b/stdlib/SparseArrays.version index 5f3276dfcc279b..3c71453fe06999 100644 --- a/stdlib/SparseArrays.version +++ b/stdlib/SparseArrays.version @@ -1,4 +1,4 @@ SPARSEARRAYS_BRANCH = main -SPARSEARRAYS_SHA1 = 31b491e431d0b9c4e5e17fdcbbc9f71579e7c79c +SPARSEARRAYS_SHA1 = a3116b95add064055c1f737abd98b5a5839c4147 SPARSEARRAYS_GIT_URL := https://github.com/JuliaSparse/SparseArrays.jl.git SPARSEARRAYS_TAR_URL = https://api.github.com/repos/JuliaSparse/SparseArrays.jl/tarball/$1 From f6b5157eb5d0c490dff7ee2d0c284a8b04cd62d9 Mon Sep 17 00:00:00 2001 From: t-bltg Date: Sun, 8 Jan 2023 19:48:28 +0100 Subject: [PATCH 261/387] deps: fix broken `p7zip` url (#48176) --- deps/p7zip.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/p7zip.mk b/deps/p7zip.mk index 5fea4b63366c2a..c7c2874d49a5e5 100644 --- a/deps/p7zip.mk +++ b/deps/p7zip.mk @@ -4,7 +4,7 @@ include $(SRCDIR)/p7zip.version ifneq ($(USE_BINARYBUILDER_P7ZIP),1) $(SRCCACHE)/p7zip-$(P7ZIP_VER).tar.gz: | $(SRCCACHE) - $(JLDOWNLOAD) $@ https://github.com/jinfeihan57/p7zip/archive/refs/tags/v$(P7ZIP_VER).tar.gz + $(JLDOWNLOAD) $@ https://github.com/p7zip-project/p7zip/archive/refs/tags/v$(P7ZIP_VER).tar.gz $(BUILDDIR)/p7zip-$(P7ZIP_VER)/source-extracted: $(SRCCACHE)/p7zip-$(P7ZIP_VER).tar.gz $(JLCHECKSUM) $< From 708d1bdb36e962b2eb8b2040777f5dea272c40c7 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Sun, 8 Jan 2023 16:16:48 -0600 Subject: [PATCH 262/387] Add internal `top_set_bit` function (#47523) * add top_set_bit Co-authored-by: Lilith Hafner Co-authored-by: Michael Abbott <32575566+mcabbott@users.noreply.github.com> --- base/abstractdict.jl | 2 +- base/bitarray.jl | 4 ++-- base/float.jl | 4 ++-- base/gmp.jl | 10 ++++++++-- base/int.jl | 25 +++++++++++++++++++++++++ base/intfuncs.jl | 19 +++++++++++-------- base/sort.jl | 2 +- base/special/rem_pio2.jl | 4 ++-- base/twiceprecision.jl | 4 ++-- test/intfuncs.jl | 32 +++++++++++++++++++++++++++++++- 10 files changed, 85 insertions(+), 21 deletions(-) diff --git a/base/abstractdict.jl b/base/abstractdict.jl index fcadeab76de839..c523a25cecd3f0 100644 --- a/base/abstractdict.jl +++ b/base/abstractdict.jl @@ -575,7 +575,7 @@ function convert(::Type{T}, x::AbstractDict) where T<:AbstractDict end # hashing objects by identity -_tablesz(x::T) where T <: Integer = x < 16 ? T(16) : one(T)<<((sizeof(T)<<3)-leading_zeros(x-one(T))) +_tablesz(x::T) where T <: Integer = x < 16 ? T(16) : one(T)<<(top_set_bit(x-one(T))) TP{K,V} = Union{Type{Tuple{K,V}},Type{Pair{K,V}}} diff --git a/base/bitarray.jl b/base/bitarray.jl index 470d14d290ef45..f29b30d0ac8c08 100644 --- a/base/bitarray.jl +++ b/base/bitarray.jl @@ -1545,12 +1545,12 @@ function unsafe_bitfindprev(Bc::Vector{UInt64}, start::Int) @inbounds begin if Bc[chunk_start] & mask != 0 - return (chunk_start-1) << 6 + (64 - leading_zeros(Bc[chunk_start] & mask)) + return (chunk_start-1) << 6 + (top_set_bit(Bc[chunk_start] & mask)) end for i = (chunk_start-1):-1:1 if Bc[i] != 0 - return (i-1) << 6 + (64 - leading_zeros(Bc[i])) + return (i-1) << 6 + (top_set_bit(Bc[i])) end end end diff --git a/base/float.jl b/base/float.jl index eda0865c9fbac7..2677fd5dfba38b 100644 --- a/base/float.jl +++ b/base/float.jl @@ -221,7 +221,7 @@ end function Float32(x::UInt128) x == 0 && return 0f0 - n = 128-leading_zeros(x) # ndigits0z(x,2) + n = top_set_bit(x) # ndigits0z(x,2) if n <= 24 y = ((x % UInt32) << (24-n)) & 0x007f_ffff else @@ -237,7 +237,7 @@ function Float32(x::Int128) x == 0 && return 0f0 s = ((x >>> 96) % UInt32) & 0x8000_0000 # sign bit x = abs(x) % UInt128 - n = 128-leading_zeros(x) # ndigits0z(x,2) + n = top_set_bit(x) # ndigits0z(x,2) if n <= 24 y = ((x % UInt32) << (24-n)) & 0x007f_ffff else diff --git a/base/gmp.jl b/base/gmp.jl index 0e5eb66d0d193c..1deccccfe75cd3 100644 --- a/base/gmp.jl +++ b/base/gmp.jl @@ -10,7 +10,7 @@ import .Base: *, +, -, /, <, <<, >>, >>>, <=, ==, >, >=, ^, (~), (&), (|), xor, trailing_zeros, trailing_ones, count_ones, count_zeros, tryparse_internal, bin, oct, dec, hex, isequal, invmod, _prevpow2, _nextpow2, ndigits0zpb, widen, signed, unsafe_trunc, trunc, iszero, isone, big, flipsign, signbit, - sign, hastypemax, isodd, iseven, digits!, hash, hash_integer + sign, hastypemax, isodd, iseven, digits!, hash, hash_integer, top_set_bit if Clong == Int32 const ClongMax = Union{Int8, Int16, Int32} @@ -396,7 +396,7 @@ function Float64(x::BigInt, ::RoundingMode{:Nearest}) z = Float64((unsafe_load(x.d, 2) % UInt64) << BITS_PER_LIMB + unsafe_load(x.d)) else y1 = unsafe_load(x.d, xsize) % UInt64 - n = 64 - leading_zeros(y1) + n = top_set_bit(y1) # load first 54(1 + 52 bits of fraction + 1 for rounding) y = y1 >> (n - (precision(Float64)+1)) if Limb == UInt64 @@ -586,6 +586,12 @@ Number of ones in the binary representation of abs(x). """ count_ones_abs(x::BigInt) = iszero(x) ? 0 : MPZ.mpn_popcount(x) +function top_set_bit(x::BigInt) + x < 0 && throw(DomainError(x, "top_set_bit only supports negative arguments when they have type BitSigned.")) + x == 0 && return 0 + Int(ccall((:__gmpz_sizeinbase, :libgmp), Csize_t, (Base.GMP.MPZ.mpz_t, Cint), x, 2)) +end + divrem(x::BigInt, y::BigInt) = MPZ.tdiv_qr(x, y) divrem(x::BigInt, y::Integer) = MPZ.tdiv_qr(x, big(y)) diff --git a/base/int.jl b/base/int.jl index 554f0a7f1a4460..5612f4d7f06274 100644 --- a/base/int.jl +++ b/base/int.jl @@ -482,6 +482,31 @@ julia> trailing_ones(3) """ trailing_ones(x::Integer) = trailing_zeros(~x) +""" + top_set_bit(x::Integer) -> Integer + +The number of bits in `x`'s binary representation, excluding leading zeros. + +Equivalently, the position of the most significant set bit in `x`'s binary +representation, measured from the least significant side. + +Negative `x` are only supported when `x::BitSigned`. + +See also: [`ndigits0z`](@ref), [`ndigits`](@ref). + +# Examples +```jldoctest +julia> top_set_bit(4) +3 + +julia> top_set_bit(0) +0 + +julia> top_set_bit(-1) +64 +""" +top_set_bit(x::BitInteger) = 8sizeof(x) - leading_zeros(x) + ## integer comparisons ## (< )(x::T, y::T) where {T<:BitUnsigned} = ult_int(x, y) diff --git a/base/intfuncs.jl b/base/intfuncs.jl index 3064a284589971..736b949aca3b0f 100644 --- a/base/intfuncs.jl +++ b/base/intfuncs.jl @@ -391,9 +391,9 @@ end # optimization: promote the modulus m to BigInt only once (cf. widemul in generic powermod above) powermod(x::Integer, p::Integer, m::Union{Int128,UInt128}) = oftype(m, powermod(x, p, big(m))) -_nextpow2(x::Unsigned) = oneunit(x)<<((sizeof(x)<<3)-leading_zeros(x-oneunit(x))) +_nextpow2(x::Unsigned) = oneunit(x)<<(top_set_bit(x-oneunit(x))) _nextpow2(x::Integer) = reinterpret(typeof(x),x < 0 ? -_nextpow2(unsigned(-x)) : _nextpow2(unsigned(x))) -_prevpow2(x::Unsigned) = one(x) << unsigned((sizeof(x)<<3)-leading_zeros(x)-1) +_prevpow2(x::Unsigned) = one(x) << unsigned(top_set_bit(x)-1) _prevpow2(x::Integer) = reinterpret(typeof(x),x < 0 ? -_prevpow2(unsigned(-x)) : _prevpow2(unsigned(x))) """ @@ -526,7 +526,7 @@ const powers_of_ten = [ 0x002386f26fc10000, 0x016345785d8a0000, 0x0de0b6b3a7640000, 0x8ac7230489e80000, ] function bit_ndigits0z(x::Base.BitUnsigned64) - lz = (sizeof(x)<<3)-leading_zeros(x) + lz = top_set_bit(x) nd = (1233*lz)>>12+1 nd -= x < powers_of_ten[nd] end @@ -571,12 +571,12 @@ function ndigits0zpb(x::Integer, b::Integer) x = abs(x) if x isa Base.BitInteger x = unsigned(x)::Unsigned - b == 2 && return sizeof(x)<<3 - leading_zeros(x) - b == 8 && return (sizeof(x)<<3 - leading_zeros(x) + 2) ÷ 3 + b == 2 && return top_set_bit(x) + b == 8 && return (top_set_bit(x) + 2) ÷ 3 b == 16 && return sizeof(x)<<1 - leading_zeros(x)>>2 b == 10 && return bit_ndigits0z(x) if ispow2(b) - dv, rm = divrem(sizeof(x)<<3 - leading_zeros(x), trailing_zeros(b)) + dv, rm = divrem(top_set_bit(x), trailing_zeros(b)) return iszero(rm) ? dv : dv + 1 end end @@ -638,6 +638,9 @@ function ndigits0z(x::Integer, b::Integer) end end +# Extends the definition in base/int.jl +top_set_bit(x::Integer) = ceil(Integer, log2(x + oneunit(x))) + """ ndigits(n::Integer; base::Integer=10, pad::Integer=1) @@ -673,7 +676,7 @@ ndigits(x::Integer; base::Integer=10, pad::Integer=1) = max(pad, ndigits0z(x, ba ## integer to string functions ## function bin(x::Unsigned, pad::Int, neg::Bool) - m = 8 * sizeof(x) - leading_zeros(x) + m = top_set_bit(x) n = neg + max(pad, m) a = StringVector(n) # for i in 0x0:UInt(n-1) # automatic vectorization produces redundant codes @@ -700,7 +703,7 @@ function bin(x::Unsigned, pad::Int, neg::Bool) end function oct(x::Unsigned, pad::Int, neg::Bool) - m = div(8 * sizeof(x) - leading_zeros(x) + 2, 3) + m = div(top_set_bit(x) + 2, 3) n = neg + max(pad, m) a = StringVector(n) i = n diff --git a/base/sort.jl b/base/sort.jl index 1cf6a970e728b5..1f8a6797427819 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -5,7 +5,7 @@ module Sort using Base.Order using Base: copymutable, midpoint, require_one_based_indexing, uinttype, - sub_with_overflow, add_with_overflow, OneTo, BitSigned, BitIntegerType + sub_with_overflow, add_with_overflow, OneTo, BitSigned, BitIntegerType, top_set_bit import Base: sort, diff --git a/base/special/rem_pio2.jl b/base/special/rem_pio2.jl index ef4c2923f393b6..de5c4151df2d01 100644 --- a/base/special/rem_pio2.jl +++ b/base/special/rem_pio2.jl @@ -109,7 +109,7 @@ function fromfraction(f::Int128) # 1. get leading term truncated to 26 bits s = ((f < 0) % UInt64) << 63 # sign bit x = abs(f) % UInt128 # magnitude - n1 = 128-leading_zeros(x) # ndigits0z(x,2) + n1 = Base.top_set_bit(x) # ndigits0z(x,2) m1 = ((x >> (n1-26)) % UInt64) << 27 d1 = ((n1-128+1021) % UInt64) << 52 z1 = reinterpret(Float64, s | (d1 + m1)) @@ -119,7 +119,7 @@ function fromfraction(f::Int128) if x2 == 0 return (z1, 0.0) end - n2 = 128-leading_zeros(x2) + n2 = Base.top_set_bit(x2) m2 = (x2 >> (n2-53)) % UInt64 d2 = ((n2-128+1021) % UInt64) << 52 z2 = reinterpret(Float64, s | (d2 + m2)) diff --git a/base/twiceprecision.jl b/base/twiceprecision.jl index edbce928f527c9..6a1232cdcd8103 100644 --- a/base/twiceprecision.jl +++ b/base/twiceprecision.jl @@ -254,7 +254,7 @@ nbitslen(::Type{T}, len, offset) where {T<:IEEEFloat} = min(cld(precision(T), 2), nbitslen(len, offset)) # The +1 here is for safety, because the precision of the significand # is 1 bit higher than the number that are explicitly stored. -nbitslen(len, offset) = len < 2 ? 0 : ceil(Int, log2(max(offset-1, len-offset))) + 1 +nbitslen(len, offset) = len < 2 ? 0 : top_set_bit(max(offset-1, len-offset) - 1) + 1 eltype(::Type{TwicePrecision{T}}) where {T} = T @@ -310,7 +310,7 @@ function *(x::TwicePrecision, v::Number) end function *(x::TwicePrecision{<:IEEEFloat}, v::Integer) v == 0 && return TwicePrecision(x.hi*v, x.lo*v) - nb = ceil(Int, log2(abs(v))) + nb = top_set_bit(abs(v)-1) u = truncbits(x.hi, nb) TwicePrecision(canonicalize2(u*v, ((x.hi-u) + x.lo)*v)...) end diff --git a/test/intfuncs.jl b/test/intfuncs.jl index c74e5be305a313..c93cfb0e2ac72b 100644 --- a/test/intfuncs.jl +++ b/test/intfuncs.jl @@ -441,12 +441,42 @@ end end end -@testset "leading_ones and count_zeros" begin +@testset "leading_ones, count_zeros, etc." begin @test leading_ones(UInt32(Int64(2) ^ 32 - 2)) == 31 @test leading_ones(1) == 0 @test leading_zeros(Int32(1)) == 31 @test leading_zeros(UInt32(Int64(2) ^ 32 - 2)) == 0 + @test Base.top_set_bit(3) == 2 + @test Base.top_set_bit(-Int64(17)) == 64 + @test Base.top_set_bit(big(15)) != Base.top_set_bit(big(16)) == Base.top_set_bit(big(17)) == 5 + @test_throws DomainError Base.top_set_bit(big(-17)) + + struct MyInt <: Integer + x::Int + end + MyInt(x::MyInt) = x + Base.:+(a::MyInt, b::MyInt) = a.x + b.x + + for n in 0:100 + x = ceil(Int, log2(n + 1)) + @test x == Base.top_set_bit(Int128(n)) == Base.top_set_bit(unsigned(Int128(n))) + @test x == Base.top_set_bit(Int32(n)) == Base.top_set_bit(unsigned(Int64(n))) + @test x == Base.top_set_bit(Int8(n)) == Base.top_set_bit(unsigned(Int8(n))) + @test x == Base.top_set_bit(big(n)) # BigInt fallback + @test x == Base.top_set_bit(MyInt(n)) # generic fallback + end + + for n in -10:-1 + @test 128 == Base.top_set_bit(Int128(n)) == Base.top_set_bit(unsigned(Int128(n))) + @test 32 == Base.top_set_bit(Int32(n)) == Base.top_set_bit(unsigned(Int32(n))) + @test 8 == Base.top_set_bit(Int8(n)) == Base.top_set_bit(unsigned(Int8(n))) + @test_throws DomainError Base.top_set_bit(big(n)) + # This error message should never be exposed to the end user anyway. + err = n == -1 ? InexactError : DomainError + @test_throws err Base.top_set_bit(MyInt(n)) + end + @test count_zeros(Int64(1)) == 63 end From 53a0a69f56ded710fae2b17a33e571aa4714a80a Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Mon, 9 Jan 2023 15:11:15 +0100 Subject: [PATCH 263/387] Extend method root to support more than 16bit roots (#48185) --- src/ircode.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/ircode.c b/src/ircode.c index 9f71d8e8dd28cc..648b954449aa2c 100644 --- a/src/ircode.c +++ b/src/ircode.c @@ -110,14 +110,14 @@ static void jl_encode_as_indexed_root(jl_ircode_state *s, jl_value_t *v) write_uint8(s->s, TAG_RELOC_METHODROOT); write_uint64(s->s, rr.key); } - if (id < 256) { + if (id <= UINT8_MAX) { write_uint8(s->s, TAG_METHODROOT); write_uint8(s->s, id); } else { - assert(id <= UINT16_MAX); + assert(id <= UINT32_MAX); write_uint8(s->s, TAG_LONG_METHODROOT); - write_uint16(s->s, id); + write_uint32(s->s, id); } } @@ -646,11 +646,17 @@ static jl_value_t *jl_decode_value(jl_ircode_state *s) JL_GC_DISABLED key = read_uint64(s->s); tag = read_uint8(s->s); assert(tag == TAG_METHODROOT || tag == TAG_LONG_METHODROOT); - return lookup_root(s->method, key, tag == TAG_METHODROOT ? read_uint8(s->s) : read_uint16(s->s)); + int index = -1; + if (tag == TAG_METHODROOT) + index = read_uint8(s->s); + else if (tag == TAG_LONG_METHODROOT) + index = read_uint32(s->s); + assert(index >= 0); + return lookup_root(s->method, key, index); case TAG_METHODROOT: return lookup_root(s->method, 0, read_uint8(s->s)); case TAG_LONG_METHODROOT: - return lookup_root(s->method, 0, read_uint16(s->s)); + return lookup_root(s->method, 0, read_uint32(s->s)); case TAG_SVEC: JL_FALLTHROUGH; case TAG_LONG_SVEC: return jl_decode_value_svec(s, tag); case TAG_COMMONSYM: From 27bdbf64898c6bec020251db6dfc9b87857f1ea1 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 4 Jan 2023 01:11:54 -0500 Subject: [PATCH 264/387] ambiguous: fix a rare case of comparison This used to do the opposite test of what should have been required here (it checked if it was ambiguous, rather than if it was sortable). This now better aligns with the implementation in gf.c for the similar fast path check during ml_matches. --- base/reflection.jl | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/base/reflection.jl b/base/reflection.jl index d54d31a3d626aa..75d45cd1e93724 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1789,7 +1789,9 @@ function isambiguous(m1::Method, m2::Method; ambiguous_bottom::Bool=false) end end # if ml-matches reported the existence of an ambiguity over their - # intersection, see if both m1 and m2 may be involved in it + # intersection, see if both m1 and m2 seem to be involved in it + # (if one was fully dominated by a different method, we want to will + # report the other ambiguous pair) have_m1 = have_m2 = false for match in ms match = match::Core.MethodMatch @@ -1814,18 +1816,14 @@ function isambiguous(m1::Method, m2::Method; ambiguous_bottom::Bool=false) minmax = m end end - if minmax === nothing + if minmax === nothing || minmax == m1 || minmax == m2 return true end for match in ms m = match.method m === minmax && continue - if match.fully_covers - if !morespecific(minmax.sig, m.sig) - return true - end - else - if morespecific(m.sig, minmax.sig) + if !morespecific(minmax.sig, m.sig) + if match.full_covers || !morespecific(m.sig, minmax.sig) return true end end @@ -1842,12 +1840,12 @@ function isambiguous(m1::Method, m2::Method; ambiguous_bottom::Bool=false) if ti2 <: m1.sig && ti2 <: m2.sig ti = ti2 elseif ti != ti2 - # TODO: this would be the correct way to handle this case, but + # TODO: this would be the more correct way to handle this case, but # people complained so we don't do it - # inner(ti2) || return false - return false # report that the type system failed to decide if it was ambiguous by saying they definitely aren't + #inner(ti2) || return false # report that the type system failed to decide if it was ambiguous by saying they definitely are + return false # report that the type system failed to decide if it was ambiguous by saying they definitely are not else - return false # report that the type system failed to decide if it was ambiguous by saying they definitely aren't + return false # report that the type system failed to decide if it was ambiguous by saying they definitely are not end end inner(ti) || return false From 45c81b1e75ba18fba15e4dfa4289ba476eda1e76 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Mon, 9 Jan 2023 13:37:08 -0500 Subject: [PATCH 265/387] ensure jl_compilation_sig does not narrow Vararg (#48152) Some code cleanup, and an early exit path that avoids trying to create a compilation signature from something that cannot be turned into one. Previously we might try a little too hard to make one, even if it meant we ignored that it was expected to be Varargs. Fix #48085 --- src/gf.c | 78 +++++++++++++++++++++++++++------------- test/compiler/codegen.jl | 4 +++ 2 files changed, 58 insertions(+), 24 deletions(-) diff --git a/src/gf.c b/src/gf.c index af9061737b3196..65dcf264021071 100644 --- a/src/gf.c +++ b/src/gf.c @@ -584,15 +584,12 @@ jl_value_t *jl_nth_slot_type(jl_value_t *sig, size_t i) JL_NOTSAFEPOINT { sig = jl_unwrap_unionall(sig); size_t len = jl_nparams(sig); - if (len == 0) - return NULL; if (i < len-1) return jl_tparam(sig, i); - if (jl_is_vararg(jl_tparam(sig, len-1))) - return jl_unwrap_vararg(jl_tparam(sig, len-1)); - if (i == len-1) - return jl_tparam(sig, i); - return NULL; + jl_value_t *p = jl_tparam(sig, len-1); + if (jl_is_vararg(p)) + p = jl_unwrap_vararg(p); + return p; } // if concrete_match returns false, the sig may specify `Type{T::DataType}`, while the `tt` contained DataType @@ -660,31 +657,62 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, // get the compilation signature specialization for this method static void jl_compilation_sig( - jl_tupletype_t *const tt, // the original tupletype of the call : this is expected to be a relative simple type (no Varags, Union, UnionAll, etc.) + jl_tupletype_t *const tt, // the original tupletype of the call (or DataType from precompile) jl_svec_t *sparams, jl_method_t *definition, intptr_t nspec, // output: jl_svec_t **const newparams JL_REQUIRE_ROOTED_SLOT) { + assert(jl_is_tuple_type(tt)); + jl_value_t *decl = definition->sig; + size_t nargs = definition->nargs; // == jl_nparams(jl_unwrap_unionall(decl)); + if (definition->generator) { // staged functions aren't optimized // so assume the caller was intelligent about calling us return; } - if (definition->sig == (jl_value_t*)jl_anytuple_type && jl_atomic_load_relaxed(&definition->unspecialized)) { + + if (decl == (jl_value_t*)jl_anytuple_type && jl_atomic_load_relaxed(&definition->unspecialized)) { *newparams = jl_anytuple_type->parameters; // handle builtin methods return; } - jl_value_t *decl = definition->sig; - assert(jl_is_tuple_type(tt)); + // some early sanity checks size_t i, np = jl_nparams(tt); - size_t nargs = definition->nargs; // == jl_nparams(jl_unwrap_unionall(decl)); + switch (jl_va_tuple_kind((jl_datatype_t*)decl)) { + case JL_VARARG_NONE: + if (jl_is_va_tuple(tt)) + // odd + return; + if (np != nargs) + // there are not enough input parameters to make this into a compilation sig + return; + break; + case JL_VARARG_INT: + case JL_VARARG_BOUND: + if (jl_is_va_tuple(tt)) + // the length needed is not known, but required for compilation + return; + if (np < nargs - 1) + // there are not enough input parameters to make this into a compilation sig + return; + break; + case JL_VARARG_UNBOUND: + if (np < nspec && jl_is_va_tuple(tt)) + // there are insufficient given parameters for jl_isa_compileable_sig now to like this type + // (there were probably fewer methods defined when we first selected this signature) + return; + break; + } + jl_value_t *type_i = NULL; JL_GC_PUSH1(&type_i); for (i = 0; i < np; i++) { jl_value_t *elt = jl_tparam(tt, i); + if (jl_is_vararg(elt)) + elt = jl_unwrap_vararg(elt); jl_value_t *decl_i = jl_nth_slot_type(decl, i); type_i = jl_rewrap_unionall(decl_i, decl); size_t i_arg = (i < nargs - 1 ? i : nargs - 1); @@ -732,16 +760,14 @@ static void jl_compilation_sig( if (!jl_has_free_typevars(decl_i) && !jl_is_kind(decl_i)) { if (decl_i != elt) { if (!*newparams) *newparams = jl_svec_copy(tt->parameters); + // n.b. it is possible here that !(elt <: decl_i), if elt was something unusual from intersection + // so this might narrow the result slightly, though still being compatible with the declared signature jl_svecset(*newparams, i, (jl_value_t*)decl_i); } continue; } } - if (jl_is_vararg(elt)) { - continue; - } - if (jl_types_equal(elt, (jl_value_t*)jl_type_type)) { // elt == Type{T} where T // not triggered for isdispatchtuple(tt), this attempts to handle // some cases of adapting a random signature into a compilation signature @@ -827,7 +853,7 @@ static void jl_compilation_sig( // in general, here we want to find the biggest type that's not a // supertype of any other method signatures. so far we are conservative // and the types we find should be bigger. - if (jl_nparams(tt) >= nspec && jl_va_tuple_kind((jl_datatype_t*)decl) == JL_VARARG_UNBOUND) { + if (np >= nspec && jl_va_tuple_kind((jl_datatype_t*)decl) == JL_VARARG_UNBOUND) { if (!*newparams) *newparams = tt->parameters; type_i = jl_svecref(*newparams, nspec - 2); // if all subsequent arguments are subtypes of type_i, specialize @@ -2076,7 +2102,9 @@ JL_DLLEXPORT jl_value_t *jl_matching_methods(jl_tupletype_t *types, jl_value_t * if (ambig != NULL) *ambig = 0; jl_value_t *unw = jl_unwrap_unionall((jl_value_t*)types); - if (jl_is_tuple_type(unw) && (unw == (jl_value_t*)jl_emptytuple_type || jl_tparam0(unw) == jl_bottom_type)) + if (!jl_is_tuple_type(unw)) + return (jl_value_t*)jl_an_empty_vec_any; + if (unw == (jl_value_t*)jl_emptytuple_type || jl_tparam0(unw) == jl_bottom_type) return (jl_value_t*)jl_an_empty_vec_any; if (mt == jl_nothing) mt = (jl_value_t*)jl_method_table_for(unw); @@ -2173,8 +2201,8 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t if (codeinst) return codeinst; - // if mi has a better (wider) signature for compilation use that instead - // and just copy it here for caching + // if mi has a better (wider) signature preferred for compilation use that + // instead and just copy it here for caching jl_method_instance_t *mi2 = jl_normalize_to_compilable_mi(mi); if (mi2 != mi) { jl_code_instance_t *codeinst2 = jl_compile_method_internal(mi2, world); @@ -2363,7 +2391,7 @@ JL_DLLEXPORT jl_value_t *jl_normalize_to_compilable_sig(jl_methtable_t *mt, jl_t jl_method_instance_t *jl_normalize_to_compilable_mi(jl_method_instance_t *mi JL_PROPAGATES_ROOT) { jl_method_t *def = mi->def.method; - if (!jl_is_method(def)) + if (!jl_is_method(def) || !jl_is_datatype(mi->specTypes)) return mi; jl_methtable_t *mt = jl_method_get_table(def); if ((jl_value_t*)mt == jl_nothing) @@ -2445,7 +2473,7 @@ jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types JL_PROPAGATES // Get a MethodInstance for a precompile() call. This uses a special kind of lookup that // tries to find a method for which the requested signature is compileable. -jl_method_instance_t *jl_get_compile_hint_specialization(jl_tupletype_t *types JL_PROPAGATES_ROOT, size_t world, size_t *min_valid, size_t *max_valid, int mt_cache) +static jl_method_instance_t *jl_get_compile_hint_specialization(jl_tupletype_t *types JL_PROPAGATES_ROOT, size_t world, size_t *min_valid, size_t *max_valid, int mt_cache) { if (jl_has_free_typevars((jl_value_t*)types)) return NULL; // don't poison the cache due to a malformed query @@ -2468,7 +2496,7 @@ jl_method_instance_t *jl_get_compile_hint_specialization(jl_tupletype_t *types J if (n == 1) { match = (jl_method_match_t*)jl_array_ptr_ref(matches, 0); } - else { + else if (jl_is_datatype(types)) { // first, select methods for which `types` is compileable size_t count = 0; for (i = 0; i < n; i++) { @@ -2839,7 +2867,9 @@ JL_DLLEXPORT jl_value_t *jl_apply_generic(jl_value_t *F, jl_value_t **args, uint static jl_method_match_t *_gf_invoke_lookup(jl_value_t *types JL_PROPAGATES_ROOT, jl_value_t *mt, size_t world, size_t *min_valid, size_t *max_valid) { jl_value_t *unw = jl_unwrap_unionall((jl_value_t*)types); - if (jl_is_tuple_type(unw) && jl_tparam0(unw) == jl_bottom_type) + if (!jl_is_tuple_type(unw)) + return NULL; + if (jl_tparam0(unw) == jl_bottom_type) return NULL; if (mt == jl_nothing) mt = (jl_value_t*)jl_method_table_for(unw); diff --git a/test/compiler/codegen.jl b/test/compiler/codegen.jl index 11cbd21b793a11..0d87f8cf8b56ba 100644 --- a/test/compiler/codegen.jl +++ b/test/compiler/codegen.jl @@ -790,3 +790,7 @@ f47247(a::Ref{Int}, b::Nothing) = setfield!(a, :x, b) f(x) = Core.bitcast(UInt64, x) @test occursin("llvm.trap", get_llvm(f, Tuple{Union{}})) end + +f48085(@nospecialize x...) = length(x) +@test Core.Compiler.get_compileable_sig(which(f48085, (Vararg{Any},)), Tuple{typeof(f48085), Vararg{Int}}, Core.svec()) === nothing +@test Core.Compiler.get_compileable_sig(which(f48085, (Vararg{Any},)), Tuple{typeof(f48085), Int, Vararg{Int}}, Core.svec()) === Tuple{typeof(f48085), Any, Vararg{Any}} From ec437b7744a85002eef8479880e96731e260045f Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Mon, 9 Jan 2023 19:44:29 +0100 Subject: [PATCH 266/387] add a suffix to a new cache files in case of failure of renaming it to an exisiting cache file already in use (#48137) * add a suffix to a new cache files in case of failure of renaming it to an exisiting file --- base/loading.jl | 29 +++++++++++++++++++++++++---- test/precompile.jl | 25 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/base/loading.jl b/base/loading.jl index 9137651007c30a..462e48df9a67f1 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -927,6 +927,8 @@ function find_all_in_cache_path(pkg::PkgId) end ocachefile_from_cachefile(cachefile) = string(chopsuffix(cachefile, ".ji"), ".", Base.Libc.dlext) +cachefile_from_ocachefile(cachefile) = string(chopsuffix(cachefile, ".$(Base.Libc.dlext)"), ".ji") + # use an Int counter so that nested @time_imports calls all remain open const TIMING_IMPORTS = Threads.Atomic{Int}(0) @@ -2091,7 +2093,6 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in if pkg.uuid !== nothing entrypath, entryfile = cache_file_entry(pkg) cachefiles = filter!(x -> startswith(x, entryfile * "_") && endswith(x, ".ji"), readdir(cachepath)) - if length(cachefiles) >= MAX_NUM_PRECOMPILE_FILES[] idx = findmin(mtime.(joinpath.(cachepath, cachefiles)))[2] evicted_cachefile = joinpath(cachepath, cachefiles[idx]) @@ -2104,11 +2105,31 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in end end - # this is atomic according to POSIX (not Win32): - rename(tmppath, cachefile; force=true) if cache_objects - rename(tmppath_so, ocachefile::String; force=true) + try + rename(tmppath_so, ocachefile::String; force=true) + catch e + e isa IOError || rethrow() + isfile(ocachefile) || rethrow() + # Windows prevents renaming a file that is in use so if there is a Julia session started + # with a package image loaded, we cannot rename that file. + # The code belows append a `_i` to the name of the cache file where `i` is the smallest number such that + # that cache file does not exist. + ocachename, ocacheext = splitext(ocachefile) + old_cachefiles = Set(readdir(cachepath)) + num = 1 + while true + ocachefile = ocachename * "_$num" * ocacheext + in(basename(ocachefile), old_cachefiles) || break + num += 1 + end + # TODO: Risk for a race here if some other process grabs this name before us + cachefile = cachefile_from_ocachefile(ocachefile) + rename(tmppath_so, ocachefile::String; force=true) + end end + # this is atomic according to POSIX (not Win32): + rename(tmppath, cachefile; force=true) return cachefile, ocachefile end finally diff --git a/test/precompile.jl b/test/precompile.jl index 8f8c8202832ee0..a553dd5b34a317 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -1708,6 +1708,31 @@ precompile_test_harness("BadInvalidations") do load_path end end +# https://github.com/JuliaLang/julia/issues/48074 +precompile_test_harness("WindowsCacheOverwrite") do load_path + # https://github.com/JuliaLang/julia/pull/47184#issuecomment-1364716312 + write(joinpath(load_path, "WindowsCacheOverwrite.jl"), + """ + module WindowsCacheOverwrite + + end # module + """) + ji, ofile = Base.compilecache(Base.PkgId("WindowsCacheOverwrite")) + (@eval (using WindowsCacheOverwrite)) + + write(joinpath(load_path, "WindowsCacheOverwrite.jl"), + """ + module WindowsCacheOverwrite + + f() = "something new" + + end # module + """) + + ji_2, ofile_2 = Base.compilecache(Base.PkgId("WindowsCacheOverwrite")) + @test ofile_2 == Base.ocachefile_from_cachefile(ji_2) +end + empty!(Base.DEPOT_PATH) append!(Base.DEPOT_PATH, original_depot_path) empty!(Base.LOAD_PATH) From d21a25d52ce9236a147b09ed1e25ec619ef1f06a Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Tue, 10 Jan 2023 05:35:43 +0900 Subject: [PATCH 267/387] parameterize lattice operations in `abstract_call_unionall` (#48191) --- base/compiler/abstractinterpretation.jl | 17 ++++++++--------- base/compiler/tfuncs.jl | 7 ++++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 4700e72ccf8744..24a5336254bebc 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1836,14 +1836,13 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, (; fargs return rt end -function abstract_call_unionall(argtypes::Vector{Any}) +function abstract_call_unionall(interp::AbstractInterpreter, argtypes::Vector{Any}) if length(argtypes) == 3 canconst = true a2 = argtypes[2] a3 = argtypes[3] - nothrow = a2 ⊑ TypeVar && ( - a3 ⊑ Type || - a3 ⊑ TypeVar) + ⊑ᵢ = ⊑(typeinf_lattice(interp)) + nothrow = a2 ⊑ᵢ TypeVar && (a3 ⊑ᵢ Type || a3 ⊑ᵢ TypeVar) if isa(a3, Const) body = a3.val elseif isType(a3) @@ -1852,7 +1851,7 @@ function abstract_call_unionall(argtypes::Vector{Any}) else return CallMeta(Any, Effects(EFFECTS_TOTAL; nothrow), NoCallInfo()) end - if !isa(body, Type) && !isa(body, TypeVar) + if !(isa(body, Type) || isa(body, TypeVar)) return CallMeta(Any, EFFECTS_THROWS, NoCallInfo()) end if has_free_typevars(body) @@ -1864,7 +1863,7 @@ function abstract_call_unionall(argtypes::Vector{Any}) else return CallMeta(Any, EFFECTS_THROWS, NoCallInfo()) end - !isa(tv, TypeVar) && return CallMeta(Any, EFFECTS_THROWS, NoCallInfo()) + isa(tv, TypeVar) || return CallMeta(Any, EFFECTS_THROWS, NoCallInfo()) body = UnionAll(tv, body) end ret = canconst ? Const(body) : Type{body} @@ -1983,10 +1982,10 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), ub_var = argtypes[3] end pT = typevar_tfunc(𝕃ᵢ, n, lb_var, ub_var) - return CallMeta(pT, - builtin_effects(𝕃ᵢ, Core._typevar, Any[n, lb_var, ub_var], pT), NoCallInfo()) + effects = builtin_effects(𝕃ᵢ, Core._typevar, Any[n, lb_var, ub_var], pT) + return CallMeta(pT, effects, NoCallInfo()) elseif f === UnionAll - return abstract_call_unionall(argtypes) + return abstract_call_unionall(interp, argtypes) elseif f === Tuple && la == 2 aty = argtypes[2] ty = isvarargtype(aty) ? unwrapva(aty) : widenconst(aty) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 1450be754a7bbb..48aa4fbfa1b110 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -574,8 +574,9 @@ end end return false end -@nospecs function typevar_nothrow(n, lb, ub) - (n ⊑ Symbol) || return false +@nospecs function typevar_nothrow(𝕃::AbstractLattice, n, lb, ub) + ⊑ = Core.Compiler.:⊑(𝕃) + n ⊑ Symbol || return false typebound_nothrow(lb) || return false typebound_nothrow(ub) || return false return true @@ -2004,7 +2005,7 @@ end return arraysize_nothrow(argtypes[1], argtypes[2]) elseif f === Core._typevar na == 3 || return false - return typevar_nothrow(argtypes[1], argtypes[2], argtypes[3]) + return typevar_nothrow(𝕃, argtypes[1], argtypes[2], argtypes[3]) elseif f === invoke return false elseif f === getfield From 88030b001846fe1f26be6636bd1d03dd40cc06af Mon Sep 17 00:00:00 2001 From: DilumAluthgeBot <43731525+DilumAluthgeBot@users.noreply.github.com> Date: Mon, 9 Jan 2023 17:07:42 -0500 Subject: [PATCH 268/387] =?UTF-8?q?=F0=9F=A4=96=20[master]=20Bump=20the=20?= =?UTF-8?q?Pkg=20stdlib=20from=20a8ae3c580=20to=205ae866151=20(#48184)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dilum Aluthge --- .../Pkg-5ae866151e0cfd0536d0bfbb66e7f14ea026bd31.tar.gz/md5 | 1 + .../Pkg-5ae866151e0cfd0536d0bfbb66e7f14ea026bd31.tar.gz/sha512 | 1 + .../Pkg-a8ae3c58078f8815718d3edc3842edf3a36adefa.tar.gz/md5 | 1 - .../Pkg-a8ae3c58078f8815718d3edc3842edf3a36adefa.tar.gz/sha512 | 1 - stdlib/Pkg.version | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 deps/checksums/Pkg-5ae866151e0cfd0536d0bfbb66e7f14ea026bd31.tar.gz/md5 create mode 100644 deps/checksums/Pkg-5ae866151e0cfd0536d0bfbb66e7f14ea026bd31.tar.gz/sha512 delete mode 100644 deps/checksums/Pkg-a8ae3c58078f8815718d3edc3842edf3a36adefa.tar.gz/md5 delete mode 100644 deps/checksums/Pkg-a8ae3c58078f8815718d3edc3842edf3a36adefa.tar.gz/sha512 diff --git a/deps/checksums/Pkg-5ae866151e0cfd0536d0bfbb66e7f14ea026bd31.tar.gz/md5 b/deps/checksums/Pkg-5ae866151e0cfd0536d0bfbb66e7f14ea026bd31.tar.gz/md5 new file mode 100644 index 00000000000000..1853fc3d640299 --- /dev/null +++ b/deps/checksums/Pkg-5ae866151e0cfd0536d0bfbb66e7f14ea026bd31.tar.gz/md5 @@ -0,0 +1 @@ +5ff653fadbde2fe95ffe1d696facaa5a diff --git a/deps/checksums/Pkg-5ae866151e0cfd0536d0bfbb66e7f14ea026bd31.tar.gz/sha512 b/deps/checksums/Pkg-5ae866151e0cfd0536d0bfbb66e7f14ea026bd31.tar.gz/sha512 new file mode 100644 index 00000000000000..b5f5bec511fcf9 --- /dev/null +++ b/deps/checksums/Pkg-5ae866151e0cfd0536d0bfbb66e7f14ea026bd31.tar.gz/sha512 @@ -0,0 +1 @@ +88f7ba8852efaff0416c382166cc2bcdb4090fde15b90b6ec62831bd640c8cc8cf3124d00b4392bc2b7719f0b51cb791c812e4e6b1e4fa3edf1cc5414dcd8ef6 diff --git a/deps/checksums/Pkg-a8ae3c58078f8815718d3edc3842edf3a36adefa.tar.gz/md5 b/deps/checksums/Pkg-a8ae3c58078f8815718d3edc3842edf3a36adefa.tar.gz/md5 deleted file mode 100644 index a45343b02aee0a..00000000000000 --- a/deps/checksums/Pkg-a8ae3c58078f8815718d3edc3842edf3a36adefa.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -6b0dfd893c25254e303607de4d9bda1f diff --git a/deps/checksums/Pkg-a8ae3c58078f8815718d3edc3842edf3a36adefa.tar.gz/sha512 b/deps/checksums/Pkg-a8ae3c58078f8815718d3edc3842edf3a36adefa.tar.gz/sha512 deleted file mode 100644 index bc7943a1aac62e..00000000000000 --- a/deps/checksums/Pkg-a8ae3c58078f8815718d3edc3842edf3a36adefa.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -8fcc8f8a8166ca6941ac1be72ec71bce935860cff3fc93609af68ac54b139743d3e3d7fd67ab538fa8ca1ce3279bb451c7c3df77f6fce8d65b11134ccd2581d4 diff --git a/stdlib/Pkg.version b/stdlib/Pkg.version index f5db6741ee3c29..b7b947b542e043 100644 --- a/stdlib/Pkg.version +++ b/stdlib/Pkg.version @@ -1,4 +1,4 @@ PKG_BRANCH = master -PKG_SHA1 = a8ae3c58078f8815718d3edc3842edf3a36adefa +PKG_SHA1 = 5ae866151e0cfd0536d0bfbb66e7f14ea026bd31 PKG_GIT_URL := https://github.com/JuliaLang/Pkg.jl.git PKG_TAR_URL = https://api.github.com/repos/JuliaLang/Pkg.jl/tarball/$1 From 3a8abf13d7c5e2af281b23b1def4373cf096563e Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 15 Dec 2022 11:25:03 -0500 Subject: [PATCH 269/387] Continue working to make GlobalRef more first-class The `owner` field now points directly at the target binding (which may be itself), rather than indirecting through a module+name re-lookup. This makes `module X; import .x as y; end` behavior more precise. The `globalref` field is currently now mandatory, since otherwise incremental compilation will be impossible right now. Maybe world-age splitting will help improve that later? Fix up a lot of locations that previously used the `name` field badly. There are some valid uses of this, but mostly it was wrong, since it would may fail to reflect what content actually appeared in the user's code. Directly forwarding the actual lookup result is cleaner and clearer for the user in most cases. Also remove `resolve` for GlobalRef: This has been wrong since `import as` was added, and appears unused and untested. --- base/boot.jl | 26 +- base/compiler/abstractinterpretation.jl | 6 +- base/reflection.jl | 10 +- base/show.jl | 20 +- doc/src/manual/embedding.md | 6 +- doc/src/manual/modules.md | 2 +- doc/src/manual/variables.md | 2 +- src/ast.c | 2 +- src/builtins.c | 54 +- src/cgutils.cpp | 14 +- src/codegen.cpp | 93 ++-- src/gc-heap-snapshot.cpp | 6 +- src/gc-heap-snapshot.h | 6 +- src/gc.c | 2 +- src/gf.c | 13 +- src/interpreter.c | 16 +- src/jl_exported_funcs.inc | 1 - src/jltypes.c | 17 +- src/julia.h | 35 +- src/julia_internal.h | 2 +- src/method.c | 7 +- src/module.c | 623 ++++++++++++------------ src/staticdata.c | 156 ++---- src/staticdata_utils.c | 15 +- src/toplevel.c | 31 +- test/atomics.jl | 9 +- test/core.jl | 8 +- test/syntax.jl | 19 +- 28 files changed, 592 insertions(+), 609 deletions(-) diff --git a/base/boot.jl b/base/boot.jl index 399fa1cdaf1ff3..84de1b54957827 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -414,7 +414,6 @@ eval(Core, quote end LineInfoNode(mod::Module, @nospecialize(method), file::Symbol, line::Int32, inlined_at::Int32) = $(Expr(:new, :LineInfoNode, :mod, :method, :file, :line, :inlined_at)) - GlobalRef(m::Module, s::Symbol, binding::Ptr{Nothing}) = $(Expr(:new, :GlobalRef, :m, :s, :binding)) SlotNumber(n::Int) = $(Expr(:new, :SlotNumber, :n)) TypedSlot(n::Int, @nospecialize(t)) = $(Expr(:new, :TypedSlot, :n, :t)) PhiNode(edges::Array{Int32, 1}, values::Array{Any, 1}) = $(Expr(:new, :PhiNode, :edges, :values)) @@ -422,17 +421,6 @@ eval(Core, quote PhiCNode(values::Array{Any, 1}) = $(Expr(:new, :PhiCNode, :values)) UpsilonNode(@nospecialize(val)) = $(Expr(:new, :UpsilonNode, :val)) UpsilonNode() = $(Expr(:new, :UpsilonNode)) - function CodeInstance( - mi::MethodInstance, @nospecialize(rettype), @nospecialize(inferred_const), - @nospecialize(inferred), const_flags::Int32, min_world::UInt, max_world::UInt, - ipo_effects::UInt32, effects::UInt32, @nospecialize(argescapes#=::Union{Nothing,Vector{ArgEscapeInfo}}=#), - relocatability::UInt8) - return ccall(:jl_new_codeinst, Ref{CodeInstance}, - (Any, Any, Any, Any, Int32, UInt, UInt, UInt32, UInt32, Any, UInt8), - mi, rettype, inferred_const, inferred, const_flags, min_world, max_world, - ipo_effects, effects, argescapes, - relocatability) - end Const(@nospecialize(v)) = $(Expr(:new, :Const, :v)) # NOTE the main constructor is defined within `Core.Compiler` _PartialStruct(@nospecialize(typ), fields::Array{Any, 1}) = $(Expr(:new, :PartialStruct, :typ, :fields)) @@ -441,6 +429,18 @@ eval(Core, quote MethodMatch(@nospecialize(spec_types), sparams::SimpleVector, method::Method, fully_covers::Bool) = $(Expr(:new, :MethodMatch, :spec_types, :sparams, :method, :fully_covers)) end) +function CodeInstance( + mi::MethodInstance, @nospecialize(rettype), @nospecialize(inferred_const), + @nospecialize(inferred), const_flags::Int32, min_world::UInt, max_world::UInt, + ipo_effects::UInt32, effects::UInt32, @nospecialize(argescapes#=::Union{Nothing,Vector{ArgEscapeInfo}}=#), + relocatability::UInt8) + return ccall(:jl_new_codeinst, Ref{CodeInstance}, + (Any, Any, Any, Any, Int32, UInt, UInt, UInt32, UInt32, Any, UInt8), + mi, rettype, inferred_const, inferred, const_flags, min_world, max_world, + ipo_effects, effects, argescapes, + relocatability) +end +GlobalRef(m::Module, s::Symbol) = ccall(:jl_module_globalref, Ref{GlobalRef}, (Any, Any), m, s) Module(name::Symbol=:anonymous, std_imports::Bool=true, default_names::Bool=true) = ccall(:jl_f_new_module, Ref{Module}, (Any, Bool, Bool), name, std_imports, default_names) function _Task(@nospecialize(f), reserved_stack::Int, completion_future) @@ -818,8 +818,6 @@ Unsigned(x::Union{Float16, Float32, Float64, Bool}) = UInt(x) Integer(x::Integer) = x Integer(x::Union{Float16, Float32, Float64}) = Int(x) -GlobalRef(m::Module, s::Symbol) = GlobalRef(m, s, bitcast(Ptr{Nothing}, 0)) - # Binding for the julia parser, called as # # Core._parse(text, filename, lineno, offset, options) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 24a5336254bebc..5d713f4d5f19af 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2521,14 +2521,12 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), end function isdefined_globalref(g::GlobalRef) - g.binding != C_NULL && return ccall(:jl_binding_boundp, Cint, (Ptr{Cvoid},), g.binding) != 0 - return isdefined(g.mod, g.name) + return ccall(:jl_globalref_boundp, Cint, (Any,), g) != 0 end function abstract_eval_globalref(g::GlobalRef) if isdefined_globalref(g) && isconst(g) - g.binding != C_NULL && return Const(ccall(:jl_binding_value, Any, (Ptr{Cvoid},), g.binding)) - return Const(getglobal(g.mod, g.name)) + return Const(ccall(:jl_get_globalref_value, Any, (Any,), g)) end ty = ccall(:jl_get_binding_type, Any, (Any, Any), g.mod, g.name) ty === nothing && return Any diff --git a/base/reflection.jl b/base/reflection.jl index d54d31a3d626aa..1afef448528b88 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -114,13 +114,6 @@ function binding_module(m::Module, s::Symbol) return unsafe_pointer_to_objref(p)::Module end -function resolve(g::GlobalRef; force::Bool=false) - if force || isbindingresolved(g.mod, g.name) - return GlobalRef(binding_module(g.mod, g.name), g.name) - end - return g -end - const _NAMEDTUPLE_NAME = NamedTuple.body.body.name function _fieldnames(@nospecialize t) @@ -268,8 +261,7 @@ isconst(m::Module, s::Symbol) = ccall(:jl_is_const, Cint, (Any, Any), m, s) != 0 function isconst(g::GlobalRef) - g.binding != C_NULL && return ccall(:jl_binding_is_const, Cint, (Ptr{Cvoid},), g.binding) != 0 - return isconst(g.mod, g.name) + return ccall(:jl_globalref_is_const, Cint, (Any,), g) != 0 end """ diff --git a/base/show.jl b/base/show.jl index c7dbb4a46e18e7..3dcdac77afb89e 100644 --- a/base/show.jl +++ b/base/show.jl @@ -1004,9 +1004,9 @@ end # If an object with this name exists in 'from', we need to check that it's the same binding # and that it's not deprecated. function isvisible(sym::Symbol, parent::Module, from::Module) - owner = ccall(:jl_binding_owner, Any, (Any, Any), parent, sym) - from_owner = ccall(:jl_binding_owner, Any, (Any, Any), from, sym) - return owner !== nothing && from_owner === owner && + owner = ccall(:jl_binding_owner, Ptr{Cvoid}, (Any, Any), parent, sym) + from_owner = ccall(:jl_binding_owner, Ptr{Cvoid}, (Any, Any), from, sym) + return owner !== C_NULL && from_owner === owner && !isdeprecated(parent, sym) && isdefined(from, sym) # if we're going to return true, force binding resolution end @@ -1842,10 +1842,10 @@ function allow_macroname(ex) end end -function is_core_macro(arg::GlobalRef, macro_name::AbstractString) - arg == GlobalRef(Core, Symbol(macro_name)) -end +is_core_macro(arg::GlobalRef, macro_name::AbstractString) = is_core_macro(arg, Symbol(macro_name)) +is_core_macro(arg::GlobalRef, macro_name::Symbol) = arg == GlobalRef(Core, macro_name) is_core_macro(@nospecialize(arg), macro_name::AbstractString) = false +is_core_macro(@nospecialize(arg), macro_name::Symbol) = false # symbol for IOContext flag signaling whether "begin" is treated # as an ordinary symbol, which is true in indexing expressions. @@ -2173,12 +2173,12 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In elseif head === :macrocall && nargs >= 2 # handle some special syntaxes # `a b c` - if is_core_macro(args[1], "@cmd") + if is_core_macro(args[1], :var"@cmd") print(io, "`", args[3], "`") # 11111111111111111111, 0xfffffffffffffffff, 1111...many digits... - elseif is_core_macro(args[1], "@int128_str") || - is_core_macro(args[1], "@uint128_str") || - is_core_macro(args[1], "@big_str") + elseif is_core_macro(args[1], :var"@int128_str") || + is_core_macro(args[1], :var"@uint128_str") || + is_core_macro(args[1], :var"@big_str") print(io, args[3]) # x"y" and x"y"z elseif isa(args[1], Symbol) && nargs >= 3 && isa(args[3], String) && diff --git a/doc/src/manual/embedding.md b/doc/src/manual/embedding.md index d384880728e454..cbad9955ab1902 100644 --- a/doc/src/manual/embedding.md +++ b/doc/src/manual/embedding.md @@ -406,8 +406,10 @@ As an alternative for very simple cases, it is possible to just create a global per pointer using ```c -jl_binding_t *bp = jl_get_binding_wr(jl_main_module, jl_symbol("var"), 1); -jl_checked_assignment(bp, val); +jl_module_t *mod = jl_main_module; +jl_sym_t *var = jl_symbol("var"); +jl_binding_t *bp = jl_get_binding_wr(mod, var, 1); +jl_checked_assignment(bp, mod, var, val); ``` ### Updating fields of GC-managed objects diff --git a/doc/src/manual/modules.md b/doc/src/manual/modules.md index 90680828d2bc22..4ffb1bca26e50e 100644 --- a/doc/src/manual/modules.md +++ b/doc/src/manual/modules.md @@ -171,7 +171,7 @@ julia> using .NiceStuff: nice julia> struct Cat end julia> nice(::Cat) = "nice 😸" -ERROR: error in method definition: function NiceStuff.nice must be explicitly imported to be extended +ERROR: invalid method definition in Main: function NiceStuff.nice must be explicitly imported to be extended Stacktrace: [1] top-level scope @ none:0 diff --git a/doc/src/manual/variables.md b/doc/src/manual/variables.md index 8f8c58d319edaa..4d057a1ab48834 100644 --- a/doc/src/manual/variables.md +++ b/doc/src/manual/variables.md @@ -81,7 +81,7 @@ julia> pi π = 3.1415926535897... julia> pi = 3 -ERROR: cannot assign a value to imported variable MathConstants.pi from module Main +ERROR: cannot assign a value to imported variable Base.pi from module Main julia> sqrt(100) 10.0 diff --git a/src/ast.c b/src/ast.c index b3f50417124d33..d0934d9eb82658 100644 --- a/src/ast.c +++ b/src/ast.c @@ -157,7 +157,7 @@ static value_t fl_defined_julia_global(fl_context_t *fl_ctx, value_t *args, uint jl_ast_context_t *ctx = jl_ast_ctx(fl_ctx); jl_sym_t *var = jl_symbol(symbol_name(fl_ctx, args[0])); jl_binding_t *b = jl_get_module_binding(ctx->module, var); - return (b != NULL && b->owner == ctx->module) ? fl_ctx->T : fl_ctx->F; + return (b != NULL && jl_atomic_load_relaxed(&b->owner) == b) ? fl_ctx->T : fl_ctx->F; } static value_t fl_current_module_counter(fl_context_t *fl_ctx, value_t *args, uint32_t nargs) JL_NOTSAFEPOINT diff --git a/src/builtins.c b/src/builtins.c index 6ebad43629c8c0..80a273a9a7d576 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1194,11 +1194,13 @@ JL_CALLABLE(jl_f_getglobal) JL_TYPECHK(getglobal, symbol, args[2]); order = jl_get_atomic_order_checked((jl_sym_t*)args[2], 1, 0); } - JL_TYPECHK(getglobal, module, args[0]); - JL_TYPECHK(getglobal, symbol, args[1]); + jl_module_t *mod = (jl_module_t*)args[0]; + jl_sym_t *sym = (jl_sym_t*)args[1]; + JL_TYPECHK(getglobal, module, (jl_value_t*)mod); + JL_TYPECHK(getglobal, symbol, (jl_value_t*)sym); if (order == jl_memory_order_notatomic) jl_atomic_error("getglobal: module binding cannot be read non-atomically"); - jl_value_t *v = jl_eval_global_var((jl_module_t*)args[0], (jl_sym_t*)args[1]); + jl_value_t *v = jl_eval_global_var(mod, sym); // is seq_cst already, no fence needed return v; } @@ -1211,32 +1213,36 @@ JL_CALLABLE(jl_f_setglobal) JL_TYPECHK(setglobal!, symbol, args[3]); order = jl_get_atomic_order_checked((jl_sym_t*)args[3], 0, 1); } - JL_TYPECHK(setglobal!, module, args[0]); - JL_TYPECHK(setglobal!, symbol, args[1]); + jl_module_t *mod = (jl_module_t*)args[0]; + jl_sym_t *var = (jl_sym_t*)args[1]; + JL_TYPECHK(setglobal!, module, (jl_value_t*)mod); + JL_TYPECHK(setglobal!, symbol, (jl_value_t*)var); if (order == jl_memory_order_notatomic) jl_atomic_error("setglobal!: module binding cannot be written non-atomically"); // is seq_cst already, no fence needed - jl_binding_t *b = jl_get_binding_wr_or_error((jl_module_t*)args[0], (jl_sym_t*)args[1]); - jl_checked_assignment(b, args[2]); + jl_binding_t *b = jl_get_binding_wr_or_error(mod, var); + jl_checked_assignment(b, mod, var, args[2]); return args[2]; } JL_CALLABLE(jl_f_get_binding_type) { JL_NARGS(get_binding_type, 2, 2); - JL_TYPECHK(get_binding_type, module, args[0]); - JL_TYPECHK(get_binding_type, symbol, args[1]); jl_module_t *mod = (jl_module_t*)args[0]; - jl_sym_t *sym = (jl_sym_t*)args[1]; - jl_value_t *ty = jl_get_binding_type(mod, sym); + jl_sym_t *var = (jl_sym_t*)args[1]; + JL_TYPECHK(get_binding_type, module, (jl_value_t*)mod); + JL_TYPECHK(get_binding_type, symbol, (jl_value_t*)var); + jl_value_t *ty = jl_get_binding_type(mod, var); if (ty == (jl_value_t*)jl_nothing) { - jl_binding_t *b = jl_get_binding_wr(mod, sym, 0); - if (b && b->owner == mod) { - jl_value_t *old_ty = NULL; - jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type); - return jl_atomic_load_relaxed(&b->ty); - } - return (jl_value_t*)jl_any_type; + jl_binding_t *b = jl_get_binding_wr(mod, var, 0); + if (b == NULL) + return (jl_value_t*)jl_any_type; + jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner); + if (b2 != b) + return (jl_value_t*)jl_any_type; + jl_value_t *old_ty = NULL; + jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type); + return jl_atomic_load_relaxed(&b->ty); } return ty; } @@ -1244,17 +1250,19 @@ JL_CALLABLE(jl_f_get_binding_type) JL_CALLABLE(jl_f_set_binding_type) { JL_NARGS(set_binding_type!, 2, 3); - JL_TYPECHK(set_binding_type!, module, args[0]); - JL_TYPECHK(set_binding_type!, symbol, args[1]); + jl_module_t *m = (jl_module_t*)args[0]; + jl_sym_t *s = (jl_sym_t*)args[1]; + JL_TYPECHK(set_binding_type!, module, (jl_value_t*)m); + JL_TYPECHK(set_binding_type!, symbol, (jl_value_t*)s); jl_value_t *ty = nargs == 2 ? (jl_value_t*)jl_any_type : args[2]; JL_TYPECHK(set_binding_type!, type, ty); - jl_binding_t *b = jl_get_binding_wr((jl_module_t*)args[0], (jl_sym_t*)args[1], 1); + jl_binding_t *b = jl_get_binding_wr(m, s, 1); jl_value_t *old_ty = NULL; if (!jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, ty) && ty != old_ty) { if (nargs == 2) return jl_nothing; - jl_errorf("cannot set type for global %s. It already has a value or is already set to a different type.", - jl_symbol_name(b->name)); + jl_errorf("cannot set type for global %s.%s. It already has a value or is already set to a different type.", + jl_symbol_name(m->name), jl_symbol_name(s)); } jl_gc_wb_binding(b, ty); return jl_nothing; diff --git a/src/cgutils.cpp b/src/cgutils.cpp index d2862e55a32413..693c4e66bd35a1 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -488,7 +488,8 @@ static Value *literal_pointer_val(jl_codectx_t &ctx, jl_binding_t *p) if (!ctx.emission_context.imaging) return literal_static_pointer_val(p, ctx.types().T_pjlvalue); // bindings are prefixed with jl_bnd# - Value *pgv = julia_pgv(ctx, "jl_bnd#", p->name, p->owner, p); + jl_globalref_t *gr = p->globalref; + Value *pgv = gr ? julia_pgv(ctx, "jl_bnd#", gr->name, gr->mod, p) : julia_pgv(ctx, "jl_bnd#", p); return tbaa_decorate(ctx.tbaa().tbaa_const, maybe_mark_load_dereferenceable( ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, pgv, Align(sizeof(void*))), false, sizeof(jl_binding_t), alignof(jl_binding_t))); @@ -526,11 +527,14 @@ static Value *julia_binding_gv(jl_codectx_t &ctx, jl_binding_t *b) { // emit a literal_pointer_val to a jl_binding_t // binding->value are prefixed with * - if (ctx.emission_context.imaging) - return tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, - julia_pgv(ctx, "*", b->name, b->owner, b), Align(sizeof(void*)))); - else + if (ctx.emission_context.imaging) { + jl_globalref_t *gr = b->globalref; + Value *pgv = gr ? julia_pgv(ctx, "*", gr->name, gr->mod, b) : julia_pgv(ctx, "*jl_bnd#", b); + return tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, pgv, Align(sizeof(void*)))); + } + else { return literal_static_pointer_val(b, ctx.types().T_pjlvalue); + } } // --- mapping between julia and llvm types --- diff --git a/src/codegen.cpp b/src/codegen.cpp index 1caafd8e64330a..f36a7458c48297 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -662,20 +662,25 @@ static const auto jlvboundserror_func = new JuliaFunction{ }; static const auto jluboundserror_func = new JuliaFunction{ XSTR(jl_bounds_error_unboxed_int), - [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), + [](LLVMContext &C) { + return FunctionType::get(getVoidTy(C), {PointerType::get(getInt8Ty(C), AddressSpace::Derived), JuliaType::get_pjlvalue_ty(C), getSizeTy(C)}, false); }, get_attrs_noreturn, }; static const auto jlcheckassign_func = new JuliaFunction{ XSTR(jl_checked_assignment), - [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), - {JuliaType::get_pjlvalue_ty(C), PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted)}, false); }, + [](LLVMContext &C) { + auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); + return FunctionType::get(getVoidTy(C), + {T_pjlvalue, T_pjlvalue, T_pjlvalue, PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted)}, false); }, nullptr, }; static const auto jldeclareconst_func = new JuliaFunction{ XSTR(jl_declare_constant), - [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), - {JuliaType::get_pjlvalue_ty(C)}, false); }, + [](LLVMContext &C) { + auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); + return FunctionType::get(getVoidTy(C), + {T_pjlvalue, T_pjlvalue, T_pjlvalue}, false); }, nullptr, }; static const auto jlgetbindingorerror_func = new JuliaFunction{ @@ -770,8 +775,7 @@ static const auto jlgenericfunction_func = new JuliaFunction{ auto T_pjlvalue = PointerType::get(T_jlvalue, 0); auto T_prjlvalue = PointerType::get(T_jlvalue, AddressSpace::Tracked); auto T_pprjlvalue = PointerType::get(T_prjlvalue, 0); - return FunctionType::get(T_prjlvalue, - {T_pjlvalue, T_pjlvalue, T_pprjlvalue, T_pjlvalue, T_pjlvalue}, false); + return FunctionType::get(T_prjlvalue, {T_pjlvalue, T_pjlvalue, T_pprjlvalue, T_pjlvalue}, false); }, nullptr, }; @@ -2258,9 +2262,9 @@ static void show_source_loc(jl_codectx_t &ctx, JL_STREAM *out) jl_printf(out, "in %s at %s", ctx.name, ctx.file.str().c_str()); } -static void cg_bdw(jl_codectx_t &ctx, jl_binding_t *b) +static void cg_bdw(jl_codectx_t &ctx, jl_sym_t *var, jl_binding_t *b) { - jl_binding_deprecation_warning(ctx.module, b); + jl_binding_deprecation_warning(ctx.module, var, b); if (b->deprecated == 1 && jl_options.depwarn) { show_source_loc(ctx, JL_STDERR); jl_printf(JL_STDERR, "\n"); @@ -2322,7 +2326,7 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex) jl_binding_t *b = jl_get_binding(jl_globalref_mod(ex), s); if (b && b->constp) { if (b->deprecated) - cg_bdw(ctx, b); + cg_bdw(ctx, s, b); return jl_atomic_load_relaxed(&b->value); } return NULL; @@ -2344,7 +2348,7 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex) jl_binding_t *b = jl_get_binding(m, s); if (b && b->constp) { if (b->deprecated) - cg_bdw(ctx, b); + cg_bdw(ctx, s, b); return jl_atomic_load_relaxed(&b->value); } } @@ -2603,8 +2607,12 @@ static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t * return emit_checked_var(ctx, bp, name, false, ctx.tbaa().tbaa_binding); } -static void emit_globalset(jl_codectx_t &ctx, jl_binding_t *bnd, Value *bp, const jl_cgval_t &rval_info, AtomicOrdering Order) +static bool emit_globalset(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *sym, const jl_cgval_t &rval_info, AtomicOrdering Order) { + jl_binding_t *bnd = NULL; + Value *bp = global_binding_pointer(ctx, mod, sym, &bnd, true); + if (bp == NULL) + return false; Value *rval = boxed(ctx, rval_info); if (bnd && !bnd->constp) { jl_value_t *ty = jl_atomic_load_relaxed(&bnd->ty); @@ -2613,10 +2621,12 @@ static void emit_globalset(jl_codectx_t &ctx, jl_binding_t *bnd, Value *bp, cons v->setOrdering(Order); tbaa_decorate(ctx.tbaa().tbaa_binding, v); emit_write_barrier(ctx, bp, rval); - return; + return true; } } - ctx.builder.CreateCall(prepare_call(jlcheckassign_func), { bp, mark_callee_rooted(ctx, rval) }); + ctx.builder.CreateCall(prepare_call(jlcheckassign_func), + { bp, literal_pointer_val(ctx, (jl_value_t*)mod), literal_pointer_val(ctx, (jl_value_t*)sym), mark_callee_rooted(ctx, rval) }); + return true; } static Value *emit_box_compare(jl_codectx_t &ctx, const jl_cgval_t &arg1, const jl_cgval_t &arg2, @@ -2915,15 +2925,10 @@ static bool emit_f_opglobal(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, if (sym.constant && jl_is_symbol(sym.constant)) { jl_sym_t *name = (jl_sym_t*)sym.constant; if (mod.constant && jl_is_module(mod.constant)) { - jl_binding_t *bnd = NULL; - Value *bp = global_binding_pointer(ctx, (jl_module_t*)mod.constant, name, &bnd, true); - if (bp) { - emit_globalset(ctx, bnd, bp, val, get_llvm_atomic_order(order)); + if (emit_globalset(ctx, (jl_module_t*)mod.constant, name, val, get_llvm_atomic_order(order))) *ret = val; - } - else { + else *ret = jl_cgval_t(); // unreachable - } return true; } } @@ -4238,18 +4243,18 @@ static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t return p; } if (assign) { - if (b->owner != m) { - char *msg; - (void)asprintf(&msg, "cannot assign a value to imported variable %s.%s from module %s", - jl_symbol_name(b->owner->name), jl_symbol_name(s), jl_symbol_name(m->name)); - emit_error(ctx, msg); - free(msg); + if (jl_atomic_load_relaxed(&b->owner) != b) { + // this will fail at runtime, so defer to the runtime to create the error + ctx.builder.CreateCall(prepare_call(jlgetbindingwrorerror_func), + { literal_pointer_val(ctx, (jl_value_t*)m), + literal_pointer_val(ctx, (jl_value_t*)s) }); + CreateTrap(ctx.builder); return NULL; } } else { if (b->deprecated) - cg_bdw(ctx, b); + cg_bdw(ctx, s, b); } *pbnd = b; return julia_binding_gv(ctx, b); @@ -4711,20 +4716,20 @@ static void emit_assignment(jl_codectx_t &ctx, jl_value_t *l, jl_value_t *r, ssi return emit_varinfo_assign(ctx, vi, rval_info, l); } - jl_binding_t *bnd = NULL; - Value *bp = NULL; - if (jl_is_symbol(l)) - bp = global_binding_pointer(ctx, ctx.module, (jl_sym_t*)l, &bnd, true); + jl_module_t *mod; + jl_sym_t *sym; + if (jl_is_symbol(l)) { + mod = ctx.module; + sym = (jl_sym_t*)l; + } else { assert(jl_is_globalref(l)); - bp = global_binding_pointer(ctx, jl_globalref_mod(l), jl_globalref_name(l), &bnd, true); - } - if (bp != NULL) { - emit_globalset(ctx, bnd, bp, rval_info, AtomicOrdering::Release); - // Global variable. Does not need debug info because the debugger knows about - // its memory location. + mod = jl_globalref_mod(l); + sym = jl_globalref_name(l); } - return; + emit_globalset(ctx, mod, sym, rval_info, AtomicOrdering::Release); + // Global variable. Does not need debug info because the debugger knows about + // its memory location. } static void emit_upsilonnode(jl_codectx_t &ctx, ssize_t phic, jl_value_t *val) @@ -5064,7 +5069,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ jl_value_t *mn = args[0]; assert(jl_is_symbol(mn) || jl_is_slot(mn)); - Value *bp = NULL, *name, *bp_owner = Constant::getNullValue(ctx.types().T_pjlvalue); + Value *bp = NULL, *name; jl_binding_t *bnd = NULL; bool issym = jl_is_symbol(mn); bool isglobalref = !issym && jl_is_globalref(mn); @@ -5089,17 +5094,16 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ } bp = julia_binding_gv(ctx, bnd); bp = julia_binding_pvalue(ctx, bp); - bp_owner = literal_pointer_val(ctx, (jl_value_t*)mod); } else if (jl_is_slot(mn) || jl_is_argument(mn)) { + // XXX: eval_methoddef does not have this code branch int sl = jl_slot_number(mn)-1; jl_varinfo_t &vi = ctx.slots[sl]; bp = vi.boxroot; name = literal_pointer_val(ctx, (jl_value_t*)slot_symbol(ctx, sl)); } if (bp) { - Value *mdargs[5] = { name, literal_pointer_val(ctx, (jl_value_t*)mod), bp, - bp_owner, literal_pointer_val(ctx, bnd) }; + Value *mdargs[] = { name, literal_pointer_val(ctx, (jl_value_t*)mod), bp, literal_pointer_val(ctx, bnd) }; jl_cgval_t gf = mark_julia_type( ctx, ctx.builder.CreateCall(prepare_call(jlgenericfunction_func), makeArrayRef(mdargs)), @@ -5138,7 +5142,8 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ jl_binding_t *bnd = NULL; Value *bp = global_binding_pointer(ctx, mod, sym, &bnd, true); if (bp) - ctx.builder.CreateCall(prepare_call(jldeclareconst_func), bp); + ctx.builder.CreateCall(prepare_call(jldeclareconst_func), + { bp, literal_pointer_val(ctx, (jl_value_t*)mod), literal_pointer_val(ctx, (jl_value_t*)sym) }); } } else if (head == jl_new_sym) { diff --git a/src/gc-heap-snapshot.cpp b/src/gc-heap-snapshot.cpp index ac2a0469364525..c8c25623f81962 100644 --- a/src/gc-heap-snapshot.cpp +++ b/src/gc-heap-snapshot.cpp @@ -392,16 +392,16 @@ void _gc_heap_snapshot_record_object_edge(jl_value_t *from, jl_value_t *to, void g_snapshot->names.find_or_create_string_id(path)); } -void _gc_heap_snapshot_record_module_to_binding(jl_module_t* module, jl_binding_t* binding) JL_NOTSAFEPOINT +void _gc_heap_snapshot_record_module_to_binding(jl_module_t *module, jl_sym_t *name, jl_binding_t *binding) JL_NOTSAFEPOINT { auto from_node_idx = record_node_to_gc_snapshot((jl_value_t*)module); - auto to_node_idx = record_pointer_to_gc_snapshot(binding, sizeof(jl_binding_t), jl_symbol_name(binding->name)); + auto to_node_idx = record_pointer_to_gc_snapshot(binding, sizeof(jl_binding_t), jl_symbol_name(name)); jl_value_t *value = jl_atomic_load_relaxed(&binding->value); auto value_idx = value ? record_node_to_gc_snapshot(value) : 0; jl_value_t *ty = jl_atomic_load_relaxed(&binding->ty); auto ty_idx = ty ? record_node_to_gc_snapshot(ty) : 0; - jl_value_t *globalref = jl_atomic_load_relaxed(&binding->globalref); + jl_value_t *globalref = (jl_value_t*)binding->globalref; auto globalref_idx = globalref ? record_node_to_gc_snapshot(globalref) : 0; auto &from_node = g_snapshot->nodes[from_node_idx]; diff --git a/src/gc-heap-snapshot.h b/src/gc-heap-snapshot.h index 8c3af5b86bec7b..1ddb4e93167367 100644 --- a/src/gc-heap-snapshot.h +++ b/src/gc-heap-snapshot.h @@ -20,7 +20,7 @@ void _gc_heap_snapshot_record_task_to_frame_edge(jl_task_t *from, void *to) JL_N void _gc_heap_snapshot_record_frame_to_frame_edge(jl_gcframe_t *from, jl_gcframe_t *to) JL_NOTSAFEPOINT; void _gc_heap_snapshot_record_array_edge(jl_value_t *from, jl_value_t *to, size_t index) JL_NOTSAFEPOINT; void _gc_heap_snapshot_record_object_edge(jl_value_t *from, jl_value_t *to, void* slot) JL_NOTSAFEPOINT; -void _gc_heap_snapshot_record_module_to_binding(jl_module_t* module, jl_binding_t* binding) JL_NOTSAFEPOINT; +void _gc_heap_snapshot_record_module_to_binding(jl_module_t* module, jl_sym_t *name, jl_binding_t* binding) JL_NOTSAFEPOINT; // Used for objects managed by GC, but which aren't exposed in the julia object, so have no // field or index. i.e. they're not reachable from julia code, but we _will_ hit them in // the GC mark phase (so we can check their type tag to get the size). @@ -73,10 +73,10 @@ static inline void gc_heap_snapshot_record_object_edge(jl_value_t *from, jl_valu } } -static inline void gc_heap_snapshot_record_module_to_binding(jl_module_t* module, jl_binding_t* binding) JL_NOTSAFEPOINT +static inline void gc_heap_snapshot_record_module_to_binding(jl_module_t* module, jl_sym_t *name, jl_binding_t* binding) JL_NOTSAFEPOINT { if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full)) { - _gc_heap_snapshot_record_module_to_binding(module, binding); + _gc_heap_snapshot_record_module_to_binding(module, name, binding); } } diff --git a/src/gc.c b/src/gc.c index 9c1301eb1c938e..995d8306168cd1 100644 --- a/src/gc.c +++ b/src/gc.c @@ -2543,7 +2543,7 @@ module_binding: { continue; verify_parent1("module", binding->parent, begin, "binding_buff"); // Record the size used for the box for non-const bindings - gc_heap_snapshot_record_module_to_binding(binding->parent, b); + gc_heap_snapshot_record_module_to_binding(binding->parent, (jl_sym_t*)begin[-1], b); if (gc_try_setmark((jl_value_t*)b, &binding->nptr, &tag, &bits)) { begin += 2; binding->begin = begin; diff --git a/src/gf.c b/src/gf.c index 65dcf264021071..46506c2df98e9b 100644 --- a/src/gf.c +++ b/src/gf.c @@ -473,17 +473,18 @@ int foreach_mtable_in_module( { size_t i; void **table = m->bindings.table; - for (i = 1; i < m->bindings.size; i += 2) { - if (table[i] != HT_NOTFOUND) { - jl_binding_t *b = (jl_binding_t*)table[i]; + for (i = 0; i < m->bindings.size; i += 2) { + if (table[i+1] != HT_NOTFOUND) { + jl_sym_t *name = (jl_sym_t*)table[i]; + jl_binding_t *b = (jl_binding_t*)table[i+1]; JL_GC_PROMISE_ROOTED(b); - if (b->owner == m && b->constp) { + if (jl_atomic_load_relaxed(&b->owner) == b && b->constp) { jl_value_t *v = jl_atomic_load_relaxed(&b->value); if (v) { jl_value_t *uw = jl_unwrap_unionall(v); if (jl_is_datatype(uw)) { jl_typename_t *tn = ((jl_datatype_t*)uw)->name; - if (tn->module == m && tn->name == b->name && tn->wrapper == v) { + if (tn->module == m && tn->name == name && tn->wrapper == v) { // this is the original/primary binding for the type (name/wrapper) jl_methtable_t *mt = tn->mt; if (mt != NULL && (jl_value_t*)mt != jl_nothing && mt != jl_type_type_mt && mt != jl_nonfunction_mt) { @@ -494,7 +495,7 @@ int foreach_mtable_in_module( } else if (jl_is_module(v)) { jl_module_t *child = (jl_module_t*)v; - if (child != m && child->parent == m && child->name == b->name) { + if (child != m && child->parent == m && child->name == name) { // this is the original/primary binding for the submodule if (!foreach_mtable_in_module(child, visit, env)) return 0; diff --git a/src/interpreter.c b/src/interpreter.c index 3a3e270c3a5523..bc816b0025d9aa 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -91,10 +91,9 @@ static jl_value_t *eval_methoddef(jl_expr_t *ex, interpreter_state *s) if (!jl_is_symbol(fname)) { jl_error("method: invalid declaration"); } - jl_value_t *bp_owner = (jl_value_t*)modu; jl_binding_t *b = jl_get_binding_for_method_def(modu, fname); _Atomic(jl_value_t*) *bp = &b->value; - jl_value_t *gf = jl_generic_function_def(b->name, b->owner, bp, bp_owner, b); + jl_value_t *gf = jl_generic_function_def(fname, modu, bp, b); return gf; } @@ -153,13 +152,10 @@ jl_value_t *jl_eval_global_var(jl_module_t *m, jl_sym_t *e) jl_value_t *jl_eval_globalref(jl_globalref_t *g) { - if (g->bnd_cache) { - jl_value_t *v = jl_atomic_load_relaxed(&g->bnd_cache->value); - if (v == NULL) - jl_undefined_var_error(g->name); - return v; - } - return jl_eval_global_var(g->mod, g->name); + jl_value_t *v = jl_get_globalref_value(g); + if (v == NULL) + jl_undefined_var_error(g->name); + return v; } static int jl_source_nslots(jl_code_info_t *src) JL_NOTSAFEPOINT @@ -497,7 +493,7 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, } JL_GC_PUSH1(&rhs); jl_binding_t *b = jl_get_binding_wr_or_error(modu, sym); - jl_checked_assignment(b, rhs); + jl_checked_assignment(b, modu, sym, rhs); JL_GC_POP(); } } diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index 8ea77bfe12be35..ccffb793588e33 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -55,7 +55,6 @@ XX(jl_atomic_swap_bits) \ XX(jl_backtrace_from_here) \ XX(jl_base_relative_to) \ - XX(jl_binding_owner) \ XX(jl_binding_resolved_p) \ XX(jl_bitcast) \ XX(jl_boundp) \ diff --git a/src/jltypes.c b/src/jltypes.c index 2b83c6396aa1ee..08ca8817c94c6a 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -2751,18 +2751,18 @@ void jl_init_types(void) JL_GC_DISABLED jl_binding_type = jl_new_datatype(jl_symbol("Binding"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(6, "name", "value", "globalref", "owner", "ty", "flags"), - jl_svec(6, jl_symbol_type, jl_any_type, jl_any_type/*jl_globalref_type*/, jl_module_type, jl_any_type, jl_uint8_type), - jl_emptysvec, 0, 1, 1); - const static uint32_t binding_constfields[1] = { 0x0001 }; // Set fields 1 as const - const static uint32_t binding_atomicfields[1] = { 0x0016 }; // Set fields 2, 3, 5 as atomic - jl_binding_type->name->constfields = binding_constfields; + jl_perm_symsvec(5, "value", "globalref", "owner", "ty", "flags"), + jl_svec(5, jl_any_type, jl_any_type/*jl_globalref_type*/, jl_any_type/*jl_binding_type*/, jl_type_type, jl_uint8_type), + jl_emptysvec, 0, 1, 0); + const static uint32_t binding_atomicfields[] = { 0x0015 }; // Set fields 1, 3, 4 as atomic jl_binding_type->name->atomicfields = binding_atomicfields; + const static uint32_t binding_constfields[] = { 0x0002 }; // Set fields 2 as constant + jl_binding_type->name->constfields = binding_constfields; jl_globalref_type = jl_new_datatype(jl_symbol("GlobalRef"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(3, "mod", "name", "binding"), - jl_svec(3, jl_module_type, jl_symbol_type, pointer_void), + jl_svec(3, jl_module_type, jl_symbol_type, jl_binding_type), jl_emptysvec, 0, 0, 3); tv = jl_svec2(tvar("A"), tvar("R")); @@ -2804,7 +2804,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_svecset(jl_method_instance_type->types, 6, jl_code_instance_type); jl_svecset(jl_code_instance_type->types, 13, jl_voidpointer_type); jl_svecset(jl_code_instance_type->types, 14, jl_voidpointer_type); - jl_svecset(jl_binding_type->types, 2, jl_globalref_type); + jl_svecset(jl_binding_type->types, 1, jl_globalref_type); + jl_svecset(jl_binding_type->types, 2, jl_binding_type); jl_compute_field_offsets(jl_datatype_type); jl_compute_field_offsets(jl_typename_type); diff --git a/src/julia.h b/src/julia.h index 1395df45013290..0c18bfbaadc554 100644 --- a/src/julia.h +++ b/src/julia.h @@ -196,6 +196,9 @@ STATIC_INLINE int jl_array_ndimwords(uint32_t ndims) JL_NOTSAFEPOINT typedef struct _jl_datatype_t jl_tupletype_t; struct _jl_code_instance_t; +typedef struct _jl_method_instance_t jl_method_instance_t; +typedef struct _jl_globalref_t jl_globalref_t; + // TypeMap is an implicitly defined type // that can consist of any of the following nodes: @@ -226,8 +229,6 @@ typedef jl_value_t *(*jl_fptr_sparam_t)(jl_value_t*, jl_value_t**, uint32_t, jl_ extern jl_call_t jl_fptr_interpret_call; JL_DLLEXPORT extern jl_callptr_t jl_fptr_interpret_call_addr; -typedef struct _jl_method_instance_t jl_method_instance_t; - typedef struct _jl_line_info_node_t { struct _jl_module_t *module; jl_value_t *method; @@ -312,7 +313,7 @@ typedef struct _jl_method_t { jl_value_t *slot_syms; // compacted list of slot names (String) jl_value_t *external_mt; // reference to the method table this method is part of, null if part of the internal table jl_value_t *source; // original code template (jl_code_info_t, but may be compressed), null for builtins - _Atomic(struct _jl_method_instance_t*) unspecialized; // unspecialized executable method instance, or null + _Atomic(jl_method_instance_t*) unspecialized; // unspecialized executable method instance, or null jl_value_t *generator; // executable code-generating function if available jl_array_t *roots; // pointers in generated code (shared to reduce memory), or null // Identify roots by module-of-origin. We only track the module for roots added during incremental compilation. @@ -373,7 +374,7 @@ struct _jl_method_instance_t { }; // OpaqueClosure -typedef struct jl_opaque_closure_t { +typedef struct _jl_opaque_closure_t { JL_DATA_TYPE jl_value_t *captures; size_t world; @@ -554,21 +555,21 @@ typedef struct _jl_vararg_t { jl_value_t *N; } jl_vararg_t; -typedef struct { +typedef struct _jl_weakref_t { JL_DATA_TYPE jl_value_t *value; } jl_weakref_t; -typedef struct { +typedef struct _jl_binding_t { JL_DATA_TYPE - jl_sym_t *name; _Atomic(jl_value_t*) value; - _Atomic(jl_value_t*) globalref; // cached GlobalRef for this binding - struct _jl_module_t* owner; // for individual imported bindings -- TODO: make _Atomic + jl_globalref_t *globalref; // cached GlobalRef for this binding + _Atomic(struct _jl_binding_t*) owner; // for individual imported bindings (NULL until 'resolved') _Atomic(jl_value_t*) ty; // binding type uint8_t constp:1; uint8_t exportp:1; uint8_t imported:1; + uint8_t usingfailed:1; uint8_t deprecated:2; // 0=not deprecated, 1=renamed, 2=moved to another package } jl_binding_t; @@ -598,11 +599,10 @@ typedef struct _jl_module_t { intptr_t hash; } jl_module_t; -typedef struct { +typedef struct _jl_globalref_t { jl_module_t *mod; jl_sym_t *name; - // Not serialized. Caches the value of jl_get_binding(mod, name). - jl_binding_t *bnd_cache; + jl_binding_t *binding; } jl_globalref_t; // one Type-to-Value entry @@ -1490,7 +1490,7 @@ JL_DLLEXPORT jl_sym_t *jl_tagged_gensym(const char *str, size_t len); JL_DLLEXPORT jl_sym_t *jl_get_root_symbol(void); JL_DLLEXPORT jl_value_t *jl_generic_function_def(jl_sym_t *name, jl_module_t *module, - _Atomic(jl_value_t*) *bp, jl_value_t *bp_owner, + _Atomic(jl_value_t*) *bp, jl_binding_t *bnd); JL_DLLEXPORT jl_method_t *jl_method_def(jl_svec_t *argdata, jl_methtable_t *mt, jl_code_info_t *f, jl_module_t *module); JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo); @@ -1634,13 +1634,14 @@ JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT int jl_defines_or_exports_p(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT int jl_binding_resolved_p(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT int jl_is_const(jl_module_t *m, jl_sym_t *var); -JL_DLLEXPORT int jl_binding_is_const(jl_binding_t *b); -JL_DLLEXPORT int jl_binding_boundp(jl_binding_t *b); +JL_DLLEXPORT int jl_globalref_is_const(jl_globalref_t *gr); +JL_DLLEXPORT int jl_globalref_boundp(jl_globalref_t *gr); +JL_DLLEXPORT jl_value_t *jl_get_globalref_value(jl_globalref_t *gr); JL_DLLEXPORT jl_value_t *jl_get_global(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); JL_DLLEXPORT void jl_set_global(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT); JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT); -JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_value_t *rhs JL_MAYBE_UNROOTED); -JL_DLLEXPORT void jl_declare_constant(jl_binding_t *b); +JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs JL_MAYBE_UNROOTED); +JL_DLLEXPORT void jl_declare_constant(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var); JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from); JL_DLLEXPORT void jl_module_use(jl_module_t *to, jl_module_t *from, jl_sym_t *s); JL_DLLEXPORT void jl_module_use_as(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname); diff --git a/src/julia_internal.h b/src/julia_internal.h index b839d5eda1650c..c769779f82d161 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -763,7 +763,7 @@ jl_array_t *jl_new_array_for_deserialization(jl_value_t *atype, uint32_t ndims, int isunboxed, int hasptr, int isunion, int elsz); void jl_module_run_initializer(jl_module_t *m); jl_binding_t *jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); -JL_DLLEXPORT void jl_binding_deprecation_warning(jl_module_t *m, jl_binding_t *b); +JL_DLLEXPORT void jl_binding_deprecation_warning(jl_module_t *m, jl_sym_t *sym, jl_binding_t *b); extern jl_array_t *jl_module_init_order JL_GLOBALLY_ROOTED; extern htable_t jl_current_modules JL_GLOBALLY_ROOTED; extern JL_DLLEXPORT jl_module_t *jl_precompile_toplevel_module JL_GLOBALLY_ROOTED; diff --git a/src/method.c b/src/method.c index 3da88ac8211ac8..850e520c945291 100644 --- a/src/method.c +++ b/src/method.c @@ -892,25 +892,24 @@ jl_method_t *jl_make_opaque_closure_method(jl_module_t *module, jl_value_t *name JL_DLLEXPORT jl_value_t *jl_generic_function_def(jl_sym_t *name, jl_module_t *module, _Atomic(jl_value_t*) *bp, - jl_value_t *bp_owner, jl_binding_t *bnd) { jl_value_t *gf = NULL; assert(name && bp); if (bnd && jl_atomic_load_relaxed(&bnd->value) != NULL && !bnd->constp) - jl_errorf("cannot define function %s; it already has a value", jl_symbol_name(bnd->name)); + jl_errorf("cannot define function %s; it already has a value", jl_symbol_name(name)); gf = jl_atomic_load_relaxed(bp); if (gf != NULL) { if (!jl_is_datatype_singleton((jl_datatype_t*)jl_typeof(gf)) && !jl_is_type(gf)) jl_errorf("cannot define function %s; it already has a value", jl_symbol_name(name)); } if (bnd) - bnd->constp = 1; + bnd->constp = 1; // XXX: use jl_declare_constant and jl_checked_assignment if (gf == NULL) { gf = (jl_value_t*)jl_new_generic_function(name, module); jl_atomic_store(bp, gf); // TODO: fix constp assignment data race - if (bp_owner) jl_gc_wb(bp_owner, gf); + if (bnd) jl_gc_wb(bnd, gf); } return gf; } diff --git a/src/module.c b/src/module.c index 11e92cb9e2a189..c0508349abc8f7 100644 --- a/src/module.c +++ b/src/module.c @@ -156,23 +156,39 @@ JL_DLLEXPORT uint8_t jl_istopmod(jl_module_t *mod) return mod->istopmod; } -static jl_binding_t *new_binding(jl_sym_t *name) +static jl_globalref_t *jl_new_globalref(jl_module_t *mod, jl_sym_t *name, jl_binding_t *b) { jl_task_t *ct = jl_current_task; - assert(jl_is_symbol(name)); + jl_globalref_t *g = (jl_globalref_t*)jl_gc_alloc(ct->ptls, sizeof(jl_globalref_t), jl_globalref_type); + g->mod = mod; + jl_gc_wb(g, g->mod); + g->name = name; + g->binding = b; + return g; +} + +static jl_binding_t *new_binding(jl_module_t *mod, jl_sym_t *name) +{ + jl_task_t *ct = jl_current_task; + assert(jl_is_module(mod) && jl_is_symbol(name)); jl_binding_t *b = (jl_binding_t*)jl_gc_alloc(ct->ptls, sizeof(jl_binding_t), jl_binding_type); - b->name = name; jl_atomic_store_relaxed(&b->value, NULL); - b->owner = NULL; + jl_atomic_store_relaxed(&b->owner, NULL); jl_atomic_store_relaxed(&b->ty, NULL); - jl_atomic_store_relaxed(&b->globalref, NULL); + b->globalref = NULL; b->constp = 0; b->exportp = 0; b->imported = 0; b->deprecated = 0; + b->usingfailed = 0; + JL_GC_PUSH1(&b); + b->globalref = jl_new_globalref(mod, name, b); + JL_GC_POP(); return b; } +static jl_module_t *jl_binding_dbgmodule(jl_binding_t *b, jl_module_t *m, jl_sym_t *var) JL_GLOBALLY_ROOTED; + // get binding for assignment JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int alloc) { @@ -181,20 +197,27 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_binding_t *b = *bp; if (b != HT_NOTFOUND) { - if (b->owner != m) { - if (b->owner == NULL) { - b->owner = m; + JL_GC_PROMISE_ROOTED(b); + jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner); + if (b2 != b) { + if (b2 == NULL) { + jl_atomic_store_release(&b->owner, b); } else if (alloc) { + jl_module_t *from = jl_binding_dbgmodule(b, m, var); JL_UNLOCK(&m->lock); - jl_errorf("cannot assign a value to imported variable %s.%s from module %s", - jl_symbol_name(b->owner->name), jl_symbol_name(var), jl_symbol_name(m->name)); + if (from == m) + jl_errorf("cannot assign a value to imported variable %s.%s", + jl_symbol_name(from->name), jl_symbol_name(var)); + else + jl_errorf("cannot assign a value to imported variable %s.%s from module %s", + jl_symbol_name(from->name), jl_symbol_name(var), jl_symbol_name(m->name)); } } } else if (alloc) { - b = new_binding(var); - b->owner = m; + b = new_binding(m, var); + jl_atomic_store_release(&b->owner, b); *bp = b; JL_GC_PROMISE_ROOTED(b); jl_gc_wb(m, b); @@ -219,14 +242,13 @@ static inline jl_binding_t *_jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ } #endif - // return module of binding JL_DLLEXPORT jl_module_t *jl_get_module_of_binding(jl_module_t *m, jl_sym_t *var) { jl_binding_t *b = jl_get_binding(m, var); if (b == NULL) return NULL; - return b->owner; + return b->globalref->mod; // TODO: deprecate this? } // get binding for adding a method @@ -236,42 +258,47 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_ JL_LOCK(&m->lock); jl_binding_t **bp = (jl_binding_t**)ptrhash_bp(&m->bindings, var); jl_binding_t *b = *bp; - JL_GC_PROMISE_ROOTED(b); if (b != HT_NOTFOUND) { - if (b->owner != m) { - if (b->owner == NULL) { - b->owner = m; + JL_GC_PROMISE_ROOTED(b); + jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner); + if (b2 != b) { + // TODO: make this cmpswap atomic + if (b2 == NULL) { + jl_atomic_store_release(&b->owner, b); } else { + jl_value_t *f = jl_atomic_load_relaxed(&b2->value); + jl_module_t *from = jl_binding_dbgmodule(b, m, var); JL_UNLOCK(&m->lock); - jl_binding_t *b2 = jl_get_binding(b->owner, b->name); - if (b2 == NULL || jl_atomic_load_relaxed(&b2->value) == NULL) - jl_errorf("invalid method definition: imported function %s.%s does not exist", - jl_symbol_name(b->owner->name), jl_symbol_name(b->name)); + if (f == NULL) { + // we must have implicitly imported this with using, so call jl_binding_dbgmodule to try to get the name of the module we got this from + jl_errorf("invalid method definition in %s: exported function %s.%s does not exist", + jl_symbol_name(m->name), jl_symbol_name(from->name), jl_symbol_name(var)); + } // TODO: we might want to require explicitly importing types to add constructors - if (!b->imported && (!b2->constp || !jl_is_type(jl_atomic_load_relaxed(&b2->value)))) { - jl_errorf("error in method definition: function %s.%s must be explicitly imported to be extended", - jl_symbol_name(b->owner->name), jl_symbol_name(b->name)); + // or we might want to drop this error entirely + if (!b->imported && (!b2->constp || !jl_is_type(f))) { + jl_errorf("invalid method definition in %s: function %s.%s must be explicitly imported to be extended", + jl_symbol_name(m->name), jl_symbol_name(from->name), jl_symbol_name(var)); } return b2; } } } else { - b = new_binding(var); - b->owner = m; + b = new_binding(m, var); + jl_atomic_store_relaxed(&b->owner, b); *bp = b; JL_GC_PROMISE_ROOTED(b); jl_gc_wb(m, b); } - JL_UNLOCK(&m->lock); + JL_UNLOCK(&m->lock); // may gc return b; } -static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname, - int explici); +static void module_import_(jl_module_t *to, jl_module_t *from, jl_binding_t *b, jl_sym_t *asname, jl_sym_t *s, int explici); typedef struct _modstack_t { jl_module_t *m; @@ -279,7 +306,7 @@ typedef struct _modstack_t { struct _modstack_t *prev; } modstack_t; -static jl_binding_t *jl_get_binding_(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, modstack_t *st); +static jl_binding_t *jl_resolve_owner(jl_binding_t *b/*optional*/, jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, modstack_t *st); static inline jl_module_t *module_usings_getidx(jl_module_t *m JL_PROPAGATES_ROOT, size_t i) JL_NOTSAFEPOINT; @@ -291,29 +318,44 @@ static inline jl_module_t *module_usings_getidx(jl_module_t *m JL_PROPAGATES_ROO } #endif +static int eq_bindings(jl_binding_t *a, jl_binding_t *b) +{ + if (a == b) + return 1; + if (jl_atomic_load_relaxed(&a->owner) == jl_atomic_load_relaxed(&b->owner)) + return 1; + if (a->constp && b->constp && jl_atomic_load_relaxed(&a->value) && jl_atomic_load_relaxed(&b->value) == jl_atomic_load_relaxed(&a->value)) + return 1; + return 0; +} + // find a binding from a module's `usings` list // called while holding m->lock -static jl_binding_t *using_resolve_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, modstack_t *st, int warn) +static jl_binding_t *using_resolve_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, jl_module_t **from, modstack_t *st, int warn) { jl_binding_t *b = NULL; jl_module_t *owner = NULL; - for(int i=(int)m->usings.len-1; i >= 0; --i) { + for (int i = (int)m->usings.len - 1; i >= 0; --i) { jl_module_t *imp = module_usings_getidx(m, i); // TODO: make sure this can't deadlock - JL_LOCK(&imp->lock); - jl_binding_t *tempb = _jl_get_module_binding(imp, var); - JL_UNLOCK(&imp->lock); - if (tempb != HT_NOTFOUND && tempb->exportp) { - tempb = jl_get_binding_(imp, var, st); - if (tempb == NULL || tempb->owner == NULL) + jl_binding_t *tempb = jl_get_module_binding(imp, var); + if (tempb != NULL && tempb->exportp) { + tempb = jl_resolve_owner(NULL, imp, var, st); // find the owner for tempb + if (tempb == NULL) // couldn't resolve; try next using (see issue #6105) continue; - if (owner != NULL && tempb->owner != b->owner && - !tempb->deprecated && !b->deprecated && - !(tempb->constp && b->constp && jl_atomic_load_relaxed(&tempb->value) && b->constp && jl_atomic_load_relaxed(&b->value) == jl_atomic_load_relaxed(&tempb->value))) { + assert(jl_atomic_load_relaxed(&tempb->owner) == tempb); + if (b != NULL && !tempb->deprecated && !b->deprecated && !eq_bindings(tempb, b)) { if (warn) { - // mark this binding resolved (by creating it or setting the owner), to avoid repeating the warning - (void)jl_get_binding_wr(m, var, 1); + // set usingfailed=1 to avoid repeating this warning + // the owner will still be NULL, so it can be later imported or defined + tempb = _jl_get_module_binding(m, var); + if (tempb == HT_NOTFOUND) { + tempb = new_binding(m, var); + ptrhash_put(&m->bindings, (void*)var, (void*)tempb); + jl_gc_wb(m, tempb); + } + tempb->usingfailed = 1; JL_UNLOCK(&m->lock); jl_printf(JL_STDERR, "WARNING: both %s and %s export \"%s\"; uses of it in module %s must be qualified\n", @@ -330,77 +372,97 @@ static jl_binding_t *using_resolve_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl } } } + *from = owner; return b; } +// for error message printing: look up the module that exported a binding to m as var +// this might not be the same as the owner of the binding, since the binding itself may itself have been imported from elsewhere +// must be holding m->lock before calling this +static jl_module_t *jl_binding_dbgmodule(jl_binding_t *b, jl_module_t *m, jl_sym_t *var) +{ + jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner); + if (b2 != b && !b->imported) { + // for implicitly imported globals, try to re-resolve it to find the module we got it from most directly + jl_module_t *from = NULL; + b = using_resolve_binding(m, var, &from, NULL, 0); + if (b) { + if (b2 == NULL || jl_atomic_load_relaxed(&b->owner) == jl_atomic_load_relaxed(&b2->owner)) + return from; + // if we did not find it (or accidentally found a different one), ignore this + } + } + return m; +} + // get binding for reading. might return NULL for unbound. -static jl_binding_t *jl_get_binding_(jl_module_t *m, jl_sym_t *var, modstack_t *st) +static jl_binding_t *jl_resolve_owner(jl_binding_t *b/*optional*/, jl_module_t *m, jl_sym_t *var, modstack_t *st) { - modstack_t top = { m, var, st }; - modstack_t *tmp = st; - while (tmp != NULL) { - if (tmp->m == m && tmp->var == var) { - // import cycle without finding actual location + if (b == NULL) + b = jl_get_module_binding(m, var); + if (b != NULL) { + if (jl_atomic_load_relaxed(&b->owner) == NULL && b->usingfailed) return NULL; - } - tmp = tmp->prev; + b = jl_atomic_load_relaxed(&b->owner); } - JL_LOCK(&m->lock); - jl_binding_t *b = _jl_get_module_binding(m, var); - if (b == HT_NOTFOUND || b->owner == NULL) { - b = using_resolve_binding(m, var, &top, 1); + if (b == NULL) { + modstack_t top = { m, var, st }; + modstack_t *tmp = st; + for (; tmp != NULL; tmp = tmp->prev) { + if (tmp->m == m && tmp->var == var) { + // import cycle without finding actual location + return NULL; + } + } + jl_module_t *from = NULL; // for error message printing + JL_LOCK(&m->lock); + b = using_resolve_binding(m, var, &from, &top, 1); JL_UNLOCK(&m->lock); if (b != NULL) { // do a full import to prevent the result of this lookup // from changing, for example if this var is assigned to // later. - module_import_(m, b->owner, b->name, var, 0); + // TODO: make this more thread-safe + assert(jl_atomic_load_relaxed(&b->owner) == b && from); + module_import_(m, from, b, var, var, 0); return b; } return NULL; } - JL_UNLOCK(&m->lock); - if (b->owner != m || b->name != var) - return jl_get_binding_(b->owner, b->name, &top); + assert(jl_atomic_load_relaxed(&b->owner) == b); return b; } JL_DLLEXPORT jl_binding_t *jl_get_binding_if_bound(jl_module_t *m, jl_sym_t *var) { - JL_LOCK(&m->lock); - jl_binding_t *b = _jl_get_module_binding(m, var); - JL_UNLOCK(&m->lock); - if (b == HT_NOTFOUND || b->owner == NULL) { - return NULL; - } - if (b->owner != m || b->name != var) - return jl_get_binding_if_bound(b->owner, b->name); - return b; + jl_binding_t *b = jl_get_module_binding(m, var); + return b == NULL ? NULL : jl_atomic_load_relaxed(&b->owner); } -// get owner of binding when accessing m.var, without resolving the binding -JL_DLLEXPORT jl_value_t *jl_binding_owner(jl_module_t *m, jl_sym_t *var) +// get the current likely owner of binding when accessing m.var, without resolving the binding (it may change later) +JL_DLLEXPORT jl_binding_t *jl_binding_owner(jl_module_t *m, jl_sym_t *var) { JL_LOCK(&m->lock); jl_binding_t *b = (jl_binding_t*)ptrhash_get(&m->bindings, var); - if (b == HT_NOTFOUND || b->owner == NULL) - b = using_resolve_binding(m, var, NULL, 0); + jl_module_t *from = m; + if (b == HT_NOTFOUND || (!b->usingfailed && jl_atomic_load_relaxed(&b->owner) == NULL)) + b = using_resolve_binding(m, var, &from, NULL, 0); + else + b = jl_atomic_load_relaxed(&b->owner); JL_UNLOCK(&m->lock); - if (b == NULL || b->owner == NULL) - return jl_nothing; - return (jl_value_t*)b->owner; + return b; } // get type of binding m.var, without resolving the binding JL_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var) { - JL_LOCK(&m->lock); - jl_binding_t *b = _jl_get_module_binding(m, var); - JL_UNLOCK(&m->lock); - if (b == HT_NOTFOUND || b->owner == NULL) + jl_binding_t *b = jl_get_module_binding(m, var); + if (b == NULL) + return jl_nothing; + b = jl_atomic_load_relaxed(&b->owner); + if (b == NULL) return jl_nothing; - b = jl_get_binding(m, var); jl_value_t *ty = jl_atomic_load_relaxed(&b->ty); return ty ? ty : jl_nothing; } @@ -412,7 +474,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_wr_or_error(jl_module_t *m, jl_sym_t * JL_DLLEXPORT jl_binding_t *jl_get_binding(jl_module_t *m, jl_sym_t *var) { - return jl_get_binding_(m, var, NULL); + return jl_resolve_owner(NULL, m, var, NULL); } JL_DLLEXPORT jl_binding_t *jl_get_binding_or_error(jl_module_t *m, jl_sym_t *var) @@ -420,53 +482,26 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_or_error(jl_module_t *m, jl_sym_t *var jl_binding_t *b = jl_get_binding(m, var); if (b == NULL) jl_undefined_var_error(var); + // XXX: this only considers if the original is deprecated, not the binding in m if (b->deprecated) - jl_binding_deprecation_warning(m, b); + jl_binding_deprecation_warning(m, var, b); return b; } -JL_DLLEXPORT jl_globalref_t *jl_new_globalref(jl_module_t *mod, jl_sym_t *name, jl_binding_t *b) -{ - jl_task_t *ct = jl_current_task; - jl_globalref_t *g = (jl_globalref_t *)jl_gc_alloc(ct->ptls, sizeof(jl_globalref_t), jl_globalref_type); - g->mod = mod; - jl_gc_wb(g, g->mod); - g->name = name; - g->bnd_cache = b; - return g; -} - JL_DLLEXPORT jl_value_t *jl_module_globalref(jl_module_t *m, jl_sym_t *var) { JL_LOCK(&m->lock); jl_binding_t *b = _jl_get_module_binding(m, var); if (b == HT_NOTFOUND) { - JL_UNLOCK(&m->lock); - return (jl_value_t *)jl_new_globalref(m, var, NULL); - } - jl_value_t *globalref = jl_atomic_load_relaxed(&b->globalref); - if (globalref == NULL) { - jl_value_t *newref = (jl_value_t *)jl_new_globalref(m, var, - !b->owner ? NULL : b->owner == m ? b : _jl_get_module_binding(b->owner, b->name)); - if (jl_atomic_cmpswap_relaxed(&b->globalref, &globalref, newref)) { - JL_GC_PROMISE_ROOTED(newref); - globalref = newref; - jl_gc_wb_binding(b, globalref); - } + b = new_binding(m, var); + ptrhash_put(&m->bindings, (void*)var, (void*)b); + jl_gc_wb(m, b); + JL_GC_PROMISE_ROOTED(b); } JL_UNLOCK(&m->lock); // may GC - return globalref; -} - -static int eq_bindings(jl_binding_t *a, jl_binding_t *b) -{ - if (a == b) - return 1; - if (a->name == b->name && a->owner == b->owner) - return 1; - if (a->constp && b->constp && jl_atomic_load_relaxed(&a->value) && jl_atomic_load_relaxed(&b->value) == jl_atomic_load_relaxed(&a->value)) - return 1; - return 0; + jl_globalref_t *globalref = b->globalref; + assert(globalref != NULL); + return (jl_value_t*)globalref; } // does module m explicitly import s? @@ -478,10 +513,62 @@ JL_DLLEXPORT int jl_is_imported(jl_module_t *m, jl_sym_t *s) return (b != HT_NOTFOUND && b->imported); } +extern const char *jl_filename; +extern int jl_lineno; + +static char const dep_message_prefix[] = "_dep_message_"; + +static void jl_binding_dep_message(jl_module_t *m, jl_sym_t *name, jl_binding_t *b) +{ + size_t prefix_len = strlen(dep_message_prefix); + size_t name_len = strlen(jl_symbol_name(name)); + char *dep_binding_name = (char*)alloca(prefix_len+name_len+1); + memcpy(dep_binding_name, dep_message_prefix, prefix_len); + memcpy(dep_binding_name + prefix_len, jl_symbol_name(name), name_len); + dep_binding_name[prefix_len+name_len] = '\0'; + jl_binding_t *dep_message_binding = jl_get_binding(m, jl_symbol(dep_binding_name)); + jl_value_t *dep_message = NULL; + if (dep_message_binding != NULL) + dep_message = jl_atomic_load_relaxed(&dep_message_binding->value); + JL_GC_PUSH1(&dep_message); + if (dep_message != NULL) { + if (jl_is_string(dep_message)) { + jl_uv_puts(JL_STDERR, jl_string_data(dep_message), jl_string_len(dep_message)); + } + else { + jl_static_show(JL_STDERR, dep_message); + } + } + else { + jl_value_t *v = jl_atomic_load_relaxed(&b->value); + dep_message = v; // use as gc-root + if (v) { + if (jl_is_type(v) || jl_is_module(v)) { + jl_printf(JL_STDERR, ", use "); + jl_static_show(JL_STDERR, v); + jl_printf(JL_STDERR, " instead."); + } + else { + jl_methtable_t *mt = jl_gf_mtable(v); + if (mt != NULL) { + jl_printf(JL_STDERR, ", use "); + if (mt->module != jl_core_module) { + jl_static_show(JL_STDERR, (jl_value_t*)mt->module); + jl_printf(JL_STDERR, "."); + } + jl_printf(JL_STDERR, "%s", jl_symbol_name(mt->name)); + jl_printf(JL_STDERR, " instead."); + } + } + } + } + jl_printf(JL_STDERR, "\n"); + JL_GC_POP(); +} + // NOTE: we use explici since explicit is a C++ keyword -static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname, int explici) +static void module_import_(jl_module_t *to, jl_module_t *from, jl_binding_t *b, jl_sym_t *asname, jl_sym_t *s, int explici) { - jl_binding_t *b = jl_get_binding(from, s); if (b == NULL) { jl_printf(JL_STDERR, "WARNING: could not import %s.%s into %s\n", @@ -489,8 +576,10 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_s jl_symbol_name(to->name)); } else { + assert(jl_atomic_load_relaxed(&b->owner) == b); if (b->deprecated) { if (jl_atomic_load_relaxed(&b->value) == jl_nothing) { + // silently skip importing deprecated values assigned to nothing (to allow later mutation) return; } else if (to != jl_main_module && to != jl_base_module && @@ -499,9 +588,12 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_s deprecated Base bindings should simply export the new binding */ jl_printf(JL_STDERR, - "WARNING: importing deprecated binding %s.%s into %s.\n", + "WARNING: importing deprecated binding %s.%s into %s%s%s.\n", jl_symbol_name(from->name), jl_symbol_name(s), - jl_symbol_name(to->name)); + jl_symbol_name(to->name), + asname == s ? "" : " as ", + asname == s ? "" : jl_symbol_name(asname)); + jl_binding_dep_message(from, s, b); } } @@ -509,10 +601,17 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_s jl_binding_t **bp = (jl_binding_t**)ptrhash_bp(&to->bindings, asname); jl_binding_t *bto = *bp; if (bto != HT_NOTFOUND) { + JL_GC_PROMISE_ROOTED(bto); + jl_binding_t *ownerto = jl_atomic_load_relaxed(&bto->owner); if (bto == b) { // importing a binding on top of itself. harmless. } - else if (bto->name != s) { + else if (eq_bindings(bto, b)) { + // already imported + bto->imported = (explici != 0); + } + else if (ownerto != b && ownerto != NULL) { + // already imported from somewhere else JL_UNLOCK(&to->lock); jl_printf(JL_STDERR, "WARNING: ignoring conflicting import of %s.%s into %s\n", @@ -520,53 +619,26 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_s jl_symbol_name(to->name)); return; } - else if (bto->owner == b->owner) { - // already imported - bto->imported = (explici!=0); - } - else if (bto->owner != to && bto->owner != NULL) { - // already imported from somewhere else - jl_binding_t *bval = jl_get_binding(to, asname); - if (bval->constp && b->constp && jl_atomic_load_relaxed(&bval->value) && jl_atomic_load_relaxed(&b->value) == jl_atomic_load_relaxed(&bval->value)) { - // equivalent binding - bto->imported = (explici!=0); - JL_UNLOCK(&to->lock); - } - else { - JL_UNLOCK(&to->lock); - jl_printf(JL_STDERR, - "WARNING: ignoring conflicting import of %s.%s into %s\n", - jl_symbol_name(from->name), jl_symbol_name(s), - jl_symbol_name(to->name)); - } - return; - } else if (bto->constp || jl_atomic_load_relaxed(&bto->value)) { // conflict with name owned by destination module - assert(bto->owner == to); - if (bto->constp && b->constp && jl_atomic_load_relaxed(&bto->value) && jl_atomic_load_relaxed(&b->value) == jl_atomic_load_relaxed(&bto->value)) { - // equivalent binding - JL_UNLOCK(&to->lock); - } - else { - JL_UNLOCK(&to->lock); - jl_printf(JL_STDERR, - "WARNING: import of %s.%s into %s conflicts with an existing identifier; ignored.\n", - jl_symbol_name(from->name), jl_symbol_name(s), - jl_symbol_name(to->name)); - } + assert(ownerto == bto); + JL_UNLOCK(&to->lock); + jl_printf(JL_STDERR, + "WARNING: import of %s.%s into %s conflicts with an existing identifier; ignored.\n", + jl_symbol_name(from->name), jl_symbol_name(s), + jl_symbol_name(to->name)); return; } else { - bto->owner = b->owner; - bto->imported = (explici!=0); + jl_atomic_store_release(&bto->owner, b); + bto->imported = (explici != 0); } } else { - jl_binding_t *nb = new_binding(b->name); - nb->owner = b->owner; - nb->imported = (explici!=0); - nb->deprecated = b->deprecated; + jl_binding_t *nb = new_binding(to, asname); + jl_atomic_store_relaxed(&nb->owner, b); + nb->imported = (explici != 0); + nb->deprecated = b->deprecated; // we already warned about this above, but we might want to warn at the use sites too *bp = nb; jl_gc_wb(to, nb); } @@ -576,22 +648,26 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_s JL_DLLEXPORT void jl_module_import(jl_module_t *to, jl_module_t *from, jl_sym_t *s) { - module_import_(to, from, s, s, 1); + jl_binding_t *b = jl_get_binding(from, s); + module_import_(to, from, b, s, s, 1); } JL_DLLEXPORT void jl_module_import_as(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname) { - module_import_(to, from, s, asname, 1); + jl_binding_t *b = jl_get_binding(from, s); + module_import_(to, from, b, asname, s, 1); } JL_DLLEXPORT void jl_module_use(jl_module_t *to, jl_module_t *from, jl_sym_t *s) { - module_import_(to, from, s, s, 0); + jl_binding_t *b = jl_get_binding(from, s); + module_import_(to, from, b, s, s, 0); } JL_DLLEXPORT void jl_module_use_as(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname) { - module_import_(to, from, s, asname, 0); + jl_binding_t *b = jl_get_binding(from, s); + module_import_(to, from, b, asname, s, 0); } JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from) @@ -599,7 +675,7 @@ JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from) if (to == from) return; JL_LOCK(&to->lock); - for(size_t i=0; i < to->usings.len; i++) { + for (size_t i = 0; i < to->usings.len; i++) { if (from == to->usings.items[i]) { JL_UNLOCK(&to->lock); return; @@ -611,17 +687,17 @@ JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from) // an existing identifier. note that an identifier added later may still // silently override a "using" name. see issue #2054. void **table = from->bindings.table; - for(size_t i=1; i < from->bindings.size; i+=2) { - if (table[i] != HT_NOTFOUND) { - jl_binding_t *b = (jl_binding_t*)table[i]; - if (b->exportp && (b->owner==from || b->imported)) { + for (size_t i = 1; i < from->bindings.size; i += 2) { + jl_binding_t *b = (jl_binding_t*)table[i]; + if (b != HT_NOTFOUND) { + if (b->exportp && (jl_atomic_load_relaxed(&b->owner) == b || b->imported)) { jl_sym_t *var = (jl_sym_t*)table[i-1]; jl_binding_t **tobp = (jl_binding_t**)ptrhash_bp(&to->bindings, var); - if (*tobp != HT_NOTFOUND && (*tobp)->owner != NULL && + if (*tobp != HT_NOTFOUND && jl_atomic_load_relaxed(&(*tobp)->owner) != NULL && // don't warn for conflicts with the module name itself. // see issue #4715 var != to->name && - !eq_bindings(jl_get_binding(to,var), b)) { + !eq_bindings(jl_get_binding(to, var), b)) { // TODO: not ideal to print this while holding module locks jl_printf(JL_STDERR, "WARNING: using %s.%s in module %s conflicts with an existing identifier.\n", @@ -643,9 +719,8 @@ JL_DLLEXPORT void jl_module_export(jl_module_t *from, jl_sym_t *s) JL_LOCK(&from->lock); jl_binding_t **bp = (jl_binding_t**)ptrhash_bp(&from->bindings, s); if (*bp == HT_NOTFOUND) { - jl_binding_t *b = new_binding(s); - // don't yet know who the owner is - b->owner = NULL; + jl_binding_t *b = new_binding(from, s); + // don't yet know who the owner will be *bp = b; jl_gc_wb(from, b); } @@ -665,23 +740,19 @@ JL_DLLEXPORT int jl_defines_or_exports_p(jl_module_t *m, jl_sym_t *var) JL_LOCK(&m->lock); jl_binding_t *b = (jl_binding_t*)ptrhash_get(&m->bindings, var); JL_UNLOCK(&m->lock); - return b != HT_NOTFOUND && (b->exportp || b->owner == m); + return b != HT_NOTFOUND && (b->exportp || jl_atomic_load_relaxed(&b->owner) == b); } JL_DLLEXPORT int jl_module_exports_p(jl_module_t *m, jl_sym_t *var) { - JL_LOCK(&m->lock); - jl_binding_t *b = _jl_get_module_binding(m, var); - JL_UNLOCK(&m->lock); - return b != HT_NOTFOUND && b->exportp; + jl_binding_t *b = jl_get_module_binding(m, var); + return b && b->exportp; } JL_DLLEXPORT int jl_binding_resolved_p(jl_module_t *m, jl_sym_t *var) { - JL_LOCK(&m->lock); - jl_binding_t *b = _jl_get_module_binding(m, var); - JL_UNLOCK(&m->lock); - return b != HT_NOTFOUND && b->owner != NULL; + jl_binding_t *b = jl_get_module_binding(m, var); + return b && jl_atomic_load_relaxed(&b->owner) != NULL; } JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var) @@ -693,23 +764,29 @@ JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m JL_PROPAGATES_RO } -JL_DLLEXPORT jl_value_t *jl_binding_value(jl_binding_t *b JL_PROPAGATES_ROOT) +JL_DLLEXPORT jl_value_t *jl_get_globalref_value(jl_globalref_t *gr) { - return jl_atomic_load_relaxed(&b->value); + jl_binding_t *b = gr->binding; + b = jl_resolve_owner(b, gr->mod, gr->name, NULL); + // ignores b->deprecated + return b == NULL ? NULL : jl_atomic_load_relaxed(&b->value); } JL_DLLEXPORT jl_value_t *jl_get_global(jl_module_t *m, jl_sym_t *var) { jl_binding_t *b = jl_get_binding(m, var); - if (b == NULL) return NULL; - if (b->deprecated) jl_binding_deprecation_warning(m, b); - return jl_binding_value(b); + if (b == NULL) + return NULL; + // XXX: this only considers if the original is deprecated, not the binding in m + if (b->deprecated) + jl_binding_deprecation_warning(m, var, b); + return jl_atomic_load_relaxed(&b->value); } JL_DLLEXPORT void jl_set_global(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT) { jl_binding_t *bp = jl_get_binding_wr(m, var, 1); - jl_checked_assignment(bp, val); + jl_checked_assignment(bp, m, var, val); } JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT) @@ -729,32 +806,34 @@ JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var } } } - jl_errorf("invalid redefinition of constant %s", - jl_symbol_name(bp->name)); + jl_errorf("invalid redefinition of constant %s", jl_symbol_name(var)); } -JL_DLLEXPORT int jl_binding_is_const(jl_binding_t *b) +JL_DLLEXPORT int jl_globalref_is_const(jl_globalref_t *gr) { - assert(b); - return b->constp; + jl_binding_t *b = gr->binding; + b = jl_resolve_owner(b, gr->mod, gr->name, NULL); + return b && b->constp; } -JL_DLLEXPORT int jl_binding_boundp(jl_binding_t *b) +JL_DLLEXPORT int jl_globalref_boundp(jl_globalref_t *gr) { - assert(b); - return jl_atomic_load_relaxed(&b->value) != NULL; + jl_binding_t *b = gr->binding; + b = jl_resolve_owner(b, gr->mod, gr->name, NULL); + return b && (jl_atomic_load_relaxed(&b->value) != NULL); } JL_DLLEXPORT int jl_is_const(jl_module_t *m, jl_sym_t *var) { jl_binding_t *b = jl_get_binding(m, var); - return b && jl_binding_is_const(b); + return b && b->constp; } // set the deprecated flag for a binding: // 0=not deprecated, 1=renamed, 2=moved to another package JL_DLLEXPORT void jl_deprecate_binding(jl_module_t *m, jl_sym_t *var, int flag) { + // XXX: this deprecates the original value, which might be imported from elsewhere jl_binding_t *b = jl_get_binding(m, var); if (b) b->deprecated = flag; } @@ -762,29 +841,14 @@ JL_DLLEXPORT void jl_deprecate_binding(jl_module_t *m, jl_sym_t *var, int flag) JL_DLLEXPORT int jl_is_binding_deprecated(jl_module_t *m, jl_sym_t *var) { if (jl_binding_resolved_p(m, var)) { + // XXX: this only considers if the original is deprecated, not this precise binding jl_binding_t *b = jl_get_binding(m, var); return b && b->deprecated; } return 0; } -extern const char *jl_filename; -extern int jl_lineno; - -static char const dep_message_prefix[] = "_dep_message_"; - -static jl_binding_t *jl_get_dep_message_binding(jl_module_t *m, jl_binding_t *deprecated_binding) -{ - size_t prefix_len = strlen(dep_message_prefix); - size_t name_len = strlen(jl_symbol_name(deprecated_binding->name)); - char *dep_binding_name = (char*)alloca(prefix_len+name_len+1); - memcpy(dep_binding_name, dep_message_prefix, prefix_len); - memcpy(dep_binding_name + prefix_len, jl_symbol_name(deprecated_binding->name), name_len); - dep_binding_name[prefix_len+name_len] = '\0'; - return jl_get_binding(m, jl_symbol(dep_binding_name)); -} - -void jl_binding_deprecation_warning(jl_module_t *m, jl_binding_t *b) +void jl_binding_deprecation_warning(jl_module_t *m, jl_sym_t *s, jl_binding_t *b) { // Only print a warning for deprecated == 1 (renamed). // For deprecated == 2 (moved to a package) the binding is to a function @@ -792,82 +856,34 @@ void jl_binding_deprecation_warning(jl_module_t *m, jl_binding_t *b) if (b->deprecated == 1 && jl_options.depwarn) { if (jl_options.depwarn != JL_OPTIONS_DEPWARN_ERROR) jl_printf(JL_STDERR, "WARNING: "); - jl_value_t *dep_message = NULL; - if (b->owner) { - jl_printf(JL_STDERR, "%s.%s is deprecated", - jl_symbol_name(b->owner->name), jl_symbol_name(b->name)); - jl_binding_t *dep_message_binding = jl_get_dep_message_binding(b->owner, b); - if (dep_message_binding != NULL) - dep_message = jl_atomic_load_relaxed(&dep_message_binding->value); - } - else { - jl_printf(JL_STDERR, "%s is deprecated", jl_symbol_name(b->name)); - } - - JL_GC_PUSH1(&dep_message); - if (dep_message != NULL) { - if (jl_is_string(dep_message)) { - jl_uv_puts(JL_STDERR, jl_string_data(dep_message), jl_string_len(dep_message)); - } - else { - jl_static_show(JL_STDERR, dep_message); - } - } - else { - jl_value_t *v = jl_atomic_load_relaxed(&b->value); - dep_message = v; - if (v) { - if (jl_is_type(v) || jl_is_module(v)) { - jl_printf(JL_STDERR, ", use "); - jl_static_show(JL_STDERR, v); - jl_printf(JL_STDERR, " instead."); - } - else { - jl_methtable_t *mt = jl_gf_mtable(v); - if (mt != NULL) { - jl_printf(JL_STDERR, ", use "); - if (mt->module != jl_core_module) { - jl_static_show(JL_STDERR, (jl_value_t*)mt->module); - jl_printf(JL_STDERR, "."); - } - jl_printf(JL_STDERR, "%s", jl_symbol_name(mt->name)); - jl_printf(JL_STDERR, " instead."); - } - } - } - } - jl_printf(JL_STDERR, "\n"); - JL_GC_POP(); + assert(jl_atomic_load_relaxed(&b->owner) == b); + jl_printf(JL_STDERR, "%s.%s is deprecated", + jl_symbol_name(m->name), jl_symbol_name(s)); + jl_binding_dep_message(m, s, b); if (jl_options.depwarn != JL_OPTIONS_DEPWARN_ERROR) { - if (jl_lineno == 0) { - jl_printf(JL_STDERR, " in module %s\n", jl_symbol_name(m->name)); - } - else { + if (jl_lineno != 0) { jl_printf(JL_STDERR, " likely near %s:%d\n", jl_filename, jl_lineno); } } if (jl_options.depwarn == JL_OPTIONS_DEPWARN_ERROR) { - if (b->owner) - jl_errorf("deprecated binding: %s.%s", - jl_symbol_name(b->owner->name), - jl_symbol_name(b->name)); - else - jl_errorf("deprecated binding: %s", jl_symbol_name(b->name)); + jl_errorf("use of deprecated variable: %s.%s", + jl_symbol_name(m->name), + jl_symbol_name(s)); } } } -JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_value_t *rhs) +JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs) { jl_value_t *old_ty = NULL; if (!jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type)) { if (old_ty != (jl_value_t*)jl_any_type && jl_typeof(rhs) != old_ty) { - JL_GC_PUSH1(&rhs); + JL_GC_PUSH1(&rhs); // callee-rooted if (!jl_isa(rhs, old_ty)) - jl_errorf("cannot assign an incompatible value to the global %s.", - jl_symbol_name(b->name)); + jl_errorf("cannot assign an incompatible value to the global %s.%s.", + jl_symbol_name(mod->name), jl_symbol_name(var)); JL_GC_POP(); } } @@ -880,36 +896,39 @@ JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_value_t *rhs) if (jl_egal(rhs, old)) return; if (jl_typeof(rhs) != jl_typeof(old) || jl_is_type(rhs) || jl_is_module(rhs)) { - jl_errorf("invalid redefinition of constant %s", - jl_symbol_name(b->name)); + jl_errorf("invalid redefinition of constant %s.%s", + jl_symbol_name(mod->name), jl_symbol_name(var)); + } - jl_safe_printf("WARNING: redefinition of constant %s. This may fail, cause incorrect answers, or produce other errors.\n", - jl_symbol_name(b->name)); + jl_safe_printf("WARNING: redefinition of constant %s.%s. This may fail, cause incorrect answers, or produce other errors.\n", + jl_symbol_name(mod->name), jl_symbol_name(var)); } jl_atomic_store_release(&b->value, rhs); jl_gc_wb_binding(b, rhs); } -JL_DLLEXPORT void jl_declare_constant(jl_binding_t *b) +JL_DLLEXPORT void jl_declare_constant(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var) { - if (jl_atomic_load_relaxed(&b->value) != NULL && !b->constp) { - jl_errorf("cannot declare %s constant; it already has a value", - jl_symbol_name(b->name)); + // n.b. jl_get_binding_wr should have ensured b->owner == b as mod.var + if (jl_atomic_load_relaxed(&b->owner) != b || (jl_atomic_load_relaxed(&b->value) != NULL && !b->constp)) { + jl_errorf("cannot declare %s.%s constant; it already has a value", + jl_symbol_name(mod->name), jl_symbol_name(var)); } b->constp = 1; } JL_DLLEXPORT jl_value_t *jl_module_usings(jl_module_t *m) { - jl_array_t *a = jl_alloc_array_1d(jl_array_any_type, 0); - JL_GC_PUSH1(&a); JL_LOCK(&m->lock); - for(int i=(int)m->usings.len-1; i >= 0; --i) { - jl_array_grow_end(a, 1); + int j = m->usings.len; + jl_array_t *a = jl_alloc_array_1d(jl_array_any_type, j); + JL_GC_PUSH1(&a); + for (int i = 0; j > 0; i++) { + j--; jl_module_t *imp = (jl_module_t*)m->usings.items[i]; - jl_array_ptr_set(a,jl_array_dim0(a)-1, (jl_value_t*)imp); + jl_array_ptr_set(a, j, (jl_value_t*)imp); } - JL_UNLOCK(&m->lock); + JL_UNLOCK(&m->lock); // may gc JL_GC_POP(); return (jl_value_t*)a; } @@ -921,18 +940,18 @@ JL_DLLEXPORT jl_value_t *jl_module_names(jl_module_t *m, int all, int imported) size_t i; JL_LOCK(&m->lock); void **table = m->bindings.table; - for (i = 1; i < m->bindings.size; i+=2) { - if (table[i] != HT_NOTFOUND) { - jl_binding_t *b = (jl_binding_t*)table[i]; - int hidden = jl_symbol_name(b->name)[0]=='#'; + for (i = 0; i < m->bindings.size; i+=2) { + jl_binding_t *b = (jl_binding_t*)table[i+1]; + if (b != HT_NOTFOUND) { + jl_sym_t *asname = (jl_sym_t*)table[i]; + int hidden = jl_symbol_name(asname)[0]=='#'; if ((b->exportp || (imported && b->imported) || - (b->owner == m && !b->imported && (all || m == jl_main_module))) && + (jl_atomic_load_relaxed(&b->owner) == b && !b->imported && (all || m == jl_main_module))) && (all || (!b->deprecated && !hidden))) { - jl_sym_t *in_module_name = (jl_sym_t*)table[i-1]; // the name in the module may not be b->name, use the httable key instead jl_array_grow_end(a, 1); - //XXX: change to jl_arrayset if array storage allocation for Array{Symbols,1} changes: - jl_array_ptr_set(a, jl_array_dim0(a)-1, (jl_value_t*)in_module_name); + // n.b. change to jl_arrayset if array storage allocation for Array{Symbols,1} changes: + jl_array_ptr_set(a, jl_array_dim0(a)-1, (jl_value_t*)asname); } } } @@ -969,11 +988,11 @@ JL_DLLEXPORT void jl_clear_implicit_imports(jl_module_t *m) size_t i; JL_LOCK(&m->lock); void **table = m->bindings.table; - for (i = 1; i < m->bindings.size; i+=2) { + for (i = 1; i < m->bindings.size; i += 2) { if (table[i] != HT_NOTFOUND) { jl_binding_t *b = (jl_binding_t*)table[i]; - if (b->owner != m && !b->imported) - table[i] = HT_NOTFOUND; + if (jl_atomic_load_relaxed(&b->owner) && jl_atomic_load_relaxed(&b->owner) != b && !b->imported) + jl_atomic_store_relaxed(&b->owner, NULL); } } JL_UNLOCK(&m->lock); diff --git a/src/staticdata.c b/src/staticdata.c index 929c44273a6ef3..f4ba300a37c04f 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -295,7 +295,6 @@ static htable_t external_objects; static htable_t serialization_order; // to break cycles, mark all objects that are serialized static htable_t unique_ready; // as we serialize types, we need to know if all reachable objects are also already serialized. This tracks whether `immediate` has been set for all of them. static htable_t nullptrs; -static htable_t bindings; // because they are not first-class objects // FIFO queue for objects to be serialized. Anything requiring fixup upon deserialization // must be "toplevel" in this queue. For types, parameters and field types must appear // before the "wrapper" type so they can be properly recached against the running system. @@ -606,19 +605,12 @@ static void jl_queue_module_for_serialization(jl_serializer_state *s, jl_module_ void **table = m->bindings.table; for (i = 0; i < m->bindings.size; i += 2) { if (table[i+1] != HT_NOTFOUND) { - jl_queue_for_serialization(s, (jl_value_t*)table[i]); + jl_sym_t *name = (jl_sym_t*)table[i]; + jl_queue_for_serialization(s, (jl_value_t*)name); jl_binding_t *b = (jl_binding_t*)table[i+1]; - ptrhash_put(&bindings, b, (void*)(uintptr_t)-1); - jl_queue_for_serialization(s, b->name); - jl_value_t *value; - if (jl_docmeta_sym && b->name == jl_docmeta_sym && jl_options.strip_metadata) - value = jl_nothing; - else - value = get_replaceable_field((jl_value_t**)&b->value, !b->constp); - jl_queue_for_serialization(s, value); - jl_queue_for_serialization(s, jl_atomic_load_relaxed(&b->globalref)); - jl_queue_for_serialization(s, b->owner); - jl_queue_for_serialization(s, jl_atomic_load_relaxed(&b->ty)); + if (name == jl_docmeta_sym && jl_atomic_load_relaxed(&b->value) && jl_options.strip_metadata) + record_field_change((jl_value_t**)&b->value, jl_nothing); + jl_queue_for_serialization(s, jl_module_globalref(m, name)); } } @@ -659,9 +651,9 @@ static void jl_insert_into_serialization_queue(jl_serializer_state *s, jl_value_ immediate = 0; // do not handle remaining fields immediately (just field types remains) } if (s->incremental && jl_is_method_instance(v)) { + jl_method_instance_t *mi = (jl_method_instance_t*)v; if (needs_uniquing(v)) { // we only need 3 specific fields of this (the rest are not used) - jl_method_instance_t *mi = (jl_method_instance_t*)v; jl_queue_for_serialization(s, mi->def.value); jl_queue_for_serialization(s, mi->specTypes); jl_queue_for_serialization(s, (jl_value_t*)mi->sparam_vals); @@ -670,13 +662,18 @@ static void jl_insert_into_serialization_queue(jl_serializer_state *s, jl_value_ } else if (needs_recaching(v)) { // we only need 3 specific fields of this (the rest are restored afterward, if valid) - jl_method_instance_t *mi = (jl_method_instance_t*)v; record_field_change((jl_value_t**)&mi->uninferred, NULL); record_field_change((jl_value_t**)&mi->backedges, NULL); record_field_change((jl_value_t**)&mi->callbacks, NULL); record_field_change((jl_value_t**)&mi->cache, NULL); } } + if (s->incremental && jl_is_globalref(v)) { + jl_globalref_t *gr = (jl_globalref_t*)v; + if (jl_object_in_image((jl_value_t*)gr->mod)) { + record_field_change((jl_value_t**)&gr->binding, NULL); + } + } if (jl_is_typename(v)) { jl_typename_t *tn = (jl_typename_t*)v; // don't recurse into several fields (yet) @@ -735,7 +732,10 @@ static void jl_insert_into_serialization_queue(jl_serializer_state *s, jl_value_ size_t i, np = layout->npointers; for (i = 0; i < np; i++) { uint32_t ptr = jl_ptr_offset(t, i); - jl_value_t *fld = get_replaceable_field(&((jl_value_t**)data)[ptr], t->name->mutabl); + int mutabl = t->name->mutabl; + if (jl_is_binding(v) && ((jl_binding_t*)v)->constp && i == 0) // value field depends on constp field + mutabl = 0; + jl_value_t *fld = get_replaceable_field(&((jl_value_t**)data)[ptr], mutabl); jl_queue_for_serialization_(s, fld, 1, immediate); } } @@ -1001,26 +1001,10 @@ static void jl_write_module(jl_serializer_state *s, uintptr_t item, jl_module_t void **table = m->bindings.table; for (i = 0; i < m->bindings.size; i += 2) { if (table[i+1] != HT_NOTFOUND) { - jl_binding_t *b = (jl_binding_t*)table[i+1]; - write_pointerfield(s, (jl_value_t*)table[i]); - tot += sizeof(void*); - write_gctaggedfield(s, jl_binding_type); + jl_globalref_t *gr = ((jl_binding_t*)table[i+1])->globalref; + assert(gr && gr->mod == m && gr->name == (jl_sym_t*)table[i]); + write_pointerfield(s, (jl_value_t*)gr); tot += sizeof(void*); - size_t binding_reloc_offset = ios_pos(s->s); - ptrhash_put(&bindings, b, (void*)(((uintptr_t)DataRef << RELOC_TAG_OFFSET) + binding_reloc_offset)); - write_pointerfield(s, (jl_value_t*)b->name); - jl_value_t *value; - if (jl_docmeta_sym && b->name == jl_docmeta_sym && jl_options.strip_metadata) - value = jl_nothing; - else - value = get_replaceable_field((jl_value_t**)&b->value, !b->constp); - write_pointerfield(s, value); - write_pointerfield(s, jl_atomic_load_relaxed(&b->globalref)); - write_pointerfield(s, (jl_value_t*)b->owner); - write_pointerfield(s, jl_atomic_load_relaxed(&b->ty)); - size_t flag_offset = offsetof(jl_binding_t, ty) + sizeof(b->ty); - ios_write(s->s, (char*)b + flag_offset, sizeof(*b) - flag_offset); - tot += sizeof(jl_binding_t); count += 1; } } @@ -1060,21 +1044,8 @@ static void jl_write_module(jl_serializer_state *s, uintptr_t item, jl_module_t static void record_gvars(jl_serializer_state *s, arraylist_t *globals) JL_NOTSAFEPOINT { - for (size_t i = 0; i < globals->len; i++) { - void *g = globals->items[i]; - if (jl_is_binding((uintptr_t)g)) { - if (!ptrhash_has(&bindings, g)) { - // need to deal with foreign bindings here too - assert(s->incremental); - jl_binding_t *b = (jl_binding_t*)g; - jl_value_t *gr = jl_module_globalref(b->owner, b->name); - jl_queue_for_serialization(s, gr); - } - continue; - } - assert(!ptrhash_has(&bindings, g)); - jl_queue_for_serialization(s, g); - } + for (size_t i = 0; i < globals->len; i++) + jl_queue_for_serialization(s, globals->items[i]); } static void record_external_fns(jl_serializer_state *s, arraylist_t *external_fns) JL_NOTSAFEPOINT @@ -1141,6 +1112,11 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED else if (s->incremental && needs_recaching(v)) { arraylist_push(jl_is_datatype(v) ? &s->fixup_types : &s->fixup_objs, (void*)reloc_offset); } + else if (s->incremental && jl_typeis(v, jl_binding_type)) { + jl_binding_t *b = (jl_binding_t*)v; + if (b->globalref == NULL || jl_object_in_image((jl_value_t*)b->globalref->mod)) + jl_error("Binding cannot be serialized"); // no way (currently) to recover its identity + } // write data if (jl_is_array(v)) { @@ -1251,9 +1227,6 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED else if (jl_typeis(v, jl_task_type)) { jl_error("Task cannot be serialized"); } - else if (jl_typeis(v, jl_binding_type)) { - jl_error("Binding cannot be serialized"); // no way (currently) to recover its identity - } else if (jl_is_svec(v)) { ios_write(s->s, (char*)v, sizeof(void*)); size_t ii, l = jl_svec_len(v); @@ -1318,7 +1291,10 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED size_t np = t->layout->npointers; for (i = 0; i < np; i++) { size_t offset = jl_ptr_offset(t, i) * sizeof(jl_value_t*); - jl_value_t *fld = get_replaceable_field((jl_value_t**)&data[offset], t->name->mutabl); + int mutabl = t->name->mutabl; + if (jl_is_binding(v) && ((jl_binding_t*)v)->constp && i == 0) // value field depends on constp field + mutabl = 0; + jl_value_t *fld = get_replaceable_field((jl_value_t**)&data[offset], mutabl); size_t fld_pos = offset + reloc_offset; if (fld != NULL) { arraylist_push(&s->relocs_list, (void*)(uintptr_t)(fld_pos)); // relocation location @@ -1443,15 +1419,6 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED arraylist_push(&s->relocs_list, (void*)(((uintptr_t)BuiltinFunctionRef << RELOC_TAG_OFFSET) + builtin_id - 2)); // relocation target } } - else if (jl_is_globalref(v)) { - jl_globalref_t *newg = (jl_globalref_t*)&s->s->buf[reloc_offset]; - // Don't save the cached binding reference in staticdata - // (it does not happen automatically since we declare the struct immutable) - // TODO: this should be a relocation pointing to the binding in the new image - newg->bnd_cache = NULL; - if (s->incremental) - arraylist_push(&s->fixup_objs, (void*)reloc_offset); - } else if (jl_is_datatype(v)) { jl_datatype_t *dt = (jl_datatype_t*)v; jl_datatype_t *newdt = (jl_datatype_t*)&s->s->buf[reloc_offset]; @@ -1508,6 +1475,13 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED ios_write(s->const_data, (char*)tn->constfields, nb); } } + else if (jl_is_globalref(v)) { + jl_globalref_t *gr = (jl_globalref_t*)v; + if (s->incremental && jl_object_in_image((jl_value_t*)gr->mod)) { + // will need to populate the binding field later + arraylist_push(&s->fixup_objs, (void*)reloc_offset); + } + } else if (((jl_datatype_t*)(jl_typeof(v)))->name == jl_idtable_typename) { // will need to rehash this, later (after types are fully constructed) arraylist_push(&s->fixup_objs, (void*)reloc_offset); @@ -1550,7 +1524,7 @@ static uintptr_t get_reloc_for_item(uintptr_t reloc_item, size_t reloc_offset) uintptr_t reloc_base = (uintptr_t)layout_table.items[reloc_item]; assert(reloc_base != 0 && "layout offset missing for relocation item"); // write reloc_offset into s->s at pos - return reloc_base + reloc_offset; + return ((uintptr_t)tag << RELOC_TAG_OFFSET) + reloc_base + reloc_offset; } else { // just write the item reloc_id directly @@ -1574,7 +1548,6 @@ static uintptr_t get_reloc_for_item(uintptr_t reloc_item, size_t reloc_offset) break; case ExternalLinkage: break; - case DataRef: default: assert(0 && "corrupt relocation item id"); abort(); @@ -1904,19 +1877,6 @@ static uint32_t write_gvars(jl_serializer_state *s, arraylist_t *globals, arrayl ios_ensureroom(s->gvar_record, len * sizeof(reloc_t)); for (size_t i = 0; i < globals->len; i++) { void *g = globals->items[i]; - if (jl_is_binding((uintptr_t)g)) { - jl_binding_t *b = (jl_binding_t*)g; - void *reloc = ptrhash_get(&bindings, g); - if (reloc != HT_NOTFOUND) { - assert(reloc != (void*)(uintptr_t)-1); - write_reloc_t(s->gvar_record, (uintptr_t)reloc); - continue; - } - // need to deal with foreign bindings here too - assert(s->incremental); - arraylist_push(&s->uniquing_objs, (void*)((i << 2) | 2)); // mark as gvar && !tag - g = (void*)jl_module_globalref(b->owner, b->name); - } uintptr_t item = backref_id(s, g, s->link_ids_gvars); uintptr_t reloc = get_reloc_for_item(item, 0); write_reloc_t(s->gvar_record, reloc); @@ -2297,7 +2257,6 @@ static void jl_save_system_image_to_stream(ios_t *f, htable_new(&serialization_order, 25000); htable_new(&unique_ready, 0); htable_new(&nullptrs, 0); - htable_new(&bindings, 0); arraylist_new(&object_worklist, 0); arraylist_new(&serialization_queue, 0); ios_t sysimg, const_data, symbols, relocs, gvar_record, fptr_record; @@ -2401,7 +2360,7 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_queue_for_serialization(&s, edges); } jl_serialize_reachable(&s); - // step 1.2: now that we have marked all bindings (badly), ensure all gvars are part of the sysimage + // step 1.2: ensure all gvars are part of the sysimage too record_gvars(&s, &gvars); record_external_fns(&s, &external_fns); jl_serialize_reachable(&s); @@ -2548,7 +2507,6 @@ static void jl_save_system_image_to_stream(ios_t *f, htable_free(&serialization_order); htable_free(&unique_ready); htable_free(&nullptrs); - htable_free(&bindings); htable_free(&symbol_table); htable_free(&fptr_to_id); nsym_tag = 0; @@ -3065,13 +3023,6 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl obj[0] = newobj; } } - else if (otyp == (jl_value_t*)jl_globalref_type) { - // this actually needs a binding_t object at that gvar slot if we encountered it in the uniquing_objs - jl_globalref_t *g = (jl_globalref_t*)obj; - jl_binding_t *b = jl_get_binding_if_bound(g->mod, g->name); - assert(b); // XXX: actually this is probably quite buggy, since julia's handling of global resolution is rather bad - newobj = (jl_value_t*)b; - } else { abort(); // should be unreachable } @@ -3128,24 +3079,22 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl } else if (jl_is_module(obj)) { // rebuild the binding table for module v - // TODO: maybe want to delay this more, but that only strongly matters for async / thread safety + // TODO: maybe want to hold the lock on `v`, but that only strongly matters for async / thread safety // and we are already bad at that jl_module_t *mod = (jl_module_t*)obj; mod->build_id.hi = checksum; size_t nbindings = mod->bindings.size; htable_new(&mod->bindings, nbindings); - struct binding { - jl_sym_t *asname; - uintptr_t tag; - jl_binding_t b; - } *b; - b = (struct binding*)&mod[1]; - while (nbindings > 0) { - ptrhash_put(&mod->bindings, b->asname, &b->b); - b += 1; - nbindings -= 1; + jl_globalref_t **pgr = (jl_globalref_t**)&mod[1]; + for (size_t i = 0; i < nbindings; i++) { + jl_globalref_t *gr = pgr[i]; + jl_binding_t *b = gr->binding; + assert(gr->mod == mod); + assert(b && (!b->globalref || b->globalref->mod == mod)); + ptrhash_put(&mod->bindings, gr->name, b); } if (mod->usings.items != &mod->usings._space[0]) { + // arraylist_t assumes we called malloc to get this memory, so make that true now void **newitems = (void**)malloc_s(mod->usings.max * sizeof(void*)); memcpy(newitems, mod->usings.items, mod->usings.len * sizeof(void*)); mod->usings.items = newitems; @@ -3160,14 +3109,17 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl jl_gc_wb(obj, *a); } } - // Now pick up the globalref binding pointer field, when we can + // Now pick up the globalref binding pointer field for (size_t i = 0; i < s.fixup_objs.len; i++) { uintptr_t item = (uintptr_t)s.fixup_objs.items[i]; jl_value_t *obj = (jl_value_t*)(image_base + item); if (jl_is_globalref(obj)) { jl_globalref_t *r = (jl_globalref_t*)obj; - jl_binding_t *b = jl_get_binding_if_bound(r->mod, r->name); - r->bnd_cache = b && b->value ? b : NULL; + if (r->binding == NULL) { + jl_globalref_t *gr = (jl_globalref_t*)jl_module_globalref(r->mod, r->name); + r->binding = gr->binding; + jl_gc_wb(r, gr->binding); + } } } arraylist_free(&s.fixup_types); diff --git a/src/staticdata_utils.c b/src/staticdata_utils.c index cb107b06a95850..cbebe239808ed9 100644 --- a/src/staticdata_utils.c +++ b/src/staticdata_utils.c @@ -393,14 +393,15 @@ static void jl_collect_extext_methods_from_mod(jl_array_t *s, jl_module_t *m) s = NULL; // do not collect any methods size_t i; void **table = m->bindings.table; - for (i = 1; i < m->bindings.size; i += 2) { - if (table[i] != HT_NOTFOUND) { - jl_binding_t *b = (jl_binding_t*)table[i]; - if (b->owner == m && b->value && b->constp) { + for (i = 0; i < m->bindings.size; i += 2) { + if (table[i+1] != HT_NOTFOUND) { + jl_sym_t *name = (jl_sym_t*)table[i]; + jl_binding_t *b = (jl_binding_t*)table[i+1]; + if (b->owner == b && b->value && b->constp) { jl_value_t *bv = jl_unwrap_unionall(b->value); if (jl_is_datatype(bv)) { jl_typename_t *tn = ((jl_datatype_t*)bv)->name; - if (tn->module == m && tn->name == b->name && tn->wrapper == b->value) { + if (tn->module == m && tn->name == name && tn->wrapper == b->value) { jl_methtable_t *mt = tn->mt; if (mt != NULL && (jl_value_t*)mt != jl_nothing && @@ -414,14 +415,14 @@ static void jl_collect_extext_methods_from_mod(jl_array_t *s, jl_module_t *m) } else if (jl_is_module(b->value)) { jl_module_t *child = (jl_module_t*)b->value; - if (child != m && child->parent == m && child->name == b->name) { + if (child != m && child->parent == m && child->name == name) { // this is the original/primary binding for the submodule jl_collect_extext_methods_from_mod(s, (jl_module_t*)b->value); } } else if (jl_is_mtable(b->value)) { jl_methtable_t *mt = (jl_methtable_t*)b->value; - if (mt->module == m && mt->name == b->name) { + if (mt->module == m && mt->name == name) { // this is probably an external method table, so let's assume so // as there is no way to precisely distinguish them, // and the rest of this serializer does not bother diff --git a/src/toplevel.c b/src/toplevel.c index 0b0b819a723a23..5e31167a2879a3 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -155,7 +155,7 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex } else { jl_binding_t *b = jl_get_binding_wr(parent_module, name, 1); - jl_declare_constant(b); + jl_declare_constant(b, parent_module, name); jl_value_t *old = NULL; if (!jl_atomic_cmpswap(&b->value, &old, (jl_value_t*)newm)) { if (!jl_is_module(old)) { @@ -214,11 +214,11 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex jl_binding_t *b = (jl_binding_t*)table[i]; // remove non-exported macros if (jl_symbol_name(b->name)[0]=='@' && - !b->exportp && b->owner == newm) + !b->exportp && b->owner == b) b->value = NULL; // error for unassigned exports /* - if (b->exportp && b->owner==newm && b->value==NULL) + if (b->exportp && b->owner==b && b->value==NULL) jl_errorf("identifier %s exported from %s is not initialized", jl_symbol_name(b->name), jl_symbol_name(newm->name)); */ @@ -589,25 +589,22 @@ static void import_module(jl_module_t *JL_NONNULL m, jl_module_t *import, jl_sym { assert(m); jl_sym_t *name = asname ? asname : import->name; - jl_binding_t *b; - if (jl_binding_resolved_p(m, name)) { - b = jl_get_binding(m, name); - jl_value_t *bv = jl_atomic_load_relaxed(&b->value); - if ((!b->constp && b->owner != m) || (bv && bv != (jl_value_t*)import)) { + // TODO: this is a bit race-y with what error message we might print + jl_binding_t *b = jl_get_module_binding(m, name); + jl_binding_t *b2; + if (b != NULL && (b2 = jl_atomic_load_relaxed(&b->owner)) != NULL) { + if (b2->constp && jl_atomic_load_relaxed(&b2->value) == (jl_value_t*)import) + return; + if (b2 != b) jl_errorf("importing %s into %s conflicts with an existing global", jl_symbol_name(name), jl_symbol_name(m->name)); - } } else { b = jl_get_binding_wr(m, name, 1); - b->imported = 1; - } - if (!b->constp) { - // TODO: constp is not threadsafe - jl_atomic_store_release(&b->value, (jl_value_t*)import); - b->constp = 1; - jl_gc_wb(m, (jl_value_t*)import); } + jl_declare_constant(b, m, name); + jl_checked_assignment(b, m, name, (jl_value_t*)import); + b->imported = 1; } // in `import A.B: x, y, ...`, evaluate the `A.B` part if it exists @@ -845,7 +842,7 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int gs = (jl_sym_t*)arg; } jl_binding_t *b = jl_get_binding_wr(gm, gs, 1); - jl_declare_constant(b); + jl_declare_constant(b, gm, gs); JL_GC_POP(); return jl_nothing; } diff --git a/test/atomics.jl b/test/atomics.jl index e93afc3bff2c26..dd50fb96be49f8 100644 --- a/test/atomics.jl +++ b/test/atomics.jl @@ -22,19 +22,20 @@ mutable struct Refxy{T} Refxy{T}() where {T} = new() # unused, but sets ninitialized to 0 end -@test_throws ErrorException("invalid redefinition of constant ARefxy") @eval mutable struct ARefxy{T} +modname = String(nameof(@__MODULE__)) +@test_throws ErrorException("invalid redefinition of constant $modname.ARefxy") @eval mutable struct ARefxy{T} @atomic x::T @atomic y::T end -@test_throws ErrorException("invalid redefinition of constant ARefxy") @eval mutable struct ARefxy{T} +@test_throws ErrorException("invalid redefinition of constant $modname.ARefxy") @eval mutable struct ARefxy{T} x::T y::T end -@test_throws ErrorException("invalid redefinition of constant ARefxy") @eval mutable struct ARefxy{T} +@test_throws ErrorException("invalid redefinition of constant $modname.ARefxy") @eval mutable struct ARefxy{T} x::T @atomic y::T end -@test_throws ErrorException("invalid redefinition of constant Refxy") @eval mutable struct Refxy{T} +@test_throws ErrorException("invalid redefinition of constant $modname.Refxy") @eval mutable struct Refxy{T} x::T @atomic y::T end diff --git a/test/core.jl b/test/core.jl index e6ad8705d4e2ba..a6926860ed8db6 100644 --- a/test/core.jl +++ b/test/core.jl @@ -57,14 +57,14 @@ mutable struct ABCDconst c const d::Union{Int,Nothing} end -@test_throws(ErrorException("invalid redefinition of constant ABCDconst"), +@test_throws(ErrorException("invalid redefinition of constant $(nameof(curmod)).ABCDconst"), mutable struct ABCDconst const a const b::Int c d::Union{Int,Nothing} end) -@test_throws(ErrorException("invalid redefinition of constant ABCDconst"), +@test_throws(ErrorException("invalid redefinition of constant $(nameof(curmod)).ABCDconst"), mutable struct ABCDconst a b::Int @@ -5963,7 +5963,7 @@ module GlobalDef18933 global sincos nothing end - @test which(Main, :sincos) === Base.Math + @test which(@__MODULE__, :sincos) === Base.Math @test @isdefined sincos @test sincos === Base.sincos end @@ -7535,7 +7535,7 @@ end struct X36104; x::Int; end @test fieldtypes(X36104) == (Int,) primitive type P36104 8 end -@test_throws ErrorException("invalid redefinition of constant P36104") @eval(primitive type P36104 16 end) +@test_throws ErrorException("invalid redefinition of constant $(nameof(curmod)).P36104") @eval(primitive type P36104 16 end) # Malformed invoke f_bad_invoke(x::Int) = invoke(x, (Any,), x) diff --git a/test/syntax.jl b/test/syntax.jl index fe9f6c43332e50..1fb040c7cbdac9 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -2537,7 +2537,8 @@ using Test module Mod const x = 1 -global maybe_undef +global maybe_undef, always_undef +export always_undef def() = (global maybe_undef = 0) func(x) = 2x + 1 @@ -2575,10 +2576,18 @@ import .Mod.maybe_undef as mu Mod.def() @test mu === 0 -using .Mod: func as f -@test f(10) == 21 -@test !@isdefined(func) -@test_throws ErrorException("error in method definition: function Mod.func must be explicitly imported to be extended") eval(:(f(x::Int) = x)) +module Mod3 +using ..Mod: func as f +using ..Mod +end +@test Mod3.f(10) == 21 +@test !isdefined(Mod3, :func) +@test_throws ErrorException("invalid method definition in Mod3: function Mod3.f must be explicitly imported to be extended") Core.eval(Mod3, :(f(x::Int) = x)) +@test !isdefined(Mod3, :always_undef) # resolve this binding now in Mod3 +@test_throws ErrorException("invalid method definition in Mod3: exported function Mod.always_undef does not exist") Core.eval(Mod3, :(always_undef(x::Int) = x)) +@test_throws ErrorException("cannot assign a value to imported variable Mod.always_undef from module Mod3") Core.eval(Mod3, :(const always_undef = 3)) +@test_throws ErrorException("cannot assign a value to imported variable Mod3.f") Core.eval(Mod3, :(const f = 3)) +@test_throws ErrorException("cannot declare Mod.maybe_undef constant; it already has a value") Core.eval(Mod, :(const maybe_undef = 3)) z = 42 import .z as also_z From fae53d0b4dc3e7715b108bd29cb178f2109caf62 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Mon, 9 Jan 2023 17:17:31 -0500 Subject: [PATCH 270/387] binding: drop lock around owner field access The owner field is now handled with cmpswap, so we can avoid needing a module lock for it. This now means we only need the module lock for handling the bindings and usings fields. --- doc/src/manual/embedding.md | 2 +- src/ast.c | 2 +- src/builtins.c | 6 +- src/codegen.cpp | 21 ++- src/dlload.c | 2 +- src/interpreter.c | 2 +- src/jl_exported_funcs.inc | 2 - src/julia.h | 3 +- src/julia_internal.h | 2 +- src/module.c | 366 +++++++++++++++--------------------- src/rtutils.c | 10 +- src/toplevel.c | 10 +- 12 files changed, 186 insertions(+), 242 deletions(-) diff --git a/doc/src/manual/embedding.md b/doc/src/manual/embedding.md index cbad9955ab1902..9b8a67bb8c4c2e 100644 --- a/doc/src/manual/embedding.md +++ b/doc/src/manual/embedding.md @@ -408,7 +408,7 @@ per pointer using ```c jl_module_t *mod = jl_main_module; jl_sym_t *var = jl_symbol("var"); -jl_binding_t *bp = jl_get_binding_wr(mod, var, 1); +jl_binding_t *bp = jl_get_binding_wr(mod, var); jl_checked_assignment(bp, mod, var, val); ``` diff --git a/src/ast.c b/src/ast.c index d0934d9eb82658..0ff7c882ab8e73 100644 --- a/src/ast.c +++ b/src/ast.c @@ -156,7 +156,7 @@ static value_t fl_defined_julia_global(fl_context_t *fl_ctx, value_t *args, uint (void)tosymbol(fl_ctx, args[0], "defined-julia-global"); jl_ast_context_t *ctx = jl_ast_ctx(fl_ctx); jl_sym_t *var = jl_symbol(symbol_name(fl_ctx, args[0])); - jl_binding_t *b = jl_get_module_binding(ctx->module, var); + jl_binding_t *b = jl_get_module_binding(ctx->module, var, 0); return (b != NULL && jl_atomic_load_relaxed(&b->owner) == b) ? fl_ctx->T : fl_ctx->F; } diff --git a/src/builtins.c b/src/builtins.c index 80a273a9a7d576..b090e952cc1cf2 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1220,7 +1220,7 @@ JL_CALLABLE(jl_f_setglobal) if (order == jl_memory_order_notatomic) jl_atomic_error("setglobal!: module binding cannot be written non-atomically"); // is seq_cst already, no fence needed - jl_binding_t *b = jl_get_binding_wr_or_error(mod, var); + jl_binding_t *b = jl_get_binding_wr(mod, var); jl_checked_assignment(b, mod, var, args[2]); return args[2]; } @@ -1234,7 +1234,7 @@ JL_CALLABLE(jl_f_get_binding_type) JL_TYPECHK(get_binding_type, symbol, (jl_value_t*)var); jl_value_t *ty = jl_get_binding_type(mod, var); if (ty == (jl_value_t*)jl_nothing) { - jl_binding_t *b = jl_get_binding_wr(mod, var, 0); + jl_binding_t *b = jl_get_module_binding(mod, var, 0); if (b == NULL) return (jl_value_t*)jl_any_type; jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner); @@ -1256,7 +1256,7 @@ JL_CALLABLE(jl_f_set_binding_type) JL_TYPECHK(set_binding_type!, symbol, (jl_value_t*)s); jl_value_t *ty = nargs == 2 ? (jl_value_t*)jl_any_type : args[2]; JL_TYPECHK(set_binding_type!, type, ty); - jl_binding_t *b = jl_get_binding_wr(m, s, 1); + jl_binding_t *b = jl_get_binding_wr(m, s); jl_value_t *old_ty = NULL; if (!jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, ty) && ty != old_ty) { if (nargs == 2) diff --git a/src/codegen.cpp b/src/codegen.cpp index f36a7458c48297..0bb1044dbce7ea 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -693,7 +693,7 @@ static const auto jlgetbindingorerror_func = new JuliaFunction{ nullptr, }; static const auto jlgetbindingwrorerror_func = new JuliaFunction{ - XSTR(jl_get_binding_wr_or_error), + XSTR(jl_get_binding_wr), [](LLVMContext &C) { auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); return FunctionType::get(T_pjlvalue, @@ -4212,11 +4212,18 @@ static void undef_var_error_ifnot(jl_codectx_t &ctx, Value *ok, jl_sym_t *name) static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t *s, jl_binding_t **pbnd, bool assign) { - jl_binding_t *b = NULL; - if (assign) - b = jl_get_binding_wr(m, s, 0); - else - b = jl_get_binding(m, s); + jl_binding_t *b = jl_get_module_binding(m, s, 1); + if (assign) { + if (jl_atomic_load_relaxed(&b->owner) == NULL) + // not yet declared + b = NULL; + } + else { + b = jl_atomic_load_relaxed(&b->owner); + if (b == NULL) + // try to look this up now + b = jl_get_binding(m, s); + } if (b == NULL) { // var not found. switch to delayed lookup. Constant *initnul = Constant::getNullValue(ctx.types().T_pjlvalue); @@ -8461,7 +8468,7 @@ static void init_jit_functions(void) add_named_global(jlcheckassign_func, &jl_checked_assignment); add_named_global(jldeclareconst_func, &jl_declare_constant); add_named_global(jlgetbindingorerror_func, &jl_get_binding_or_error); - add_named_global(jlgetbindingwrorerror_func, &jl_get_binding_wr_or_error); + add_named_global(jlgetbindingwrorerror_func, &jl_get_binding_wr); add_named_global(jlboundp_func, &jl_boundp); for (auto it : builtin_func_map()) add_named_global(it.second, it.first); diff --git a/src/dlload.c b/src/dlload.c index c1a8e02268ba23..281d9d55bfe41b 100644 --- a/src/dlload.c +++ b/src/dlload.c @@ -293,7 +293,7 @@ JL_DLLEXPORT void *jl_load_dynamic_library(const char *modname, unsigned flags, such as Windows, so we emulate them here. */ if (!abspath && !is_atpath && jl_base_module != NULL) { - jl_binding_t *b = jl_get_module_binding(jl_base_module, jl_symbol("DL_LOAD_PATH")); + jl_binding_t *b = jl_get_module_binding(jl_base_module, jl_symbol("DL_LOAD_PATH"), 0); jl_array_t *DL_LOAD_PATH = (jl_array_t*)(b ? jl_atomic_load_relaxed(&b->value) : NULL); if (DL_LOAD_PATH != NULL) { size_t j; diff --git a/src/interpreter.c b/src/interpreter.c index bc816b0025d9aa..c2398201fd4f6a 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -492,7 +492,7 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, sym = (jl_sym_t*)lhs; } JL_GC_PUSH1(&rhs); - jl_binding_t *b = jl_get_binding_wr_or_error(modu, sym); + jl_binding_t *b = jl_get_binding_wr(modu, sym); jl_checked_assignment(b, modu, sym, rhs); JL_GC_POP(); } diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index ccffb793588e33..bd587e28abca78 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -209,7 +209,6 @@ XX(jl_get_binding) \ XX(jl_get_binding_for_method_def) \ XX(jl_get_binding_or_error) \ - XX(jl_get_binding_wr_or_error) \ XX(jl_get_binding_wr) \ XX(jl_get_cpu_name) \ XX(jl_get_current_task) \ @@ -223,7 +222,6 @@ XX(jl_get_julia_bin) \ XX(jl_get_julia_bindir) \ XX(jl_get_method_inferred) \ - XX(jl_get_module_binding) \ XX(jl_get_module_compile) \ XX(jl_get_module_infer) \ XX(jl_get_module_of_binding) \ diff --git a/src/julia.h b/src/julia.h index 0c18bfbaadc554..90fac113a7acf8 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1627,8 +1627,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_if_bound(jl_module_t *m, jl_sym_t *var JL_DLLEXPORT jl_value_t *jl_module_globalref(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var); // get binding for assignment -JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int alloc); -JL_DLLEXPORT jl_binding_t *jl_get_binding_wr_or_error(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); +JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT int jl_defines_or_exports_p(jl_module_t *m, jl_sym_t *var); diff --git a/src/julia_internal.h b/src/julia_internal.h index c769779f82d161..2c3b8e3fe92e83 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -762,7 +762,7 @@ void jl_compute_field_offsets(jl_datatype_t *st); jl_array_t *jl_new_array_for_deserialization(jl_value_t *atype, uint32_t ndims, size_t *dims, int isunboxed, int hasptr, int isunion, int elsz); void jl_module_run_initializer(jl_module_t *m); -jl_binding_t *jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); +jl_binding_t *jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int alloc); JL_DLLEXPORT void jl_binding_deprecation_warning(jl_module_t *m, jl_sym_t *sym, jl_binding_t *b); extern jl_array_t *jl_module_init_order JL_GLOBALLY_ROOTED; extern htable_t jl_current_modules JL_GLOBALLY_ROOTED; diff --git a/src/module.c b/src/module.c index c0508349abc8f7..30e31ba73514b0 100644 --- a/src/module.c +++ b/src/module.c @@ -190,58 +190,26 @@ static jl_binding_t *new_binding(jl_module_t *mod, jl_sym_t *name) static jl_module_t *jl_binding_dbgmodule(jl_binding_t *b, jl_module_t *m, jl_sym_t *var) JL_GLOBALLY_ROOTED; // get binding for assignment -JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int alloc) -{ - JL_LOCK(&m->lock); - jl_binding_t **bp = (jl_binding_t**)ptrhash_bp(&m->bindings, var); - jl_binding_t *b = *bp; - - if (b != HT_NOTFOUND) { - JL_GC_PROMISE_ROOTED(b); - jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner); - if (b2 != b) { - if (b2 == NULL) { - jl_atomic_store_release(&b->owner, b); - } - else if (alloc) { - jl_module_t *from = jl_binding_dbgmodule(b, m, var); - JL_UNLOCK(&m->lock); - if (from == m) - jl_errorf("cannot assign a value to imported variable %s.%s", - jl_symbol_name(from->name), jl_symbol_name(var)); - else - jl_errorf("cannot assign a value to imported variable %s.%s from module %s", - jl_symbol_name(from->name), jl_symbol_name(var), jl_symbol_name(m->name)); - } +JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var) +{ + jl_binding_t *b = jl_get_module_binding(m, var, 1); + + if (b) { + jl_binding_t *b2 = NULL; + if (!jl_atomic_cmpswap(&b->owner, &b2, b) && b2 != b) { + jl_module_t *from = jl_binding_dbgmodule(b, m, var); + if (from == m) + jl_errorf("cannot assign a value to imported variable %s.%s", + jl_symbol_name(from->name), jl_symbol_name(var)); + else + jl_errorf("cannot assign a value to imported variable %s.%s from module %s", + jl_symbol_name(from->name), jl_symbol_name(var), jl_symbol_name(m->name)); } } - else if (alloc) { - b = new_binding(m, var); - jl_atomic_store_release(&b->owner, b); - *bp = b; - JL_GC_PROMISE_ROOTED(b); - jl_gc_wb(m, b); - } - else { - b = NULL; - } - JL_UNLOCK(&m->lock); return b; } -// Hash tables don't generically root their contents, but they do for bindings. -// Express this to the analyzer. -// NOTE: Must hold m->lock while calling these. -#ifdef __clang_gcanalyzer__ -jl_binding_t *_jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var) JL_NOTSAFEPOINT; -#else -static inline jl_binding_t *_jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var) JL_NOTSAFEPOINT -{ - return (jl_binding_t*)ptrhash_get(&m->bindings, var); -} -#endif - // return module of binding JL_DLLEXPORT jl_module_t *jl_get_module_of_binding(jl_module_t *m, jl_sym_t *var) { @@ -255,51 +223,29 @@ JL_DLLEXPORT jl_module_t *jl_get_module_of_binding(jl_module_t *m, jl_sym_t *var // like jl_get_binding_wr, but has different error paths JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_t *var) { - JL_LOCK(&m->lock); - jl_binding_t **bp = (jl_binding_t**)ptrhash_bp(&m->bindings, var); - jl_binding_t *b = *bp; - - if (b != HT_NOTFOUND) { - JL_GC_PROMISE_ROOTED(b); - jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner); - if (b2 != b) { - // TODO: make this cmpswap atomic - if (b2 == NULL) { - jl_atomic_store_release(&b->owner, b); - } - else { - jl_value_t *f = jl_atomic_load_relaxed(&b2->value); - jl_module_t *from = jl_binding_dbgmodule(b, m, var); - JL_UNLOCK(&m->lock); - if (f == NULL) { - // we must have implicitly imported this with using, so call jl_binding_dbgmodule to try to get the name of the module we got this from - jl_errorf("invalid method definition in %s: exported function %s.%s does not exist", - jl_symbol_name(m->name), jl_symbol_name(from->name), jl_symbol_name(var)); - } - // TODO: we might want to require explicitly importing types to add constructors - // or we might want to drop this error entirely - if (!b->imported && (!b2->constp || !jl_is_type(f))) { - jl_errorf("invalid method definition in %s: function %s.%s must be explicitly imported to be extended", - jl_symbol_name(m->name), jl_symbol_name(from->name), jl_symbol_name(var)); - } - return b2; - } + jl_binding_t *b = jl_get_module_binding(m, var, 1); + + jl_binding_t *b2 = NULL; + if (!jl_atomic_cmpswap(&b->owner, &b2, b) && b2 != b) { + jl_value_t *f = jl_atomic_load_relaxed(&b2->value); + jl_module_t *from = jl_binding_dbgmodule(b, m, var); + if (f == NULL) { + // we must have implicitly imported this with using, so call jl_binding_dbgmodule to try to get the name of the module we got this from + jl_errorf("invalid method definition in %s: exported function %s.%s does not exist", + jl_symbol_name(m->name), jl_symbol_name(from->name), jl_symbol_name(var)); } - } - else { - b = new_binding(m, var); - jl_atomic_store_relaxed(&b->owner, b); - *bp = b; - JL_GC_PROMISE_ROOTED(b); - jl_gc_wb(m, b); + // TODO: we might want to require explicitly importing types to add constructors + // or we might want to drop this error entirely + if (!b->imported && (!b2->constp || !jl_is_type(f))) { + jl_errorf("invalid method definition in %s: function %s.%s must be explicitly imported to be extended", + jl_symbol_name(m->name), jl_symbol_name(from->name), jl_symbol_name(var)); + } + return b2; } - JL_UNLOCK(&m->lock); // may gc return b; } -static void module_import_(jl_module_t *to, jl_module_t *from, jl_binding_t *b, jl_sym_t *asname, jl_sym_t *s, int explici); - typedef struct _modstack_t { jl_module_t *m; jl_sym_t *var; @@ -318,27 +264,32 @@ static inline jl_module_t *module_usings_getidx(jl_module_t *m JL_PROPAGATES_ROO } #endif -static int eq_bindings(jl_binding_t *a, jl_binding_t *b) +static int eq_bindings(jl_binding_t *owner, jl_binding_t *alias) { - if (a == b) + assert(owner == jl_atomic_load_relaxed(&owner->owner)); + if (owner == alias) return 1; - if (jl_atomic_load_relaxed(&a->owner) == jl_atomic_load_relaxed(&b->owner)) + alias = jl_atomic_load_relaxed(&alias->owner); + if (owner == alias) return 1; - if (a->constp && b->constp && jl_atomic_load_relaxed(&a->value) && jl_atomic_load_relaxed(&b->value) == jl_atomic_load_relaxed(&a->value)) + if (owner->constp && alias->constp && jl_atomic_load_relaxed(&owner->value) && jl_atomic_load_relaxed(&alias->value) == jl_atomic_load_relaxed(&owner->value)) return 1; return 0; } // find a binding from a module's `usings` list -// called while holding m->lock static jl_binding_t *using_resolve_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, jl_module_t **from, modstack_t *st, int warn) { jl_binding_t *b = NULL; jl_module_t *owner = NULL; - for (int i = (int)m->usings.len - 1; i >= 0; --i) { + JL_LOCK(&m->lock); + int i = (int)m->usings.len - 1; + JL_UNLOCK(&m->lock); + for (; i >= 0; --i) { + JL_LOCK(&m->lock); jl_module_t *imp = module_usings_getidx(m, i); - // TODO: make sure this can't deadlock - jl_binding_t *tempb = jl_get_module_binding(imp, var); + JL_UNLOCK(&m->lock); + jl_binding_t *tempb = jl_get_module_binding(imp, var, 0); if (tempb != NULL && tempb->exportp) { tempb = jl_resolve_owner(NULL, imp, var, st); // find the owner for tempb if (tempb == NULL) @@ -349,20 +300,13 @@ static jl_binding_t *using_resolve_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl if (warn) { // set usingfailed=1 to avoid repeating this warning // the owner will still be NULL, so it can be later imported or defined - tempb = _jl_get_module_binding(m, var); - if (tempb == HT_NOTFOUND) { - tempb = new_binding(m, var); - ptrhash_put(&m->bindings, (void*)var, (void*)tempb); - jl_gc_wb(m, tempb); - } + tempb = jl_get_module_binding(m, var, 1); tempb->usingfailed = 1; - JL_UNLOCK(&m->lock); jl_printf(JL_STDERR, "WARNING: both %s and %s export \"%s\"; uses of it in module %s must be qualified\n", jl_symbol_name(owner->name), jl_symbol_name(imp->name), jl_symbol_name(var), jl_symbol_name(m->name)); - JL_LOCK(&m->lock); } return NULL; } @@ -378,7 +322,6 @@ static jl_binding_t *using_resolve_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl // for error message printing: look up the module that exported a binding to m as var // this might not be the same as the owner of the binding, since the binding itself may itself have been imported from elsewhere -// must be holding m->lock before calling this static jl_module_t *jl_binding_dbgmodule(jl_binding_t *b, jl_module_t *m, jl_sym_t *var) { jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner); @@ -395,17 +338,17 @@ static jl_module_t *jl_binding_dbgmodule(jl_binding_t *b, jl_module_t *m, jl_sym return m; } +static void jl_binding_dep_message(jl_module_t *m, jl_sym_t *name, jl_binding_t *b); + // get binding for reading. might return NULL for unbound. static jl_binding_t *jl_resolve_owner(jl_binding_t *b/*optional*/, jl_module_t *m, jl_sym_t *var, modstack_t *st) { if (b == NULL) - b = jl_get_module_binding(m, var); - if (b != NULL) { - if (jl_atomic_load_relaxed(&b->owner) == NULL && b->usingfailed) + b = jl_get_module_binding(m, var, 1); + jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner); + if (b2 == NULL) { + if (b->usingfailed) return NULL; - b = jl_atomic_load_relaxed(&b->owner); - } - if (b == NULL) { modstack_t top = { m, var, st }; modstack_t *tmp = st; for (; tmp != NULL; tmp = tmp->prev) { @@ -415,27 +358,45 @@ static jl_binding_t *jl_resolve_owner(jl_binding_t *b/*optional*/, jl_module_t * } } jl_module_t *from = NULL; // for error message printing - JL_LOCK(&m->lock); - b = using_resolve_binding(m, var, &from, &top, 1); - JL_UNLOCK(&m->lock); - if (b != NULL) { - // do a full import to prevent the result of this lookup - // from changing, for example if this var is assigned to - // later. - // TODO: make this more thread-safe - assert(jl_atomic_load_relaxed(&b->owner) == b && from); - module_import_(m, from, b, var, var, 0); - return b; + b2 = using_resolve_binding(m, var, &from, &top, 1); + if (b2 == NULL) + return NULL; + assert(from); + if (b2->deprecated) { + if (jl_atomic_load_relaxed(&b2->value) == jl_nothing) { + // silently skip importing deprecated values assigned to nothing (to allow later mutation) + return NULL; + } + } + // do a full import to prevent the result of this lookup from + // changing, for example if this var is assigned to later. + jl_binding_t *owner = NULL; + if (!jl_atomic_cmpswap(&b->owner, &owner, b2)) { + // concurrent import + return owner; + } + if (b2->deprecated) { + b->deprecated = 1; // we will warn about this below, but we might want to warn at the use sites too + if (m != jl_main_module && m != jl_base_module && + jl_options.depwarn != JL_OPTIONS_DEPWARN_OFF) { + /* with #22763, external packages wanting to replace + deprecated Base bindings should simply export the new + binding */ + jl_printf(JL_STDERR, + "WARNING: using deprecated binding %s.%s in %s.\n", + jl_symbol_name(from->name), jl_symbol_name(var), + jl_symbol_name(m->name)); + jl_binding_dep_message(from, var, b2); + } } - return NULL; } - assert(jl_atomic_load_relaxed(&b->owner) == b); - return b; + assert(jl_atomic_load_relaxed(&b2->owner) == b2); + return b2; } JL_DLLEXPORT jl_binding_t *jl_get_binding_if_bound(jl_module_t *m, jl_sym_t *var) { - jl_binding_t *b = jl_get_module_binding(m, var); + jl_binding_t *b = jl_get_module_binding(m, var, 0); return b == NULL ? NULL : jl_atomic_load_relaxed(&b->owner); } @@ -443,21 +404,19 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_if_bound(jl_module_t *m, jl_sym_t *var // get the current likely owner of binding when accessing m.var, without resolving the binding (it may change later) JL_DLLEXPORT jl_binding_t *jl_binding_owner(jl_module_t *m, jl_sym_t *var) { - JL_LOCK(&m->lock); - jl_binding_t *b = (jl_binding_t*)ptrhash_get(&m->bindings, var); + jl_binding_t *b = jl_get_module_binding(m, var, 0); jl_module_t *from = m; - if (b == HT_NOTFOUND || (!b->usingfailed && jl_atomic_load_relaxed(&b->owner) == NULL)) + if (b == NULL || (!b->usingfailed && jl_atomic_load_relaxed(&b->owner) == NULL)) b = using_resolve_binding(m, var, &from, NULL, 0); else b = jl_atomic_load_relaxed(&b->owner); - JL_UNLOCK(&m->lock); return b; } // get type of binding m.var, without resolving the binding JL_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var) { - jl_binding_t *b = jl_get_module_binding(m, var); + jl_binding_t *b = jl_get_module_binding(m, var, 0); if (b == NULL) return jl_nothing; b = jl_atomic_load_relaxed(&b->owner); @@ -467,11 +426,6 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var) return ty ? ty : jl_nothing; } -JL_DLLEXPORT jl_binding_t *jl_get_binding_wr_or_error(jl_module_t *m, jl_sym_t *var) -{ - return jl_get_binding_wr(m, var, 1); -} - JL_DLLEXPORT jl_binding_t *jl_get_binding(jl_module_t *m, jl_sym_t *var) { return jl_resolve_owner(NULL, m, var, NULL); @@ -490,27 +444,17 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_or_error(jl_module_t *m, jl_sym_t *var JL_DLLEXPORT jl_value_t *jl_module_globalref(jl_module_t *m, jl_sym_t *var) { - JL_LOCK(&m->lock); - jl_binding_t *b = _jl_get_module_binding(m, var); - if (b == HT_NOTFOUND) { - b = new_binding(m, var); - ptrhash_put(&m->bindings, (void*)var, (void*)b); - jl_gc_wb(m, b); - JL_GC_PROMISE_ROOTED(b); - } - JL_UNLOCK(&m->lock); // may GC + jl_binding_t *b = jl_get_module_binding(m, var, 1); jl_globalref_t *globalref = b->globalref; assert(globalref != NULL); return (jl_value_t*)globalref; } // does module m explicitly import s? -JL_DLLEXPORT int jl_is_imported(jl_module_t *m, jl_sym_t *s) +JL_DLLEXPORT int jl_is_imported(jl_module_t *m, jl_sym_t *var) { - JL_LOCK(&m->lock); - jl_binding_t *b = (jl_binding_t*)ptrhash_get(&m->bindings, s); - JL_UNLOCK(&m->lock); - return (b != HT_NOTFOUND && b->imported); + jl_binding_t *b = jl_get_module_binding(m, var, 0); + return b && b->imported; } extern const char *jl_filename; @@ -567,8 +511,9 @@ static void jl_binding_dep_message(jl_module_t *m, jl_sym_t *name, jl_binding_t } // NOTE: we use explici since explicit is a C++ keyword -static void module_import_(jl_module_t *to, jl_module_t *from, jl_binding_t *b, jl_sym_t *asname, jl_sym_t *s, int explici) +static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *asname, jl_sym_t *s, int explici) { + jl_binding_t *b = jl_get_binding(from, s); if (b == NULL) { jl_printf(JL_STDERR, "WARNING: could not import %s.%s into %s\n", @@ -597,79 +542,60 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_binding_t *b, } } - JL_LOCK(&to->lock); - jl_binding_t **bp = (jl_binding_t**)ptrhash_bp(&to->bindings, asname); - jl_binding_t *bto = *bp; - if (bto != HT_NOTFOUND) { - JL_GC_PROMISE_ROOTED(bto); - jl_binding_t *ownerto = jl_atomic_load_relaxed(&bto->owner); - if (bto == b) { - // importing a binding on top of itself. harmless. - } - else if (eq_bindings(bto, b)) { + jl_binding_t *bto = jl_get_module_binding(to, asname, 1); + if (bto == b) { + // importing a binding on top of itself. harmless. + return; + } + jl_binding_t *ownerto = NULL; + if (jl_atomic_cmpswap(&bto->owner, &ownerto, b)) { + bto->imported |= (explici != 0); + bto->deprecated |= b->deprecated; // we already warned about this above, but we might want to warn at the use sites too + } + else { + if (eq_bindings(b, bto)) { // already imported - bto->imported = (explici != 0); + bto->imported |= (explici != 0); } - else if (ownerto != b && ownerto != NULL) { + else if (ownerto != bto) { // already imported from somewhere else - JL_UNLOCK(&to->lock); jl_printf(JL_STDERR, "WARNING: ignoring conflicting import of %s.%s into %s\n", jl_symbol_name(from->name), jl_symbol_name(s), jl_symbol_name(to->name)); - return; } - else if (bto->constp || jl_atomic_load_relaxed(&bto->value)) { + else { // conflict with name owned by destination module - assert(ownerto == bto); - JL_UNLOCK(&to->lock); jl_printf(JL_STDERR, "WARNING: import of %s.%s into %s conflicts with an existing identifier; ignored.\n", jl_symbol_name(from->name), jl_symbol_name(s), jl_symbol_name(to->name)); - return; } - else { - jl_atomic_store_release(&bto->owner, b); - bto->imported = (explici != 0); - } - } - else { - jl_binding_t *nb = new_binding(to, asname); - jl_atomic_store_relaxed(&nb->owner, b); - nb->imported = (explici != 0); - nb->deprecated = b->deprecated; // we already warned about this above, but we might want to warn at the use sites too - *bp = nb; - jl_gc_wb(to, nb); } - JL_UNLOCK(&to->lock); } } JL_DLLEXPORT void jl_module_import(jl_module_t *to, jl_module_t *from, jl_sym_t *s) { - jl_binding_t *b = jl_get_binding(from, s); - module_import_(to, from, b, s, s, 1); + module_import_(to, from, s, s, 1); } JL_DLLEXPORT void jl_module_import_as(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname) { - jl_binding_t *b = jl_get_binding(from, s); - module_import_(to, from, b, asname, s, 1); + module_import_(to, from, asname, s, 1); } JL_DLLEXPORT void jl_module_use(jl_module_t *to, jl_module_t *from, jl_sym_t *s) { - jl_binding_t *b = jl_get_binding(from, s); - module_import_(to, from, b, s, s, 0); + module_import_(to, from, s, s, 0); } JL_DLLEXPORT void jl_module_use_as(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname) { - jl_binding_t *b = jl_get_binding(from, s); - module_import_(to, from, b, asname, s, 0); + module_import_(to, from, asname, s, 0); } + JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from) { if (to == from) @@ -681,7 +607,10 @@ JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from) return; } } - // TODO: make sure this can't deadlock + arraylist_push(&to->usings, from); + jl_gc_wb(to, from); + + // TODO: make so this can't deadlock JL_LOCK(&from->lock); // print a warning if something visible via this "using" conflicts with // an existing identifier. note that an identifier added later may still @@ -708,25 +637,13 @@ JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from) } } JL_UNLOCK(&from->lock); - - arraylist_push(&to->usings, from); - jl_gc_wb(to, from); JL_UNLOCK(&to->lock); } JL_DLLEXPORT void jl_module_export(jl_module_t *from, jl_sym_t *s) { - JL_LOCK(&from->lock); - jl_binding_t **bp = (jl_binding_t**)ptrhash_bp(&from->bindings, s); - if (*bp == HT_NOTFOUND) { - jl_binding_t *b = new_binding(from, s); - // don't yet know who the owner will be - *bp = b; - jl_gc_wb(from, b); - } - assert(*bp != HT_NOTFOUND); - (*bp)->exportp = 1; - JL_UNLOCK(&from->lock); + jl_binding_t *b = jl_get_module_binding(from, s, 1); + b->exportp = 1; } JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var) @@ -737,30 +654,53 @@ JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var) JL_DLLEXPORT int jl_defines_or_exports_p(jl_module_t *m, jl_sym_t *var) { - JL_LOCK(&m->lock); - jl_binding_t *b = (jl_binding_t*)ptrhash_get(&m->bindings, var); - JL_UNLOCK(&m->lock); - return b != HT_NOTFOUND && (b->exportp || jl_atomic_load_relaxed(&b->owner) == b); + jl_binding_t *b = jl_get_module_binding(m, var, 0); + return b && (b->exportp || jl_atomic_load_relaxed(&b->owner) == b); } JL_DLLEXPORT int jl_module_exports_p(jl_module_t *m, jl_sym_t *var) { - jl_binding_t *b = jl_get_module_binding(m, var); + jl_binding_t *b = jl_get_module_binding(m, var, 0); return b && b->exportp; } JL_DLLEXPORT int jl_binding_resolved_p(jl_module_t *m, jl_sym_t *var) { - jl_binding_t *b = jl_get_module_binding(m, var); + jl_binding_t *b = jl_get_module_binding(m, var, 0); return b && jl_atomic_load_relaxed(&b->owner) != NULL; } -JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var) +// Hash tables don't generically root their contents, but they do for bindings. +// Express this to the analyzer. +// NOTE: Must hold m->lock while calling these. +#ifdef __clang_gcanalyzer__ +jl_binding_t *_jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var) JL_NOTSAFEPOINT; +#else +static inline jl_binding_t *_jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var) JL_NOTSAFEPOINT +{ + return (jl_binding_t*)ptrhash_get(&m->bindings, var); +} +#endif + +JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int alloc) { JL_LOCK(&m->lock); jl_binding_t *b = _jl_get_module_binding(m, var); + if (b == HT_NOTFOUND) { + b = NULL; + if (alloc) { + jl_binding_t **bp = (jl_binding_t**)ptrhash_bp(&m->bindings, var); + b = *bp; + if (b == HT_NOTFOUND) { + b = new_binding(m, var); + *bp = b; + JL_GC_PROMISE_ROOTED(b); + jl_gc_wb(m, b); + } + } + } JL_UNLOCK(&m->lock); - return b == HT_NOTFOUND ? NULL : b; + return b; } @@ -785,14 +725,14 @@ JL_DLLEXPORT jl_value_t *jl_get_global(jl_module_t *m, jl_sym_t *var) JL_DLLEXPORT void jl_set_global(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT) { - jl_binding_t *bp = jl_get_binding_wr(m, var, 1); + jl_binding_t *bp = jl_get_binding_wr(m, var); jl_checked_assignment(bp, m, var, val); } JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT) { // this function is mostly only used during initialization, so the data races here are not too important to us - jl_binding_t *bp = jl_get_binding_wr(m, var, 1); + jl_binding_t *bp = jl_get_binding_wr(m, var); if (jl_atomic_load_relaxed(&bp->value) == NULL) { jl_value_t *old_ty = NULL; jl_atomic_cmpswap_relaxed(&bp->ty, &old_ty, (jl_value_t*)jl_any_type); @@ -820,7 +760,7 @@ JL_DLLEXPORT int jl_globalref_boundp(jl_globalref_t *gr) { jl_binding_t *b = gr->binding; b = jl_resolve_owner(b, gr->mod, gr->name, NULL); - return b && (jl_atomic_load_relaxed(&b->value) != NULL); + return b && jl_atomic_load_relaxed(&b->value) != NULL; } JL_DLLEXPORT int jl_is_const(jl_module_t *m, jl_sym_t *var) diff --git a/src/rtutils.c b/src/rtutils.c index 6ac35760a5fc6f..dd606f38d065ca 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -524,7 +524,7 @@ JL_DLLEXPORT jl_value_t *jl_stdout_obj(void) JL_NOTSAFEPOINT { if (jl_base_module == NULL) return NULL; - jl_binding_t *stdout_obj = jl_get_module_binding(jl_base_module, jl_symbol("stdout")); + jl_binding_t *stdout_obj = jl_get_module_binding(jl_base_module, jl_symbol("stdout"), 0); return stdout_obj ? jl_atomic_load_relaxed(&stdout_obj->value) : NULL; } @@ -532,7 +532,7 @@ JL_DLLEXPORT jl_value_t *jl_stderr_obj(void) JL_NOTSAFEPOINT { if (jl_base_module == NULL) return NULL; - jl_binding_t *stderr_obj = jl_get_module_binding(jl_base_module, jl_symbol("stderr")); + jl_binding_t *stderr_obj = jl_get_module_binding(jl_base_module, jl_symbol("stderr"), 0); return stderr_obj ? jl_atomic_load_relaxed(&stderr_obj->value) : NULL; } @@ -625,9 +625,9 @@ JL_DLLEXPORT jl_value_t *jl_argument_datatype(jl_value_t *argt JL_PROPAGATES_ROO static int is_globname_binding(jl_value_t *v, jl_datatype_t *dv) JL_NOTSAFEPOINT { jl_sym_t *globname = dv->name->mt != NULL ? dv->name->mt->name : NULL; - if (globname && dv->name->module && jl_binding_resolved_p(dv->name->module, globname)) { - jl_binding_t *b = jl_get_module_binding(dv->name->module, globname); - if (b && b->constp) { + if (globname && dv->name->module) { + jl_binding_t *b = jl_get_module_binding(dv->name->module, globname, 0); + if (b && jl_atomic_load_relaxed(&b->owner) && b->constp) { jl_value_t *bv = jl_atomic_load_relaxed(&b->value); // The `||` makes this function work for both function instances and function types. if (bv == v || jl_typeof(bv) == v) diff --git a/src/toplevel.c b/src/toplevel.c index 5e31167a2879a3..65a64001c3e832 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -154,7 +154,7 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex } } else { - jl_binding_t *b = jl_get_binding_wr(parent_module, name, 1); + jl_binding_t *b = jl_get_binding_wr(parent_module, name); jl_declare_constant(b, parent_module, name); jl_value_t *old = NULL; if (!jl_atomic_cmpswap(&b->value, &old, (jl_value_t*)newm)) { @@ -314,7 +314,7 @@ void jl_eval_global_expr(jl_module_t *m, jl_expr_t *ex, int set_type) { gs = (jl_sym_t*)arg; } if (!jl_binding_resolved_p(gm, gs)) { - jl_binding_t *b = jl_get_binding_wr(gm, gs, 1); + jl_binding_t *b = jl_get_binding_wr(gm, gs); if (set_type) { jl_value_t *old_ty = NULL; // maybe set the type too, perhaps @@ -590,7 +590,7 @@ static void import_module(jl_module_t *JL_NONNULL m, jl_module_t *import, jl_sym assert(m); jl_sym_t *name = asname ? asname : import->name; // TODO: this is a bit race-y with what error message we might print - jl_binding_t *b = jl_get_module_binding(m, name); + jl_binding_t *b = jl_get_module_binding(m, name, 0); jl_binding_t *b2; if (b != NULL && (b2 = jl_atomic_load_relaxed(&b->owner)) != NULL) { if (b2->constp && jl_atomic_load_relaxed(&b2->value) == (jl_value_t*)import) @@ -600,7 +600,7 @@ static void import_module(jl_module_t *JL_NONNULL m, jl_module_t *import, jl_sym jl_symbol_name(name), jl_symbol_name(m->name)); } else { - b = jl_get_binding_wr(m, name, 1); + b = jl_get_binding_wr(m, name); } jl_declare_constant(b, m, name); jl_checked_assignment(b, m, name, (jl_value_t*)import); @@ -841,7 +841,7 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int gm = m; gs = (jl_sym_t*)arg; } - jl_binding_t *b = jl_get_binding_wr(gm, gs, 1); + jl_binding_t *b = jl_get_binding_wr(gm, gs); jl_declare_constant(b, gm, gs); JL_GC_POP(); return jl_nothing; From e163c84c9dafbadbd6e0374d24abf86208544d15 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Tue, 10 Jan 2023 01:00:37 -0500 Subject: [PATCH 271/387] lattice: Thread lattice through to va_process_argtypes (#48198) --- base/compiler/abstractinterpretation.jl | 12 +++++----- base/compiler/abstractlattice.jl | 1 - base/compiler/inferenceresult.jl | 32 ++++++++++++------------- base/compiler/ssair/irinterp.jl | 2 +- base/compiler/ssair/legacy.jl | 2 +- base/compiler/types.jl | 9 +++---- 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 24a5336254bebc..dd9349a48aa527 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -927,18 +927,18 @@ struct ConditionalArgtypes <: ForwardableArgtypes end """ - matching_cache_argtypes(linfo::MethodInstance, argtypes::ConditionalArgtypes) + matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance, argtypes::ConditionalArgtypes) The implementation is able to forward `Conditional` of `argtypes`, as well as the other general extended lattice inforamtion. """ -function matching_cache_argtypes(linfo::MethodInstance, argtypes::ConditionalArgtypes) +function matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance, argtypes::ConditionalArgtypes) (; arginfo, sv) = argtypes (; fargs, argtypes) = arginfo given_argtypes = Vector{Any}(undef, length(argtypes)) def = linfo.def::Method nargs = Int(def.nargs) - cache_argtypes, overridden_by_const = matching_cache_argtypes(linfo) + cache_argtypes, overridden_by_const = matching_cache_argtypes(𝕃, linfo) local condargs = nothing for i in 1:length(argtypes) argtype = argtypes[i] @@ -969,7 +969,7 @@ function matching_cache_argtypes(linfo::MethodInstance, argtypes::ConditionalArg end if condargs !== nothing given_argtypes = let condargs=condargs - va_process_argtypes(given_argtypes, linfo) do isva_given_argtypes::Vector{Any}, last::Int + va_process_argtypes(𝕃, given_argtypes, linfo) do isva_given_argtypes::Vector{Any}, last::Int # invalidate `Conditional` imposed on varargs for (slotid, i) in condargs if slotid ≥ last && (1 ≤ i ≤ length(isva_given_argtypes)) # `Conditional` is already widened to vararg-tuple otherwise @@ -979,9 +979,9 @@ function matching_cache_argtypes(linfo::MethodInstance, argtypes::ConditionalArg end end else - given_argtypes = va_process_argtypes(given_argtypes, linfo) + given_argtypes = va_process_argtypes(𝕃, given_argtypes, linfo) end - return pick_const_args!(cache_argtypes, overridden_by_const, given_argtypes) + return pick_const_args!(𝕃, cache_argtypes, overridden_by_const, given_argtypes) end function abstract_call_method_with_const_args(interp::AbstractInterpreter, diff --git a/base/compiler/abstractlattice.jl b/base/compiler/abstractlattice.jl index 7e4fd3e59f1534..f578ec8d6f60d0 100644 --- a/base/compiler/abstractlattice.jl +++ b/base/compiler/abstractlattice.jl @@ -1,6 +1,5 @@ # TODO add more documentations -abstract type AbstractLattice end function widenlattice end function is_valid_lattice_norec end diff --git a/base/compiler/inferenceresult.jl b/base/compiler/inferenceresult.jl index 5001be9cc3601b..c079553fca06a1 100644 --- a/base/compiler/inferenceresult.jl +++ b/base/compiler/inferenceresult.jl @@ -1,14 +1,14 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license """ - matching_cache_argtypes(linfo::MethodInstance) -> + matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance) -> (cache_argtypes::Vector{Any}, overridden_by_const::BitVector) Returns argument types `cache_argtypes::Vector{Any}` for `linfo` that are in the native Julia type domain. `overridden_by_const::BitVector` is all `false` meaning that there is no additional extended lattice information there. - matching_cache_argtypes(linfo::MethodInstance, argtypes::ForwardableArgtypes) -> + matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance, argtypes::ForwardableArgtypes) -> (cache_argtypes::Vector{Any}, overridden_by_const::BitVector) Returns cache-correct extended lattice argument types `cache_argtypes::Vector{Any}` @@ -22,7 +22,7 @@ so that we can construct cache-correct `InferenceResult`s in the first place. """ function matching_cache_argtypes end -function matching_cache_argtypes(linfo::MethodInstance) +function matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance) mthd = isa(linfo.def, Method) ? linfo.def::Method : nothing cache_argtypes = most_general_argtypes(mthd, linfo.specTypes) return cache_argtypes, falses(length(cache_argtypes)) @@ -33,33 +33,33 @@ struct SimpleArgtypes <: ForwardableArgtypes end """ - matching_cache_argtypes(linfo::MethodInstance, argtypes::SimpleArgtypes) + matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance, argtypes::SimpleArgtypes) The implementation for `argtypes` with general extended lattice information. This is supposed to be used for debugging and testing or external `AbstractInterpreter` usages and in general `matching_cache_argtypes(::MethodInstance, ::ConditionalArgtypes)` is more preferred it can forward `Conditional` information. """ -function matching_cache_argtypes(linfo::MethodInstance, simple_argtypes::SimpleArgtypes) +function matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance, simple_argtypes::SimpleArgtypes) (; argtypes) = simple_argtypes given_argtypes = Vector{Any}(undef, length(argtypes)) for i = 1:length(argtypes) given_argtypes[i] = widenslotwrapper(argtypes[i]) end - given_argtypes = va_process_argtypes(given_argtypes, linfo) - return pick_const_args(linfo, given_argtypes) + given_argtypes = va_process_argtypes(𝕃, given_argtypes, linfo) + return pick_const_args(𝕃, linfo, given_argtypes) end -function pick_const_args(linfo::MethodInstance, given_argtypes::Vector{Any}) - cache_argtypes, overridden_by_const = matching_cache_argtypes(linfo) - return pick_const_args!(cache_argtypes, overridden_by_const, given_argtypes) +function pick_const_args(𝕃::AbstractLattice, linfo::MethodInstance, given_argtypes::Vector{Any}) + cache_argtypes, overridden_by_const = matching_cache_argtypes(𝕃, linfo) + return pick_const_args!(𝕃, cache_argtypes, overridden_by_const, given_argtypes) end -function pick_const_args!(cache_argtypes::Vector{Any}, overridden_by_const::BitVector, given_argtypes::Vector{Any}) +function pick_const_args!(𝕃::AbstractLattice, cache_argtypes::Vector{Any}, overridden_by_const::BitVector, given_argtypes::Vector{Any}) for i = 1:length(given_argtypes) given_argtype = given_argtypes[i] cache_argtype = cache_argtypes[i] - if !is_argtype_match(fallback_lattice, given_argtype, cache_argtype, false) + if !is_argtype_match(𝕃, given_argtype, cache_argtype, false) # prefer the argtype we were given over the one computed from `linfo` cache_argtypes[i] = given_argtype overridden_by_const[i] = true @@ -78,9 +78,9 @@ function is_argtype_match(𝕃::AbstractLattice, return !overridden_by_const end -va_process_argtypes(given_argtypes::Vector{Any}, linfo::MethodInstance) = - va_process_argtypes(Returns(nothing), given_argtypes, linfo) -function va_process_argtypes(@nospecialize(va_handler!), given_argtypes::Vector{Any}, linfo::MethodInstance) +va_process_argtypes(𝕃::AbstractLattice, given_argtypes::Vector{Any}, linfo::MethodInstance) = + va_process_argtypes(Returns(nothing), 𝕃, given_argtypes, linfo) +function va_process_argtypes(@nospecialize(va_handler!), 𝕃::AbstractLattice, given_argtypes::Vector{Any}, linfo::MethodInstance) def = linfo.def::Method isva = def.isva nargs = Int(def.nargs) @@ -95,7 +95,7 @@ function va_process_argtypes(@nospecialize(va_handler!), given_argtypes::Vector{ else last = nargs end - isva_given_argtypes[nargs] = tuple_tfunc(fallback_lattice, given_argtypes[last:end]) + isva_given_argtypes[nargs] = tuple_tfunc(𝕃, given_argtypes[last:end]) va_handler!(isva_given_argtypes, last) end return isva_given_argtypes diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index e5d5c5834301e3..631b00797f17c7 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -104,7 +104,7 @@ struct IRInterpretationState lazydomtree::LazyDomtree function IRInterpretationState(interp::AbstractInterpreter, ir::IRCode, mi::MethodInstance, world::UInt, argtypes::Vector{Any}) - argtypes = va_process_argtypes(argtypes, mi) + argtypes = va_process_argtypes(typeinf_lattice(interp), argtypes, mi) for i = 1:length(argtypes) argtypes[i] = widenslotwrapper(argtypes[i]) end diff --git a/base/compiler/ssair/legacy.jl b/base/compiler/ssair/legacy.jl index bf8f4eee15020d..0ddefa4483eb10 100644 --- a/base/compiler/ssair/legacy.jl +++ b/base/compiler/ssair/legacy.jl @@ -11,7 +11,7 @@ the original `ci::CodeInfo` are modified. function inflate_ir!(ci::CodeInfo, linfo::MethodInstance) sptypes = sptypes_from_meth_instance(linfo) if ci.inferred - argtypes, _ = matching_cache_argtypes(linfo) + argtypes, _ = matching_cache_argtypes(fallback_lattice, linfo) else argtypes = Any[ Any for i = 1:length(ci.slotflags) ] end diff --git a/base/compiler/types.jl b/base/compiler/types.jl index 1514b3f101a60f..4d5a77f4ee70d3 100644 --- a/base/compiler/types.jl +++ b/base/compiler/types.jl @@ -17,6 +17,7 @@ the following methods to satisfy the `AbstractInterpreter` API requirement: - `code_cache(interp::NewInterpreter)` - return the global inference cache """ abstract type AbstractInterpreter end +abstract type AbstractLattice end struct ArgInfo fargs::Union{Nothing,Vector{Any}} @@ -57,11 +58,11 @@ mutable struct InferenceResult WorldRange(), Effects(), Effects(), nothing, true) end end -function InferenceResult(linfo::MethodInstance) - return InferenceResult(linfo, matching_cache_argtypes(linfo)...) +function InferenceResult(linfo::MethodInstance; lattice::AbstractLattice=fallback_lattice) + return InferenceResult(linfo, matching_cache_argtypes(lattice, linfo)...) end -function InferenceResult(linfo::MethodInstance, argtypes::ForwardableArgtypes) - return InferenceResult(linfo, matching_cache_argtypes(linfo, argtypes)...) +function InferenceResult(linfo::MethodInstance, argtypes::ForwardableArgtypes; lattice::AbstractLattice=fallback_lattice) + return InferenceResult(linfo, matching_cache_argtypes(lattice, linfo, argtypes)...) end """ From 557ddf2b207551691974fa99c70abebd97301457 Mon Sep 17 00:00:00 2001 From: Diogo Netto <61364108+d-netto@users.noreply.github.com> Date: Tue, 10 Jan 2023 01:18:15 -0500 Subject: [PATCH 272/387] fix some nits in gc docs (#48204) Co-authored-by: Diogo Netto --- doc/src/devdocs/gc.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/src/devdocs/gc.md b/doc/src/devdocs/gc.md index 0aef8569a96476..c072912e77c3f0 100644 --- a/doc/src/devdocs/gc.md +++ b/doc/src/devdocs/gc.md @@ -2,7 +2,7 @@ ## Introduction -Julia has a generational non-moving mark-sweep garbage collector. +Julia has a serial, stop-the-world, generational, non-moving mark-sweep garbage collector. Native objects are precisely scanned and foreign ones are conservatively marked. ## Memory layout of objects and GC bits @@ -15,7 +15,8 @@ Objects are aligned by a multiple of 4 bytes to ensure this pointer tagging is l ## Pool allocation -Sufficiently small objects (up to 2032 bytes) are pool-allocated. +Sufficiently small objects (up to 2032 bytes) are allocated on per-thread object +pools. A three-level tree (analogous to a three-level page-table) is used to keep metadata (e.g. whether a page has been allocated, whether contains marked objects, number of free objects etc.) From 2fc3b2962b60fb376c22e129e96b0f1c8a13295b Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Tue, 10 Jan 2023 01:27:41 -0500 Subject: [PATCH 273/387] irinterp: Fix extra_reprocess with loops and add control hook (#48199) Fixes a bug where the extra_reprocess argument was ignored once we switched to the looping code and also adds a hook to allow external absint that may have control-dependent lattice elements to enqueue additional statements to revisit during irinterp. --- base/compiler/ssair/irinterp.jl | 36 +++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index 631b00797f17c7..661f76b61fd846 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -173,9 +173,17 @@ function abstract_eval_phi_stmt(interp::AbstractInterpreter, phi::PhiNode, ::Int return abstract_eval_phi(interp, phi, nothing, irsv.ir) end +function propagate_control_effects!(interp::AbstractInterpreter, idx::Int, stmt::GotoIfNot, + irsv::IRInterpretationState, reprocess::Union{Nothing, BitSet, BitSetBoundedMinPrioritySet}) + # Nothing to do for most abstract interpreters, but if the abstract + # interpreter has control-dependent lattice effects, it can override + # this method. + return false +end + function reprocess_instruction!(interp::AbstractInterpreter, idx::Int, bb::Union{Int, Nothing}, @nospecialize(inst), @nospecialize(typ), - irsv::IRInterpretationState) + irsv::IRInterpretationState, reprocess::Union{Nothing, BitSet, BitSetBoundedMinPrioritySet}) ir = irsv.ir if isa(inst, GotoIfNot) cond = inst.cond @@ -222,7 +230,7 @@ function reprocess_instruction!(interp::AbstractInterpreter, end return true end - return false + return propagate_control_effects!(interp, idx, inst, irsv, reprocess) end rt = nothing @@ -308,8 +316,9 @@ function process_terminator!(ir::IRCode, idx::Int, bb::Int, end end +default_reprocess(interp::AbstractInterpreter, irsv::IRInterpretationState) = nothing function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IRInterpretationState; - extra_reprocess::Union{Nothing,BitSet} = nothing) + extra_reprocess::Union{Nothing,BitSet} = default_reprocess(interp, irsv)) (; ir, tpdum, ssa_refined) = irsv bbs = ir.cfg.blocks @@ -327,7 +336,13 @@ function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IR for idx = stmts inst = ir.stmts[idx][:inst] typ = ir.stmts[idx][:type] - any_refined = extra_reprocess === nothing ? false : (idx in extra_reprocess) + any_refined = false + if extra_reprocess !== nothing + if idx in extra_reprocess + pop!(extra_reprocess, idx) + any_refined = true + end + end for ur in userefs(inst) val = ur[] if isa(val, Argument) @@ -342,11 +357,13 @@ function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IR delete!(ssa_refined, idx) end if any_refined && reprocess_instruction!(interp, - idx, bb, inst, typ, irsv) + idx, bb, inst, typ, irsv, extra_reprocess) push!(ssa_refined, idx) end - if idx == lstmt && process_terminator!(ir, idx, bb, all_rets, ip) - @goto residual_scan + if idx == lstmt + if process_terminator!(ir, idx, bb, all_rets, ip) + @goto residual_scan + end end if typ === Bottom && !isa(inst, PhiNode) break @@ -358,6 +375,9 @@ function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IR # Slow path begin @label residual_scan stmt_ip = BitSetBoundedMinPrioritySet(length(ir.stmts)) + if extra_reprocess !== nothing + append!(stmt_ip, extra_reprocess) + end # Slow Path Phase 1.A: Complete use scanning while !isempty(ip) @@ -410,7 +430,7 @@ function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IR inst = ir.stmts[idx][:inst] typ = ir.stmts[idx][:type] if reprocess_instruction!(interp, - idx, nothing, inst, typ, irsv) + idx, nothing, inst, typ, irsv, stmt_ip) append!(stmt_ip, tpdum[idx]) end end From 5ba3f1b57685792bd2c0c5b5a09b7003f76f1e11 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Mon, 9 Jan 2023 22:16:27 +0000 Subject: [PATCH 274/387] Refine effects for T.instance We were already able to essentially fold this away, but we were incorrectly tainting :consistency. --- base/compiler/tfuncs.jl | 15 +++++++++++++-- test/compiler/effects.jl | 8 ++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 48aa4fbfa1b110..46c89c7a61785c 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -1226,6 +1226,16 @@ end end @nospecs function getfield_notundefined(typ0, name) + if isa(typ0, Const) && isa(name, Const) + namev = name.val + if isa(namev, Symbol) || isa(namev, Int) + # Fields are not allowed to transition from defined to undefined, so + # even if the field is not const, all we need to check here is that + # it is defined here. + return isdefined(typ0.val, namev) + end + end + typ0 = widenconst(typ0) typ = unwrap_unionall(typ0) if isa(typ, Union) return getfield_notundefined(rewrap_unionall(typ.a, typ0), name) && @@ -2172,13 +2182,14 @@ function getfield_effects(argtypes::Vector{Any}, @nospecialize(rt)) isempty(argtypes) && return EFFECTS_THROWS obj = argtypes[1] isvarargtype(obj) && return Effects(EFFECTS_THROWS; consistent=ALWAYS_FALSE) - consistent = is_immutable_argtype(obj) ? ALWAYS_TRUE : CONSISTENT_IF_INACCESSIBLEMEMONLY + consistent = (is_immutable_argtype(obj) || is_mutation_free_argtype(obj)) ? + ALWAYS_TRUE : CONSISTENT_IF_INACCESSIBLEMEMONLY # access to `isbitstype`-field initialized with undefined value leads to undefined behavior # so should taint `:consistent`-cy while access to uninitialized non-`isbitstype` field # throws `UndefRefError` so doesn't need to taint it # NOTE `getfield_notundefined` conservatively checks if this field is never initialized # with undefined value so that we don't taint `:consistent`-cy too aggressively here - if !(length(argtypes) ≥ 2 && getfield_notundefined(widenconst(obj), argtypes[2])) + if !(length(argtypes) ≥ 2 && getfield_notundefined(obj, argtypes[2])) consistent = ALWAYS_FALSE end nothrow = getfield_nothrow(argtypes, true) diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl index 54ce22421dc47d..12a4c8e11bae2c 100644 --- a/test/compiler/effects.jl +++ b/test/compiler/effects.jl @@ -676,3 +676,11 @@ mksparamunused(x) = (SparamUnused(x); nothing) let src = code_typed1(mksparamunused, (Any,)) @test count(isnew, src.code) == 0 end + +# Effects for getfield of type instance +struct WrapperOneField{T} + x::T +end +Base.infer_effects(Tuple{Nothing}) do x + WrapperOneField{typeof(x)}.instance +end |> Core.Compiler.is_total \ No newline at end of file From e3979486a28d126b642d9c20afb2adb2982c297f Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Mon, 9 Jan 2023 22:35:45 +0000 Subject: [PATCH 275/387] effects: Refine effects for getfield of unknown field When all fields are known initialized syntactically, we do not need to test whether accessing the fields will give an UndefRef. We only need to check for the fields that are not syntactically known to be initialized. As a result, this commit improves `:consistent`-cy of `getfield` call, which is better in general but leads to inference/inlining accuracy regression in some edge cases because now irinterp is enabled on more frames. There are two regressions, but we are fine with them so we modify the test cases: - inlining regression: irinterp ends up some extra junk basic blocks, that LLVM can optimize away down the road. - inference regressions: these regressions are all related to the `MustAlias` lattice extension, which was added for JET's use case especially and not enabled in base yet. Since JET doesn't enable irinterp, this commit marks the regressed cases as broken. --- base/compiler/tfuncs.jl | 2 +- test/compiler/effects.jl | 16 +++++++++++++--- test/compiler/inference.jl | 6 +++--- test/compiler/inline.jl | 10 ++++------ 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 46c89c7a61785c..be4157ddec72e7 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -1256,7 +1256,7 @@ end # initialized with undefined value so to avoid being too conservative fcnt = fieldcount_noerror(typ) fcnt === nothing && return false - all(i::Int->is_undefref_fieldtype(fieldtype(typ,i)), 1:fcnt) && return true + all(i::Int->is_undefref_fieldtype(fieldtype(typ,i)), (datatype_min_ninitialized(typ)+1):fcnt) && return true return false end name = name.val diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl index 12a4c8e11bae2c..b59df0778a1ec2 100644 --- a/test/compiler/effects.jl +++ b/test/compiler/effects.jl @@ -224,6 +224,10 @@ struct Maybe{T} end Base.getindex(x::Maybe) = x.x +struct SyntacticallyDefined{T} + x::T +end + import Core.Compiler: Const, getfield_notundefined for T = (Base.RefValue, Maybe) # both mutable and immutable for name = (Const(1), Const(:x)) @@ -265,6 +269,8 @@ for TupleType = Any[Tuple{Int,Int,Int}, Tuple{Int,Vararg{Int}}, Tuple{Any}, Tupl FieldType = Any[Int, Symbol, Any] @test getfield_notundefined(TupleType, FieldType) end +# skip analysis on fields that are known to be defined syntactically +@test Core.Compiler.getfield_notundefined(SyntacticallyDefined{Float64}, Symbol) # high-level tests for `getfield_notundefined` @test Base.infer_effects() do Maybe{Int}() @@ -303,6 +309,9 @@ let f() = Ref{String}()[] f() # this call should be concrete evaluated end |> only === Union{} end +@test Base.infer_effects((SyntacticallyDefined{Float64}, Symbol)) do w, s + getfield(w, s) +end |> Core.Compiler.is_foldable # effects propagation for `Core.invoke` calls # https://github.com/JuliaLang/julia/issues/44763 @@ -677,10 +686,11 @@ let src = code_typed1(mksparamunused, (Any,)) @test count(isnew, src.code) == 0 end -# Effects for getfield of type instance struct WrapperOneField{T} x::T end -Base.infer_effects(Tuple{Nothing}) do x + +# Effects for getfield of type instance +@test Base.infer_effects(Tuple{Nothing}) do x WrapperOneField{typeof(x)}.instance -end |> Core.Compiler.is_total \ No newline at end of file +end |> Core.Compiler.is_total diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 5adc1a0dc6c4c3..f91809d7cfe476 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -2446,7 +2446,7 @@ end |> only === Int # handle multiple call-site refinment targets isasome(_) = true isasome(::Nothing) = false -@test Base.return_types((AliasableField{Union{Int,Nothing}},); interp=MustAliasInterpreter()) do a +@test_broken Base.return_types((AliasableField{Union{Int,Nothing}},); interp=MustAliasInterpreter()) do a if isasome(a.f) return a.f end @@ -2523,13 +2523,13 @@ end |> only === Int end return 0 end |> only === Int -@test Base.return_types((AliasableField{Union{Nothing,Int}},); interp=MustAliasInterpreter()) do x +@test_broken Base.return_types((AliasableField{Union{Nothing,Int}},); interp=MustAliasInterpreter()) do x if !isnothing(x.f) return x.f end return 0 end |> only === Int -@test Base.return_types((AliasableField{Union{Some{Int},Nothing}},); interp=MustAliasInterpreter()) do x +@test_broken Base.return_types((AliasableField{Union{Some{Int},Nothing}},); interp=MustAliasInterpreter()) do x if !isnothing(x.f) return x.f end diff --git a/test/compiler/inline.jl b/test/compiler/inline.jl index 5991b67b1618b0..431a3fddd7fca2 100644 --- a/test/compiler/inline.jl +++ b/test/compiler/inline.jl @@ -307,13 +307,11 @@ end f_29115(x) = (x...,) @test @allocated(f_29115(1)) == 0 @test @allocated(f_29115(1=>2)) == 0 -let ci = code_typed(f_29115, Tuple{Int64})[1].first - @test length(ci.code) == 2 && isexpr(ci.code[1], :call) && - ci.code[1].args[1] === GlobalRef(Core, :tuple) +let src = code_typed(f_29115, Tuple{Int64}) |> only |> first + @test iscall((src, tuple), src.code[end-1]) end -let ci = code_typed(f_29115, Tuple{Pair{Int64, Int64}})[1].first - @test length(ci.code) == 4 && isexpr(ci.code[1], :call) && - ci.code[end-1].args[1] === GlobalRef(Core, :tuple) +let src = code_typed(f_29115, Tuple{Pair{Int64, Int64}}) |> only |> first + @test iscall((src, tuple), src.code[end-1]) end # Issue #37182 & #37555 - Inlining of pending nodes From 26b05884e097114b243ad95e17805bb3e91a51f5 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Mon, 9 Jan 2023 22:52:41 +0000 Subject: [PATCH 276/387] effects: Mark _typevar CONSISTENT_IF_NOTRETURNED Like other mutable allocation consistency here is flow-dependent. --- base/compiler/tfuncs.jl | 3 ++- test/compiler/effects.jl | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index be4157ddec72e7..743c777161db11 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -2249,7 +2249,8 @@ function builtin_effects(𝕃::AbstractLattice, @nospecialize(f::Builtin), argty effect_free = get_binding_type_effect_free(argtypes[1], argtypes[2]) ? ALWAYS_TRUE : ALWAYS_FALSE return Effects(EFFECTS_TOTAL; effect_free) else - consistent = contains_is(_CONSISTENT_BUILTINS, f) ? ALWAYS_TRUE : ALWAYS_FALSE + consistent = contains_is(_CONSISTENT_BUILTINS, f) ? ALWAYS_TRUE : + (f === Core._typevar) ? CONSISTENT_IF_NOTRETURNED : ALWAYS_FALSE if f === setfield! || f === arrayset effect_free = EFFECT_FREE_IF_INACCESSIBLEMEMONLY elseif contains_is(_EFFECT_FREE_BUILTINS, f) || contains_is(_PURE_BUILTINS, f) diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl index b59df0778a1ec2..9eb5927ce7aa3b 100644 --- a/test/compiler/effects.jl +++ b/test/compiler/effects.jl @@ -694,3 +694,8 @@ end @test Base.infer_effects(Tuple{Nothing}) do x WrapperOneField{typeof(x)}.instance end |> Core.Compiler.is_total + +# Flow-sensitive consistenct for _typevar +@test Base.infer_effects() do + return WrapperOneField == (WrapperOneField{T} where T) +end |> Core.Compiler.is_total From 7928591e900d25bde2213d85abde63bd7833cd59 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Sat, 7 Jan 2023 01:07:53 +0900 Subject: [PATCH 277/387] effects: taint `:consistent`-cy on `:inbounds` and `:boundscheck` exprs - taint `:consistent`-cy on `:boundscheck` expr - taint `:consistent`-cy on `:inbounds` expr N.B it turns out that we didn't taint it correctly before, since `jl_code_info_set_ir` encodes `:inbounds` expressions into `ssaflags` and eliminates them before abstract interpretation - improved `ntuple` effects slightly Since this commit ends up tainting `:consistent` of `getindex(::Tuple, ::Int)` too aggressively even for cases when the `getindex` call is known to safe, this commit also converts some `getindex(::Tuple, ::Int)` calls in Base to direct `getfield(::Tuple, i)` calls. --- base/compiler/abstractinterpretation.jl | 13 ++--- base/compiler/inferencestate.jl | 12 +---- base/compiler/tfuncs.jl | 2 +- base/math.jl | 22 ++++----- base/ntuple.jl | 8 +-- base/operators.jl | 66 ++++++++++++------------- base/special/exp.jl | 4 +- test/compiler/effects.jl | 18 +++++-- 8 files changed, 74 insertions(+), 71 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index dd9349a48aa527..2c52795ebd7a61 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2170,21 +2170,22 @@ function abstract_eval_cfunction(interp::AbstractInterpreter, e::Expr, vtypes::V end function abstract_eval_value_expr(interp::AbstractInterpreter, e::Expr, sv::Union{InferenceState, IRCode}) + rt = Any head = e.head if head === :static_parameter n = e.args[1]::Int - t = Any if 1 <= n <= length(sv.sptypes) - t = sv.sptypes[n] + rt = sv.sptypes[n] end - return t elseif head === :boundscheck - return Bool + merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE, noinbounds=false)) + rt = Bool + elseif head === :inbounds + merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE, noinbounds=false)) elseif head === :the_exception merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE)) - return Any end - return Any + return rt end function abstract_eval_special_value(interp::AbstractInterpreter, @nospecialize(e), vtypes::Union{VarTable, Nothing}, sv::Union{InferenceState, IRCode}) diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index df65d6668df3d3..e66f1335531429 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -184,7 +184,7 @@ mutable struct InferenceState # are stronger than the inbounds assumptions, since the latter # requires dynamic reachability, while the former is global). inbounds = inbounds_option() - noinbounds = inbounds === :on || (inbounds === :default && !any_inbounds(code)) + noinbounds = inbounds === :on || (inbounds === :default && all(flag::UInt8->iszero(flag&IR_FLAG_INBOUNDS), src.ssaflags)) consistent = noinbounds ? ALWAYS_TRUE : ALWAYS_FALSE ipo_effects = Effects(EFFECTS_TOTAL; consistent, noinbounds) @@ -240,16 +240,6 @@ function bail_out_apply(::AbstractInterpreter, @nospecialize(rt), sv::Union{Infe return rt === Any end -function any_inbounds(code::Vector{Any}) - for i = 1:length(code) - stmt = code[i] - if isexpr(stmt, :inbounds) - return true - end - end - return false -end - was_reached(sv::InferenceState, pc::Int) = sv.ssavaluetypes[pc] !== NOT_FOUND function compute_trycatch(code::Vector{Any}, ip::BitSet) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 743c777161db11..726381d718573f 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -2197,7 +2197,7 @@ function getfield_effects(argtypes::Vector{Any}, @nospecialize(rt)) # If we cannot independently prove inboundsness, taint consistency. # The inbounds-ness assertion requires dynamic reachability, while # :consistent needs to be true for all input values. - # N.B. We do not taint for `--check-bounds=no` here -that happens in + # N.B. We do not taint for `--check-bounds=no` here that happens in # InferenceState. consistent = ALWAYS_FALSE end diff --git a/base/math.jl b/base/math.jl index f41057c76cfc2c..56aeb07bbe9021 100644 --- a/base/math.jl +++ b/base/math.jl @@ -177,9 +177,9 @@ julia> evalpoly(2, (1, 2, 3)) function evalpoly(x, p::Tuple) if @generated N = length(p.parameters::Core.SimpleVector) - ex = :(p[end]) + ex = :(getfield(p,$N)) for i in N-1:-1:1 - ex = :(muladd(x, $ex, p[$i])) + ex = :(muladd(x, $ex, getfield(p,$i))) end ex else @@ -192,7 +192,7 @@ evalpoly(x, p::AbstractVector) = _evalpoly(x, p) function _evalpoly(x, p) Base.require_one_based_indexing(p) N = length(p) - ex = p[end] + ex = p[N] for i in N-1:-1:1 ex = muladd(x, ex, p[i]) end @@ -202,14 +202,14 @@ end function evalpoly(z::Complex, p::Tuple) if @generated N = length(p.parameters) - a = :(p[end]) - b = :(p[end-1]) + a = :(getfield(p,$N)) + b = :(getfield(p,$N-1)) as = [] for i in N-2:-1:1 ai = Symbol("a", i) push!(as, :($ai = $a)) a = :(muladd(r, $ai, $b)) - b = :(muladd(-s, $ai, p[$i])) + b = :(muladd(-s, $ai, getfield(p,$i))) end ai = :a0 push!(as, :($ai = $a)) @@ -224,8 +224,7 @@ function evalpoly(z::Complex, p::Tuple) _evalpoly(z, p) end end -evalpoly(z::Complex, p::Tuple{<:Any}) = p[1] - +evalpoly(z::Complex, p::Tuple{<:Any}) = getfield(p,1) evalpoly(z::Complex, p::AbstractVector) = _evalpoly(z, p) @@ -296,9 +295,10 @@ end # polynomial evaluation using compensated summation. # much more accurate, especially when lo can be combined with other rounding errors Base.@assume_effects :terminates_locally @inline function exthorner(x, p::Tuple) - hi, lo = p[end], zero(x) - for i in length(p)-1:-1:1 - pi = getfield(p, i) # needed to prove consistency + N = length(p) + hi, lo = getfield(p,N), zero(x) + for i in N-1:-1:1 + pi = getfield(p,i) # needed to prove consistency prod, err = two_mul(hi,x) hi = pi+prod lo = fma(lo, x, prod - (hi - pi) + err) diff --git a/base/ntuple.jl b/base/ntuple.jl index 443409f37f87af..7391b86154ac41 100644 --- a/base/ntuple.jl +++ b/base/ntuple.jl @@ -33,13 +33,13 @@ end function _ntuple(f::F, n) where F @noinline - (n >= 0) || throw(ArgumentError(string("tuple length should be ≥ 0, got ", n))) + (n >= 0) || throw(ArgumentError(LazyString("tuple length should be ≥ 0, got ", n))) ([f(i) for i = 1:n]...,) end function ntupleany(f, n) @noinline - (n >= 0) || throw(ArgumentError(string("tuple length should be ≥ 0, got ", n))) + (n >= 0) || throw(ArgumentError(LazyString("tuple length should be ≥ 0, got ", n))) (Any[f(i) for i = 1:n]...,) end @@ -68,7 +68,7 @@ julia> ntuple(i -> 2*i, Val(4)) """ @inline function ntuple(f::F, ::Val{N}) where {F,N} N::Int - (N >= 0) || throw(ArgumentError(string("tuple length should be ≥ 0, got ", N))) + (N >= 0) || throw(ArgumentError(LazyString("tuple length should be ≥ 0, got ", N))) if @generated :(@ntuple $N i -> f(i)) else @@ -79,7 +79,7 @@ end @inline function fill_to_length(t::Tuple, val, ::Val{_N}) where {_N} M = length(t) N = _N::Int - M > N && throw(ArgumentError("input tuple of length $M, requested $N")) + M > N && throw(ArgumentError(LazyString("input tuple of length ", M, ", requested ", N))) if @generated quote (t..., $(fill(:val, (_N::Int) - length(t.parameters))...)) diff --git a/base/operators.jl b/base/operators.jl index 9922305e14a3e9..5c6846b1e59ce2 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -531,40 +531,40 @@ afoldl(op, a) = a function afoldl(op, a, bs...) l = length(bs) i = 0; y = a; l == i && return y - #@nexprs 31 i -> (y = op(y, bs[i]); l == i && return y) - i = 1; y = op(y, bs[i]); l == i && return y - i = 2; y = op(y, bs[i]); l == i && return y - i = 3; y = op(y, bs[i]); l == i && return y - i = 4; y = op(y, bs[i]); l == i && return y - i = 5; y = op(y, bs[i]); l == i && return y - i = 6; y = op(y, bs[i]); l == i && return y - i = 7; y = op(y, bs[i]); l == i && return y - i = 8; y = op(y, bs[i]); l == i && return y - i = 9; y = op(y, bs[i]); l == i && return y - i = 10; y = op(y, bs[i]); l == i && return y - i = 11; y = op(y, bs[i]); l == i && return y - i = 12; y = op(y, bs[i]); l == i && return y - i = 13; y = op(y, bs[i]); l == i && return y - i = 14; y = op(y, bs[i]); l == i && return y - i = 15; y = op(y, bs[i]); l == i && return y - i = 16; y = op(y, bs[i]); l == i && return y - i = 17; y = op(y, bs[i]); l == i && return y - i = 18; y = op(y, bs[i]); l == i && return y - i = 19; y = op(y, bs[i]); l == i && return y - i = 20; y = op(y, bs[i]); l == i && return y - i = 21; y = op(y, bs[i]); l == i && return y - i = 22; y = op(y, bs[i]); l == i && return y - i = 23; y = op(y, bs[i]); l == i && return y - i = 24; y = op(y, bs[i]); l == i && return y - i = 25; y = op(y, bs[i]); l == i && return y - i = 26; y = op(y, bs[i]); l == i && return y - i = 27; y = op(y, bs[i]); l == i && return y - i = 28; y = op(y, bs[i]); l == i && return y - i = 29; y = op(y, bs[i]); l == i && return y - i = 30; y = op(y, bs[i]); l == i && return y - i = 31; y = op(y, bs[i]); l == i && return y + #@nexprs 31 i -> (y = op(y, getfield(bs,i)); l == i && return y) + i = 1; y = op(y, getfield(bs,i)); l == i && return y + i = 2; y = op(y, getfield(bs,i)); l == i && return y + i = 3; y = op(y, getfield(bs,i)); l == i && return y + i = 4; y = op(y, getfield(bs,i)); l == i && return y + i = 5; y = op(y, getfield(bs,i)); l == i && return y + i = 6; y = op(y, getfield(bs,i)); l == i && return y + i = 7; y = op(y, getfield(bs,i)); l == i && return y + i = 8; y = op(y, getfield(bs,i)); l == i && return y + i = 9; y = op(y, getfield(bs,i)); l == i && return y + i = 10; y = op(y, getfield(bs,i)); l == i && return y + i = 11; y = op(y, getfield(bs,i)); l == i && return y + i = 12; y = op(y, getfield(bs,i)); l == i && return y + i = 13; y = op(y, getfield(bs,i)); l == i && return y + i = 14; y = op(y, getfield(bs,i)); l == i && return y + i = 15; y = op(y, getfield(bs,i)); l == i && return y + i = 16; y = op(y, getfield(bs,i)); l == i && return y + i = 17; y = op(y, getfield(bs,i)); l == i && return y + i = 18; y = op(y, getfield(bs,i)); l == i && return y + i = 19; y = op(y, getfield(bs,i)); l == i && return y + i = 20; y = op(y, getfield(bs,i)); l == i && return y + i = 21; y = op(y, getfield(bs,i)); l == i && return y + i = 22; y = op(y, getfield(bs,i)); l == i && return y + i = 23; y = op(y, getfield(bs,i)); l == i && return y + i = 24; y = op(y, getfield(bs,i)); l == i && return y + i = 25; y = op(y, getfield(bs,i)); l == i && return y + i = 26; y = op(y, getfield(bs,i)); l == i && return y + i = 27; y = op(y, getfield(bs,i)); l == i && return y + i = 28; y = op(y, getfield(bs,i)); l == i && return y + i = 29; y = op(y, getfield(bs,i)); l == i && return y + i = 30; y = op(y, getfield(bs,i)); l == i && return y + i = 31; y = op(y, getfield(bs,i)); l == i && return y for i in (i + 1):l - y = op(y, bs[i]) + y = op(y, getfield(bs,i)) end return y end diff --git a/base/special/exp.jl b/base/special/exp.jl index 42ad4bf7e073fd..4d54650f18efa3 100644 --- a/base/special/exp.jl +++ b/base/special/exp.jl @@ -415,13 +415,13 @@ Ln2(::Type{Float32}) = -0.6931472f0 0.001388888889068783, 0.00019841269447671544, 2.480157691845342e-5, 2.7558212415361945e-6, 2.758218402815439e-7, 2.4360682937111612e-8)) p2 = exthorner(x, (1.0, .5, p)) - return fma(x, p2[1], x*p2[2]) + return fma(x, getfield(p2,1), x*getfield(p2,2)) end @inline function expm1_small(x::Float32) p = evalpoly(x, (0.16666666f0, 0.041666627f0, 0.008333682f0, 0.0013908712f0, 0.0001933096f0)) p2 = exthorner(x, (1f0, .5f0, p)) - return fma(x, p2[1], x*p2[2]) + return fma(x, getfield(p2,1), x*getfield(p2,2)) end function expm1(x::Float64) diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl index 9eb5927ce7aa3b..84c43b09a7d176 100644 --- a/test/compiler/effects.jl +++ b/test/compiler/effects.jl @@ -323,15 +323,27 @@ invoke44763(x) = @invoke increase_x44763!(x) end |> only === Int @test x44763 == 0 +# `@inbounds`/`@boundscheck` expression should taint :consistent-cy correctly +# https://github.com/JuliaLang/julia/issues/48099 +function A1_inbounds() + r = 0 + @inbounds begin + @boundscheck r += 1 + end + return r +end +@test !Core.Compiler.is_consistent(Base.infer_effects(A1_inbounds)) + # Test that purity doesn't try to accidentally run unreachable code due to # boundscheck elimination function f_boundscheck_elim(n) - # Inbounds here assumes that this is only ever called with n==0, but of + # Inbounds here assumes that this is only ever called with `n==0`, but of # course the compiler has no way of knowing that, so it must not attempt - # to run the @inbounds `getfield(sin, 1)`` that ntuple generates. + # to run the `@inbounds getfield(sin, 1)` that `ntuple` generates. ntuple(x->(@inbounds getfield(sin, x)), n) end -@test Tuple{} <: code_typed(f_boundscheck_elim, Tuple{Int})[1][2] +@test !Core.Compiler.is_consistent(Base.infer_effects(f_boundscheck_elim, (Int,))) +@test Tuple{} <: only(Base.return_types(f_boundscheck_elim, (Int,))) # Test that purity modeling doesn't accidentally introduce new world age issues f_redefine_me(x) = x+1 From e9f44a9be08b97740d8c99237656f59e7eec6915 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Tue, 10 Jan 2023 17:39:42 +0900 Subject: [PATCH 278/387] add a hack to avoid tainting `:consistent`-cy of `getfield(::Tuple, ::Int)` too aggressively --- base/compiler/abstractinterpretation.jl | 18 +++++-- base/math.jl | 22 ++++----- base/operators.jl | 66 ++++++++++++------------- base/special/exp.jl | 4 +- 4 files changed, 61 insertions(+), 49 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 2c52795ebd7a61..f1260085cd03fa 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2169,7 +2169,7 @@ function abstract_eval_cfunction(interp::AbstractInterpreter, e::Expr, vtypes::V nothing end -function abstract_eval_value_expr(interp::AbstractInterpreter, e::Expr, sv::Union{InferenceState, IRCode}) +function abstract_eval_value_expr(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable, Nothing}, sv::Union{InferenceState, IRCode}) rt = Any head = e.head if head === :static_parameter @@ -2178,7 +2178,19 @@ function abstract_eval_value_expr(interp::AbstractInterpreter, e::Expr, sv::Unio rt = sv.sptypes[n] end elseif head === :boundscheck + if isa(sv, InferenceState) + stmt = sv.src.code[sv.currpc] + if isexpr(stmt, :call) + f = abstract_eval_value(interp, stmt.args[1], vtypes, sv) + if f isa Const && f.val === getfield + # boundscheck of `getfield` call is analyzed by tfunc potentially without + # tainting :consistent-cy when it's known to be nothrow + @goto delay_effects_analysis + end + end + end merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE, noinbounds=false)) + @label delay_effects_analysis rt = Bool elseif head === :inbounds merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE, noinbounds=false)) @@ -2215,7 +2227,7 @@ end function abstract_eval_value(interp::AbstractInterpreter, @nospecialize(e), vtypes::Union{VarTable, Nothing}, sv::Union{InferenceState, IRCode}) if isa(e, Expr) - return abstract_eval_value_expr(interp, e, sv) + return abstract_eval_value_expr(interp, e, vtypes, sv) else typ = abstract_eval_special_value(interp, e, vtypes, sv) return collect_limitations!(typ, sv) @@ -2444,7 +2456,7 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp effects = EFFECTS_THROWS merge_effects!(interp, sv, effects) else - t = abstract_eval_value_expr(interp, e, sv) + t = abstract_eval_value_expr(interp, e, vtypes, sv) end return RTEffects(t, effects) end diff --git a/base/math.jl b/base/math.jl index 56aeb07bbe9021..f41057c76cfc2c 100644 --- a/base/math.jl +++ b/base/math.jl @@ -177,9 +177,9 @@ julia> evalpoly(2, (1, 2, 3)) function evalpoly(x, p::Tuple) if @generated N = length(p.parameters::Core.SimpleVector) - ex = :(getfield(p,$N)) + ex = :(p[end]) for i in N-1:-1:1 - ex = :(muladd(x, $ex, getfield(p,$i))) + ex = :(muladd(x, $ex, p[$i])) end ex else @@ -192,7 +192,7 @@ evalpoly(x, p::AbstractVector) = _evalpoly(x, p) function _evalpoly(x, p) Base.require_one_based_indexing(p) N = length(p) - ex = p[N] + ex = p[end] for i in N-1:-1:1 ex = muladd(x, ex, p[i]) end @@ -202,14 +202,14 @@ end function evalpoly(z::Complex, p::Tuple) if @generated N = length(p.parameters) - a = :(getfield(p,$N)) - b = :(getfield(p,$N-1)) + a = :(p[end]) + b = :(p[end-1]) as = [] for i in N-2:-1:1 ai = Symbol("a", i) push!(as, :($ai = $a)) a = :(muladd(r, $ai, $b)) - b = :(muladd(-s, $ai, getfield(p,$i))) + b = :(muladd(-s, $ai, p[$i])) end ai = :a0 push!(as, :($ai = $a)) @@ -224,7 +224,8 @@ function evalpoly(z::Complex, p::Tuple) _evalpoly(z, p) end end -evalpoly(z::Complex, p::Tuple{<:Any}) = getfield(p,1) +evalpoly(z::Complex, p::Tuple{<:Any}) = p[1] + evalpoly(z::Complex, p::AbstractVector) = _evalpoly(z, p) @@ -295,10 +296,9 @@ end # polynomial evaluation using compensated summation. # much more accurate, especially when lo can be combined with other rounding errors Base.@assume_effects :terminates_locally @inline function exthorner(x, p::Tuple) - N = length(p) - hi, lo = getfield(p,N), zero(x) - for i in N-1:-1:1 - pi = getfield(p,i) # needed to prove consistency + hi, lo = p[end], zero(x) + for i in length(p)-1:-1:1 + pi = getfield(p, i) # needed to prove consistency prod, err = two_mul(hi,x) hi = pi+prod lo = fma(lo, x, prod - (hi - pi) + err) diff --git a/base/operators.jl b/base/operators.jl index 5c6846b1e59ce2..9922305e14a3e9 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -531,40 +531,40 @@ afoldl(op, a) = a function afoldl(op, a, bs...) l = length(bs) i = 0; y = a; l == i && return y - #@nexprs 31 i -> (y = op(y, getfield(bs,i)); l == i && return y) - i = 1; y = op(y, getfield(bs,i)); l == i && return y - i = 2; y = op(y, getfield(bs,i)); l == i && return y - i = 3; y = op(y, getfield(bs,i)); l == i && return y - i = 4; y = op(y, getfield(bs,i)); l == i && return y - i = 5; y = op(y, getfield(bs,i)); l == i && return y - i = 6; y = op(y, getfield(bs,i)); l == i && return y - i = 7; y = op(y, getfield(bs,i)); l == i && return y - i = 8; y = op(y, getfield(bs,i)); l == i && return y - i = 9; y = op(y, getfield(bs,i)); l == i && return y - i = 10; y = op(y, getfield(bs,i)); l == i && return y - i = 11; y = op(y, getfield(bs,i)); l == i && return y - i = 12; y = op(y, getfield(bs,i)); l == i && return y - i = 13; y = op(y, getfield(bs,i)); l == i && return y - i = 14; y = op(y, getfield(bs,i)); l == i && return y - i = 15; y = op(y, getfield(bs,i)); l == i && return y - i = 16; y = op(y, getfield(bs,i)); l == i && return y - i = 17; y = op(y, getfield(bs,i)); l == i && return y - i = 18; y = op(y, getfield(bs,i)); l == i && return y - i = 19; y = op(y, getfield(bs,i)); l == i && return y - i = 20; y = op(y, getfield(bs,i)); l == i && return y - i = 21; y = op(y, getfield(bs,i)); l == i && return y - i = 22; y = op(y, getfield(bs,i)); l == i && return y - i = 23; y = op(y, getfield(bs,i)); l == i && return y - i = 24; y = op(y, getfield(bs,i)); l == i && return y - i = 25; y = op(y, getfield(bs,i)); l == i && return y - i = 26; y = op(y, getfield(bs,i)); l == i && return y - i = 27; y = op(y, getfield(bs,i)); l == i && return y - i = 28; y = op(y, getfield(bs,i)); l == i && return y - i = 29; y = op(y, getfield(bs,i)); l == i && return y - i = 30; y = op(y, getfield(bs,i)); l == i && return y - i = 31; y = op(y, getfield(bs,i)); l == i && return y + #@nexprs 31 i -> (y = op(y, bs[i]); l == i && return y) + i = 1; y = op(y, bs[i]); l == i && return y + i = 2; y = op(y, bs[i]); l == i && return y + i = 3; y = op(y, bs[i]); l == i && return y + i = 4; y = op(y, bs[i]); l == i && return y + i = 5; y = op(y, bs[i]); l == i && return y + i = 6; y = op(y, bs[i]); l == i && return y + i = 7; y = op(y, bs[i]); l == i && return y + i = 8; y = op(y, bs[i]); l == i && return y + i = 9; y = op(y, bs[i]); l == i && return y + i = 10; y = op(y, bs[i]); l == i && return y + i = 11; y = op(y, bs[i]); l == i && return y + i = 12; y = op(y, bs[i]); l == i && return y + i = 13; y = op(y, bs[i]); l == i && return y + i = 14; y = op(y, bs[i]); l == i && return y + i = 15; y = op(y, bs[i]); l == i && return y + i = 16; y = op(y, bs[i]); l == i && return y + i = 17; y = op(y, bs[i]); l == i && return y + i = 18; y = op(y, bs[i]); l == i && return y + i = 19; y = op(y, bs[i]); l == i && return y + i = 20; y = op(y, bs[i]); l == i && return y + i = 21; y = op(y, bs[i]); l == i && return y + i = 22; y = op(y, bs[i]); l == i && return y + i = 23; y = op(y, bs[i]); l == i && return y + i = 24; y = op(y, bs[i]); l == i && return y + i = 25; y = op(y, bs[i]); l == i && return y + i = 26; y = op(y, bs[i]); l == i && return y + i = 27; y = op(y, bs[i]); l == i && return y + i = 28; y = op(y, bs[i]); l == i && return y + i = 29; y = op(y, bs[i]); l == i && return y + i = 30; y = op(y, bs[i]); l == i && return y + i = 31; y = op(y, bs[i]); l == i && return y for i in (i + 1):l - y = op(y, getfield(bs,i)) + y = op(y, bs[i]) end return y end diff --git a/base/special/exp.jl b/base/special/exp.jl index 4d54650f18efa3..42ad4bf7e073fd 100644 --- a/base/special/exp.jl +++ b/base/special/exp.jl @@ -415,13 +415,13 @@ Ln2(::Type{Float32}) = -0.6931472f0 0.001388888889068783, 0.00019841269447671544, 2.480157691845342e-5, 2.7558212415361945e-6, 2.758218402815439e-7, 2.4360682937111612e-8)) p2 = exthorner(x, (1.0, .5, p)) - return fma(x, getfield(p2,1), x*getfield(p2,2)) + return fma(x, p2[1], x*p2[2]) end @inline function expm1_small(x::Float32) p = evalpoly(x, (0.16666666f0, 0.041666627f0, 0.008333682f0, 0.0013908712f0, 0.0001933096f0)) p2 = exthorner(x, (1f0, .5f0, p)) - return fma(x, getfield(p2,1), x*getfield(p2,2)) + return fma(x, p2[1], x*p2[2]) end function expm1(x::Float64) From 79eb7be6f7383666a32f0f74c3b9c2bb31ffd773 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Sat, 7 Jan 2023 01:29:14 +0900 Subject: [PATCH 279/387] fix boundscheck tests Co-Authored-By: Shawn LeMaster --- test/boundscheck.jl | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/test/boundscheck.jl b/test/boundscheck.jl index 09cc8d2cd13e85..ad7f50a84e0867 100644 --- a/test/boundscheck.jl +++ b/test/boundscheck.jl @@ -2,17 +2,14 @@ # run boundscheck tests on separate workers launched with --check-bounds={default,yes,no} -cmd = `$(Base.julia_cmd()) --depwarn=error --startup-file=no boundscheck_exec.jl` -if !success(pipeline(cmd; stdout=stdout, stderr=stderr)) - error("boundscheck test failed, cmd : $cmd") +let cmd = `$(Base.julia_cmd()) --check-bounds=auto --depwarn=error --startup-file=no boundscheck_exec.jl` + success(pipeline(cmd; stdout=stdout, stderr=stderr)) || error("boundscheck test failed, cmd : $cmd") end -cmd = `$(Base.julia_cmd()) --check-bounds=yes --startup-file=no --depwarn=error boundscheck_exec.jl` -if !success(pipeline(cmd; stdout=stdout, stderr=stderr)) - error("boundscheck test failed, cmd : $cmd") +let cmd = `$(Base.julia_cmd()) --check-bounds=yes --startup-file=no --depwarn=error boundscheck_exec.jl` + success(pipeline(cmd; stdout=stdout, stderr=stderr)) || error("boundscheck test failed, cmd : $cmd") end -cmd = `$(Base.julia_cmd()) --check-bounds=no --startup-file=no --depwarn=error boundscheck_exec.jl` -if !success(pipeline(cmd; stdout=stdout, stderr=stderr)) - error("boundscheck test failed, cmd : $cmd") +let cmd = `$(Base.julia_cmd()) --check-bounds=no --startup-file=no --depwarn=error boundscheck_exec.jl` + success(pipeline(cmd; stdout=stdout, stderr=stderr)) || error("boundscheck test failed, cmd : $cmd") end From 8c131d439d36cf0c3954ed292b44240daf9d4e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mos=C3=A8=20Giordano?= Date: Tue, 10 Jan 2023 10:24:31 +0000 Subject: [PATCH 280/387] Fix `top_set_bit` docstring and use it in more places (#48201) * Fix `top_set_bit` docstring and use it in more places * Fix `top_set_bit` doctest now that it's actually running Co-authored-by: Oscar Smith --- base/int.jl | 7 ++++--- base/sort.jl | 4 ++-- stdlib/Random/src/generation.jl | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/base/int.jl b/base/int.jl index 5612f4d7f06274..4b2f542bba7882 100644 --- a/base/int.jl +++ b/base/int.jl @@ -496,14 +496,15 @@ See also: [`ndigits0z`](@ref), [`ndigits`](@ref). # Examples ```jldoctest -julia> top_set_bit(4) +julia> Base.top_set_bit(4) 3 -julia> top_set_bit(0) +julia> Base.top_set_bit(0) 0 -julia> top_set_bit(-1) +julia> Base.top_set_bit(-1) 64 +``` """ top_set_bit(x::BitInteger) = 8sizeof(x) - leading_zeros(x) diff --git a/base/sort.jl b/base/sort.jl index 1f8a6797427819..d6ae6aa6e2e101 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -885,7 +885,7 @@ ConsiderRadixSort(next) = ConsiderRadixSort(RadixSort(), next) function _sort!(v::AbstractVector, a::ConsiderRadixSort, o::DirectOrdering, kw) @getkw lo hi mn mx urange = uint_map(mx, o)-uint_map(mn, o) - bits = unsigned(8sizeof(urange) - leading_zeros(urange)) + bits = unsigned(top_set_bit(urange)) if sizeof(eltype(v)) <= 8 && bits+70 < 22log(hi-lo) _sort!(v, a.radix, o, kw) else @@ -920,7 +920,7 @@ function _sort!(v::AbstractVector, a::RadixSort, o::DirectOrdering, kw) @getkw lo hi mn mx scratch umn = uint_map(mn, o) urange = uint_map(mx, o)-umn - bits = unsigned(8sizeof(urange) - leading_zeros(urange)) + bits = unsigned(top_set_bit(urange)) # At this point, we are committed to radix sort. u = uint_map!(v, lo, hi, o) diff --git a/stdlib/Random/src/generation.jl b/stdlib/Random/src/generation.jl index 6fe5b585b088f2..cc9840f6784136 100644 --- a/stdlib/Random/src/generation.jl +++ b/stdlib/Random/src/generation.jl @@ -212,7 +212,7 @@ SamplerRangeFast(r::AbstractUnitRange{T}) where T<:BitInteger = function SamplerRangeFast(r::AbstractUnitRange{T}, ::Type{U}) where {T,U} isempty(r) && throw(ArgumentError("collection must be non-empty")) m = (last(r) - first(r)) % unsigned(T) % U # % unsigned(T) to not propagate sign bit - bw = (sizeof(U) << 3 - leading_zeros(m)) % UInt # bit-width + bw = (Base.top_set_bit(m)) % UInt # bit-width mask = ((1 % U) << bw) - (1 % U) SamplerRangeFast{U,T}(first(r), bw, m, mask) end @@ -288,7 +288,7 @@ function SamplerRangeInt(r::AbstractUnitRange{T}, ::Type{U}) where {T,U} a = first(r) m = (last(r) - first(r)) % unsigned(T) % U k = m + one(U) - bw = (sizeof(U) << 3 - leading_zeros(m)) % Int + bw = (Base.top_set_bit(m)) % Int mult = if U === UInt32 maxmultiple(k) elseif U === UInt64 From 748149efb3b43e732eab8e9e4276e0ee2bcfa65b Mon Sep 17 00:00:00 2001 From: N5N3 <2642243996@qq.com> Date: Sun, 1 Jan 2023 20:21:09 +0800 Subject: [PATCH 281/387] Only merge vars occur in the local union decision. If we always merge the whole env, then the output bounds would be widen than input if different Union decision touch different vars. Also add missing `occurs_inv/cov`'s merge (by max). --- src/subtype.c | 134 ++++++++++++++++++++++++++++++++++++++---------- test/subtype.jl | 7 +++ 2 files changed, 115 insertions(+), 26 deletions(-) diff --git a/src/subtype.c b/src/subtype.c index 4f2cf7a5c60570..231cad0a84dce4 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -65,6 +65,7 @@ typedef struct jl_varbinding_t { jl_value_t *lb; jl_value_t *ub; int8_t right; // whether this variable came from the right side of `A <: B` + int8_t occurs; // occurs in any position int8_t occurs_inv; // occurs in invariant position int8_t occurs_cov; // # of occurrences in covariant position int8_t concrete; // 1 if another variable has a constraint forcing this one to be concrete @@ -161,7 +162,7 @@ static void statestack_set(jl_unionstate_t *st, int i, int val) JL_NOTSAFEPOINT typedef struct { int8_t *buf; int rdepth; - int8_t _space[16]; + int8_t _space[24]; } jl_savedenv_t; static void save_env(jl_stenv_t *e, jl_value_t **root, jl_savedenv_t *se) @@ -174,9 +175,9 @@ static void save_env(jl_stenv_t *e, jl_value_t **root, jl_savedenv_t *se) } if (root) *root = (jl_value_t*)jl_alloc_svec(len * 3); - se->buf = (int8_t*)(len > 8 ? malloc_s(len * 2) : &se->_space); + se->buf = (int8_t*)(len > 8 ? malloc_s(len * 3) : &se->_space); #ifdef __clang_gcanalyzer__ - memset(se->buf, 0, len * 2); + memset(se->buf, 0, len * 3); #endif int i=0, j=0; v = e->vars; while (v != NULL) { @@ -185,6 +186,7 @@ static void save_env(jl_stenv_t *e, jl_value_t **root, jl_savedenv_t *se) jl_svecset(*root, i++, v->ub); jl_svecset(*root, i++, (jl_value_t*)v->innervars); } + se->buf[j++] = v->occurs; se->buf[j++] = v->occurs_inv; se->buf[j++] = v->occurs_cov; v = v->prev; @@ -207,6 +209,7 @@ static void restore_env(jl_stenv_t *e, jl_value_t *root, jl_savedenv_t *se) JL_N if (root) v->lb = jl_svecref(root, i++); if (root) v->ub = jl_svecref(root, i++); if (root) v->innervars = (jl_array_t*)jl_svecref(root, i++); + v->occurs = se->buf[j++]; v->occurs_inv = se->buf[j++]; v->occurs_cov = se->buf[j++]; v = v->prev; @@ -227,6 +230,15 @@ static int current_env_length(jl_stenv_t *e) return len; } +static void clean_occurs(jl_stenv_t *e) +{ + jl_varbinding_t *v = e->vars; + while (v) { + v->occurs = 0; + v = v->prev; + } +} + // type utilities // quickly test that two types are identical @@ -590,6 +602,8 @@ static int subtype_left_var(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int par // of determining whether the variable is concrete. static void record_var_occurrence(jl_varbinding_t *vb, jl_stenv_t *e, int param) JL_NOTSAFEPOINT { + if (vb != NULL) + vb->occurs = 1; if (vb != NULL && param) { // saturate counters at 2; we don't need values bigger than that if (param == 2 && (vb->right ? e->Rinvdepth : e->invdepth) > vb->depth0) { @@ -782,7 +796,7 @@ static jl_unionall_t *unalias_unionall(jl_unionall_t *u, jl_stenv_t *e) static int subtype_unionall(jl_value_t *t, jl_unionall_t *u, jl_stenv_t *e, int8_t R, int param) { u = unalias_unionall(u, e); - jl_varbinding_t vb = { u->var, u->var->lb, u->var->ub, R, 0, 0, 0, 0, 0, 0, + jl_varbinding_t vb = { u->var, u->var->lb, u->var->ub, R, 0, 0, 0, 0, 0, 0, 0, R ? e->Rinvdepth : e->invdepth, 0, NULL, e->vars }; JL_GC_PUSH4(&u, &vb.lb, &vb.ub, &vb.innervars); e->vars = &vb; @@ -2741,7 +2755,7 @@ static jl_value_t *intersect_unionall(jl_value_t *t, jl_unionall_t *u, jl_stenv_ { jl_value_t *res=NULL, *save=NULL; jl_savedenv_t se; - jl_varbinding_t vb = { u->var, u->var->lb, u->var->ub, R, 0, 0, 0, 0, 0, 0, + jl_varbinding_t vb = { u->var, u->var->lb, u->var->ub, R, 0, 0, 0, 0, 0, 0, 0, R ? e->Rinvdepth : e->invdepth, 0, NULL, e->vars }; JL_GC_PUSH5(&res, &vb.lb, &vb.ub, &save, &vb.innervars); save_env(e, &save, &se); @@ -2754,13 +2768,13 @@ static jl_value_t *intersect_unionall(jl_value_t *t, jl_unionall_t *u, jl_stenv_ else if (res != jl_bottom_type) { if (vb.concrete || vb.occurs_inv>1 || vb.intvalued > 1 || u->var->lb != jl_bottom_type || (vb.occurs_inv && vb.occurs_cov)) { restore_env(e, NULL, &se); - vb.occurs_cov = vb.occurs_inv = 0; + vb.occurs = vb.occurs_cov = vb.occurs_inv = 0; vb.constraintkind = vb.concrete ? 1 : 2; res = intersect_unionall_(t, u, e, R, param, &vb); } else if (vb.occurs_cov && !var_occurs_invariant(u->body, u->var, 0)) { restore_env(e, save, &se); - vb.occurs_cov = vb.occurs_inv = 0; + vb.occurs = vb.occurs_cov = vb.occurs_inv = 0; vb.constraintkind = 1; res = intersect_unionall_(t, u, e, R, param, &vb); } @@ -3271,29 +3285,42 @@ static jl_value_t *intersect(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int pa static int merge_env(jl_stenv_t *e, jl_value_t **root, jl_savedenv_t *se, int count) { - if (!count) { - save_env(e, root, se); - return 1; + if (count == 0) { + int len = current_env_length(e); + *root = (jl_value_t*)jl_alloc_svec(len * 3); + se->buf = (int8_t*)(len > 8 ? malloc_s(len * 3) : &se->_space); + memset(se->buf, 0, len * 3); } int n = 0; jl_varbinding_t *v = e->vars; jl_value_t *b1 = NULL, *b2 = NULL; JL_GC_PUSH2(&b1, &b2); // clang-sagc does not understand that *root is rooted already + v = e->vars; while (v != NULL) { - b1 = jl_svecref(*root, n); - b2 = v->lb; - jl_svecset(*root, n, simple_meet(b1, b2)); - b1 = jl_svecref(*root, n+1); - b2 = v->ub; - jl_svecset(*root, n+1, simple_join(b1, b2)); - b1 = jl_svecref(*root, n+2); - b2 = (jl_value_t*)v->innervars; - if (b2 && b1 != b2) { - if (b1) - jl_array_ptr_1d_append((jl_array_t*)b2, (jl_array_t*)b1); - else - jl_svecset(*root, n+2, b2); + if (v->occurs) { + // only merge lb/ub/innervars if this var occurs. + b1 = jl_svecref(*root, n); + b2 = v->lb; + jl_svecset(*root, n, b1 ? simple_meet(b1, b2) : b2); + b1 = jl_svecref(*root, n+1); + b2 = v->ub; + jl_svecset(*root, n+1, b1 ? simple_join(b1, b2) : b2); + b1 = jl_svecref(*root, n+2); + b2 = (jl_value_t*)v->innervars; + if (b2 && b1 != b2) { + if (b1) + jl_array_ptr_1d_append((jl_array_t*)b1, (jl_array_t*)b2); + else + jl_svecset(*root, n+2, b2); + } + // record the meeted vars. + se->buf[n] = 1; } + // always merge occurs_inv/cov by max (never decrease) + if (v->occurs_inv > se->buf[n+1]) + se->buf[n+1] = v->occurs_inv; + if (v->occurs_cov > se->buf[n+2]) + se->buf[n+2] = v->occurs_cov; n = n + 3; v = v->prev; } @@ -3301,6 +3328,54 @@ static int merge_env(jl_stenv_t *e, jl_value_t **root, jl_savedenv_t *se, int co return count + 1; } +// merge untouched vars' info. +static void final_merge_env(jl_value_t **merged, jl_savedenv_t *me, jl_value_t **saved, jl_savedenv_t *se) +{ + int l = jl_svec_len(*merged); + assert(l == jl_svec_len(*saved) && l%3 == 0); + jl_value_t *b1 = NULL, *b2 = NULL; + JL_GC_PUSH2(&b1, &b2); + for (int n = 0; n < l; n = n + 3) { + if (jl_svecref(*merged, n) == NULL) + jl_svecset(*merged, n, jl_svecref(*saved, n)); + if (jl_svecref(*merged, n+1) == NULL) + jl_svecset(*merged, n+1, jl_svecref(*saved, n+1)); + b1 = jl_svecref(*merged, n+2); + b2 = jl_svecref(*saved , n+2); + if (b2 && b1 != b2) { + if (b1) + jl_array_ptr_1d_append((jl_array_t*)b1, (jl_array_t*)b2); + else + jl_svecset(*merged, n+2, b2); + } + me->buf[n] |= se->buf[n]; + } + JL_GC_POP(); +} + +static void expand_local_env(jl_stenv_t *e, jl_value_t *res) +{ + jl_varbinding_t *v = e->vars; + // Here we pull in some typevar missed in fastpath. + while (v != NULL) { + v->occurs = v->occurs || jl_has_typevar(res, v->var); + assert(v->occurs == 0 || v->occurs == 1); + v = v->prev; + } + v = e->vars; + while (v != NULL) { + if (v->occurs == 1) { + jl_varbinding_t *v2 = e->vars; + while (v2 != NULL) { + if (v2 != v && v2->occurs == 0) + v2->occurs = -(jl_has_typevar(v->lb, v2->var) || jl_has_typevar(v->ub, v2->var)); + v2 = v2->prev; + } + } + v = v->prev; + } +} + static jl_value_t *intersect_all(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) { e->Runions.depth = 0; @@ -3313,10 +3388,13 @@ static jl_value_t *intersect_all(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) jl_savedenv_t se, me; save_env(e, saved, &se); int lastset = 0, niter = 0, total_iter = 0; + clean_occurs(e); jl_value_t *ii = intersect(x, y, e, 0); is[0] = ii; // root - if (is[0] != jl_bottom_type) + if (is[0] != jl_bottom_type) { + expand_local_env(e, is[0]); niter = merge_env(e, merged, &me, niter); + } restore_env(e, *saved, &se); while (e->Runions.more) { if (e->emptiness_only && ii != jl_bottom_type) @@ -3330,9 +3408,12 @@ static jl_value_t *intersect_all(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) lastset = set; is[0] = ii; + clean_occurs(e); is[1] = intersect(x, y, e, 0); - if (is[1] != jl_bottom_type) + if (is[1] != jl_bottom_type) { + expand_local_env(e, is[1]); niter = merge_env(e, merged, &me, niter); + } restore_env(e, *saved, &se); if (is[0] == jl_bottom_type) ii = is[1]; @@ -3348,7 +3429,8 @@ static jl_value_t *intersect_all(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) break; } } - if (niter){ + if (niter) { + final_merge_env(merged, &me, saved, &se); restore_env(e, *merged, &me); free_env(&me); } diff --git a/test/subtype.jl b/test/subtype.jl index b6e30ce6771466..f1d0b09c5f19a8 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -2319,6 +2319,13 @@ let S = Tuple{T2, V2} where {T2, N2, V2<:(Array{S2, N2} where {S2 <: T2})}, @testintersect(S, T, !Union{}) end +# A simple case which has a small local union. +# make sure the env is not widened too much when we intersect(Int8, Int8). +struct T48006{A1,A2,A3} end +@testintersect(Tuple{T48006{Float64, Int, S1}, Int} where {F1<:Real, S1<:Union{Int8, Val{F1}}}, + Tuple{T48006{F2, I, S2}, I} where {F2<:Real, I<:Int, S2<:Union{Int8, Val{F2}}}, + Tuple{T48006{Float64, Int, S1}, Int} where S1<:Union{Val{Float64}, Int8}) + @testset "known subtype/intersect issue" begin #issue 45874 # Causes a hang due to jl_critical_error calling back into malloc... From 303734204dbe74f1a5d1defcb4ae3ada3e318dd4 Mon Sep 17 00:00:00 2001 From: N5N3 <2642243996@qq.com> Date: Fri, 6 Jan 2023 23:03:53 +0800 Subject: [PATCH 282/387] Omit circular bounds in upbound to avoid false `Union{}` This is not ideal. As some times circular bound seems meaningful. But at least better than `Union{}` ? --- src/subtype.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++--- test/subtype.jl | 8 ++++---- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/subtype.c b/src/subtype.c index 231cad0a84dce4..1cb1d00ec6e5b6 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -2535,6 +2535,39 @@ static int var_occurs_inside(jl_value_t *v, jl_tvar_t *var, int inside, int want return 0; } +static jl_value_t *omit_bad_union(jl_value_t *u, jl_tvar_t *t) +{ + if (!jl_has_typevar(u, t)) + return u; // return u if possible as many checks use `==`. + jl_value_t *res = NULL; + if (jl_is_unionall(u)) { + jl_tvar_t *var = ((jl_unionall_t *)u)->var; + jl_value_t *ub = var->ub, *body = ((jl_unionall_t *)u)->body; + JL_GC_PUSH3(&ub, &body, &var); + assert(var != t); + ub = omit_bad_union(ub, t); + body = omit_bad_union(body, t); + if (ub != NULL && body != NULL && !jl_has_typevar(var->lb, t)) { + if (ub != var->ub) + var = jl_new_typevar(var->name, var->lb, ub); + res = jl_new_struct(jl_unionall_type, var, body); + } + JL_GC_POP(); + } + else if (jl_is_uniontype(u)) { + jl_value_t *a = ((jl_uniontype_t *)u)->a; + jl_value_t *b = ((jl_uniontype_t *)u)->b; + JL_GC_PUSH2(&a, &b); + a = omit_bad_union(a, t); + b = omit_bad_union(b, t); + res = a == NULL ? b : + b == NULL ? a : + jl_new_struct(jl_uniontype_type, a, b); + JL_GC_POP(); + } + return res; +} + // Caller might not have rooted `res` static jl_value_t *finish_unionall(jl_value_t *res JL_MAYBE_UNROOTED, jl_varbinding_t *vb, jl_unionall_t *u, jl_stenv_t *e) { @@ -2618,8 +2651,11 @@ static jl_value_t *finish_unionall(jl_value_t *res JL_MAYBE_UNROOTED, jl_varbind } if (jl_has_typevar(btemp->ub, vb->var)) { if (vb->ub == (jl_value_t*)btemp->var) { - JL_GC_POP(); - return jl_bottom_type; + btemp->ub = omit_bad_union(btemp->ub, vb->var); + if (btemp->ub == NULL) { + JL_GC_POP(); + return jl_bottom_type; + } } if (varval) { JL_TRY { @@ -2739,10 +2775,17 @@ static jl_value_t *intersect_unionall_(jl_value_t *t, jl_unionall_t *u, jl_stenv // T=Bottom in covariant position res = jl_bottom_type; } - else if (jl_has_typevar(vb->lb, u->var) || jl_has_typevar(vb->ub, u->var)) { + else if (jl_has_typevar(vb->lb, u->var)) { // fail on circular constraints res = jl_bottom_type; } + else { + JL_GC_PUSH1(&res); + vb->ub = omit_bad_union(vb->ub, u->var); + JL_GC_POP(); + if (vb->ub == NULL) + res = jl_bottom_type; + } } if (res != jl_bottom_type) // res is rooted by callee diff --git a/test/subtype.jl b/test/subtype.jl index f1d0b09c5f19a8..a6f9d3a3e8119b 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -1893,10 +1893,10 @@ end let # issue #22787 - # for now check that these don't stack overflow - t = typeintersect(Tuple{Type{Q}, Q, Ref{Q}} where Q<:Ref, - Tuple{Type{S}, Union{Ref{S}, Ref{R}}, R} where R where S) - @test_broken t != Union{} + @testintersect(Tuple{Type{Q}, Q, Ref{Q}} where Q<:Ref, + Tuple{Type{S}, Union{Ref{S}, Ref{R}}, R} where R where S, + !Union{}) + t = typeintersect(Tuple{Type{T}, T, Ref{T}} where T, Tuple{Type{S}, Ref{S}, S} where S) @test_broken t != Union{} From 56be1cd44d6937c16f3c9c71b66cd25214676040 Mon Sep 17 00:00:00 2001 From: N5N3 <2642243996@qq.com> Date: Sat, 7 Jan 2023 21:38:22 +0800 Subject: [PATCH 283/387] Always accumulates bounds on outer var. --- src/subtype.c | 12 ++++++++++++ test/subtype.jl | 8 ++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/subtype.c b/src/subtype.c index 1cb1d00ec6e5b6..75d970e290eff9 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -3150,6 +3150,18 @@ static jl_value_t *intersect(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int pa return res; } record_var_occurrence(yy, e, param); + if (yub == ylb && jl_is_typevar(yub)) { + // We always set inner var equal to outer. + if (!yy || yy->offset == 0) + return intersect(x, yub, e, param); + // try to propagate the y's offset to yub. + jl_varbinding_t *tvb = lookup(e, (jl_tvar_t*)yub); + assert(tvb && tvb->offset == 0); + tvb->offset = yy->offset; + jl_value_t *res = intersect(x, yub, e, param); + tvb->offset = 0; + return res; + } if (!jl_is_type(ylb) && !jl_is_typevar(ylb)) { if (xx) return set_var_to_const(xx, ylb, yy); diff --git a/test/subtype.jl b/test/subtype.jl index a6f9d3a3e8119b..ae220d1075c3a3 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -1883,12 +1883,8 @@ struct AlmostLU{T, S<:AbstractMatrix{T}} end let X1 = Tuple{AlmostLU, Vector{T}} where T, X2 = Tuple{AlmostLU{S, X} where X<:Matrix, Vector{S}} where S<:Union{Float32, Float64}, - I = typeintersect(X1, X2) - # TODO: the quality of this intersection is not great; for now just test that it - # doesn't stack overflow - @test I<:X1 || I<:X2 - actual = Tuple{Union{AlmostLU{S, X} where X<:Matrix{S}, AlmostLU{S, <:Matrix}}, Vector{S}} where S<:Union{Float32, Float64} - @test I == actual + I = Tuple{AlmostLU{T, S} where S<:Matrix{T}, Vector{T}} where T<:Union{Float32, Float64} + @testintersect(X1, X2, I) end let From df377ad9d4dd18018bc212335a52856d2e53651b Mon Sep 17 00:00:00 2001 From: N5N3 <2642243996@qq.com> Date: Thu, 5 Jan 2023 16:08:18 +0800 Subject: [PATCH 284/387] bounds merge tuning 1. use `obviously_in_union` to catch more unneeded duplication in `Union`. 2. under-estimate merged `lb` in more case. (Should not affect subtype path.) --- src/subtype.c | 43 +++++++++++++++++++++++++++++++++---------- test/subtype.jl | 6 ++++++ 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/subtype.c b/src/subtype.c index 75d970e290eff9..cc788422dd8921 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -367,6 +367,28 @@ static int in_union(jl_value_t *u, jl_value_t *x) JL_NOTSAFEPOINT return in_union(((jl_uniontype_t*)u)->a, x) || in_union(((jl_uniontype_t*)u)->b, x); } +static int obviously_in_union(jl_value_t *u, jl_value_t *x) +{ + jl_value_t *a = NULL, *b = NULL; + if (jl_is_uniontype(x)) { + a = ((jl_uniontype_t*)x)->a; + b = ((jl_uniontype_t*)x)->b; + JL_GC_PUSH2(&a, &b); + int res = obviously_in_union(u, a) && obviously_in_union(u, b); + JL_GC_POP(); + return res; + } + if (jl_is_uniontype(u)) { + a = ((jl_uniontype_t*)u)->a; + b = ((jl_uniontype_t*)u)->b; + JL_GC_PUSH2(&a, &b); + int res = obviously_in_union(a, x) || obviously_in_union(b, x); + JL_GC_POP(); + return res; + } + return obviously_egal(u, x); +} + static int obviously_disjoint(jl_value_t *a, jl_value_t *b, int specificity) { if (a == b || a == (jl_value_t*)jl_any_type || b == (jl_value_t*)jl_any_type) @@ -464,9 +486,9 @@ static jl_value_t *simple_join(jl_value_t *a, jl_value_t *b) return a; if (!(jl_is_type(a) || jl_is_typevar(a)) || !(jl_is_type(b) || jl_is_typevar(b))) return (jl_value_t*)jl_any_type; - if (jl_is_uniontype(a) && in_union(a, b)) + if (jl_is_uniontype(a) && obviously_in_union(a, b)) return a; - if (jl_is_uniontype(b) && in_union(b, a)) + if (jl_is_uniontype(b) && obviously_in_union(b, a)) return b; if (jl_is_kind(a) && jl_is_type_type(b) && jl_typeof(jl_tparam0(b)) == a) return a; @@ -485,9 +507,10 @@ static jl_value_t *simple_join(jl_value_t *a, jl_value_t *b) return jl_new_struct(jl_uniontype_type, a, b); } -// compute a greatest lower bound of `a` and `b` -// in many cases, we need to over-estimate this by returning `b`. -static jl_value_t *simple_meet(jl_value_t *a, jl_value_t *b) +// Compute a greatest lower bound of `a` and `b` +// For the subtype path, we need to over-estimate this by returning `b` in many cases. +// But for `merge_env`, we'd better under-estimate and return a `Union{}` +static jl_value_t *simple_meet(jl_value_t *a, jl_value_t *b, int overesi) { if (a == (jl_value_t*)jl_any_type || b == jl_bottom_type || obviously_egal(a,b)) return b; @@ -495,9 +518,9 @@ static jl_value_t *simple_meet(jl_value_t *a, jl_value_t *b) return a; if (!(jl_is_type(a) || jl_is_typevar(a)) || !(jl_is_type(b) || jl_is_typevar(b))) return jl_bottom_type; - if (jl_is_uniontype(a) && in_union(a, b)) + if (jl_is_uniontype(a) && obviously_in_union(a, b)) return b; - if (jl_is_uniontype(b) && in_union(b, a)) + if (jl_is_uniontype(b) && obviously_in_union(b, a)) return a; if (jl_is_kind(a) && jl_is_type_type(b) && jl_typeof(jl_tparam0(b)) == a) return b; @@ -513,7 +536,7 @@ static jl_value_t *simple_meet(jl_value_t *a, jl_value_t *b) if (jl_subtype(a, b)) return a; if (jl_subtype(b, a)) return b; } - return b; + return overesi ? b : jl_bottom_type; } static jl_unionall_t *rename_unionall(jl_unionall_t *u) @@ -655,7 +678,7 @@ static int var_lt(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int param) JL_GC_POP(); } else { - bb->ub = simple_meet(bb->ub, a); + bb->ub = simple_meet(bb->ub, a, 1); } assert(bb->ub != (jl_value_t*)b); if (jl_is_typevar(a)) { @@ -3356,7 +3379,7 @@ static int merge_env(jl_stenv_t *e, jl_value_t **root, jl_savedenv_t *se, int co // only merge lb/ub/innervars if this var occurs. b1 = jl_svecref(*root, n); b2 = v->lb; - jl_svecset(*root, n, b1 ? simple_meet(b1, b2) : b2); + jl_svecset(*root, n, b1 ? simple_meet(b1, b2, 0) : b2); b1 = jl_svecref(*root, n+1); b2 = v->ub; jl_svecset(*root, n+1, b1 ? simple_join(b1, b2) : b2); diff --git a/test/subtype.jl b/test/subtype.jl index ae220d1075c3a3..5f8874152f5be0 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -2322,6 +2322,12 @@ struct T48006{A1,A2,A3} end Tuple{T48006{F2, I, S2}, I} where {F2<:Real, I<:Int, S2<:Union{Int8, Val{F2}}}, Tuple{T48006{Float64, Int, S1}, Int} where S1<:Union{Val{Float64}, Int8}) + +f48167(::Type{Val{L2}}, ::Type{Union{Val{L1}, Set{R}}}) where {L1, R, L2<:L1} = 1 +f48167(::Type{Val{L1}}, ::Type{Union{Val{L2}, Set{R}}}) where {L1, R, L2<:L1} = 2 +f48167(::Type{Val{L}}, ::Type{Union{Val{L}, Set{R}}}) where {L, R} = 3 +@test f48167(Val{Nothing}, Union{Val{Nothing}, Set{Int}}) == 3 + @testset "known subtype/intersect issue" begin #issue 45874 # Causes a hang due to jl_critical_error calling back into malloc... From 5e2fbc84f127221b0f5851f7d50d52096373a8a4 Mon Sep 17 00:00:00 2001 From: N5N3 <2642243996@qq.com> Date: Sat, 7 Jan 2023 14:50:56 +0800 Subject: [PATCH 285/387] Add another by bounds check to avoid stack-overflow. --- src/subtype.c | 26 ++++++++++++++++++++++++++ test/subtype.jl | 6 ++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/subtype.c b/src/subtype.c index cc788422dd8921..1b3abf7a1748e5 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -2292,8 +2292,34 @@ static jl_value_t *bound_var_below(jl_tvar_t *tv, jl_varbinding_t *bb, jl_stenv_ return (jl_value_t*)tv; } +static int subtype_by_bounds(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) JL_NOTSAFEPOINT; + +// similar to `subtype_by_bounds`, used to avoid stack-overflow caused by circulation constraints. +static int try_subtype_by_bounds(jl_value_t *a, jl_value_t *b, jl_stenv_t *e) +{ + if (jl_is_uniontype(a)) + return try_subtype_by_bounds(((jl_uniontype_t *)a)->a, b, e) && + try_subtype_by_bounds(((jl_uniontype_t *)a)->b, b, e); + else if (jl_is_uniontype(b)) + return try_subtype_by_bounds(a, ((jl_uniontype_t *)b)->a, e) || + try_subtype_by_bounds(a, ((jl_uniontype_t *)b)->b, e); + else if (jl_egal(a, b)) + return 1; + else if (!jl_is_typevar(b)) + return 0; + jl_varbinding_t *vb = e->vars; + while (vb != NULL) { + if (subtype_by_bounds(b, (jl_value_t *)vb->var, e) && obviously_in_union(a, vb->ub)) + return 1; + vb = vb->prev; + } + return 0; +} + static int try_subtype_in_env(jl_value_t *a, jl_value_t *b, jl_stenv_t *e, int R, int d) { + if (a == jl_bottom_type || b == (jl_value_t *)jl_any_type || try_subtype_by_bounds(a, b, e)) + return 1; jl_value_t *root=NULL; jl_savedenv_t se; JL_GC_PUSH1(&root); save_env(e, &root, &se); diff --git a/test/subtype.jl b/test/subtype.jl index 5f8874152f5be0..c91a8b77a831eb 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -2286,10 +2286,8 @@ T46784{B<:Val, M<:AbstractMatrix} = Tuple{<:Union{B, <:Val{<:B}}, M, Union{Abstr let S = Tuple{Type{T},Array{Union{T,Missing},N}} where {T,N}, T = Tuple{Type{T},Array{Union{T,Nothing},N}} where {T,N} @testintersect(S, T, !Union{}) - I = typeintersect(S, T) - @test (Tuple{Type{Any},Array{Any,N}} where {N}) <: I - @test_broken I <: S - @test_broken I <: T + @test_broken typeintersect(S, T) != S + @test_broken typeintersect(T, S) != T end #issue 46736 From c221f0f9a417da7750af6c0c2f44119a2cb8fdef Mon Sep 17 00:00:00 2001 From: N5N3 <2642243996@qq.com> Date: Sun, 8 Jan 2023 15:22:28 +0800 Subject: [PATCH 286/387] More circulation check. --- src/subtype.c | 4 +++- test/subtype.jl | 13 ++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/subtype.c b/src/subtype.c index 1b3abf7a1748e5..b5d10cefef5cb9 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -3237,7 +3237,9 @@ static jl_value_t *intersect(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int pa } if (!ccheck) return jl_bottom_type; - if (var_occurs_inside(xub, (jl_tvar_t*)y, 0, 0) && var_occurs_inside(yub, (jl_tvar_t*)x, 0, 0)) { + if ((jl_has_typevar(xub, (jl_tvar_t*)y) || jl_has_typevar(xub, (jl_tvar_t*)x)) && + (jl_has_typevar(yub, (jl_tvar_t*)x) || jl_has_typevar(yub, (jl_tvar_t*)y))) { + // TODO: This doesn't make much sense. // circular constraint. the result will be Bottom, but in the meantime // we need to avoid computing intersect(xub, yub) since it won't terminate. return y; diff --git a/test/subtype.jl b/test/subtype.jl index c91a8b77a831eb..ea35f11abbd2f1 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -2320,12 +2320,23 @@ struct T48006{A1,A2,A3} end Tuple{T48006{F2, I, S2}, I} where {F2<:Real, I<:Int, S2<:Union{Int8, Val{F2}}}, Tuple{T48006{Float64, Int, S1}, Int} where S1<:Union{Val{Float64}, Int8}) - f48167(::Type{Val{L2}}, ::Type{Union{Val{L1}, Set{R}}}) where {L1, R, L2<:L1} = 1 f48167(::Type{Val{L1}}, ::Type{Union{Val{L2}, Set{R}}}) where {L1, R, L2<:L1} = 2 f48167(::Type{Val{L}}, ::Type{Union{Val{L}, Set{R}}}) where {L, R} = 3 @test f48167(Val{Nothing}, Union{Val{Nothing}, Set{Int}}) == 3 +# https://github.com/JuliaLang/julia/pull/31167#issuecomment-1358381818 +let S = Tuple{Type{T1}, T1, Val{T1}} where T1<:(Val{S1} where S1<:Val), + T = Tuple{Union{Type{T2}, Type{S2}}, Union{Val{T2}, Val{S2}}, Union{Val{T2}, S2}} where T2<:Val{A2} where A2 where S2<:Val + I1 = typeintersect(S, T) + I2 = typeintersect(T, S) + @test I1 !== Union{} && I2 !== Union{} + @test_broken I1 <: S + @test_broken I2 <: T + @test I2 <: S + @test_broken I2 <: T +end + @testset "known subtype/intersect issue" begin #issue 45874 # Causes a hang due to jl_critical_error calling back into malloc... From 04cb6fb9266314d585f45a466e02a4cdc7b33d4a Mon Sep 17 00:00:00 2001 From: N5N3 <2642243996@qq.com> Date: Sun, 8 Jan 2023 15:21:14 +0800 Subject: [PATCH 287/387] More thorough `reachable_var` check. Fix #47874 case2 --- src/subtype.c | 3 +++ test/subtype.jl | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/src/subtype.c b/src/subtype.c index b5d10cefef5cb9..1c6d3ffb2e8dec 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -2380,6 +2380,9 @@ static int _reachable_var(jl_value_t *x, jl_tvar_t *y, jl_stenv_t *e) { if (in_union(x, (jl_value_t*)y)) return 1; + if (jl_is_uniontype(x)) + return _reachable_var(((jl_uniontype_t *)x)->a, y, e) || + _reachable_var(((jl_uniontype_t *)x)->b, y, e); if (!jl_is_typevar(x)) return 0; jl_varbinding_t *xv = lookup(e, (jl_tvar_t*)x); diff --git a/test/subtype.jl b/test/subtype.jl index ea35f11abbd2f1..a88ba8e3f0f766 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -2308,6 +2308,15 @@ let S1 = Tuple{Int, Any, Union{Val{C1}, C1}} where {R1<:Real, C1<:Union{Complex{ end end +#issue #47874:case2 +let S = Tuple{Int, Vararg{Val{C} where C<:Union{Complex{R}, R}}} where R + T = Tuple{Any, Vararg{Val{C} where C<:Union{Complex{R}, R}}} where R<:Real + I = Tuple{Any, Vararg{Val{C} where C<:Union{Complex{R}, R}}} where R<:Real + @testintersect(S, T, !Union{}) + @test_broken typeintersect(S, T) == I + @test_broken typeintersect(T, S) == I +end + let S = Tuple{T2, V2} where {T2, N2, V2<:(Array{S2, N2} where {S2 <: T2})}, T = Tuple{V1, T1} where {T1, N1, V1<:(Array{S1, N1} where {S1 <: T1})} @testintersect(S, T, !Union{}) From 11d83b85c335866de933c5438cab5dbc6b1e958a Mon Sep 17 00:00:00 2001 From: N5N3 <2642243996@qq.com> Date: Mon, 9 Jan 2023 16:06:45 +0800 Subject: [PATCH 288/387] "Widen" `T>:Any` to `Any` in `simple_join`. I gave up fixing the deep stack overflow. Making the `env` soundness seems much easier. close #47874. At present, we only catch cases with pattern like A1 = Union{T,Int} where {T} A2 = Union{T2,Int} where {T,T2<:Union{T,Int}} A3 = Union{Int,A2} --- src/subtype.c | 34 ++++++++++++++++++++++++++++++++-- test/subtype.jl | 6 ++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/subtype.c b/src/subtype.c index 1c6d3ffb2e8dec..7af4e8b28b9342 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -477,12 +477,42 @@ static int obviously_disjoint(jl_value_t *a, jl_value_t *b, int specificity) return 0; } +static int is_any_like(jl_value_t* x, jl_typeenv_t *env) JL_NOTSAFEPOINT +{ + if (x == (jl_value_t *)jl_any_type) + return 1; + if (jl_is_uniontype(x)) + return is_any_like(((jl_uniontype_t*)x)->a, env) || + is_any_like(((jl_uniontype_t*)x)->b, env); + if (jl_is_unionall(x)) { + jl_unionall_t *ua = (jl_unionall_t*)x; + jl_typeenv_t newenv = { ua->var, NULL, env }; + return is_any_like(ua->body, &newenv); + } + if (jl_is_typevar(x) && env != NULL) { + jl_tvar_t *v = (jl_tvar_t *)x; + if (v->lb != jl_bottom_type) + return 0; + int in_env = 0; + jl_typeenv_t *vs = env; + while (vs != NULL) { + in_env = vs->var == v; + if (in_env) break; + vs = vs->prev; + } + return in_env && is_any_like(v->ub, env); + } + return 0; +} + // compute a least upper bound of `a` and `b` static jl_value_t *simple_join(jl_value_t *a, jl_value_t *b) { - if (a == jl_bottom_type || b == (jl_value_t*)jl_any_type || obviously_egal(a,b)) + if (is_any_like(a, NULL) || is_any_like(b, NULL)) + return (jl_value_t *)jl_any_type; + if (a == jl_bottom_type || obviously_egal(a,b)) return b; - if (b == jl_bottom_type || a == (jl_value_t*)jl_any_type) + if (b == jl_bottom_type) return a; if (!(jl_is_type(a) || jl_is_typevar(a)) || !(jl_is_type(b) || jl_is_typevar(b))) return (jl_value_t*)jl_any_type; diff --git a/test/subtype.jl b/test/subtype.jl index a88ba8e3f0f766..a182bb99909eeb 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -2317,6 +2317,12 @@ let S = Tuple{Int, Vararg{Val{C} where C<:Union{Complex{R}, R}}} where R @test_broken typeintersect(T, S) == I end +#issue #47874:case3 +let S = Tuple{Int, Tuple{Vararg{Val{C1} where C1<:Union{Complex{R1}, R1}}} where R1<:(Union{Real, V1} where V1), Tuple{Vararg{Val{C2} where C2<:Union{Complex{R2}, Complex{R3}, R3}}} where {R2<:(Union{Real, V2} where V2), R3<:Union{Complex{R2}, Real, R2}}}, + T = Tuple{Any, Tuple{Vararg{Val{CC1} where CC1<:Union{Complex{R}, R}}}, Tuple{Vararg{Val{CC2} where CC2<:Union{Complex{R}, R}}}} where R<:Real + @testintersect(S, T, !Union{}) +end + let S = Tuple{T2, V2} where {T2, N2, V2<:(Array{S2, N2} where {S2 <: T2})}, T = Tuple{V1, T1} where {T1, N1, V1<:(Array{S1, N1} where {S1 <: T1})} @testintersect(S, T, !Union{}) From 2424af17a37cd17a1eec2d67c346ad7b29607b27 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Tue, 10 Jan 2023 22:57:09 +0900 Subject: [PATCH 289/387] make `getfield_notundefined` more robust (#48207) Follow up #48203. See the added test cases for the purpose of this commit. --- base/compiler/tfuncs.jl | 4 +++- test/compiler/effects.jl | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 726381d718573f..72e02d262ea944 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -1227,12 +1227,14 @@ end @nospecs function getfield_notundefined(typ0, name) if isa(typ0, Const) && isa(name, Const) + typv = typ0.val namev = name.val + isa(typv, Module) && return true if isa(namev, Symbol) || isa(namev, Int) # Fields are not allowed to transition from defined to undefined, so # even if the field is not const, all we need to check here is that # it is defined here. - return isdefined(typ0.val, namev) + return isdefined(typv, namev) end end typ0 = widenconst(typ0) diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl index 84c43b09a7d176..a92cd1bc1003a0 100644 --- a/test/compiler/effects.jl +++ b/test/compiler/effects.jl @@ -271,6 +271,8 @@ for TupleType = Any[Tuple{Int,Int,Int}, Tuple{Int,Vararg{Int}}, Tuple{Any}, Tupl end # skip analysis on fields that are known to be defined syntactically @test Core.Compiler.getfield_notundefined(SyntacticallyDefined{Float64}, Symbol) +@test Core.Compiler.getfield_notundefined(Const(Main), Const(:var)) +@test Core.Compiler.getfield_notundefined(Const(Main), Const(42)) # high-level tests for `getfield_notundefined` @test Base.infer_effects() do Maybe{Int}() From 548aee61c05bba0771d1f28523117df11c9406cb Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 10 Jan 2023 09:20:53 -0500 Subject: [PATCH 290/387] Revert "Merge pull request #47994 from JuliaLang/avi/improve-semi-concrete-accuracy" (#48194) This reverts commit 03bdf15483cbe2d06526d3b46f26e5d20893f07e, reversing changes made to a9e0545969bb76f33fe9ad9bcf52180caa1651b9. Fixes #48089 regression That PR causes the runtime to store an undesirable amount of garbage, to work around a bug in the semi-concrete interpreter failing to correctly compute the inferred result. That should be fixed in the semi-concrete interpreter, without bloating the runtime caches with extraneous information. --- base/boot.jl | 4 ++-- base/compiler/optimize.jl | 13 +++++++------ base/compiler/typelattice.jl | 29 ----------------------------- base/opaque_closure.jl | 2 +- src/ccall.cpp | 6 ++---- src/codegen.cpp | 15 +++++++-------- src/interpreter.c | 6 ++---- src/jltypes.c | 22 ---------------------- src/julia.h | 1 + src/julia_internal.h | 1 - test/compiler/inference.jl | 20 -------------------- 11 files changed, 22 insertions(+), 97 deletions(-) diff --git a/base/boot.jl b/base/boot.jl index 399fa1cdaf1ff3..33b2cd07688ad6 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -524,12 +524,12 @@ module IR export CodeInfo, MethodInstance, CodeInstance, GotoNode, GotoIfNot, ReturnNode, NewvarNode, SSAValue, Slot, SlotNumber, TypedSlot, Argument, PiNode, PhiNode, PhiCNode, UpsilonNode, LineInfoNode, - Const, PartialStruct, InterConditional, PartialOpaque + Const, PartialStruct import Core: CodeInfo, MethodInstance, CodeInstance, GotoNode, GotoIfNot, ReturnNode, NewvarNode, SSAValue, Slot, SlotNumber, TypedSlot, Argument, PiNode, PhiNode, PhiCNode, UpsilonNode, LineInfoNode, - Const, PartialStruct, InterConditional, PartialOpaque + Const, PartialStruct end diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index c00cfd8d0c26a6..de0a8d5cf5a93f 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -179,28 +179,29 @@ function ir_to_codeinf!(opt::OptimizationState) optdef = linfo.def replace_code_newstyle!(src, opt.ir::IRCode, isa(optdef, Method) ? Int(optdef.nargs) : 0) opt.ir = nothing - widencompileronly!(src) - src.rettype = widenconst(src.rettype) + widen_all_consts!(src) src.inferred = true # finish updating the result struct validate_code_in_debug_mode(linfo, src, "optimized") return src end -# widen extended lattice elements in type annotations so that they are recognizable by the codegen system. -function widencompileronly!(src::CodeInfo) +# widen all Const elements in type annotations +function widen_all_consts!(src::CodeInfo) ssavaluetypes = src.ssavaluetypes::Vector{Any} for i = 1:length(ssavaluetypes) - ssavaluetypes[i] = widencompileronly(ssavaluetypes[i]) + ssavaluetypes[i] = widenconst(ssavaluetypes[i]) end for i = 1:length(src.code) x = src.code[i] if isa(x, PiNode) - src.code[i] = PiNode(x.val, widencompileronly(x.typ)) + src.code[i] = PiNode(x.val, widenconst(x.typ)) end end + src.rettype = widenconst(src.rettype) + return src end diff --git a/base/compiler/typelattice.jl b/base/compiler/typelattice.jl index 5439ec8654f31e..831219e70a29cb 100644 --- a/base/compiler/typelattice.jl +++ b/base/compiler/typelattice.jl @@ -81,8 +81,6 @@ const AnyConditional = Union{Conditional,InterConditional} Conditional(cnd::InterConditional) = Conditional(cnd.slot, cnd.thentype, cnd.elsetype) InterConditional(cnd::Conditional) = InterConditional(cnd.slot, cnd.thentype, cnd.elsetype) -# TODO make `MustAlias` and `InterMustAlias` recognizable by the codegen system - """ alias::MustAlias @@ -715,33 +713,6 @@ function tmeet(lattice::OptimizerLattice, @nospecialize(v), @nospecialize(t::Typ tmeet(widenlattice(lattice), v, t) end -""" - is_core_extended_info(t) -> Bool - -Check if extended lattice element `t` is recognizable by the runtime/codegen system. - -See also the implementation of `jl_widen_core_extended_info` in jltypes.c. -""" -function is_core_extended_info(@nospecialize t) - isa(t, Type) && return true - isa(t, Const) && return true - isa(t, PartialStruct) && return true - isa(t, InterConditional) && return true - # TODO isa(t, InterMustAlias) && return true - isa(t, PartialOpaque) && return true - return false -end - -""" - widencompileronly(t) -> wt::Any - -Widen the extended lattice element `x` so that `wt` is recognizable by the runtime/codegen system. -""" -function widencompileronly(@nospecialize t) - is_core_extended_info(t) && return t - return widenconst(t) -end - """ widenconst(x) -> t::Type diff --git a/base/opaque_closure.jl b/base/opaque_closure.jl index 707550cd2a444d..2bccd613d0009f 100644 --- a/base/opaque_closure.jl +++ b/base/opaque_closure.jl @@ -68,7 +68,7 @@ function Core.OpaqueClosure(ir::IRCode, env...; src.slotnames = fill(:none, nargs+1) src.slottypes = copy(ir.argtypes) Core.Compiler.replace_code_newstyle!(src, ir, nargs+1) - Core.Compiler.widencompileronly!(src) + Core.Compiler.widen_all_consts!(src) src.inferred = true # NOTE: we need ir.argtypes[1] == typeof(env) diff --git a/src/ccall.cpp b/src/ccall.cpp index ed3992a78091c2..e71b65ad886004 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -760,8 +760,7 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar return jl_cgval_t(); } if (jl_is_ssavalue(args[2]) && !jl_is_long(ctx.source->ssavaluetypes)) { - jl_value_t *rtt = jl_widen_core_extended_info( - jl_arrayref((jl_array_t*)ctx.source->ssavaluetypes, ((jl_ssavalue_t*)args[2])->id - 1)); + jl_value_t *rtt = jl_arrayref((jl_array_t*)ctx.source->ssavaluetypes, ((jl_ssavalue_t*)args[2])->id - 1); if (jl_is_type_type(rtt)) rt = jl_tparam0(rtt); } @@ -774,8 +773,7 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar } } if (jl_is_ssavalue(args[3]) && !jl_is_long(ctx.source->ssavaluetypes)) { - jl_value_t *att = jl_widen_core_extended_info( - jl_arrayref((jl_array_t*)ctx.source->ssavaluetypes, ((jl_ssavalue_t*)args[3])->id - 1)); + jl_value_t *att = jl_arrayref((jl_array_t*)ctx.source->ssavaluetypes, ((jl_ssavalue_t*)args[3])->id - 1); if (jl_is_type_type(att)) at = jl_tparam0(att); } diff --git a/src/codegen.cpp b/src/codegen.cpp index 1caafd8e64330a..287c77bf54de9a 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -4524,7 +4524,7 @@ static void emit_phinode_assign(jl_codectx_t &ctx, ssize_t idx, jl_value_t *r) jl_value_t *ssavalue_types = (jl_value_t*)ctx.source->ssavaluetypes; jl_value_t *phiType = NULL; if (jl_is_array(ssavalue_types)) { - phiType = jl_widen_core_extended_info(jl_array_ptr_ref(ssavalue_types, idx)); + phiType = jl_array_ptr_ref(ssavalue_types, idx); } else { phiType = (jl_value_t*)jl_any_type; } @@ -4633,7 +4633,7 @@ static void emit_ssaval_assign(jl_codectx_t &ctx, ssize_t ssaidx_0based, jl_valu // e.g. sometimes the information is inconsistent after inlining getfield on a Tuple jl_value_t *ssavalue_types = (jl_value_t*)ctx.source->ssavaluetypes; if (jl_is_array(ssavalue_types)) { - jl_value_t *declType = jl_widen_core_extended_info(jl_array_ptr_ref(ssavalue_types, ssaidx_0based)); + jl_value_t *declType = jl_array_ptr_ref(ssavalue_types, ssaidx_0based); if (declType != slot.typ) { slot = update_julia_type(ctx, slot, declType); } @@ -4972,7 +4972,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ } if (jl_is_pinode(expr)) { Value *skip = NULL; - return convert_julia_type(ctx, emit_expr(ctx, jl_fieldref_noalloc(expr, 0)), jl_widen_core_extended_info(jl_fieldref_noalloc(expr, 1)), &skip); + return convert_julia_type(ctx, emit_expr(ctx, jl_fieldref_noalloc(expr, 0)), jl_fieldref_noalloc(expr, 1), &skip); } if (!jl_is_expr(expr)) { jl_value_t *val = expr; @@ -5010,13 +5010,13 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ else if (head == jl_invoke_sym) { assert(ssaidx_0based >= 0); jl_value_t *expr_t = jl_is_long(ctx.source->ssavaluetypes) ? (jl_value_t*)jl_any_type : - jl_widen_core_extended_info(jl_array_ptr_ref(ctx.source->ssavaluetypes, ssaidx_0based)); + jl_array_ptr_ref(ctx.source->ssavaluetypes, ssaidx_0based); return emit_invoke(ctx, ex, expr_t); } else if (head == jl_invoke_modify_sym) { assert(ssaidx_0based >= 0); jl_value_t *expr_t = jl_is_long(ctx.source->ssavaluetypes) ? (jl_value_t*)jl_any_type : - jl_widen_core_extended_info(jl_array_ptr_ref(ctx.source->ssavaluetypes, ssaidx_0based)); + jl_array_ptr_ref(ctx.source->ssavaluetypes, ssaidx_0based); return emit_invoke_modify(ctx, ex, expr_t); } else if (head == jl_call_sym) { @@ -5026,8 +5026,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ // TODO: this case is needed for the call to emit_expr in emit_llvmcall expr_t = (jl_value_t*)jl_any_type; else { - expr_t = jl_is_long(ctx.source->ssavaluetypes) ? (jl_value_t*)jl_any_type : - jl_widen_core_extended_info(jl_array_ptr_ref(ctx.source->ssavaluetypes, ssaidx_0based)); + expr_t = jl_is_long(ctx.source->ssavaluetypes) ? (jl_value_t*)jl_any_type : jl_array_ptr_ref(ctx.source->ssavaluetypes, ssaidx_0based); is_promotable = ctx.ssavalue_usecount.at(ssaidx_0based) == 1; } jl_cgval_t res = emit_call(ctx, ex, expr_t, is_promotable); @@ -7147,7 +7146,7 @@ static jl_llvm_functions_t } jl_varinfo_t &vi = (ctx.phic_slots.emplace(i, jl_varinfo_t(ctx.builder.getContext())).first->second = jl_varinfo_t(ctx.builder.getContext())); - jl_value_t *typ = jl_widen_core_extended_info(jl_array_ptr_ref(src->ssavaluetypes, i)); + jl_value_t *typ = jl_array_ptr_ref(src->ssavaluetypes, i); vi.used = true; vi.isVolatile = true; vi.value = mark_julia_type(ctx, (Value*)NULL, false, typ); diff --git a/src/interpreter.c b/src/interpreter.c index 3a3e270c3a5523..cf9ddd5b50630d 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -209,10 +209,8 @@ static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s) if (jl_is_pinode(e)) { jl_value_t *val = eval_value(jl_fieldref_noalloc(e, 0), s); #ifndef JL_NDEBUG - jl_value_t *typ = NULL; - JL_GC_PUSH2(&val, &typ); - typ = jl_widen_core_extended_info(jl_fieldref_noalloc(e, 1)); - jl_typeassert(val, typ); + JL_GC_PUSH1(&val); + jl_typeassert(val, jl_fieldref_noalloc(e, 1)); JL_GC_POP(); #endif return val; diff --git a/src/jltypes.c b/src/jltypes.c index 2b83c6396aa1ee..253768ad075036 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -2015,28 +2015,6 @@ void jl_reinstantiate_inner_types(jl_datatype_t *t) // can throw! } } -// Widens "core" extended lattice element `t` to the native `Type` representation. -// The implementation of this function should sync with those of the corresponding `widenconst`s. -JL_DLLEXPORT jl_value_t *jl_widen_core_extended_info(jl_value_t *t) -{ - jl_value_t* tt = jl_typeof(t); - if (tt == (jl_value_t*)jl_const_type) { - jl_value_t* val = jl_fieldref_noalloc(t, 0); - if (jl_isa(val, (jl_value_t*)jl_type_type)) - return (jl_value_t*)jl_wrap_Type(val); - else - return jl_typeof(val); - } - else if (tt == (jl_value_t*)jl_partial_struct_type) - return (jl_value_t*)jl_fieldref_noalloc(t, 0); - else if (tt == (jl_value_t*)jl_interconditional_type) - return (jl_value_t*)jl_bool_type; - else if (tt == (jl_value_t*)jl_partial_opaque_type) - return (jl_value_t*)jl_fieldref_noalloc(t, 0); - else - return t; -} - // initialization ------------------------------------------------------------- static jl_tvar_t *tvar(const char *name) diff --git a/src/julia.h b/src/julia.h index 1395df45013290..65b5a77c3a0650 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1842,6 +1842,7 @@ JL_DLLEXPORT jl_value_t *jl_compress_argnames(jl_array_t *syms); JL_DLLEXPORT jl_array_t *jl_uncompress_argnames(jl_value_t *syms); JL_DLLEXPORT jl_value_t *jl_uncompress_argname_n(jl_value_t *syms, size_t i); + JL_DLLEXPORT int jl_is_operator(char *sym); JL_DLLEXPORT int jl_is_unary_operator(char *sym); JL_DLLEXPORT int jl_is_unary_and_binary_operator(char *sym); diff --git a/src/julia_internal.h b/src/julia_internal.h index b839d5eda1650c..7d7a07b9d1b971 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -722,7 +722,6 @@ void jl_init_main_module(void); JL_DLLEXPORT int jl_is_submodule(jl_module_t *child, jl_module_t *parent) JL_NOTSAFEPOINT; jl_array_t *jl_get_loaded_modules(void); JL_DLLEXPORT int jl_datatype_isinlinealloc(jl_datatype_t *ty, int pointerfree); -JL_DLLEXPORT jl_value_t *jl_widen_core_extended_info(jl_value_t *t); void jl_eval_global_expr(jl_module_t *m, jl_expr_t *ex, int set_type); jl_value_t *jl_toplevel_eval_flex(jl_module_t *m, jl_value_t *e, int fast, int expanded); diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index f91809d7cfe476..b045e9b5333cb8 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -4689,23 +4689,3 @@ end @test Base.return_types(empty_nt_keys, (Any,)) |> only === Tuple{} g() = empty_nt_values(Base.inferencebarrier(Tuple{})) @test g() == () # Make sure to actually run this to test this in the inference world age - -let # jl_widen_core_extended_info - for (extended, widened) = [(Core.Const(42), Int), - (Core.Const(Int), Type{Int}), - (Core.Const(Vector), Type{Vector}), - (Core.PartialStruct(Some{Any}, Any["julia"]), Some{Any}), - (Core.InterConditional(2, Int, Nothing), Bool)] - @test @ccall(jl_widen_core_extended_info(extended::Any)::Any) === - Core.Compiler.widenconst(extended) === - widened - end -end - -# This is somewhat sensitive to the exact recursion level that inference is willing to do, but the intention -# is to test the case where inference limited a recursion, but then a forced constprop nevertheless managed -# to terminate the call. -@Base.constprop :aggressive type_level_recurse1(x...) = x[1] == 2 ? 1 : (length(x) > 100 ? x : type_level_recurse2(x[1] + 1, x..., x...)) -@Base.constprop :aggressive type_level_recurse2(x...) = type_level_recurse1(x...) -type_level_recurse_entry() = Val{type_level_recurse1(1)}() -@test Base.return_types(type_level_recurse_entry, ()) |> only == Val{1} From 236491446b295fd64c1f447d4fd644ebb41bce2b Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Wed, 11 Jan 2023 00:54:46 +0900 Subject: [PATCH 291/387] limit the type parameter of `CachedMethodTable` to `<:MethodTableView` (#48208) --- base/compiler/methodtable.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/compiler/methodtable.jl b/base/compiler/methodtable.jl index 4456bd600fecc5..7f344aeb0e6de1 100644 --- a/base/compiler/methodtable.jl +++ b/base/compiler/methodtable.jl @@ -54,7 +54,7 @@ end Overlays another method table view with an additional local fast path cache that can respond to repeated, identical queries faster than the original method table. """ -struct CachedMethodTable{T} <: MethodTableView +struct CachedMethodTable{T<:MethodTableView} <: MethodTableView cache::IdDict{MethodMatchKey, Union{Nothing,MethodMatchResult}} table::T end From e81218ea2ffbf83101eca39a0e76b59f215f4ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 10 Jan 2023 16:56:28 +0100 Subject: [PATCH 292/387] Typo fix in comment of sort.jl (#48190) --- base/sort.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/sort.jl b/base/sort.jl index d6ae6aa6e2e101..252d5b962d83b2 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -1943,7 +1943,7 @@ maybe_apply_initial_optimizations(alg::InsertionSortAlg) = InitialOptimizations( # selectpivot! # -# Given 3 locations in an array (lo, mi, and hi), sort v[lo], v[mi], v[hi]) and +# Given 3 locations in an array (lo, mi, and hi), sort v[lo], v[mi], v[hi] and # choose the middle value as a pivot # # Upon return, the pivot is in v[lo], and v[hi] is guaranteed to be From 5f84b3591f60292b63cac0527c5b530fe64cdc67 Mon Sep 17 00:00:00 2001 From: Udoh Jeremiah Date: Tue, 10 Jan 2023 18:49:56 +0100 Subject: [PATCH 293/387] doc: improve RegexMatch example (#48156) --- base/regex.jl | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/base/regex.jl b/base/regex.jl index dfd5d29b8d9781..2837cbb819a7d8 100644 --- a/base/regex.jl +++ b/base/regex.jl @@ -167,16 +167,23 @@ See [`keys`](@ref keys(::RegexMatch)) for more information. julia> m = match(r"(?\\d+):(?\\d+)(am|pm)?", "11:30 in the morning") RegexMatch("11:30", hour="11", minute="30", 3=nothing) -julia> hr, min, ampm = m; +julia> m.match +"11:30" + +julia> m.captures +3-element Vector{Union{Nothing, SubString{String}}}: + "11" + "30" + nothing -julia> hr -"11" julia> m["minute"] "30" -julia> m.match -"11:30" +julia> hr, min, ampm = m; # destructure capture groups by iteration + +julia> hr +"11" ``` """ struct RegexMatch <: AbstractMatch From a229fbdbf5d75d9e37cba35f7fb6142fda8e5c6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunnar=20Farneb=C3=A4ck?= Date: Tue, 10 Jan 2023 18:56:38 +0100 Subject: [PATCH 294/387] [REPL] Fix bug in TerminalMenus when pagesize is larger than the number of options. (#48173) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gunnar Farnebäck --- stdlib/REPL/src/TerminalMenus/AbstractMenu.jl | 6 ++-- .../REPL/test/TerminalMenus/dynamic_menu.jl | 33 +++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/stdlib/REPL/src/TerminalMenus/AbstractMenu.jl b/stdlib/REPL/src/TerminalMenus/AbstractMenu.jl index 2dc7161be99da7..a1f94852b38ec5 100644 --- a/stdlib/REPL/src/TerminalMenus/AbstractMenu.jl +++ b/stdlib/REPL/src/TerminalMenus/AbstractMenu.jl @@ -216,7 +216,7 @@ function request(term::REPL.Terminals.TTYTerminal, m::AbstractMenu; cursor::Unio m.pageoffset = 0 elseif c == Int(END_KEY) cursor[] = lastoption - m.pageoffset = lastoption - m.pagesize + m.pageoffset = max(0, lastoption - m.pagesize) elseif c == 13 # # will break if pick returns true pick(m, cursor[]) && break @@ -269,7 +269,7 @@ function move_up!(m::AbstractMenu, cursor::Int, lastoption::Int=numoptions(m)) elseif scroll_wrap(m) # wrap to bottom cursor = lastoption - m.pageoffset = lastoption - m.pagesize + m.pageoffset = max(0, lastoption - m.pagesize) end return cursor end @@ -299,7 +299,7 @@ end function page_down!(m::AbstractMenu, cursor::Int, lastoption::Int=numoptions(m)) m.pageoffset += m.pagesize - (cursor == 1 ? 1 : 0) - m.pageoffset = min(m.pageoffset, lastoption - m.pagesize) + m.pageoffset = max(0, min(m.pageoffset, lastoption - m.pagesize)) return min(cursor + m.pagesize, lastoption) end diff --git a/stdlib/REPL/test/TerminalMenus/dynamic_menu.jl b/stdlib/REPL/test/TerminalMenus/dynamic_menu.jl index 23d026358385f8..63b48b71734910 100644 --- a/stdlib/REPL/test/TerminalMenus/dynamic_menu.jl +++ b/stdlib/REPL/test/TerminalMenus/dynamic_menu.jl @@ -116,3 +116,36 @@ str = String(take!(io)) nback, strs = linesplitter(str) @test nback == 3 @test strs == ["^ 3", " 4", " 5", " > 6*"] + +# Test with page size larger than number of options. +# END_KEY, PAGE_DOWN, and ARROW_UP (from first element with scroll +# wrap) used to be problematic. The last two are tested here, whereas +# the first one is unreachable within the `request` function. +menu = DynamicMenu(4, 0, -1, 2, TerminalMenus.Config(scroll_wrap = true)) + +cursor = 1 +state = TerminalMenus.printmenu(io, menu, cursor; init=true) +str = String(take!(io)) +@test count(isequal('\n'), str) == state +nback, strs = linesplitter(str) +@test nback == 0 +@test strs == [" > 1*", " 2"] + +cursor = TerminalMenus.page_down!(menu, cursor) +@test cursor == menu.numopts +@test menu.pageoffset == 0 +state = TerminalMenus.printmenu(io, menu, cursor; oldstate=state) +str = String(take!(io)) +nback, strs = linesplitter(str) +@test nback == 1 +@test strs == [" 1", " > 2*"] + +cursor = TerminalMenus.page_up!(menu, cursor) +cursor = TerminalMenus.move_up!(menu, cursor) +@test cursor == menu.numopts +@test menu.pageoffset == 0 +state = TerminalMenus.printmenu(io, menu, cursor; oldstate=state) +str = String(take!(io)) +nback, strs = linesplitter(str) +@test nback == 1 +@test strs == [" 1", " > 2*"] From 79ceb8dbeab1b5a47c6bd664214616c19607ffab Mon Sep 17 00:00:00 2001 From: Zachary P Christensen Date: Tue, 10 Jan 2023 12:56:53 -0500 Subject: [PATCH 295/387] Define `valtype(::NamedTuple)` and `keytype(::NamedTuple)` (#46555) --- base/namedtuple.jl | 6 ++++++ test/namedtuple.jl | 3 +++ 2 files changed, 9 insertions(+) diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 7b58ccce1b3041..9713ca97980fd9 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -219,6 +219,12 @@ eltype(::Type{T}) where T<:NamedTuple = nteltype(T) nteltype(::Type) = Any nteltype(::Type{NamedTuple{names,T}} where names) where {T} = eltype(T) +keytype(@nospecialize nt::NamedTuple) = keytype(typeof(nt)) +keytype(@nospecialize T::Type{<:NamedTuple}) = Symbol + +valtype(@nospecialize nt::NamedTuple) = valtype(typeof(nt)) +valtype(@nospecialize T::Type{<:NamedTuple}) = eltype(T) + ==(a::NamedTuple{n}, b::NamedTuple{n}) where {n} = Tuple(a) == Tuple(b) ==(a::NamedTuple, b::NamedTuple) = false diff --git a/test/namedtuple.jl b/test/namedtuple.jl index 816a0b17a79c27..79d0e4883f25d2 100644 --- a/test/namedtuple.jl +++ b/test/namedtuple.jl @@ -84,6 +84,9 @@ end @test eltype(NamedTuple{(:x, :y),Tuple{Union{Missing, Int},Union{Missing, Float64}}}( (missing, missing))) === Union{Real, Missing} +@test valtype((a=[1,2], b=[3,4])) === Vector{Int} +@test keytype((a=[1,2], b=[3,4])) === Symbol + @test Tuple((a=[1,2], b=[3,4])) == ([1,2], [3,4]) @test Tuple(NamedTuple()) === () @test Tuple((x=4, y=5, z=6)) == (4,5,6) From 1e5fdb29f8858f3244f6aff116ee12e4c8247f3a Mon Sep 17 00:00:00 2001 From: Simon Byrne Date: Tue, 10 Jan 2023 14:52:36 -0800 Subject: [PATCH 296/387] update MPFR to 4.2.0 (#48165) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mosè Giordano --- base/mpfr.jl | 34 +++++++++++-- deps/checksums/mpfr | 68 +++++++++++++------------- deps/mpfr.version | 2 +- stdlib/MPFR_jll/Project.toml | 2 +- stdlib/MPFR_jll/test/runtests.jl | 2 +- test/math.jl | 83 +++++++++++++++++--------------- 6 files changed, 112 insertions(+), 79 deletions(-) diff --git a/base/mpfr.jl b/base/mpfr.jl index 047f682e769016..d3ad34cd5c42f8 100644 --- a/base/mpfr.jl +++ b/base/mpfr.jl @@ -16,7 +16,8 @@ import cosh, sinh, tanh, sech, csch, coth, acosh, asinh, atanh, lerpi, cbrt, typemax, typemin, unsafe_trunc, floatmin, floatmax, rounding, setrounding, maxintfloat, widen, significand, frexp, tryparse, iszero, - isone, big, _string_n, decompose, minmax + isone, big, _string_n, decompose, minmax, + sinpi, cospi, sincospi, sind, cosd, tand, asind, acosd, atand import ..Rounding: rounding_raw, setrounding_raw @@ -780,7 +781,7 @@ function sum(arr::AbstractArray{BigFloat}) end # Functions for which NaN results are converted to DomainError, following Base -for f in (:sin, :cos, :tan, :sec, :csc, :acos, :asin, :atan, :acosh, :asinh, :atanh) +for f in (:sin, :cos, :tan, :sec, :csc, :acos, :asin, :atan, :acosh, :asinh, :atanh, :sinpi, :cospi) @eval begin function ($f)(x::BigFloat) isnan(x) && return x @@ -791,6 +792,7 @@ for f in (:sin, :cos, :tan, :sec, :csc, :acos, :asin, :atan, :acosh, :asinh, :at end end end +sincospi(x::BigFloat) = (sinpi(x), cospi(x)) function atan(y::BigFloat, x::BigFloat) z = BigFloat() @@ -798,6 +800,32 @@ function atan(y::BigFloat, x::BigFloat) return z end +# degree functions +for f in (:sin, :cos, :tan) + @eval begin + function ($(Symbol(f,:d)))(x::BigFloat) + isnan(x) && return x + z = BigFloat() + ccall(($(string(:mpfr_,f,:u)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, x, 360, ROUNDING_MODE[]) + isnan(z) && throw(DomainError(x, "NaN result for non-NaN input.")) + return z + end + function ($(Symbol(:a,f,:d)))(x::BigFloat) + isnan(x) && return x + z = BigFloat() + ccall(($(string(:mpfr_a,f,:u)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, x, 360, ROUNDING_MODE[]) + isnan(z) && throw(DomainError(x, "NaN result for non-NaN input.")) + return z + end + end +end +function atand(y::BigFloat, x::BigFloat) + z = BigFloat() + ccall((:mpfr_atan2u, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, y, x, 360, ROUNDING_MODE[]) + return z +end + + # Utility functions ==(x::BigFloat, y::BigFloat) = ccall((:mpfr_equal_p, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), x, y) != 0 <=(x::BigFloat, y::BigFloat) = ccall((:mpfr_lessequal_p, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), x, y) != 0 @@ -1053,7 +1081,7 @@ function _string(x::BigFloat, fmt::String)::String isfinite(x) || return string(Float64(x)) _prettify_bigfloat(string_mpfr(x, fmt)) end -_string(x::BigFloat) = _string(x, "%.Re") +_string(x::BigFloat) = _string(x, "%Re") _string(x::BigFloat, k::Integer) = _string(x, "%.$(k)Re") string(b::BigFloat) = _string(b) diff --git a/deps/checksums/mpfr b/deps/checksums/mpfr index 99c02301251d8a..2b4281659b13a5 100644 --- a/deps/checksums/mpfr +++ b/deps/checksums/mpfr @@ -1,34 +1,34 @@ -MPFR.v4.1.1+4.aarch64-apple-darwin.tar.gz/md5/07c92f3104cf508189292287719e77df -MPFR.v4.1.1+4.aarch64-apple-darwin.tar.gz/sha512/75f828f39091abcb8c8742ba7ea2bea2affb1644848a4272ec39081d6ad1399f027c3371f922d424c5d8bc72b78b408ce78f53a3c7b784140b2002f140684665 -MPFR.v4.1.1+4.aarch64-linux-gnu.tar.gz/md5/a6f60de83c161fa401c5a49c283ee94a -MPFR.v4.1.1+4.aarch64-linux-gnu.tar.gz/sha512/1c3f52d0f3c9005f2290a7a632458486972f768a9772a55ec59438f5010441768e1351a1a23e4a0b1f341b038324ceea0032b1efc0a0ad017aacbf70cde2cafb -MPFR.v4.1.1+4.aarch64-linux-musl.tar.gz/md5/8e6bc4cf8b94bdbd08ec7428d29f75b7 -MPFR.v4.1.1+4.aarch64-linux-musl.tar.gz/sha512/08489b81aa665bb2eb62c6c804c1c041c90587a0df6004a10017a3490c4ad049511dcca29cf38dbaada44fbf783b2bd1a788797dc16f128adce77bef4ec9a4a3 -MPFR.v4.1.1+4.armv6l-linux-gnueabihf.tar.gz/md5/f6f7f3f264e7b48ee9a518f21a7249f5 -MPFR.v4.1.1+4.armv6l-linux-gnueabihf.tar.gz/sha512/3b45907a1de70fcddf5a0fb90ce45d5dabf09f11b92e1174e2779a79b0991c75b1c9037981b0cd999f32cebfc358d311377af71130222a5b81dbb43c0a9ebe76 -MPFR.v4.1.1+4.armv6l-linux-musleabihf.tar.gz/md5/07304ab9676c39c56aad073f2825fd1d -MPFR.v4.1.1+4.armv6l-linux-musleabihf.tar.gz/sha512/3c7a872aab1baa4d1966cbf42cc09799944d319441f41df560632f5e4d9af9c71de25c714faab223aa1cf4e5ae09ff68c514d073711b07758e033cd492bf7eb7 -MPFR.v4.1.1+4.armv7l-linux-gnueabihf.tar.gz/md5/261482058f90306858833156bb332281 -MPFR.v4.1.1+4.armv7l-linux-gnueabihf.tar.gz/sha512/c0acb7f476a736360763e269fb7b309b9f8843d19a9931694bb01efe77e6fe4f12c969d9ae0e16c16cb14cd9a0d67ff91fa02ba141c3f2f7b908170cac216800 -MPFR.v4.1.1+4.armv7l-linux-musleabihf.tar.gz/md5/c61c6d04f3d4147b76480867e90d2262 -MPFR.v4.1.1+4.armv7l-linux-musleabihf.tar.gz/sha512/3e6cc63c7404899de3d4e4da208c40e363f427ce1bd4f0c1d5d04711870239240a8b98e4d152f6d78128e4430f703ab0debe6c35e6cd8ef80aa4a605105d619f -MPFR.v4.1.1+4.i686-linux-gnu.tar.gz/md5/0dff053d5488f220f94a56beae0bf4a4 -MPFR.v4.1.1+4.i686-linux-gnu.tar.gz/sha512/26c5c4b91998f5bcffcf5a873c451acab376efd25e13671ec5cb4f1316d1866cf7fc841f7aff17a339326ed1730b720be8ab39349ff5cee0619891925b4eb79e -MPFR.v4.1.1+4.i686-linux-musl.tar.gz/md5/2edb5f985db6b39115f13bd05d623677 -MPFR.v4.1.1+4.i686-linux-musl.tar.gz/sha512/207f346be68458aeadc803d0466eba428b63c7ee9c654b06c00ae4a7e2bbd01ab3644f1db1ef9870730937a37e658956bdc2fdcab70d4619e149574a48a7191d -MPFR.v4.1.1+4.i686-w64-mingw32.tar.gz/md5/7228b731bfb530c48d5afe7c5f51cccc -MPFR.v4.1.1+4.i686-w64-mingw32.tar.gz/sha512/faac80db43d5c252c8d7f90a56b832b6a8bd7543465dadc57dfc8590c6eb54e49c96d6b337b4caeeba73917440be512d115b54485de73b6194f67d67e3d11dce -MPFR.v4.1.1+4.powerpc64le-linux-gnu.tar.gz/md5/27e01308e698ddd83a68cd0fdbea318b -MPFR.v4.1.1+4.powerpc64le-linux-gnu.tar.gz/sha512/48718cff4df3e16c50d7ed47fc0a693699919b9033fd31084e125d8a7abb68cecfcf6e1b34be83f4b6ada9d168a01fc653b4e33e1b5021b3143e603b560a8225 -MPFR.v4.1.1+4.x86_64-apple-darwin.tar.gz/md5/a91682cb62bd6c7f8acb36a33585867a -MPFR.v4.1.1+4.x86_64-apple-darwin.tar.gz/sha512/82d2ff90e1a8a358f2fab643dfc3ead84edc8fabcf956b7479c0a0b1005430187a5315951e1b160e843776233cb2d655b5a27cfd37691cfed42f9b89f824e525 -MPFR.v4.1.1+4.x86_64-linux-gnu.tar.gz/md5/d3a3c97177e554685882f7b9f3eb0ee8 -MPFR.v4.1.1+4.x86_64-linux-gnu.tar.gz/sha512/c7af9df8c12ea3d3f784a048aae7c630f07515b509d9d0a3e0003b9697a3370112c3507a39b442d80a5671df95c2fa6a20b446443ac4cb0d48f3108e21e0d755 -MPFR.v4.1.1+4.x86_64-linux-musl.tar.gz/md5/bda6453ee85bf43348c41ebfd4accc94 -MPFR.v4.1.1+4.x86_64-linux-musl.tar.gz/sha512/0e85dd4361a67c7fe91bf9fffaad0eddfc93d578b0452e662628124d1e7589502221f20919d442875c731f57678c87b30ccfa1e9a00a77a6b42740dce96fd410 -MPFR.v4.1.1+4.x86_64-unknown-freebsd.tar.gz/md5/b2e40a50e486991660c30985a0ee6214 -MPFR.v4.1.1+4.x86_64-unknown-freebsd.tar.gz/sha512/bfc3010b2c94384ca2050b41e08ca26b22c813c1f38b274074854430a736f0f45530ee0df36030cfa479950848d8623c4e9b07fc8de4f6fbfda31a98abc9a4c6 -MPFR.v4.1.1+4.x86_64-w64-mingw32.tar.gz/md5/1b87833f68846d342dbdf283f3d39170 -MPFR.v4.1.1+4.x86_64-w64-mingw32.tar.gz/sha512/5c85a5664b4106eae733be0a85e8ab645b93dd78983cab8741cc13451ea429cb432a783f5a3b2a815db9376eb8bf83a6649247ef028d6a7f5dab9e519a9005b4 -mpfr-4.1.1.tar.bz2/md5/48eea07f8bb60dd9bbec1ec37a749f24 -mpfr-4.1.1.tar.bz2/sha512/f0efefbfc4dec367cdab6299272062508ec80d53daa779fe05954cd626983277039a10d9d072ae686584f6ce75014ef2136e3f095128fa21fc994f7c6f33d674 +MPFR.v4.2.0+0.aarch64-apple-darwin.tar.gz/md5/f9393a636497b19c846343b456b2dd7e +MPFR.v4.2.0+0.aarch64-apple-darwin.tar.gz/sha512/a77a0387e84f572ef5558977096e70da8eb7b3674a8198cc6ae35462971f76d684145ffae7c2ddca32e2bd1c8b2ccb33e4447eb8606d5d5cd5958298472b3ea9 +MPFR.v4.2.0+0.aarch64-linux-gnu.tar.gz/md5/ade253017d195de694780c32f9161dcf +MPFR.v4.2.0+0.aarch64-linux-gnu.tar.gz/sha512/1b68de5f8e557b7434c8c1bc016227b58683b56c0977b763422ea85a673bec446fcfee3a4f69e1d4689abb9bb6bf47f2a50fbb56ecac6a9d40096e66bd0f2080 +MPFR.v4.2.0+0.aarch64-linux-musl.tar.gz/md5/7dbd121c7192ccaf7191de5ab8d91afb +MPFR.v4.2.0+0.aarch64-linux-musl.tar.gz/sha512/8614e3cb28491b24a0ec5060b44abaf264b61c91ddd29d70105ff583bd3112cff1b9bd5ed45e39f186265333982d5eeb8bf35fedc3b51b2a009cc7a51046b50b +MPFR.v4.2.0+0.armv6l-linux-gnueabihf.tar.gz/md5/adb2b7fdf111c8b19df1516cfb278bb1 +MPFR.v4.2.0+0.armv6l-linux-gnueabihf.tar.gz/sha512/0c47aeffd05a194802f6c4e0e2779d56fb46007e6c3e145ee6992854a21a317a9d51512c59a0ce4ddcd314c387945225c6557d6c2ab6961ae4848875e8983de8 +MPFR.v4.2.0+0.armv6l-linux-musleabihf.tar.gz/md5/c30358bdeffcff65ba9be906cd35889b +MPFR.v4.2.0+0.armv6l-linux-musleabihf.tar.gz/sha512/2857ec27ae2d53a451d62dd241ce9b43f7ee182bee180ecd9ad92c907c66d0b0ab2d1ea3b20fe61cc176ae44ecbe6041305cc8a9343b396c9cb54dd77a1e2868 +MPFR.v4.2.0+0.armv7l-linux-gnueabihf.tar.gz/md5/a1e30436bade2150c9dc924177f0c321 +MPFR.v4.2.0+0.armv7l-linux-gnueabihf.tar.gz/sha512/d2f4662c494fefda66847e7a085edda3ce396383aafb4e17fc2e176191b0f530541726c261cac3467f13136e8ec728c8a7cf0e352f3e9ebf960d153cbfe766b8 +MPFR.v4.2.0+0.armv7l-linux-musleabihf.tar.gz/md5/857e3c82804e7c853d21603f18caa715 +MPFR.v4.2.0+0.armv7l-linux-musleabihf.tar.gz/sha512/86cf3e940fd66820b5269e9aa2a49c3fc3077857bec037a08e0d301b0bf3cc5c79ac331cc6370d852e20f4acf8f601c49d5dbe24e96652e4411b3f33a11e3f45 +MPFR.v4.2.0+0.i686-linux-gnu.tar.gz/md5/5a432be79a112e67e970980f4bde13a0 +MPFR.v4.2.0+0.i686-linux-gnu.tar.gz/sha512/94198b23ac94dcb9dca95938a46b9899c3ef329bafbb13b32076cd3415b89f11908632c7c07e90549c01bd9ed7fc9a002dae07a645f85b8509234c49be729621 +MPFR.v4.2.0+0.i686-linux-musl.tar.gz/md5/4ce71dc250c2469f844a02c6ee6571a1 +MPFR.v4.2.0+0.i686-linux-musl.tar.gz/sha512/134b67b23de75ab172594cd0fac55b5c265730bfea195978698e3e6fbc47d65617652bd72d90ba092ed1bac4c29d5b2c109df5d8dc60b5d8f91159fd58575b67 +MPFR.v4.2.0+0.i686-w64-mingw32.tar.gz/md5/df41bde61d33b56fd48bdb0f9ec0c624 +MPFR.v4.2.0+0.i686-w64-mingw32.tar.gz/sha512/145bc14f22eb077992cd993a20d3205eeeee1d2bb99ff4f48277173b0b39c848e2cd3044d2141003607aa4ea3665546a87b9ffea87bf570ab1b152117ef4045c +MPFR.v4.2.0+0.powerpc64le-linux-gnu.tar.gz/md5/d818894054b38232ba02ee0e129f6fe0 +MPFR.v4.2.0+0.powerpc64le-linux-gnu.tar.gz/sha512/0e73ca926f3e06466d1899f0b3e9ae4abe15102804dce6716ce23154344a571773c40d276f0038a0ae4e626799867ee715428e1d961334a01ad3091745367e8e +MPFR.v4.2.0+0.x86_64-apple-darwin.tar.gz/md5/9652148df4e771be39713c4f43d3ff61 +MPFR.v4.2.0+0.x86_64-apple-darwin.tar.gz/sha512/91a0219fd1880dfa90d196fa403f4e1df0347ced58a4772492196b94476f346d80696885a4f3520424494bc09679cca0c0ccf2f6e9247d60b52ebdf564485e72 +MPFR.v4.2.0+0.x86_64-linux-gnu.tar.gz/md5/4de39327a792be708119ac7b43957628 +MPFR.v4.2.0+0.x86_64-linux-gnu.tar.gz/sha512/447b59d5589a8517061627668e8baed4366408cacc9d8e063528b9b795de6d27e4005844578310185f03f568f4948bc4a794624235875fb61b6187264b6f483b +MPFR.v4.2.0+0.x86_64-linux-musl.tar.gz/md5/f9b8c3c094b339341b19828cc5e1d47c +MPFR.v4.2.0+0.x86_64-linux-musl.tar.gz/sha512/c661e7c5bded3bdf11b2bd5e5ef4ad8e446934d9b82dfe26f0be1b83cea98d7e56e0903bfc1075f91c8d23401cc6b3b722f2d60f46d73cab884e81fe518aba27 +MPFR.v4.2.0+0.x86_64-unknown-freebsd.tar.gz/md5/83700aaebc7344d84d70f0bd0f9c7890 +MPFR.v4.2.0+0.x86_64-unknown-freebsd.tar.gz/sha512/039cb18a142a90fadc7951f05324fe9c033da9502a61da77fdcd5d9557075ad1ca8500b9b9b39ce57a44b9cb28d41dfc6cbde10cfdbdb40077ebada24a2bab9a +MPFR.v4.2.0+0.x86_64-w64-mingw32.tar.gz/md5/9cdaa3fc0d13a8835d165c745937c385 +MPFR.v4.2.0+0.x86_64-w64-mingw32.tar.gz/sha512/21464bf836362ecc50da82859a4ba2de3d32d76ff57de9719ac850e73918814e1002130e0d6797fbb914b822f13bea383be3a29b2a1c9c8415cb2e3c5d321669 +mpfr-4.2.0.tar.bz2/md5/f8c66d737283fd35f9fe433fb419b05f +mpfr-4.2.0.tar.bz2/sha512/cb2a9314b94e34a4ea49ce2619802e9420c982e55258a4bc423f802740632646a3d420e7fcf373b19618385b8b2b412abfa127e8f473053863424cac233893c0 diff --git a/deps/mpfr.version b/deps/mpfr.version index 5e9e119e3e3a1e..e4f1c8a45aeb05 100644 --- a/deps/mpfr.version +++ b/deps/mpfr.version @@ -2,4 +2,4 @@ MPFR_JLL_NAME := MPFR ## source build -MPFR_VER := 4.1.1 +MPFR_VER := 4.2.0 diff --git a/stdlib/MPFR_jll/Project.toml b/stdlib/MPFR_jll/Project.toml index 560c25a5564017..39f99815832eb9 100644 --- a/stdlib/MPFR_jll/Project.toml +++ b/stdlib/MPFR_jll/Project.toml @@ -1,6 +1,6 @@ name = "MPFR_jll" uuid = "3a97d323-0669-5f0c-9066-3539efd106a3" -version = "4.1.1+4" +version = "4.2.0+0" [deps] GMP_jll = "781609d7-10c4-51f6-84f2-b8444358ff6d" diff --git a/stdlib/MPFR_jll/test/runtests.jl b/stdlib/MPFR_jll/test/runtests.jl index 31c4ed07025512..81b6e06ed7b49d 100644 --- a/stdlib/MPFR_jll/test/runtests.jl +++ b/stdlib/MPFR_jll/test/runtests.jl @@ -4,5 +4,5 @@ using Test, Libdl, MPFR_jll @testset "MPFR_jll" begin vn = VersionNumber(unsafe_string(ccall((:mpfr_get_version,libmpfr), Cstring, ()))) - @test vn == v"4.1.1" + @test vn == v"4.2.0" end diff --git a/test/math.jl b/test/math.jl index 93fcf1a8e3150a..e36c1c2195b946 100644 --- a/test/math.jl +++ b/test/math.jl @@ -19,6 +19,7 @@ has_fma = Dict( Rational{Int} => has_fma_Int(), Float32 => has_fma_Float32(), Float64 => has_fma_Float64(), + BigFloat => true, ) @testset "clamp" begin @@ -424,47 +425,51 @@ end @test rad2deg(pi + (pi/3)*im) ≈ 180 + 60im end +# ensure zeros are signed the same +⩲(x,y) = typeof(x) == typeof(y) && x == y && signbit(x) == signbit(y) +⩲(x::Tuple, y::Tuple) = length(x) == length(y) && all(map(⩲,x,y)) + @testset "degree-based trig functions" begin - @testset "$T" for T = (Float32,Float64,Rational{Int}) + @testset "$T" for T = (Float32,Float64,Rational{Int},BigFloat) fT = typeof(float(one(T))) fTsc = typeof( (float(one(T)), float(one(T))) ) for x = -400:40:400 - @test sind(convert(T,x))::fT ≈ convert(fT,sin(pi/180*x)) atol=eps(deg2rad(convert(fT,x))) - @test cosd(convert(T,x))::fT ≈ convert(fT,cos(pi/180*x)) atol=eps(deg2rad(convert(fT,x))) + @test sind(convert(T,x))::fT ≈ sin(pi*convert(fT,x)/180) atol=eps(deg2rad(convert(fT,x))) + @test cosd(convert(T,x))::fT ≈ cos(pi*convert(fT,x)/180) atol=eps(deg2rad(convert(fT,x))) s,c = sincosd(convert(T,x)) - @test s::fT ≈ convert(fT,sin(pi/180*x)) atol=eps(deg2rad(convert(fT,x))) - @test c::fT ≈ convert(fT,cos(pi/180*x)) atol=eps(deg2rad(convert(fT,x))) + @test s::fT ≈ sin(pi*convert(fT,x)/180) atol=eps(deg2rad(convert(fT,x))) + @test c::fT ≈ cos(pi*convert(fT,x)/180) atol=eps(deg2rad(convert(fT,x))) end @testset "sind" begin - @test sind(convert(T,0.0))::fT === zero(fT) - @test sind(convert(T,180.0))::fT === zero(fT) - @test sind(convert(T,360.0))::fT === zero(fT) - T != Rational{Int} && @test sind(convert(T,-0.0))::fT === -zero(fT) - @test sind(convert(T,-180.0))::fT === -zero(fT) - @test sind(convert(T,-360.0))::fT === -zero(fT) + @test sind(convert(T,0.0))::fT ⩲ zero(fT) + @test sind(convert(T,180.0))::fT ⩲ zero(fT) + @test sind(convert(T,360.0))::fT ⩲ zero(fT) + T != Rational{Int} && @test sind(convert(T,-0.0))::fT ⩲ -zero(fT) + @test sind(convert(T,-180.0))::fT ⩲ -zero(fT) + @test sind(convert(T,-360.0))::fT ⩲ -zero(fT) if T <: AbstractFloat @test isnan(sind(T(NaN))) end end @testset "cosd" begin - @test cosd(convert(T,90))::fT === zero(fT) - @test cosd(convert(T,270))::fT === zero(fT) - @test cosd(convert(T,-90))::fT === zero(fT) - @test cosd(convert(T,-270))::fT === zero(fT) + @test cosd(convert(T,90))::fT ⩲ zero(fT) + @test cosd(convert(T,270))::fT ⩲ zero(fT) + @test cosd(convert(T,-90))::fT ⩲ zero(fT) + @test cosd(convert(T,-270))::fT ⩲ zero(fT) if T <: AbstractFloat @test isnan(cosd(T(NaN))) end end @testset "sincosd" begin - @test sincosd(convert(T,-360))::fTsc === ( -zero(fT), one(fT) ) - @test sincosd(convert(T,-270))::fTsc === ( one(fT), zero(fT) ) - @test sincosd(convert(T,-180))::fTsc === ( -zero(fT), -one(fT) ) - @test sincosd(convert(T, -90))::fTsc === ( -one(fT), zero(fT) ) - @test sincosd(convert(T, 0))::fTsc === ( zero(fT), one(fT) ) - @test sincosd(convert(T, 90))::fTsc === ( one(fT), zero(fT) ) - @test sincosd(convert(T, 180))::fTsc === ( zero(fT), -one(fT) ) - @test sincosd(convert(T, 270))::fTsc === ( -one(fT), zero(fT) ) + @test sincosd(convert(T,-360))::fTsc ⩲ ( -zero(fT), one(fT) ) + @test sincosd(convert(T,-270))::fTsc ⩲ ( one(fT), zero(fT) ) + @test sincosd(convert(T,-180))::fTsc ⩲ ( -zero(fT), -one(fT) ) + @test sincosd(convert(T, -90))::fTsc ⩲ ( -one(fT), zero(fT) ) + @test sincosd(convert(T, 0))::fTsc ⩲ ( zero(fT), one(fT) ) + @test sincosd(convert(T, 90))::fTsc ⩲ ( one(fT), zero(fT) ) + @test sincosd(convert(T, 180))::fTsc ⩲ ( zero(fT), -one(fT) ) + @test sincosd(convert(T, 270))::fTsc ⩲ ( -one(fT), zero(fT) ) if T <: AbstractFloat @test_throws DomainError sincosd(T(Inf)) @test all(isnan.(sincosd(T(NaN)))) @@ -476,22 +481,22 @@ end "sincospi" => (x->sincospi(x)[1], x->sincospi(x)[2]) ) @testset "pi * $x" for x = -3:0.3:3 - @test sinpi(convert(T,x))::fT ≈ convert(fT,sin(pi*x)) atol=eps(pi*convert(fT,x)) - @test cospi(convert(T,x))::fT ≈ convert(fT,cos(pi*x)) atol=eps(pi*convert(fT,x)) + @test sinpi(convert(T,x))::fT ≈ sin(pi*convert(fT,x)) atol=eps(pi*convert(fT,x)) + @test cospi(convert(T,x))::fT ≈ cos(pi*convert(fT,x)) atol=eps(pi*convert(fT,x)) end - @test sinpi(convert(T,0.0))::fT === zero(fT) - @test sinpi(convert(T,1.0))::fT === zero(fT) - @test sinpi(convert(T,2.0))::fT === zero(fT) - T != Rational{Int} && @test sinpi(convert(T,-0.0))::fT === -zero(fT) - @test sinpi(convert(T,-1.0))::fT === -zero(fT) - @test sinpi(convert(T,-2.0))::fT === -zero(fT) + @test sinpi(convert(T,0.0))::fT ⩲ zero(fT) + @test sinpi(convert(T,1.0))::fT ⩲ zero(fT) + @test sinpi(convert(T,2.0))::fT ⩲ zero(fT) + T != Rational{Int} && @test sinpi(convert(T,-0.0))::fT ⩲ -zero(fT) + @test sinpi(convert(T,-1.0))::fT ⩲ -zero(fT) + @test sinpi(convert(T,-2.0))::fT ⩲ -zero(fT) @test_throws DomainError sinpi(convert(T,Inf)) - @test cospi(convert(T,0.5))::fT === zero(fT) - @test cospi(convert(T,1.5))::fT === zero(fT) - @test cospi(convert(T,-0.5))::fT === zero(fT) - @test cospi(convert(T,-1.5))::fT === zero(fT) + @test cospi(convert(T,0.5))::fT ⩲ zero(fT) + @test cospi(convert(T,1.5))::fT ⩲ zero(fT) + @test cospi(convert(T,-0.5))::fT ⩲ zero(fT) + @test cospi(convert(T,-1.5))::fT ⩲ zero(fT) @test_throws DomainError cospi(convert(T,Inf)) end @testset begin @@ -512,8 +517,8 @@ end @test my_eq(sincospi(one(T)/convert(T,6))[1], 0.5) @test_throws DomainError sind(convert(T,Inf)) @test_throws DomainError cosd(convert(T,Inf)) - T != Float32 && @test my_eq(cospi(one(T)/convert(T,3)), 0.5) - T != Float32 && @test my_eq(sincospi(one(T)/convert(T,3))[2], 0.5) + fT == Float64 && @test my_eq(cospi(one(T)/convert(T,3)), 0.5) + fT == Float64 && @test my_eq(sincospi(one(T)/convert(T,3))[2], 0.5) T == Rational{Int} && @test my_eq(sinpi(5//6), 0.5) T == Rational{Int} && @test my_eq(sincospi(5//6)[1], 0.5) end @@ -562,8 +567,8 @@ end end end end - @test @inferred(sinc(0//1)) === 1.0 - @test @inferred(cosc(0//1)) === -0.0 + @test @inferred(sinc(0//1)) ⩲ 1.0 + @test @inferred(cosc(0//1)) ⩲ -0.0 # test right before/after thresholds of Taylor series @test sinc(0.001) ≈ 0.999998355066745 rtol=1e-15 From 392725da2fccf84be90bf7fcfa8b871ea6a27985 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Wed, 11 Jan 2023 13:35:18 +0900 Subject: [PATCH 297/387] recover test case that was accidentally removed by #48194 (#48222) --- test/compiler/inference.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index b045e9b5333cb8..b7adcba2979253 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -4689,3 +4689,11 @@ end @test Base.return_types(empty_nt_keys, (Any,)) |> only === Tuple{} g() = empty_nt_values(Base.inferencebarrier(Tuple{})) @test g() == () # Make sure to actually run this to test this in the inference world age + +# This is somewhat sensitive to the exact recursion level that inference is willing to do, but the intention +# is to test the case where inference limited a recursion, but then a forced constprop nevertheless managed +# to terminate the call. +Base.@constprop :aggressive type_level_recurse1(x...) = x[1] == 2 ? 1 : (length(x) > 100 ? x : type_level_recurse2(x[1] + 1, x..., x...)) +Base.@constprop :aggressive type_level_recurse2(x...) = type_level_recurse1(x...) +type_level_recurse_entry() = Val{type_level_recurse1(1)}() +@test Base.return_types(type_level_recurse_entry, ()) |> only == Val{1} From 07172386ddf73ae34a656c33535390d24ef0196c Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Mon, 9 Jan 2023 22:55:46 +0000 Subject: [PATCH 298/387] effects: Improve is_mutation_free_type to also include immutable types thereof Addresses an outstanding todo. The property is memoized in the datatype like `isbitstype`. It is possible for types to refer to themselves via fields, so some form of memoization is required to avoid an infinite recursion. --- base/compiler/typeutils.jl | 18 +++--------------- base/reflection.jl | 29 +++++++++++++++++++++++++---- src/datatype.c | 23 +++++++++++++++++++++-- src/jltypes.c | 21 +++++++++++++++++++-- src/julia.h | 17 +++++++++-------- test/compiler/effects.jl | 9 ++++++++- 6 files changed, 85 insertions(+), 32 deletions(-) diff --git a/base/compiler/typeutils.jl b/base/compiler/typeutils.jl index 0950150637b0a8..510d3f3614a355 100644 --- a/base/compiler/typeutils.jl +++ b/base/compiler/typeutils.jl @@ -93,7 +93,7 @@ function datatype_min_ninitialized(t::DataType) return length(t.name.names) - t.name.n_uninitialized end -has_concrete_subtype(d::DataType) = d.flags & 0x20 == 0x20 # n.b. often computed only after setting the type and layout fields +has_concrete_subtype(d::DataType) = d.flags & 0x0020 == 0x0020 # n.b. often computed only after setting the type and layout fields # determine whether x is a valid lattice element tag # For example, Type{v} is not valid if v is a value @@ -353,18 +353,6 @@ function _is_immutable_type(@nospecialize ty) end is_mutation_free_argtype(@nospecialize argtype) = - is_mutation_free_type(widenconst(ignorelimited(argtype))) + ismutationfree(widenconst(ignorelimited(argtype))) is_mutation_free_type(@nospecialize ty) = - _is_mutation_free_type(unwrap_unionall(ty)) -function _is_mutation_free_type(@nospecialize ty) - if isa(ty, Union) - return _is_mutation_free_type(ty.a) && _is_mutation_free_type(ty.b) - end - if isType(ty) || ty === DataType || ty === String || ty === Symbol || ty === SimpleVector - return true - end - # this is okay as access and modification on global state are tracked separately - ty === Module && return true - # TODO improve this analysis, e.g. allow `Some{Symbol}` - return isbitstype(ty) -end + ismutationfree(ty) diff --git a/base/reflection.jl b/base/reflection.jl index 6b4d25b1cd5172..078bfb2d981059 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -580,7 +580,7 @@ function isprimitivetype(@nospecialize t) t = unwrap_unionall(t) # TODO: what to do for `Union`? isa(t, DataType) || return false - return (t.flags & 0x80) == 0x80 + return (t.flags & 0x0080) == 0x0080 end """ @@ -606,7 +606,7 @@ julia> isbitstype(Complex) false ``` """ -isbitstype(@nospecialize t) = (@_total_meta; isa(t, DataType) && (t.flags & 0x8) == 0x8) +isbitstype(@nospecialize t) = (@_total_meta; isa(t, DataType) && (t.flags & 0x0008) == 0x0008) """ isbits(x) @@ -622,7 +622,28 @@ Determine whether type `T` is a tuple "leaf type", meaning it could appear as a type signature in dispatch and has no subtypes (or supertypes) which could appear in a call. """ -isdispatchtuple(@nospecialize(t)) = (@_total_meta; isa(t, DataType) && (t.flags & 0x4) == 0x4) +isdispatchtuple(@nospecialize(t)) = (@_total_meta; isa(t, DataType) && (t.flags & 0x0004) == 0x0004) + + +datatype_ismutationfree(dt::DataType) = (@_total_meta; (dt.flags & 0x0100) == 0x0100) + +""" + ismutationfree(T) + +Determine whether type `T` is mutation free in the sense that no mutable memory +is reachable from this type (either in the type itself) or through any fields. +Note that the type itself need not be immutable. For example, an empty mutable +type is `ismutabletype`, but also `ismutationfree`. +""" +function ismutationfree(@nospecialize(t::Type)) + t = unwrap_unionall(t) + if isa(t, DataType) + return datatype_ismutationfree(t) + elseif isa(t, Union) + return ismutationfree(t.a) && ismutationfree(t.b) + end + return false +end iskindtype(@nospecialize t) = (t === DataType || t === UnionAll || t === Union || t === typeof(Bottom)) isconcretedispatch(@nospecialize t) = isconcretetype(t) && !iskindtype(t) @@ -667,7 +688,7 @@ julia> isconcretetype(Union{Int,String}) false ``` """ -isconcretetype(@nospecialize(t)) = (@_total_meta; isa(t, DataType) && (t.flags & 0x2) == 0x2) +isconcretetype(@nospecialize(t)) = (@_total_meta; isa(t, DataType) && (t.flags & 0x0002) == 0x0002) """ isabstracttype(T) diff --git a/src/datatype.c b/src/datatype.c index 920475af955292..161263296d6ce1 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -104,6 +104,7 @@ jl_datatype_t *jl_new_uninitialized_datatype(void) t->zeroinit = 0; t->has_concrete_subtype = 1; t->cached_by_hash = 0; + t->ismutationfree = 0; t->name = NULL; t->super = NULL; t->parameters = NULL; @@ -446,6 +447,20 @@ static void throw_ovf(int should_malloc, void *desc, jl_datatype_t* st, int offs jl_errorf("type %s has field offset %d that exceeds the page size", jl_symbol_name(st->name->name), offset); } +static int is_type_mutationfree(jl_value_t *t) +{ + t = jl_unwrap_unionall(t); + if (jl_is_uniontype(t)) { + jl_uniontype_t *u = (jl_uniontype_t*)t; + return is_type_mutationfree(u->a) && is_type_mutationfree(u->b); + } + if (jl_is_datatype(t)) { + return ((jl_datatype_t*)t)->ismutationfree; + } + // Free tvars, etc. + return 0; +} + void jl_compute_field_offsets(jl_datatype_t *st) { const uint64_t max_offset = (((uint64_t)1) << 32) - 1; @@ -461,6 +476,7 @@ void jl_compute_field_offsets(jl_datatype_t *st) st->has_concrete_subtype = 1; } int isbitstype = st->isconcretetype && st->name->mayinlinealloc; + int ismutationfree = !w->layout || !jl_is_layout_opaque(w->layout); // If layout doesn't depend on type parameters, it's stored in st->name->wrapper // and reused by all subtypes. if (w->layout) { @@ -511,9 +527,10 @@ void jl_compute_field_offsets(jl_datatype_t *st) } } - for (i = 0; isbitstype && i < nfields; i++) { + for (i = 0; (isbitstype || ismutationfree) && i < nfields; i++) { jl_value_t *fld = jl_field_type(st, i); - isbitstype = jl_isbits(fld); + isbitstype &= jl_isbits(fld); + ismutationfree &= (!st->name->mutabl || jl_field_isconst(st, i)) && is_type_mutationfree(fld); } // if we didn't reuse the layout above, compute it now @@ -645,6 +662,7 @@ void jl_compute_field_offsets(jl_datatype_t *st) // now finish deciding if this instantiation qualifies for special properties assert(!isbitstype || st->layout->npointers == 0); // the definition of isbits st->isbitstype = isbitstype; + st->ismutationfree = ismutationfree; jl_maybe_allocate_singleton_instance(st); return; } @@ -791,6 +809,7 @@ JL_DLLEXPORT jl_datatype_t *jl_new_primitivetype(jl_value_t *name, jl_module_t * // (dta->name->names == svec() && dta->layout && dta->layout->size != 0) // and we easily have a free bit for it in the DataType flags bt->isprimitivetype = 1; + bt->ismutationfree = 1; bt->isbitstype = (parameters == jl_emptysvec); bt->layout = jl_get_layout(nbytes, 0, 0, alignm, 0, NULL, NULL); bt->instance = NULL; diff --git a/src/jltypes.c b/src/jltypes.c index 19b8499d3c1f53..bb41f8cdf9b960 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -1548,6 +1548,9 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value // create and initialize new type ndt = jl_new_uninitialized_datatype(); ndt->isprimitivetype = dt->isprimitivetype; + // Usually dt won't have ismutationfree set at this point, but it is + // overriden for `Type`, which we handle here. + ndt->ismutationfree = dt->ismutationfree; // associate these parameters with the new type on // the stack, in case one of its field types references it. top.tt = (jl_datatype_t*)ndt; @@ -2072,7 +2075,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_any_type, // instance jl_any_type /*jl_voidpointer_type*/, jl_any_type /*jl_int32_type*/, - jl_any_type /*jl_uint8_type*/); + jl_any_type /*jl_uint16_type*/); const static uint32_t datatype_constfields[1] = { 0x00000057 }; // (1<<0)|(1<<1)|(1<<2)|(1<<4)|(1<<6) const static uint32_t datatype_atomicfields[1] = { 0x00000028 }; // (1<<3)|(1<<5) jl_datatype_type->name->constfields = datatype_constfields; @@ -2761,7 +2764,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_uint8pointer_type = (jl_datatype_t*)jl_apply_type1((jl_value_t*)jl_pointer_type, (jl_value_t*)jl_uint8_type); jl_svecset(jl_datatype_type->types, 5, jl_voidpointer_type); jl_svecset(jl_datatype_type->types, 6, jl_int32_type); - jl_svecset(jl_datatype_type->types, 7, jl_uint8_type); + jl_svecset(jl_datatype_type->types, 7, jl_uint16_type); jl_svecset(jl_typename_type->types, 1, jl_module_type); jl_svecset(jl_typename_type->types, 3, jl_voidpointer_type); jl_svecset(jl_typename_type->types, 4, jl_voidpointer_type); @@ -2797,6 +2800,20 @@ void jl_init_types(void) JL_GC_DISABLED jl_compute_field_offsets(jl_simplevector_type); jl_compute_field_offsets(jl_symbol_type); + // override ismutationfree for builtin types that are mutable for identity + jl_string_type->ismutationfree = 1; + jl_symbol_type->ismutationfree = 1; + jl_simplevector_type->ismutationfree = 1; + jl_datatype_type->ismutationfree = 1; + ((jl_datatype_t*)jl_type_type->body)->ismutationfree = 1; + + // Technically not ismutationfree, but there's a separate system to deal + // with mutations for global state. + jl_module_type->ismutationfree = 1; + + // Array's mutable data is hidden, so we need to override it + ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_array_type))->ismutationfree = 0; + // override the preferred layout for a couple types jl_lineinfonode_type->name->mayinlinealloc = 0; // FIXME: assumed to be a pointer by codegen } diff --git a/src/julia.h b/src/julia.h index 30d43051268a5e..d67d13ce2a3b55 100644 --- a/src/julia.h +++ b/src/julia.h @@ -539,14 +539,15 @@ typedef struct _jl_datatype_t { const jl_datatype_layout_t *layout; // memoized properties (set on construction) uint32_t hash; - uint8_t hasfreetypevars:1; // majority part of isconcrete computation - uint8_t isconcretetype:1; // whether this type can have instances - uint8_t isdispatchtuple:1; // aka isleaftupletype - uint8_t isbitstype:1; // relevant query for C-api and type-parameters - uint8_t zeroinit:1; // if one or more fields requires zero-initialization - uint8_t has_concrete_subtype:1; // If clear, no value will have this datatype - uint8_t cached_by_hash:1; // stored in hash-based set cache (instead of linear cache) - uint8_t isprimitivetype:1; // whether this is declared with 'primitive type' keyword (sized, no fields, and immutable) + uint16_t hasfreetypevars:1; // majority part of isconcrete computation + uint16_t isconcretetype:1; // whether this type can have instances + uint16_t isdispatchtuple:1; // aka isleaftupletype + uint16_t isbitstype:1; // relevant query for C-api and type-parameters + uint16_t zeroinit:1; // if one or more fields requires zero-initialization + uint16_t has_concrete_subtype:1; // If clear, no value will have this datatype + uint16_t cached_by_hash:1; // stored in hash-based set cache (instead of linear cache) + uint16_t isprimitivetype:1; // whether this is declared with 'primitive type' keyword (sized, no fields, and immutable) + uint16_t ismutationfree:1; // whether any mutable memory is reachable through this type (in the type or via fields) } jl_datatype_t; typedef struct _jl_vararg_t { diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl index a92cd1bc1003a0..5ae642fa9e7e34 100644 --- a/test/compiler/effects.jl +++ b/test/compiler/effects.jl @@ -487,7 +487,7 @@ end |> Core.Compiler.is_inaccessiblememonly @test Base.infer_effects() do ConstantType{Any}() end |> Core.Compiler.is_inaccessiblememonly -@test_broken Base.infer_effects() do +@test Base.infer_effects() do constant_global_nonisbits end |> Core.Compiler.is_inaccessiblememonly @test Base.infer_effects() do @@ -708,6 +708,13 @@ end @test Base.infer_effects(Tuple{Nothing}) do x WrapperOneField{typeof(x)}.instance end |> Core.Compiler.is_total +@test Base.infer_effects(Tuple{WrapperOneField{Float64}, Symbol}) do w, s + getfield(w, s) +end |> Core.Compiler.is_foldable +@test Core.Compiler.getfield_notundefined(WrapperOneField{Float64}, Symbol) +@test Base.infer_effects(Tuple{WrapperOneField{Symbol}, Symbol}) do w, s + getfield(w, s) +end |> Core.Compiler.is_foldable # Flow-sensitive consistenct for _typevar @test Base.infer_effects() do From 318828c99a2c1f4dcf288fae364a625bf53b1b08 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Wed, 11 Jan 2023 03:08:25 +0000 Subject: [PATCH 299/387] Also memoize is_consistent_type I called the data type property `isidentityfree`, which I think more accurately reflects what is being queried here. --- base/compiler/typeutils.jl | 6 ++++-- base/reflection.jl | 2 ++ src/datatype.c | 22 +++++++++++++++++++++- src/jltypes.c | 6 +++--- src/julia.h | 1 + 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/base/compiler/typeutils.jl b/base/compiler/typeutils.jl index 510d3f3614a355..f11340607c72ad 100644 --- a/base/compiler/typeutils.jl +++ b/base/compiler/typeutils.jl @@ -336,11 +336,13 @@ end is_consistent_argtype(@nospecialize ty) = is_consistent_type(widenconst(ignorelimited(ty))) is_consistent_type(@nospecialize ty) = _is_consistent_type(unwrap_unionall(ty)) function _is_consistent_type(@nospecialize ty) + ty = unwrap_unionall(ty) if isa(ty, Union) return is_consistent_type(ty.a) && is_consistent_type(ty.b) + elseif isa(ty, DataType) + return datatype_isidentityfree(ty) end - # N.B. String and Symbol are mutable, but also egal always, and so they never be inconsistent - return ty === String || ty === Symbol || isbitstype(ty) + return false end is_immutable_argtype(@nospecialize ty) = is_immutable_type(widenconst(ignorelimited(ty))) diff --git a/base/reflection.jl b/base/reflection.jl index 078bfb2d981059..2a3a3ec6531931 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -645,6 +645,8 @@ function ismutationfree(@nospecialize(t::Type)) return false end +datatype_isidentityfree(dt::DataType) = (@_total_meta; (dt.flags & 0x0200) == 0x0200) + iskindtype(@nospecialize t) = (t === DataType || t === UnionAll || t === Union || t === typeof(Bottom)) isconcretedispatch(@nospecialize t) = isconcretetype(t) && !iskindtype(t) has_free_typevars(@nospecialize(t)) = ccall(:jl_has_free_typevars, Cint, (Any,), t) != 0 diff --git a/src/datatype.c b/src/datatype.c index 161263296d6ce1..11982045077a12 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -105,6 +105,7 @@ jl_datatype_t *jl_new_uninitialized_datatype(void) t->has_concrete_subtype = 1; t->cached_by_hash = 0; t->ismutationfree = 0; + t->isidentityfree = 0; t->name = NULL; t->super = NULL; t->parameters = NULL; @@ -461,6 +462,21 @@ static int is_type_mutationfree(jl_value_t *t) return 0; } +static int is_type_identityfree(jl_value_t *t) +{ + t = jl_unwrap_unionall(t); + if (jl_is_uniontype(t)) { + jl_uniontype_t *u = (jl_uniontype_t*)t; + return is_type_identityfree(u->a) && is_type_identityfree(u->b); + } + if (jl_is_datatype(t)) { + return ((jl_datatype_t*)t)->isidentityfree; + } + // Free tvars, etc. + return 0; +} + + void jl_compute_field_offsets(jl_datatype_t *st) { const uint64_t max_offset = (((uint64_t)1) << 32) - 1; @@ -477,6 +493,7 @@ void jl_compute_field_offsets(jl_datatype_t *st) } int isbitstype = st->isconcretetype && st->name->mayinlinealloc; int ismutationfree = !w->layout || !jl_is_layout_opaque(w->layout); + int isidentityfree = !st->name->mutabl; // If layout doesn't depend on type parameters, it's stored in st->name->wrapper // and reused by all subtypes. if (w->layout) { @@ -527,10 +544,11 @@ void jl_compute_field_offsets(jl_datatype_t *st) } } - for (i = 0; (isbitstype || ismutationfree) && i < nfields; i++) { + for (i = 0; (isbitstype || isidentityfree || ismutationfree) && i < nfields; i++) { jl_value_t *fld = jl_field_type(st, i); isbitstype &= jl_isbits(fld); ismutationfree &= (!st->name->mutabl || jl_field_isconst(st, i)) && is_type_mutationfree(fld); + isidentityfree &= is_type_identityfree(fld); } // if we didn't reuse the layout above, compute it now @@ -663,6 +681,7 @@ void jl_compute_field_offsets(jl_datatype_t *st) assert(!isbitstype || st->layout->npointers == 0); // the definition of isbits st->isbitstype = isbitstype; st->ismutationfree = ismutationfree; + st->isidentityfree = isidentityfree; jl_maybe_allocate_singleton_instance(st); return; } @@ -810,6 +829,7 @@ JL_DLLEXPORT jl_datatype_t *jl_new_primitivetype(jl_value_t *name, jl_module_t * // and we easily have a free bit for it in the DataType flags bt->isprimitivetype = 1; bt->ismutationfree = 1; + bt->isidentityfree = 1; bt->isbitstype = (parameters == jl_emptysvec); bt->layout = jl_get_layout(nbytes, 0, 0, alignm, 0, NULL, NULL); bt->instance = NULL; diff --git a/src/jltypes.c b/src/jltypes.c index bb41f8cdf9b960..a950d147d0ae73 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -2801,9 +2801,9 @@ void jl_init_types(void) JL_GC_DISABLED jl_compute_field_offsets(jl_symbol_type); // override ismutationfree for builtin types that are mutable for identity - jl_string_type->ismutationfree = 1; - jl_symbol_type->ismutationfree = 1; - jl_simplevector_type->ismutationfree = 1; + jl_string_type->ismutationfree = jl_string_type->isidentityfree = 1; + jl_symbol_type->ismutationfree = jl_symbol_type->isidentityfree = 1; + jl_simplevector_type->ismutationfree = jl_simplevector_type->isidentityfree = 1; jl_datatype_type->ismutationfree = 1; ((jl_datatype_t*)jl_type_type->body)->ismutationfree = 1; diff --git a/src/julia.h b/src/julia.h index d67d13ce2a3b55..2545cabe56ffab 100644 --- a/src/julia.h +++ b/src/julia.h @@ -548,6 +548,7 @@ typedef struct _jl_datatype_t { uint16_t cached_by_hash:1; // stored in hash-based set cache (instead of linear cache) uint16_t isprimitivetype:1; // whether this is declared with 'primitive type' keyword (sized, no fields, and immutable) uint16_t ismutationfree:1; // whether any mutable memory is reachable through this type (in the type or via fields) + uint16_t isidentityfree:1; // whether this type or any object reachable through its fields has non-content-based identity } jl_datatype_t; typedef struct _jl_vararg_t { From 8985403c0090b0f41bb0c4165ba1666e3611151e Mon Sep 17 00:00:00 2001 From: David Bach Date: Wed, 11 Jan 2023 06:52:14 +0200 Subject: [PATCH 300/387] Make LLVM Profiling robust for multithreaded programs (#47778) * Use stringsteam to atomically write LLVM opt timings * Add boolean to ensure we don't _only_ write the after block * Use ios_printf Co-authored-by: Nathan Daly --- src/jitlayers.cpp | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index 98c24dfcdf9413..b744f9fcbd3f22 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -3,6 +3,7 @@ #include "llvm-version.h" #include "platform.h" #include +#include #include "llvm/IR/Mangler.h" #include @@ -1107,23 +1108,29 @@ namespace { OptimizerResultT operator()(orc::ThreadSafeModule TSM, orc::MaterializationResponsibility &R) JL_NOTSAFEPOINT { TSM.withModuleDo([&](Module &M) JL_NOTSAFEPOINT { uint64_t start_time = 0; + std::stringstream before_stats_ss; + bool should_dump_opt_stats = false; { auto stream = *jl_ExecutionEngine->get_dump_llvm_opt_stream(); if (stream) { + // Ensures that we don't _just_ write the second part of the YAML object + should_dump_opt_stats = true; + // We use a stringstream to later atomically write a YAML object + // without the need to hold the stream lock over the optimization // Print LLVM function statistics _before_ optimization // Print all the information about this invocation as a YAML object - ios_printf(stream, "- \n"); + before_stats_ss << "- \n"; // We print the name and some statistics for each function in the module, both // before optimization and again afterwards. - ios_printf(stream, " before: \n"); + before_stats_ss << " before: \n"; for (auto &F : M.functions()) { if (F.isDeclaration() || F.getName().startswith("jfptr_")) { continue; } // Each function is printed as a YAML object with several attributes - ios_printf(stream, " \"%s\":\n", F.getName().str().c_str()); - ios_printf(stream, " instructions: %u\n", F.getInstructionCount()); - ios_printf(stream, " basicblocks: %zd\n", countBasicBlocks(F)); + before_stats_ss << " \"" << F.getName().str().c_str() << "\":\n"; + before_stats_ss << " instructions: " << F.getInstructionCount() << "\n"; + before_stats_ss << " basicblocks: " << countBasicBlocks(F) << "\n"; } start_time = jl_hrtime(); @@ -1140,7 +1147,8 @@ namespace { uint64_t end_time = 0; { auto stream = *jl_ExecutionEngine->get_dump_llvm_opt_stream(); - if (stream) { + if (stream && should_dump_opt_stats) { + ios_printf(stream, "%s", before_stats_ss.str().c_str()); end_time = jl_hrtime(); ios_printf(stream, " time_ns: %" PRIu64 "\n", end_time - start_time); ios_printf(stream, " optlevel: %d\n", optlevel); From fec8304a8c6e2157759b76d20339b426201a5f61 Mon Sep 17 00:00:00 2001 From: Stoner <91111113+StonerShaw@users.noreply.github.com> Date: Thu, 12 Jan 2023 04:12:52 +0800 Subject: [PATCH 301/387] fixed the overflow problem of powermod(x::Integer, p::Integer, m::T) (#48192) * fixed the overflow problem of `powermod(x::Integer, p::Integer, m::T)` Co-authored-by: Oscar Smith --- base/intfuncs.jl | 13 ++++++++++++- test/intfuncs.jl | 6 ++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/base/intfuncs.jl b/base/intfuncs.jl index 736b949aca3b0f..dca0cddb939878 100644 --- a/base/intfuncs.jl +++ b/base/intfuncs.jl @@ -369,8 +369,19 @@ julia> powermod(5, 3, 19) ``` """ function powermod(x::Integer, p::Integer, m::T) where T<:Integer - p < 0 && return powermod(invmod(x, m), -p, m) p == 0 && return mod(one(m),m) + # When the concrete type of p is signed and has the lowest value, + # `p != 0 && p == -p` is equivalent to `p == typemin(typeof(p))` for 2's complement representation. + # but will work for integer types like `BigInt` that don't have `typemin` defined + # It needs special handling otherwise will cause overflow problem. + if p == -p + t = powermod(invmod(x, m), -(p÷2), m) + t = mod(widemul(t, t), m) + iseven(p) && return t + #else odd + return mod(widemul(t, invmod(x, m)), m) + end + p < 0 && return powermod(invmod(x, m), -p, m) (m == 1 || m == -1) && return zero(m) b = oftype(m,mod(x,m)) # this also checks for divide by zero diff --git a/test/intfuncs.jl b/test/intfuncs.jl index c93cfb0e2ac72b..7f0261e1756177 100644 --- a/test/intfuncs.jl +++ b/test/intfuncs.jl @@ -267,6 +267,12 @@ end @test powermod(2, -2, 5) == 4 @test powermod(2, -1, -5) == -2 @test powermod(2, -2, -5) == -1 + + @test powermod(2, typemin(Int128), 5) == 1 + @test powermod(2, typemin(Int128), -5) == -4 + + @test powermod(2, big(3), 5) == 3 + @test powermod(2, big(3), -5) == -2 end @testset "nextpow/prevpow" begin From 793eaa3147239feeccf14a57dfb099411ed3bafe Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Wed, 11 Jan 2023 18:17:32 -0600 Subject: [PATCH 302/387] Stop using `rand(lo:hi)` for QuickerSort pivot selection (#48241) Co-authored-by: Fredrik Ekre --- base/sort.jl | 14 ++++---------- stdlib/Random/src/Random.jl | 6 ------ 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/base/sort.jl b/base/sort.jl index 252d5b962d83b2..485b9d7fe1d14e 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -983,21 +983,15 @@ QuickerSort(lo::Union{Integer, Missing}, hi::Union{Integer, Missing}) = QuickerS QuickerSort(lo::Union{Integer, Missing}, next::Algorithm=SMALL_ALGORITHM) = QuickerSort(lo, lo, next) QuickerSort(r::OrdinalRange, next::Algorithm=SMALL_ALGORITHM) = QuickerSort(first(r), last(r), next) -# select a pivot for QuickerSort -# -# This method is redefined to rand(lo:hi) in Random.jl -# We can't use rand here because it is not available in Core.Compiler and -# because rand is defined in the stdlib Random.jl after sorting is used in Base. -select_pivot(lo::Integer, hi::Integer) = typeof(hi-lo)(hash(lo) % (hi-lo+1)) + lo - # select a pivot, partition v[lo:hi] according # to the pivot, and store the result in t[lo:hi]. # -# returns (pivot, pivot_index) where pivot_index is the location the pivot -# should end up, but does not set t[pivot_index] = pivot +# sets `pivot_dest[pivot_index+pivot_index_offset] = pivot` and returns that index. function partition!(t::AbstractVector, lo::Integer, hi::Integer, offset::Integer, o::Ordering, v::AbstractVector, rev::Bool, pivot_dest::AbstractVector, pivot_index_offset::Integer) - pivot_index = select_pivot(lo, hi) + # Ideally we would use `pivot_index = rand(lo:hi)`, but that requires Random.jl + # and would mutate the global RNG in sorting. + pivot_index = typeof(hi-lo)(hash(lo) % (hi-lo+1)) + lo @inbounds begin pivot = v[pivot_index] while lo < pivot_index diff --git a/stdlib/Random/src/Random.jl b/stdlib/Random/src/Random.jl index bc016fc1cd0576..8da2dd6f3e9c7c 100644 --- a/stdlib/Random/src/Random.jl +++ b/stdlib/Random/src/Random.jl @@ -434,10 +434,4 @@ true """ seed!(rng::AbstractRNG, ::Nothing) = seed!(rng) -# Randomize quicksort pivot selection. This code is here because of bootstrapping: -# we need to sort things before we load this standard library. -# TODO move this into Sort.jl -Base.delete_method(only(methods(Base.Sort.select_pivot))) -Base.Sort.select_pivot(lo::Integer, hi::Integer) = rand(lo:hi) - end # module From 3933f90f67d1033ec04b3d296f41bb60eb5fefc1 Mon Sep 17 00:00:00 2001 From: N5N3 <2642243996@qq.com> Date: Thu, 12 Jan 2023 12:46:20 +0800 Subject: [PATCH 303/387] Some small patch for typeintersect. (#48224) * Add missing var-substitution in omit_bad_union. follow up 303734204dbe74f1a5d1defcb4ae3ada3e318dd4 * Also check free typevar's bounds in `reachable_var` They might be recreated in `finish_unionall`, (thus `lookup` returns false.) But their bounds might still live in the current env. close #44395. (#44395 could also be fixed by the fast path added in #48221. This commit would skip more `intersect_var` under circular constraint.) * Disallow more circulation once we set `lb`==`ub`. close #26487. This should be valid as we never set `X<:Y<:X` (assuming `Y` is the outer var). --- src/subtype.c | 54 ++++++++++++++++++++++++------------------------- test/subtype.jl | 19 ++++++++--------- 2 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/subtype.c b/src/subtype.c index 7af4e8b28b9342..19edb5e51bf70c 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -2406,43 +2406,31 @@ static int subtype_in_env_existential(jl_value_t *x, jl_value_t *y, jl_stenv_t * } // See if var y is reachable from x via bounds; used to avoid cycles. -static int _reachable_var(jl_value_t *x, jl_tvar_t *y, jl_stenv_t *e) +static int _reachable_var(jl_value_t *x, jl_tvar_t *y, jl_stenv_t *e, jl_typeenv_t *log) { if (in_union(x, (jl_value_t*)y)) return 1; if (jl_is_uniontype(x)) - return _reachable_var(((jl_uniontype_t *)x)->a, y, e) || - _reachable_var(((jl_uniontype_t *)x)->b, y, e); + return _reachable_var(((jl_uniontype_t *)x)->a, y, e, log) || + _reachable_var(((jl_uniontype_t *)x)->b, y, e, log); if (!jl_is_typevar(x)) return 0; + jl_typeenv_t *t = log; + while (t != NULL) { + if (x == (jl_value_t *)t->var) + return 0; + t = t->prev; + } jl_varbinding_t *xv = lookup(e, (jl_tvar_t*)x); - if (xv == NULL || xv->right) - return 0; - xv->right = 1; - return _reachable_var(xv->ub, y, e) || _reachable_var(xv->lb, y, e); + jl_value_t *lb = xv == NULL ? ((jl_tvar_t*)x)->lb : xv->lb; + jl_value_t *ub = xv == NULL ? ((jl_tvar_t*)x)->ub : xv->ub; + jl_typeenv_t newlog = { (jl_tvar_t*)x, NULL, log }; + return _reachable_var(ub, y, e, &newlog) || _reachable_var(lb, y, e, &newlog); } static int reachable_var(jl_value_t *x, jl_tvar_t *y, jl_stenv_t *e) { - int len = current_env_length(e); - int8_t *rs = (int8_t*)malloc_s(len); - int n = 0; - jl_varbinding_t *v = e->vars; - while (n < len) { - assert(v != NULL); - rs[n++] = v->right; - v->right = 0; - v = v->prev; - } - int res = _reachable_var(x, y, e); - n = 0; v = e->vars; - while (n < len) { - assert(v != NULL); - v->right = rs[n++]; - v = v->prev; - } - free(rs); - return res; + return _reachable_var(x, y, e, NULL); } // check whether setting v == t implies v == SomeType{v}, which is unsatisfiable. @@ -2630,8 +2618,10 @@ static jl_value_t *omit_bad_union(jl_value_t *u, jl_tvar_t *t) ub = omit_bad_union(ub, t); body = omit_bad_union(body, t); if (ub != NULL && body != NULL && !jl_has_typevar(var->lb, t)) { - if (ub != var->ub) + if (ub != var->ub) { var = jl_new_typevar(var->name, var->lb, ub); + body = jl_substitute_var(body, ((jl_unionall_t *)u)->var, (jl_value_t *)var); + } res = jl_new_struct(jl_unionall_type, var, body); } JL_GC_POP(); @@ -3259,7 +3249,15 @@ static jl_value_t *intersect(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int pa return jl_bottom_type; } int ccheck; - if (yub == xub || + if (xlb == xub && ylb == yub && + jl_has_typevar(xlb, (jl_tvar_t *)y) && + jl_has_typevar(ylb, (jl_tvar_t *)x)) { + // specical case for e.g. + // 1) Val{Y}<:X<:Val{Y} && Val{X}<:Y<:Val{X} + // 2) Y<:X<:Y && Val{X}<:Y<:Val{X} => Val{Y}<:Y<:Val{Y} + ccheck = 0; + } + else if (yub == xub || (subtype_by_bounds(xlb, yub, e) && subtype_by_bounds(ylb, xub, e))) { ccheck = 1; } diff --git a/test/subtype.jl b/test/subtype.jl index a182bb99909eeb..7236d3438a692f 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -2352,6 +2352,16 @@ let S = Tuple{Type{T1}, T1, Val{T1}} where T1<:(Val{S1} where S1<:Val), @test_broken I2 <: T end +#issue 44395 +@testintersect(Tuple{Type{T}, T} where {T <: Vector{Union{T, R}} where {R<:Real, T<:Real}}, + Tuple{Type{Vector{Union{T, R}}}, Matrix{Union{T, R}}} where {R<:Real, T<:Real}, + Union{}) + +#issue 26487 +@testintersect(Tuple{Type{Tuple{T,Val{T}}}, Val{T}} where T, + Tuple{Type{Tuple{Val{T},T}}, Val{T}} where T, + Union{}) + @testset "known subtype/intersect issue" begin #issue 45874 # Causes a hang due to jl_critical_error calling back into malloc... @@ -2361,12 +2371,6 @@ end # @test_broken typeintersect(S,T) === S # end - #issue 44395 - @test_broken typeintersect( - Tuple{Type{T}, T} where {T <: Vector{Union{T, R}} where {R<:Real, T<:Real}}, - Tuple{Type{Vector{Union{T, R}}}, Matrix{Union{T, R}}} where {R<:Real, T<:Real}, - ) === Union{} - #issue 41561 @test_broken typeintersect(Tuple{Vector{VT}, Vector{VT}} where {N1, VT<:AbstractVector{N1}}, Tuple{Vector{VN} where {N, VN<:AbstractVector{N}}, Vector{Vector{Float64}}}) !== Union{} @@ -2385,9 +2389,6 @@ end #issue 33137 @test_broken (Tuple{Q,Int} where Q<:Int) <: Tuple{T,T} where T - #issue 26487 - @test_broken typeintersect(Tuple{Type{Tuple{T,Val{T}}}, Val{T}} where T, Tuple{Type{Tuple{Val{T},T}}, Val{T}} where T) <: Any - # issue 24333 @test_broken (Type{Union{Ref,Cvoid}} <: Type{Union{T,Cvoid}} where T) From e40d813bef7b92d9dd4b4fc2b653ff3fe9fe86ba Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Thu, 12 Jan 2023 18:10:01 +0900 Subject: [PATCH 304/387] NFC: minor followups for #48220 (#48234) --- base/compiler/typeutils.jl | 19 +++++-------------- base/reflection.jl | 16 +++++++++++++++- src/datatype.c | 5 ----- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/base/compiler/typeutils.jl b/base/compiler/typeutils.jl index f11340607c72ad..8f256ea88b78fd 100644 --- a/base/compiler/typeutils.jl +++ b/base/compiler/typeutils.jl @@ -333,17 +333,9 @@ end # this query is specially written for `adjust_effects` and returns true if a value of this type # never involves inconsistency of mutable objects that are allocated somewhere within a call graph -is_consistent_argtype(@nospecialize ty) = is_consistent_type(widenconst(ignorelimited(ty))) -is_consistent_type(@nospecialize ty) = _is_consistent_type(unwrap_unionall(ty)) -function _is_consistent_type(@nospecialize ty) - ty = unwrap_unionall(ty) - if isa(ty, Union) - return is_consistent_type(ty.a) && is_consistent_type(ty.b) - elseif isa(ty, DataType) - return datatype_isidentityfree(ty) - end - return false -end +is_consistent_argtype(@nospecialize ty) = + is_consistent_type(widenconst(ignorelimited(ty))) +is_consistent_type(@nospecialize ty) = isidentityfree(ty) is_immutable_argtype(@nospecialize ty) = is_immutable_type(widenconst(ignorelimited(ty))) is_immutable_type(@nospecialize ty) = _is_immutable_type(unwrap_unionall(ty)) @@ -355,6 +347,5 @@ function _is_immutable_type(@nospecialize ty) end is_mutation_free_argtype(@nospecialize argtype) = - ismutationfree(widenconst(ignorelimited(argtype))) -is_mutation_free_type(@nospecialize ty) = - ismutationfree(ty) + is_mutation_free_type(widenconst(ignorelimited(argtype))) +is_mutation_free_type(@nospecialize ty) = ismutationfree(ty) diff --git a/base/reflection.jl b/base/reflection.jl index 2a3a3ec6531931..c836201d40a422 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -624,7 +624,6 @@ and has no subtypes (or supertypes) which could appear in a call. """ isdispatchtuple(@nospecialize(t)) = (@_total_meta; isa(t, DataType) && (t.flags & 0x0004) == 0x0004) - datatype_ismutationfree(dt::DataType) = (@_total_meta; (dt.flags & 0x0100) == 0x0100) """ @@ -647,6 +646,21 @@ end datatype_isidentityfree(dt::DataType) = (@_total_meta; (dt.flags & 0x0200) == 0x0200) +""" + isidentityfree(T) + +Determine whether type `T` is identity free in the sense that this type or any +reachable through its fields has non-content-based identity. +""" +function isidentityfree(@nospecialize(t::Type)) + t = unwrap_unionall(t) + if isa(t, DataType) + return datatype_isidentityfree(t) + elseif isa(t, Union) + return isidentityfree(t.a) && isidentityfree(t.b) + end +end + iskindtype(@nospecialize t) = (t === DataType || t === UnionAll || t === Union || t === typeof(Bottom)) isconcretedispatch(@nospecialize t) = isconcretetype(t) && !iskindtype(t) has_free_typevars(@nospecialize(t)) = ccall(:jl_has_free_typevars, Cint, (Any,), t) != 0 diff --git a/src/datatype.c b/src/datatype.c index 11982045077a12..6e71c6573c91f4 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -476,7 +476,6 @@ static int is_type_identityfree(jl_value_t *t) return 0; } - void jl_compute_field_offsets(jl_datatype_t *st) { const uint64_t max_offset = (((uint64_t)1) << 32) - 1; @@ -886,7 +885,6 @@ JL_DLLEXPORT int jl_is_foreign_type(jl_datatype_t *dt) return jl_is_datatype(dt) && dt->layout && dt->layout->fielddesc_type == 3; } - // bits constructors ---------------------------------------------------------- #if MAX_ATOMIC_SIZE > MAX_POINTERATOMIC_SIZE @@ -1224,8 +1222,6 @@ JL_DLLEXPORT jl_value_t *jl_atomic_cmpswap_bits(jl_datatype_t *dt, jl_datatype_t return y; } - - // used by boot.jl JL_DLLEXPORT jl_value_t *jl_typemax_uint(jl_value_t *bt) { @@ -1945,7 +1941,6 @@ JL_DLLEXPORT int jl_field_isdefined_checked(jl_value_t *v, size_t i) return !!jl_field_isdefined(v, i); } - JL_DLLEXPORT size_t jl_get_field_offset(jl_datatype_t *ty, int field) { if (!jl_struct_try_layout(ty) || field > jl_datatype_nfields(ty) || field < 1) From b07484ca39a963b49fe31b8d5d2ceee4864f1737 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 12 Jan 2023 10:05:40 -0500 Subject: [PATCH 305/387] store bindings now in an array for safe, fast iteration (#48212) This lets us query and iterate the bindings without needing to hold locks over the queries, making those operations more scalable and safe to use across safepoints or concurrently. It is similar to how we already deal with specializations and datatype caches. Updates the smallintcache to additionally be more threadsafe for users, with explicit acquire and release operations. --- src/gc-heap-snapshot.cpp | 7 +- src/gc-heap-snapshot.h | 6 +- src/gc.c | 26 ++++-- src/gf.c | 55 +++++++------ src/init.c | 8 +- src/julia.h | 14 ++-- src/module.c | 168 ++++++++++++++++++++++----------------- src/smallintset.c | 38 ++++++--- src/staticdata.c | 62 +++++---------- src/staticdata_utils.c | 73 ++++++++--------- src/toplevel.c | 8 +- 11 files changed, 252 insertions(+), 213 deletions(-) diff --git a/src/gc-heap-snapshot.cpp b/src/gc-heap-snapshot.cpp index c8c25623f81962..b1401653d99ffc 100644 --- a/src/gc-heap-snapshot.cpp +++ b/src/gc-heap-snapshot.cpp @@ -392,8 +392,10 @@ void _gc_heap_snapshot_record_object_edge(jl_value_t *from, jl_value_t *to, void g_snapshot->names.find_or_create_string_id(path)); } -void _gc_heap_snapshot_record_module_to_binding(jl_module_t *module, jl_sym_t *name, jl_binding_t *binding) JL_NOTSAFEPOINT +void _gc_heap_snapshot_record_module_to_binding(jl_module_t *module, jl_binding_t *binding) JL_NOTSAFEPOINT { + jl_globalref_t *globalref = binding->globalref; + jl_sym_t *name = globalref->name; auto from_node_idx = record_node_to_gc_snapshot((jl_value_t*)module); auto to_node_idx = record_pointer_to_gc_snapshot(binding, sizeof(jl_binding_t), jl_symbol_name(name)); @@ -401,8 +403,7 @@ void _gc_heap_snapshot_record_module_to_binding(jl_module_t *module, jl_sym_t *n auto value_idx = value ? record_node_to_gc_snapshot(value) : 0; jl_value_t *ty = jl_atomic_load_relaxed(&binding->ty); auto ty_idx = ty ? record_node_to_gc_snapshot(ty) : 0; - jl_value_t *globalref = (jl_value_t*)binding->globalref; - auto globalref_idx = globalref ? record_node_to_gc_snapshot(globalref) : 0; + auto globalref_idx = record_node_to_gc_snapshot((jl_value_t*)globalref); auto &from_node = g_snapshot->nodes[from_node_idx]; auto &to_node = g_snapshot->nodes[to_node_idx]; diff --git a/src/gc-heap-snapshot.h b/src/gc-heap-snapshot.h index 1ddb4e93167367..8c3af5b86bec7b 100644 --- a/src/gc-heap-snapshot.h +++ b/src/gc-heap-snapshot.h @@ -20,7 +20,7 @@ void _gc_heap_snapshot_record_task_to_frame_edge(jl_task_t *from, void *to) JL_N void _gc_heap_snapshot_record_frame_to_frame_edge(jl_gcframe_t *from, jl_gcframe_t *to) JL_NOTSAFEPOINT; void _gc_heap_snapshot_record_array_edge(jl_value_t *from, jl_value_t *to, size_t index) JL_NOTSAFEPOINT; void _gc_heap_snapshot_record_object_edge(jl_value_t *from, jl_value_t *to, void* slot) JL_NOTSAFEPOINT; -void _gc_heap_snapshot_record_module_to_binding(jl_module_t* module, jl_sym_t *name, jl_binding_t* binding) JL_NOTSAFEPOINT; +void _gc_heap_snapshot_record_module_to_binding(jl_module_t* module, jl_binding_t* binding) JL_NOTSAFEPOINT; // Used for objects managed by GC, but which aren't exposed in the julia object, so have no // field or index. i.e. they're not reachable from julia code, but we _will_ hit them in // the GC mark phase (so we can check their type tag to get the size). @@ -73,10 +73,10 @@ static inline void gc_heap_snapshot_record_object_edge(jl_value_t *from, jl_valu } } -static inline void gc_heap_snapshot_record_module_to_binding(jl_module_t* module, jl_sym_t *name, jl_binding_t* binding) JL_NOTSAFEPOINT +static inline void gc_heap_snapshot_record_module_to_binding(jl_module_t* module, jl_binding_t* binding) JL_NOTSAFEPOINT { if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full)) { - _gc_heap_snapshot_record_module_to_binding(module, name, binding); + _gc_heap_snapshot_record_module_to_binding(module, binding); } } diff --git a/src/gc.c b/src/gc.c index 995d8306168cd1..3cfb02166e7813 100644 --- a/src/gc.c +++ b/src/gc.c @@ -2537,22 +2537,35 @@ module_binding: { gc_mark_binding_t *binding = gc_pop_markdata(&sp, gc_mark_binding_t); jl_binding_t **begin = binding->begin; jl_binding_t **end = binding->end; - for (; begin < end; begin += 2) { + for (; begin < end; begin++) { jl_binding_t *b = *begin; - if (b == (jl_binding_t*)HT_NOTFOUND) + if (b == (jl_binding_t*)jl_nothing) continue; verify_parent1("module", binding->parent, begin, "binding_buff"); // Record the size used for the box for non-const bindings - gc_heap_snapshot_record_module_to_binding(binding->parent, (jl_sym_t*)begin[-1], b); + gc_heap_snapshot_record_module_to_binding(binding->parent, b); if (gc_try_setmark((jl_value_t*)b, &binding->nptr, &tag, &bits)) { - begin += 2; + begin++; binding->begin = begin; gc_repush_markdata(&sp, gc_mark_binding_t); new_obj = (jl_value_t*)b; goto mark; } } + binding->begin = begin; jl_module_t *m = binding->parent; + jl_value_t *bindings = (jl_value_t*)jl_atomic_load_relaxed(&m->bindings); + if (gc_try_setmark(bindings, &binding->nptr, &tag, &bits)) { + gc_repush_markdata(&sp, gc_mark_binding_t); + new_obj = (jl_value_t*)bindings; + goto mark; + } + jl_value_t *bindingkeyset = (jl_value_t*)jl_atomic_load_relaxed(&m->bindingkeyset); + if (gc_try_setmark(bindingkeyset, &binding->nptr, &tag, &bits)) { + gc_repush_markdata(&sp, gc_mark_binding_t); + new_obj = bindingkeyset; + goto mark; + } int scanparent = gc_try_setmark((jl_value_t*)m->parent, &binding->nptr, &tag, &bits); size_t nusings = m->usings.len; if (nusings) { @@ -2760,8 +2773,9 @@ mark: { else if (foreign_alloc) objprofile_count(vt, bits == GC_OLD_MARKED, sizeof(jl_module_t)); jl_module_t *m = (jl_module_t*)new_obj; - jl_binding_t **table = (jl_binding_t**)m->bindings.table; - size_t bsize = m->bindings.size; + jl_svec_t *bindings = jl_atomic_load_relaxed(&m->bindings); + jl_binding_t **table = (jl_binding_t**)jl_svec_data(bindings); + size_t bsize = jl_svec_len(bindings); uintptr_t nptr = ((bsize + m->usings.len + 1) << 2) | (bits & GC_OLD); gc_mark_binding_t markdata = {m, table + 1, table + bsize, nptr}; gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(module_binding), diff --git a/src/gf.c b/src/gf.c index 46506c2df98e9b..f559ca4faeacee 100644 --- a/src/gf.c +++ b/src/gf.c @@ -172,7 +172,7 @@ static jl_method_instance_t *jl_specializations_get_linfo_(jl_method_t *m JL_PRO if (!hv) i -= 1; assert(jl_svecref(specializations, i) == jl_nothing); - jl_svecset(specializations, i, mi); // jl_atomic_store_release? + jl_svecset(specializations, i, mi); // jl_atomic_store_relaxed? if (hv) { // TODO: fuse lookup and insert steps? jl_smallintset_insert(&m->speckeyset, (jl_value_t*)m, speccache_hash, i, specializations); @@ -471,39 +471,38 @@ int foreach_mtable_in_module( int (*visit)(jl_methtable_t *mt, void *env), void *env) { - size_t i; - void **table = m->bindings.table; - for (i = 0; i < m->bindings.size; i += 2) { - if (table[i+1] != HT_NOTFOUND) { - jl_sym_t *name = (jl_sym_t*)table[i]; - jl_binding_t *b = (jl_binding_t*)table[i+1]; - JL_GC_PROMISE_ROOTED(b); - if (jl_atomic_load_relaxed(&b->owner) == b && b->constp) { - jl_value_t *v = jl_atomic_load_relaxed(&b->value); - if (v) { - jl_value_t *uw = jl_unwrap_unionall(v); - if (jl_is_datatype(uw)) { - jl_typename_t *tn = ((jl_datatype_t*)uw)->name; - if (tn->module == m && tn->name == name && tn->wrapper == v) { - // this is the original/primary binding for the type (name/wrapper) - jl_methtable_t *mt = tn->mt; - if (mt != NULL && (jl_value_t*)mt != jl_nothing && mt != jl_type_type_mt && mt != jl_nonfunction_mt) { - if (!visit(mt, env)) - return 0; - } - } - } - else if (jl_is_module(v)) { - jl_module_t *child = (jl_module_t*)v; - if (child != m && child->parent == m && child->name == name) { - // this is the original/primary binding for the submodule - if (!foreach_mtable_in_module(child, visit, env)) + jl_svec_t *table = jl_atomic_load_relaxed(&m->bindings); + for (size_t i = 0; i < jl_svec_len(table); i++) { + jl_binding_t *b = (jl_binding_t*)jl_svec_ref(table, i); + if ((void*)b == jl_nothing) + break; + jl_sym_t *name = b->globalref->name; + if (jl_atomic_load_relaxed(&b->owner) == b && b->constp) { + jl_value_t *v = jl_atomic_load_relaxed(&b->value); + if (v) { + jl_value_t *uw = jl_unwrap_unionall(v); + if (jl_is_datatype(uw)) { + jl_typename_t *tn = ((jl_datatype_t*)uw)->name; + if (tn->module == m && tn->name == name && tn->wrapper == v) { + // this is the original/primary binding for the type (name/wrapper) + jl_methtable_t *mt = tn->mt; + if (mt != NULL && (jl_value_t*)mt != jl_nothing && mt != jl_type_type_mt && mt != jl_nonfunction_mt) { + if (!visit(mt, env)) return 0; } } } + else if (jl_is_module(v)) { + jl_module_t *child = (jl_module_t*)v; + if (child != m && child->parent == m && child->name == name) { + // this is the original/primary binding for the submodule + if (!foreach_mtable_in_module(child, visit, env)) + return 0; + } + } } } + table = jl_atomic_load_relaxed(&m->bindings); } return 1; } diff --git a/src/init.c b/src/init.c index 18e4d41eb6d792..939c30f209943d 100644 --- a/src/init.c +++ b/src/init.c @@ -910,10 +910,10 @@ static void post_boot_hooks(void) jl_init_box_caches(); // set module field of primitive types - int i; - void **table = jl_core_module->bindings.table; - for (i = 1; i < jl_core_module->bindings.size; i += 2) { - if (table[i] != HT_NOTFOUND) { + jl_svec_t *bindings = jl_atomic_load_relaxed(&jl_core_module->bindings); + jl_value_t **table = jl_svec_data(bindings); + for (size_t i = 0; i < jl_svec_len(bindings); i++) { + if (table[i] != jl_nothing) { jl_binding_t *b = (jl_binding_t*)table[i]; jl_value_t *v = jl_atomic_load_relaxed(&b->value); if (v) { diff --git a/src/julia.h b/src/julia.h index 2545cabe56ffab..7183383a924855 100644 --- a/src/julia.h +++ b/src/julia.h @@ -584,8 +584,9 @@ typedef struct _jl_module_t { JL_DATA_TYPE jl_sym_t *name; struct _jl_module_t *parent; + _Atomic(jl_svec_t*) bindings; + _Atomic(jl_array_t*) bindingkeyset; // index lookup by name into bindings // hidden fields: - htable_t bindings; arraylist_t usings; // modules with all bindings potentially imported jl_uuid_t build_id; jl_uuid_t uuid; @@ -648,12 +649,12 @@ typedef struct _jl_typemap_level_t { // contains the TypeMap for one Type typedef struct _jl_methtable_t { JL_DATA_TYPE - jl_sym_t *name; // sometimes a hack used by serialization to handle kwsorter + jl_sym_t *name; // sometimes used for debug printing _Atomic(jl_typemap_t*) defs; _Atomic(jl_array_t*) leafcache; _Atomic(jl_typemap_t*) cache; _Atomic(intptr_t) max_args; // max # of non-vararg arguments in a signature - jl_module_t *module; // used for incremental serialization to locate original binding + jl_module_t *module; // sometimes used for debug printing jl_array_t *backedges; // (sig, caller::MethodInstance) pairs jl_mutex_t writelock; uint8_t offs; // 0, or 1 to skip splitting typemap on first (function) argument @@ -986,9 +987,10 @@ STATIC_INLINE jl_value_t *jl_svecset( { assert(jl_typeis(t,jl_simplevector_type)); assert(i < jl_svec_len(t)); - // TODO: while svec is supposedly immutable, in practice we sometimes publish it first - // and set the values lazily. Those users should be using jl_atomic_store_release here. - jl_svec_data(t)[i] = (jl_value_t*)x; + // while svec is supposedly immutable, in practice we sometimes publish it + // first and set the values lazily. Those users occasionally might need to + // instead use jl_atomic_store_release here. + jl_atomic_store_relaxed((_Atomic(jl_value_t*)*)jl_svec_data(t) + i, (jl_value_t*)x); jl_gc_wb(t, x); return (jl_value_t*)x; } diff --git a/src/module.c b/src/module.c index 30e31ba73514b0..9a8285ad003f63 100644 --- a/src/module.c +++ b/src/module.c @@ -37,7 +37,8 @@ JL_DLLEXPORT jl_module_t *jl_new_module_(jl_sym_t *name, jl_module_t *parent, ui m->hash = parent == NULL ? bitmix(name->hash, jl_module_type->hash) : bitmix(name->hash, parent->hash); JL_MUTEX_INIT(&m->lock); - htable_new(&m->bindings, 0); + jl_atomic_store_relaxed(&m->bindings, jl_emptysvec); + jl_atomic_store_relaxed(&m->bindingkeyset, (jl_array_t*)jl_an_empty_vec_any); arraylist_new(&m->usings, 0); JL_GC_PUSH1(&m); if (jl_core_module && default_names) { @@ -362,6 +363,7 @@ static jl_binding_t *jl_resolve_owner(jl_binding_t *b/*optional*/, jl_module_t * if (b2 == NULL) return NULL; assert(from); + JL_GC_PROMISE_ROOTED(from); // gc-analysis does not understand output parameters if (b2->deprecated) { if (jl_atomic_load_relaxed(&b2->value) == jl_nothing) { // silently skip importing deprecated values assigned to nothing (to allow later mutation) @@ -609,35 +611,32 @@ JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from) } arraylist_push(&to->usings, from); jl_gc_wb(to, from); + JL_UNLOCK(&to->lock); - // TODO: make so this can't deadlock - JL_LOCK(&from->lock); // print a warning if something visible via this "using" conflicts with // an existing identifier. note that an identifier added later may still // silently override a "using" name. see issue #2054. - void **table = from->bindings.table; - for (size_t i = 1; i < from->bindings.size; i += 2) { - jl_binding_t *b = (jl_binding_t*)table[i]; - if (b != HT_NOTFOUND) { - if (b->exportp && (jl_atomic_load_relaxed(&b->owner) == b || b->imported)) { - jl_sym_t *var = (jl_sym_t*)table[i-1]; - jl_binding_t **tobp = (jl_binding_t**)ptrhash_bp(&to->bindings, var); - if (*tobp != HT_NOTFOUND && jl_atomic_load_relaxed(&(*tobp)->owner) != NULL && - // don't warn for conflicts with the module name itself. - // see issue #4715 - var != to->name && - !eq_bindings(jl_get_binding(to, var), b)) { - // TODO: not ideal to print this while holding module locks - jl_printf(JL_STDERR, - "WARNING: using %s.%s in module %s conflicts with an existing identifier.\n", - jl_symbol_name(from->name), jl_symbol_name(var), - jl_symbol_name(to->name)); - } + jl_svec_t *table = jl_atomic_load_relaxed(&from->bindings); + for (size_t i = 0; i < jl_svec_len(table); i++) { + jl_binding_t *b = (jl_binding_t*)jl_svec_ref(table, i); + if ((void*)b == jl_nothing) + break; + if (b->exportp && (jl_atomic_load_relaxed(&b->owner) == b || b->imported)) { + jl_sym_t *var = b->globalref->name; + jl_binding_t *tob = jl_get_module_binding(to, var, 0); + if (tob && jl_atomic_load_relaxed(&tob->owner) != NULL && + // don't warn for conflicts with the module name itself. + // see issue #4715 + var != to->name && + !eq_bindings(jl_atomic_load_relaxed(&tob->owner), b)) { + jl_printf(JL_STDERR, + "WARNING: using %s.%s in module %s conflicts with an existing identifier.\n", + jl_symbol_name(from->name), jl_symbol_name(var), + jl_symbol_name(to->name)); } } + table = jl_atomic_load_relaxed(&from->bindings); } - JL_UNLOCK(&from->lock); - JL_UNLOCK(&to->lock); } JL_DLLEXPORT void jl_module_export(jl_module_t *from, jl_sym_t *s) @@ -670,37 +669,65 @@ JL_DLLEXPORT int jl_binding_resolved_p(jl_module_t *m, jl_sym_t *var) return b && jl_atomic_load_relaxed(&b->owner) != NULL; } -// Hash tables don't generically root their contents, but they do for bindings. -// Express this to the analyzer. -// NOTE: Must hold m->lock while calling these. -#ifdef __clang_gcanalyzer__ -jl_binding_t *_jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var) JL_NOTSAFEPOINT; -#else -static inline jl_binding_t *_jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var) JL_NOTSAFEPOINT +static uint_t bindingkey_hash(size_t idx, jl_svec_t *data) { - return (jl_binding_t*)ptrhash_get(&m->bindings, var); + jl_binding_t *b = (jl_binding_t*)jl_svecref(data, idx); + jl_sym_t *var = b->globalref->name; + return var->hash; } -#endif -JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int alloc) +static int bindingkey_eq(size_t idx, const void *var, jl_svec_t *data, uint_t hv) { - JL_LOCK(&m->lock); - jl_binding_t *b = _jl_get_module_binding(m, var); - if (b == HT_NOTFOUND) { - b = NULL; - if (alloc) { - jl_binding_t **bp = (jl_binding_t**)ptrhash_bp(&m->bindings, var); - b = *bp; - if (b == HT_NOTFOUND) { - b = new_binding(m, var); - *bp = b; - JL_GC_PROMISE_ROOTED(b); - jl_gc_wb(m, b); + jl_binding_t *b = (jl_binding_t*)jl_svecref(data, idx); + jl_sym_t *name = b->globalref->name; + return var == name; +} + +JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m, jl_sym_t *var, int alloc) +{ + uint_t hv = var->hash; + for (int locked = 0; ; locked++) { + jl_array_t *bindingkeyset = jl_atomic_load_acquire(&m->bindingkeyset); + jl_svec_t *bindings = jl_atomic_load_relaxed(&m->bindings); + ssize_t idx = jl_smallintset_lookup(bindingkeyset, bindingkey_eq, var, bindings, hv); // acquire + if (idx != -1) { + jl_binding_t *b = (jl_binding_t*)jl_svecref(bindings, idx); // relaxed + if (locked) + JL_UNLOCK(&m->lock); + return b; + } + if (!alloc) { + return NULL; + } + else if (!locked) { + JL_LOCK(&m->lock); + } + else { + size_t i, cl = jl_svec_len(bindings); + for (i = cl; i > 0; i--) { + jl_value_t *b = jl_svecref(bindings, i - 1); + if (b != jl_nothing) + break; + } + if (i == cl) { + size_t ncl = cl < 8 ? 8 : (cl*3)>>1; // grow 50% + jl_svec_t *nc = jl_alloc_svec_uninit(ncl); + if (i > 0) + memcpy((char*)jl_svec_data(nc), jl_svec_data(bindings), sizeof(void*) * i); + for (size_t j = i; j < ncl; j++) + jl_svec_data(nc)[j] = jl_nothing; + jl_atomic_store_release(&m->bindings, nc); + jl_gc_wb(m, nc); + bindings = nc; } + jl_binding_t *b = new_binding(m, var); + assert(jl_svecref(bindings, i) == jl_nothing); + jl_svecset(bindings, i, b); // relaxed + jl_smallintset_insert(&m->bindingkeyset, (jl_value_t*)m, bindingkey_hash, i, bindings); // release + JL_UNLOCK(&m->lock); + return b; } } - JL_UNLOCK(&m->lock); - return b; } @@ -877,25 +904,23 @@ JL_DLLEXPORT jl_value_t *jl_module_names(jl_module_t *m, int all, int imported) { jl_array_t *a = jl_alloc_array_1d(jl_array_symbol_type, 0); JL_GC_PUSH1(&a); - size_t i; - JL_LOCK(&m->lock); - void **table = m->bindings.table; - for (i = 0; i < m->bindings.size; i+=2) { - jl_binding_t *b = (jl_binding_t*)table[i+1]; - if (b != HT_NOTFOUND) { - jl_sym_t *asname = (jl_sym_t*)table[i]; - int hidden = jl_symbol_name(asname)[0]=='#'; - if ((b->exportp || - (imported && b->imported) || - (jl_atomic_load_relaxed(&b->owner) == b && !b->imported && (all || m == jl_main_module))) && - (all || (!b->deprecated && !hidden))) { - jl_array_grow_end(a, 1); - // n.b. change to jl_arrayset if array storage allocation for Array{Symbols,1} changes: - jl_array_ptr_set(a, jl_array_dim0(a)-1, (jl_value_t*)asname); - } + jl_svec_t *table = jl_atomic_load_relaxed(&m->bindings); + for (size_t i = 0; i < jl_svec_len(table); i++) { + jl_binding_t *b = (jl_binding_t*)jl_svec_ref(table, i); + if ((void*)b == jl_nothing) + break; + jl_sym_t *asname = b->globalref->name; + int hidden = jl_symbol_name(asname)[0]=='#'; + if ((b->exportp || + (imported && b->imported) || + (jl_atomic_load_relaxed(&b->owner) == b && !b->imported && (all || m == jl_main_module))) && + (all || (!b->deprecated && !hidden))) { + jl_array_grow_end(a, 1); + // n.b. change to jl_arrayset if array storage allocation for Array{Symbols,1} changes: + jl_array_ptr_set(a, jl_array_dim0(a)-1, (jl_value_t*)asname); } + table = jl_atomic_load_relaxed(&m->bindings); } - JL_UNLOCK(&m->lock); JL_GC_POP(); return (jl_value_t*)a; } @@ -925,15 +950,14 @@ int jl_is_submodule(jl_module_t *child, jl_module_t *parent) JL_NOTSAFEPOINT // is to leave `Main` as empty as possible in the default system image. JL_DLLEXPORT void jl_clear_implicit_imports(jl_module_t *m) { - size_t i; JL_LOCK(&m->lock); - void **table = m->bindings.table; - for (i = 1; i < m->bindings.size; i += 2) { - if (table[i] != HT_NOTFOUND) { - jl_binding_t *b = (jl_binding_t*)table[i]; - if (jl_atomic_load_relaxed(&b->owner) && jl_atomic_load_relaxed(&b->owner) != b && !b->imported) - jl_atomic_store_relaxed(&b->owner, NULL); - } + jl_svec_t *table = jl_atomic_load_relaxed(&m->bindings); + for (size_t i = 0; i < jl_svec_len(table); i++) { + jl_binding_t *b = (jl_binding_t*)jl_svec_ref(table, i); + if ((void*)b == jl_nothing) + break; + if (jl_atomic_load_relaxed(&b->owner) && jl_atomic_load_relaxed(&b->owner) != b && !b->imported) + jl_atomic_store_relaxed(&b->owner, NULL); } JL_UNLOCK(&m->lock); } diff --git a/src/smallintset.c b/src/smallintset.c index 54fdad616a7581..fa647b57e7d3ee 100644 --- a/src/smallintset.c +++ b/src/smallintset.c @@ -13,6 +13,13 @@ #define max_probe(size) ((size) <= 1024 ? 16 : (size) >> 6) #define h2index(hv, sz) (size_t)((hv) & ((sz)-1)) +// a set of small positive integers representing the indices into another set +// (or dict) where the hash is derived from the keys in the set via the lambdas +// `hash` and `eq` supports concurrent calls to jl_smallintset_lookup (giving +// acquire ordering), provided that a lock is held over calls to +// smallintset_rehash, and the elements of `data` support release-consume +// atomics. + #ifdef __cplusplus extern "C" { #endif @@ -21,24 +28,37 @@ static inline size_t jl_intref(const jl_array_t *arr, size_t idx) JL_NOTSAFEPOIN { jl_value_t *el = jl_tparam0(jl_typeof(arr)); if (el == (jl_value_t*)jl_uint8_type) - return ((uint8_t*)jl_array_data(arr))[idx]; + return jl_atomic_load_relaxed(&((_Atomic(uint8_t)*)jl_array_data(arr))[idx]); + else if (el == (jl_value_t*)jl_uint16_type) + return jl_atomic_load_relaxed(&((_Atomic(uint16_t)*)jl_array_data(arr))[idx]); + else if (el == (jl_value_t*)jl_uint32_type) + return jl_atomic_load_relaxed(&((_Atomic(uint32_t)*)jl_array_data(arr))[idx]); + else + abort(); +} + +static inline size_t jl_intref_acquire(const jl_array_t *arr, size_t idx) JL_NOTSAFEPOINT +{ + jl_value_t *el = jl_tparam0(jl_typeof(arr)); + if (el == (jl_value_t*)jl_uint8_type) + return jl_atomic_load_acquire(&((_Atomic(uint8_t)*)jl_array_data(arr))[idx]); else if (el == (jl_value_t*)jl_uint16_type) - return ((uint16_t*)jl_array_data(arr))[idx]; + return jl_atomic_load_acquire(&((_Atomic(uint16_t)*)jl_array_data(arr))[idx]); else if (el == (jl_value_t*)jl_uint32_type) - return ((uint32_t*)jl_array_data(arr))[idx]; + return jl_atomic_load_acquire(&((_Atomic(uint32_t)*)jl_array_data(arr))[idx]); else abort(); } -static inline void jl_intset(const jl_array_t *arr, size_t idx, size_t val) JL_NOTSAFEPOINT +static inline void jl_intset_release(const jl_array_t *arr, size_t idx, size_t val) JL_NOTSAFEPOINT { jl_value_t *el = jl_tparam0(jl_typeof(arr)); if (el == (jl_value_t*)jl_uint8_type) - ((uint8_t*)jl_array_data(arr))[idx] = val; + jl_atomic_store_release(&((_Atomic(uint8_t)*)jl_array_data(arr))[idx], val); else if (el == (jl_value_t*)jl_uint16_type) - ((uint16_t*)jl_array_data(arr))[idx] = val; + jl_atomic_store_release(&((_Atomic(uint16_t)*)jl_array_data(arr))[idx], val); else if (el == (jl_value_t*)jl_uint32_type) - ((uint32_t*)jl_array_data(arr))[idx] = val; + jl_atomic_store_release(&((_Atomic(uint32_t)*)jl_array_data(arr))[idx], val); else abort(); } @@ -93,7 +113,7 @@ ssize_t jl_smallintset_lookup(jl_array_t *cache, smallintset_eq eq, const void * size_t orig = index; size_t iter = 0; do { - size_t val1 = jl_intref(cache, index); + size_t val1 = jl_intref_acquire(cache, index); if (val1 == 0) { JL_GC_POP(); return -1; @@ -121,7 +141,7 @@ static int smallintset_insert_(jl_array_t *a, uint_t hv, size_t val1) size_t maxprobe = max_probe(sz); do { if (jl_intref(a, index) == 0) { - jl_intset(a, index, val1); + jl_intset_release(a, index, val1); return 1; } index = (index + 1) & (sz - 1); diff --git a/src/staticdata.c b/src/staticdata.c index f4ba300a37c04f..bdbe73f857f261 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -601,20 +601,21 @@ static void jl_queue_module_for_serialization(jl_serializer_state *s, jl_module_ { jl_queue_for_serialization(s, m->name); jl_queue_for_serialization(s, m->parent); - size_t i; - void **table = m->bindings.table; - for (i = 0; i < m->bindings.size; i += 2) { - if (table[i+1] != HT_NOTFOUND) { - jl_sym_t *name = (jl_sym_t*)table[i]; - jl_queue_for_serialization(s, (jl_value_t*)name); - jl_binding_t *b = (jl_binding_t*)table[i+1]; - if (name == jl_docmeta_sym && jl_atomic_load_relaxed(&b->value) && jl_options.strip_metadata) + jl_queue_for_serialization(s, m->bindings); + jl_queue_for_serialization(s, m->bindingkeyset); + if (jl_options.strip_metadata) { + jl_svec_t *table = jl_atomic_load_relaxed(&m->bindings); + for (size_t i = 0; i < jl_svec_len(table); i++) { + jl_binding_t *b = (jl_binding_t*)jl_svec_ref(table, i); + if ((void*)b == jl_nothing) + break; + jl_sym_t *name = b->globalref->name; + if (name == jl_docmeta_sym && jl_atomic_load_relaxed(&b->value)) record_field_change((jl_value_t**)&b->value, jl_nothing); - jl_queue_for_serialization(s, jl_module_globalref(m, name)); } } - for (i = 0; i < m->usings.len; i++) { + for (size_t i = 0; i < m->usings.len; i++) { jl_queue_for_serialization(s, (jl_value_t*)m->usings.items[i]); } } @@ -991,28 +992,13 @@ static void jl_write_module(jl_serializer_state *s, uintptr_t item, jl_module_t newm->parent = NULL; arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_module_t, parent))); arraylist_push(&s->relocs_list, (void*)backref_id(s, m->parent, s->link_ids_relocs)); - newm->primary_world = jl_atomic_load_acquire(&jl_world_counter); - - // write out the bindings table as a list - // immediately after jl_module_t - // (the ptrhash will need to be recreated on load) - size_t count = 0; - size_t i; - void **table = m->bindings.table; - for (i = 0; i < m->bindings.size; i += 2) { - if (table[i+1] != HT_NOTFOUND) { - jl_globalref_t *gr = ((jl_binding_t*)table[i+1])->globalref; - assert(gr && gr->mod == m && gr->name == (jl_sym_t*)table[i]); - write_pointerfield(s, (jl_value_t*)gr); - tot += sizeof(void*); - count += 1; - } - } - assert(ios_pos(s->s) - reloc_offset == tot); - newm = (jl_module_t*)&s->s->buf[reloc_offset]; // buf might have been reallocated - newm->bindings.size = count; // stash the count in newm->size - newm->bindings.table = NULL; - memset(&newm->bindings._space, 0, sizeof(newm->bindings._space)); + newm->bindings = NULL; + arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_module_t, bindings))); + arraylist_push(&s->relocs_list, (void*)backref_id(s, m->bindings, s->link_ids_relocs)); + newm->bindingkeyset = NULL; + arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_module_t, bindingkeyset))); + arraylist_push(&s->relocs_list, (void*)backref_id(s, m->bindingkeyset, s->link_ids_relocs)); + newm->primary_world = ~(size_t)0; // write out the usings list memset(&newm->usings._space, 0, sizeof(newm->usings._space)); @@ -1040,6 +1026,7 @@ static void jl_write_module(jl_serializer_state *s, uintptr_t item, jl_module_t tot += sizeof(void*); } } + assert(ios_pos(s->s) - reloc_offset == tot); } static void record_gvars(jl_serializer_state *s, arraylist_t *globals) JL_NOTSAFEPOINT @@ -3083,16 +3070,7 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl // and we are already bad at that jl_module_t *mod = (jl_module_t*)obj; mod->build_id.hi = checksum; - size_t nbindings = mod->bindings.size; - htable_new(&mod->bindings, nbindings); - jl_globalref_t **pgr = (jl_globalref_t**)&mod[1]; - for (size_t i = 0; i < nbindings; i++) { - jl_globalref_t *gr = pgr[i]; - jl_binding_t *b = gr->binding; - assert(gr->mod == mod); - assert(b && (!b->globalref || b->globalref->mod == mod)); - ptrhash_put(&mod->bindings, gr->name, b); - } + mod->primary_world = world; if (mod->usings.items != &mod->usings._space[0]) { // arraylist_t assumes we called malloc to get this memory, so make that true now void **newitems = (void**)malloc_s(mod->usings.max * sizeof(void*)); diff --git a/src/staticdata_utils.c b/src/staticdata_utils.c index cbebe239808ed9..297dbbdf085e31 100644 --- a/src/staticdata_utils.c +++ b/src/staticdata_utils.c @@ -391,47 +391,47 @@ static void jl_collect_extext_methods_from_mod(jl_array_t *s, jl_module_t *m) { if (s && !jl_object_in_image((jl_value_t*)m)) s = NULL; // do not collect any methods - size_t i; - void **table = m->bindings.table; - for (i = 0; i < m->bindings.size; i += 2) { - if (table[i+1] != HT_NOTFOUND) { - jl_sym_t *name = (jl_sym_t*)table[i]; - jl_binding_t *b = (jl_binding_t*)table[i+1]; - if (b->owner == b && b->value && b->constp) { - jl_value_t *bv = jl_unwrap_unionall(b->value); - if (jl_is_datatype(bv)) { - jl_typename_t *tn = ((jl_datatype_t*)bv)->name; - if (tn->module == m && tn->name == name && tn->wrapper == b->value) { - jl_methtable_t *mt = tn->mt; - if (mt != NULL && - (jl_value_t*)mt != jl_nothing && - (mt != jl_type_type_mt && mt != jl_nonfunction_mt)) { - assert(mt->module == tn->module); - jl_collect_methtable_from_mod(s, mt); - if (s) - jl_collect_missing_backedges(mt); - } + jl_svec_t *table = jl_atomic_load_relaxed(&m->bindings); + for (size_t i = 0; i < jl_svec_len(table); i++) { + jl_binding_t *b = (jl_binding_t*)jl_svec_ref(table, i); + if ((void*)b == jl_nothing) + break; + jl_sym_t *name = b->globalref->name; + if (b->owner == b && b->value && b->constp) { + jl_value_t *bv = jl_unwrap_unionall(b->value); + if (jl_is_datatype(bv)) { + jl_typename_t *tn = ((jl_datatype_t*)bv)->name; + if (tn->module == m && tn->name == name && tn->wrapper == b->value) { + jl_methtable_t *mt = tn->mt; + if (mt != NULL && + (jl_value_t*)mt != jl_nothing && + (mt != jl_type_type_mt && mt != jl_nonfunction_mt)) { + assert(mt->module == tn->module); + jl_collect_methtable_from_mod(s, mt); + if (s) + jl_collect_missing_backedges(mt); } } - else if (jl_is_module(b->value)) { - jl_module_t *child = (jl_module_t*)b->value; - if (child != m && child->parent == m && child->name == name) { - // this is the original/primary binding for the submodule - jl_collect_extext_methods_from_mod(s, (jl_module_t*)b->value); - } + } + else if (jl_is_module(b->value)) { + jl_module_t *child = (jl_module_t*)b->value; + if (child != m && child->parent == m && child->name == name) { + // this is the original/primary binding for the submodule + jl_collect_extext_methods_from_mod(s, (jl_module_t*)b->value); } - else if (jl_is_mtable(b->value)) { - jl_methtable_t *mt = (jl_methtable_t*)b->value; - if (mt->module == m && mt->name == name) { - // this is probably an external method table, so let's assume so - // as there is no way to precisely distinguish them, - // and the rest of this serializer does not bother - // to handle any method tables specially - jl_collect_methtable_from_mod(s, (jl_methtable_t*)bv); - } + } + else if (jl_is_mtable(b->value)) { + jl_methtable_t *mt = (jl_methtable_t*)b->value; + if (mt->module == m && mt->name == name) { + // this is probably an external method table, so let's assume so + // as there is no way to precisely distinguish them, + // and the rest of this serializer does not bother + // to handle any method tables specially + jl_collect_methtable_from_mod(s, (jl_methtable_t*)bv); } } } + table = jl_atomic_load_relaxed(&m->bindings); } } @@ -513,7 +513,7 @@ static void jl_collect_edges(jl_array_t *edges, jl_array_t *ext_targets) if (jl_is_method_instance(callee)) { jl_methtable_t *mt = jl_method_get_table(((jl_method_instance_t*)callee)->def.method); - if (!jl_object_in_image((jl_value_t*)mt->module)) + if (!jl_object_in_image((jl_value_t*)mt)) continue; } @@ -526,6 +526,7 @@ static void jl_collect_edges(jl_array_t *edges, jl_array_t *ext_targets) size_t min_valid = 0; size_t max_valid = ~(size_t)0; if (invokeTypes) { + assert(jl_is_method_instance(callee)); jl_methtable_t *mt = jl_method_get_table(((jl_method_instance_t*)callee)->def.method); if ((jl_value_t*)mt == jl_nothing) { callee_ids = NULL; // invalid diff --git a/src/toplevel.c b/src/toplevel.c index 65a64001c3e832..0b752fb1c161e0 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -208,10 +208,10 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex #if 0 // some optional post-processing steps size_t i; - void **table = newm->bindings.table; - for(i=1; i < newm->bindings.size; i+=2) { - if (table[i] != HT_NOTFOUND) { - jl_binding_t *b = (jl_binding_t*)table[i]; + jl_svec_t *table = jl_atomic_load_relaxed(&newm->bindings); + for (size_t i = 0; i < jl_svec_len(table); i++) { + jl_binding_t *b = (jl_binding_t*)jl_svec_ref(table, i); + if ((void*)b != jl_nothing) { // remove non-exported macros if (jl_symbol_name(b->name)[0]=='@' && !b->exportp && b->owner == b) From 7313b7f090c8579b753d2a3a7b65af939864b65c Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Thu, 12 Jan 2023 10:40:03 -0700 Subject: [PATCH 306/387] Add !noalias and !alias.scope metadata The main idea here is that the TBAA domain is ill-equipped for reasoning about regions (and, in particular, suffers total precision less when merging disparate types in a `memcpy`). Instead, `!noalias` should be used for region-based memory information and `!tbaa` should be used exclusively for layout. We use (5) regions corresponding to the top level of the TBAA tree: - gcframe - stack - data - constant - type_metadata For now, this leaves the TBAA hierarchy in tact and only adds additional `!noalias` metadata. `!tbaa` annotations should be the same as before. --- src/cgutils.cpp | 57 ++++++++++---- src/codegen.cpp | 198 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 238 insertions(+), 17 deletions(-) diff --git a/src/cgutils.cpp b/src/cgutils.cpp index 693c4e66bd35a1..bffaa888aaa9ad 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -890,8 +890,8 @@ static Value *data_pointer(jl_codectx_t &ctx, const jl_cgval_t &x) return data; } -static void emit_memcpy_llvm(jl_codectx_t &ctx, Value *dst, MDNode *tbaa_dst, Value *src, MDNode *tbaa_src, - uint64_t sz, unsigned align, bool is_volatile) +static void emit_memcpy_llvm(jl_codectx_t &ctx, Value *dst, jl_aliasinfo_t const &dst_ai, Value *src, + jl_aliasinfo_t const &src_ai, uint64_t sz, unsigned align, bool is_volatile) { if (sz == 0) return; @@ -933,44 +933,73 @@ static void emit_memcpy_llvm(jl_codectx_t &ctx, Value *dst, MDNode *tbaa_dst, Va src = emit_bitcast(ctx, src, dstty); } if (directel) { - auto val = tbaa_decorate(tbaa_src, ctx.builder.CreateAlignedLoad(directel, src, Align(align), is_volatile)); - tbaa_decorate(tbaa_dst, ctx.builder.CreateAlignedStore(val, dst, Align(align), is_volatile)); + auto val = src_ai.decorateInst(ctx.builder.CreateAlignedLoad(directel, src, Align(align), is_volatile)); + dst_ai.decorateInst(ctx.builder.CreateAlignedStore(val, dst, Align(align), is_volatile)); ++SkippedMemcpys; return; } } #endif + ++EmittedMemcpys; + // the memcpy intrinsic does not allow to specify different alias tags // for the load part (x.tbaa) and the store part (ctx.tbaa().tbaa_stack). // since the tbaa lattice has to be a tree we have unfortunately // x.tbaa ∪ ctx.tbaa().tbaa_stack = tbaa_root if x.tbaa != ctx.tbaa().tbaa_stack - ++EmittedMemcpys; - ctx.builder.CreateMemCpy(dst, MaybeAlign(align), src, MaybeAlign(0), sz, is_volatile, MDNode::getMostGenericTBAA(tbaa_dst, tbaa_src)); + + // Now that we use scoped aliases to label disparate regions of memory, the TBAA + // metadata should be revisited so that it only represents memory layouts. Once + // that's done, we can expect that in most cases tbaa(src) == tbaa(dst) and the + // above problem won't be as serious. + + auto merged_ai = dst_ai.merge(src_ai); + ctx.builder.CreateMemCpy(dst, MaybeAlign(align), src, MaybeAlign(0), sz, is_volatile, + merged_ai.tbaa, merged_ai.tbaa_struct, merged_ai.scope, merged_ai.noalias); } -static void emit_memcpy_llvm(jl_codectx_t &ctx, Value *dst, MDNode *tbaa_dst, Value *src, MDNode *tbaa_src, - Value *sz, unsigned align, bool is_volatile) +static void emit_memcpy_llvm(jl_codectx_t &ctx, Value *dst, jl_aliasinfo_t const &dst_ai, Value *src, + jl_aliasinfo_t const &src_ai, Value *sz, unsigned align, bool is_volatile) { if (auto const_sz = dyn_cast(sz)) { - emit_memcpy_llvm(ctx, dst, tbaa_dst, src, tbaa_src, const_sz->getZExtValue(), align, is_volatile); + emit_memcpy_llvm(ctx, dst, dst_ai, src, src_ai, const_sz->getZExtValue(), align, is_volatile); return; } ++EmittedMemcpys; - ctx.builder.CreateMemCpy(dst, MaybeAlign(align), src, MaybeAlign(0), sz, is_volatile, MDNode::getMostGenericTBAA(tbaa_dst, tbaa_src)); + + auto merged_ai = dst_ai.merge(src_ai); + ctx.builder.CreateMemCpy(dst, MaybeAlign(align), src, MaybeAlign(0), sz, is_volatile, + merged_ai.tbaa, merged_ai.tbaa_struct, merged_ai.scope, merged_ai.noalias); } template static void emit_memcpy(jl_codectx_t &ctx, Value *dst, MDNode *tbaa_dst, Value *src, MDNode *tbaa_src, T1 &&sz, unsigned align, bool is_volatile=false) { - emit_memcpy_llvm(ctx, dst, tbaa_dst, src, tbaa_src, sz, align, is_volatile); + emit_memcpy_llvm(ctx, dst, jl_aliasinfo_t::fromTBAA(ctx, tbaa_dst), src, + jl_aliasinfo_t::fromTBAA(ctx, tbaa_src), sz, align, is_volatile); } template static void emit_memcpy(jl_codectx_t &ctx, Value *dst, MDNode *tbaa_dst, const jl_cgval_t &src, T1 &&sz, unsigned align, bool is_volatile=false) { - emit_memcpy_llvm(ctx, dst, tbaa_dst, data_pointer(ctx, src), src.tbaa, sz, align, is_volatile); + emit_memcpy_llvm(ctx, dst, jl_aliasinfo_t::fromTBAA(ctx, tbaa_dst), data_pointer(ctx, src), + jl_aliasinfo_t::fromTBAA(ctx, src.tbaa), sz, align, is_volatile); +} + +template +static void emit_memcpy(jl_codectx_t &ctx, Value *dst, jl_aliasinfo_t const &dst_ai, Value *src, + jl_aliasinfo_t const &src_ai, T1 &&sz, unsigned align, bool is_volatile=false) +{ + emit_memcpy_llvm(ctx, dst, dst_ai, src, src_ai, sz, align, is_volatile); +} + +template +static void emit_memcpy(jl_codectx_t &ctx, Value *dst, jl_aliasinfo_t const &dst_ai, const jl_cgval_t &src, + T1 &&sz, unsigned align, bool is_volatile=false) +{ + auto src_ai = jl_aliasinfo_t::fromTBAA(ctx, src.tbaa); + emit_memcpy_llvm(ctx, dst, dst_ai, data_pointer(ctx, src), src_ai, sz, align, is_volatile); } static LoadInst *emit_nthptr_recast(jl_codectx_t &ctx, Value *v, Value *idx, MDNode *tbaa, Type *type) @@ -2699,7 +2728,9 @@ static Value *emit_arrayptr_internal(jl_codectx_t &ctx, const jl_cgval_t &tinfo, LoadInst *LI = ctx.builder.CreateAlignedLoad(LoadT, addr, Align(sizeof(char *))); LI->setOrdering(AtomicOrdering::NotAtomic); LI->setMetadata(LLVMContext::MD_nonnull, MDNode::get(ctx.builder.getContext(), None)); - tbaa_decorate(arraytype_constshape(tinfo.typ) ? ctx.tbaa().tbaa_const : ctx.tbaa().tbaa_arrayptr, LI); + jl_aliasinfo_t aliasinfo = jl_aliasinfo_t::fromTBAA(ctx, arraytype_constshape(tinfo.typ) ? ctx.tbaa().tbaa_const : ctx.tbaa().tbaa_arrayptr); + aliasinfo.decorateInst(LI); + return LI; } diff --git a/src/codegen.cpp b/src/codegen.cpp index 6a214c54244a60..1e1205a0d17ee8 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -356,6 +356,57 @@ struct jl_tbaacache_t { } }; +struct jl_noaliascache_t { + // Each domain operates completely independently. + // "No aliasing" is inferred if it is implied by any domain. + + // memory regions domain + struct jl_regions_t { + MDNode *gcframe; // GC frame + MDNode *stack; // Stack slot + MDNode *data; // Any user data that `pointerset/ref` are allowed to alias + MDNode *type_metadata; // Non-user-accessible type metadata incl. size, union selectors, etc. + MDNode *constant; // Memory that is immutable by the time LLVM can see it + + jl_regions_t(): gcframe(nullptr), stack(nullptr), data(nullptr), type_metadata(nullptr), constant(nullptr) {} + + void initialize(llvm::LLVMContext &context) { + MDBuilder mbuilder(context); + MDNode *domain = mbuilder.createAliasScopeDomain("jnoalias"); + + this->gcframe = mbuilder.createAliasScope("jnoalias_gcframe", domain); + this->stack = mbuilder.createAliasScope("jnoalias_stack", domain); + this->data = mbuilder.createAliasScope("jnoalias_data", domain); + this->type_metadata = mbuilder.createAliasScope("jnoalias_typemd", domain); + this->constant = mbuilder.createAliasScope("jnoalias_const", domain); + } + } regions; + + // `@aliasscope` domain + struct jl_aliasscope_t { + MDNode *current; + + jl_aliasscope_t(): current(nullptr) {} + + // No init required, this->current is only used to store the currently active aliasscope + void initialize(llvm::LLVMContext &context) {} + } aliasscope; + + bool initialized; + + jl_noaliascache_t(): regions(), aliasscope(), initialized(false) {} + + void initialize(llvm::LLVMContext &context) { + if (initialized) { + assert(®ions.constant->getContext() == &context); + return; + } + initialized = true; + regions.initialize(context); + aliasscope.initialize(context); + } +}; + struct jl_debugcache_t { // Basic DITypes DIDerivedType *jl_pvalue_dillvmt; @@ -1276,6 +1327,69 @@ static bool deserves_sret(jl_value_t *dt, Type *T) return (size_t)jl_datatype_size(dt) > sizeof(void*) && !T->isFloatingPointTy() && !T->isVectorTy(); } +// Alias Analysis Info (analogous to llvm::AAMDNodes) +struct jl_aliasinfo_t { + MDNode *tbaa = nullptr; // '!tbaa': Struct-path TBAA. TBAA graph forms a tree (indexed by offset). + // Two pointers do not alias if they are not transitive parents + // (effectively, subfields) of each other or equal. + MDNode *tbaa_struct = nullptr; // '!tbaa.struct': Describes memory layout of struct. + MDNode *scope = nullptr; // '!alias.scope': Generic "noalias" memory access sets. + // If alias.scope(inst_a) ⊆ noalias(inst_b) (in any "domain") + // => inst_a, inst_b do not alias. + MDNode *noalias = nullptr; // '!noalias': See '!alias.scope' above. + + enum class Region { unknown, gcframe, stack, data, constant, type_metadata }; // See jl_regions_t + + explicit jl_aliasinfo_t() = default; + explicit jl_aliasinfo_t(jl_codectx_t &ctx, Region r, MDNode *tbaa); + explicit jl_aliasinfo_t(MDNode *tbaa, MDNode *tbaa_struct, MDNode *scope, MDNode *noalias) + : tbaa(tbaa), tbaa_struct(tbaa_struct), scope(scope), noalias(noalias) {} + jl_aliasinfo_t(const jl_aliasinfo_t &) = default; + + // Add !tbaa, !tbaa.struct, !alias.scope, !noalias annotations to an instruction. + // + // Also adds `invariant.load` to load instructions in the constant !noalias scope. + Instruction *decorateInst(Instruction *inst) const { + + if (this->tbaa) + inst->setMetadata(LLVMContext::MD_tbaa, this->tbaa); + if (this->tbaa_struct) + inst->setMetadata(LLVMContext::MD_tbaa_struct, this->tbaa_struct); + if (this->scope) + inst->setMetadata(LLVMContext::MD_alias_scope, this->scope); + if (this->noalias) + inst->setMetadata(LLVMContext::MD_noalias, this->noalias); + + if (this->scope && isa(inst)) { + // If this is in the read-only region, mark the load with "!invariant.load" + if (this->scope->getNumOperands() == 1) { + MDNode *operand = cast(this->scope->getOperand(0)); + auto scope_name = cast(operand->getOperand(0))->getString(); + if (scope_name == "jnoalias_const") + inst->setMetadata(LLVMContext::MD_invariant_load, MDNode::get(inst->getContext(), None)); + } + } + + return inst; + } + + // Merge two sets of alias information. + jl_aliasinfo_t merge(const jl_aliasinfo_t &other) const { + jl_aliasinfo_t result; + result.tbaa = MDNode::getMostGenericTBAA(this->tbaa, other.tbaa); + result.tbaa_struct = nullptr; + result.scope = MDNode::getMostGenericAliasScope(this->scope, other.scope); + result.noalias = MDNode::intersect(this->noalias, other.noalias); + return result; + } + + // Create alias information based on the provided TBAA metadata. + // + // This function only exists to help transition to using !noalias to encode + // memory region non-aliasing. It should be deleted once the TBAA metadata + // is improved to encode only memory layout and *not* memory regions. + static jl_aliasinfo_t fromTBAA(jl_codectx_t &ctx, MDNode *tbaa); +}; // metadata tracking for a llvm Value* during codegen struct jl_cgval_t { @@ -1441,6 +1555,7 @@ class jl_codectx_t { jl_module_t *module = NULL; jl_typecache_t type_cache; jl_tbaacache_t tbaa_cache; + jl_noaliascache_t aliasscope_cache; jl_method_instance_t *linfo = NULL; jl_value_t *rettype = NULL; jl_code_info_t *source = NULL; @@ -1452,7 +1567,6 @@ class jl_codectx_t { Value *spvals_ptr = NULL; Value *argArray = NULL; Value *argCount = NULL; - MDNode *aliasscope = NULL; std::string funcName; int vaSlot = -1; // name of vararg argument int nReqArgs = 0; @@ -1491,6 +1605,11 @@ class jl_codectx_t { return tbaa_cache; } + jl_noaliascache_t &noalias() { + aliasscope_cache.initialize(builder.getContext()); + return aliasscope_cache; + } + ~jl_codectx_t() { // Transfer local delayed calls to the global queue for (auto call_target : call_targets) @@ -1502,6 +1621,77 @@ GlobalVariable *JuliaVariable::realize(jl_codectx_t &ctx) { return realize(jl_Module); } +jl_aliasinfo_t::jl_aliasinfo_t(jl_codectx_t &ctx, Region r, MDNode *tbaa): tbaa(tbaa), tbaa_struct(nullptr) { + MDNode *alias_scope = nullptr; + jl_noaliascache_t::jl_regions_t regions = ctx.noalias().regions; + switch (r) { + case Region::unknown: + alias_scope = nullptr; + break; + case Region::gcframe: + alias_scope = regions.gcframe; + break; + case Region::stack: + alias_scope = regions.stack; + break; + case Region::data: + alias_scope = regions.data; + break; + case Region::constant: + alias_scope = regions.constant; + break; + case Region::type_metadata: + alias_scope = regions.type_metadata; + break; + } + + MDNode *all_scopes[5] = { regions.gcframe, regions.stack, regions.data, regions.type_metadata, regions.constant }; + if (alias_scope) { + // The matching region is added to !alias.scope + // All other regions are added to !noalias + + int i = 0; + Metadata *scopes[1] = { alias_scope }; + Metadata *noaliases[4]; + for (auto const &scope: all_scopes) { + if (scope == alias_scope) continue; + noaliases[i++] = scope; + } + + this->scope = MDNode::get(ctx.builder.getContext(), ArrayRef(scopes)); + this->noalias = MDNode::get(ctx.builder.getContext(), ArrayRef(noaliases)); + } +} + +jl_aliasinfo_t jl_aliasinfo_t::fromTBAA(jl_codectx_t &ctx, MDNode *tbaa) { + auto cache = ctx.tbaa(); + + // Each top-level TBAA node has a corresponding !alias.scope scope + MDNode *tbaa_srcs[5] = { cache.tbaa_gcframe, cache.tbaa_stack, cache.tbaa_data, cache.tbaa_array, cache.tbaa_const }; + Region regions[5] = { Region::gcframe, Region::stack, Region::data, Region::type_metadata, Region::constant }; + + if (tbaa != nullptr) { + MDNode *node = cast(tbaa->getOperand(1)); + if (cast(node->getOperand(0))->getString() != "jtbaa") { + + // Climb up to node just before root + MDNode *parent_node = cast(node->getOperand(1)); + while (cast(parent_node->getOperand(0))->getString() != "jtbaa") { + node = parent_node; + parent_node = cast(node->getOperand(1)); + } + + // Find the matching node's index + for (int i = 0; i < 5; i++) { + if (cast(tbaa_srcs[i]->getOperand(1)) == node) + return jl_aliasinfo_t(ctx, regions[i], tbaa); + } + } + } + + return jl_aliasinfo_t(ctx, Region::unknown, tbaa); +} + static Type *julia_type_to_llvm(jl_codectx_t &ctx, jl_value_t *jt, bool *isboxed = NULL); static jl_returninfo_t get_specsig_function(jl_codectx_t &ctx, Module *M, StringRef name, jl_value_t *sig, jl_value_t *jlrettype, bool is_opaque_closure); static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval = -1); @@ -3254,7 +3444,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, *ret = emit_unionload(ctx, data, ptindex, ety, elsz, al, ctx.tbaa().tbaa_arraybuf, true, union_max, ctx.tbaa().tbaa_arrayselbyte); } else { - MDNode *aliasscope = (f == jl_builtin_const_arrayref) ? ctx.aliasscope : nullptr; + MDNode *aliasscope = (f == jl_builtin_const_arrayref) ? ctx.noalias().aliasscope.current : nullptr; *ret = typed_load(ctx, emit_arrayptr(ctx, ary, ary_ex), idx, ety, @@ -3369,7 +3559,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, emit_arrayptr(ctx, ary, ary_ex, isboxed), idx, val, jl_cgval_t(), ety, isboxed ? ctx.tbaa().tbaa_ptrarraybuf : ctx.tbaa().tbaa_arraybuf, - ctx.aliasscope, + ctx.noalias().aliasscope.current, data_owner, isboxed, isboxed ? AtomicOrdering::Release : AtomicOrdering::NotAtomic, // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0 @@ -7679,7 +7869,7 @@ static jl_llvm_functions_t ctx.builder.SetCurrentDebugLocation(linetable.at(debuginfoloc).loc); coverageVisitStmt(debuginfoloc); } - ctx.aliasscope = aliasscopes[cursor]; + ctx.noalias().aliasscope.current = aliasscopes[cursor]; jl_value_t *stmt = jl_array_ptr_ref(stmts, cursor); jl_expr_t *expr = jl_is_expr(stmt) ? (jl_expr_t*)stmt : nullptr; if (jl_is_returnnode(stmt)) { From b08c644de5d469b352c3ccf7e2b7cdf4ac009008 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Thu, 12 Jan 2023 11:00:20 -0700 Subject: [PATCH 307/387] Derive `!noalias` from `!tbaa` for most loads/stores This is an interim solution that derives the correct `!noalias` region from the existing TBAA information. Later we will want to: - Revise the TBAA hierarchy to remove region information - Delete `jl_aliasinfo_t::fromTBAA()` - Update `jl_cgval_t` to store a `jl_aliasinfo_t` --- src/ccall.cpp | 30 +++-- src/cgutils.cpp | 199 +++++++++++++++++---------------- src/codegen.cpp | 122 +++++++++++--------- src/intrinsics.cpp | 35 +++--- test/llvmpasses/aliasscopes.jl | 27 +++-- 5 files changed, 232 insertions(+), 181 deletions(-) diff --git a/src/ccall.cpp b/src/ccall.cpp index e71b65ad886004..2dea1e07ca45bc 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -550,10 +550,12 @@ static Value *julia_to_native( // since those are immutable. Value *slot = emit_static_alloca(ctx, to); if (!jvinfo.ispointer()) { - tbaa_decorate(jvinfo.tbaa, ctx.builder.CreateStore(emit_unbox(ctx, to, jvinfo, jlto), slot)); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, jvinfo.tbaa); + ai.decorateInst(ctx.builder.CreateStore(emit_unbox(ctx, to, jvinfo, jlto), slot)); } else { - emit_memcpy(ctx, slot, jvinfo.tbaa, jvinfo, jl_datatype_size(jlto), julia_alignment(jlto)); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, jvinfo.tbaa); + emit_memcpy(ctx, slot, ai, jvinfo, jl_datatype_size(jlto), julia_alignment(jlto)); } return slot; } @@ -1571,7 +1573,8 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) const int tid_offset = offsetof(jl_task_t, tid); Value *ptid = ctx.builder.CreateInBoundsGEP(getInt16Ty(ctx.builder.getContext()), ptask_i16, ConstantInt::get(getSizeTy(ctx.builder.getContext()), tid_offset / sizeof(int16_t))); LoadInst *tid = ctx.builder.CreateAlignedLoad(getInt16Ty(ctx.builder.getContext()), ptid, Align(sizeof(int16_t))); - tbaa_decorate(ctx.tbaa().tbaa_gcframe, tid); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_gcframe); + ai.decorateInst(tid); return mark_or_box_ccall_result(ctx, tid, retboxed, rt, unionall, static_rt); } else if (is_libjulia_func(jl_gc_disable_finalizers_internal) @@ -1675,8 +1678,10 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) len = ctx.builder.CreateAlignedLoad(getSizeTy(ctx.builder.getContext()), ptr, Align(sizeof(size_t))); // Only mark with TBAA if we are sure about the type. // This could otherwise be in a dead branch - if (svecv.typ == (jl_value_t*)jl_simplevector_type) - tbaa_decorate(ctx.tbaa().tbaa_const, cast(len)); + if (svecv.typ == (jl_value_t*)jl_simplevector_type) { + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + ai.decorateInst(cast(len)); + } MDBuilder MDB(ctx.builder.getContext()); auto rng = MDB.createRange( Constant::getNullValue(getSizeTy(ctx.builder.getContext())), ConstantInt::get(getSizeTy(ctx.builder.getContext()), INTPTR_MAX / sizeof(void*) - 1)); @@ -1701,8 +1706,10 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) load->setAtomic(AtomicOrdering::Unordered); // Only mark with TBAA if we are sure about the type. // This could otherwise be in a dead branch - if (svecv.typ == (jl_value_t*)jl_simplevector_type) - tbaa_decorate(ctx.tbaa().tbaa_const, load); + if (svecv.typ == (jl_value_t*)jl_simplevector_type) { + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + ai.decorateInst(load); + } JL_GC_POP(); return mark_or_box_ccall_result(ctx, load, retboxed, rt, unionall, static_rt); } @@ -1736,7 +1743,8 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) Value *slot_addr = ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, arrayptr, idx); LoadInst *load = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, slot_addr, Align(sizeof(void*))); load->setAtomic(AtomicOrdering::Unordered); - tbaa_decorate(ctx.tbaa().tbaa_ptrarraybuf, load); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_ptrarraybuf); + ai.decorateInst(load); Value *res = ctx.builder.CreateZExt(ctx.builder.CreateICmpNE(load, Constant::getNullValue(ctx.types().T_prjlvalue)), getInt32Ty(ctx.builder.getContext())); JL_GC_POP(); return mark_or_box_ccall_result(ctx, res, retboxed, rt, unionall, static_rt); @@ -1838,7 +1846,8 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) Value *ph1 = emit_bitcast(ctx, decay_derived(ctx, boxed(ctx, val)), getSizePtrTy(ctx.builder.getContext())); Value *ph2 = ctx.builder.CreateInBoundsGEP(getSizeTy(ctx.builder.getContext()), ph1, ConstantInt::get(getSizeTy(ctx.builder.getContext()), hash_offset / sizeof(size_t))); LoadInst *hashval = ctx.builder.CreateAlignedLoad(getSizeTy(ctx.builder.getContext()), ph2, Align(sizeof(size_t))); - tbaa_decorate(ctx.tbaa().tbaa_const, hashval); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + ai.decorateInst(hashval); return mark_or_box_ccall_result(ctx, hashval, retboxed, rt, unionall, static_rt); } else if (!val.isboxed) { @@ -2128,7 +2137,8 @@ jl_cgval_t function_sig_t::emit_a_ccall( auto slot = emit_static_alloca(ctx, resultTy); slot->setAlignment(Align(boxalign)); ctx.builder.CreateAlignedStore(result, slot, Align(boxalign)); - emit_memcpy(ctx, strct, tbaa, slot, tbaa, rtsz, boxalign); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + emit_memcpy(ctx, strct, ai, slot, ai, rtsz, boxalign); } else { init_bits_value(ctx, strct, result, tbaa, boxalign); diff --git a/src/cgutils.cpp b/src/cgutils.cpp index bffaa888aaa9ad..7acfb24b916159 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -474,7 +474,8 @@ static Value *literal_pointer_val(jl_codectx_t &ctx, jl_value_t *p) if (!ctx.emission_context.imaging) return literal_static_pointer_val(p, ctx.types().T_pjlvalue); Value *pgv = literal_pointer_val_slot(ctx, p); - return tbaa_decorate(ctx.tbaa().tbaa_const, maybe_mark_load_dereferenceable( + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + return ai.decorateInst(maybe_mark_load_dereferenceable( ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, pgv, Align(sizeof(void*))), false, jl_typeof(p))); } @@ -490,7 +491,8 @@ static Value *literal_pointer_val(jl_codectx_t &ctx, jl_binding_t *p) // bindings are prefixed with jl_bnd# jl_globalref_t *gr = p->globalref; Value *pgv = gr ? julia_pgv(ctx, "jl_bnd#", gr->name, gr->mod, p) : julia_pgv(ctx, "jl_bnd#", p); - return tbaa_decorate(ctx.tbaa().tbaa_const, maybe_mark_load_dereferenceable( + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + return ai.decorateInst(maybe_mark_load_dereferenceable( ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, pgv, Align(sizeof(void*))), false, sizeof(jl_binding_t), alignof(jl_binding_t))); } @@ -530,7 +532,8 @@ static Value *julia_binding_gv(jl_codectx_t &ctx, jl_binding_t *b) if (ctx.emission_context.imaging) { jl_globalref_t *gr = b->globalref; Value *pgv = gr ? julia_pgv(ctx, "*", gr->name, gr->mod, b) : julia_pgv(ctx, "*jl_bnd#", b); - return tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, pgv, Align(sizeof(void*)))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + return ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, pgv, Align(sizeof(void*)))); } else { return literal_static_pointer_val(b, ctx.types().T_pjlvalue); @@ -971,22 +974,6 @@ static void emit_memcpy_llvm(jl_codectx_t &ctx, Value *dst, jl_aliasinfo_t const merged_ai.tbaa, merged_ai.tbaa_struct, merged_ai.scope, merged_ai.noalias); } -template -static void emit_memcpy(jl_codectx_t &ctx, Value *dst, MDNode *tbaa_dst, Value *src, MDNode *tbaa_src, - T1 &&sz, unsigned align, bool is_volatile=false) -{ - emit_memcpy_llvm(ctx, dst, jl_aliasinfo_t::fromTBAA(ctx, tbaa_dst), src, - jl_aliasinfo_t::fromTBAA(ctx, tbaa_src), sz, align, is_volatile); -} - -template -static void emit_memcpy(jl_codectx_t &ctx, Value *dst, MDNode *tbaa_dst, const jl_cgval_t &src, - T1 &&sz, unsigned align, bool is_volatile=false) -{ - emit_memcpy_llvm(ctx, dst, jl_aliasinfo_t::fromTBAA(ctx, tbaa_dst), data_pointer(ctx, src), - jl_aliasinfo_t::fromTBAA(ctx, src.tbaa), sz, align, is_volatile); -} - template static void emit_memcpy(jl_codectx_t &ctx, Value *dst, jl_aliasinfo_t const &dst_ai, Value *src, jl_aliasinfo_t const &src_ai, T1 &&sz, unsigned align, bool is_volatile=false) @@ -1010,7 +997,8 @@ static LoadInst *emit_nthptr_recast(jl_codectx_t &ctx, Value *v, Value *idx, MDN emit_bitcast(ctx, maybe_decay_tracked(ctx, v), ctx.types().T_pprjlvalue), idx); LoadInst *load = ctx.builder.CreateLoad(type, emit_bitcast(ctx, vptr, PointerType::get(type, 0))); - tbaa_decorate(tbaa, load); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.decorateInst(load); return load; } @@ -1053,9 +1041,10 @@ static jl_cgval_t emit_typeof(jl_codectx_t &ctx, const jl_cgval_t &p, bool maybe p.typ, counter); auto emit_unboxty = [&] () -> Value* { + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); if (ctx.emission_context.imaging) return track_pjlvalue( - ctx, tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, datatype_or_p, Align(sizeof(void*))))); + ctx, ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, datatype_or_p, Align(sizeof(void*))))); return datatype_or_p; }; Value *res; @@ -1097,25 +1086,28 @@ static Value *emit_datatype_types(jl_codectx_t &ctx, Value *dt) { Value *Ptr = emit_bitcast(ctx, decay_derived(ctx, dt), ctx.types().T_ppjlvalue); Value *Idx = ConstantInt::get(getSizeTy(ctx.builder.getContext()), offsetof(jl_datatype_t, types) / sizeof(void*)); - return tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad( + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + return ai.decorateInst(ctx.builder.CreateAlignedLoad( ctx.types().T_pjlvalue, ctx.builder.CreateInBoundsGEP(ctx.types().T_pjlvalue, Ptr, Idx), Align(sizeof(void*)))); } static Value *emit_datatype_nfields(jl_codectx_t &ctx, Value *dt) { Value *type_svec = emit_bitcast(ctx, emit_datatype_types(ctx, dt), getSizePtrTy(ctx.builder.getContext())); - return tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(getSizeTy(ctx.builder.getContext()), type_svec, Align(sizeof(void*)))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + return ai.decorateInst(ctx.builder.CreateAlignedLoad(getSizeTy(ctx.builder.getContext()), type_svec, Align(sizeof(void*)))); } static Value *emit_datatype_size(jl_codectx_t &ctx, Value *dt) { + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); Value *Ptr = emit_bitcast(ctx, decay_derived(ctx, dt), getInt32PtrTy(ctx.builder.getContext())->getPointerTo()); Value *Idx = ConstantInt::get(getSizeTy(ctx.builder.getContext()), offsetof(jl_datatype_t, layout) / sizeof(int32_t*)); Ptr = ctx.builder.CreateInBoundsGEP(getInt32PtrTy(ctx.builder.getContext()), Ptr, Idx); - Ptr = tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(getInt32PtrTy(ctx.builder.getContext()), Ptr, Align(sizeof(int32_t*)))); + Ptr = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt32PtrTy(ctx.builder.getContext()), Ptr, Align(sizeof(int32_t*)))); Idx = ConstantInt::get(getSizeTy(ctx.builder.getContext()), offsetof(jl_datatype_layout_t, size) / sizeof(int32_t)); Ptr = ctx.builder.CreateInBoundsGEP(getInt32Ty(ctx.builder.getContext()), Ptr, Idx); - return tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(getInt32Ty(ctx.builder.getContext()), Ptr, Align(sizeof(int32_t)))); + return ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt32Ty(ctx.builder.getContext()), Ptr, Align(sizeof(int32_t)))); } /* this is valid code, it's simply unused @@ -1169,12 +1161,13 @@ static Value *emit_sizeof(jl_codectx_t &ctx, const jl_cgval_t &p) static Value *emit_datatype_mutabl(jl_codectx_t &ctx, Value *dt) { + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); Value *Ptr = emit_bitcast(ctx, decay_derived(ctx, dt), ctx.types().T_ppint8); Value *Idx = ConstantInt::get(getSizeTy(ctx.builder.getContext()), offsetof(jl_datatype_t, name)); - Value *Nam = tbaa_decorate(ctx.tbaa().tbaa_const, + Value *Nam = ai.decorateInst( ctx.builder.CreateAlignedLoad(getInt8PtrTy(ctx.builder.getContext()), ctx.builder.CreateInBoundsGEP(getInt8PtrTy(ctx.builder.getContext()), Ptr, Idx), Align(sizeof(int8_t*)))); Value *Idx2 = ConstantInt::get(getSizeTy(ctx.builder.getContext()), offsetof(jl_typename_t, n_uninitialized) + sizeof(((jl_typename_t*)nullptr)->n_uninitialized)); - Value *mutabl = tbaa_decorate(ctx.tbaa().tbaa_const, + Value *mutabl = ai.decorateInst( ctx.builder.CreateAlignedLoad(getInt8Ty(ctx.builder.getContext()), ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), Nam, Idx2), Align(1))); mutabl = ctx.builder.CreateLShr(mutabl, 1); return ctx.builder.CreateTrunc(mutabl, getInt1Ty(ctx.builder.getContext())); @@ -1185,7 +1178,8 @@ static Value *emit_datatype_isprimitivetype(jl_codectx_t &ctx, Value *typ) { Value *isprimitive; isprimitive = ctx.builder.CreateConstInBoundsGEP1_32(getInt8Ty(ctx.builder.getContext()), emit_bitcast(ctx, decay_derived(ctx, typ), getInt8PtrTy(ctx.builder.getContext())), offsetof(jl_datatype_t, hash) + sizeof(((jl_datatype_t*)nullptr)->hash)); - isprimitive = tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(getInt8Ty(ctx.builder.getContext()), isprimitive, Align(1))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + isprimitive = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt8Ty(ctx.builder.getContext()), isprimitive, Align(1))); isprimitive = ctx.builder.CreateLShr(isprimitive, 7); isprimitive = ctx.builder.CreateTrunc(isprimitive, getInt1Ty(ctx.builder.getContext())); return isprimitive; @@ -1198,7 +1192,8 @@ static Value *emit_datatype_name(jl_codectx_t &ctx, Value *dt) ctx.types().T_pjlvalue, emit_bitcast(ctx, maybe_decay_tracked(ctx, dt), ctx.types().T_ppjlvalue), ConstantInt::get(getSizeTy(ctx.builder.getContext()), n)); - return tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, vptr, Align(sizeof(void*)))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + return ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, vptr, Align(sizeof(void*)))); } // --- generating various error checks --- @@ -1600,7 +1595,8 @@ static Value *emit_isconcrete(jl_codectx_t &ctx, Value *typ) { Value *isconcrete; isconcrete = ctx.builder.CreateConstInBoundsGEP1_32(getInt8Ty(ctx.builder.getContext()), emit_bitcast(ctx, decay_derived(ctx, typ), getInt8PtrTy(ctx.builder.getContext())), offsetof(jl_datatype_t, hash) + sizeof(((jl_datatype_t*)nullptr)->hash)); - isconcrete = tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(getInt8Ty(ctx.builder.getContext()), isconcrete, Align(1))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + isconcrete = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt8Ty(ctx.builder.getContext()), isconcrete, Align(1))); isconcrete = ctx.builder.CreateLShr(isconcrete, 1); isconcrete = ctx.builder.CreateTrunc(isconcrete, getInt1Ty(ctx.builder.getContext())); return isconcrete; @@ -1778,17 +1774,16 @@ static jl_cgval_t typed_load(jl_codectx_t &ctx, Value *ptr, Value *idx_0based, j else if (!alignment) alignment = julia_alignment(jltype); if (intcast && Order == AtomicOrdering::NotAtomic) { - emit_memcpy(ctx, intcast, ctx.tbaa().tbaa_stack, data, tbaa, nb, alignment); + emit_memcpy(ctx, intcast, jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_stack), data, jl_aliasinfo_t::fromTBAA(ctx, tbaa), nb, alignment); } else { LoadInst *load = ctx.builder.CreateAlignedLoad(elty, data, Align(alignment), false); load->setOrdering(Order); - if (aliasscope) - load->setMetadata("alias.scope", aliasscope); if (isboxed) maybe_mark_load_dereferenceable(load, true, jltype); - if (tbaa) - tbaa_decorate(tbaa, load); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.scope = MDNode::concatenate(aliasscope, ai.scope); + ai.decorateInst(load); instr = load; if (elty != realelty) instr = ctx.builder.CreateTrunc(instr, realelty); @@ -1912,20 +1907,18 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, auto *load = ctx.builder.CreateAlignedLoad(elty, ptr, Align(alignment)); if (isboxed) load->setOrdering(AtomicOrdering::Unordered); - if (aliasscope) - load->setMetadata("noalias", aliasscope); - if (tbaa) - tbaa_decorate(tbaa, load); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.noalias = MDNode::concatenate(aliasscope, ai.noalias); + ai.decorateInst(load); assert(realelty == elty); instr = load; } if (r) { StoreInst *store = ctx.builder.CreateAlignedStore(r, ptr, Align(alignment)); store->setOrdering(Order == AtomicOrdering::NotAtomic && isboxed ? AtomicOrdering::Release : Order); - if (aliasscope) - store->setMetadata("noalias", aliasscope); - if (tbaa) - tbaa_decorate(tbaa, store); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.noalias = MDNode::concatenate(aliasscope, ai.noalias); + ai.decorateInst(store); } else { assert(Order == AtomicOrdering::NotAtomic && !isboxed && rhs.typ == jltype); @@ -1942,10 +1935,9 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, auto *store = ctx.builder.CreateAtomicRMW(AtomicRMWInst::Xchg, ptr, r, Order); store->setAlignment(Align(alignment)); #endif - if (aliasscope) - store->setMetadata("noalias", aliasscope); - if (tbaa) - tbaa_decorate(tbaa, store); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.noalias = MDNode::concatenate(aliasscope, ai.noalias); + ai.decorateInst(store); instr = store; } else { @@ -1968,11 +1960,9 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, ctx.builder.SetInsertPoint(SkipBB); LoadInst *load = ctx.builder.CreateAlignedLoad(elty, ptr, Align(alignment)); load->setOrdering(FailOrder == AtomicOrdering::NotAtomic && isboxed ? AtomicOrdering::Monotonic : FailOrder); - if (aliasscope) - load->setMetadata("noalias", aliasscope); - if (tbaa) - tbaa_decorate(tbaa, load); - instr = load; + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.noalias = MDNode::concatenate(aliasscope, ai.noalias); + instr = ai.decorateInst(load); ctx.builder.CreateBr(DoneBB); ctx.builder.SetInsertPoint(DoneBB); Succ = ctx.builder.CreatePHI(getInt1Ty(ctx.builder.getContext()), 2); @@ -1999,11 +1989,9 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, else { // swap or modify LoadInst *Current = ctx.builder.CreateAlignedLoad(elty, ptr, Align(alignment)); Current->setOrdering(Order == AtomicOrdering::NotAtomic && !isboxed ? Order : AtomicOrdering::Monotonic); - if (aliasscope) - Current->setMetadata("noalias", aliasscope); - if (tbaa) - tbaa_decorate(tbaa, Current); - Compare = Current; + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.noalias = MDNode::concatenate(aliasscope, ai.noalias); + Compare = ai.decorateInst(Current); needloop = !isswapfield || Order != AtomicOrdering::NotAtomic; } BasicBlock *BB = NULL; @@ -2055,12 +2043,11 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, // modifyfield or replacefield assert(elty == realelty && !intcast); auto *load = ctx.builder.CreateAlignedLoad(elty, ptr, Align(alignment)); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.noalias = MDNode::concatenate(aliasscope, ai.noalias); + ai.decorateInst(load); if (isboxed) load->setOrdering(AtomicOrdering::Monotonic); - if (aliasscope) - load->setMetadata("noalias", aliasscope); - if (tbaa) - tbaa_decorate(tbaa, load); Value *first_ptr = nullptr; if (maybe_null_if_boxed && !ismodifyfield) first_ptr = isboxed ? load : extract_first_ptr(ctx, load); @@ -2076,10 +2063,9 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, ctx.builder.SetInsertPoint(XchgBB); if (r) { auto *store = ctx.builder.CreateAlignedStore(r, ptr, Align(alignment)); - if (aliasscope) - store->setMetadata("noalias", aliasscope); - if (tbaa) - tbaa_decorate(tbaa, store); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.noalias = MDNode::concatenate(aliasscope, ai.noalias); + ai.decorateInst(store); } else { assert(!isboxed && rhs.typ == jltype); @@ -2104,10 +2090,9 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, auto *store = ctx.builder.CreateAtomicCmpXchg(ptr, Compare, r, Order, FailOrder); store->setAlignment(Align(alignment)); #endif - if (aliasscope) - store->setMetadata("noalias", aliasscope); - if (tbaa) - tbaa_decorate(tbaa, store); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.noalias = MDNode::concatenate(aliasscope, ai.noalias); + ai.decorateInst(store); instr = ctx.builder.Insert(ExtractValueInst::Create(store, 0)); Success = ctx.builder.Insert(ExtractValueInst::Create(store, 1)); Done = Success; @@ -2333,7 +2318,8 @@ static bool emit_getfield_unknownidx(jl_codectx_t &ctx, idx0()); LoadInst *fld = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, fldptr, Align(sizeof(void*))); fld->setOrdering(AtomicOrdering::Unordered); - tbaa_decorate(strct.tbaa, fld); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, strct.tbaa); + ai.decorateInst(fld); maybe_mark_load_dereferenceable(fld, maybe_null, minimum_field_size, minimum_align); if (maybe_null) null_pointer_check(ctx, fld); @@ -2372,7 +2358,8 @@ static jl_cgval_t emit_unionload(jl_codectx_t &ctx, Value *addr, Value *ptindex, unsigned union_max, MDNode *tbaa_ptindex) { ++EmittedUnionLoads; - Instruction *tindex0 = tbaa_decorate(tbaa_ptindex, ctx.builder.CreateAlignedLoad(getInt8Ty(ctx.builder.getContext()), ptindex, Align(1))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa_ptindex); + Instruction *tindex0 = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt8Ty(ctx.builder.getContext()), ptindex, Align(1))); tindex0->setMetadata(LLVMContext::MD_range, MDNode::get(ctx.builder.getContext(), { ConstantAsMetadata::get(ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0)), ConstantAsMetadata::get(ConstantInt::get(getInt8Ty(ctx.builder.getContext()), union_max)) })); @@ -2383,7 +2370,8 @@ static jl_cgval_t emit_unionload(jl_codectx_t &ctx, Value *addr, Value *ptindex, AllocaInst *lv = emit_static_alloca(ctx, AT); if (al > 1) lv->setAlignment(Align(al)); - emit_memcpy(ctx, lv, tbaa, addr, tbaa, fsz, al); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + emit_memcpy(ctx, lv, ai, addr, ai, fsz, al); addr = lv; } return mark_julia_slot(fsz > 0 ? addr : nullptr, jfty, tindex, tbaa); @@ -2453,7 +2441,8 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st LoadInst *Load = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, maybe_bitcast(ctx, addr, ctx.types().T_pprjlvalue), Align(sizeof(void*))); Load->setOrdering(order <= jl_memory_order_notatomic ? AtomicOrdering::Unordered : get_llvm_atomic_order(order)); maybe_mark_load_dereferenceable(Load, maybe_null, jl_field_type(jt, idx)); - Value *fldv = tbaa_decorate(tbaa, Load); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + Value *fldv = ai.decorateInst(Load); if (maybe_null) null_pointer_check(ctx, fldv, nullcheck); return mark_julia_type(ctx, fldv, true, jfty); @@ -2695,7 +2684,8 @@ static Value *emit_arraylen_prim(jl_codectx_t &ctx, const jl_cgval_t &tinfo) MDBuilder MDB(ctx.builder.getContext()); auto rng = MDB.createRange(Constant::getNullValue(getSizeTy(ctx.builder.getContext())), ConstantInt::get(getSizeTy(ctx.builder.getContext()), arraytype_maxsize(tinfo.typ))); len->setMetadata(LLVMContext::MD_range, rng); - return tbaa_decorate(tbaa, len); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + return ai.decorateInst(len); } static Value *emit_arraylen(jl_codectx_t &ctx, const jl_cgval_t &tinfo) @@ -2766,7 +2756,8 @@ static Value *emit_arrayflags(jl_codectx_t &ctx, const jl_cgval_t &tinfo) ctx.types().T_jlarray, emit_bitcast(ctx, decay_derived(ctx, t), ctx.types().T_pjlarray), arrayflag_field); - return tbaa_decorate(ctx.tbaa().tbaa_arrayflags, ctx.builder.CreateAlignedLoad(getInt16Ty(ctx.builder.getContext()), addr, Align(sizeof(int16_t)))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_arrayflags); + return ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt16Ty(ctx.builder.getContext()), addr, Align(sizeof(int16_t)))); } static Value *emit_arrayndims(jl_codectx_t &ctx, const jl_cgval_t &ary) @@ -2787,7 +2778,8 @@ static Value *emit_arrayelsize(jl_codectx_t &ctx, const jl_cgval_t &tinfo) Value *addr = ctx.builder.CreateStructGEP(ctx.types().T_jlarray, emit_bitcast(ctx, decay_derived(ctx, t), ctx.types().T_pjlarray), elsize_field); - return tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(getInt16Ty(ctx.builder.getContext()), addr, Align(sizeof(int16_t)))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + return ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt16Ty(ctx.builder.getContext()), addr, Align(sizeof(int16_t)))); } static Value *emit_arrayoffset(jl_codectx_t &ctx, const jl_cgval_t &tinfo, int nd) @@ -2802,7 +2794,8 @@ static Value *emit_arrayoffset(jl_codectx_t &ctx, const jl_cgval_t &tinfo, int n ctx.types().T_jlarray, emit_bitcast(ctx, decay_derived(ctx, t), ctx.types().T_pjlarray), offset_field); - return tbaa_decorate(ctx.tbaa().tbaa_arrayoffset, ctx.builder.CreateAlignedLoad(getInt32Ty(ctx.builder.getContext()), addr, Align(sizeof(int32_t)))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_arrayoffset); + return ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt32Ty(ctx.builder.getContext()), addr, Align(sizeof(int32_t)))); } // Returns the size of the array represented by `tinfo` for the given dimension `dim` if @@ -2915,8 +2908,9 @@ static Value *emit_allocobj(jl_codectx_t &ctx, size_t static_size, Value *jt); static void init_bits_value(jl_codectx_t &ctx, Value *newv, Value *v, MDNode *tbaa, unsigned alignment = sizeof(void*)) // min alignment in julia's gc is pointer-aligned { + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); // newv should already be tagged - tbaa_decorate(tbaa, ctx.builder.CreateAlignedStore(v, emit_bitcast(ctx, newv, + ai.decorateInst(ctx.builder.CreateAlignedStore(v, emit_bitcast(ctx, newv, PointerType::get(v->getType(), 0)), Align(alignment))); } @@ -2924,7 +2918,7 @@ static void init_bits_cgval(jl_codectx_t &ctx, Value *newv, const jl_cgval_t& v, { // newv should already be tagged if (v.ispointer()) { - emit_memcpy(ctx, newv, tbaa, v, jl_datatype_size(v.typ), sizeof(void*)); + emit_memcpy(ctx, newv, jl_aliasinfo_t::fromTBAA(ctx, tbaa), v, jl_datatype_size(v.typ), sizeof(void*)); } else { init_bits_value(ctx, newv, v.V, tbaa); @@ -3032,7 +3026,8 @@ static Value *load_i8box(jl_codectx_t &ctx, Value *v, jl_datatype_t *ty) GlobalVariable *gv = prepare_global_in(jl_Module, jvar); Value *idx[] = {ConstantInt::get(getInt32Ty(ctx.builder.getContext()), 0), ctx.builder.CreateZExt(v, getInt32Ty(ctx.builder.getContext()))}; auto slot = ctx.builder.CreateInBoundsGEP(gv->getValueType(), gv, idx); - return tbaa_decorate(ctx.tbaa().tbaa_const, maybe_mark_load_dereferenceable( + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + return ai.decorateInst(maybe_mark_load_dereferenceable( ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, slot, Align(sizeof(void*))), false, (jl_value_t*)ty)); } @@ -3387,7 +3382,8 @@ static void emit_unionmove(jl_codectx_t &ctx, Value *dest, MDNode *tbaa_dst, con // select copy dest -> dest to simulate an undef value / conditional copy // if (skip) src_ptr = ctx.builder.CreateSelect(skip, dest, src_ptr); auto f = [&] { - (void)emit_memcpy(ctx, dest, tbaa_dst, src_ptr, src.tbaa, nb, alignment, isVolatile); + (void)emit_memcpy(ctx, dest, jl_aliasinfo_t::fromTBAA(ctx, tbaa_dst), src_ptr, + jl_aliasinfo_t::fromTBAA(ctx, src.tbaa), nb, alignment, isVolatile); return nullptr; }; if (skip) @@ -3423,8 +3419,8 @@ static void emit_unionmove(jl_codectx_t &ctx, Value *dest, MDNode *tbaa_dst, con ctx.builder.CreateUnreachable(); return; } else { - emit_memcpy(ctx, dest, tbaa_dst, src_ptr, - src.tbaa, nb, alignment, isVolatile); + emit_memcpy(ctx, dest, jl_aliasinfo_t::fromTBAA(ctx, tbaa_dst), src_ptr, + jl_aliasinfo_t::fromTBAA(ctx, src.tbaa), nb, alignment, isVolatile); } } ctx.builder.CreateBr(postBB); @@ -3449,7 +3445,7 @@ static void emit_unionmove(jl_codectx_t &ctx, Value *dest, MDNode *tbaa_dst, con auto f = [&] { Value *datatype = emit_typeof_boxed(ctx, src); Value *copy_bytes = emit_datatype_size(ctx, datatype); - emit_memcpy(ctx, dest, tbaa_dst, src, copy_bytes, /*TODO: min-align*/1, isVolatile); + emit_memcpy(ctx, dest, jl_aliasinfo_t::fromTBAA(ctx, tbaa_dst), src, copy_bytes, /*TODO: min-align*/1, isVolatile); return nullptr; }; if (skip) @@ -3633,7 +3629,8 @@ static jl_cgval_t emit_setfield(jl_codectx_t &ctx, } Value *tindex = compute_tindex_unboxed(ctx, rhs_union, jfty); tindex = ctx.builder.CreateNUWSub(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 1)); - tbaa_decorate(ctx.tbaa().tbaa_unionselbyte, ctx.builder.CreateAlignedStore(tindex, ptindex, Align(1))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_unionselbyte); + ai.decorateInst(ctx.builder.CreateAlignedStore(tindex, ptindex, Align(1))); // copy data if (!rhs.isghost) { emit_unionmove(ctx, addr, strct.tbaa, rhs, nullptr); @@ -3707,7 +3704,7 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg else { strct = emit_static_alloca(ctx, lt); if (tracked.count) - undef_derived_strct(ctx.builder, strct, sty, ctx.tbaa().tbaa_stack); + undef_derived_strct(ctx, strct, sty, ctx.tbaa().tbaa_stack); } for (unsigned i = 0; i < na; i++) { @@ -3761,10 +3758,12 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg Value *fval = NULL; if (jl_field_isptr(sty, i)) { fval = boxed(ctx, fval_info, field_promotable); - if (!init_as_value) - cast(tbaa_decorate(ctx.tbaa().tbaa_stack, - ctx.builder.CreateAlignedStore(fval, dest, Align(jl_field_align(sty, i))))) - ->setOrdering(AtomicOrdering::Unordered); + if (!init_as_value) { + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_stack); + StoreInst *SI = cast(ai.decorateInst( + ctx.builder.CreateAlignedStore(fval, dest, Align(jl_field_align(sty, i))))); + SI->setOrdering(AtomicOrdering::Unordered); + } } else if (jl_is_uniontype(jtype)) { // compute tindex from rhs @@ -3792,7 +3791,8 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg unsigned i = 0; for (; i < fsz / al; i++) { Value *fldp = ctx.builder.CreateConstInBoundsGEP1_32(ET, lv, i); - Value *fldv = tbaa_decorate(ctx.tbaa().tbaa_stack, ctx.builder.CreateAlignedLoad(ET, fldp, Align(al))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_stack); + Value *fldv = ai.decorateInst(ctx.builder.CreateAlignedLoad(ET, fldp, Align(al))); strct = ctx.builder.CreateInsertValue(strct, fldv, makeArrayRef(llvm_idx + i)); } // emit remaining bytes up to tindex @@ -3801,7 +3801,8 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg staddr = ctx.builder.CreateBitCast(staddr, getInt8PtrTy(ctx.builder.getContext())); for (; i < ptindex - llvm_idx; i++) { Value *fldp = ctx.builder.CreateConstInBoundsGEP1_32(getInt8Ty(ctx.builder.getContext()), staddr, i); - Value *fldv = tbaa_decorate(ctx.tbaa().tbaa_stack, ctx.builder.CreateAlignedLoad(getInt8Ty(ctx.builder.getContext()), fldp, Align(1))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_stack); + Value *fldv = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt8Ty(ctx.builder.getContext()), fldp, Align(1))); strct = ctx.builder.CreateInsertValue(strct, fldv, makeArrayRef(llvm_idx + i)); } } @@ -3813,7 +3814,8 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg } else { Value *ptindex = emit_struct_gep(ctx, lt, strct, offs + fsz); - tbaa_decorate(ctx.tbaa().tbaa_unionselbyte, ctx.builder.CreateAlignedStore(tindex, ptindex, Align(1))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_unionselbyte); + ai.decorateInst(ctx.builder.CreateAlignedStore(tindex, ptindex, Align(1))); if (!rhs_union.isghost) emit_unionmove(ctx, dest, ctx.tbaa().tbaa_stack, fval_info, nullptr); } @@ -3850,11 +3852,13 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg unsigned llvm_idx = convert_struct_offset(ctx, cast(lt), offs + fsz); if (init_as_value) strct = ctx.builder.CreateInsertValue(strct, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0), makeArrayRef(llvm_idx)); - else - tbaa_decorate(ctx.tbaa().tbaa_unionselbyte, ctx.builder.CreateAlignedStore( + else { + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_unionselbyte); + ai.decorateInst(ctx.builder.CreateAlignedStore( ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0), ctx.builder.CreateConstInBoundsGEP2_32(lt, strct, 0, llvm_idx), Align(1))); + } } } if (type_is_ghost(lt)) @@ -3874,10 +3878,11 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg literal_pointer_val(ctx, (jl_value_t*)ty)); jl_cgval_t strctinfo = mark_julia_type(ctx, strct, true, ty); strct = decay_derived(ctx, strct); - undef_derived_strct(ctx.builder, strct, sty, strctinfo.tbaa); + undef_derived_strct(ctx, strct, sty, strctinfo.tbaa); for (size_t i = nargs; i < nf; i++) { if (!jl_field_isptr(sty, i) && jl_is_uniontype(jl_field_type(sty, i))) { - tbaa_decorate(ctx.tbaa().tbaa_unionselbyte, ctx.builder.CreateAlignedStore( + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_unionselbyte); + ai.decorateInst(ctx.builder.CreateAlignedStore( ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0), ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), emit_bitcast(ctx, strct, getInt8PtrTy(ctx.builder.getContext())), ConstantInt::get(getSizeTy(ctx.builder.getContext()), jl_field_offset(sty, i) + jl_field_size(sty, i) - 1)), diff --git a/src/codegen.cpp b/src/codegen.cpp index 1e1205a0d17ee8..f4dc4f9f5501ca 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1715,7 +1715,6 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, const static Value *literal_pointer_val(jl_codectx_t &ctx, jl_value_t *p); static GlobalVariable *prepare_global_in(Module *M, GlobalVariable *G); -Instruction *tbaa_decorate(MDNode *md, Instruction *inst); static GlobalVariable *prepare_global_in(Module *M, JuliaVariable *G) { @@ -1784,20 +1783,21 @@ static AllocaInst *emit_static_alloca(jl_codectx_t &ctx, Type *lty) return new AllocaInst(lty, ctx.topalloca->getModule()->getDataLayout().getAllocaAddrSpace(), "", /*InsertBefore=*/ctx.topalloca); } -static void undef_derived_strct(IRBuilder<> &irbuilder, Value *ptr, jl_datatype_t *sty, MDNode *tbaa) +static void undef_derived_strct(jl_codectx_t &ctx, Value *ptr, jl_datatype_t *sty, MDNode *tbaa) { assert(ptr->getType()->getPointerAddressSpace() != AddressSpace::Tracked); size_t first_offset = sty->layout->nfields ? jl_field_offset(sty, 0) : 0; if (first_offset != 0) - irbuilder.CreateMemSet(ptr, ConstantInt::get(getInt8Ty(irbuilder.getContext()), 0), first_offset, MaybeAlign(0)); + ctx.builder.CreateMemSet(ptr, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0), first_offset, MaybeAlign(0)); size_t i, np = sty->layout->npointers; if (np == 0) return; - auto T_prjlvalue = JuliaType::get_prjlvalue_ty(irbuilder.getContext()); - ptr = irbuilder.CreateBitCast(ptr, T_prjlvalue->getPointerTo(ptr->getType()->getPointerAddressSpace())); + auto T_prjlvalue = JuliaType::get_prjlvalue_ty(ctx.builder.getContext()); + ptr = ctx.builder.CreateBitCast(ptr, T_prjlvalue->getPointerTo(ptr->getType()->getPointerAddressSpace())); for (i = 0; i < np; i++) { - Value *fld = irbuilder.CreateConstInBoundsGEP1_32(T_prjlvalue, ptr, jl_ptr_offset(sty, i)); - tbaa_decorate(tbaa, irbuilder.CreateStore(Constant::getNullValue(T_prjlvalue), fld)); + Value *fld = ctx.builder.CreateConstInBoundsGEP1_32(T_prjlvalue, ptr, jl_ptr_offset(sty, i)); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.decorateInst(ctx.builder.CreateStore(Constant::getNullValue(T_prjlvalue), fld)); } } @@ -2788,7 +2788,8 @@ static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t * return mark_julia_const(ctx, v); LoadInst *v = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*))); v->setOrdering(order); - tbaa_decorate(ctx.tbaa().tbaa_binding, v); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_binding); + ai.decorateInst(v); jl_value_t *ty = jl_atomic_load_relaxed(&bnd->ty); return mark_julia_type(ctx, v, true, ty); } @@ -2809,7 +2810,8 @@ static bool emit_globalset(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *sym, c if (ty && jl_subtype(rval_info.typ, ty)) { // TODO: use typeassert here instead StoreInst *v = ctx.builder.CreateAlignedStore(rval, julia_binding_pvalue(ctx, bp), Align(sizeof(void*))); v->setOrdering(Order); - tbaa_decorate(ctx.tbaa().tbaa_binding, v); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_binding); + ai.decorateInst(v); emit_write_barrier(ctx, bp, rval); return true; } @@ -2950,18 +2952,22 @@ static Value *emit_bits_compare(jl_codectx_t &ctx, jl_cgval_t arg1, jl_cgval_t a ctx.builder.CreateBitCast(varg2, getInt8PtrTy(ctx.builder.getContext())), ConstantInt::get(getSizeTy(ctx.builder.getContext()), sz) }, ArrayRef(&OpBundle, nroots ? 1 : 0)); - MDNode *tbaa = nullptr; - if (!arg1.tbaa) { - tbaa = arg2.tbaa; - } - else if (!arg2.tbaa) { - tbaa = arg1.tbaa; - } - else { - tbaa = MDNode::getMostGenericTBAA(arg1.tbaa, arg2.tbaa); + + if (arg1.tbaa || arg2.tbaa) { + jl_aliasinfo_t ai; + if (!arg1.tbaa) { + ai = jl_aliasinfo_t::fromTBAA(ctx, arg2.tbaa); + } + else if (!arg2.tbaa) { + ai = jl_aliasinfo_t::fromTBAA(ctx, arg1.tbaa); + } + else { + jl_aliasinfo_t arg1_ai = jl_aliasinfo_t::fromTBAA(ctx, arg1.tbaa); + jl_aliasinfo_t arg2_ai = jl_aliasinfo_t::fromTBAA(ctx, arg2.tbaa); + ai = arg1_ai.merge(arg2_ai); + } + ai.decorateInst(answer); } - if (tbaa) - tbaa_decorate(tbaa, answer); return ctx.builder.CreateICmpEQ(answer, ConstantInt::get(getInt32Ty(ctx.builder.getContext()), 0)); } else { @@ -3511,7 +3517,8 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, emit_bitcast(ctx, decay_derived(ctx, aryv), ctx.types().T_pprjlvalue), jl_array_data_owner_offset(nd) / sizeof(jl_value_t*)), Align(sizeof(void*))); - tbaa_decorate(ctx.tbaa().tbaa_const, maybe_mark_load_dereferenceable(own_ptr, false, (jl_value_t*)jl_array_any_type)); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + ai.decorateInst(maybe_mark_load_dereferenceable(own_ptr, false, (jl_value_t*)jl_array_any_type)); } else { own_ptr = ctx.builder.CreateCall( @@ -3548,7 +3555,8 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, ptindex = emit_bitcast(ctx, ptindex, getInt8PtrTy(ctx.builder.getContext())); ptindex = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), ptindex, offset); ptindex = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), ptindex, idx); - tbaa_decorate(ctx.tbaa().tbaa_arrayselbyte, ctx.builder.CreateStore(tindex, ptindex)); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_arrayselbyte); + ai.decorateInst(ctx.builder.CreateStore(tindex, ptindex)); if (elsz > 0 && (!jl_is_datatype(val.typ) || jl_datatype_size(val.typ) > 0)) { // copy data (if any) emit_unionmove(ctx, data, ctx.tbaa().tbaa_arraybuf, val, nullptr); @@ -3646,7 +3654,8 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, idx = ctx.builder.CreateAdd(idx, ConstantInt::get(getSizeTy(ctx.builder.getContext()), ctx.nReqArgs)); Instruction *v = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, ctx.argArray, idx), Align(sizeof(void*))); // if we know the result type of this load, we will mark that information here too - tbaa_decorate(ctx.tbaa().tbaa_value, maybe_mark_load_dereferenceable(v, false, rt)); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_value); + ai.decorateInst(maybe_mark_load_dereferenceable(v, false, rt)); *ret = mark_julia_type(ctx, v, /*boxed*/ true, rt); return true; } @@ -3809,7 +3818,8 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, emit_typecheck(ctx, argv[3], (jl_value_t*)jl_bool_type, "fieldtype"); emit_bounds_check(ctx, typ, (jl_value_t*)jl_datatype_type, idx, types_len, boundscheck); Value *fieldtyp_p = ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, decay_derived(ctx, emit_bitcast(ctx, types_svec, ctx.types().T_pprjlvalue)), idx); - Value *fieldtyp = tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, fieldtyp_p, Align(sizeof(void*)))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + Value *fieldtyp = ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, fieldtyp_p, Align(sizeof(void*)))); *ret = mark_julia_type(ctx, fieldtyp, true, (jl_value_t*)jl_type_type); return true; } @@ -3834,7 +3844,8 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, } // String and SimpleVector's length fields have the same layout auto ptr = emit_bitcast(ctx, boxed(ctx, obj), getSizePtrTy(ctx.builder.getContext())); - Value *len = tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(getSizeTy(ctx.builder.getContext()), ptr, Align(sizeof(size_t)))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + Value *len = ai.decorateInst(ctx.builder.CreateAlignedLoad(getSizeTy(ctx.builder.getContext()), ptr, Align(sizeof(size_t)))); MDBuilder MDB(ctx.builder.getContext()); if (sty == jl_simplevector_type) { auto rng = MDB.createRange( @@ -3965,7 +3976,8 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, Value *addr = ctx.builder.CreateConstInBoundsGEP1_32(ctx.types().T_prjlvalue, ptr, offs); // emit this using the same type as emit_getfield_knownidx // so that LLVM may be able to load-load forward them and fold the result - fldv = tbaa_decorate(tbaa, ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, addr, Align(sizeof(size_t)))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + fldv = ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, addr, Align(sizeof(size_t)))); cast(fldv)->setOrdering(order <= jl_memory_order_notatomic ? AtomicOrdering::Unordered : get_llvm_atomic_order(order)); } else { @@ -4463,8 +4475,10 @@ static jl_cgval_t emit_checked_var(jl_codectx_t &ctx, Value *bp, jl_sym_t *name, if (isvol) v->setVolatile(true); v->setOrdering(AtomicOrdering::Unordered); - if (tbaa) - tbaa_decorate(tbaa, v); + if (tbaa) { + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.decorateInst(v); + } undef_var_error_ifnot(ctx, ctx.builder.CreateIsNotNull(v), name); return mark_julia_type(ctx, v, true, jl_any_type); } @@ -4482,7 +4496,8 @@ static jl_cgval_t emit_sparam(jl_codectx_t &ctx, size_t i) ctx.types().T_prjlvalue, ctx.spvals_ptr, i + sizeof(jl_svec_t) / sizeof(jl_value_t*)); - Value *sp = tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*)))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + Value *sp = ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*)))); Value *isnull = ctx.builder.CreateICmpNE(emit_typeof(ctx, sp, false), track_pjlvalue(ctx, literal_pointer_val(ctx, (jl_value_t*)jl_tvar_type))); jl_unionall_t *sparam = (jl_unionall_t*)ctx.linfo->def.method->sig; @@ -4537,7 +4552,8 @@ static jl_cgval_t emit_isdefined(jl_codectx_t &ctx, jl_value_t *sym) ctx.types().T_prjlvalue, ctx.spvals_ptr, i + sizeof(jl_svec_t) / sizeof(jl_value_t*)); - Value *sp = tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*)))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + Value *sp = ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*)))); isnull = ctx.builder.CreateICmpNE(emit_typeof(ctx, sp, false), track_pjlvalue(ctx, literal_pointer_val(ctx, (jl_value_t*)jl_tvar_type))); } @@ -4560,7 +4576,8 @@ static jl_cgval_t emit_isdefined(jl_codectx_t &ctx, jl_value_t *sym) Value *bp = julia_binding_gv(ctx, bnd); bp = julia_binding_pvalue(ctx, bp); LoadInst *v = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*))); - tbaa_decorate(ctx.tbaa().tbaa_binding, v); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_binding); + ai.decorateInst(v); v->setOrdering(AtomicOrdering::Unordered); isnull = ctx.builder.CreateICmpNE(v, Constant::getNullValue(ctx.types().T_prjlvalue)); } @@ -4601,7 +4618,7 @@ static jl_cgval_t emit_varinfo(jl_codectx_t &ctx, jl_varinfo_t &vi, jl_sym_t *va else { const DataLayout &DL = jl_Module->getDataLayout(); uint64_t sz = DL.getTypeStoreSize(T); - emit_memcpy(ctx, ssaslot, ctx.tbaa().tbaa_stack, vi.value, sz, ssaslot->getAlign().value()); + emit_memcpy(ctx, ssaslot, jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_stack), vi.value, sz, ssaslot->getAlign().value()); } Value *tindex = NULL; if (vi.pTIndex) @@ -4691,7 +4708,8 @@ static void emit_vi_assignment_unboxed(jl_codectx_t &ctx, jl_varinfo_t &vi, Valu Type *dest_ty = store_ty->getPointerTo(); if (dest_ty != dest->getType()) dest = emit_bitcast(ctx, dest, dest_ty); - tbaa_decorate(ctx.tbaa().tbaa_stack, ctx.builder.CreateStore( + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_stack); + ai.decorateInst(ctx.builder.CreateStore( emit_unbox(ctx, store_ty, rval_info, rval_info.typ), dest, vi.isVolatile)); @@ -4707,7 +4725,7 @@ static void emit_vi_assignment_unboxed(jl_codectx_t &ctx, jl_varinfo_t &vi, Valu // This check should probably mostly catch the relevant situations. if (vi.value.V != rval_info.V) { Value *copy_bytes = ConstantInt::get(getInt32Ty(ctx.builder.getContext()), jl_datatype_size(vi.value.typ)); - emit_memcpy(ctx, vi.value.V, ctx.tbaa().tbaa_stack, rval_info, copy_bytes, + emit_memcpy(ctx, vi.value.V, jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_stack), rval_info, copy_bytes, julia_alignment(rval_info.typ), vi.isVolatile); } } @@ -5419,11 +5437,9 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ std::tie(F, specF) = get_oc_function(ctx, (jl_method_t*)source.constant, env_t, argt_typ, ub.constant); if (F) { jl_cgval_t jlcall_ptr = mark_julia_type(ctx, F, false, jl_voidpointer_type); - jl_cgval_t world_age = mark_julia_type(ctx, - tbaa_decorate(ctx.tbaa().tbaa_gcframe, - ctx.builder.CreateAlignedLoad(getSizeTy(ctx.builder.getContext()), get_last_age_field(ctx), Align(sizeof(size_t)))), - false, - jl_long_type); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_gcframe); + Instruction *I = ctx.builder.CreateAlignedLoad(getSizeTy(ctx.builder.getContext()), get_last_age_field(ctx), Align(sizeof(size_t))); + jl_cgval_t world_age = mark_julia_type(ctx, ai.decorateInst(I), false, jl_long_type); jl_cgval_t fptr; if (specF) fptr = mark_julia_type(ctx, specF, false, jl_voidpointer_type); @@ -5712,7 +5728,8 @@ static void emit_cfunc_invalidate( root1 = ctx.builder.CreateConstInBoundsGEP2_32(get_returnroots_type(ctx, return_roots), root1, 0, 0); ctx.builder.CreateStore(gf_ret, root1); } - emit_memcpy(ctx, &*gf_thunk->arg_begin(), nullptr, gf_ret, nullptr, jl_datatype_size(rettype), julia_alignment(rettype)); + emit_memcpy(ctx, &*gf_thunk->arg_begin(), jl_aliasinfo_t::fromTBAA(ctx, nullptr), gf_ret, + jl_aliasinfo_t::fromTBAA(ctx, nullptr), jl_datatype_size(rettype), julia_alignment(rettype)); ctx.builder.CreateRetVoid(); break; } @@ -5882,7 +5899,8 @@ static Function* gen_cfun_wrapper( allocate_gc_frame(ctx, b0, true); Value *world_age_field = get_last_age_field(ctx); - Value *last_age = tbaa_decorate(ctx.tbaa().tbaa_gcframe, + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_gcframe); + Value *last_age = ai.decorateInst( ctx.builder.CreateAlignedLoad(getSizeTy(ctx.builder.getContext()), world_age_field, Align(sizeof(size_t)))); Value *world_v = ctx.builder.CreateAlignedLoad(getSizeTy(ctx.builder.getContext()), @@ -6438,13 +6456,14 @@ static jl_cgval_t emit_cfunction(jl_codectx_t &ctx, jl_value_t *output_type, con literal_pointer_val(ctx, (jl_value_t*)output_type)); Value *derived_strct = emit_bitcast(ctx, decay_derived(ctx, strct), getSizePtrTy(ctx.builder.getContext())); MDNode *tbaa = best_tbaa(ctx.tbaa(), output_type); - tbaa_decorate(tbaa, ctx.builder.CreateStore(F, derived_strct)); - tbaa_decorate(tbaa, ctx.builder.CreateStore( + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.decorateInst(ctx.builder.CreateStore(F, derived_strct)); + ai.decorateInst(ctx.builder.CreateStore( ctx.builder.CreatePtrToInt(literal_pointer_val(ctx, fexpr_rt.constant), getSizeTy(ctx.builder.getContext())), ctx.builder.CreateConstInBoundsGEP1_32(getSizeTy(ctx.builder.getContext()), derived_strct, 1))); - tbaa_decorate(tbaa, ctx.builder.CreateStore(Constant::getNullValue(getSizeTy(ctx.builder.getContext())), + ai.decorateInst(ctx.builder.CreateStore(Constant::getNullValue(getSizeTy(ctx.builder.getContext())), ctx.builder.CreateConstInBoundsGEP1_32(getSizeTy(ctx.builder.getContext()), derived_strct, 2))); - tbaa_decorate(tbaa, ctx.builder.CreateStore(Constant::getNullValue(getSizeTy(ctx.builder.getContext())), + ai.decorateInst(ctx.builder.CreateStore(Constant::getNullValue(getSizeTy(ctx.builder.getContext())), ctx.builder.CreateConstInBoundsGEP1_32(getSizeTy(ctx.builder.getContext()), derived_strct, 3))); F = strct; } @@ -6578,7 +6597,8 @@ static Function *gen_invoke_wrapper(jl_method_instance_t *lam, jl_value_t *jlret } else { Value *argPtr = ctx.builder.CreateConstInBoundsGEP1_32(ctx.types().T_prjlvalue, argArray, i - 1); - theArg = tbaa_decorate(ctx.tbaa().tbaa_const, maybe_mark_load_dereferenceable( + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + theArg = ai.decorateInst(maybe_mark_load_dereferenceable( ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, argPtr, Align(sizeof(void*))), false, ty)); @@ -7221,7 +7241,8 @@ static jl_llvm_functions_t Value *last_age = NULL; Value *world_age_field = get_last_age_field(ctx); if (toplevel || ctx.is_opaque_closure) { - last_age = tbaa_decorate(ctx.tbaa().tbaa_gcframe, ctx.builder.CreateAlignedLoad( + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_gcframe); + last_age = ai.decorateInst(ctx.builder.CreateAlignedLoad( getSizeTy(ctx.builder.getContext()), world_age_field, Align(sizeof(size_t)))); } @@ -7462,7 +7483,8 @@ static jl_llvm_functions_t } else { Value *argPtr = ctx.builder.CreateConstInBoundsGEP1_32(ctx.types().T_prjlvalue, argArray, i - 1); - Value *load = tbaa_decorate(ctx.tbaa().tbaa_const, maybe_mark_load_dereferenceable( + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + Value *load = ai.decorateInst(maybe_mark_load_dereferenceable( ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, argPtr, Align(sizeof(void*))), false, vi.value.typ)); theArg = mark_julia_type(ctx, load, true, vi.value.typ); @@ -7949,8 +7971,8 @@ static jl_llvm_functions_t } if (returninfo.cc == jl_returninfo_t::SRet) { assert(jl_is_concrete_type(jlrettype)); - emit_memcpy(ctx, sret, nullptr, retvalinfo, jl_datatype_size(jlrettype), - julia_alignment(jlrettype)); + emit_memcpy(ctx, sret, jl_aliasinfo_t::fromTBAA(ctx, nullptr), retvalinfo, + jl_datatype_size(jlrettype), julia_alignment(jlrettype)); } else { // must be jl_returninfo_t::Union emit_unionmove(ctx, sret, nullptr, retvalinfo, /*skip*/isboxed_union); diff --git a/src/intrinsics.cpp b/src/intrinsics.cpp index 38d923cb5a99e0..b822907e635246 100644 --- a/src/intrinsics.cpp +++ b/src/intrinsics.cpp @@ -397,7 +397,8 @@ static Value *emit_unbox(jl_codectx_t &ctx, Type *to, const jl_cgval_t &x, jl_va Value *p = x.constant ? literal_pointer_val(ctx, x.constant) : x.V; if (jt == (jl_value_t*)jl_bool_type || to->isIntegerTy(1)) { - Instruction *unbox_load = tbaa_decorate(x.tbaa, ctx.builder.CreateLoad(getInt8Ty(ctx.builder.getContext()), maybe_bitcast(ctx, p, getInt8PtrTy(ctx.builder.getContext())))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, x.tbaa); + Instruction *unbox_load = ai.decorateInst(ctx.builder.CreateLoad(getInt8Ty(ctx.builder.getContext()), maybe_bitcast(ctx, p, getInt8PtrTy(ctx.builder.getContext())))); if (jt == (jl_value_t*)jl_bool_type) unbox_load->setMetadata(LLVMContext::MD_range, MDNode::get(ctx.builder.getContext(), { ConstantAsMetadata::get(ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0)), @@ -425,12 +426,14 @@ static Value *emit_unbox(jl_codectx_t &ctx, Type *to, const jl_cgval_t &x, jl_va (to->isFloatingPointTy() || to->isIntegerTy() || to->isPointerTy()) && DL.getTypeSizeInBits(AllocType) == DL.getTypeSizeInBits(to)) { Instruction *load = ctx.builder.CreateAlignedLoad(AllocType, p, Align(alignment)); - return emit_unboxed_coercion(ctx, to, tbaa_decorate(x.tbaa, load)); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, x.tbaa); + return emit_unboxed_coercion(ctx, to, ai.decorateInst(load)); } } p = maybe_bitcast(ctx, p, ptype); Instruction *load = ctx.builder.CreateAlignedLoad(to, p, Align(alignment)); - return tbaa_decorate(x.tbaa, load); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, x.tbaa); + return ai.decorateInst(load); } // emit code to store a raw value into a destination @@ -459,12 +462,13 @@ static void emit_unbox_store(jl_codectx_t &ctx, const jl_cgval_t &x, Value *dest dest = emit_bitcast(ctx, dest, dest_ty); StoreInst *store = ctx.builder.CreateAlignedStore(unboxed, dest, Align(alignment)); store->setVolatile(isVolatile); - tbaa_decorate(tbaa_dest, store); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa_dest); + ai.decorateInst(store); return; } Value *src = data_pointer(ctx, x); - emit_memcpy(ctx, dest, tbaa_dest, src, x.tbaa, jl_datatype_size(x.typ), alignment, isVolatile); + emit_memcpy(ctx, dest, jl_aliasinfo_t::fromTBAA(ctx, tbaa_dest), src, jl_aliasinfo_t::fromTBAA(ctx, x.tbaa), jl_datatype_size(x.typ), alignment, isVolatile); } static jl_datatype_t *staticeval_bitstype(const jl_cgval_t &targ) @@ -546,7 +550,8 @@ static jl_cgval_t generic_bitcast(jl_codectx_t &ctx, const jl_cgval_t *argv) if (isboxed) vxt = llvmt; auto storage_type = vxt->isIntegerTy(1) ? getInt8Ty(ctx.builder.getContext()) : vxt; - vx = tbaa_decorate(v.tbaa, ctx.builder.CreateLoad( + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, v.tbaa); + vx = ai.decorateInst(ctx.builder.CreateLoad( storage_type, emit_bitcast(ctx, data_pointer(ctx, v), storage_type->getPointerTo()))); @@ -662,7 +667,8 @@ static jl_cgval_t emit_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) if (ety == (jl_value_t*)jl_any_type) { Value *thePtr = emit_unbox(ctx, ctx.types().T_pprjlvalue, e, e.typ); LoadInst *load = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, thePtr, im1), Align(align_nb)); - tbaa_decorate(ctx.tbaa().tbaa_data, load); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_data); + ai.decorateInst(load); return mark_julia_type(ctx, load, true, ety); } else if (!jl_isbits(ety)) { @@ -675,7 +681,7 @@ static jl_cgval_t emit_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) Value *thePtr = emit_unbox(ctx, getInt8PtrTy(ctx.builder.getContext()), e, e.typ); thePtr = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), emit_bitcast(ctx, thePtr, getInt8PtrTy(ctx.builder.getContext())), im1); MDNode *tbaa = best_tbaa(ctx.tbaa(), ety); - emit_memcpy(ctx, strct, tbaa, thePtr, nullptr, size, 1); + emit_memcpy(ctx, strct, jl_aliasinfo_t::fromTBAA(ctx, tbaa), thePtr, jl_aliasinfo_t::fromTBAA(ctx, nullptr), size, 1); return mark_julia_type(ctx, strct, true, ety); } else { @@ -735,14 +741,15 @@ static jl_cgval_t emit_pointerset(jl_codectx_t &ctx, jl_cgval_t *argv) Instruction *store = ctx.builder.CreateAlignedStore( ctx.builder.CreatePtrToInt(emit_pointer_from_objref(ctx, boxed(ctx, x)), getSizeTy(ctx.builder.getContext())), ctx.builder.CreateInBoundsGEP(getSizeTy(ctx.builder.getContext()), thePtr, im1), Align(align_nb)); - tbaa_decorate(ctx.tbaa().tbaa_data, store); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_data); + ai.decorateInst(store); } else if (!jl_isbits(ety)) { thePtr = emit_unbox(ctx, getInt8PtrTy(ctx.builder.getContext()), e, e.typ); uint64_t size = jl_datatype_size(ety); im1 = ctx.builder.CreateMul(im1, ConstantInt::get(getSizeTy(ctx.builder.getContext()), LLT_ALIGN(size, jl_datatype_align(ety)))); - emit_memcpy(ctx, ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), thePtr, im1), nullptr, x, size, align_nb); + emit_memcpy(ctx, ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), thePtr, im1), jl_aliasinfo_t::fromTBAA(ctx, nullptr), x, size, align_nb); } else { bool isboxed; @@ -793,7 +800,8 @@ static jl_cgval_t emit_atomic_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) if (ety == (jl_value_t*)jl_any_type) { Value *thePtr = emit_unbox(ctx, ctx.types().T_pprjlvalue, e, e.typ); LoadInst *load = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, thePtr, Align(sizeof(jl_value_t*))); - tbaa_decorate(ctx.tbaa().tbaa_data, load); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_data); + ai.decorateInst(load); load->setOrdering(llvm_order); return mark_julia_type(ctx, load, true, ety); } @@ -819,11 +827,12 @@ static jl_cgval_t emit_atomic_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) thePtr = emit_bitcast(ctx, thePtr, loadT->getPointerTo()); MDNode *tbaa = best_tbaa(ctx.tbaa(), ety); LoadInst *load = ctx.builder.CreateAlignedLoad(loadT, thePtr, Align(nb)); - tbaa_decorate(tbaa, load); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.decorateInst(load); load->setOrdering(llvm_order); thePtr = emit_bitcast(ctx, strct, thePtr->getType()); StoreInst *store = ctx.builder.CreateAlignedStore(load, thePtr, Align(julia_alignment(ety))); - tbaa_decorate(tbaa, store); + ai.decorateInst(store); return mark_julia_type(ctx, strct, true, ety); } else { diff --git a/test/llvmpasses/aliasscopes.jl b/test/llvmpasses/aliasscopes.jl index 5c0fe48091ade7..751e351dfad1e5 100644 --- a/test/llvmpasses/aliasscopes.jl +++ b/test/llvmpasses/aliasscopes.jl @@ -18,8 +18,8 @@ import Base.Experimental: Const, @aliasscope function simple(A, B) @aliasscope @inbounds for I in eachindex(A, B) A[I] = Const(B)[I] -# CHECK: load double, {{.*}} !alias.scope [[SCOPE:![0-9]+]] -# CHECK: store double {{.*}} !noalias [[SCOPE]] +# CHECK: load double, {{.*}} !alias.scope [[SCOPE_LD:![0-9]+]] +# CHECK: store double {{.*}} !noalias [[SCOPE_ST:![0-9]+]] end return 0 # return nothing causes japi1 end @@ -28,8 +28,8 @@ end function constargs(A, B::Const) @aliasscope @inbounds for I in eachindex(A, B) A[I] = B[I] -# CHECK: load double, {{.*}} !alias.scope [[SCOPE2:![0-9]+]] -# CHECK: store double {{.*}} !noalias [[SCOPE2]] +# CHECK: load double, {{.*}} !alias.scope [[SCOPE2_LD:![0-9]+]] +# CHECK: store double {{.*}} !noalias [[SCOPE2_ST:![0-9]+]] end return 0 end @@ -40,10 +40,10 @@ function micro_ker!(AB, Ac, Bc, kc, offSetA, offSetB) @inbounds @aliasscope for k in 1:kc for j in 1:NR, i in 1:MR AB[i+(j-1)*MR] = muladd(Const(Ac)[offSetA+i], Const(Bc)[offSetB+j], Const(AB)[i+(j-1)*MR]) -# CHECK: load double, {{.*}} !alias.scope [[SCOPE3:![0-9]+]] -# CHECK: load double, {{.*}} !alias.scope [[SCOPE3]] -# CHECK: load double, {{.*}} !alias.scope [[SCOPE3]] -# CHECK: store double {{.*}} !noalias [[SCOPE3]] +# CHECK: load double, {{.*}} !alias.scope [[SCOPE3_LD:![0-9]+]] +# CHECK: load double, {{.*}} !alias.scope [[SCOPE3_LD]] +# CHECK: load double, {{.*}} !alias.scope [[SCOPE3_LD]] +# CHECK: store double {{.*}} !noalias [[SCOPE3_ST:![0-9]+]] end offSetA += MR offSetB += NR @@ -51,9 +51,14 @@ function micro_ker!(AB, Ac, Bc, kc, offSetA, offSetB) return end -# CHECK: [[SCOPE]] = !{[[ALIASSCOPE:![0-9]+]]} -# CHECK: [[ALIASSCOPE]] = !{!"aliasscope", [[MDNODE:![0-9]+]]} -# CHECK: [[MDNODE]] = !{!"simple"} +# CHECK-DAG: [[SCOPE_LD]] = !{[[ALIASSCOPE:![0-9]+]] +# CHECK-DAG: [[SCOPE_ST]] = !{[[ALIASSCOPE]] +# CHECK-DAG: [[SCOPE2_LD]] = !{[[ALIASSCOPE2:![0-9]+]] +# CHECK-DAG: [[SCOPE2_ST]] = !{[[ALIASSCOPE2]] +# CHECK-DAG: [[SCOPE3_LD]] = !{[[ALIASSCOPE3:![0-9]+]] +# CHECK-DAG: [[SCOPE3_ST]] = !{[[ALIASSCOPE3]] +# CHECK-DAG: [[ALIASSCOPE]] = !{!"aliasscope", [[MDNODE:![0-9]+]]} +# CHECK-DAG: [[MDNODE]] = !{!"simple"} emit(simple, Vector{Float64}, Vector{Float64}) emit(constargs, Vector{Float64}, Const{Float64, 1}) From 1a94dab756aea66180bb720ae7088fc01749dd63 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Thu, 12 Jan 2023 21:41:33 +0100 Subject: [PATCH 308/387] move some badly typed logging calls behind an invokelatest (#48254) --- base/logging.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/logging.jl b/base/logging.jl index c670d658cdaeb9..dd45d05a084af2 100644 --- a/base/logging.jl +++ b/base/logging.jl @@ -349,7 +349,7 @@ function logmsg_code(_module, file, line, level, message, exs...) kwargs = (;$(log_data.kwargs...)) true else - logging_error(logger, level, _module, group, id, file, line, err, false) + @invokelatest logging_error(logger, level, _module, group, id, file, line, err, false) false end end @@ -361,7 +361,7 @@ function logmsg_code(_module, file, line, level, message, exs...) kwargs = (;$(log_data.kwargs...)) true catch err - logging_error(logger, level, _module, group, id, file, line, err, true) + @invokelatest logging_error(logger, level, _module, group, id, file, line, err, true) false end end From d544e786fa732b8f578a74e5ee94928075866f20 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Thu, 12 Jan 2023 16:12:08 -0500 Subject: [PATCH 309/387] Rework :inbounds effects tainting (#48246) This works to fix #48243, by only tanting effects if an `@inbounds` statement is actually reached. Further, it refines the `noinbounds` effect to be IPO-cached and used to track whether a particular method read the inbounds state. A `:boundscheck` expression now does not immediately taint consistencty, but instead, taints `noinbounds` only. Then, if a method that has `:noinbounds` tainted is called within an `@inbounds` region, consistency is tainted. Similarly, a tainted `:noinbounds` disables constant propagation at `@inbounds` statements or if the method propagates inbounds. --- base/compiler/abstractinterpretation.jl | 61 ++++++++++++++++--------- base/compiler/effects.jl | 20 ++++---- base/compiler/inferencestate.jl | 11 +---- base/compiler/ssair/show.jl | 2 + base/expr.jl | 4 ++ test/compiler/effects.jl | 10 ++++ 6 files changed, 67 insertions(+), 41 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 5c40e3058f7ce7..fd6f480741eabc 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -828,9 +828,18 @@ end # - false: eligible for semi-concrete evaluation # - nothing: not eligible for either of it function concrete_eval_eligible(interp::AbstractInterpreter, - @nospecialize(f), result::MethodCallResult, arginfo::ArgInfo) + @nospecialize(f), result::MethodCallResult, arginfo::ArgInfo, sv::InferenceState) # disable all concrete-evaluation if this function call is tainted by some overlayed # method since currently there is no direct way to execute overlayed methods + if inbounds_option() === :off + # Disable concrete evaluation in `--check-bounds=no` mode, since we cannot be sure + # that inferred effects are accurate. + return nothing + elseif !result.effects.noinbounds && stmt_taints_inbounds_consistency(sv) + # If the current statement is @inbounds or we propagate inbounds, the call's consistency + # is tainted and not consteval eligible. + return nothing + end isoverlayed(method_table(interp)) && !is_nonoverlayed(result.effects) && return nothing if f !== nothing && result.edge !== nothing && is_foldable(result.effects) if is_all_const_arg(arginfo, #=start=#2) @@ -869,7 +878,7 @@ end function concrete_eval_call(interp::AbstractInterpreter, @nospecialize(f), result::MethodCallResult, arginfo::ArgInfo, si::StmtInfo, sv::InferenceState, invokecall::Union{Nothing,InvokeCall}=nothing) - eligible = concrete_eval_eligible(interp, f, result, arginfo) + eligible = concrete_eval_eligible(interp, f, result, arginfo, sv) eligible === nothing && return false if eligible args = collect_const_args(arginfo, #=start=#2) @@ -2179,21 +2188,18 @@ function abstract_eval_value_expr(interp::AbstractInterpreter, e::Expr, vtypes:: end elseif head === :boundscheck if isa(sv, InferenceState) - stmt = sv.src.code[sv.currpc] - if isexpr(stmt, :call) - f = abstract_eval_value(interp, stmt.args[1], vtypes, sv) - if f isa Const && f.val === getfield - # boundscheck of `getfield` call is analyzed by tfunc potentially without - # tainting :consistent-cy when it's known to be nothrow - @goto delay_effects_analysis - end - end + flag = sv.src.ssaflags[sv.currpc] + # If there is no particular @inbounds for this function, then we only taint `noinbounds`, + # which will subsequently taint consistency if this function is called from another + # function that uses `@inbounds`. However, if this :boundscheck is itself within an + # `@inbounds` region, its value depends on `--check-bounds`, so we need to taint + # consistency here also. + merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; noinbounds=false, + consistent = (flag & IR_FLAG_INBOUNDS) != 0 ? ALWAYS_FALSE : ALWAYS_TRUE)) end - merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE, noinbounds=false)) - @label delay_effects_analysis rt = Bool elseif head === :inbounds - merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE, noinbounds=false)) + @assert false && "Expected this to have been moved into flags" elseif head === :the_exception merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE)) end @@ -2269,7 +2275,6 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp arginfo = ArgInfo(ea, argtypes) si = StmtInfo(isa(sv, IRCode) ? true : !call_result_unused(sv, sv.currpc)) (; rt, effects, info) = abstract_call(interp, arginfo, si, sv) - merge_effects!(interp, sv, effects) if isa(sv, InferenceState) sv.stmt_info[sv.currpc] = info # mark this call statement as DCE-elgible @@ -2341,7 +2346,6 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp t = refine_partial_type(t) end effects = Effects(EFFECTS_TOTAL; consistent, nothrow) - merge_effects!(interp, sv, effects) elseif ehead === :splatnew t, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv)) nothrow = false # TODO: More precision @@ -2362,7 +2366,6 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp end consistent = !ismutabletype(t) ? ALWAYS_TRUE : CONSISTENT_IF_NOTRETURNED effects = Effects(EFFECTS_TOTAL; consistent, nothrow) - merge_effects!(interp, sv, effects) elseif ehead === :new_opaque_closure t = Union{} effects = Effects() # TODO @@ -2390,7 +2393,6 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp elseif ehead === :foreigncall (;rt, effects) = abstract_eval_foreigncall(interp, e, vtypes, sv, mi) t = rt - merge_effects!(interp, sv, effects) if isa(sv, InferenceState) # mark this call statement as DCE-elgible if is_removable_if_unused(effects) @@ -2401,17 +2403,14 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp end elseif ehead === :cfunction effects = EFFECTS_UNKNOWN - merge_effects!(interp, sv, effects) t = e.args[1] isa(t, Type) || (t = Any) abstract_eval_cfunction(interp, e, vtypes, sv) elseif ehead === :method t = (length(e.args) == 1) ? Any : Nothing effects = EFFECTS_UNKNOWN - merge_effects!(interp, sv, effects) elseif ehead === :copyast effects = EFFECTS_UNKNOWN - merge_effects!(interp, sv, effects) t = abstract_eval_value(interp, e.args[1], vtypes, sv) if t isa Const && t.val isa Expr # `copyast` makes copies of Exprs @@ -2422,6 +2421,7 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp elseif ehead === :isdefined sym = e.args[1] t = Bool + effects = EFFECTS_TOTAL if isa(sym, SlotNumber) vtyp = vtypes[slot_id(sym)] if vtyp.typ === Bottom @@ -2454,9 +2454,9 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp @label always_throw t = Bottom effects = EFFECTS_THROWS - merge_effects!(interp, sv, effects) else t = abstract_eval_value_expr(interp, e, vtypes, sv) + effects = EFFECTS_TOTAL end return RTEffects(t, effects) end @@ -2511,6 +2511,11 @@ function abstract_eval_phi(interp::AbstractInterpreter, phi::PhiNode, vtypes::Un return rt end +function stmt_taints_inbounds_consistency(sv::InferenceState) + flag = sv.src.ssaflags[sv.currpc] + return sv.src.propagate_inbounds || (flag & IR_FLAG_INBOUNDS) != 0 +end + function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), vtypes::VarTable, sv::InferenceState) if !isa(e, Expr) if isa(e, PhiNode) @@ -2519,6 +2524,18 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), return abstract_eval_special_value(interp, e, vtypes, sv) end (;rt, effects) = abstract_eval_statement_expr(interp, e, vtypes, sv, nothing) + if !effects.noinbounds + flag = sv.src.ssaflags[sv.currpc] + if !sv.src.propagate_inbounds + # The callee read our inbounds flag, but unless we propagate inbounds, + # we ourselves don't read our parent's inbounds. + effects = Effects(effects; noinbounds=true) + end + if (flag & IR_FLAG_INBOUNDS) != 0 + effects = Effects(effects; consistent=ALWAYS_FALSE) + end + end + merge_effects!(interp, sv, effects) e = e::Expr @assert !isa(rt, TypeVar) "unhandled TypeVar" rt = maybe_singleton_const(rt) diff --git a/base/compiler/effects.jl b/base/compiler/effects.jl index b935f2ab813858..48842d7f51cd8c 100644 --- a/base/compiler/effects.jl +++ b/base/compiler/effects.jl @@ -40,9 +40,9 @@ following meanings: This state corresponds to LLVM's `inaccessiblemem_or_argmemonly` function attribute. - `nonoverlayed::Bool`: indicates that any methods that may be called within this method are not defined in an [overlayed method table](@ref OverlayMethodTable). -- `noinbounds::Bool`: indicates this method can't be `:consistent` because of bounds checking. - This effect is currently only set on `InferenceState` construction and used to taint - `:consistent`-cy before caching. We may want to track it with more accuracy in the future. +- `noinbounds::Bool`: If set, indicates that this method does not read the parent's :inbounds + state. In particular, it does not have any reached :boundscheck exprs, not propagates inbounds + to any children that do. Note that the representations above are just internal implementation details and thus likely to change in the future. See [`Base.@assume_effects`](@ref) for more detailed explanation @@ -98,10 +98,10 @@ const EFFECT_FREE_IF_INACCESSIBLEMEMONLY = 0x01 << 1 # :inaccessiblememonly bits const INACCESSIBLEMEM_OR_ARGMEMONLY = 0x01 << 1 -const EFFECTS_TOTAL = Effects(ALWAYS_TRUE, ALWAYS_TRUE, true, true, true, ALWAYS_TRUE, true) -const EFFECTS_THROWS = Effects(ALWAYS_TRUE, ALWAYS_TRUE, false, true, true, ALWAYS_TRUE, true) -const EFFECTS_UNKNOWN = Effects(ALWAYS_FALSE, ALWAYS_FALSE, false, false, false, ALWAYS_FALSE, true) # unknown mostly, but it's not overlayed at least (e.g. it's not a call) -const EFFECTS_UNKNOWN′ = Effects(ALWAYS_FALSE, ALWAYS_FALSE, false, false, false, ALWAYS_FALSE, false) # unknown really +const EFFECTS_TOTAL = Effects(ALWAYS_TRUE, ALWAYS_TRUE, true, true, true, ALWAYS_TRUE, true, true) +const EFFECTS_THROWS = Effects(ALWAYS_TRUE, ALWAYS_TRUE, false, true, true, ALWAYS_TRUE, true, true) +const EFFECTS_UNKNOWN = Effects(ALWAYS_FALSE, ALWAYS_FALSE, false, false, false, ALWAYS_FALSE, true, true) # unknown mostly, but it's not overlayed at least (e.g. it's not a call) +const EFFECTS_UNKNOWN′ = Effects(ALWAYS_FALSE, ALWAYS_FALSE, false, false, false, ALWAYS_FALSE, false, true) # unknown really function Effects(e::Effects = EFFECTS_UNKNOWN′; consistent::UInt8 = e.consistent, @@ -184,7 +184,8 @@ function encode_effects(e::Effects) ((e.terminates % UInt32) << 6) | ((e.notaskstate % UInt32) << 7) | ((e.inaccessiblememonly % UInt32) << 8) | - ((e.nonoverlayed % UInt32) << 10) + ((e.nonoverlayed % UInt32) << 10)| + ((e.noinbounds % UInt32) << 11) end function decode_effects(e::UInt32) @@ -195,7 +196,8 @@ function decode_effects(e::UInt32) _Bool((e >> 6) & 0x01), _Bool((e >> 7) & 0x01), UInt8((e >> 8) & 0x03), - _Bool((e >> 10) & 0x01)) + _Bool((e >> 10) & 0x01), + _Bool((e >> 11) & 0x01)) end struct EffectsOverride diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index e66f1335531429..37d644bcfc9811 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -177,16 +177,7 @@ mutable struct InferenceState valid_worlds = WorldRange(src.min_world, src.max_world == typemax(UInt) ? get_world_counter() : src.max_world) bestguess = Bottom - # TODO: Currently, any :inbounds declaration taints consistency, - # because we cannot be guaranteed whether or not boundschecks - # will be eliminated and if they are, we cannot be guaranteed - # that no undefined behavior will occur (the effects assumptions - # are stronger than the inbounds assumptions, since the latter - # requires dynamic reachability, while the former is global). - inbounds = inbounds_option() - noinbounds = inbounds === :on || (inbounds === :default && all(flag::UInt8->iszero(flag&IR_FLAG_INBOUNDS), src.ssaflags)) - consistent = noinbounds ? ALWAYS_TRUE : ALWAYS_FALSE - ipo_effects = Effects(EFFECTS_TOTAL; consistent, noinbounds) + ipo_effects = Effects(EFFECTS_TOTAL) params = InferenceParams(interp) restrict_abstract_call_sites = isa(linfo.def, Module) diff --git a/base/compiler/ssair/show.jl b/base/compiler/ssair/show.jl index d2291404f11c6d..f4d240f423e895 100644 --- a/base/compiler/ssair/show.jl +++ b/base/compiler/ssair/show.jl @@ -1012,6 +1012,8 @@ function Base.show(io::IO, e::Effects) printstyled(io, effectbits_letter(e, :notaskstate, 's'); color=effectbits_color(e, :notaskstate)) print(io, ',') printstyled(io, effectbits_letter(e, :inaccessiblememonly, 'm'); color=effectbits_color(e, :inaccessiblememonly)) + print(io, ',') + printstyled(io, effectbits_letter(e, :noinbounds, 'i'); color=effectbits_color(e, :noinbounds)) print(io, ')') e.nonoverlayed || printstyled(io, '′'; color=:red) end diff --git a/base/expr.jl b/base/expr.jl index 769b1faa0d24db..6f6fcd86fc680c 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -607,6 +607,10 @@ currently equivalent to the following `setting`s: however, that by the `:consistent`-cy requirements, any such annotated call must consistently throw given the same argument values. +!!! note + An explict `@inbounds` annotation inside the function will also disable + constant propagation and not be overriden by :foldable. + --- ## `:removable` diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl index 5ae642fa9e7e34..809d7e2d37f5bb 100644 --- a/test/compiler/effects.jl +++ b/test/compiler/effects.jl @@ -720,3 +720,13 @@ end |> Core.Compiler.is_foldable @test Base.infer_effects() do return WrapperOneField == (WrapperOneField{T} where T) end |> Core.Compiler.is_total + +# Test that dead `@inbounds` does not taint consistency +@test Base.infer_effects() do + false && @inbounds (1,2,3)[1] + return 1 +end |> Core.Compiler.is_total + +@test Base.infer_effects(Tuple{Int64}) do i + @inbounds (1,2,3)[i] +end |> !Core.Compiler.is_consistent From d61cfd253b95993ef0dfa1a52cee0997a07b9d46 Mon Sep 17 00:00:00 2001 From: Rogerluo Date: Thu, 12 Jan 2023 16:53:16 -0500 Subject: [PATCH 310/387] support UInt & BigInt in TOML (#47903) * support parsing uint and long int Co-authored-by: Kristoffer Carlsson --- base/toml_parser.jl | 35 +++++++++++------ stdlib/TOML/src/print.jl | 10 ++++- stdlib/TOML/test/print.jl | 13 +++++++ stdlib/TOML/test/readme.jl | 79 +++++++++++++++++++++++++++++++++----- stdlib/TOML/test/values.jl | 26 ++++++++++++- 5 files changed, 139 insertions(+), 24 deletions(-) diff --git a/base/toml_parser.jl b/base/toml_parser.jl index 0e90f46315e5e5..6c4ff6e2a52c08 100644 --- a/base/toml_parser.jl +++ b/base/toml_parser.jl @@ -823,15 +823,15 @@ function parse_number_or_date_start(l::Parser) elseif accept(l, 'x') parsed_sign && return ParserError(ErrSignInNonBase10Number) ate, contains_underscore = @try accept_batch_underscore(l, isvalid_hex) - ate && return parse_int(l, contains_underscore) + ate && return parse_hex(l, contains_underscore) elseif accept(l, 'o') parsed_sign && return ParserError(ErrSignInNonBase10Number) ate, contains_underscore = @try accept_batch_underscore(l, isvalid_oct) - ate && return parse_int(l, contains_underscore) + ate && return parse_oct(l, contains_underscore) elseif accept(l, 'b') parsed_sign && return ParserError(ErrSignInNonBase10Number) ate, contains_underscore = @try accept_batch_underscore(l, isvalid_binary) - ate && return parse_int(l, contains_underscore) + ate && return parse_bin(l, contains_underscore) elseif accept(l, isdigit) return parse_local_time(l) end @@ -899,15 +899,28 @@ function parse_float(l::Parser, contains_underscore)::Err{Float64} return v end -function parse_int(l::Parser, contains_underscore, base=nothing)::Err{Int64} - s = take_string_or_substring(l, contains_underscore) - v = try - Base.parse(Int64, s; base=base) - catch e - e isa Base.OverflowError && return(ParserError(ErrOverflowError)) - error("internal parser error: did not correctly discredit $(repr(s)) as an int") +for (name, T1, T2, n1, n2) in (("int", Int64, Int128, 17, 33), + ("hex", UInt64, UInt128, 18, 34), + ("oct", UInt64, UInt128, 24, 45), + ("bin", UInt64, UInt128, 66, 130), + ) + @eval function $(Symbol("parse_", name))(l::Parser, contains_underscore, base=nothing)::Err{Union{$(T1), $(T2), BigInt}} + s = take_string_or_substring(l, contains_underscore) + len = length(s) + v = try + if len ≤ $(n1) + Base.parse($(T1), s; base) + elseif $(n1) < len ≤ $(n2) + Base.parse($(T2), s; base) + else + Base.parse(BigInt, s; base) + end + catch e + e isa Base.OverflowError && return(ParserError(ErrOverflowError)) + error("internal parser error: did not correctly discredit $(repr(s)) as an int") + end + return v end - return v end diff --git a/stdlib/TOML/src/print.jl b/stdlib/TOML/src/print.jl index 61d13a8f4853ef..f5bef8344f64fd 100644 --- a/stdlib/TOML/src/print.jl +++ b/stdlib/TOML/src/print.jl @@ -93,7 +93,7 @@ function printvalue(f::MbyFunc, io::IO, value::TOMLValue) value isa Dates.Time ? Base.print(io, Dates.format(value, Dates.dateformat"HH:MM:SS.sss")) : value isa Dates.Date ? Base.print(io, Dates.format(value, Dates.dateformat"YYYY-mm-dd")) : value isa Bool ? Base.print(io, value ? "true" : "false") : - value isa Integer ? Base.print(io, Int64(value)) : # TOML specifies 64-bit signed long range for integer + value isa Integer ? print_integer(io, value) : # Julia's own printing should be compatible with TOML on integers value isa AbstractFloat ? Base.print(io, isnan(value) ? "nan" : isinf(value) ? string(value > 0 ? "+" : "-", "inf") : Float64(value)) : # TOML specifies IEEE 754 binary64 for float @@ -104,6 +104,14 @@ function printvalue(f::MbyFunc, io::IO, value::TOMLValue) error("internal error in TOML printing, unhandled value") end +function print_integer(io::IO, value::Integer) + value isa Signed && return Base.show(io, value) + # unsigned integers are printed as hex + n = 2 * ndigits(value, base=256) + Base.print(io, "0x", string(value, base=16, pad=n)) + return +end + function print_inline_table(f::MbyFunc, io::IO, value::AbstractDict) Base.print(io, "{") for (i, (k,v)) in enumerate(value) diff --git a/stdlib/TOML/test/print.jl b/stdlib/TOML/test/print.jl index bbfce3b7d7474e..765b6feb491a51 100644 --- a/stdlib/TOML/test/print.jl +++ b/stdlib/TOML/test/print.jl @@ -96,6 +96,19 @@ loaders = ["gzip", { driver = "csv", args = {delim = "\t"}}] """ end +@testset "unsigned integers" for (x, s) in [ + 0x1a0 => "0x01a0", + 0x1aea8 => "0x01aea8", + 0x1aeee8 => "0x1aeee8", + 0x1aea01231 => "0x01aea01231", + 0x1aea01231213ae13125 => "0x01aea01231213ae13125", + ] + d = Dict("x" => x) + @test toml_str(d) == """ + x = $s + """ +end + struct Foo a::Int64 b::Float64 diff --git a/stdlib/TOML/test/readme.jl b/stdlib/TOML/test/readme.jl index 50d47dafeec223..ee267414485ba7 100644 --- a/stdlib/TOML/test/readme.jl +++ b/stdlib/TOML/test/readme.jl @@ -410,31 +410,90 @@ d = parse(str) @test d["oct2"] == 0o755 @test d["bin1"] == 0b11010110 +str = """ +hex1 = 0x6E # UInt8 +hex2 = 0x8f1e # UInt16 +hex3 = 0x765f3173 # UInt32 +hex4 = 0xc13b830a807cc7f4 # UInt64 +hex5 = 0x937efe0a4241edb24a04b97bd90ef363 # UInt128 +hex6 = 0x937efe0a4241edb24a04b97bd90ef3632 # BigInt +""" +@test roundtrip(str) +d = parse(str) +@test d["hex1"] isa UInt64 +@test d["hex2"] isa UInt64 +@test d["hex3"] isa UInt64 +@test d["hex4"] isa UInt64 +@test d["hex5"] isa UInt128 +@test d["hex6"] isa BigInt + +str = """ +oct1 = 0o140 # UInt8 +oct2 = 0o46244 # UInt16 +oct3 = 0o32542120656 # UInt32 +oct4 = 0o1526535761042630654411 # UInt64 +oct5 = 0o3467204325743773607311464533371572447656531 # UInt128 +oct6 = 0o34672043257437736073114645333715724476565312 # BigInt +""" +@test roundtrip(str) +d = parse(str) +@test d["oct1"] isa UInt64 +@test d["oct2"] isa UInt64 +@test d["oct3"] isa UInt64 +@test d["oct4"] isa UInt64 +@test d["oct5"] isa UInt128 +@test d["oct6"] isa BigInt + +str = """ +bin1 = 0b10001010 # UInt8 +bin2 = 0b11111010001100 # UInt16 +bin3 = 0b11100011110000010101000010101 # UInt32 +bin4 = 0b10000110100111011010001000000111110110000011111101101110011011 # UInt64 +bin5 = 0b1101101101101100110001010110111011101000111010101110011000011100110100101111110001010001011001000001000001010010011101100100111 # UInt128 +bin6 = 0b110110110110110011000101011011101110100011101010111001100001110011010010111111000101000101100100000100000101001001110110010011111 # BigInt +""" + +@test roundtrip(str) +d = parse(str) +@test d["bin1"] isa UInt64 +@test d["bin2"] isa UInt64 +@test d["bin3"] isa UInt64 +@test d["bin4"] isa UInt64 +@test d["bin5"] isa UInt128 +@test d["bin6"] isa BigInt + #Arbitrary 64-bit signed integers (from −2^63 to 2^63−1) should be accepted and #handled losslessly. If an integer cannot be represented losslessly, an error #must be thrown. str = """ -low = -9_223_372_036_854_775_808 -high = 9_223_372_036_854_775_807 +low = -170_141_183_460_469_231_731_687_303_715_884_105_728 +high = 170_141_183_460_469_231_731_687_303_715_884_105_727 +""" +@test roundtrip(str) +d = parse(str) +@test d["low"] == typemin(Int128) +@test d["high"] == typemax(Int128) + +str = """ +low = -170_141_183_460_469_231_731_687_303_715_884_105_728_123 +high = 170_141_183_460_469_231_731_687_303_715_884_105_727_123 """ @test roundtrip(str) d = parse(str) -@test d["low"] == -9_223_372_036_854_775_808 -@test d["high"] == 9_223_372_036_854_775_807 +@test d["low"] == big"-170_141_183_460_469_231_731_687_303_715_884_105_728_123" +@test d["high"] == big"170_141_183_460_469_231_731_687_303_715_884_105_727_123" str = """ toolow = -9_223_372_036_854_775_809 """ -err = tryparse(str) -@test err isa ParserError -@test err.type == Internals.ErrOverflowError +d = parse(str) +@test d["toolow"] == -9223372036854775809 str = """ toohigh = 9_223_372_036_854_775_808 """ -err = tryparse(str) -@test err isa ParserError -@test err.type == Internals.ErrOverflowError +d = parse(str) +d["toohigh"] == 9_223_372_036_854_775_808 end diff --git a/stdlib/TOML/test/values.jl b/stdlib/TOML/test/values.jl index 8337bb5a547148..be2ed3acce5b5c 100644 --- a/stdlib/TOML/test/values.jl +++ b/stdlib/TOML/test/values.jl @@ -23,8 +23,6 @@ end @test failval("00.0" , Internals.ErrParsingDateTime) @test failval("-00.0" , Internals.ErrParsingDateTime) @test failval("+00.0" , Internals.ErrParsingDateTime) - @test failval("9223372036854775808" , Internals.ErrOverflowError) - @test failval("-9223372036854775809" , Internals.ErrOverflowError) @test failval("0." , Internals.ErrNoTrailingDigitAfterDot) @test failval("0.e" , Internals.ErrNoTrailingDigitAfterDot) @@ -54,6 +52,30 @@ end @test testval("+1_000" , 1000 |> Int64) @test testval("-1_000" , -1000 |> Int64) + @test testval("0x6E", 0x6E|> UInt64) + @test testval("0x8f1e", 0x8f1e|> UInt64) + @test testval("0x765f3173", 0x765f3173|> UInt64) + @test testval("0xc13b830a807cc7f4", 0xc13b830a807cc7f4|> UInt64) + @test testval("0x937efe_0a4241_edb24a04b97bd90ef363", 0x937efe0a4241edb24a04b97bd90ef363 |> UInt128) + + @test testval("0o140", 0o140 |> UInt64) # UInt8 + @test testval("0o46244", 0o46244 |> UInt64) # UInt16 + @test testval("0o32542120656", 0o32542120656 |> UInt64) # UInt32 + @test testval("0o1526535761042630654411", 0o1526535761042630654411 |> UInt64) # UInt64 + @test testval("0o3467204325743773607311464533371572447656531", 0o3467204325743773607311464533371572447656531 |> UInt128) # UInt128 + @test testval("0o34672043257437736073114645333715724476565312", 0o34672043257437736073114645333715724476565312 |> BigInt) # BigInt + + @test testval("0b10001010",0b10001010 |> UInt64) # UInt8 + @test testval("0b11111010001100",0b11111010001100 |> UInt64) # UInt16 + @test testval("0b11100011110000010101000010101",0b11100011110000010101000010101 |> UInt64) # UInt32 + @test testval("0b10000110100111011010001000000111110110000011111101101110011011",0b10000110100111011010001000000111110110000011111101101110011011 |> UInt64) # UInt64 + @test testval( + "0b1101101101101100110001010110111011101000111010101110011000011100110100101111110001010001011001000001000001010010011101100100111", + 0b1101101101101100110001010110111011101000111010101110011000011100110100101111110001010001011001000001000001010010011101100100111 |> UInt128) # UInt128 + @test testval( + "0b110110110110110011000101011011101110100011101010111001100001110011010010111111000101000101100100000100000101001001110110010011111", + 0b110110110110110011000101011011101110100011101010111001100001110011010010111111000101000101100100000100000101001001110110010011111 |> BigInt) # BigInt + @test failval("0_" , Internals.ErrUnderscoreNotSurroundedByDigits) @test failval("0__0" , Internals.ErrUnderscoreNotSurroundedByDigits) @test failval("__0" , Internals.ErrUnexpectedStartOfValue) From 206fd5aa16e30baf2cc251955e7ffce4c03563b5 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Fri, 13 Jan 2023 12:58:36 +0900 Subject: [PATCH 311/387] distinguish "inlineable" from "declared as inline" (#48250) This commit addresses the current asymmetry in handling `@inline/@noinline` declarations by storing the information in `src::CodeInfo`. `src` now has the `inlining` field that indicates the inlining declaration as follows: - `src.inlining == 0`: no declaration - `src.inlining == 1`: declared as `@inline` - `src.inlining == 2`: declared as `@noinline` This change is a preparation for an upcoming refactor that will allow for judging inlineability at callsites of `is_inlineable`, while leaving the `inline_cost` function to simply compute the inlining cost without determining inlineability. --- base/compiler/abstractinterpretation.jl | 63 ++++++++++++----------- base/compiler/optimize.jl | 30 +++++++---- base/compiler/utilities.jl | 21 ++++++++ src/codegen.cpp | 2 +- src/ircode.c | 18 ++++++- src/jl_exported_funcs.inc | 1 + src/jltypes.c | 6 ++- src/julia.h | 2 + src/julia_internal.h | 1 + src/method.c | 5 +- stdlib/Serialization/src/Serialization.jl | 8 +-- test/compiler/inline.jl | 46 +++++++++++------ test/compiler/ssair.jl | 9 ---- test/syntax.jl | 7 ++- 14 files changed, 140 insertions(+), 79 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index fd6f480741eabc..4a05503cfe4bbe 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -223,7 +223,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), method = match.method sig = match.spec_types mi = specialize_method(match; preexisting=true) - if mi !== nothing && !const_prop_methodinstance_heuristic(interp, match, mi, arginfo, sv) + if mi !== nothing && !const_prop_methodinstance_heuristic(interp, mi, arginfo, sv) csig = get_compileable_sig(method, sig, match.sparams) if csig !== nothing && csig !== sig abstract_call_method(interp, method, csig, match.sparams, multiple_matches, StmtInfo(false), sv) @@ -1096,7 +1096,7 @@ function maybe_get_const_prop_profitable(interp::AbstractInterpreter, return nothing end mi = mi::MethodInstance - if !force && !const_prop_methodinstance_heuristic(interp, match, mi, arginfo, sv) + if !force && !const_prop_methodinstance_heuristic(interp, mi, arginfo, sv) add_remark!(interp, sv, "[constprop] Disabled by method instance heuristic") return nothing end @@ -1248,8 +1248,8 @@ end # where we would spend a lot of time, but are probably unlikely to get an improved # result anyway. function const_prop_methodinstance_heuristic(interp::AbstractInterpreter, - match::MethodMatch, mi::MethodInstance, arginfo::ArgInfo, sv::InferenceState) - method = match.method + mi::MethodInstance, arginfo::ArgInfo, sv::InferenceState) + method = mi.def::Method if method.is_for_opaque_closure # Not inlining an opaque closure can be very expensive, so be generous # with the const-prop-ability. It is quite possible that we can't infer @@ -1257,34 +1257,37 @@ function const_prop_methodinstance_heuristic(interp::AbstractInterpreter, # isn't particularly helpful here. return true end - # Peek at the inferred result for the function to determine if the optimizer - # was able to cut it down to something simple (inlineable in particular). - # If so, there's a good chance we might be able to const prop all the way - # through and learn something new. - if isdefined(method, :source) && is_inlineable(method.source) + # now check if the source of this method instance is inlineable, since the extended type + # information we have here would be discarded if it is not inlined into a callee context + # (modulo the inferred return type that can be potentially refined) + if is_declared_inline(method) + # this method is declared as `@inline` and will be inlined + return true + end + flag = get_curr_ssaflag(sv) + if is_stmt_inline(flag) + # force constant propagation for a call that is going to be inlined + # since the inliner will try to find this constant result + # if these constant arguments arrive there return true + elseif is_stmt_noinline(flag) + # this call won't be inlined, thus this constant-prop' will most likely be unfruitful + return false else - flag = get_curr_ssaflag(sv) - if is_stmt_inline(flag) - # force constant propagation for a call that is going to be inlined - # since the inliner will try to find this constant result - # if these constant arguments arrive there - return true - elseif is_stmt_noinline(flag) - # this call won't be inlined, thus this constant-prop' will most likely be unfruitful - return false - else - code = get(code_cache(interp), mi, nothing) - if isdefined(code, :inferred) - if isa(code, CodeInstance) - inferred = @atomic :monotonic code.inferred - else - inferred = code.inferred - end - # TODO propagate a specific `CallInfo` that conveys information about this call - if inlining_policy(interp, inferred, NoCallInfo(), IR_FLAG_NULL, mi, arginfo.argtypes) !== nothing - return true - end + # Peek at the inferred result for the method to determine if the optimizer + # was able to cut it down to something simple (inlineable in particular). + # If so, there will be a good chance we might be able to const prop + # all the way through and learn something new. + code = get(code_cache(interp), mi, nothing) + if isdefined(code, :inferred) + if isa(code, CodeInstance) + inferred = @atomic :monotonic code.inferred + else + inferred = code.inferred + end + # TODO propagate a specific `CallInfo` that conveys information about this call + if inlining_policy(interp, inferred, NoCallInfo(), IR_FLAG_NULL, mi, arginfo.argtypes) !== nothing + return true end end end diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index de0a8d5cf5a93f..5efad6a55f19ee 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -39,9 +39,12 @@ const TOP_TUPLE = GlobalRef(Core, :tuple) const InlineCostType = UInt16 const MAX_INLINE_COST = typemax(InlineCostType) const MIN_INLINE_COST = InlineCostType(10) +const MaybeCompressed = Union{CodeInfo, Vector{UInt8}} -is_inlineable(src::Union{CodeInfo, Vector{UInt8}}) = ccall(:jl_ir_inlining_cost, InlineCostType, (Any,), src) != MAX_INLINE_COST -set_inlineable!(src::CodeInfo, val::Bool) = src.inlining_cost = (val ? MIN_INLINE_COST : MAX_INLINE_COST) +is_inlineable(@nospecialize src::MaybeCompressed) = + ccall(:jl_ir_inlining_cost, InlineCostType, (Any,), src) != MAX_INLINE_COST +set_inlineable!(src::CodeInfo, val::Bool) = + src.inlining_cost = (val ? MIN_INLINE_COST : MAX_INLINE_COST) function inline_cost_clamp(x::Int)::InlineCostType x > MAX_INLINE_COST && return MAX_INLINE_COST @@ -49,6 +52,12 @@ function inline_cost_clamp(x::Int)::InlineCostType return convert(InlineCostType, x) end +is_declared_inline(@nospecialize src::MaybeCompressed) = + ccall(:jl_ir_flag_inlining, UInt8, (Any,), src) == 1 + +is_declared_noinline(@nospecialize src::MaybeCompressed) = + ccall(:jl_ir_flag_inlining, UInt8, (Any,), src) == 2 + ##################### # OptimizationState # ##################### @@ -73,16 +82,16 @@ function add_invoke_backedge!(et::EdgeTracker, @nospecialize(invokesig), mi::Met return nothing end -is_source_inferred(@nospecialize(src::Union{CodeInfo, Vector{UInt8}})) = +is_source_inferred(@nospecialize src::MaybeCompressed) = ccall(:jl_ir_flag_inferred, Bool, (Any,), src) function inlining_policy(interp::AbstractInterpreter, @nospecialize(src), @nospecialize(info::CallInfo), stmt_flag::UInt8, mi::MethodInstance, argtypes::Vector{Any}) - if isa(src, CodeInfo) || isa(src, Vector{UInt8}) - src_inferred = is_source_inferred(src) + if isa(src, MaybeCompressed) + is_source_inferred(src) || return nothing src_inlineable = is_stmt_inline(stmt_flag) || is_inlineable(src) - return src_inferred && src_inlineable ? src : nothing + return src_inlineable ? src : nothing elseif src === nothing && is_stmt_inline(stmt_flag) # if this statement is forced to be inlined, make an additional effort to find the # inferred source in the local cache @@ -413,7 +422,7 @@ function finish(interp::AbstractInterpreter, opt::OptimizationState, (; def, specTypes) = linfo analyzed = nothing # `ConstAPI` if this call can use constant calling convention - force_noinline = _any(x::Expr -> x.head === :meta && x.args[1] === :noinline, ir.meta) + force_noinline = is_declared_noinline(src) # compute inlining and other related optimizations result = caller.result @@ -483,15 +492,16 @@ function finish(interp::AbstractInterpreter, opt::OptimizationState, else force_noinline = true end - if !is_inlineable(src) && result === Bottom + if !is_declared_inline(src) && result === Bottom force_noinline = true end end if force_noinline set_inlineable!(src, false) elseif isa(def, Method) - if is_inlineable(src) && isdispatchtuple(specTypes) + if is_declared_inline(src) && isdispatchtuple(specTypes) # obey @inline declaration if a dispatch barrier would not help + set_inlineable!(src, true) else # compute the cost (size) of inlining this code cost_threshold = default = params.inline_cost_threshold @@ -499,7 +509,7 @@ function finish(interp::AbstractInterpreter, opt::OptimizationState, cost_threshold += params.inline_tupleret_bonus end # if the method is declared as `@inline`, increase the cost threshold 20x - if is_inlineable(src) + if is_declared_inline(src) cost_threshold += 19*default end # a few functions get special treatment diff --git a/base/compiler/utilities.jl b/base/compiler/utilities.jl index 624b024841ae6d..0c263931d8fd2d 100644 --- a/base/compiler/utilities.jl +++ b/base/compiler/utilities.jl @@ -225,6 +225,27 @@ function specialize_method(match::MethodMatch; kwargs...) return specialize_method(match.method, match.spec_types, match.sparams; kwargs...) end +""" + is_declared_inline(method::Method) -> Bool + +Check if `method` is declared as `@inline`. +""" +is_declared_inline(method::Method) = _is_declared_inline(method, true) + +""" + is_declared_noinline(method::Method) -> Bool + +Check if `method` is declared as `@noinline`. +""" +is_declared_noinline(method::Method) = _is_declared_inline(method, false) + +function _is_declared_inline(method::Method, inline::Bool) + isdefined(method, :source) || return false + src = method.source + isa(src, MaybeCompressed) || return false + return (inline ? is_declared_inline : is_declared_noinline)(src) +end + """ is_aggressive_constprop(method::Union{Method,CodeInfo}) -> Bool diff --git a/src/codegen.cpp b/src/codegen.cpp index 6a214c54244a60..3e23536c844b30 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -6877,7 +6877,7 @@ static jl_llvm_functions_t FnAttrs.addAttribute(polly::PollySkipFnAttr); #endif - if (jl_has_meta(stmts, jl_noinline_sym)) + if (src->inlining == 2) FnAttrs.addAttribute(Attribute::NoInline); #ifdef JL_DEBUG_BUILD diff --git a/src/ircode.c b/src/ircode.c index 648b954449aa2c..42bf3f4e7ec3d8 100644 --- a/src/ircode.c +++ b/src/ircode.c @@ -434,12 +434,14 @@ static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal) } } -static jl_code_info_flags_t code_info_flags(uint8_t pure, uint8_t propagate_inbounds, uint8_t inferred, uint8_t constprop) +static jl_code_info_flags_t code_info_flags(uint8_t pure, uint8_t propagate_inbounds, uint8_t inferred, + uint8_t inlining, uint8_t constprop) { jl_code_info_flags_t flags; flags.bits.pure = pure; flags.bits.propagate_inbounds = propagate_inbounds; flags.bits.inferred = inferred; + flags.bits.inlining = inlining; flags.bits.constprop = constprop; return flags; } @@ -778,7 +780,8 @@ JL_DLLEXPORT jl_array_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code) 1 }; - jl_code_info_flags_t flags = code_info_flags(code->pure, code->propagate_inbounds, code->inferred, code->constprop); + jl_code_info_flags_t flags = code_info_flags(code->pure, code->propagate_inbounds, code->inferred, + code->inlining, code->constprop); write_uint8(s.s, flags.packed); write_uint8(s.s, code->purity.bits); write_uint16(s.s, code->inlining_cost); @@ -873,6 +876,7 @@ JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t jl_code_info_t *code = jl_new_code_info_uninit(); jl_code_info_flags_t flags; flags.packed = read_uint8(s.s); + code->inlining = flags.bits.inlining; code->constprop = flags.bits.constprop; code->inferred = flags.bits.inferred; code->propagate_inbounds = flags.bits.propagate_inbounds; @@ -944,6 +948,16 @@ JL_DLLEXPORT uint8_t jl_ir_flag_inferred(jl_array_t *data) return flags.bits.inferred; } +JL_DLLEXPORT uint8_t jl_ir_flag_inlining(jl_array_t *data) +{ + if (jl_is_code_info(data)) + return ((jl_code_info_t*)data)->inlining; + assert(jl_typeis(data, jl_array_uint8_type)); + jl_code_info_flags_t flags; + flags.packed = ((uint8_t*)data->data)[0]; + return flags.bits.inlining; +} + JL_DLLEXPORT uint8_t jl_ir_flag_pure(jl_array_t *data) { if (jl_is_code_info(data)) diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index bd587e28abca78..76c2c651dad3df 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -275,6 +275,7 @@ XX(jl_ios_get_nbyte_int) \ XX(jl_ir_flag_inferred) \ XX(jl_ir_inlining_cost) \ + XX(jl_ir_flag_inlining) \ XX(jl_ir_flag_pure) \ XX(jl_ir_nslots) \ XX(jl_ir_slotflag) \ diff --git a/src/jltypes.c b/src/jltypes.c index a950d147d0ae73..8d1862f829598a 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -2432,7 +2432,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_code_info_type = jl_new_datatype(jl_symbol("CodeInfo"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(21, + jl_perm_symsvec(22, "code", "codelocs", "ssavaluetypes", @@ -2452,9 +2452,10 @@ void jl_init_types(void) JL_GC_DISABLED "propagate_inbounds", "pure", "has_fcall", + "inlining", "constprop", "purity"), - jl_svec(21, + jl_svec(22, jl_array_any_type, jl_array_int32_type, jl_any_type, @@ -2475,6 +2476,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_bool_type, jl_bool_type, jl_uint8_type, + jl_uint8_type, jl_uint8_type), jl_emptysvec, 0, 1, 20); diff --git a/src/julia.h b/src/julia.h index 7183383a924855..04326b712fdf4a 100644 --- a/src/julia.h +++ b/src/julia.h @@ -288,6 +288,7 @@ typedef struct _jl_code_info_t { uint8_t pure; uint8_t has_fcall; // uint8 settings + uint8_t inlining; // 0 = default; 1 = @inline; 2 = @noinline uint8_t constprop; // 0 = use heuristic; 1 = aggressive; 2 = none _jl_purity_overrides_t purity; } jl_code_info_t; @@ -1838,6 +1839,7 @@ JL_DLLEXPORT jl_value_t *jl_copy_ast(jl_value_t *expr JL_MAYBE_UNROOTED); JL_DLLEXPORT jl_array_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code); JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t *metadata, jl_array_t *data); JL_DLLEXPORT uint8_t jl_ir_flag_inferred(jl_array_t *data) JL_NOTSAFEPOINT; +JL_DLLEXPORT uint8_t jl_ir_flag_inlining(jl_array_t *data) JL_NOTSAFEPOINT; JL_DLLEXPORT uint8_t jl_ir_flag_pure(jl_array_t *data) JL_NOTSAFEPOINT; JL_DLLEXPORT uint16_t jl_ir_inlining_cost(jl_array_t *data) JL_NOTSAFEPOINT; JL_DLLEXPORT ssize_t jl_ir_nslots(jl_array_t *data) JL_NOTSAFEPOINT; diff --git a/src/julia_internal.h b/src/julia_internal.h index 223d6b5ce12d89..710c0d8d3dc374 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -599,6 +599,7 @@ typedef struct { uint8_t pure:1; uint8_t propagate_inbounds:1; uint8_t inferred:1; + uint8_t inlining:2; // 0 = use heuristic; 1 = aggressive; 2 = none uint8_t constprop:2; // 0 = use heuristic; 1 = aggressive; 2 = none } jl_code_info_flags_bitfield_t; diff --git a/src/method.c b/src/method.c index 850e520c945291..01d77888ef3c9a 100644 --- a/src/method.c +++ b/src/method.c @@ -315,7 +315,9 @@ static void jl_code_info_set_ir(jl_code_info_t *li, jl_expr_t *ir) if (ma == (jl_value_t*)jl_pure_sym) li->pure = 1; else if (ma == (jl_value_t*)jl_inline_sym) - li->inlining_cost = 0x10; // This corresponds to MIN_INLINE_COST + li->inlining = 1; + else if (ma == (jl_value_t*)jl_noinline_sym) + li->inlining = 2; else if (ma == (jl_value_t*)jl_propagate_inbounds_sym) li->propagate_inbounds = 1; else if (ma == (jl_value_t*)jl_aggressive_constprop_sym) @@ -477,6 +479,7 @@ JL_DLLEXPORT jl_code_info_t *jl_new_code_info_uninit(void) src->has_fcall = 0; src->edges = jl_nothing; src->constprop = 0; + src->inlining = 0; src->purity.bits = 0; return src; } diff --git a/stdlib/Serialization/src/Serialization.jl b/stdlib/Serialization/src/Serialization.jl index e36b5c67e22829..f0f5ff38970a60 100644 --- a/stdlib/Serialization/src/Serialization.jl +++ b/stdlib/Serialization/src/Serialization.jl @@ -79,7 +79,7 @@ const TAGS = Any[ @assert length(TAGS) == 255 -const ser_version = 20 # do not make changes without bumping the version #! +const ser_version = 21 # do not make changes without bumping the version #! format_version(::AbstractSerializer) = ser_version format_version(s::Serializer) = s.version @@ -1027,8 +1027,7 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) nargs = deserialize(s)::Int32 isva = deserialize(s)::Bool is_for_opaque_closure = false - constprop = 0x00 - purity = 0x00 + constprop = purity = 0x00 template_or_is_opaque = deserialize(s) if isa(template_or_is_opaque, Bool) is_for_opaque_closure = template_or_is_opaque @@ -1194,6 +1193,9 @@ function deserialize(s::AbstractSerializer, ::Type{CodeInfo}) if format_version(s) >= 20 ci.has_fcall = deserialize(s) end + if format_version(s) >= 21 + ci.inlining = deserialize(s)::UInt8 + end if format_version(s) >= 14 ci.constprop = deserialize(s)::UInt8 end diff --git a/test/compiler/inline.jl b/test/compiler/inline.jl index 431a3fddd7fca2..77113b49a3c310 100644 --- a/test/compiler/inline.jl +++ b/test/compiler/inline.jl @@ -273,19 +273,29 @@ f34900(x, y::Int) = y f34900(x::Int, y::Int) = invoke(f34900, Tuple{Int, Any}, x, y) @test fully_eliminated(f34900, Tuple{Int, Int}; retval=Core.Argument(2)) -using Core.Compiler: is_inlineable, set_inlineable! - -@testset "check jl_ir_inlining_cost for inline macro" begin - @test is_inlineable(only(methods(@inline x -> x)).source) - @test is_inlineable(only(methods(x -> (@inline; x))).source) - @test !is_inlineable(only(methods(x -> x)).source) - @test is_inlineable(only(methods(@inline function f(x) x end)).source) - @test is_inlineable(only(methods(function f(x) @inline; x end)).source) - @test !is_inlineable(only(methods(function f(x) x end)).source) - @test is_inlineable(only(methods() do x @inline; x end).source) - @test !is_inlineable(only(methods() do x x end).source) +using Core.Compiler: is_declared_inline, is_declared_noinline + +@testset "is_declared_[no]inline" begin + @test is_declared_inline(only(methods(@inline x -> x))) + @test is_declared_inline(only(methods(x -> (@inline; x)))) + @test is_declared_inline(only(methods(@inline function f(x) x end))) + @test is_declared_inline(only(methods(function f(x) @inline; x end))) + @test is_declared_inline(only(methods() do x @inline; x end)) + @test is_declared_noinline(only(methods(@noinline x -> x))) + @test is_declared_noinline(only(methods(x -> (@noinline; x)))) + @test is_declared_noinline(only(methods(@noinline function f(x) x end))) + @test is_declared_noinline(only(methods(function f(x) @noinline; x end))) + @test is_declared_noinline(only(methods() do x @noinline; x end)) + @test !is_declared_inline(only(methods(x -> x))) + @test !is_declared_noinline(only(methods(x -> x))) + @test !is_declared_inline(only(methods(function f(x) x end))) + @test !is_declared_noinline(only(methods(function f(x) x end))) + @test !is_declared_inline(only(methods() do x x end)) + @test !is_declared_noinline(only(methods() do x x end)) end +using Core.Compiler: is_inlineable, set_inlineable! + @testset "basic set_inlineable! functionality" begin ci = code_typed1() do x -> x @@ -1567,16 +1577,18 @@ end end end +using Core.Compiler: is_declared_inline, is_declared_noinline + # https://github.com/JuliaLang/julia/issues/45050 @testset "propagate :meta annotations to keyword sorter methods" begin # @inline, @noinline, @constprop let @inline f(::Any; x::Int=1) = 2x - @test is_inlineable(only(methods(f)).source) - @test is_inlineable(only(methods(Core.kwcall, (Any, typeof(f), Vararg))).source) + @test is_declared_inline(only(methods(f))) + @test is_declared_inline(only(methods(Core.kwcall, (Any, typeof(f), Vararg)))) end let @noinline f(::Any; x::Int=1) = 2x - @test !is_inlineable(only(methods(f)).source) - @test !is_inlineable(only(methods(Core.kwcall, (Any, typeof(f), Vararg))).source) + @test is_declared_noinline(only(methods(f))) + @test is_declared_noinline(only(methods(Core.kwcall, (Any, typeof(f), Vararg)))) end let Base.@constprop :aggressive f(::Any; x::Int=1) = 2x @test Core.Compiler.is_aggressive_constprop(only(methods(f))) @@ -1602,9 +1614,9 @@ end end # propagate multiple metadata also let @inline Base.@assume_effects :notaskstate Base.@constprop :aggressive f(::Any; x::Int=1) = (@nospecialize; 2x) - @test is_inlineable(only(methods(f)).source) + @test is_declared_inline(only(methods(f))) @test Core.Compiler.is_aggressive_constprop(only(methods(f))) - @test is_inlineable(only(methods(Core.kwcall, (Any, typeof(f), Vararg))).source) + @test is_declared_inline(only(methods(Core.kwcall, (Any, typeof(f), Vararg)))) @test Core.Compiler.is_aggressive_constprop(only(methods(Core.kwcall, (Any, typeof(f), Vararg)))) @test only(methods(f)).nospecialize == -1 @test only(methods(Core.kwcall, (Any, typeof(f), Vararg))).nospecialize == -1 diff --git a/test/compiler/ssair.jl b/test/compiler/ssair.jl index a9107608620ac8..6f485a7c781ec9 100644 --- a/test/compiler/ssair.jl +++ b/test/compiler/ssair.jl @@ -106,15 +106,6 @@ for compile in ("min", "yes") end end -# Issue #27104 -# Test whether meta nodes are still present after code optimization. -let - @noinline f(x, y) = x + y - @test any(code_typed(f)[1][1].code) do ex - Meta.isexpr(ex, :meta) - end -end - # PR #32145 # Make sure IncrementalCompact can handle blocks with predecessors of index 0 # while removing blocks with no predecessors. diff --git a/test/syntax.jl b/test/syntax.jl index 1fb040c7cbdac9..2410e5c8a8b629 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -547,7 +547,7 @@ end # meta nodes for optional positional arguments let src = Meta.lower(Main, :(@inline f(p::Int=2) = 3)).args[1].code[end-1].args[3] - @test Core.Compiler.is_inlineable(src) + @test Core.Compiler.is_declared_inline(src) end # issue #16096 @@ -1558,9 +1558,8 @@ end # issue #27129 f27129(x = 1) = (@inline; x) -for meth in methods(f27129) - src = ccall(:jl_uncompress_ir, Any, (Any, Ptr{Cvoid}, Any), meth, C_NULL, meth.source) - @test Core.Compiler.is_inlineable(src) +for method in methods(f27129) + @test Core.Compiler.is_declared_inline(method) end # issue #27710 From b43ef97dbc496c3982b1afbc73bc7d3a43905cc6 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Fri, 13 Jan 2023 13:31:27 +0900 Subject: [PATCH 312/387] NFC followups for #48246 (#48264) --- base/compiler/abstractinterpretation.jl | 18 ++++++++---------- base/compiler/effects.jl | 4 ++-- base/compiler/inferencestate.jl | 2 +- base/expr.jl | 4 ++-- test/compiler/effects.jl | 1 + 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 4a05503cfe4bbe..38367269391738 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2191,14 +2191,13 @@ function abstract_eval_value_expr(interp::AbstractInterpreter, e::Expr, vtypes:: end elseif head === :boundscheck if isa(sv, InferenceState) - flag = sv.src.ssaflags[sv.currpc] - # If there is no particular @inbounds for this function, then we only taint `noinbounds`, - # which will subsequently taint consistency if this function is called from another - # function that uses `@inbounds`. However, if this :boundscheck is itself within an + # If there is no particular `@inbounds` for this function, then we only taint `:noinbounds`, + # which will subsequently taint `:consistent`-cy if this function is called from another + # function that uses `@inbounds`. However, if this `:boundscheck` is itself within an # `@inbounds` region, its value depends on `--check-bounds`, so we need to taint - # consistency here also. + # `:consistent`-cy here also. merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; noinbounds=false, - consistent = (flag & IR_FLAG_INBOUNDS) != 0 ? ALWAYS_FALSE : ALWAYS_TRUE)) + consistent = (get_curr_ssaflag(sv) & IR_FLAG_INBOUNDS) != 0 ? ALWAYS_FALSE : ALWAYS_TRUE)) end rt = Bool elseif head === :inbounds @@ -2515,8 +2514,8 @@ function abstract_eval_phi(interp::AbstractInterpreter, phi::PhiNode, vtypes::Un end function stmt_taints_inbounds_consistency(sv::InferenceState) - flag = sv.src.ssaflags[sv.currpc] - return sv.src.propagate_inbounds || (flag & IR_FLAG_INBOUNDS) != 0 + sv.src.propagate_inbounds && return true + return (get_curr_ssaflag(sv) & IR_FLAG_INBOUNDS) != 0 end function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), vtypes::VarTable, sv::InferenceState) @@ -2528,13 +2527,12 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), end (;rt, effects) = abstract_eval_statement_expr(interp, e, vtypes, sv, nothing) if !effects.noinbounds - flag = sv.src.ssaflags[sv.currpc] if !sv.src.propagate_inbounds # The callee read our inbounds flag, but unless we propagate inbounds, # we ourselves don't read our parent's inbounds. effects = Effects(effects; noinbounds=true) end - if (flag & IR_FLAG_INBOUNDS) != 0 + if (get_curr_ssaflag(sv) & IR_FLAG_INBOUNDS) != 0 effects = Effects(effects; consistent=ALWAYS_FALSE) end end diff --git a/base/compiler/effects.jl b/base/compiler/effects.jl index 48842d7f51cd8c..27e41bf04865d8 100644 --- a/base/compiler/effects.jl +++ b/base/compiler/effects.jl @@ -40,8 +40,8 @@ following meanings: This state corresponds to LLVM's `inaccessiblemem_or_argmemonly` function attribute. - `nonoverlayed::Bool`: indicates that any methods that may be called within this method are not defined in an [overlayed method table](@ref OverlayMethodTable). -- `noinbounds::Bool`: If set, indicates that this method does not read the parent's :inbounds - state. In particular, it does not have any reached :boundscheck exprs, not propagates inbounds +- `noinbounds::Bool`: If set, indicates that this method does not read the parent's `:inbounds` + state. In particular, it does not have any reached `:boundscheck` exprs, not propagates inbounds to any children that do. Note that the representations above are just internal implementation details and thus likely diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index 37d644bcfc9811..0ebcd94409aa2b 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -177,7 +177,7 @@ mutable struct InferenceState valid_worlds = WorldRange(src.min_world, src.max_world == typemax(UInt) ? get_world_counter() : src.max_world) bestguess = Bottom - ipo_effects = Effects(EFFECTS_TOTAL) + ipo_effects = EFFECTS_TOTAL params = InferenceParams(interp) restrict_abstract_call_sites = isa(linfo.def, Module) diff --git a/base/expr.jl b/base/expr.jl index 6f6fcd86fc680c..a0a9a5676c760d 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -608,8 +608,8 @@ currently equivalent to the following `setting`s: must consistently throw given the same argument values. !!! note - An explict `@inbounds` annotation inside the function will also disable - constant propagation and not be overriden by :foldable. + An explicit `@inbounds` annotation inside the function will also disable + constant folding and not be overriden by `:foldable`. --- ## `:removable` diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl index 809d7e2d37f5bb..fc5fb6d3b9cecc 100644 --- a/test/compiler/effects.jl +++ b/test/compiler/effects.jl @@ -722,6 +722,7 @@ end |> Core.Compiler.is_foldable end |> Core.Compiler.is_total # Test that dead `@inbounds` does not taint consistency +# https://github.com/JuliaLang/julia/issues/48243 @test Base.infer_effects() do false && @inbounds (1,2,3)[1] return 1 From 8ad9dbd2679d9bafe9387eb3afa143ddea332ef6 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Thu, 12 Jan 2023 23:33:10 -0500 Subject: [PATCH 313/387] Improve effects for NamedTuple merge/diff fallback (#48262) This fallback path is rarely used when the compiler is available. However, inference does look at it to determine effects for the entire method (side note, it's not entirely clear that this is sound for `if @generated` methods, but that's a more general problem). Previously inference was able to determine neither effects nor return type for `merge`/`structdiff` of unknown `NamedTuples`, which was problematic, because it prevented other methods that made use of these primitives from having sufficient effects to be eligible for concrete evaluation. Co-authored-by: Shuhei Kadowaki --- base/namedtuple.jl | 42 ++++++++++++++++++++++++++++++++---------- test/namedtuple.jl | 6 ++++++ 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 9713ca97980fd9..7549014abe3d10 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -267,6 +267,19 @@ end return Tuple{Any[ fieldtype(sym_in(names[n], bn) ? b : a, names[n]) for n in 1:length(names) ]...} end +@assume_effects :foldable function merge_fallback(@nospecialize(a::NamedTuple), @nospecialize(b::NamedTuple), + @nospecialize(an::Tuple{Vararg{Symbol}}), @nospecialize(bn::Tuple{Vararg{Symbol}})) + names = merge_names(an, bn) + types = merge_types(names, typeof(a), typeof(b)) + n = length(names) + A = Vector{Any}(undef, n) + for i=1:n + n = names[i] + A[i] = getfield(sym_in(n, bn) ? b : a, n) + end + NamedTuple{names}((A...,))::NamedTuple{names, types} +end + """ merge(a::NamedTuple, bs::NamedTuple...) @@ -299,9 +312,7 @@ function merge(a::NamedTuple{an}, b::NamedTuple{bn}) where {an, bn} vals = Any[ :(getfield($(sym_in(names[n], bn) ? :b : :a), $(QuoteNode(names[n])))) for n in 1:length(names) ] :( NamedTuple{$names,$types}(($(vals...),)) ) else - names = merge_names(an, bn) - types = merge_types(names, typeof(a), typeof(b)) - NamedTuple{names,types}(map(n->getfield(sym_in(n, bn) ? b : a, n), names)) + merge_fallback(a, b, an, bn) end end @@ -365,6 +376,23 @@ reverse(nt::NamedTuple) = NamedTuple{reverse(keys(nt))}(reverse(values(nt))) (names...,) end +@assume_effects :foldable function diff_types(@nospecialize(a::NamedTuple), @nospecialize(names::Tuple{Vararg{Symbol}})) + return Tuple{Any[ fieldtype(typeof(a), names[n]) for n in 1:length(names) ]...} +end + +@assume_effects :foldable function diff_fallback(@nospecialize(a::NamedTuple), @nospecialize(an::Tuple{Vararg{Symbol}}), @nospecialize(bn::Tuple{Vararg{Symbol}})) + names = diff_names(an, bn) + isempty(names) && return (;) + types = diff_types(a, names) + n = length(names) + A = Vector{Any}(undef, n) + for i=1:n + n = names[i] + A[i] = getfield(a, n) + end + NamedTuple{names}((A...,))::NamedTuple{names, types} +end + """ structdiff(a::NamedTuple, b::Union{NamedTuple,Type{NamedTuple}}) @@ -380,13 +408,7 @@ function structdiff(a::NamedTuple{an}, b::Union{NamedTuple{bn}, Type{NamedTuple{ vals = Any[ :(getfield(a, $(idx[n]))) for n in 1:length(idx) ] return :( NamedTuple{$names,$types}(($(vals...),)) ) else - names = diff_names(an, bn) - # N.B this early return is necessary to get a better type stability, - # and also allows us to cut off the cost from constructing - # potentially type unstable closure passed to the `map` below - isempty(names) && return (;) - types = Tuple{Any[ fieldtype(typeof(a), names[n]) for n in 1:length(names) ]...} - return NamedTuple{names,types}(map(n::Symbol->getfield(a, n), names)) + return diff_fallback(a, an, bn) end end diff --git a/test/namedtuple.jl b/test/namedtuple.jl index 79d0e4883f25d2..82a2bc7bf833dd 100644 --- a/test/namedtuple.jl +++ b/test/namedtuple.jl @@ -353,3 +353,9 @@ end @test mapfoldl(abs, Pair{Any,Any}, NamedTuple(Symbol(:x,i) => i for i in 1:30)) == mapfoldl(abs, Pair{Any,Any}, [1:30;]) @test_throws "reducing over an empty collection" mapfoldl(abs, =>, (;)) end + +# Test effect/inference for merge/diff of unknown NamedTuples +for f in (Base.merge, Base.structdiff) + @test Core.Compiler.is_foldable(Base.infer_effects(f, Tuple{NamedTuple, NamedTuple})) + @test Core.Compiler.return_type(f, Tuple{NamedTuple, NamedTuple}) == NamedTuple +end From 1ee253d3291728fc74051aff7cd6a5ed02b9f77d Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Fri, 13 Jan 2023 15:45:34 +0900 Subject: [PATCH 314/387] respect `@noinline` annotations when inlining semi-concrete eval result (#48266) --- base/compiler/optimize.jl | 8 ++++++-- base/compiler/ssair/inlining.jl | 28 +++++++++++++++++++--------- test/compiler/inline.jl | 27 ++++++++++++++++++++++++--- 3 files changed, 49 insertions(+), 14 deletions(-) diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index 5efad6a55f19ee..c295c70e5ee44f 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -109,8 +109,12 @@ function inlining_policy(interp::AbstractInterpreter, elseif isa(src, IRCode) return src elseif isa(src, SemiConcreteResult) - # For NativeInterpreter, SemiConcreteResult are only produced if they're supposed - # to be inlined. + if is_declared_noinline(mi.def::Method) + # For `NativeInterpreter`, `SemiConcreteResult` may be produced for + # a `@noinline`-declared method when it's marked as `@constprop :aggressive`. + # Suppress the inlining here. + return nothing + end return src end return nothing diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 09340d8f21637e..725c6a9e9265a7 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -1174,7 +1174,7 @@ function handle_invoke_call!(todo::Vector{Pair{Int,Any}}, result = info.result invokesig = sig.argtypes if isa(result, ConcreteResult) - item = concrete_result_item(result, state, info; invokesig) + item = concrete_result_item(result, info, state; invokesig) else argtypes = invoke_rewrite(sig.argtypes) if isa(result, ConstPropResult) @@ -1305,12 +1305,12 @@ function handle_any_const_result!(cases::Vector{InliningCase}, @nospecialize(info::CallInfo), flag::UInt8, state::InliningState; allow_abstract::Bool, allow_typevars::Bool) if isa(result, ConcreteResult) - return handle_concrete_result!(cases, result, state, info) + return handle_concrete_result!(cases, result, info, state) end if isa(result, SemiConcreteResult) result = inlining_policy(state.interp, result, info, flag, result.mi, argtypes) if isa(result, SemiConcreteResult) - return handle_semi_concrete_result!(cases, result; allow_abstract) + return handle_semi_concrete_result!(cases, result, info, flag, state; allow_abstract) end end if isa(result, ConstPropResult) @@ -1477,17 +1477,27 @@ function handle_const_prop_result!(cases::Vector{InliningCase}, return true end -function handle_semi_concrete_result!(cases::Vector{InliningCase}, result::SemiConcreteResult; allow_abstract::Bool) +function handle_semi_concrete_result!(cases::Vector{InliningCase}, result::SemiConcreteResult, + @nospecialize(info::CallInfo), flag::UInt8, state::InliningState; + allow_abstract::Bool) mi = result.mi spec_types = mi.specTypes allow_abstract || isdispatchtuple(spec_types) || return false validate_sparams(mi.sparam_vals) || return false - push!(cases, InliningCase(spec_types, InliningTodo(mi, result.ir, result.effects))) + if !state.params.inlining || is_stmt_noinline(flag) + et = InliningEdgeTracker(state.et, nothing) + item = compileable_specialization(mi, result.effects, et, info; + compilesig_invokes=state.params.compilesig_invokes) + item === nothing && return false + else + item = InliningTodo(mi, result.ir, result.effects) + end + push!(cases, InliningCase(spec_types, item)) return true end -function handle_concrete_result!(cases::Vector{InliningCase}, result::ConcreteResult, state::InliningState, @nospecialize(info::CallInfo)) - case = concrete_result_item(result, state, info) +function handle_concrete_result!(cases::Vector{InliningCase}, result::ConcreteResult, @nospecialize(info::CallInfo), state::InliningState) + case = concrete_result_item(result, info, state) push!(cases, InliningCase(result.mi.specTypes, case)) return true end @@ -1495,7 +1505,7 @@ end may_inline_concrete_result(result::ConcreteResult) = isdefined(result, :result) && is_inlineable_constant(result.result) -function concrete_result_item(result::ConcreteResult, state::InliningState, @nospecialize(info::CallInfo); +function concrete_result_item(result::ConcreteResult, @nospecialize(info::CallInfo), state::InliningState; invokesig::Union{Nothing,Vector{Any}}=nothing) if !may_inline_concrete_result(result) et = InliningEdgeTracker(state.et, invokesig) @@ -1537,7 +1547,7 @@ function handle_opaque_closure_call!(todo::Vector{Pair{Int,Any}}, validate_sparams(mi.sparam_vals) || return nothing item = resolve_todo(mi, result.result, sig.argtypes, info, flag, state) elseif isa(result, ConcreteResult) - item = concrete_result_item(result, state, info) + item = concrete_result_item(result, info, state) else item = analyze_method!(info.match, sig.argtypes, info, flag, state; allow_typevars=false) end diff --git a/test/compiler/inline.jl b/test/compiler/inline.jl index 77113b49a3c310..af83fe0df45d7b 100644 --- a/test/compiler/inline.jl +++ b/test/compiler/inline.jl @@ -1660,18 +1660,39 @@ end end end -# Test that semi-concrete eval can inline constant results function twice_sitofp(x::Int, y::Int) x = Base.sitofp(Float64, x) y = Base.sitofp(Float64, y) return (x, y) end -call_twice_sitofp(x::Int) = twice_sitofp(x, 2) -let src = code_typed1(call_twice_sitofp, (Int,)) +# Test that semi-concrete eval can inline constant results +let src = code_typed1((Int,)) do x + twice_sitofp(x, 2) + end @test count(iscall((src, Base.sitofp)), src.code) == 1 end +# `@noinline` annotations with semi-concrete eval +let src = code_typed1((Int,)) do x + @noinline twice_sitofp(x, 2) + end + @test count(isinvoke(:twice_sitofp), src.code) == 1 +end + +# `Base.@constprop :aggressive` forces semi-concrete eval, but it should still not be inlined +@noinline Base.@constprop :aggressive function twice_sitofp_noinline(x::Int, y::Int) + x = Base.sitofp(Float64, x) + y = Base.sitofp(Float64, y) + return (x, y) +end + +let src = code_typed1((Int,)) do x + twice_sitofp_noinline(x, 2) + end + @test count(isinvoke(:twice_sitofp_noinline), src.code) == 1 +end + # Test getfield modeling of Type{Ref{_A}} where _A let getfield_tfunc(@nospecialize xs...) = Core.Compiler.getfield_tfunc(Core.Compiler.fallback_lattice, xs...) From 4dad6d30f45a0a0c239d68c942f2a972a40b49b4 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Fri, 13 Jan 2023 18:00:29 +0900 Subject: [PATCH 315/387] avoid calling `uncompressed_ir` when checking `has_fcall` (#48258) We don't need to allocate new `CodeInfo` just to check `has_fcall` since the equivalent information can be encoded in `Method` object. --- base/compiler/ssair/inlining.jl | 23 ++++++-------------- src/ircode.c | 26 ++++++++++++++++------- src/jl_exported_funcs.inc | 5 +++-- src/jltypes.c | 8 +++---- src/julia.h | 5 ++++- src/julia_internal.h | 5 +++-- src/method.c | 2 +- stdlib/Serialization/src/Serialization.jl | 17 +++++++++------ 8 files changed, 50 insertions(+), 41 deletions(-) diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 725c6a9e9265a7..87210ccae48893 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -916,22 +916,11 @@ function validate_sparams(sparams::SimpleVector) end function may_have_fcalls(m::Method) - may_have_fcall = true - if isdefined(m, :source) - src = m.source - isa(src, Vector{UInt8}) && (src = uncompressed_ir(m)) - if isa(src, CodeInfo) - may_have_fcall = src.has_fcall - end - end - return may_have_fcall -end - -function can_inline_typevars(method::Method, argtypes::Vector{Any}) - may_have_fcalls(method) && return false - return true + isdefined(m, :source) || return true + src = m.source + isa(src, CodeInfo) || isa(src, Vector{UInt8}) || return true + return ccall(:jl_ir_flag_has_fcall, Bool, (Any,), src) end -can_inline_typevars(m::MethodMatch, argtypes::Vector{Any}) = can_inline_typevars(m.method, argtypes) function analyze_method!(match::MethodMatch, argtypes::Vector{Any}, @nospecialize(info::CallInfo), flag::UInt8, state::InliningState; @@ -958,7 +947,7 @@ function analyze_method!(match::MethodMatch, argtypes::Vector{Any}, end if !validate_sparams(match.sparams) - (allow_typevars && can_inline_typevars(match, argtypes)) || return nothing + (allow_typevars && !may_have_fcalls(match.method)) || return nothing end # See if there exists a specialization for this method signature @@ -1469,7 +1458,7 @@ function handle_const_prop_result!(cases::Vector{InliningCase}, spec_types = mi.specTypes allow_abstract || isdispatchtuple(spec_types) || return false if !validate_sparams(mi.sparam_vals) - (allow_typevars && can_inline_typevars(mi.def, argtypes)) || return false + (allow_typevars && !may_have_fcalls(mi.def::Method)) || return false end item = resolve_todo(mi, result.result, argtypes, info, flag, state) item === nothing && return false diff --git a/src/ircode.c b/src/ircode.c index 42bf3f4e7ec3d8..0e74f7700ebf22 100644 --- a/src/ircode.c +++ b/src/ircode.c @@ -434,13 +434,14 @@ static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal) } } -static jl_code_info_flags_t code_info_flags(uint8_t pure, uint8_t propagate_inbounds, uint8_t inferred, - uint8_t inlining, uint8_t constprop) +static jl_code_info_flags_t code_info_flags(uint8_t inferred, uint8_t propagate_inbounds, uint8_t pure, + uint8_t has_fcall, uint8_t inlining, uint8_t constprop) { jl_code_info_flags_t flags; - flags.bits.pure = pure; - flags.bits.propagate_inbounds = propagate_inbounds; flags.bits.inferred = inferred; + flags.bits.propagate_inbounds = propagate_inbounds; + flags.bits.pure = pure; + flags.bits.has_fcall = has_fcall; flags.bits.inlining = inlining; flags.bits.constprop = constprop; return flags; @@ -780,8 +781,8 @@ JL_DLLEXPORT jl_array_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code) 1 }; - jl_code_info_flags_t flags = code_info_flags(code->pure, code->propagate_inbounds, code->inferred, - code->inlining, code->constprop); + jl_code_info_flags_t flags = code_info_flags(code->inferred, code->propagate_inbounds, code->pure, + code->has_fcall, code->inlining, code->constprop); write_uint8(s.s, flags.packed); write_uint8(s.s, code->purity.bits); write_uint16(s.s, code->inlining_cost); @@ -835,7 +836,6 @@ JL_DLLEXPORT jl_array_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code) ios_write(s.s, (char*)jl_array_data(code->codelocs), nstmt * sizeof(int32_t)); } - write_uint8(s.s, code->has_fcall); write_uint8(s.s, s.relocatability); ios_flush(s.s); @@ -881,6 +881,7 @@ JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t code->inferred = flags.bits.inferred; code->propagate_inbounds = flags.bits.propagate_inbounds; code->pure = flags.bits.pure; + code->has_fcall = flags.bits.has_fcall; code->purity.bits = read_uint8(s.s); code->inlining_cost = read_uint16(s.s); @@ -919,7 +920,6 @@ JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t ios_readall(s.s, (char*)jl_array_data(code->codelocs), nstmt * sizeof(int32_t)); } - code->has_fcall = read_uint8(s.s); (void) read_uint8(s.s); // relocatability assert(ios_getc(s.s) == -1); @@ -968,6 +968,16 @@ JL_DLLEXPORT uint8_t jl_ir_flag_pure(jl_array_t *data) return flags.bits.pure; } +JL_DLLEXPORT uint8_t jl_ir_flag_has_fcall(jl_array_t *data) +{ + if (jl_is_code_info(data)) + return ((jl_code_info_t*)data)->has_fcall; + assert(jl_typeis(data, jl_array_uint8_type)); + jl_code_info_flags_t flags; + flags.packed = ((uint8_t*)data->data)[0]; + return flags.bits.has_fcall; +} + JL_DLLEXPORT uint16_t jl_ir_inlining_cost(jl_array_t *data) { if (jl_is_code_info(data)) diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index 76c2c651dad3df..c475184573faa0 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -274,9 +274,10 @@ XX(jl_ios_fd) \ XX(jl_ios_get_nbyte_int) \ XX(jl_ir_flag_inferred) \ - XX(jl_ir_inlining_cost) \ - XX(jl_ir_flag_inlining) \ XX(jl_ir_flag_pure) \ + XX(jl_ir_flag_has_fcall) \ + XX(jl_ir_flag_inlining) \ + XX(jl_ir_inlining_cost) \ XX(jl_ir_nslots) \ XX(jl_ir_slotflag) \ XX(jl_isa) \ diff --git a/src/jltypes.c b/src/jltypes.c index 8d1862f829598a..9428bf6a910928 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -2448,13 +2448,13 @@ void jl_init_types(void) JL_GC_DISABLED "min_world", "max_world", "inferred", - "inlining_cost", "propagate_inbounds", "pure", "has_fcall", "inlining", "constprop", - "purity"), + "purity", + "inlining_cost"), jl_svec(22, jl_array_any_type, jl_array_int32_type, @@ -2471,13 +2471,13 @@ void jl_init_types(void) JL_GC_DISABLED jl_ulong_type, jl_ulong_type, jl_bool_type, - jl_uint16_type, jl_bool_type, jl_bool_type, jl_bool_type, jl_uint8_type, jl_uint8_type, - jl_uint8_type), + jl_uint8_type, + jl_uint16_type), jl_emptysvec, 0, 1, 20); diff --git a/src/julia.h b/src/julia.h index 04326b712fdf4a..d2ffa13dc2796e 100644 --- a/src/julia.h +++ b/src/julia.h @@ -283,7 +283,6 @@ typedef struct _jl_code_info_t { size_t max_world; // various boolean properties: uint8_t inferred; - uint16_t inlining_cost; uint8_t propagate_inbounds; uint8_t pure; uint8_t has_fcall; @@ -291,6 +290,8 @@ typedef struct _jl_code_info_t { uint8_t inlining; // 0 = default; 1 = @inline; 2 = @noinline uint8_t constprop; // 0 = use heuristic; 1 = aggressive; 2 = none _jl_purity_overrides_t purity; + // uint16 settings + uint16_t inlining_cost; } jl_code_info_t; // This type describes a single method definition, and stores data @@ -339,6 +340,7 @@ typedef struct _jl_method_t { uint32_t nospecialize; // bit flags: which arguments should not be specialized uint32_t nkw; // # of leading arguments that are actually keyword arguments // of another method. + // various boolean properties uint8_t isva; uint8_t pure; uint8_t is_for_opaque_closure; @@ -1841,6 +1843,7 @@ JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t JL_DLLEXPORT uint8_t jl_ir_flag_inferred(jl_array_t *data) JL_NOTSAFEPOINT; JL_DLLEXPORT uint8_t jl_ir_flag_inlining(jl_array_t *data) JL_NOTSAFEPOINT; JL_DLLEXPORT uint8_t jl_ir_flag_pure(jl_array_t *data) JL_NOTSAFEPOINT; +JL_DLLEXPORT uint8_t jl_ir_flag_has_fcall(jl_array_t *data) JL_NOTSAFEPOINT; JL_DLLEXPORT uint16_t jl_ir_inlining_cost(jl_array_t *data) JL_NOTSAFEPOINT; JL_DLLEXPORT ssize_t jl_ir_nslots(jl_array_t *data) JL_NOTSAFEPOINT; JL_DLLEXPORT uint8_t jl_ir_slotflag(jl_array_t *data, size_t i) JL_NOTSAFEPOINT; diff --git a/src/julia_internal.h b/src/julia_internal.h index 710c0d8d3dc374..6ea8ecafc2497b 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -596,9 +596,10 @@ STATIC_INLINE jl_value_t *undefref_check(jl_datatype_t *dt, jl_value_t *v) JL_NO // -- helper types -- // typedef struct { - uint8_t pure:1; - uint8_t propagate_inbounds:1; uint8_t inferred:1; + uint8_t propagate_inbounds:1; + uint8_t pure:1; + uint8_t has_fcall:1; uint8_t inlining:2; // 0 = use heuristic; 1 = aggressive; 2 = none uint8_t constprop:2; // 0 = use heuristic; 1 = aggressive; 2 = none } jl_code_info_flags_bitfield_t; diff --git a/src/method.c b/src/method.c index 01d77888ef3c9a..6b13045db81a75 100644 --- a/src/method.c +++ b/src/method.c @@ -473,7 +473,6 @@ JL_DLLEXPORT jl_code_info_t *jl_new_code_info_uninit(void) src->min_world = 1; src->max_world = ~(size_t)0; src->inferred = 0; - src->inlining_cost = UINT16_MAX; src->propagate_inbounds = 0; src->pure = 0; src->has_fcall = 0; @@ -481,6 +480,7 @@ JL_DLLEXPORT jl_code_info_t *jl_new_code_info_uninit(void) src->constprop = 0; src->inlining = 0; src->purity.bits = 0; + src->inlining_cost = UINT16_MAX; return src; } diff --git a/stdlib/Serialization/src/Serialization.jl b/stdlib/Serialization/src/Serialization.jl index f0f5ff38970a60..a45523bc94d7d6 100644 --- a/stdlib/Serialization/src/Serialization.jl +++ b/stdlib/Serialization/src/Serialization.jl @@ -79,7 +79,7 @@ const TAGS = Any[ @assert length(TAGS) == 255 -const ser_version = 21 # do not make changes without bumping the version #! +const ser_version = 22 # do not make changes without bumping the version #! format_version(::AbstractSerializer) = ser_version format_version(s::Serializer) = s.version @@ -1182,11 +1182,13 @@ function deserialize(s::AbstractSerializer, ::Type{CodeInfo}) end end ci.inferred = deserialize(s) - inlining = deserialize(s) - if isa(inlining, Bool) - Core.Compiler.set_inlineable!(ci, inlining) - else - ci.inlining_cost = inlining + if format_version(s) < 22 + inlining_cost = deserialize(s) + if isa(inlining_cost, Bool) + Core.Compiler.set_inlineable!(ci, inlining_cost) + else + ci.inlining_cost = inlining_cost + end end ci.propagate_inbounds = deserialize(s) ci.pure = deserialize(s) @@ -1202,6 +1204,9 @@ function deserialize(s::AbstractSerializer, ::Type{CodeInfo}) if format_version(s) >= 17 ci.purity = deserialize(s)::UInt8 end + if format_version(s) >= 22 + ci.inlining_cost = deserialize(s)::UInt16 + end return ci end From 29fbd1c04acf0a040ad270f2021a549e42514687 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Fri, 13 Jan 2023 16:28:23 +0100 Subject: [PATCH 316/387] also cache `identify_package` and `locate_package` during package loading (#48247) --- base/loading.jl | 88 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 69 insertions(+), 19 deletions(-) diff --git a/base/loading.jl b/base/loading.jl index 462e48df9a67f1..5ce41139a1b127 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -256,9 +256,12 @@ struct LoadingCache env_project_file::Dict{String, Union{Bool, String}} project_file_manifest_path::Dict{String, Union{Nothing, String}} require_parsed::Set{String} + identified_where::Dict{Tuple{PkgId, String}, Union{Nothing, Tuple{PkgId, Union{Nothing, String}}}} + identified::Dict{String, Union{Nothing, Tuple{PkgId, Union{Nothing, String}}}} + located::Dict{Tuple{PkgId, Union{String, Nothing}}, Union{String, Nothing}} end const LOADING_CACHE = Ref{Union{LoadingCache, Nothing}}(nothing) -LoadingCache() = LoadingCache(load_path(), Dict(), Dict(), Dict(), Set()) +LoadingCache() = LoadingCache(load_path(), Dict(), Dict(), Dict(), Set(), Dict(), Dict(), Dict()) struct TOMLCache @@ -311,22 +314,49 @@ is also returned. """ identify_package_env(where::Module, name::String) = identify_package_env(PkgId(where), name) function identify_package_env(where::PkgId, name::String) - where.name === name && return where, nothing - where.uuid === nothing && return identify_package_env(name) # ignore `where` - for env in load_path() - pkgid = manifest_deps_get(env, where, name) - pkgid === nothing && continue # not found--keep looking - pkgid.uuid === nothing || return pkgid, env # found in explicit environment--use it - return nothing # found in implicit environment--return "not found" + cache = LOADING_CACHE[] + if cache !== nothing + pkg_env = get(cache.identified_where, (where, name), nothing) + pkg_env === nothing || return pkg_env + end + pkg_env = nothing + if where.name === name + pkg_env = where, nothing + elseif where.uuid === nothing + pkg_env = identify_package_env(name) # ignore `where` + else + for env in load_path() + pkgid = manifest_deps_get(env, where, name) + pkgid === nothing && continue # not found--keep looking + if pkgid.uuid !== nothing + pkg_env = pkgid, env # found in explicit environment--use it + end + break # found in implicit environment--return "not found" + end end - return nothing + if cache !== nothing + cache.identified_where[(where, name)] = pkg_env + end + return pkg_env end function identify_package_env(name::String) + cache = LOADING_CACHE[] + if cache !== nothing + pkg_env = get(cache.identified, name, nothing) + pkg_env === nothing || return pkg_env + end + pkg_env = nothing for env in load_path() - uuid = project_deps_get(env, name) - uuid === nothing || return uuid, env # found--return it + pkg = project_deps_get(env, name) + if pkg !== nothing + pkg_env = pkg, env # found--return it + break + end end - return nothing + if cache !== nothing + cache.identified[name] = pkg_env + end + return pkg_env end _nothing_or_first(x) = x === nothing ? nothing : first(x) @@ -376,6 +406,12 @@ julia> Base.locate_package(pkg) ``` """ function locate_package(pkg::PkgId, stopenv::Union{String, Nothing}=nothing)::Union{Nothing,String} + cache = LOADING_CACHE[] + if cache !== nothing + path = get(cache.located, (pkg, stopenv), nothing) + path === nothing || return path + end + path = nothing if pkg.uuid === nothing for env in load_path() # look for the toplevel pkg `pkg.name` in this entry @@ -386,25 +422,39 @@ function locate_package(pkg::PkgId, stopenv::Union{String, Nothing}=nothing)::Un # pkg.name is present in this directory or project file, # return the path the entry point for the code, if it could be found # otherwise, signal failure - return implicit_manifest_uuid_path(env, pkg) + path = implicit_manifest_uuid_path(env, pkg) + @goto done end end - stopenv == env && return nothing + stopenv == env && @goto done end else for env in load_path() path = manifest_uuid_path(env, pkg) # missing is used as a sentinel to stop looking further down in envs - path === missing && return nothing - path === nothing || return entry_path(path, pkg.name) + if path === missing + path = nothing + @goto done + end + if path !== nothing + path = entry_path(path, pkg.name) + @goto done + end stopenv == env && break end # Allow loading of stdlibs if the name/uuid are given # e.g. if they have been explicitly added to the project/manifest - path = manifest_uuid_path(Sys.STDLIB, pkg) - path isa String && return entry_path(path, pkg.name) + mbypath = manifest_uuid_path(Sys.STDLIB, pkg) + if mbypath isa String + path = entry_path(mbypath, pkg.name) + @goto done + end end - return nothing + @label done + if cache !== nothing + cache.located[(pkg, stopenv)] = path + end + return path end """ From eb5f6d62009a5792acb2abe5daac00601da4df31 Mon Sep 17 00:00:00 2001 From: Tomas Fiers Date: Fri, 13 Jan 2023 16:58:48 +0100 Subject: [PATCH 317/387] docstring for `@time_imports`: explain what is shown (it's not cumulative) (#48248) Co-authored-by: Ian Butterworth --- stdlib/InteractiveUtils/src/macros.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stdlib/InteractiveUtils/src/macros.jl b/stdlib/InteractiveUtils/src/macros.jl index cc0fa019c604a5..9c2200db0245dd 100644 --- a/stdlib/InteractiveUtils/src/macros.jl +++ b/stdlib/InteractiveUtils/src/macros.jl @@ -360,6 +360,8 @@ See also: [`code_native`](@ref), [`@code_llvm`](@ref), [`@code_typed`](@ref) and A macro to execute an expression and produce a report of any time spent importing packages and their dependencies. Any compilation time will be reported as a percentage, and how much of which was recompilation, if any. +One line is printed per package or package extension. The duration shown is the time to import that package itself, not including the time to load any of its dependencies. + On Julia 1.9+ [package extensions](@ref man-extensions) will show as Parent → Extension. !!! note From 9707594d37fd2d169974f1912dbb0ecc34ae378c Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Fri, 13 Jan 2023 12:01:08 -0600 Subject: [PATCH 318/387] rename QuickerSort to ScratchQuickSort (#48160) --- base/sort.jl | 34 +++++++++++++++++----------------- test/sorting.jl | 20 ++++++++++---------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/base/sort.jl b/base/sort.jl index 485b9d7fe1d14e..985e0e8f597f38 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -86,7 +86,7 @@ issorted(itr; issorted(itr, ord(lt,by,rev,order)) function partialsort!(v::AbstractVector, k::Union{Integer,OrdinalRange}, o::Ordering) - _sort!(v, InitialOptimizations(QuickerSort(k)), o, (;)) + _sort!(v, InitialOptimizations(ScratchQuickSort(k)), o, (;)) maybeview(v, k) end @@ -950,12 +950,12 @@ end """ - QuickerSort(next::Algorithm=SMALL_ALGORITHM) <: Algorithm - QuickerSort(lo::Union{Integer, Missing}, hi::Union{Integer, Missing}=lo, next::Algorithm=SMALL_ALGORITHM) <: Algorithm + ScratchQuickSort(next::Algorithm=SMALL_ALGORITHM) <: Algorithm + ScratchQuickSort(lo::Union{Integer, Missing}, hi::Union{Integer, Missing}=lo, next::Algorithm=SMALL_ALGORITHM) <: Algorithm -Use the `QuickerSort` algorithm with the `next` algorithm as a base case. +Use the `ScratchQuickSort` algorithm with the `next` algorithm as a base case. -`QuickerSort` is like `QuickSort`, but utilizes scratch space to operate faster and allow +`ScratchQuickSort` is like `QuickSort`, but utilizes scratch space to operate faster and allow for the possibility of maintaining stability. If `lo` and `hi` are provided, finds and sorts the elements in the range `lo:hi`, reordering @@ -973,15 +973,15 @@ Characteristics: * *quadratic worst case runtime* in pathological cases (vanishingly rare for non-malicious input) """ -struct QuickerSort{L<:Union{Integer,Missing}, H<:Union{Integer,Missing}, T<:Algorithm} <: Algorithm +struct ScratchQuickSort{L<:Union{Integer,Missing}, H<:Union{Integer,Missing}, T<:Algorithm} <: Algorithm lo::L hi::H next::T end -QuickerSort(next::Algorithm=SMALL_ALGORITHM) = QuickerSort(missing, missing, next) -QuickerSort(lo::Union{Integer, Missing}, hi::Union{Integer, Missing}) = QuickerSort(lo, hi, SMALL_ALGORITHM) -QuickerSort(lo::Union{Integer, Missing}, next::Algorithm=SMALL_ALGORITHM) = QuickerSort(lo, lo, next) -QuickerSort(r::OrdinalRange, next::Algorithm=SMALL_ALGORITHM) = QuickerSort(first(r), last(r), next) +ScratchQuickSort(next::Algorithm=SMALL_ALGORITHM) = ScratchQuickSort(missing, missing, next) +ScratchQuickSort(lo::Union{Integer, Missing}, hi::Union{Integer, Missing}) = ScratchQuickSort(lo, hi, SMALL_ALGORITHM) +ScratchQuickSort(lo::Union{Integer, Missing}, next::Algorithm=SMALL_ALGORITHM) = ScratchQuickSort(lo, lo, next) +ScratchQuickSort(r::OrdinalRange, next::Algorithm=SMALL_ALGORITHM) = ScratchQuickSort(first(r), last(r), next) # select a pivot, partition v[lo:hi] according # to the pivot, and store the result in t[lo:hi]. @@ -1020,7 +1020,7 @@ function partition!(t::AbstractVector, lo::Integer, hi::Integer, offset::Integer pivot_index end -function _sort!(v::AbstractVector, a::QuickerSort, o::Ordering, kw; +function _sort!(v::AbstractVector, a::ScratchQuickSort, o::Ordering, kw; t=nothing, offset=nothing, swap=false, rev=false) @getkw lo hi scratch @@ -1038,7 +1038,7 @@ function _sort!(v::AbstractVector, a::QuickerSort, o::Ordering, kw; end swap = !swap - # For QuickerSort(), a.lo === a.hi === missing, so the first two branches get skipped + # For ScratchQuickSort(), a.lo === a.hi === missing, so the first two branches get skipped if !ismissing(a.lo) && j <= a.lo # Skip sorting the lower part swap && copyto!(v, lo, t, lo+offset, j-lo) rev && reverse!(v, lo, j-1) @@ -1236,7 +1236,7 @@ the initial optimizations because they can change the input vector's type and or make them `UIntMappable`. If the input is not [`UIntMappable`](@ref), then we perform a presorted check and dispatch -to [`QuickerSort`](@ref). +to [`ScratchQuickSort`](@ref). Otherwise, we dispatch to [`InsertionSort`](@ref) for inputs with `length <= 40` and then perform a presorted check ([`CheckSorted`](@ref)). @@ -1268,7 +1268,7 @@ Consequently, we apply [`RadixSort`](@ref) for any reasonably long inputs that r stage. Finally, if the input has length less than 80, we dispatch to [`InsertionSort`](@ref) and -otherwise we dispatch to [`QuickerSort`](@ref). +otherwise we dispatch to [`ScratchQuickSort`](@ref). """ const DEFAULT_STABLE = InitialOptimizations( IsUIntMappable( @@ -1278,9 +1278,9 @@ const DEFAULT_STABLE = InitialOptimizations( ConsiderCountingSort( ConsiderRadixSort( Small{80}( - QuickerSort())))))), + ScratchQuickSort())))))), StableCheckSorted( - QuickerSort()))) + ScratchQuickSort()))) """ DEFAULT_UNSTABLE @@ -1485,7 +1485,7 @@ function partialsortperm!(ix::AbstractVector{<:Integer}, v::AbstractVector, end # do partial quicksort - _sort!(ix, InitialOptimizations(QuickerSort(k)), Perm(ord(lt, by, rev, order), v), (;)) + _sort!(ix, InitialOptimizations(ScratchQuickSort(k)), Perm(ord(lt, by, rev, order), v), (;)) maybeview(ix, k) end diff --git a/test/sorting.jl b/test/sorting.jl index 1b79070d7e06ef..691f0a0e2bc398 100644 --- a/test/sorting.jl +++ b/test/sorting.jl @@ -79,8 +79,8 @@ end end @testset "stability" begin - for Alg in [InsertionSort, MergeSort, Base.Sort.QuickerSort(), Base.DEFAULT_STABLE, - Base.Sort.QuickerSort(missing, 1729), Base.Sort.QuickerSort(1729, missing)] + for Alg in [InsertionSort, MergeSort, Base.Sort.ScratchQuickSort(), Base.DEFAULT_STABLE, + Base.Sort.ScratchQuickSort(missing, 1729), Base.Sort.ScratchQuickSort(1729, missing)] @test issorted(sort(1:2000, alg=Alg, by=x->0)) @test issorted(sort(1:2000, alg=Alg, by=x->x÷100)) end @@ -333,7 +333,7 @@ end @test c == v # stable algorithms - for alg in [MergeSort, Base.Sort.QuickerSort(), Base.Sort.QuickerSort(1:n), Base.DEFAULT_STABLE] + for alg in [MergeSort, Base.Sort.ScratchQuickSort(), Base.Sort.ScratchQuickSort(1:n), Base.DEFAULT_STABLE] p = sortperm(v, alg=alg, rev=rev) p2 = sortperm(float(v), alg=alg, rev=rev) @test p == p2 @@ -381,7 +381,7 @@ end end v = randn_with_nans(n,0.1) - for alg in [InsertionSort, MergeSort, Base.Sort.QuickerSort(), Base.Sort.QuickerSort(1, n), Base.DEFAULT_UNSTABLE, Base.DEFAULT_STABLE], + for alg in [InsertionSort, MergeSort, Base.Sort.ScratchQuickSort(), Base.Sort.ScratchQuickSort(1, n), Base.DEFAULT_UNSTABLE, Base.DEFAULT_STABLE], rev in [false,true] alg === InsertionSort && n >= 3000 && continue # test float sorting with NaNs @@ -588,7 +588,7 @@ end @testset "fallback" begin @test adaptive_sort_test(rand(1:typemax(Int32), len), by=x->x^2)# fallback - @test adaptive_sort_test(rand(Int, len), by=x->0, trusted=Base.Sort.QuickerSort()) + @test adaptive_sort_test(rand(Int, len), by=x->0, trusted=Base.Sort.ScratchQuickSort()) end @test adaptive_sort_test(rand(Int, 20)) # InsertionSort @@ -692,7 +692,7 @@ end # not allowed. Consequently, none of the behavior tested in this # testset is guaranteed to work in future minor versions of Julia. - safe_algs = [InsertionSort, MergeSort, Base.Sort.QuickerSort(), Base.DEFAULT_STABLE, Base.DEFAULT_UNSTABLE] + safe_algs = [InsertionSort, MergeSort, Base.Sort.ScratchQuickSort(), Base.DEFAULT_STABLE, Base.DEFAULT_UNSTABLE] n = 1000 v = rand(1:5, n); @@ -899,8 +899,8 @@ end @test issorted(sort(rand(Int8, 600))) end -@testset "QuickerSort API" begin - bsqs = Base.Sort.QuickerSort +@testset "ScratchQuickSort API" begin + bsqs = Base.Sort.ScratchQuickSort @test bsqs(1, 2, MergeSort) === bsqs(1, 2, MergeSort) @test bsqs(missing, 2, MergeSort) === bsqs(missing, 2, MergeSort) @test bsqs(1, missing, MergeSort) === bsqs(1, missing, MergeSort) @@ -918,10 +918,10 @@ end @test bsqs() === bsqs(missing, missing, InsertionSort) end -@testset "QuickerSort allocations on non-concrete eltype" begin +@testset "ScratchQuickSort allocations on non-concrete eltype" begin v = Vector{Union{Nothing, Bool}}(rand(Bool, 10000)) @test 4 == @allocations sort(v) - @test 4 == @allocations sort(v; alg=Base.Sort.QuickerSort()) + @test 4 == @allocations sort(v; alg=Base.Sort.ScratchQuickSort()) # it would be nice if these numbers were lower (1 or 2), but these # test that we don't have O(n) allocations due to type instability end From 670190c3393e7a2193ad9e40cfa12ec643ceba75 Mon Sep 17 00:00:00 2001 From: Jeremie Knuesel Date: Fri, 13 Jan 2023 19:47:51 +0100 Subject: [PATCH 319/387] Don't deprecate splat (#48038) Keep the splat function and mention in the documentation that it's the recommended way of constructing a Base.Splat object. --- base/deprecated.jl | 2 -- base/exports.jl | 2 +- base/iterators.jl | 2 +- base/operators.jl | 35 ++++++++++++++++------- base/strings/search.jl | 4 +-- doc/src/base/base.md | 2 +- doc/src/devdocs/ast.md | 2 +- stdlib/LinearAlgebra/src/LinearAlgebra.jl | 2 +- stdlib/LinearAlgebra/src/qr.jl | 2 +- test/broadcast.jl | 4 +-- test/compiler/inference.jl | 2 +- test/iterators.jl | 2 +- 12 files changed, 36 insertions(+), 25 deletions(-) diff --git a/base/deprecated.jl b/base/deprecated.jl index 6953cd600cacd7..79ae852ff22b12 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -318,8 +318,6 @@ const var"@_noinline_meta" = var"@noinline" # BEGIN 1.9 deprecations -@deprecate splat(x) Splat(x) false - # We'd generally like to avoid direct external access to internal fields # Core.Compiler.is_inlineable and Core.Compiler.set_inlineable! move towards this direction, # but we need to keep these around for compat diff --git a/base/exports.jl b/base/exports.jl index 266a4aa8038fb3..600b36b6c37c6f 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -816,7 +816,7 @@ export atreplinit, exit, ntuple, - Splat, + splat, # I/O and events close, diff --git a/base/iterators.jl b/base/iterators.jl index 6cf8a6502959a3..db11e57e8b26e2 100644 --- a/base/iterators.jl +++ b/base/iterators.jl @@ -346,7 +346,7 @@ the `zip` iterator is a tuple of values of its subiterators. `zip()` with no arguments yields an infinite iterator of empty tuples. -See also: [`enumerate`](@ref), [`Splat`](@ref Base.Splat). +See also: [`enumerate`](@ref), [`Base.splat`](@ref). # Examples ```jldoctest diff --git a/base/operators.jl b/base/operators.jl index 9922305e14a3e9..da55981c5f7f8e 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1212,41 +1212,54 @@ used to implement specialized methods. <(x) = Fix2(<, x) """ - Splat(f) + splat(f) Equivalent to ```julia my_splat(f) = args->f(args...) ``` i.e. given a function returns a new function that takes one argument and splats -its argument into the original function. This is useful as an adaptor to pass -a multi-argument function in a context that expects a single argument, but -passes a tuple as that single argument. Additionally has pretty printing. - -!!! compat "Julia 1.9" - This function was introduced in Julia 1.9, replacing `Base.splat(f)`. +it into the original function. This is useful as an adaptor to pass a +multi-argument function in a context that expects a single argument, but passes +a tuple as that single argument. # Example usage: ```jldoctest -julia> map(Base.Splat(+), zip(1:3,4:6)) +julia> map(splat(+), zip(1:3,4:6)) 3-element Vector{Int64}: 5 7 9 -julia> my_add = Base.Splat(+) -Splat(+) +julia> my_add = splat(+) +splat(+) julia> my_add((1,2,3)) 6 ``` """ +splat(f) = Splat(f) + +""" + Base.Splat{F} <: Function + +Represents a splatted function. That is +```julia +Base.Splat(f)(args) === f(args...) +``` +The preferred way to construct an instance of `Base.Splat` is to use the [`splat`](@ref) function. + +!!! compat "Julia 1.9" + Splat requires at least Julia 1.9. In earlier versions `splat` returns an anonymous function instead. + +See also [`splat`](@ref). +""" struct Splat{F} <: Function f::F Splat(f) = new{Core.Typeof(f)}(f) end (s::Splat)(args) = s.f(args...) -print(io::IO, s::Splat) = print(io, "Splat(", s.f, ')') +print(io::IO, s::Splat) = print(io, "splat(", s.f, ')') show(io::IO, s::Splat) = print(io, s) ## in and related operators diff --git a/base/strings/search.jl b/base/strings/search.jl index eade1fbe74158e..032aa8257b26da 100644 --- a/base/strings/search.jl +++ b/base/strings/search.jl @@ -189,7 +189,7 @@ function _searchindex(s::Union{AbstractString,ByteArray}, if i === nothing return 0 end ii = nextind(s, i)::Int a = Iterators.Stateful(trest) - matched = all(Splat(==), zip(SubString(s, ii), a)) + matched = all(splat(==), zip(SubString(s, ii), a)) (isempty(a) && matched) && return i i = ii end @@ -506,7 +506,7 @@ function _rsearchindex(s::AbstractString, a = Iterators.Stateful(trest) b = Iterators.Stateful(Iterators.reverse( pairs(SubString(s, 1, ii)))) - matched = all(Splat(==), zip(a, (x[2] for x in b))) + matched = all(splat(==), zip(a, (x[2] for x in b))) if matched && isempty(a) isempty(b) && return firstindex(s) return nextind(s, popfirst!(b)[1])::Int diff --git a/doc/src/base/base.md b/doc/src/base/base.md index 72a8ec4db613c2..5bb227137b24da 100644 --- a/doc/src/base/base.md +++ b/doc/src/base/base.md @@ -259,7 +259,7 @@ new Base.:(|>) Base.:(∘) Base.ComposedFunction -Base.Splat +Base.splat Base.Fix1 Base.Fix2 ``` diff --git a/doc/src/devdocs/ast.md b/doc/src/devdocs/ast.md index 1978cd19a9a793..9ada683b1ddb0b 100644 --- a/doc/src/devdocs/ast.md +++ b/doc/src/devdocs/ast.md @@ -425,7 +425,7 @@ These symbols appear in the `head` field of [`Expr`](@ref)s in lowered form. * `splatnew` Similar to `new`, except field values are passed as a single tuple. Works similarly to - `Base.Splat(new)` if `new` were a first-class function, hence the name. + `splat(new)` if `new` were a first-class function, hence the name. * `isdefined` diff --git a/stdlib/LinearAlgebra/src/LinearAlgebra.jl b/stdlib/LinearAlgebra/src/LinearAlgebra.jl index 0cb6307079f64c..624cca69b84d98 100644 --- a/stdlib/LinearAlgebra/src/LinearAlgebra.jl +++ b/stdlib/LinearAlgebra/src/LinearAlgebra.jl @@ -18,7 +18,7 @@ import Base: USE_BLAS64, abs, acos, acosh, acot, acoth, acsc, acsch, adjoint, as vec, zero using Base: IndexLinear, promote_eltype, promote_op, promote_typeof, @propagate_inbounds, reduce, typed_hvcat, typed_vcat, require_one_based_indexing, - Splat + splat using Base.Broadcast: Broadcasted, broadcasted using Base.PermutedDimsArrays: CommutativeOps using OpenBLAS_jll diff --git a/stdlib/LinearAlgebra/src/qr.jl b/stdlib/LinearAlgebra/src/qr.jl index 5000c3f2187a6b..1de2c2edadf993 100644 --- a/stdlib/LinearAlgebra/src/qr.jl +++ b/stdlib/LinearAlgebra/src/qr.jl @@ -159,7 +159,7 @@ function Base.hash(F::QRCompactWY, h::UInt) return hash(F.factors, foldr(hash, _triuppers_qr(F.T); init=hash(QRCompactWY, h))) end function Base.:(==)(A::QRCompactWY, B::QRCompactWY) - return A.factors == B.factors && all(Splat(==), zip(_triuppers_qr.((A.T, B.T))...)) + return A.factors == B.factors && all(splat(==), zip(_triuppers_qr.((A.T, B.T))...)) end function Base.isequal(A::QRCompactWY, B::QRCompactWY) return isequal(A.factors, B.factors) && all(zip(_triuppers_qr.((A.T, B.T))...)) do (a, b) diff --git a/test/broadcast.jl b/test/broadcast.jl index 5afc60b9c55122..50c61594a220f2 100644 --- a/test/broadcast.jl +++ b/test/broadcast.jl @@ -903,13 +903,13 @@ end ys = 1:2:20 bc = Broadcast.instantiate(Broadcast.broadcasted(*, xs, ys)) @test IndexStyle(bc) == IndexLinear() - @test sum(bc) == mapreduce(Base.Splat(*), +, zip(xs, ys)) + @test sum(bc) == mapreduce(Base.splat(*), +, zip(xs, ys)) xs2 = reshape(xs, 1, :) ys2 = reshape(ys, 1, :) bc = Broadcast.instantiate(Broadcast.broadcasted(*, xs2, ys2)) @test IndexStyle(bc) == IndexCartesian() - @test sum(bc) == mapreduce(Base.Splat(*), +, zip(xs, ys)) + @test sum(bc) == mapreduce(Base.splat(*), +, zip(xs, ys)) xs = 1:5:3*5 ys = 1:4:3*4 diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index b7adcba2979253..aced289e15cbef 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -3260,7 +3260,7 @@ j30385(T, y) = k30385(f30385(T, y)) @test @inferred(j30385(:dummy, 1)) == "dummy" @test Base.return_types(Tuple, (NamedTuple{<:Any,Tuple{Any,Int}},)) == Any[Tuple{Any,Int}] -@test Base.return_types(Base.Splat(tuple), (typeof((a=1,)),)) == Any[Tuple{Int}] +@test Base.return_types(Base.splat(tuple), (typeof((a=1,)),)) == Any[Tuple{Int}] # test that return_type_tfunc isn't affected by max_methods differently than return_type _rttf_test(::Int8) = 0 diff --git a/test/iterators.jl b/test/iterators.jl index baf0095dcc43ed..59588bdac96840 100644 --- a/test/iterators.jl +++ b/test/iterators.jl @@ -618,7 +618,7 @@ end @test length(I) == iterate_length(I) == simd_iterate_length(I) == simd_trip_count(I) @test collect(I) == iterate_elements(I) == simd_iterate_elements(I) == index_elements(I) end - @test all(Base.Splat(==), zip(Iterators.flatten(map(collect, P)), iter)) + @test all(Base.splat(==), zip(Iterators.flatten(map(collect, P)), iter)) end end @testset "empty/invalid partitions" begin From 313f646c3f1d2aeab405b17dc5f8aea73b057071 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Fri, 13 Jan 2023 21:03:14 +0100 Subject: [PATCH 320/387] fix a soure of invalidations (#48270) This occurred when loading GMT and came from a new `convert(::Type{String}, ...)` method getting defined in one of the dependencies. --- base/cmd.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/cmd.jl b/base/cmd.jl index ecabb5c32b1d05..e6691835e80c90 100644 --- a/base/cmd.jl +++ b/base/cmd.jl @@ -230,7 +230,7 @@ function cstr(s) if Base.containsnul(s) throw(ArgumentError("strings containing NUL cannot be passed to spawned processes")) end - return String(s) + return String(s)::String end # convert various env representations into an array of "key=val" strings From 4f34aa9cf1988dbaaafaaf38755194dc92bd759b Mon Sep 17 00:00:00 2001 From: DilumAluthgeBot <43731525+DilumAluthgeBot@users.noreply.github.com> Date: Fri, 13 Jan 2023 16:23:54 -0500 Subject: [PATCH 321/387] =?UTF-8?q?=F0=9F=A4=96=20[master]=20Bump=20the=20?= =?UTF-8?q?Tar=20stdlib=20from=206bfc114=20to=20ff55460=20(#48268)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dilum Aluthge --- .../Tar-6bfc11475a80b752e70518047c3c3463f56bbc1d.tar.gz/md5 | 1 - .../Tar-6bfc11475a80b752e70518047c3c3463f56bbc1d.tar.gz/sha512 | 1 - .../Tar-ff55460f4d329949661a33e6c8168ce6d890676c.tar.gz/md5 | 1 + .../Tar-ff55460f4d329949661a33e6c8168ce6d890676c.tar.gz/sha512 | 1 + stdlib/Tar.version | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 deps/checksums/Tar-6bfc11475a80b752e70518047c3c3463f56bbc1d.tar.gz/md5 delete mode 100644 deps/checksums/Tar-6bfc11475a80b752e70518047c3c3463f56bbc1d.tar.gz/sha512 create mode 100644 deps/checksums/Tar-ff55460f4d329949661a33e6c8168ce6d890676c.tar.gz/md5 create mode 100644 deps/checksums/Tar-ff55460f4d329949661a33e6c8168ce6d890676c.tar.gz/sha512 diff --git a/deps/checksums/Tar-6bfc11475a80b752e70518047c3c3463f56bbc1d.tar.gz/md5 b/deps/checksums/Tar-6bfc11475a80b752e70518047c3c3463f56bbc1d.tar.gz/md5 deleted file mode 100644 index cbbc18180334e2..00000000000000 --- a/deps/checksums/Tar-6bfc11475a80b752e70518047c3c3463f56bbc1d.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -3f153a0a3646995cc7dadd4720de74a2 diff --git a/deps/checksums/Tar-6bfc11475a80b752e70518047c3c3463f56bbc1d.tar.gz/sha512 b/deps/checksums/Tar-6bfc11475a80b752e70518047c3c3463f56bbc1d.tar.gz/sha512 deleted file mode 100644 index 2a64aab3ccb9cd..00000000000000 --- a/deps/checksums/Tar-6bfc11475a80b752e70518047c3c3463f56bbc1d.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -433fe68dcf65805af68e088e127b859e3e95ff21820785ea152392554944a3d9904fa8152e43e1413593fe46a028788cea5cd7a19299a0a1f41b2cfcb7cfed73 diff --git a/deps/checksums/Tar-ff55460f4d329949661a33e6c8168ce6d890676c.tar.gz/md5 b/deps/checksums/Tar-ff55460f4d329949661a33e6c8168ce6d890676c.tar.gz/md5 new file mode 100644 index 00000000000000..40d52c28037460 --- /dev/null +++ b/deps/checksums/Tar-ff55460f4d329949661a33e6c8168ce6d890676c.tar.gz/md5 @@ -0,0 +1 @@ +438818cad063d6808354a9b4aecd3001 diff --git a/deps/checksums/Tar-ff55460f4d329949661a33e6c8168ce6d890676c.tar.gz/sha512 b/deps/checksums/Tar-ff55460f4d329949661a33e6c8168ce6d890676c.tar.gz/sha512 new file mode 100644 index 00000000000000..27c57c5051212f --- /dev/null +++ b/deps/checksums/Tar-ff55460f4d329949661a33e6c8168ce6d890676c.tar.gz/sha512 @@ -0,0 +1 @@ +f9a6e7757bbcca09a84d92ab3a2690a51612c318bdfd98bbb4ffcef56305b019029838e5f1483c9febafa7ecb5e735e68855bc82d04b593af04a446e32436145 diff --git a/stdlib/Tar.version b/stdlib/Tar.version index a6b7cf053523d1..44e829b5fea547 100644 --- a/stdlib/Tar.version +++ b/stdlib/Tar.version @@ -1,4 +1,4 @@ TAR_BRANCH = master -TAR_SHA1 = 6bfc11475a80b752e70518047c3c3463f56bbc1d +TAR_SHA1 = ff55460f4d329949661a33e6c8168ce6d890676c TAR_GIT_URL := https://github.com/JuliaIO/Tar.jl.git TAR_TAR_URL = https://api.github.com/repos/JuliaIO/Tar.jl/tarball/$1 From 428d242f9b8808e48905c7e82dede0e605f163cb Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Fri, 13 Jan 2023 19:03:51 -0600 Subject: [PATCH 322/387] threads: during abrupt thread-exit, cleanup anyways (#48223) Closes #47590 (pthread_cancel still forbidden though, since async mode will corrupt or deadlock the process, and synchronously tested with cancelation disabled whenever this is a lock is just a slow implementation of a boolean) Refs #47201 (only deals with thread exit, not other case where this is an issue, like cfunction exit and gc-safe-leave) May help #46537, by blocking jl_wake_libuv before uv_library_shutdown, and other tweaks to GC mode. For example, avoiding: [4011824] signal (6.-6): Aborted gsignal at /lib/x86_64-linux-gnu/libc.so.6 (unknown line) abort at /lib/x86_64-linux-gnu/libc.so.6 (unknown line) uv__async_send at /workspace/srcdir/libuv/src/unix/async.c:198 uv_async_send at /workspace/srcdir/libuv/src/unix/async.c:73 jl_wake_libuv at /data/vtjnash/julia1/src/jl_uv.c:44 [inlined] JL_UV_LOCK at /data/vtjnash/julia1/src/jl_uv.c:64 [inlined] ijl_iolock_begin at /data/vtjnash/julia1/src/jl_uv.c:72 iolock_begin at ./libuv.jl:48 [inlined] _trywait at ./asyncevent.jl:140 wait at ./asyncevent.jl:155 [inlined] profile_printing_listener at /data/vtjnash/julia1/usr/share/julia/stdlib/v1.10/Profile/src/Profile.jl:39 jfptr_YY.3_58617 at /data/vtjnash/julia1/usr/lib/julia/sys.so (unknown line) _jl_invoke at /data/vtjnash/julia1/src/gf.c:2665 [inlined] ijl_apply_generic at /data/vtjnash/julia1/src/gf.c:2866 jl_apply at /data/vtjnash/julia1/src/julia.h:1870 [inlined] start_task at /data/vtjnash/julia1/src/task.c:1093 Aborted Fixes #37400 --- src/Makefile | 20 +++++++---- src/codegen-stubs.c | 2 +- src/codegen.cpp | 2 +- src/debug-registry.h | 2 -- src/debuginfo.cpp | 4 ++- src/dlload.c | 19 ++++++---- src/gc.c | 2 +- src/gf.c | 6 ++-- src/init.c | 82 ++++++++++++++++++++++++++----------------- src/jitlayers.h | 9 ++--- src/julia.h | 2 +- src/julia_internal.h | 4 +-- src/partr.c | 6 ++-- src/signal-handling.c | 7 ++-- src/stackwalk.c | 4 +-- src/task.c | 1 + src/threading.c | 38 +++++++++++++++++--- src/timing.h | 16 ++++----- 18 files changed, 142 insertions(+), 84 deletions(-) diff --git a/src/Makefile b/src/Makefile index 6b914cd53da5b9..bd3f2be27219d4 100644 --- a/src/Makefile +++ b/src/Makefile @@ -286,11 +286,11 @@ $(BUILDDIR)/julia_flisp.boot: $(addprefix $(SRCDIR)/,jlfrontend.scm flisp/aliase # additional dependency links $(BUILDDIR)/codegen-stubs.o $(BUILDDIR)/codegen-stubs.dbg.obj: $(SRCDIR)/intrinsics.h -$(BUILDDIR)/aotcompile.o $(BUILDDIR)/aotcompile.dbg.obj: $(SRCDIR)/jitlayers.h $(SRCDIR)/llvm-codegen-shared.h $(SRCDIR)/debug-registry.h +$(BUILDDIR)/aotcompile.o $(BUILDDIR)/aotcompile.dbg.obj: $(SRCDIR)/jitlayers.h $(SRCDIR)/llvm-codegen-shared.h $(BUILDDIR)/ast.o $(BUILDDIR)/ast.dbg.obj: $(BUILDDIR)/julia_flisp.boot.inc $(SRCDIR)/flisp/*.h $(BUILDDIR)/builtins.o $(BUILDDIR)/builtins.dbg.obj: $(SRCDIR)/iddict.c $(SRCDIR)/builtin_proto.h $(BUILDDIR)/codegen.o $(BUILDDIR)/codegen.dbg.obj: $(addprefix $(SRCDIR)/,\ - intrinsics.cpp jitlayers.h debug-registry.h intrinsics.h llvm-codegen-shared.h cgutils.cpp ccall.cpp abi_*.cpp processor.h builtin_proto.h) + intrinsics.cpp jitlayers.h intrinsics.h llvm-codegen-shared.h cgutils.cpp ccall.cpp abi_*.cpp processor.h builtin_proto.h) $(BUILDDIR)/datatype.o $(BUILDDIR)/datatype.dbg.obj: $(SRCDIR)/support/htable.h $(SRCDIR)/support/htable.inc $(BUILDDIR)/debuginfo.o $(BUILDDIR)/debuginfo.dbg.obj: $(addprefix $(SRCDIR)/,debuginfo.h processor.h jitlayers.h debug-registry.h) $(BUILDDIR)/disasm.o $(BUILDDIR)/disasm.dbg.obj: $(SRCDIR)/debuginfo.h $(SRCDIR)/processor.h @@ -301,13 +301,13 @@ $(BUILDDIR)/gc-heap-snapshot.o $(BUILDDIR)/gc-heap-snapshot.dbg.obj: $(SRCDIR)/g $(BUILDDIR)/gc-alloc-profiler.o $(BUILDDIR)/gc-alloc-profiler.dbg.obj: $(SRCDIR)/gc.h $(SRCDIR)/gc-alloc-profiler.h $(BUILDDIR)/init.o $(BUILDDIR)/init.dbg.obj: $(SRCDIR)/builtin_proto.h $(BUILDDIR)/interpreter.o $(BUILDDIR)/interpreter.dbg.obj: $(SRCDIR)/builtin_proto.h -$(BUILDDIR)/jitlayers.o $(BUILDDIR)/jitlayers.dbg.obj: $(SRCDIR)/jitlayers.h $(SRCDIR)/llvm-codegen-shared.h $(SRCDIR)/debug-registry.h +$(BUILDDIR)/jitlayers.o $(BUILDDIR)/jitlayers.dbg.obj: $(SRCDIR)/jitlayers.h $(SRCDIR)/llvm-codegen-shared.h $(BUILDDIR)/jltypes.o $(BUILDDIR)/jltypes.dbg.obj: $(SRCDIR)/builtin_proto.h $(build_shlibdir)/libllvmcalltest.$(SHLIB_EXT): $(SRCDIR)/llvm-codegen-shared.h $(BUILDDIR)/julia_version.h $(BUILDDIR)/llvm-alloc-helpers.o $(BUILDDIR)/llvm-alloc-helpers.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-alloc-helpers.h $(BUILDDIR)/llvm-alloc-opt.o $(BUILDDIR)/llvm-alloc-opt.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-alloc-helpers.h -$(BUILDDIR)/llvm-cpufeatures.o $(BUILDDIR)/llvm-cpufeatures.dbg.obj: $(SRCDIR)/jitlayers.h $(SRCDIR)/debug-registry.h -$(BUILDDIR)/llvm-demote-float16.o $(BUILDDIR)/llvm-demote-float16.dbg.obj: $(SRCDIR)/jitlayers.h $(SRCDIR)/debug-registry.h +$(BUILDDIR)/llvm-cpufeatures.o $(BUILDDIR)/llvm-cpufeatures.dbg.obj: $(SRCDIR)/jitlayers.h +$(BUILDDIR)/llvm-demote-float16.o $(BUILDDIR)/llvm-demote-float16.dbg.obj: $(SRCDIR)/jitlayers.h $(BUILDDIR)/llvm-final-gc-lowering.o $(BUILDDIR)/llvm-final-gc-lowering.dbg.obj: $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-codegen-shared.h $(BUILDDIR)/llvm-gc-invariant-verifier.o $(BUILDDIR)/llvm-gc-invariant-verifier.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h $(BUILDDIR)/llvm-julia-licm.o $(BUILDDIR)/llvm-julia-licm.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h $(SRCDIR)/llvm-alloc-helpers.h $(SRCDIR)/llvm-pass-helpers.h @@ -323,7 +323,7 @@ $(BUILDDIR)/signal-handling.o $(BUILDDIR)/signal-handling.dbg.obj: $(addprefix $ $(BUILDDIR)/staticdata.o $(BUILDDIR)/staticdata.dbg.obj: $(SRCDIR)/staticdata_utils.c $(SRCDIR)/precompile_utils.c $(SRCDIR)/processor.h $(SRCDIR)/builtin_proto.h $(BUILDDIR)/toplevel.o $(BUILDDIR)/toplevel.dbg.obj: $(SRCDIR)/builtin_proto.h $(BUILDDIR)/ircode.o $(BUILDDIR)/ircode.dbg.obj: $(SRCDIR)/serialize.h -$(BUILDDIR)/pipeline.o $(BUILDDIR)/pipeline.dbg.obj: $(SRCDIR)/passes.h $(SRCDIR)/jitlayers.h $(SRCDIR)/debug-registry.h +$(BUILDDIR)/pipeline.o $(BUILDDIR)/pipeline.dbg.obj: $(SRCDIR)/passes.h $(SRCDIR)/jitlayers.h $(addprefix $(BUILDDIR)/,threading.o threading.dbg.obj gc.o gc.dbg.obj init.c init.dbg.obj task.o task.dbg.obj): $(addprefix $(SRCDIR)/,threading.h) $(addprefix $(BUILDDIR)/,APInt-C.o APInt-C.dbg.obj runtime_intrinsics.o runtime_intrinsics.dbg.obj): $(SRCDIR)/APInt-C.h @@ -437,8 +437,14 @@ $(build_shlibdir)/lib%Plugin.$(SHLIB_EXT): $(SRCDIR)/clangsa/%.cpp $(LLVM_CONFIG # before attempting this static analysis, so that all necessary headers # and dependencies are properly installed: # make -C src install-analysis-deps +ANALYSIS_DEPS := llvm clang llvm-tools libuv utf8proc +ifeq ($(OS),Darwin) +ANALYSIS_DEPS += llvmunwind +else ifneq ($(OS),WINNT) +ANALYSIS_DEPS += unwind +endif install-analysis-deps: - $(MAKE) -C $(JULIAHOME)/deps install-llvm install-clang install-llvm-tools install-libuv install-utf8proc install-unwind + $(MAKE) -C $(JULIAHOME)/deps $(addprefix install-,$(ANALYSIS_DEPS)) analyzegc-deps-check: $(BUILDDIR)/julia_version.h $(BUILDDIR)/julia_flisp.boot.inc $(BUILDDIR)/jl_internal_funcs.inc ifeq ($(USE_BINARYBUILDER_LLVM),0) diff --git a/src/codegen-stubs.c b/src/codegen-stubs.c index e7b7d1fb791a5e..fccef22586e5dd 100644 --- a/src/codegen-stubs.c +++ b/src/codegen-stubs.c @@ -58,7 +58,7 @@ JL_DLLEXPORT int jl_compile_extern_c_fallback(LLVMOrcThreadSafeModuleRef llvmmod return 0; } -JL_DLLEXPORT void jl_teardown_codegen_fallback(void) +JL_DLLEXPORT void jl_teardown_codegen_fallback(void) JL_NOTSAFEPOINT { } diff --git a/src/codegen.cpp b/src/codegen.cpp index b591d04df85cef..9cd15d172acbbc 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -8874,7 +8874,7 @@ extern "C" JL_DLLEXPORT void jl_init_codegen_impl(void) init_jit_functions(); } -extern "C" JL_DLLEXPORT void jl_teardown_codegen_impl() +extern "C" JL_DLLEXPORT void jl_teardown_codegen_impl() JL_NOTSAFEPOINT { // output LLVM timings and statistics reportAndResetTimings(); diff --git a/src/debug-registry.h b/src/debug-registry.h index 165f0efa479e30..bad59f205acb3f 100644 --- a/src/debug-registry.h +++ b/src/debug-registry.h @@ -3,8 +3,6 @@ #include #include "julia.h" -#include "julia_internal.h" -#include "processor.h" #include #include diff --git a/src/debuginfo.cpp b/src/debuginfo.cpp index b654846ee113af..2d087178afef11 100644 --- a/src/debuginfo.cpp +++ b/src/debuginfo.cpp @@ -39,10 +39,12 @@ using namespace llvm; #include #include #include "julia_assert.h" +#include "debug-registry.h" +static JITDebugInfoRegistry DebugRegistry; static JITDebugInfoRegistry &getJITDebugRegistry() JL_NOTSAFEPOINT { - return jl_ExecutionEngine->getDebugInfoRegistry(); + return DebugRegistry; } struct debug_link_info { diff --git a/src/dlload.c b/src/dlload.c index 281d9d55bfe41b..9f4e8be29952d4 100644 --- a/src/dlload.c +++ b/src/dlload.c @@ -70,8 +70,6 @@ const char *jl_crtdll_name = CRTDLL_BASENAME ".dll"; #define PATHBUF 4096 -#define JL_RTLD(flags, FLAG) (flags & JL_RTLD_ ## FLAG ? RTLD_ ## FLAG : 0) - #ifdef _OS_WINDOWS_ void win32_formatmessage(DWORD code, char *reason, int len) JL_NOTSAFEPOINT { @@ -160,12 +158,21 @@ JL_DLLEXPORT void *jl_dlopen(const char *filename, unsigned flags) JL_NOTSAFEPOI if (!len) return NULL; WCHAR *wfilename = (WCHAR*)alloca(len * sizeof(WCHAR)); if (!MultiByteToWideChar(CP_UTF8, 0, filename, -1, wfilename, len)) return NULL; - HANDLE lib = LoadLibraryExW(wfilename, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); - if (lib) - needsSymRefreshModuleList = 1; + HANDLE lib; + if (flags & JL_RTLD_NOLOAD) { + lib = GetModuleHandleW(wfilename); + } + else { + lib = LoadLibraryExW(wfilename, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + if (lib) + needsSymRefreshModuleList = 1; + } return lib; } #else + +#define JL_RTLD(flags, FLAG) (flags & JL_RTLD_ ## FLAG ? RTLD_ ## FLAG : 0) + JL_DLLEXPORT JL_NO_SANITIZE void *jl_dlopen(const char *filename, unsigned flags) JL_NOTSAFEPOINT { /* The sanitizers break RUNPATH use in dlopen for annoying reasons that are @@ -186,7 +193,7 @@ JL_DLLEXPORT JL_NO_SANITIZE void *jl_dlopen(const char *filename, unsigned flags dlclose(libdl_handle); assert(dlopen); } - // The real interceptors check the validty of the string here, but let's + // The real interceptors check the validity of the string here, but let's // just skip that for the time being. #endif void *hnd = dlopen(filename, diff --git a/src/gc.c b/src/gc.c index 3cfb02166e7813..5653730b89abc1 100644 --- a/src/gc.c +++ b/src/gc.c @@ -145,7 +145,7 @@ static _Atomic(int) support_conservative_marking = 0; * threads that enters `jl_gc_collect()` at the same time (or later calling * from unmanaged code) will wait in `jl_gc_collect()` until the GC is finished. * - * Before starting the mark phase the GC thread calls `jl_safepoint_gc_start()` + * Before starting the mark phase the GC thread calls `jl_safepoint_start_gc()` * and `jl_gc_wait_for_the_world()` * to make sure all the thread are in a safe state for the GC. The function * activates the safepoint and wait for all the threads to get ready for the diff --git a/src/gf.c b/src/gf.c index f559ca4faeacee..57a62fe8846b13 100644 --- a/src/gf.c +++ b/src/gf.c @@ -2320,10 +2320,11 @@ jl_value_t *jl_fptr_const_return(jl_value_t *f, jl_value_t **args, uint32_t narg jl_value_t *jl_fptr_args(jl_value_t *f, jl_value_t **args, uint32_t nargs, jl_code_instance_t *m) { + jl_fptr_args_t invoke = jl_atomic_load_relaxed(&m->specptr.fptr1); while (1) { - jl_fptr_args_t invoke = jl_atomic_load_relaxed(&m->specptr.fptr1); if (invoke) return invoke(f, args, nargs); + invoke = jl_atomic_load_acquire(&m->specptr.fptr1); // require forward progress with acquire annotation } } @@ -2331,10 +2332,11 @@ jl_value_t *jl_fptr_sparam(jl_value_t *f, jl_value_t **args, uint32_t nargs, jl_ { jl_svec_t *sparams = m->def->sparam_vals; assert(sparams != jl_emptysvec); + jl_fptr_sparam_t invoke = jl_atomic_load_relaxed(&m->specptr.fptr3); while (1) { - jl_fptr_sparam_t invoke = jl_atomic_load_relaxed(&m->specptr.fptr3); if (invoke) return invoke(f, args, nargs, sparams); + invoke = jl_atomic_load_acquire(&m->specptr.fptr3); // require forward progress with acquire annotation } } diff --git a/src/init.c b/src/init.c index 939c30f209943d..0651d3b274f244 100644 --- a/src/init.c +++ b/src/init.c @@ -199,7 +199,7 @@ static void jl_close_item_atexit(uv_handle_t *handle) // This prevents `ct` from returning via error handlers or other unintentional // means by destroying some old state before we start destroying that state in atexit hooks. -void jl_task_frame_noreturn(jl_task_t *ct); +void jl_task_frame_noreturn(jl_task_t *ct) JL_NOTSAFEPOINT; // cause this process to exit with WEXITSTATUS(signo), after waiting to finish all julia, C, and C++ cleanup JL_DLLEXPORT void jl_exit(int exitcode) @@ -246,26 +246,26 @@ JL_DLLEXPORT void jl_atexit_hook(int exitcode) JL_NOTSAFEPOINT_ENTER jl_task_t *ct = jl_get_current_task(); - // we are about to start tearing everything down, so lets try not to get - // upset by the local mess of things when we run the user's _atexit hooks - if (ct) + if (ct) { + if (exitcode == 0) + jl_write_compiler_output(); + // we are about to start tearing everything down, so lets try not to get + // upset by the local mess of things when we run the user's _atexit hooks + // this also forces us into a GC-unsafe region without a safepoint jl_task_frame_noreturn(ct); + } if (ct == NULL && jl_base_module) ct = container_of(jl_adopt_thread(), jl_task_t, gcstack); + else if (ct != NULL) + jl_gc_safepoint_(ct->ptls); - if (exitcode == 0) - jl_write_compiler_output(); jl_print_gc_stats(JL_STDERR); if (jl_options.code_coverage) jl_write_coverage_data(jl_options.output_code_coverage); if (jl_options.malloc_log) jl_write_malloc_log(); - int8_t old_state; - if (ct) - old_state = jl_gc_unsafe_enter(ct->ptls); - if (jl_base_module) { jl_value_t *f = jl_get_global(jl_base_module, jl_symbol("_atexit")); if (f != NULL) { @@ -334,19 +334,25 @@ JL_DLLEXPORT void jl_atexit_hook(int exitcode) JL_NOTSAFEPOINT_ENTER // force libuv to spin until everything has finished closing loop->stop_flag = 0; while (uv_run(loop, UV_RUN_DEFAULT)) { } - JL_UV_UNLOCK(); + jl_wake_libuv(); // set the async pending flag, so that future calls are immediate no-ops on other threads + // we would like to guarantee this, but cannot currently, so there is still a small race window + // that needs to be fixed in libuv + } + if (ct) + (void)jl_gc_safe_enter(ct->ptls); // park in gc-safe + if (loop != NULL) { + // TODO: consider uv_loop_close(loop) here, before shutdown? + uv_library_shutdown(); + // no JL_UV_UNLOCK(), since it is now torn down } - // TODO: Destroy threads + // TODO: Destroy threads? - jl_destroy_timing(); + jl_destroy_timing(); // cleans up the current timing_stack for noreturn #ifdef ENABLE_TIMINGS jl_print_timings(); #endif - - jl_teardown_codegen(); - if (ct) - jl_gc_unsafe_leave(ct->ptls, old_state); + jl_teardown_codegen(); // prints stats } JL_DLLEXPORT void jl_postoutput_hook(void) @@ -713,13 +719,32 @@ JL_DLLEXPORT int jl_default_debug_info_kind; JL_DLLEXPORT void julia_init(JL_IMAGE_SEARCH rel) { - jl_default_debug_info_kind = 0; - + // initialize many things, in no particular order + // but generally running from simple platform things to optional + // configuration features jl_init_timing(); // Make sure we finalize the tls callback before starting any threads. (void)jl_get_pgcstack(); - jl_safepoint_init(); + + // initialize backtraces + jl_init_profile_lock(); +#ifdef _OS_WINDOWS_ + uv_mutex_init(&jl_in_stackwalk); + SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES | SYMOPT_IGNORE_CVREC); + if (!SymInitialize(GetCurrentProcess(), "", 1)) { + jl_safe_printf("WARNING: failed to initialize stack walk info\n"); + } + needsSymRefreshModuleList = 0; +#else + // nongnu libunwind initialization is only threadsafe on architecture where the + // author could access TSAN, per https://github.com/libunwind/libunwind/pull/109 + // so we need to do this once early (before threads) + rec_backtrace(NULL, 0, 0); +#endif + libsupport_init(); + jl_safepoint_init(); + jl_page_size = jl_getpagesize(); htable_new(&jl_current_modules, 0); JL_MUTEX_INIT(&jl_modules_mutex); jl_precompile_toplevel_module = NULL; @@ -732,7 +757,6 @@ JL_DLLEXPORT void julia_init(JL_IMAGE_SEARCH rel) restore_signals(); jl_init_intrinsic_properties(); - jl_page_size = jl_getpagesize(); jl_prep_sanitizers(); void *stack_lo, *stack_hi; jl_init_stack_limits(1, &stack_lo, &stack_hi); @@ -746,17 +770,12 @@ JL_DLLEXPORT void julia_init(JL_IMAGE_SEARCH rel) (HMODULE*)&jl_libjulia_handle)) { jl_error("could not load base module"); } - jl_ntdll_handle = jl_dlopen("ntdll.dll", 0); // bypass julia's pathchecking for system dlls - jl_kernel32_handle = jl_dlopen("kernel32.dll", 0); - jl_crtdll_handle = jl_dlopen(jl_crtdll_name, 0); - jl_winsock_handle = jl_dlopen("ws2_32.dll", 0); - uv_mutex_init(&jl_in_stackwalk); - SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES | SYMOPT_IGNORE_CVREC); - if (!SymInitialize(GetCurrentProcess(), "", 1)) { - jl_printf(JL_STDERR, "WARNING: failed to initialize stack walk info\n"); - } + jl_ntdll_handle = jl_dlopen("ntdll.dll", JL_RTLD_NOLOAD); // bypass julia's pathchecking for system dlls + jl_kernel32_handle = jl_dlopen("kernel32.dll", JL_RTLD_NOLOAD); + jl_crtdll_handle = jl_dlopen(jl_crtdll_name, JL_RTLD_NOLOAD); + jl_winsock_handle = jl_dlopen("ws2_32.dll", JL_RTLD_NOLOAD); + HMODULE jl_dbghelp = (HMODULE) jl_dlopen("dbghelp.dll", JL_RTLD_NOLOAD); needsSymRefreshModuleList = 0; - HMODULE jl_dbghelp = (HMODULE) jl_dlopen("dbghelp.dll", 0); if (jl_dbghelp) jl_dlsym(jl_dbghelp, "SymRefreshModuleList", (void **)&hSymRefreshModuleList, 1); #else @@ -774,7 +793,6 @@ JL_DLLEXPORT void julia_init(JL_IMAGE_SEARCH rel) } jl_init_rand(); - jl_init_profile_lock(); jl_init_runtime_ccall(); jl_init_tasks(); jl_init_threading(); diff --git a/src/jitlayers.h b/src/jitlayers.h index e7dfa8959c3766..f62ee595a843b4 100644 --- a/src/jitlayers.h +++ b/src/jitlayers.h @@ -19,7 +19,8 @@ #include #include "julia_assert.h" -#include "debug-registry.h" +#include "julia.h" +#include "julia_internal.h" #include "platform.h" #include @@ -487,10 +488,6 @@ class JuliaOJIT { size_t getTotalBytes() const JL_NOTSAFEPOINT; - JITDebugInfoRegistry &getDebugInfoRegistry() JL_NOTSAFEPOINT { - return DebugRegistry; - } - jl_locked_stream &get_dump_emitted_mi_name_stream() JL_NOTSAFEPOINT { return dump_emitted_mi_name_stream; } @@ -512,8 +509,6 @@ class JuliaOJIT { orc::JITDylib &GlobalJD; orc::JITDylib &JD; - JITDebugInfoRegistry DebugRegistry; - //Map and inc are guarded by RLST_mutex std::mutex RLST_mutex{}; int RLST_inc = 0; diff --git a/src/julia.h b/src/julia.h index d2ffa13dc2796e..a32859364eb4d6 100644 --- a/src/julia.h +++ b/src/julia.h @@ -2065,7 +2065,7 @@ typedef int jl_uv_os_fd_t; JL_DLLEXPORT int jl_process_events(void); -JL_DLLEXPORT struct uv_loop_s *jl_global_event_loop(void); +JL_DLLEXPORT struct uv_loop_s *jl_global_event_loop(void) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_close_uv(struct uv_handle_s *handle); diff --git a/src/julia_internal.h b/src/julia_internal.h index 6ea8ecafc2497b..7565967b0a270e 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -868,7 +868,7 @@ void jl_init_thread_heap(jl_ptls_t ptls) JL_NOTSAFEPOINT; void jl_init_int32_int64_cache(void); JL_DLLEXPORT void jl_init_options(void); -void jl_teardown_codegen(void); +void jl_teardown_codegen(void) JL_NOTSAFEPOINT; void jl_set_base_ctx(char *__stk); @@ -920,7 +920,7 @@ void jl_safepoint_defer_sigint(void); // Return `1` if the sigint should be delivered and `0` if there's no sigint // to be delivered. int jl_safepoint_consume_sigint(void); -void jl_wake_libuv(void); +void jl_wake_libuv(void) JL_NOTSAFEPOINT; void jl_set_pgcstack(jl_gcframe_t **) JL_NOTSAFEPOINT; #if defined(_OS_DARWIN_) diff --git a/src/partr.c b/src/partr.c index f5f63f54e7d259..3840164d6f7347 100644 --- a/src/partr.c +++ b/src/partr.c @@ -183,7 +183,7 @@ static int sleep_check_after_threshold(uint64_t *start_cycles) } -static int wake_thread(int16_t tid) +static int wake_thread(int16_t tid) JL_NOTSAFEPOINT { jl_ptls_t other = jl_atomic_load_relaxed(&jl_all_tls_states)[tid]; int8_t state = sleeping; @@ -201,7 +201,7 @@ static int wake_thread(int16_t tid) } -static void wake_libuv(void) +static void wake_libuv(void) JL_NOTSAFEPOINT { JULIA_DEBUG_SLEEPWAKE( io_wakeup_enter = cycleclock() ); jl_wake_libuv(); @@ -209,7 +209,7 @@ static void wake_libuv(void) } /* ensure thread tid is awake if necessary */ -JL_DLLEXPORT void jl_wakeup_thread(int16_t tid) +JL_DLLEXPORT void jl_wakeup_thread(int16_t tid) JL_NOTSAFEPOINT { jl_task_t *ct = jl_current_task; int16_t self = jl_atomic_load_relaxed(&ct->tid); diff --git a/src/signal-handling.c b/src/signal-handling.c index 391a97055af848..e241fd22ecb186 100644 --- a/src/signal-handling.c +++ b/src/signal-handling.c @@ -419,17 +419,18 @@ void jl_show_sigill(void *_ctx) // this is generally quite an foolish operation, but does free you up to do // arbitrary things on this stack now without worrying about corrupt state that // existed already on it -void jl_task_frame_noreturn(jl_task_t *ct) +void jl_task_frame_noreturn(jl_task_t *ct) JL_NOTSAFEPOINT { jl_set_safe_restore(NULL); if (ct) { ct->gcstack = NULL; ct->eh = NULL; - ct->excstack = NULL; + ct->world_age = 1; ct->ptls->locks.len = 0; ct->ptls->in_pure_callback = 0; ct->ptls->in_finalizer = 0; - ct->world_age = 1; + ct->ptls->defer_signal = 0; + jl_atomic_store_release(&ct->ptls->gc_state, 0); // forceably exit GC (if we were in it) or safe into unsafe, without the mandatory safepoint } } diff --git a/src/stackwalk.c b/src/stackwalk.c index 4965e46931016b..caf0705b85be7c 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -214,10 +214,10 @@ NOINLINE size_t rec_backtrace(jl_bt_element_t *bt_data, size_t maxsize, int skip int r = jl_unw_get(&context); if (r < 0) return 0; - jl_gcframe_t *pgcstack = jl_pgcstack; bt_cursor_t cursor; - if (!jl_unw_init(&cursor, &context)) + if (!jl_unw_init(&cursor, &context) || maxsize == 0) return 0; + jl_gcframe_t *pgcstack = jl_pgcstack; size_t bt_size = 0; jl_unw_stepn(&cursor, bt_data, &bt_size, NULL, maxsize, skip + 1, &pgcstack, 0); return bt_size; diff --git a/src/task.c b/src/task.c index d97cadffa55c62..7373de937b9aeb 100644 --- a/src/task.c +++ b/src/task.c @@ -474,6 +474,7 @@ JL_NO_ASAN static void ctx_switch(jl_task_t *lastt) if (killed) { *pt = NULL; // can't fail after here: clear the gc-root for the target task now lastt->gcstack = NULL; + lastt->eh = NULL; if (!lastt->copy_stack && lastt->stkbuf) { // early free of stkbuf back to the pool jl_release_task_stack(ptls, lastt); diff --git a/src/threading.c b/src/threading.c index e825870edc4ea8..db9df0bad0ddec 100644 --- a/src/threading.c +++ b/src/threading.c @@ -417,11 +417,22 @@ JL_DLLEXPORT jl_gcframe_t **jl_adopt_thread(void) JL_NOTSAFEPOINT_LEAVE return &ct->gcstack; } +void jl_task_frame_noreturn(jl_task_t *ct) JL_NOTSAFEPOINT; + static void jl_delete_thread(void *value) JL_NOTSAFEPOINT_ENTER { +#ifndef _OS_WINDOWS_ + pthread_setspecific(jl_task_exit_key, NULL); +#endif jl_ptls_t ptls = (jl_ptls_t)value; + // safepoint until GC exit, in case GC was running concurrently while in + // prior unsafe-region (before we let it release the stack memory) + (void)jl_gc_unsafe_enter(ptls); + jl_atomic_store_relaxed(&ptls->sleep_check_state, 2); // dead, interpreted as sleeping and unwakeable + jl_fence(); + jl_wakeup_thread(0); // force thread 0 to see that we do not have the IO lock (and am dead) // Acquire the profile write lock, to ensure we are not racing with the `kill` - // call in the profile code which will also try to look at these variables. + // call in the profile code which will also try to look at this thread. // We have no control over when the user calls pthread_join, so we must do // this here by blocking. This also synchronizes our read of `current_task` // (which is the flag we currently use to check the liveness state of a thread). @@ -434,11 +445,22 @@ static void jl_delete_thread(void *value) JL_NOTSAFEPOINT_ENTER #else pthread_mutex_lock(&in_signal_lock); #endif -#ifndef _OS_WINDOWS_ - pthread_setspecific(jl_task_exit_key, NULL); -#endif + // need to clear pgcstack and eh, but we can clear everything now too + jl_task_frame_noreturn(jl_atomic_load_relaxed(&ptls->current_task)); + if (jl_set_task_tid(ptls->root_task, ptls->tid)) { + // the system will probably free this stack memory soon + // so prevent any other thread from accessing it later + jl_task_frame_noreturn(ptls->root_task); + } + else { + // Uh oh. The user cleared the sticky bit so it started running + // elsewhere, then called pthread_exit on this thread. This is not + // recoverable. Though we could just hang here, a fatal message is better. + jl_safe_printf("fatal: thread exited from wrong Task.\n"); + abort(); + } jl_atomic_store_relaxed(&ptls->current_task, NULL); // dead - jl_atomic_store_relaxed(&ptls->sleep_check_state, 2); // dead, interpreted as sleeping and unwakeable + // finally, release all of the locks we had grabbed #ifdef _OS_WINDOWS_ jl_unlock_profile_wr(); #elif defined(JL_DISABLE_LIBUNWIND) @@ -448,9 +470,15 @@ static void jl_delete_thread(void *value) JL_NOTSAFEPOINT_ENTER #else pthread_mutex_unlock(&in_signal_lock); #endif + // then park in safe-region (void)jl_gc_safe_enter(ptls); } +//// debugging hack: if we are exiting too fast for error message printing on threads, +//// enabling this will stall that first thread just before exiting, to give +//// the other threads time to fail and emit their failure message +//__attribute__((destructor)) static void _waitthreaddeath(void) { sleep(1); } + JL_DLLEXPORT jl_mutex_t jl_codegen_lock; jl_mutex_t typecache_lock; diff --git a/src/timing.h b/src/timing.h index fd84707ad5d2c2..70f34fa89f543a 100644 --- a/src/timing.h +++ b/src/timing.h @@ -7,7 +7,7 @@ extern "C" { #endif void jl_init_timing(void); -void jl_destroy_timing(void); +void jl_destroy_timing(void) JL_NOTSAFEPOINT; #ifdef __cplusplus } #endif @@ -87,7 +87,7 @@ struct _jl_timing_block_t { // typedef in julia.h #endif }; -STATIC_INLINE void _jl_timing_block_stop(jl_timing_block_t *block, uint64_t t) { +STATIC_INLINE void _jl_timing_block_stop(jl_timing_block_t *block, uint64_t t) JL_NOTSAFEPOINT { #ifdef JL_DEBUG_BUILD assert(block->running); block->running = 0; @@ -95,7 +95,7 @@ STATIC_INLINE void _jl_timing_block_stop(jl_timing_block_t *block, uint64_t t) { block->total += t - block->t0; } -STATIC_INLINE void _jl_timing_block_start(jl_timing_block_t *block, uint64_t t) { +STATIC_INLINE void _jl_timing_block_start(jl_timing_block_t *block, uint64_t t) JL_NOTSAFEPOINT { #ifdef JL_DEBUG_BUILD assert(!block->running); block->running = 1; @@ -103,7 +103,7 @@ STATIC_INLINE void _jl_timing_block_start(jl_timing_block_t *block, uint64_t t) block->t0 = t; } -STATIC_INLINE uint64_t _jl_timing_block_init(jl_timing_block_t *block, int owner) { +STATIC_INLINE uint64_t _jl_timing_block_init(jl_timing_block_t *block, int owner) JL_NOTSAFEPOINT { uint64_t t = cycleclock(); block->owner = owner; block->total = 0; @@ -114,7 +114,7 @@ STATIC_INLINE uint64_t _jl_timing_block_init(jl_timing_block_t *block, int owner return t; } -STATIC_INLINE void _jl_timing_block_ctor(jl_timing_block_t *block, int owner) { +STATIC_INLINE void _jl_timing_block_ctor(jl_timing_block_t *block, int owner) JL_NOTSAFEPOINT { uint64_t t = _jl_timing_block_init(block, owner); jl_task_t *ct = jl_current_task; jl_timing_block_t **prevp = &ct->ptls->timing_stack; @@ -124,7 +124,7 @@ STATIC_INLINE void _jl_timing_block_ctor(jl_timing_block_t *block, int owner) { *prevp = block; } -STATIC_INLINE void _jl_timing_block_destroy(jl_timing_block_t *block) { +STATIC_INLINE void _jl_timing_block_destroy(jl_timing_block_t *block) JL_NOTSAFEPOINT { uint64_t t = cycleclock(); jl_task_t *ct = jl_current_task; _jl_timing_block_stop(block, t); @@ -139,10 +139,10 @@ STATIC_INLINE void _jl_timing_block_destroy(jl_timing_block_t *block) { #ifdef __cplusplus struct jl_timing_block_cpp_t { jl_timing_block_t block; - jl_timing_block_cpp_t(int owner) { + jl_timing_block_cpp_t(int owner) JL_NOTSAFEPOINT { _jl_timing_block_ctor(&block, owner); } - ~jl_timing_block_cpp_t() { + ~jl_timing_block_cpp_t() JL_NOTSAFEPOINT { _jl_timing_block_destroy(&block); } jl_timing_block_cpp_t(const jl_timing_block_cpp_t&) = delete; From 0c3b950e02550cfd336cc8eaa6edbe3f23e1a4c4 Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Sat, 14 Jan 2023 09:33:22 +0100 Subject: [PATCH 323/387] Add inline to cache flags (#48179) and fix the behavior of the check-bounds flag. Co-authored-by: Kristoffer Carlsson --- base/loading.jl | 19 ++++++++++++------- doc/src/devdocs/pkgimg.md | 1 + src/staticdata_utils.c | 25 ++++++++++++------------ test/loading.jl | 40 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 19 deletions(-) diff --git a/base/loading.jl b/base/loading.jl index 5ce41139a1b127..b1f6d608daf328 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -2577,25 +2577,30 @@ function check_clone_targets(clone_targets) end struct CacheFlags - # ??OOCDDP - see jl_cache_flags + # OOICCDDP - see jl_cache_flags use_pkgimages::Bool debug_level::Int - check_bounds::Bool + check_bounds::Int + inline::Bool opt_level::Int - CacheFlags(f::Int) = CacheFlags(UInt8(f)) function CacheFlags(f::UInt8) use_pkgimages = Bool(f & 1) debug_level = Int((f >> 1) & 3) - check_bounds = Bool((f >> 2) & 1) - opt_level = Int((f >> 4) & 3) - new(use_pkgimages, debug_level, check_bounds, opt_level) + check_bounds = Int((f >> 3) & 3) + inline = Bool((f >> 5) & 1) + opt_level = Int((f >> 6) & 3) # define OPT_LEVEL in statiddata_utils + new(use_pkgimages, debug_level, check_bounds, inline, opt_level) end end +CacheFlags(f::Int) = CacheFlags(UInt8(f)) +CacheFlags() = CacheFlags(ccall(:jl_cache_flags, UInt8, ())) + function show(io::IO, cf::CacheFlags) print(io, "use_pkgimages = ", cf.use_pkgimages) print(io, ", debug_level = ", cf.debug_level) print(io, ", check_bounds = ", cf.check_bounds) + print(io, ", inline = ", cf.inline) print(io, ", opt_level = ", cf.opt_level) end @@ -2619,7 +2624,7 @@ end if ccall(:jl_match_cache_flags, UInt8, (UInt8,), flags) == 0 @debug """ Rejecting cache file $cachefile for $modkey since the flags are mismatched - current session: $(CacheFlags(ccall(:jl_cache_flags, UInt8, ()))) + current session: $(CacheFlags()) cache file: $(CacheFlags(flags)) """ return true diff --git a/doc/src/devdocs/pkgimg.md b/doc/src/devdocs/pkgimg.md index 8230c4b91b338d..f97fc36750f180 100644 --- a/doc/src/devdocs/pkgimg.md +++ b/doc/src/devdocs/pkgimg.md @@ -43,6 +43,7 @@ that were created with different flags will be rejected. - `-g`, `--debug-info`: Exact match required since it changes code generation. - `--check-bounds`: Exact match required since it changes code generation. +- `--inline`: Exact match required since it changes code generation. - `--pkgimages`: To allow running without object caching enabled. - `-O`, `--optimize`: Reject package images generated for a lower optimization level, but allow for higher optimization levels to be loaded. diff --git a/src/staticdata_utils.c b/src/staticdata_utils.c index 297dbbdf085e31..d63d2baefaefa1 100644 --- a/src/staticdata_utils.c +++ b/src/staticdata_utils.c @@ -613,17 +613,18 @@ static void write_mod_list(ios_t *s, jl_array_t *a) write_int32(s, 0); } +// OPT_LEVEL should always be the upper bits +#define OPT_LEVEL 6 + JL_DLLEXPORT uint8_t jl_cache_flags(void) { - // ??OOCDDP + // OOICCDDP uint8_t flags = 0; - flags |= (jl_options.use_pkgimages & 1); - flags |= (jl_options.debug_level & 3) << 1; - flags |= (jl_options.check_bounds & 1) << 2; - flags |= (jl_options.opt_level & 3) << 4; - // NOTES: - // In contrast to check-bounds, inline has no "observable effect" - // CacheFlags in loading.jl should be kept in-sync with this + flags |= (jl_options.use_pkgimages & 1); // 0-bit + flags |= (jl_options.debug_level & 3) << 1; // 1-2 bit + flags |= (jl_options.check_bounds & 3) << 3; // 3-4 bit + flags |= (jl_options.can_inline & 1) << 5; // 5-bit + flags |= (jl_options.opt_level & 3) << OPT_LEVEL; // 6-7 bit return flags; } @@ -639,13 +640,13 @@ JL_DLLEXPORT uint8_t jl_match_cache_flags(uint8_t flags) return 1; } - // 2. Check all flags that must be exact - uint8_t mask = (1 << 4)-1; + // 2. Check all flags, execept opt level must be exact + uint8_t mask = (1 << OPT_LEVEL)-1; if ((flags & mask) != (current_flags & mask)) return 0; // 3. allow for higher optimization flags in cache - flags >>= 4; - current_flags >>= 4; + flags >>= OPT_LEVEL; + current_flags >>= OPT_LEVEL; return flags >= current_flags; } diff --git a/test/loading.jl b/test/loading.jl index 497dfaed4af187..a7e48d6b02160b 100644 --- a/test/loading.jl +++ b/test/loading.jl @@ -1025,6 +1025,46 @@ end end end +pkgimage(val) = val == 1 ? `--pkgimage=yes` : `--pkgimage=no` +opt_level(val) = `-O$val` +debug_level(val) = `-g$val` +inline(val) = val == 1 ? `--inline=yes` : `--inline=no` +check_bounds(val) = if val == 0 + `--check-bounds=auto` +elseif val == 1 + `--check-bounds=yes` +elseif val == 2 + `--check-bounds=no` +end + +@testset "CacheFlags" begin + cf = Base.CacheFlags() + opts = Base.JLOptions() + @test cf.use_pkgimages == opts.use_pkgimages + @test cf.debug_level == opts.debug_level + @test cf.check_bounds == opts.check_bounds + @test cf.inline == opts.can_inline + @test cf.opt_level == opts.opt_level + + # OOICCDDP + for (P, D, C, I, O) in Iterators.product(0:1, 0:2, 0:2, 0:1, 0:3) + julia = joinpath(Sys.BINDIR, Base.julia_exename()) + script = """ + using Test + let + cf = Base.CacheFlags() + opts = Base.JLOptions() + @test cf.use_pkgimages == opts.use_pkgimages == $P + @test cf.debug_level == opts.debug_level == $D + @test cf.check_bounds == opts.check_bounds == $C + @test cf.inline == opts.can_inline == $I + @test cf.opt_level == opts.opt_level == $O + end + """ + cmd = `$julia $(pkgimage(P)) $(opt_level(O)) $(debug_level(D)) $(check_bounds(C)) $(inline(I)) -e $script` + @test success(pipeline(cmd; stderr)) + end +end empty!(Base.DEPOT_PATH) append!(Base.DEPOT_PATH, original_depot_path) From 65e69190791ec31370af6c87725f59b7e7b21c3e Mon Sep 17 00:00:00 2001 From: Izam Mohammed <106471909+izam-mohammed@users.noreply.github.com> Date: Sat, 14 Jan 2023 17:56:35 +0530 Subject: [PATCH 324/387] Corrected a spelling mistake in CONTRIBUTING.md (#48274) Corrected a spelling mistake in CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 94a10296754d31..099ef6b03509b2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -293,7 +293,7 @@ The process of [creating a patch release](https://docs.julialang.org/en/v1/devdo 6. Ping `@JuliaLang/releases` to tag the patch release and update the website. 7. Open a pull request that bumps the version of the relevant minor release to the - next prerelase patch version, e.g. as in [this pull request](https://github.com/JuliaLang/julia/pull/37724). + next prerelease patch version, e.g. as in [this pull request](https://github.com/JuliaLang/julia/pull/37724). Step 2 above, i.e. backporting commits to the `backports-release-X.Y` branch, has largely been automated via [`Backporter`](https://github.com/KristofferC/Backporter): Backporter From 0371bf44bf6bfd6ee9fbfc32d478c2ff4c97b08b Mon Sep 17 00:00:00 2001 From: Jakob Nybo Nissen Date: Sat, 14 Jan 2023 18:38:28 +0100 Subject: [PATCH 325/387] In string search, replace unsafe_wrap with codeunits (#48275) Currently, `findfirst(::String, ::String)` will eventually end up calling `unsafe_wrap` on both arguments, in order to use Julia's generic vector search functions. This causes unnecessary allocations. This PR replaces use of `unsafe_wrap` with `codeunits`, removing the allocation. See also: #45393 --- base/strings/search.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/base/strings/search.jl b/base/strings/search.jl index 032aa8257b26da..1bb4936661c51d 100644 --- a/base/strings/search.jl +++ b/base/strings/search.jl @@ -55,7 +55,7 @@ function _search(a::ByteArray, b::AbstractChar, i::Integer = 1) if isascii(b) _search(a,UInt8(b),i) else - _search(a,unsafe_wrap(Vector{UInt8},string(b)),i).start + _search(a,codeunits(string(b)),i).start end end @@ -98,7 +98,7 @@ function _rsearch(a::ByteArray, b::AbstractChar, i::Integer = length(a)) if isascii(b) _rsearch(a,UInt8(b),i) else - _rsearch(a,unsafe_wrap(Vector{UInt8},string(b)),i).start + _rsearch(a,codeunits(string(b)),i).start end end @@ -207,7 +207,7 @@ _nthbyte(t::AbstractVector, index) = t[index + (firstindex(t)-1)] function _searchindex(s::String, t::String, i::Integer) # Check for fast case of a single byte lastindex(t) == 1 && return something(findnext(isequal(t[1]), s, i), 0) - _searchindex(unsafe_wrap(Vector{UInt8},s), unsafe_wrap(Vector{UInt8},t), i) + _searchindex(codeunits(s), codeunits(t), i) end function _searchindex(s::AbstractVector{<:Union{Int8,UInt8}}, @@ -521,7 +521,7 @@ function _rsearchindex(s::String, t::String, i::Integer) return something(findprev(isequal(t[1]), s, i), 0) elseif lastindex(t) != 0 j = i ≤ ncodeunits(s) ? nextind(s, i)-1 : i - return _rsearchindex(unsafe_wrap(Vector{UInt8}, s), unsafe_wrap(Vector{UInt8}, t), j) + return _rsearchindex(codeunits(s), codeunits(t), j) elseif i > sizeof(s) return 0 elseif i == 0 From 1bfdf987558c2ce3dae9b329dd4d27e7d5883862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mos=C3=A8=20Giordano?= Date: Sun, 15 Jan 2023 21:24:23 +0000 Subject: [PATCH 326/387] Use `get_bool_env` in more places (#48202) --- base/linking.jl | 2 +- stdlib/Distributed/test/distributed_exec.jl | 2 +- stdlib/Test/src/Test.jl | 2 +- test/misc.jl | 2 +- test/runtests.jl | 6 +----- test/subarray.jl | 2 +- 6 files changed, 6 insertions(+), 10 deletions(-) diff --git a/base/linking.jl b/base/linking.jl index 288279347f1c59..fb9f6d087a2d0b 100644 --- a/base/linking.jl +++ b/base/linking.jl @@ -63,7 +63,7 @@ end const VERBOSE = Ref{Bool}(false) function __init__() - VERBOSE[] = parse(Bool, get(ENV, "JULIA_VERBOSE_LINKING", "false")) + VERBOSE[] = Base.get_bool_env("JULIA_VERBOSE_LINKING", false) __init_lld_path() PATH[] = dirname(lld_path[]) diff --git a/stdlib/Distributed/test/distributed_exec.jl b/stdlib/Distributed/test/distributed_exec.jl index c2c6efaa6f7e19..c3eca69bb52687 100644 --- a/stdlib/Distributed/test/distributed_exec.jl +++ b/stdlib/Distributed/test/distributed_exec.jl @@ -681,7 +681,7 @@ clear!(wp) # - ssh addprocs requires sshd to be running locally with passwordless login enabled. # The test block is enabled by defining env JULIA_TESTFULL=1 -DoFullTest = Bool(parse(Int,(get(ENV, "JULIA_TESTFULL", "0")))) +DoFullTest = Base.get_bool_env("JULIA_TESTFULL", false) if DoFullTest println("Testing exception printing on remote worker from a `remote_do` call") diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index 742a79f0e84824..f1216371d0b27f 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -1419,7 +1419,7 @@ macro testset(args...) error("Expected function call, begin/end block or for loop as argument to @testset") end - FAIL_FAST[] = something(tryparse(Bool, get(ENV, "JULIA_TEST_FAILFAST", "false")), false) + FAIL_FAST[] = Base.get_bool_env("JULIA_TEST_FAILFAST", false) if tests.head === :for return testset_forloop(args, tests, __source__) diff --git a/test/misc.jl b/test/misc.jl index 480334fdaf0ae6..7c9fa3c1fbc414 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -906,7 +906,7 @@ mutable struct Demo_20254 end # these cause stack overflows and are a little flaky on CI, ref #20256 -if Bool(parse(Int,(get(ENV, "JULIA_TESTFULL", "0")))) +if Base.get_bool_env("JULIA_TESTFULL", false) function Demo_20254(arr::AbstractArray=Any[]) Demo_20254(string.(arr)) end diff --git a/test/runtests.jl b/test/runtests.jl index 3227804cf7b479..cce4acf44d1361 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -102,11 +102,7 @@ cd(@__DIR__) do # * https://github.com/JuliaLang/julia/pull/29384 # * https://github.com/JuliaLang/julia/pull/40348 n = 1 - JULIA_TEST_USE_MULTIPLE_WORKERS = get(ENV, "JULIA_TEST_USE_MULTIPLE_WORKERS", "") |> - strip |> - lowercase |> - s -> tryparse(Bool, s) |> - x -> x === true + JULIA_TEST_USE_MULTIPLE_WORKERS = Base.get_bool_env("JULIA_TEST_USE_MULTIPLE_WORKERS", false) # If the `JULIA_TEST_USE_MULTIPLE_WORKERS` environment variable is set to `true`, we use # multiple worker processes regardless of the value of `net_on`. # Otherwise, we use multiple worker processes if and only if `net_on` is true. diff --git a/test/subarray.jl b/test/subarray.jl index 884a36670a31e3..e22c1394cbfc22 100644 --- a/test/subarray.jl +++ b/test/subarray.jl @@ -256,7 +256,7 @@ runviews(SB::AbstractArray{T,0}, indexN, indexNN, indexNNN) where {T} = nothing ######### Tests ######### -testfull = Bool(parse(Int,(get(ENV, "JULIA_TESTFULL", "0")))) +testfull = Base.get_bool_env("JULIA_TESTFULL", false) ### Views from Arrays ### index5 = (1, :, 2:5, [4,1,5], reshape([2]), view(1:5,[2 3 4 1])) # all work with at least size 5 From 12c3b1cb67ee8a40c5334a8a9ef26a5eef16093f Mon Sep 17 00:00:00 2001 From: Simone Carlo Surace <51025924+simsurace@users.noreply.github.com> Date: Mon, 16 Jan 2023 11:55:48 +0100 Subject: [PATCH 327/387] Fix mistake in docstring of `keys(::RegexMatch)` (#48252) This method was added in 1.7.0 and is not available in any 1.6 release --- base/regex.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/regex.jl b/base/regex.jl index 2837cbb819a7d8..820fc3eca502ac 100644 --- a/base/regex.jl +++ b/base/regex.jl @@ -204,8 +204,8 @@ That is, `idx` will be in the return value even if `m[idx] == nothing`. Unnamed capture groups will have integer keys corresponding to their index. Named capture groups will have string keys. -!!! compat "Julia 1.6" - This method was added in Julia 1.6 +!!! compat "Julia 1.7" + This method was added in Julia 1.7 # Examples ```jldoctest From a9ce60a8ceef96cc1d5ab36efbdd8954fa2c6471 Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Mon, 16 Jan 2023 13:16:48 +0100 Subject: [PATCH 328/387] Avoid dtrace regenerating the header (#48278) --- src/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Makefile b/src/Makefile index bd3f2be27219d4..0baa34fedf8771 100644 --- a/src/Makefile +++ b/src/Makefile @@ -91,6 +91,7 @@ endif else DTRACE_HEADERS := endif +.SECONDARY: $(addprefix $(BUILDDIR)/,$(DTRACE_HEADERS)) # headers are used for dependency tracking, while public headers will be part of the dist UV_HEADERS := From 8c48fe9f01b27d9bdc97070d4865e3ece992d49b Mon Sep 17 00:00:00 2001 From: Sukera <11753998+Seelengrab@users.noreply.github.com> Date: Mon, 16 Jan 2023 13:43:55 +0100 Subject: [PATCH 329/387] Change default output of `[@]code_native` to intel syntax (#48103) * Change `[@]code_{native,llvm}` default output to intel syntax * Add code_native change to NEWS.md Co-authored-by: Sukera --- NEWS.md | 4 ++++ stdlib/InteractiveUtils/src/codeview.jl | 10 +++++----- stdlib/InteractiveUtils/src/macros.jl | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index d3b9f618ddceef..058c35ae55a66f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -82,6 +82,10 @@ Standard library changes #### DelimitedFiles +#### InteractiveUtils + + * `code_native` and `@code_native` now default to intel syntax instead of AT&T. + Deprecated or removed --------------------- diff --git a/stdlib/InteractiveUtils/src/codeview.jl b/stdlib/InteractiveUtils/src/codeview.jl index 76666813853528..8c0658142c019c 100644 --- a/stdlib/InteractiveUtils/src/codeview.jl +++ b/stdlib/InteractiveUtils/src/codeview.jl @@ -268,7 +268,7 @@ Keyword argument `debuginfo` may be one of source (default) or none, to specify """ function code_llvm(io::IO, @nospecialize(f), @nospecialize(types), raw::Bool, dump_module::Bool=false, optimize::Bool=true, debuginfo::Symbol=:default) - d = _dump_function(f, types, false, false, !raw, dump_module, :att, optimize, debuginfo, false) + d = _dump_function(f, types, false, false, !raw, dump_module, :intel, optimize, debuginfo, false) if highlighting[:llvm] && get(io, :color, false)::Bool print_llvm(io, d) else @@ -281,12 +281,12 @@ code_llvm(@nospecialize(f), @nospecialize(types=Base.default_tt(f)); raw=false, code_llvm(stdout, f, types; raw, dump_module, optimize, debuginfo) """ - code_native([io=stdout,], f, types; syntax=:att, debuginfo=:default, binary=false, dump_module=true) + code_native([io=stdout,], f, types; syntax=:intel, debuginfo=:default, binary=false, dump_module=true) Prints the native assembly instructions generated for running the method matching the given generic function and type signature to `io`. -* Set assembly syntax by setting `syntax` to `:att` (default) for AT&T syntax or `:intel` for Intel syntax. +* Set assembly syntax by setting `syntax` to `:intel` (default) for intel syntax or `:att` for AT&T syntax. * Specify verbosity of code comments by setting `debuginfo` to `:source` (default) or `:none`. * If `binary` is `true`, also print the binary machine code for each instruction precedented by an abbreviated address. * If `dump_module` is `false`, do not print metadata such as rodata or directives. @@ -294,7 +294,7 @@ generic function and type signature to `io`. See also: [`@code_native`](@ref), [`code_llvm`](@ref), [`code_typed`](@ref) and [`code_lowered`](@ref) """ function code_native(io::IO, @nospecialize(f), @nospecialize(types=Base.default_tt(f)); - dump_module::Bool=true, syntax::Symbol=:att, debuginfo::Symbol=:default, binary::Bool=false) + dump_module::Bool=true, syntax::Symbol=:intel, debuginfo::Symbol=:default, binary::Bool=false) d = _dump_function(f, types, true, false, false, dump_module, syntax, true, debuginfo, binary) if highlighting[:native] && get(io, :color, false)::Bool print_native(io, d) @@ -302,7 +302,7 @@ function code_native(io::IO, @nospecialize(f), @nospecialize(types=Base.default_ print(io, d) end end -code_native(@nospecialize(f), @nospecialize(types=Base.default_tt(f)); dump_module::Bool=true, syntax::Symbol=:att, debuginfo::Symbol=:default, binary::Bool=false) = +code_native(@nospecialize(f), @nospecialize(types=Base.default_tt(f)); dump_module::Bool=true, syntax::Symbol=:intel, debuginfo::Symbol=:default, binary::Bool=false) = code_native(stdout, f, types; dump_module, syntax, debuginfo, binary) code_native(::IO, ::Any, ::Symbol) = error("invalid code_native call") # resolve ambiguous call diff --git a/stdlib/InteractiveUtils/src/macros.jl b/stdlib/InteractiveUtils/src/macros.jl index 9c2200db0245dd..135c207654ca0b 100644 --- a/stdlib/InteractiveUtils/src/macros.jl +++ b/stdlib/InteractiveUtils/src/macros.jl @@ -345,7 +345,7 @@ by putting it before the function call, like this: @code_native syntax=:intel debuginfo=:default binary=true dump_module=false f(x) -* Set assembly syntax by setting `syntax` to `:att` (default) for AT&T syntax or `:intel` for Intel syntax. +* Set assembly syntax by setting `syntax` to `:intel` (default) for Intel syntax or `:att` for AT&T syntax. * Specify verbosity of code comments by setting `debuginfo` to `:source` (default) or `:none`. * If `binary` is `true`, also print the binary machine code for each instruction precedented by an abbreviated address. * If `dump_module` is `false`, do not print metadata such as rodata or directives. From 74fc310748291073b6534d6fbc267ab8c8b4b599 Mon Sep 17 00:00:00 2001 From: Yue Yang Date: Mon, 16 Jan 2023 21:25:37 +0800 Subject: [PATCH 330/387] Fix code block type in doc (#48298) --- doc/src/manual/networking-and-streams.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/src/manual/networking-and-streams.md b/doc/src/manual/networking-and-streams.md index fc62632433850c..1ee2f33de5c23c 100644 --- a/doc/src/manual/networking-and-streams.md +++ b/doc/src/manual/networking-and-streams.md @@ -368,7 +368,7 @@ UDP can use special multicast addresses to allow simultaneous communication betw To transmit data over UDP multicast, simply `recv` on the socket, and the first packet received will be returned. Note that it may not be the first packet that you sent however! -``` +```julia using Sockets group = ip"228.5.6.7" socket = Sockets.UDPSocket() @@ -384,7 +384,7 @@ close(socket) To transmit data over UDP multicast, simply `send` to the socket. Notice that it is not necessary for a sender to join the multicast group. -``` +```julia using Sockets group = ip"228.5.6.7" socket = Sockets.UDPSocket() @@ -397,7 +397,7 @@ close(socket) This example gives the same functionality as the previous program, but uses IPv6 as the network-layer protocol. Listener: -``` +```julia using Sockets group = Sockets.IPv6("ff05::5:6:7") socket = Sockets.UDPSocket() @@ -409,7 +409,7 @@ close(socket) ``` Sender: -``` +```julia using Sockets group = Sockets.IPv6("ff05::5:6:7") socket = Sockets.UDPSocket() From 018a9aeca4998e867d76aa4bb6571d2237ea8427 Mon Sep 17 00:00:00 2001 From: DilumAluthgeBot <43731525+DilumAluthgeBot@users.noreply.github.com> Date: Mon, 16 Jan 2023 11:25:52 -0500 Subject: [PATCH 331/387] =?UTF-8?q?=F0=9F=A4=96=20[master]=20Bump=20the=20?= =?UTF-8?q?SparseArrays=20stdlib=20from=20a3116b9=20to=20287c6c6=20(#48287?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dilum Aluthge --- .../md5 | 1 + .../sha512 | 1 + .../md5 | 1 - .../sha512 | 1 - stdlib/SparseArrays.version | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 deps/checksums/SparseArrays-287c6c64a30bdeeee93e100a9f04d7169ee65f77.tar.gz/md5 create mode 100644 deps/checksums/SparseArrays-287c6c64a30bdeeee93e100a9f04d7169ee65f77.tar.gz/sha512 delete mode 100644 deps/checksums/SparseArrays-a3116b95add064055c1f737abd98b5a5839c4147.tar.gz/md5 delete mode 100644 deps/checksums/SparseArrays-a3116b95add064055c1f737abd98b5a5839c4147.tar.gz/sha512 diff --git a/deps/checksums/SparseArrays-287c6c64a30bdeeee93e100a9f04d7169ee65f77.tar.gz/md5 b/deps/checksums/SparseArrays-287c6c64a30bdeeee93e100a9f04d7169ee65f77.tar.gz/md5 new file mode 100644 index 00000000000000..7630e369fd826b --- /dev/null +++ b/deps/checksums/SparseArrays-287c6c64a30bdeeee93e100a9f04d7169ee65f77.tar.gz/md5 @@ -0,0 +1 @@ +2beaa9b34b54042926911a81d57462b9 diff --git a/deps/checksums/SparseArrays-287c6c64a30bdeeee93e100a9f04d7169ee65f77.tar.gz/sha512 b/deps/checksums/SparseArrays-287c6c64a30bdeeee93e100a9f04d7169ee65f77.tar.gz/sha512 new file mode 100644 index 00000000000000..cbedb683b79cdb --- /dev/null +++ b/deps/checksums/SparseArrays-287c6c64a30bdeeee93e100a9f04d7169ee65f77.tar.gz/sha512 @@ -0,0 +1 @@ +85e33eb60ec45a674309fa230735b163ca58e12dc824f4d74df7628418fdd88f5d6a50bc18abffbbc0d5e12f931ff2463c39a5b27fbfcb1bd90b08b6b898ac36 diff --git a/deps/checksums/SparseArrays-a3116b95add064055c1f737abd98b5a5839c4147.tar.gz/md5 b/deps/checksums/SparseArrays-a3116b95add064055c1f737abd98b5a5839c4147.tar.gz/md5 deleted file mode 100644 index e5d8b0a66dd7b6..00000000000000 --- a/deps/checksums/SparseArrays-a3116b95add064055c1f737abd98b5a5839c4147.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -a278371a01cb77cf360812b8b31461e1 diff --git a/deps/checksums/SparseArrays-a3116b95add064055c1f737abd98b5a5839c4147.tar.gz/sha512 b/deps/checksums/SparseArrays-a3116b95add064055c1f737abd98b5a5839c4147.tar.gz/sha512 deleted file mode 100644 index d9f99472954486..00000000000000 --- a/deps/checksums/SparseArrays-a3116b95add064055c1f737abd98b5a5839c4147.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -2c55fa70940414e9c0fee86b5a23b9e33c6d839683307941e32bdb915d8845410e63d28b7e1b627fce43ebffec2b746d4c7d3eb5df3f2ef14634eedb1ba8c77d diff --git a/stdlib/SparseArrays.version b/stdlib/SparseArrays.version index 3c71453fe06999..573e4796a96a77 100644 --- a/stdlib/SparseArrays.version +++ b/stdlib/SparseArrays.version @@ -1,4 +1,4 @@ SPARSEARRAYS_BRANCH = main -SPARSEARRAYS_SHA1 = a3116b95add064055c1f737abd98b5a5839c4147 +SPARSEARRAYS_SHA1 = 287c6c64a30bdeeee93e100a9f04d7169ee65f77 SPARSEARRAYS_GIT_URL := https://github.com/JuliaSparse/SparseArrays.jl.git SPARSEARRAYS_TAR_URL = https://api.github.com/repos/JuliaSparse/SparseArrays.jl/tarball/$1 From 681486dca9fa5aa011c736c0b617fe43d3bd2348 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Mon, 16 Jan 2023 12:55:59 -0500 Subject: [PATCH 332/387] get_bool_env: remove default for default arg (#48292) --- base/env.jl | 4 ++-- test/env.jl | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/base/env.jl b/base/env.jl index 066bb9d755a4f1..10f57f3fb9dc7f 100644 --- a/base/env.jl +++ b/base/env.jl @@ -111,7 +111,7 @@ const get_bool_env_falsy = ( "0") """ - Base.get_bool_env(name::String, default::Bool = false)::Union{Bool,Nothing} + Base.get_bool_env(name::String, default::Bool)::Union{Bool,Nothing} Evaluate whether the value of environnment variable `name` is a truthy or falsy string, and return `nothing` if it is not recognized as either. If the variable is not set, or is set to "", @@ -121,7 +121,7 @@ Recognized values are the following, and their Capitalized and UPPERCASE forms: truthy: "t", "true", "y", "yes", "1" falsy: "f", "false", "n", "no", "0" """ -function get_bool_env(name::String, default::Bool = false) +function get_bool_env(name::String, default::Bool) haskey(ENV, name) || return default val = ENV[name] if isempty(val) diff --git a/test/env.jl b/test/env.jl index 00557f7facf99f..de5cf92d9edb96 100644 --- a/test/env.jl +++ b/test/env.jl @@ -127,7 +127,6 @@ end for v in ("t", "true", "y", "yes", "1") for _v in (v, uppercasefirst(v), uppercase(v)) ENV["testing_gbe"] = _v - @test Base.get_bool_env("testing_gbe") == true @test Base.get_bool_env("testing_gbe", false) == true @test Base.get_bool_env("testing_gbe", true) == true end @@ -137,7 +136,6 @@ end for v in ("f", "false", "n", "no", "0") for _v in (v, uppercasefirst(v), uppercase(v)) ENV["testing_gbe"] = _v - @test Base.get_bool_env("testing_gbe") == false @test Base.get_bool_env("testing_gbe", true) == false @test Base.get_bool_env("testing_gbe", false) == false end @@ -145,25 +143,26 @@ end end @testset "empty" begin ENV["testing_gbe"] = "" - @test Base.get_bool_env("testing_gbe") == false @test Base.get_bool_env("testing_gbe", true) == true @test Base.get_bool_env("testing_gbe", false) == false end @testset "undefined" begin delete!(ENV, "testing_gbe") @test !haskey(ENV, "testing_gbe") - @test Base.get_bool_env("testing_gbe") == false @test Base.get_bool_env("testing_gbe", true) == true @test Base.get_bool_env("testing_gbe", false) == false end @testset "unrecognized" begin for v in ("truw", "falls") ENV["testing_gbe"] = v - @test Base.get_bool_env("testing_gbe") === nothing @test Base.get_bool_env("testing_gbe", true) === nothing @test Base.get_bool_env("testing_gbe", false) === nothing end end + + # the "default" arg shouldn't have a default val, for clarity. + @test_throws MethodError Base.get_bool_env("testing_gbe") + delete!(ENV, "testing_gbe") @test !haskey(ENV, "testing_gbe") end From f6a5de584a37d6fded172e037cf2036ae4f7eaf4 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Mon, 16 Jan 2023 12:57:23 -0500 Subject: [PATCH 333/387] Add pkgimages news to 1.9 history (#48293) --- HISTORY.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/HISTORY.md b/HISTORY.md index 35fc061f8278d5..e5c92bee5a8a6b 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -25,6 +25,9 @@ Language changes Compiler/Runtime improvements ----------------------------- +* Time to first execution (TTFX, sometimes called time to first plot) is greatly reduced. Package precompilation now + saves native code into a "pkgimage", meaning that code generated during the precompilation process will not + require compilation after package load. Use of pkgimages can be disabled via `--pkgimages=no` ([#44527]) ([#47184]). * The known quadratic behavior of type inference is now fixed and inference uses less memory in general. Certain edge cases with auto-generated long functions (e.g. ModelingToolkit.jl with partial differential equations and large causal models) should see significant compile-time improvements ([#45276], [#45404]). From 687433b49535982980a5dffdab160f417cefcb29 Mon Sep 17 00:00:00 2001 From: Oscar Smith Date: Mon, 16 Jan 2023 12:58:42 -0500 Subject: [PATCH 334/387] minor tweak to pow accuracy and tests (#48233) * minor tweak to pow accuracy and tests --- base/special/exp.jl | 6 ++++-- test/math.jl | 17 +++++++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/base/special/exp.jl b/base/special/exp.jl index 42ad4bf7e073fd..9cca6f568305f2 100644 --- a/base/special/exp.jl +++ b/base/special/exp.jl @@ -238,8 +238,10 @@ end r = muladd(N_float, LogBo256L(base, T), r) k = N >> 8 jU, jL = table_unpack(N) - very_small = muladd(jU, expm1b_kernel(base, r), jL) - small_part = muladd(jU,xlo,very_small) + jU + kern = expm1b_kernel(base, r) + very_small = muladd(kern, jU*xlo, jL) + hi, lo = Base.canonicalize2(1.0, kern) + small_part = fma(jU, hi, muladd(jU, (lo+xlo), very_small)) if !(abs(x) <= SUBNORM_EXP(base, T)) x >= MAX_EXP(base, T) && return Inf x <= MIN_EXP(base, T) && return 0.0 diff --git a/test/math.jl b/test/math.jl index e36c1c2195b946..f9af521de61ca8 100644 --- a/test/math.jl +++ b/test/math.jl @@ -1352,9 +1352,9 @@ end @testset "pow" begin # tolerance by type for regular powers - POW_TOLS = Dict(Float16=>[.51, .51, 2.0, 1.5], - Float32=>[.51, .51, 2.0, 1.5], - Float64=>[1.0, 1.5, 2.0, 1.5]) + POW_TOLS = Dict(Float16=>[.51, .51, .51, 2.0, 1.5], + Float32=>[.51, .51, .51, 2.0, 1.5], + Float64=>[.55, 0.8, 1.5, 2.0, 1.5]) for T in (Float16, Float32, Float64) for x in (0.0, -0.0, 1.0, 10.0, 2.0, Inf, NaN, -Inf, -NaN) for y in (0.0, -0.0, 1.0, -3.0,-10.0 , Inf, NaN, -Inf, -NaN) @@ -1372,9 +1372,11 @@ end got, expected = x^y, widen(x)^y if isfinite(eps(T(expected))) if y == T(-2) # unfortunately x^-2 is less accurate for performance reasons. - @test abs(expected-got) <= POW_TOLS[T][3]*eps(T(expected)) || (x,y) - elseif y == T(3) # unfortunately x^3 is less accurate for performance reasons. @test abs(expected-got) <= POW_TOLS[T][4]*eps(T(expected)) || (x,y) + elseif y == T(3) # unfortunately x^3 is less accurate for performance reasons. + @test abs(expected-got) <= POW_TOLS[T][5]*eps(T(expected)) || (x,y) + elseif issubnormal(got) + @test abs(expected-got) <= POW_TOLS[T][2]*eps(T(expected)) || (x,y) else @test abs(expected-got) <= POW_TOLS[T][1]*eps(T(expected)) || (x,y) end @@ -1385,7 +1387,7 @@ end x=rand(T)*floatmin(T); y=rand(T)*3-T(1.2) got, expected = x^y, widen(x)^y if isfinite(eps(T(expected))) - @test abs(expected-got) <= POW_TOLS[T][2]*eps(T(expected)) || (x,y) + @test abs(expected-got) <= POW_TOLS[T][3]*eps(T(expected)) || (x,y) end end # test (-x)^y for y larger than typemax(Int) @@ -1396,6 +1398,9 @@ end # test for large negative exponent where error compensation matters @test 0.9999999955206014^-1.0e8 == 1.565084574870928 @test 3e18^20 == Inf + # two cases where we have observed > 1 ULP in the past + @test 0.0013653274095082324^-97.60372292227069 == 4.088393948750035e279 + @test 8.758520413376658e-5^70.55863059215994 == 5.052076767078296e-287 end # Test that sqrt behaves correctly and doesn't exhibit fp80 double rounding. From 388864a036c4343d4825873698420a8fcfe34af6 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Mon, 16 Jan 2023 18:24:30 -0500 Subject: [PATCH 335/387] effects: Allow consistency of :new with slightly imprecise type (#48267) For our consistency check, all we need to prove is that the type we're constructing is not mutable and does not contain uinitialized data. This is possible as long as we know what the ultimate DataType is going to be, but we do not need all of the parameters. --- base/compiler/abstractinterpretation.jl | 87 +++++++++++++------------ base/compiler/optimize.jl | 4 +- base/reflection.jl | 39 ++++++----- test/compiler/effects.jl | 8 +++ 4 files changed, 78 insertions(+), 60 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 38367269391738..abb497a39b295f 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2291,33 +2291,14 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp t = rt elseif ehead === :new t, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv)) - nothrow = true - if isconcretedispatch(t) - ismutable = ismutabletype(t) - fcount = fieldcount(t) + ut = unwrap_unionall(t) + consistent = ALWAYS_FALSE + nothrow = false + if isa(ut, DataType) && !isabstracttype(ut) + ismutable = ismutabletype(ut) + fcount = datatype_fieldcount(ut) nargs = length(e.args) - 1 - @assert fcount ≥ nargs "malformed :new expression" # syntactically enforced by the front-end - ats = Vector{Any}(undef, nargs) - local anyrefine = false - local allconst = true - for i = 1:nargs - at = widenslotwrapper(abstract_eval_value(interp, e.args[i+1], vtypes, sv)) - ft = fieldtype(t, i) - nothrow && (nothrow = at ⊑ᵢ ft) - at = tmeet(𝕃ᵢ, at, ft) - at === Bottom && @goto always_throw - if ismutable && !isconst(t, i) - ats[i] = ft # can't constrain this field (as it may be modified later) - continue - end - allconst &= isa(at, Const) - if !anyrefine - anyrefine = has_nontrivial_extended_info(𝕃ᵢ, at) || # extended lattice information - ⋤(𝕃ᵢ, at, ft) # just a type-level information, but more precise than the declared type - end - ats[i] = at - end - if fcount > nargs && any(i::Int -> !is_undefref_fieldtype(fieldtype(t, i)), (nargs+1):fcount) + if fcount === nothing || (fcount > nargs && any(i::Int -> !is_undefref_fieldtype(fieldtype(t, i)), (nargs+1):fcount)) # allocation with undefined field leads to undefined behavior and should taint `:consistent`-cy consistent = ALWAYS_FALSE elseif ismutable @@ -2327,25 +2308,47 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp else consistent = ALWAYS_TRUE end - # For now, don't allow: - # - Const/PartialStruct of mutables (but still allow PartialStruct of mutables - # with `const` fields if anything refined) - # - partially initialized Const/PartialStruct - if fcount == nargs - if consistent === ALWAYS_TRUE && allconst - argvals = Vector{Any}(undef, nargs) - for j in 1:nargs - argvals[j] = (ats[j]::Const).val + if isconcretedispatch(t) + nothrow = true + @assert fcount !== nothing && fcount ≥ nargs "malformed :new expression" # syntactically enforced by the front-end + ats = Vector{Any}(undef, nargs) + local anyrefine = false + local allconst = true + for i = 1:nargs + at = widenslotwrapper(abstract_eval_value(interp, e.args[i+1], vtypes, sv)) + ft = fieldtype(t, i) + nothrow && (nothrow = at ⊑ᵢ ft) + at = tmeet(𝕃ᵢ, at, ft) + at === Bottom && @goto always_throw + if ismutable && !isconst(t, i) + ats[i] = ft # can't constrain this field (as it may be modified later) + continue + end + allconst &= isa(at, Const) + if !anyrefine + anyrefine = has_nontrivial_extended_info(𝕃ᵢ, at) || # extended lattice information + ⋤(𝕃ᵢ, at, ft) # just a type-level information, but more precise than the declared type end - t = Const(ccall(:jl_new_structv, Any, (Any, Ptr{Cvoid}, UInt32), t, argvals, nargs)) - elseif anyrefine - t = PartialStruct(t, ats) + ats[i] = at end + # For now, don't allow: + # - Const/PartialStruct of mutables (but still allow PartialStruct of mutables + # with `const` fields if anything refined) + # - partially initialized Const/PartialStruct + if fcount == nargs + if consistent === ALWAYS_TRUE && allconst + argvals = Vector{Any}(undef, nargs) + for j in 1:nargs + argvals[j] = (ats[j]::Const).val + end + t = Const(ccall(:jl_new_structv, Any, (Any, Ptr{Cvoid}, UInt32), t, argvals, nargs)) + elseif anyrefine + t = PartialStruct(t, ats) + end + end + else + t = refine_partial_type(t) end - else - consistent = ALWAYS_FALSE - nothrow = false - t = refine_partial_type(t) end effects = Effects(EFFECTS_TOTAL; consistent, nothrow) elseif ehead === :splatnew diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index c295c70e5ee44f..e5202e299d0708 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -311,7 +311,9 @@ function stmt_effect_flags(𝕃ₒ::AbstractLattice, @nospecialize(stmt), @nospe isconcretedispatch(typ) || return (false, false, false) end typ = typ::DataType - fieldcount(typ) >= length(args) - 1 || return (false, false, false) + fcount = datatype_fieldcount(typ) + fcount === nothing && return (false, false, false) + fcount >= length(args) - 1 || return (false, false, false) for fld_idx in 1:(length(args) - 1) eT = argextype(args[fld_idx + 1], src) fT = fieldtype(typ, fld_idx) diff --git a/base/reflection.jl b/base/reflection.jl index c836201d40a422..9bb224c53b4361 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -838,6 +838,25 @@ function argument_datatype(@nospecialize t) return ccall(:jl_argument_datatype, Any, (Any,), t)::Union{Nothing,DataType} end +function datatype_fieldcount(t::DataType) + if t.name === _NAMEDTUPLE_NAME + names, types = t.parameters[1], t.parameters[2] + if names isa Tuple + return length(names) + end + if types isa DataType && types <: Tuple + return fieldcount(types) + end + return nothing + elseif isabstracttype(t) || (t.name === Tuple.name && isvatuple(t)) + return nothing + end + if isdefined(t, :types) + return length(t.types) + end + return length(t.name.names) +end + """ fieldcount(t::Type) @@ -857,25 +876,11 @@ function fieldcount(@nospecialize t) if !(t isa DataType) throw(TypeError(:fieldcount, DataType, t)) end - if t.name === _NAMEDTUPLE_NAME - names, types = t.parameters[1], t.parameters[2] - if names isa Tuple - return length(names) - end - if types isa DataType && types <: Tuple - return fieldcount(types) - end - abstr = true - else - abstr = isabstracttype(t) || (t.name === Tuple.name && isvatuple(t)) - end - if abstr + fcount = datatype_fieldcount(t) + if fcount === nothing throw(ArgumentError("type does not have a definite number of fields")) end - if isdefined(t, :types) - return length(t.types) - end - return length(t.name.names) + return fcount end """ diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl index fc5fb6d3b9cecc..25b6db0f343105 100644 --- a/test/compiler/effects.jl +++ b/test/compiler/effects.jl @@ -731,3 +731,11 @@ end |> Core.Compiler.is_total @test Base.infer_effects(Tuple{Int64}) do i @inbounds (1,2,3)[i] end |> !Core.Compiler.is_consistent + +# Test that :new of non-concrete, but otherwise known type +# does not taint consistency. +@eval struct ImmutRef{T} + x::T + ImmutRef(x) = $(Expr(:new, :(ImmutRef{typeof(x)}), :x)) +end +@test Core.Compiler.is_foldable(Base.infer_effects(ImmutRef, Tuple{Any})) From 36007b7816cd9c6d955cf8b9a5c87e123b3307af Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Mon, 16 Jan 2023 20:00:37 -0500 Subject: [PATCH 336/387] Profile: print profile peek to stderr (#48291) --- stdlib/Profile/src/Profile.jl | 8 ++++---- stdlib/Profile/test/runtests.jl | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index f016e19cd3e05a..ba2d7390a214ca 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -39,9 +39,9 @@ function profile_printing_listener() wait(PROFILE_PRINT_COND[]) peek_report[]() if get(ENV, "JULIA_PROFILE_PEEK_HEAP_SNAPSHOT", nothing) === "1" - println("Saving heap snapshot...") + println(stderr, "Saving heap snapshot...") fname = take_heap_snapshot() - println("Heap snapshot saved to `$(fname)`") + println(stderr, "Heap snapshot saved to `$(fname)`") end end catch ex @@ -54,9 +54,9 @@ end # An internal function called to show the report after an information request (SIGINFO or SIGUSR1). function _peek_report() iob = IOBuffer() - ioc = IOContext(IOContext(iob, stdout), :displaysize=>displaysize(stdout)) + ioc = IOContext(IOContext(iob, stderr), :displaysize=>displaysize(stderr)) print(ioc, groupby = [:thread, :task]) - Base.print(stdout, String(take!(iob))) + Base.print(stderr, String(take!(iob))) end # This is a ref so that it can be overridden by other profile info consumers. const peek_report = Ref{Function}(_peek_report) diff --git a/stdlib/Profile/test/runtests.jl b/stdlib/Profile/test/runtests.jl index 1246dcf25a82cf..2a39640d215ed3 100644 --- a/stdlib/Profile/test/runtests.jl +++ b/stdlib/Profile/test/runtests.jl @@ -199,14 +199,14 @@ if Sys.isbsd() || Sys.islinux() let cmd = Base.julia_cmd() script = """ x = rand(1000, 1000) - println("started") + println(stderr, "started") while true x * x yield() end """ iob = Base.BufferStream() - p = run(pipeline(`$cmd -e $script`, stderr = devnull, stdout = iob), wait = false) + p = run(pipeline(`$cmd -e $script`, stderr = iob, stdout = devnull), wait = false) t = Timer(120) do t # should be under 10 seconds, so give it 2 minutes then report failure println("KILLING BY PROFILE TEST WATCHDOG\n") From 958293763dd8b79758c2237d01143d39827acb70 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Tue, 17 Jan 2023 02:24:51 -0500 Subject: [PATCH 337/387] Remove @inbounds in tuple iteration (#48297) * Remove @inbounds in tuple iteration LLVM can prove this inbounds and the annotation weakens the inferable effects for tuple iteration, which has a surprisingly large inference performance and precision impact. Unfortunately, my previous changes to :inbounds tainting weren't quite strong enough yet, because `getfield` was still tainting consistency on unknown boundscheck arguments. To fix that, we pass through the fargs into the fetfield effects to check if we're getting a literal `:boundscheck`, in which case the `:noinbounds` consistency-tainting logic I added in #48246 is sufficient to not require additional consistency tainting. Also add a test for both effects and codegen to make sure this doens't regress. * Int64 -> Int * fixup typo --- base/compiler/abstractinterpretation.jl | 5 +- base/compiler/optimize.jl | 4 +- base/compiler/tfuncs.jl | 110 ++++++++++++++---------- base/reflection.jl | 7 +- base/tuple.jl | 2 +- test/compiler/codegen.jl | 3 + test/compiler/effects.jl | 9 +- test/tuple.jl | 3 + 8 files changed, 85 insertions(+), 58 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index abb497a39b295f..0b2d641c8eab85 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1975,7 +1975,7 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), return abstract_finalizer(interp, argtypes, sv) end rt = abstract_call_builtin(interp, f, arginfo, sv, max_methods) - effects = builtin_effects(𝕃ᵢ, f, argtypes[2:end], rt) + effects = builtin_effects(𝕃ᵢ, f, arginfo, rt) return CallMeta(rt, effects, NoCallInfo()) elseif isa(f, Core.OpaqueClosure) # calling an OpaqueClosure about which we have no information returns no information @@ -1994,7 +1994,8 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), ub_var = argtypes[3] end pT = typevar_tfunc(𝕃ᵢ, n, lb_var, ub_var) - effects = builtin_effects(𝕃ᵢ, Core._typevar, Any[n, lb_var, ub_var], pT) + effects = builtin_effects(𝕃ᵢ, Core._typevar, ArgInfo(nothing, + Any[Const(Core._typevar), n, lb_var, ub_var]), pT) return CallMeta(pT, effects, NoCallInfo()) elseif f === UnionAll return abstract_call_unionall(interp, argtypes) diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index e5202e299d0708..2c26848ac1ca1b 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -289,8 +289,8 @@ function stmt_effect_flags(𝕃ₒ::AbstractLattice, @nospecialize(stmt), @nospe isa(f, Builtin) || return (false, false, false) # Needs to be handled in inlining to look at the callee effects f === Core._apply_iterate && return (false, false, false) - argtypes = Any[argextype(args[arg], src) for arg in 2:length(args)] - effects = builtin_effects(𝕃ₒ, f, argtypes, rt) + argtypes = Any[argextype(args[arg], src) for arg in 1:length(args)] + effects = builtin_effects(𝕃ₒ, f, ArgInfo(args, argtypes), rt) consistent = is_consistent(effects) effect_free = is_effect_free(effects) nothrow = is_nothrow(effects) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 72e02d262ea944..1ff427a480a7d7 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -911,41 +911,47 @@ function try_compute_fieldidx(typ::DataType, @nospecialize(field)) return field end -function getfield_boundscheck(argtypes::Vector{Any}) # ::Union{Bool, Nothing} - if length(argtypes) == 2 - return true - elseif length(argtypes) == 3 - boundscheck = argtypes[3] - isvarargtype(boundscheck) && return nothing - if widenconst(boundscheck) === Symbol - return true - end +function getfield_boundscheck((; fargs, argtypes)::ArgInfo) # Symbol + farg = nothing + if length(argtypes) == 3 + return :on elseif length(argtypes) == 4 + fargs !== nothing && (farg = fargs[4]) boundscheck = argtypes[4] + isvarargtype(boundscheck) && return :unknown + if widenconst(boundscheck) === Symbol + return :on + end + elseif length(argtypes) == 5 + fargs !== nothing && (farg = fargs[5]) + boundscheck = argtypes[5] else - return nothing + return :unknown end - isvarargtype(boundscheck) && return nothing - widenconst(boundscheck) === Bool || return nothing + isvarargtype(boundscheck) && return :unknown boundscheck = widenconditional(boundscheck) - if isa(boundscheck, Const) - return boundscheck.val::Bool - else - return nothing + if widenconst(boundscheck) === Bool + if isa(boundscheck, Const) + return boundscheck.val::Bool ? :on : :off + elseif farg !== nothing && isexpr(farg, :boundscheck) + return :boundscheck + end end + return :unknown end -function getfield_nothrow(argtypes::Vector{Any}, boundscheck::Union{Bool,Nothing}=getfield_boundscheck(argtypes)) - boundscheck === nothing && return false +function getfield_nothrow(arginfo::ArgInfo, boundscheck::Symbol=getfield_boundscheck(arginfo)) + (;argtypes) = arginfo + boundscheck === :unknown && return false ordering = Const(:not_atomic) - if length(argtypes) == 3 - isvarargtype(argtypes[3]) && return false - if widenconst(argtypes[3]) !== Bool - ordering = argtypes[3] - end - elseif length(argtypes) == 4 - ordering = argtypes[4] - elseif length(argtypes) != 2 + if length(argtypes) == 4 + isvarargtype(argtypes[4]) && return false + if widenconst(argtypes[4]) !== Bool + ordering = argtypes[4] + end + elseif length(argtypes) == 5 + ordering = argtypes[5] + elseif length(argtypes) != 3 return false end isvarargtype(ordering) && return false @@ -955,7 +961,7 @@ function getfield_nothrow(argtypes::Vector{Any}, boundscheck::Union{Bool,Nothing if ordering !== :not_atomic # TODO: this is assuming not atomic return false end - return getfield_nothrow(argtypes[1], argtypes[2], !(boundscheck === false)) + return getfield_nothrow(argtypes[2], argtypes[3], !(boundscheck === :off)) else return false end @@ -1037,7 +1043,9 @@ end end return getfield_tfunc(𝕃, s00, name) end -@nospecs getfield_tfunc(𝕃::AbstractLattice, s00, name) = _getfield_tfunc(𝕃, s00, name, false) +@nospecs function getfield_tfunc(𝕃::AbstractLattice, s00, name) + _getfield_tfunc(𝕃, s00, name, false) +end function _getfield_fieldindex(s::DataType, name::Const) nv = name.val @@ -2021,7 +2029,7 @@ end elseif f === invoke return false elseif f === getfield - return getfield_nothrow(argtypes) + return getfield_nothrow(ArgInfo(nothing, Any[Const(f), argtypes...])) elseif f === setfield! if na == 3 return setfield!_nothrow(𝕃, argtypes[1], argtypes[2], argtypes[3]) @@ -2179,10 +2187,11 @@ function isdefined_effects(𝕃::AbstractLattice, argtypes::Vector{Any}) return Effects(EFFECTS_TOTAL; consistent, nothrow) end -function getfield_effects(argtypes::Vector{Any}, @nospecialize(rt)) +function getfield_effects(arginfo::ArgInfo, @nospecialize(rt)) + (;argtypes) = arginfo # consistent if the argtype is immutable - isempty(argtypes) && return EFFECTS_THROWS - obj = argtypes[1] + length(argtypes) < 3 && return EFFECTS_THROWS + obj = argtypes[2] isvarargtype(obj) && return Effects(EFFECTS_THROWS; consistent=ALWAYS_FALSE) consistent = (is_immutable_argtype(obj) || is_mutation_free_argtype(obj)) ? ALWAYS_TRUE : CONSISTENT_IF_INACCESSIBLEMEMONLY @@ -2191,20 +2200,26 @@ function getfield_effects(argtypes::Vector{Any}, @nospecialize(rt)) # throws `UndefRefError` so doesn't need to taint it # NOTE `getfield_notundefined` conservatively checks if this field is never initialized # with undefined value so that we don't taint `:consistent`-cy too aggressively here - if !(length(argtypes) ≥ 2 && getfield_notundefined(obj, argtypes[2])) + if !(length(argtypes) ≥ 3 && getfield_notundefined(obj, argtypes[3])) consistent = ALWAYS_FALSE end - nothrow = getfield_nothrow(argtypes, true) - if !nothrow && getfield_boundscheck(argtypes) !== true - # If we cannot independently prove inboundsness, taint consistency. - # The inbounds-ness assertion requires dynamic reachability, while - # :consistent needs to be true for all input values. - # N.B. We do not taint for `--check-bounds=no` here that happens in - # InferenceState. - consistent = ALWAYS_FALSE + nothrow = getfield_nothrow(arginfo, :on) + if !nothrow + bcheck = getfield_boundscheck(arginfo) + if !(bcheck === :on || bcheck === :boundscheck) + # If we cannot independently prove inboundsness, taint consistency. + # The inbounds-ness assertion requires dynamic reachability, while + # :consistent needs to be true for all input values. + # However, as a special exception, we do allow literal `:boundscheck`. + # `:consistent`-cy will be tainted in any caller using `@inbounds` based + # on the `:noinbounds` effect. + # N.B. We do not taint for `--check-bounds=no` here. That is handled + # in concrete evaluation. + consistent = ALWAYS_FALSE + end end if hasintersect(widenconst(obj), Module) - inaccessiblememonly = getglobal_effects(argtypes, rt).inaccessiblememonly + inaccessiblememonly = getglobal_effects(argtypes[2:end], rt).inaccessiblememonly elseif is_mutation_free_argtype(obj) inaccessiblememonly = ALWAYS_TRUE else @@ -2233,17 +2248,20 @@ function getglobal_effects(argtypes::Vector{Any}, @nospecialize(rt)) return Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly) end -function builtin_effects(𝕃::AbstractLattice, @nospecialize(f::Builtin), argtypes::Vector{Any}, @nospecialize(rt)) +function builtin_effects(𝕃::AbstractLattice, @nospecialize(f::Builtin), arginfo::ArgInfo, @nospecialize(rt)) if isa(f, IntrinsicFunction) - return intrinsic_effects(f, argtypes) + return intrinsic_effects(f, arginfo.argtypes[2:end]) end @assert !contains_is(_SPECIAL_BUILTINS, f) + if f === getfield + return getfield_effects(arginfo, rt) + end + argtypes = arginfo.argtypes[2:end] + if f === isdefined return isdefined_effects(𝕃, argtypes) - elseif f === getfield - return getfield_effects(argtypes, rt) elseif f === getglobal return getglobal_effects(argtypes, rt) elseif f === Core.get_binding_type diff --git a/base/reflection.jl b/base/reflection.jl index 9bb224c53b4361..78a46c86937700 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1493,9 +1493,10 @@ function infer_effects(@nospecialize(f), @nospecialize(types=default_tt(f)); ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions") if isa(f, Core.Builtin) types = to_tuple_type(types) - argtypes = Any[types.parameters...] - rt = Core.Compiler.builtin_tfunction(interp, f, argtypes, nothing) - return Core.Compiler.builtin_effects(Core.Compiler.typeinf_lattice(interp), f, argtypes, rt) + argtypes = Any[Core.Compiler.Const(f), types.parameters...] + rt = Core.Compiler.builtin_tfunction(interp, f, argtypes[2:end], nothing) + return Core.Compiler.builtin_effects(Core.Compiler.typeinf_lattice(interp), f, + Core.Compiler.ArgInfo(nothing, argtypes), rt) end tt = signature_type(f, types) result = Core.Compiler.findall(tt, Core.Compiler.method_table(interp)) diff --git a/base/tuple.jl b/base/tuple.jl index e0adbfe6d20cc2..f5e85137bc6f01 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -67,7 +67,7 @@ end function iterate(@nospecialize(t::Tuple), i::Int=1) @inline - return (1 <= i <= length(t)) ? (@inbounds t[i], i + 1) : nothing + return (1 <= i <= length(t)) ? (t[i], i + 1) : nothing end keys(@nospecialize t::Tuple) = OneTo(length(t)) diff --git a/test/compiler/codegen.jl b/test/compiler/codegen.jl index 0d87f8cf8b56ba..6f8c31d6c4015f 100644 --- a/test/compiler/codegen.jl +++ b/test/compiler/codegen.jl @@ -794,3 +794,6 @@ end f48085(@nospecialize x...) = length(x) @test Core.Compiler.get_compileable_sig(which(f48085, (Vararg{Any},)), Tuple{typeof(f48085), Vararg{Int}}, Core.svec()) === nothing @test Core.Compiler.get_compileable_sig(which(f48085, (Vararg{Any},)), Tuple{typeof(f48085), Int, Vararg{Int}}, Core.svec()) === Tuple{typeof(f48085), Any, Vararg{Any}} + +# Make sure that the bounds check is elided in tuple iteration +@test !occursin("call void @", get_llvm(iterate, Tuple{NTuple{4, Float64}, Int})) diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl index 25b6db0f343105..42e370c922ef68 100644 --- a/test/compiler/effects.jl +++ b/test/compiler/effects.jl @@ -462,9 +462,9 @@ end |> Core.Compiler.is_consistent end |> Core.Compiler.is_effect_free # `getfield_effects` handles access to union object nicely -@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Any[Some{String}, Core.Const(:value)], String)) -@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Any[Some{Symbol}, Core.Const(:value)], Symbol)) -@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Any[Union{Some{Symbol},Some{String}}, Core.Const(:value)], Union{Symbol,String})) +@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Core.Compiler.ArgInfo(nothing, Any[Core.Const(getfield), Some{String}, Core.Const(:value)]), String)) +@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Core.Compiler.ArgInfo(nothing, Any[Core.Const(getfield), Some{Symbol}, Core.Const(:value)]), Symbol)) +@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Core.Compiler.ArgInfo(nothing, Any[Core.Const(getfield), Union{Some{Symbol},Some{String}}, Core.Const(:value)]), Union{Symbol,String})) @test Base.infer_effects((Bool,)) do c obj = c ? Some{String}("foo") : Some{Symbol}(:bar) return getfield(obj, :value) @@ -688,7 +688,8 @@ end # @testset "effects analysis on array construction" begin end # @testset "effects analysis on array ops" begin # Test that builtin_effects handles vararg correctly -@test !Core.Compiler.is_nothrow(Core.Compiler.builtin_effects(Core.Compiler.fallback_lattice, Core.isdefined, Any[String, Vararg{Any}], Bool)) +@test !Core.Compiler.is_nothrow(Core.Compiler.builtin_effects(Core.Compiler.fallback_lattice, Core.isdefined, + Core.Compiler.ArgInfo(nothing, Any[Core.Compiler.Const(Core.isdefined), String, Vararg{Any}]), Bool)) # Test that :new can be eliminated even if an sparam is unknown struct SparamUnused{T} diff --git a/test/tuple.jl b/test/tuple.jl index 945590c2dbf4b6..86f41b2fbc660e 100644 --- a/test/tuple.jl +++ b/test/tuple.jl @@ -779,3 +779,6 @@ namedtup = (;a=1, b=2, c=3) NamedTuple{(:a, :b), Tuple{Int, Int}}, NamedTuple{(:c,), Tuple{Int}}, } + +# Make sure that tuple iteration is foldable +@test Core.Compiler.is_foldable(Base.infer_effects(iterate, Tuple{NTuple{4, Float64}, Int})) From 5f40f156c156e09315f6d49e03c1d180ed801e80 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Tue, 17 Jan 2023 02:25:56 -0500 Subject: [PATCH 338/387] Don't bail out of inference early if effects could still be refined (#48263) We have an early out in inference that bails if the inferred return type of the method being called is `Any`. This makes sense in the absence of effects, because once the rt has hit `Any`, there is nothing new we can learn by looking at any subsequent calls. However, in the presence of effects, we likely want to keep going if we can prove all methods of the callsite `:foldable` as being `:foldable` can save significant inference time down the line if it enables concrete evaluation of the containing function. --- base/compiler/abstractinterpretation.jl | 4 +++- base/compiler/inferencestate.jl | 4 ++-- test/compiler/inference.jl | 7 +++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 0b2d641c8eab85..a38cf9f8778bb0 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -194,7 +194,8 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), conditionals[2][i] = tmerge(conditionals[2][i], cnd.elsetype) end end - if bail_out_call(interp, rettype, sv) + if bail_out_call(interp, rettype, sv, effects) + add_remark!(interp, sv, "One of the matched returned maximally imprecise information. Bailing on call.") break end end @@ -838,6 +839,7 @@ function concrete_eval_eligible(interp::AbstractInterpreter, elseif !result.effects.noinbounds && stmt_taints_inbounds_consistency(sv) # If the current statement is @inbounds or we propagate inbounds, the call's consistency # is tainted and not consteval eligible. + add_remark!(interp, sv, "[constprop] Concrete evel disabled for inbounds") return nothing end isoverlayed(method_table(interp)) && !is_nonoverlayed(result.effects) && return nothing diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index 0ebcd94409aa2b..0ae7989c82c761 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -224,8 +224,8 @@ add_remark!(::AbstractInterpreter, sv::Union{InferenceState, IRCode}, remark) = function bail_out_toplevel_call(::AbstractInterpreter, @nospecialize(callsig), sv::Union{InferenceState, IRCode}) return isa(sv, InferenceState) && sv.restrict_abstract_call_sites && !isdispatchtuple(callsig) end -function bail_out_call(::AbstractInterpreter, @nospecialize(rt), sv::Union{InferenceState, IRCode}) - return rt === Any +function bail_out_call(::AbstractInterpreter, @nospecialize(rt), sv::Union{InferenceState, IRCode}, effects::Effects) + return rt === Any && !is_foldable(effects) end function bail_out_apply(::AbstractInterpreter, @nospecialize(rt), sv::Union{InferenceState, IRCode}) return rt === Any diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index aced289e15cbef..feaf3770a6848d 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -4697,3 +4697,10 @@ Base.@constprop :aggressive type_level_recurse1(x...) = x[1] == 2 ? 1 : (length( Base.@constprop :aggressive type_level_recurse2(x...) = type_level_recurse1(x...) type_level_recurse_entry() = Val{type_level_recurse1(1)}() @test Base.return_types(type_level_recurse_entry, ()) |> only == Val{1} + +# Test that inference doesn't give up if it can potentially refine effects, +# even if the return type is Any. +f_no_bail_effects_any(x::Any) = x +f_no_bail_effects_any(x::NamedTuple{(:x,), Tuple{Any}}) = getfield(x, 1) +g_no_bail_effects_any(x::Any) = f_no_bail_effects_any(x) +@test Core.Compiler.is_total(Base.infer_effects(g_no_bail_effects_any, Tuple{Any})) From a6694d4edf7c331e7c4f8e7a20421ced272e26b5 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Tue, 17 Jan 2023 17:11:53 +0100 Subject: [PATCH 339/387] improve docs for IPython mode (#48314) --- stdlib/REPL/docs/src/index.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/stdlib/REPL/docs/src/index.md b/stdlib/REPL/docs/src/index.md index d696c069fbdb74..a23b8f224a6cb3 100644 --- a/stdlib/REPL/docs/src/index.md +++ b/stdlib/REPL/docs/src/index.md @@ -629,7 +629,20 @@ atreplinit() do repl end ``` -to your `startup.jl` file. +to your `startup.jl` file. In `IPython` mode the variable `Out[n]` (where `n` is an integer) can be used to refer to earlier results: + +```julia-repl +In [1]: 5 + 3 +Out[1]: 8 + +In [2]: Out[1] + 5 +Out[2]: 13 + +In [3]: Out +Out[3]: Dict{Int64, Any} with 2 entries: + 2 => 13 + 1 => 8 +``` ## TerminalMenus From ac080c54915477e46380690ffc66c4af487c5ec2 Mon Sep 17 00:00:00 2001 From: Jeremie Knuesel Date: Tue, 17 Jan 2023 17:12:34 +0100 Subject: [PATCH 340/387] Fix Splat->splat in HISTORY.md (#48305) --- HISTORY.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index e5c92bee5a8a6b..0db48d5f960e37 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -68,8 +68,6 @@ New library functions --------------------- * New function `Iterators.flatmap` ([#44792]). -* New helper `Splat(f)` which acts like `x -> f(x...)`, with pretty printing for - inspecting which function `f` was originally wrapped ([#42717]). * New `pkgversion(m::Module)` function to get the version of the package that loaded a given module, similar to `pkgdir(m::Module)` ([#45607]). * New function `stack(x)` which generalises `reduce(hcat, x::Vector{<:Vector})` to any dimensionality, @@ -98,6 +96,8 @@ Standard library changes * `@kwdef` is now exported and added to the public API ([#46273]). * An issue with order of operations in `fld1` is now fixed ([#28973]). * Sorting is now always stable by default, as `QuickSort` was stabilized ([#45222]). +* `Base.splat` is now exported. The return value is now a `Base.Splat` instead + of an anonymous function, which allows for pretty printing ([#42717]). #### Package Manager @@ -181,7 +181,6 @@ Standard library changes Deprecated or removed --------------------- -* Unexported `splat` is deprecated in favor of exported `Splat`, which has pretty printing of the wrapped function ([#42717]). External dependencies --------------------- From 1b9f640c160f4f364063a6b2b2e798a93c123abd Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Tue, 17 Jan 2023 17:32:08 -0500 Subject: [PATCH 341/387] namedtuple: Use correct return type in merge/diff (#48290) In a lapse of memory, I had assumed that NamedTuple was covariant like Tuple, but since this is not the case, we do actually need to pass the types into the constructor. However, the main constructor for NamedTuple has an extra `convert` call to the declared tuple type. This call is problematic for effects, because the type is unknown. For the merge/diff case, we are guaranteed that the convert is a no-op, but the compiler's analysis is not strong enough to prove this. Work around that by introducing an `_NamedTuple` constructor that bypasses the unnecessary convert to make sure that the compiler can prove sufficiently strong effects. --- base/namedtuple.jl | 16 +++++++++++----- test/namedtuple.jl | 10 +++++++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 7549014abe3d10..24a32e6501720a 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -129,6 +129,12 @@ function NamedTuple{names, T}(nt::NamedTuple) where {names, T <: Tuple} end end +# Like NamedTuple{names, T} as a constructor, but omits the additional +# `convert` call, when the types are known to match the fields +@eval function _new_NamedTuple(T::Type{NamedTuple{NTN, NTT}} where {NTN, NTT}, args::Tuple) + $(Expr(:splatnew, :T, :args)) +end + function NamedTuple{names}(nt::NamedTuple) where {names} if @generated idx = Int[ fieldindex(nt, names[n]) for n in 1:length(names) ] @@ -137,7 +143,7 @@ function NamedTuple{names}(nt::NamedTuple) where {names} else length_names = length(names::Tuple) types = Tuple{(fieldtype(typeof(nt), names[n]) for n in 1:length_names)...} - NamedTuple{names, types}(map(Fix1(getfield, nt), names)) + _new_NamedTuple(NamedTuple{names, types}, map(Fix1(getfield, nt), names)) end end @@ -277,7 +283,7 @@ end n = names[i] A[i] = getfield(sym_in(n, bn) ? b : a, n) end - NamedTuple{names}((A...,))::NamedTuple{names, types} + _new_NamedTuple(NamedTuple{names, types}, (A...,)) end """ @@ -310,7 +316,7 @@ function merge(a::NamedTuple{an}, b::NamedTuple{bn}) where {an, bn} names = merge_names(an, bn) types = merge_types(names, a, b) vals = Any[ :(getfield($(sym_in(names[n], bn) ? :b : :a), $(QuoteNode(names[n])))) for n in 1:length(names) ] - :( NamedTuple{$names,$types}(($(vals...),)) ) + :( _new_NamedTuple(NamedTuple{$names,$types}, ($(vals...),)) ) else merge_fallback(a, b, an, bn) end @@ -390,7 +396,7 @@ end n = names[i] A[i] = getfield(a, n) end - NamedTuple{names}((A...,))::NamedTuple{names, types} + _new_NamedTuple(NamedTuple{names, types}, (A...,)) end """ @@ -406,7 +412,7 @@ function structdiff(a::NamedTuple{an}, b::Union{NamedTuple{bn}, Type{NamedTuple{ idx = Int[ fieldindex(a, names[n]) for n in 1:length(names) ] types = Tuple{Any[ fieldtype(a, idx[n]) for n in 1:length(idx) ]...} vals = Any[ :(getfield(a, $(idx[n]))) for n in 1:length(idx) ] - return :( NamedTuple{$names,$types}(($(vals...),)) ) + return :( _new_NamedTuple(NamedTuple{$names,$types}, ($(vals...),)) ) else return diff_fallback(a, an, bn) end diff --git a/test/namedtuple.jl b/test/namedtuple.jl index 82a2bc7bf833dd..6333cfef3a1709 100644 --- a/test/namedtuple.jl +++ b/test/namedtuple.jl @@ -356,6 +356,14 @@ end # Test effect/inference for merge/diff of unknown NamedTuples for f in (Base.merge, Base.structdiff) - @test Core.Compiler.is_foldable(Base.infer_effects(f, Tuple{NamedTuple, NamedTuple})) + let eff = Base.infer_effects(f, Tuple{NamedTuple, NamedTuple}) + @test Core.Compiler.is_foldable(eff) && eff.nonoverlayed + end @test Core.Compiler.return_type(f, Tuple{NamedTuple, NamedTuple}) == NamedTuple end + +# Test that merge/diff preserves nt field types +let a = Base.NamedTuple{(:a, :b), Tuple{Any, Any}}((1, 2)), b = Base.NamedTuple{(:b,), Tuple{Float64}}(3) + @test typeof(Base.merge(a, b)) == Base.NamedTuple{(:a, :b), Tuple{Any, Float64}} + @test typeof(Base.structdiff(a, b)) == Base.NamedTuple{(:a,), Tuple{Any}} +end From 5b80e4885def5c2f3360c449be83e19fb763db14 Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Wed, 18 Jan 2023 00:19:56 +0100 Subject: [PATCH 342/387] Fix typo in `isambiguous` (#48312) * Fix typo in `isambiguous` * add test --- base/reflection.jl | 2 +- test/ambiguous.jl | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/base/reflection.jl b/base/reflection.jl index 78a46c86937700..c013f762311d52 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1858,7 +1858,7 @@ function isambiguous(m1::Method, m2::Method; ambiguous_bottom::Bool=false) m = match.method m === minmax && continue if !morespecific(minmax.sig, m.sig) - if match.full_covers || !morespecific(m.sig, minmax.sig) + if match.fully_covers || !morespecific(m.sig, minmax.sig) return true end end diff --git a/test/ambiguous.jl b/test/ambiguous.jl index bd58c9bb627ffd..e96954299b7022 100644 --- a/test/ambiguous.jl +++ b/test/ambiguous.jl @@ -153,10 +153,18 @@ ambig(x::Int8, y) = 1 ambig(x::Integer, y) = 2 ambig(x, y::Int) = 3 end - ambs = detect_ambiguities(Ambig5) @test length(ambs) == 2 +module Ambig48312 +ambig(::Integer, ::Int) = 1 +ambig(::Int, ::Integer) = 2 +ambig(::Signed, ::Int) = 3 +ambig(::Int, ::Signed) = 4 +end +ambs = detect_ambiguities(Ambig48312) +@test length(ambs) == 4 + # Test that Core and Base are free of ambiguities # not using isempty so this prints more information when it fails @testset "detect_ambiguities" begin From e1fc4824b922e2ca978cbe058064934cb9ae64bf Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Tue, 17 Jan 2023 22:54:34 -0500 Subject: [PATCH 343/387] Add missing return case in `isidentityfree` (#48321) Fixes #48313. --- base/reflection.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/base/reflection.jl b/base/reflection.jl index c013f762311d52..50a4b131c2720a 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -659,6 +659,8 @@ function isidentityfree(@nospecialize(t::Type)) elseif isa(t, Union) return isidentityfree(t.a) && isidentityfree(t.b) end + # TypeVar, etc. + return false end iskindtype(@nospecialize t) = (t === DataType || t === UnionAll || t === Union || t === typeof(Bottom)) From 688f5cf6d859f4eb74b4b19f30127687d69d76b4 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Wed, 18 Jan 2023 05:34:53 +0100 Subject: [PATCH 344/387] fix some issues discovered by JET (#48303) --- base/binaryplatforms.jl | 2 +- base/loading.jl | 28 ++++++++++++++----------- stdlib/Artifacts/src/Artifacts.jl | 2 +- stdlib/FileWatching/src/FileWatching.jl | 2 +- stdlib/LibGit2/src/LibGit2.jl | 2 +- stdlib/LibGit2/src/callbacks.jl | 21 ++++++++++--------- stdlib/LibGit2/src/gitcredential.jl | 5 +++-- stdlib/LibGit2/src/types.jl | 13 +++++++----- stdlib/LibGit2/src/utils.jl | 4 ++-- 9 files changed, 44 insertions(+), 35 deletions(-) diff --git a/base/binaryplatforms.jl b/base/binaryplatforms.jl index 92e88a5107cbed..eb4bcfd8c76fc0 100644 --- a/base/binaryplatforms.jl +++ b/base/binaryplatforms.jl @@ -873,7 +873,7 @@ function detect_libstdcxx_version(max_minor_version::Int=30) end # Brute-force our way through GLIBCXX_* symbols to discover which version we're linked against - hdl = Libdl.dlopen(first(libstdcxx_paths)) + hdl = Libdl.dlopen(first(libstdcxx_paths))::Ptr{Cvoid} # Try all GLIBCXX versions down to GCC v4.8: # https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html for minor_version in max_minor_version:-1:18 diff --git a/base/loading.jl b/base/loading.jl index b1f6d608daf328..6648c87c3f4546 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -865,7 +865,11 @@ function explicit_manifest_uuid_path(project_file::String, pkg::PkgId)::Union{No uuid = get(entry, "uuid", nothing)::Union{Nothing, String} extensions = get(entry, "extensions", nothing)::Union{Nothing, Dict{String, Any}} if extensions !== nothing && haskey(extensions, pkg.name) && uuid !== nothing && uuid5(UUID(uuid), pkg.name) == pkg.uuid - p = normpath(dirname(locate_package(PkgId(UUID(uuid), name))), "..") + parent_path = locate_package(PkgId(UUID(uuid), name)) + if parent_path === nothing + error("failed to find source of parent package: \"$name\"") + end + p = normpath(dirname(parent_path), "..") extfiledir = joinpath(p, "ext", pkg.name, pkg.name * ".jl") isfile(extfiledir) && return extfiledir return joinpath(p, "ext", pkg.name * ".jl") @@ -1138,10 +1142,10 @@ function insert_extension_triggers(env::String, pkg::PkgId)::Union{Nothing,Missi dep_name in weakdeps || continue entries::Vector{Any} if length(entries) != 1 - error("expected a single entry for $(repr(name)) in $(repr(project_file))") + error("expected a single entry for $(repr(dep_name)) in $(repr(project_file))") end entry = first(entries)::Dict{String, Any} - uuid = get(entry, "uuid", nothing)::Union{String, Nothing} + uuid = entry["uuid"]::String d_weakdeps[dep_name] = uuid end @assert length(d_weakdeps) == length(weakdeps) @@ -1247,7 +1251,7 @@ function _tryrequire_from_serialized(modkey::PkgId, build_id::UInt128) loading = get(package_locks, modkey, false) if loading !== false # load already in progress for this module - loaded = wait(loading) + loaded = wait(loading::Threads.Condition) else package_locks[modkey] = Threads.Condition(require_lock) try @@ -1282,7 +1286,7 @@ function _tryrequire_from_serialized(modkey::PkgId, path::String, ocachepath::Un loading = get(package_locks, modkey, false) if loading !== false # load already in progress for this module - loaded = wait(loading) + loaded = wait(loading::Threads.Condition) else for i in 1:length(depmods) dep = depmods[i] @@ -1324,7 +1328,7 @@ function _tryrequire_from_serialized(pkg::PkgId, path::String, ocachepath::Union pkgimage = !isempty(clone_targets) if pkgimage ocachepath !== nothing || return ArgumentError("Expected ocachepath to be provided") - isfile(ocachepath) || return ArgumentError("Ocachepath $ocachpath is not a file.") + isfile(ocachepath) || return ArgumentError("Ocachepath $ocachepath is not a file.") ocachepath == ocachefile_from_cachefile(path) || return ArgumentError("$ocachepath is not the expected ocachefile") # TODO: Check for valid clone_targets? isvalid_pkgimage_crc(io, ocachepath) || return ArgumentError("Invalid checksum in cache file $ocachepath.") @@ -1404,13 +1408,13 @@ end staledeps = true break end - staledeps[i] = dep + (staledeps::Vector{Any})[i] = dep end if staledeps === true ocachefile = nothing continue end - restored = _include_from_serialized(pkg, path_to_try, ocachefile, staledeps) + restored = _include_from_serialized(pkg, path_to_try, ocachefile::String, staledeps::Vector{Any}) if !isa(restored, Module) @debug "Deserialization checks failed while attempting to load cache from $path_to_try" exception=restored else @@ -1667,7 +1671,7 @@ function _require(pkg::PkgId, env=nothing) loading = get(package_locks, pkg, false) if loading !== false # load already in progress for this module - return wait(loading) + return wait(loading::Threads.Condition) end package_locks[pkg] = Threads.Condition(require_lock) @@ -2160,12 +2164,12 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in rename(tmppath_so, ocachefile::String; force=true) catch e e isa IOError || rethrow() - isfile(ocachefile) || rethrow() + isfile(ocachefile::String) || rethrow() # Windows prevents renaming a file that is in use so if there is a Julia session started # with a package image loaded, we cannot rename that file. # The code belows append a `_i` to the name of the cache file where `i` is the smallest number such that # that cache file does not exist. - ocachename, ocacheext = splitext(ocachefile) + ocachename, ocacheext = splitext(ocachefile::String) old_cachefiles = Set(readdir(cachepath)) num = 1 while true @@ -2185,7 +2189,7 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in finally rm(tmppath, force=true) if cache_objects - rm(tmppath_o, force=true) + rm(tmppath_o::String, force=true) rm(tmppath_so, force=true) end end diff --git a/stdlib/Artifacts/src/Artifacts.jl b/stdlib/Artifacts/src/Artifacts.jl index 4bcf98df2a1d9b..a9554c95f3151c 100644 --- a/stdlib/Artifacts/src/Artifacts.jl +++ b/stdlib/Artifacts/src/Artifacts.jl @@ -52,7 +52,7 @@ function artifacts_dirs(args...) return String[abspath(depot, "artifacts", args...) for depot in Base.DEPOT_PATH] else # If we've been given an override, use _only_ that directory. - return String[abspath(ARTIFACTS_DIR_OVERRIDE[], args...)] + return String[abspath(ARTIFACTS_DIR_OVERRIDE[]::String, args...)] end end diff --git a/stdlib/FileWatching/src/FileWatching.jl b/stdlib/FileWatching/src/FileWatching.jl index 1b4886c0d8e32d..17ae24460db6b8 100644 --- a/stdlib/FileWatching/src/FileWatching.jl +++ b/stdlib/FileWatching/src/FileWatching.jl @@ -215,7 +215,7 @@ mutable struct _FDWatcher t.refcount = (0, 0) t.active = (false, false) @static if Sys.isunix() - if FDWatchers[t.fdnum] == t + if FDWatchers[t.fdnum] === t FDWatchers[t.fdnum] = nothing end end diff --git a/stdlib/LibGit2/src/LibGit2.jl b/stdlib/LibGit2/src/LibGit2.jl index cd7dd016156483..6a797937ccf0bc 100644 --- a/stdlib/LibGit2/src/LibGit2.jl +++ b/stdlib/LibGit2/src/LibGit2.jl @@ -848,7 +848,7 @@ function rebase!(repo::GitRepo, upstream::AbstractString="", newbase::AbstractSt end finally if !isempty(newbase) - close(onto_ann) + close(onto_ann::GitAnnotated) end close(upst_ann) close(head_ann) diff --git a/stdlib/LibGit2/src/callbacks.jl b/stdlib/LibGit2/src/callbacks.jl index 83ac58010ac32a..3bc6463140d5f7 100644 --- a/stdlib/LibGit2/src/callbacks.jl +++ b/stdlib/LibGit2/src/callbacks.jl @@ -276,18 +276,20 @@ function credentials_callback(libgit2credptr::Ptr{Ptr{Cvoid}}, url_ptr::Cstring, # information cached inside the payload. if isempty(p.url) p.url = unsafe_string(url_ptr) - m = match(URL_REGEX, p.url) + m = match(URL_REGEX, p.url)::RegexMatch p.scheme = something(m[:scheme], SubString("")) p.username = something(m[:user], SubString("")) - p.host = m[:host] + p.host = something(m[:host]) # When an explicit credential is supplied we will make sure to use the given # credential during the first callback by modifying the allowed types. The # modification only is in effect for the first callback since `allowed_types` cannot # be mutated. - if p.explicit !== nothing - cred = p.explicit + cache = p.cache + explicit = p.explicit + if explicit !== nothing + cred = explicit # Copy explicit credentials to avoid mutating approved credentials. # invalidation fix from cred being non-inferrable @@ -300,16 +302,15 @@ function credentials_callback(libgit2credptr::Ptr{Ptr{Cvoid}}, url_ptr::Cstring, else allowed_types &= Cuint(0) # Unhandled credential type end - elseif p.cache !== nothing + elseif cache !== nothing cred_id = credential_identifier(p.scheme, p.host) # Perform a deepcopy as we do not want to mutate approved cached credentials - if haskey(p.cache, cred_id) - # invalidation fix from p.cache[cred_id] being non-inferrable - p.credential = Base.invokelatest(deepcopy, p.cache[cred_id]) + if haskey(cache, cred_id) + # invalidation fix from cache[cred_id] being non-inferrable + p.credential = Base.invokelatest(deepcopy, cache[cred_id]) end end - p.first_pass = true else p.first_pass = false @@ -447,7 +448,7 @@ function ssh_knownhost_check( ) if (m = match(r"^(.+):(\d+)$", host)) !== nothing host = m.captures[1] - port = parse(Int, m.captures[2]) + port = parse(Int, something(m.captures[2])) else port = 22 # default SSH port end diff --git a/stdlib/LibGit2/src/gitcredential.jl b/stdlib/LibGit2/src/gitcredential.jl index acfde02578523c..7ff20ca1fdf2cf 100644 --- a/stdlib/LibGit2/src/gitcredential.jl +++ b/stdlib/LibGit2/src/gitcredential.jl @@ -46,7 +46,8 @@ function Base.shred!(cred::GitCredential) cred.host = nothing cred.path = nothing cred.username = nothing - cred.password !== nothing && Base.shred!(cred.password) + pwd = cred.password + pwd !== nothing && Base.shred!(pwd) cred.password = nothing return cred end @@ -122,7 +123,7 @@ function Base.read!(io::IO, cred::GitCredential) if key == "url" # Any components which are missing from the URL will be set to empty # https://git-scm.com/docs/git-credential#git-credential-codeurlcode - Base.shred!(parse(GitCredential, value)) do urlcred + Base.shred!(parse(GitCredential, value::AbstractString)) do urlcred copy!(cred, urlcred) end elseif key in GIT_CRED_ATTRIBUTES diff --git a/stdlib/LibGit2/src/types.jl b/stdlib/LibGit2/src/types.jl index d5ed9014aea866..1ea6c797d16362 100644 --- a/stdlib/LibGit2/src/types.jl +++ b/stdlib/LibGit2/src/types.jl @@ -1389,7 +1389,8 @@ CredentialPayload(p::CredentialPayload) = p function Base.shred!(p::CredentialPayload) # Note: Avoid shredding the `explicit` or `cache` fields as these are just references # and it is not our responsibility to shred them. - p.credential !== nothing && Base.shred!(p.credential) + credential = p.credential + credential !== nothing && Base.shred!(credential) p.credential = nothing end @@ -1430,8 +1431,9 @@ function approve(p::CredentialPayload; shred::Bool=true) # Each `approve` call needs to avoid shredding the passed in credential as we need # the credential information intact for subsequent approve calls. - if p.cache !== nothing - approve(p.cache, cred, p.url) + cache = p.cache + if cache !== nothing + approve(cache, cred, p.url) shred = false # Avoid wiping `cred` as this would also wipe the cached copy end if p.allow_git_helpers @@ -1460,8 +1462,9 @@ function reject(p::CredentialPayload; shred::Bool=true) # Note: each `reject` call needs to avoid shredding the passed in credential as we need # the credential information intact for subsequent reject calls. - if p.cache !== nothing - reject(p.cache, cred, p.url) + cache = p.cache + if cache !== nothing + reject(cache, cred, p.url) end if p.allow_git_helpers reject(p.config, cred, p.url) diff --git a/stdlib/LibGit2/src/utils.jl b/stdlib/LibGit2/src/utils.jl index b601ea4efe6013..5234e9b6fc291a 100644 --- a/stdlib/LibGit2/src/utils.jl +++ b/stdlib/LibGit2/src/utils.jl @@ -171,7 +171,7 @@ end function credential_identifier(url::AbstractString) m = match(URL_REGEX, url) - scheme = something(m[:scheme], "") - host = m[:host] + scheme = something(m[:scheme], SubString("")) + host = something(m[:host]) credential_identifier(scheme, host) end From 15b7c6b514d988e669c47faec572bccb0a85ab8f Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Wed, 18 Jan 2023 03:01:50 -0500 Subject: [PATCH 345/387] Allow effect inference of `eltype(::Tuple)` (#48322) By bumping max_methods for `eltype` slightly to cover all four Tuple methods. --- base/tuple.jl | 15 +++++++++++++++ test/tuple.jl | 1 + 2 files changed, 16 insertions(+) diff --git a/base/tuple.jl b/base/tuple.jl index f5e85137bc6f01..134010268c7fe4 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -234,6 +234,21 @@ function _compute_eltype(@nospecialize t) end end +# We'd like to be able to infer eltype(::Tuple), which needs to be able to +# look at these four methods: +# +# julia> methods(Base.eltype, Tuple{Type{<:Tuple}}) +# 4 methods for generic function "eltype" from Base: +# [1] eltype(::Type{Union{}}) +# @ abstractarray.jl:234 +# [2] eltype(::Type{Tuple{}}) +# @ tuple.jl:199 +# [3] eltype(t::Type{<:Tuple{Vararg{E}}}) where E +# @ tuple.jl:200 +# [4] eltype(t::Type{<:Tuple}) +# @ tuple.jl:209 +typeof(function eltype end).name.max_methods = UInt8(4) + # version of tail that doesn't throw on empty tuples (used in array indexing) safe_tail(t::Tuple) = tail(t) safe_tail(t::Tuple{}) = () diff --git a/test/tuple.jl b/test/tuple.jl index 86f41b2fbc660e..ae764bd05481bf 100644 --- a/test/tuple.jl +++ b/test/tuple.jl @@ -782,3 +782,4 @@ namedtup = (;a=1, b=2, c=3) # Make sure that tuple iteration is foldable @test Core.Compiler.is_foldable(Base.infer_effects(iterate, Tuple{NTuple{4, Float64}, Int})) +@test Core.Compiler.is_foldable(Base.infer_effects(eltype, Tuple{Tuple})) From e7c339fdec26148be24370de31ff91a7f40b001b Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Wed, 18 Jan 2023 09:15:17 +0100 Subject: [PATCH 346/387] remove uses of `@Module.macro` (#48316) --- base/abstractarray.jl | 2 +- base/compiler/compiler.jl | 2 +- base/compiler/ssair/verify.jl | 10 +++++----- base/docs/Docs.jl | 2 +- stdlib/Distributed/test/distributed_exec.jl | 4 ++-- stdlib/FileWatching/test/runtests.jl | 2 +- stdlib/InteractiveUtils/test/runtests.jl | 2 +- stdlib/Profile/src/Profile.jl | 2 +- stdlib/Sockets/test/runtests.jl | 6 +++--- test/ccall.jl | 2 +- test/channels.jl | 2 +- test/compiler/inference.jl | 10 +++++----- test/compiler/inline.jl | 2 +- test/keywordargs.jl | 2 +- test/meta.jl | 4 ++-- test/runtests.jl | 2 +- test/threads.jl | 2 +- 17 files changed, 29 insertions(+), 29 deletions(-) diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 2e1d885ca5a3ff..256c5262b9bcdc 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -936,7 +936,7 @@ end ## from general iterable to any array -# This is `@Experimental.max_methods 1 function copyto! end`, which is not +# This is `Experimental.@max_methods 1 function copyto! end`, which is not # defined at this point in bootstrap. typeof(function copyto! end).name.max_methods = UInt8(1) diff --git a/base/compiler/compiler.jl b/base/compiler/compiler.jl index dae06d3e5b27f5..7213b3615e8e1b 100644 --- a/base/compiler/compiler.jl +++ b/base/compiler/compiler.jl @@ -50,7 +50,7 @@ ntuple(f, n) = (Any[f(i) for i = 1:n]...,) # core operations & types function return_type end # promotion.jl expects this to exist -is_return_type(@Core.nospecialize(f)) = f === return_type +is_return_type(Core.@nospecialize(f)) = f === return_type include("promotion.jl") include("tuple.jl") include("pair.jl") diff --git a/base/compiler/ssair/verify.jl b/base/compiler/ssair/verify.jl index 23c76525d0fc2d..7dc268f648bcc9 100644 --- a/base/compiler/ssair/verify.jl +++ b/base/compiler/ssair/verify.jl @@ -111,9 +111,9 @@ function verify_ir(ir::IRCode, print::Bool=true, error("") end if !(idx in ir.cfg.blocks[s].preds) - #@Base.show ir.cfg - #@Base.show ir - #@Base.show ir.argtypes + #Base.@show ir.cfg + #Base.@show ir + #Base.@show ir.argtypes @verify_error "Successor $s of block $idx not in predecessor list" error("") end @@ -192,8 +192,8 @@ function verify_ir(ir::IRCode, print::Bool=true, end end if !(edge == 0 && bb == 1) && !(edge in ir.cfg.blocks[bb].preds) - #@Base.show ir.argtypes - #@Base.show ir + #Base.@show ir.argtypes + #Base.@show ir @verify_error "Edge $edge of φ node $idx not in predecessor list" error("") end diff --git a/base/docs/Docs.jl b/base/docs/Docs.jl index 994d8077edc4d6..f4db13f828ed63 100644 --- a/base/docs/Docs.jl +++ b/base/docs/Docs.jl @@ -39,7 +39,7 @@ You can document an object after its definition by @doc "foo" function_to_doc @doc "bar" TypeToDoc -For macros, the syntax is `@doc "macro doc" :(@Module.macro)` or `@doc "macro doc" +For macros, the syntax is `@doc "macro doc" :(Module.@macro)` or `@doc "macro doc" :(string_macro"")` for string macros. Without the quote `:()` the expansion of the macro will be documented. diff --git a/stdlib/Distributed/test/distributed_exec.jl b/stdlib/Distributed/test/distributed_exec.jl index c3eca69bb52687..8471acade993b0 100644 --- a/stdlib/Distributed/test/distributed_exec.jl +++ b/stdlib/Distributed/test/distributed_exec.jl @@ -302,7 +302,7 @@ end # Tests for issue #23109 - should not hang. f = @spawnat :any rand(1, 1) -@Base.Experimental.sync begin +Base.Experimental.@sync begin for _ in 1:10 @async fetch(f) end @@ -310,7 +310,7 @@ end wid1, wid2 = workers()[1:2] f = @spawnat wid1 rand(1,1) -@Base.Experimental.sync begin +Base.Experimental.@sync begin @async fetch(f) @async remotecall_fetch(()->fetch(f), wid2) end diff --git a/stdlib/FileWatching/test/runtests.jl b/stdlib/FileWatching/test/runtests.jl index dd5187ec524349..75b17b5f0e5112 100644 --- a/stdlib/FileWatching/test/runtests.jl +++ b/stdlib/FileWatching/test/runtests.jl @@ -74,7 +74,7 @@ end # Odd numbers trigger reads, even numbers timeout for (i, intvl) in enumerate(intvls) - @Experimental.sync begin + Experimental.@sync begin global ready = 0 global ready_c = Condition() for idx in 1:n diff --git a/stdlib/InteractiveUtils/test/runtests.jl b/stdlib/InteractiveUtils/test/runtests.jl index b3be01e4ec6767..02fb4b25ec43fb 100644 --- a/stdlib/InteractiveUtils/test/runtests.jl +++ b/stdlib/InteractiveUtils/test/runtests.jl @@ -331,7 +331,7 @@ end # manually generate a broken function, which will break codegen # and make sure Julia doesn't crash -@eval @noinline @Base.constprop :none f_broken_code() = 0 +@eval @noinline Base.@constprop :none f_broken_code() = 0 let m = which(f_broken_code, ()) let src = Base.uncompressed_ast(m) src.code = Any[ diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index ba2d7390a214ca..59686a50a760c4 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -1250,7 +1250,7 @@ every object as one so they can be easily counted. Otherwise, report the actual size. """ function take_heap_snapshot(io::IOStream, all_one::Bool=false) - @Base._lock_ios(io, ccall(:jl_gc_take_heap_snapshot, Cvoid, (Ptr{Cvoid}, Cchar), io.handle, Cchar(all_one))) + Base.@_lock_ios(io, ccall(:jl_gc_take_heap_snapshot, Cvoid, (Ptr{Cvoid}, Cchar), io.handle, Cchar(all_one))) end function take_heap_snapshot(filepath::String, all_one::Bool=false) open(filepath, "w") do io diff --git a/stdlib/Sockets/test/runtests.jl b/stdlib/Sockets/test/runtests.jl index a27bb89408f1d9..02a994460afbfa 100644 --- a/stdlib/Sockets/test/runtests.jl +++ b/stdlib/Sockets/test/runtests.jl @@ -136,7 +136,7 @@ defaultport = rand(2000:4000) write(sock, "Hello World\n") # test "locked" println to a socket - @Experimental.sync begin + Experimental.@sync begin for i in 1:100 @async println(sock, "a", 1) end @@ -307,7 +307,7 @@ end bind(a, ip"127.0.0.1", randport) bind(b, ip"127.0.0.1", randport + 1) - @Experimental.sync begin + Experimental.@sync begin let i = 0 for _ = 1:30 @async let msg = String(recv(a)) @@ -387,7 +387,7 @@ end # connect to it client_sock = connect(addr, port) test_done = false - @Experimental.sync begin + Experimental.@sync begin @async begin Base.wait_readnb(client_sock, 1) test_done || error("Client disconnected prematurely.") diff --git a/test/ccall.jl b/test/ccall.jl index cf5bb0e2cf9476..d88e667b55c72a 100644 --- a/test/ccall.jl +++ b/test/ccall.jl @@ -1921,7 +1921,7 @@ end end @testset "ccall_effects" begin - ctest_total(x) = @Base.assume_effects :total @ccall libccalltest.ctest(x::Complex{Int})::Complex{Int} + ctest_total(x) = Base.@assume_effects :total @ccall libccalltest.ctest(x::Complex{Int})::Complex{Int} ctest_total_const() = Val{ctest_total(1 + 2im)}() Core.Compiler.return_type(ctest_total_const, Tuple{}) == Val{2 + 0im} end diff --git a/test/channels.jl b/test/channels.jl index 36b89cdadcafec..eb82a20686ae98 100644 --- a/test/channels.jl +++ b/test/channels.jl @@ -310,7 +310,7 @@ end end @testset "timedwait on multiple channels" begin - @Experimental.sync begin + Experimental.@sync begin rr1 = Channel(1) rr2 = Channel(1) rr3 = Channel(1) diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index feaf3770a6848d..d3d974e874a6bf 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -3282,8 +3282,8 @@ f_with_Type_arg(::Type{T}) where {T} = T (N >= 0) || throw(ArgumentError(string("tuple length should be ≥0, got ", N))) if @generated quote - @Base.nexprs $N i -> t_i = f(i) - @Base.ncall $N tuple t + Base.@nexprs $N i -> t_i = f(i) + Base.@ncall $N tuple t end else Tuple(f(i) for i = 1:N) @@ -4468,7 +4468,7 @@ end end # Test that max_methods works as expected -@Base.Experimental.max_methods 1 function f_max_methods end +Base.Experimental.@max_methods 1 function f_max_methods end f_max_methods(x::Int) = 1 f_max_methods(x::Float64) = 2 g_max_methods(x) = f_max_methods(x) @@ -4502,7 +4502,7 @@ end struct Issue45780 oc::Core.OpaqueClosure{Tuple{}} end -f45780() = Val{Issue45780(@Base.Experimental.opaque ()->1).oc()}() +f45780() = Val{Issue45780(Base.Experimental.@opaque ()->1).oc()}() @test (@inferred f45780()) == Val{1}() # issue #45600 @@ -4582,7 +4582,7 @@ end @test Const((1,2)) ⊑ PartialStruct(Tuple{Vararg{Int}}, [Const(1), Vararg{Int}]) # Test that semi-concrete interpretation doesn't break on functions with while loops in them. -@Base.assume_effects :consistent :effect_free :terminates_globally function pure_annotated_loop(x::Int, y::Int) +Base.@assume_effects :consistent :effect_free :terminates_globally function pure_annotated_loop(x::Int, y::Int) for i = 1:2 x += y end diff --git a/test/compiler/inline.jl b/test/compiler/inline.jl index af83fe0df45d7b..104fad5d2bbada 100644 --- a/test/compiler/inline.jl +++ b/test/compiler/inline.jl @@ -364,7 +364,7 @@ struct RealConstrained{T <: Real}; end struct Big x::NTuple{1024, Int} end -@Base.pure Big() = Big(ntuple(identity, 1024)) +Base.@pure Big() = Big(ntuple(identity, 1024)) function pure_elim_full() Big() nothing diff --git a/test/keywordargs.jl b/test/keywordargs.jl index 366f14393a94fe..a5116afa3c31de 100644 --- a/test/keywordargs.jl +++ b/test/keywordargs.jl @@ -181,7 +181,7 @@ end @test test4538_2(x=2) == 2 # that, but in a module - @Foo4538.TEST() + Foo4538.@TEST() @test test4538_foo_2() == 1 @test test4538_foo_2(x=2) == 2 diff --git a/test/meta.jl b/test/meta.jl index fd984cc837e4cc..399e106684a819 100644 --- a/test/meta.jl +++ b/test/meta.jl @@ -144,8 +144,8 @@ baremodule B x = 1 module M; x = 2; end import Base - @Base.eval x = 3 - @Base.eval M x = 4 + Base.@eval x = 3 + Base.@eval M x = 4 end @test B.x == 3 @test B.M.x == 4 diff --git a/test/runtests.jl b/test/runtests.jl index cce4acf44d1361..91f3a67490315f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -243,7 +243,7 @@ cd(@__DIR__) do end end end - o_ts_duration = @elapsed @Experimental.sync begin + o_ts_duration = @elapsed Experimental.@sync begin for p in workers() @async begin push!(all_tasks, current_task()) diff --git a/test/threads.jl b/test/threads.jl index fb684b275e8648..af752fe715b0e7 100644 --- a/test/threads.jl +++ b/test/threads.jl @@ -154,7 +154,7 @@ end # issue #34769 function idle_callback(handle) - idle = @Base.handle_as handle UvTestIdle + idle = Base.@handle_as handle UvTestIdle if idle.active idle.count += 1 if idle.count == 1 From c4cf1e69de6907e6e56382df02f2ce9bcf9e7c19 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Wed, 18 Jan 2023 09:44:46 +0100 Subject: [PATCH 347/387] fix an erronous type assert (#48327) --- base/loading.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/loading.jl b/base/loading.jl index 6648c87c3f4546..02bbd6fcff7f84 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -1414,7 +1414,7 @@ end ocachefile = nothing continue end - restored = _include_from_serialized(pkg, path_to_try, ocachefile::String, staledeps::Vector{Any}) + restored = _include_from_serialized(pkg, path_to_try, ocachefile, staledeps::Vector{Any}) if !isa(restored, Module) @debug "Deserialization checks failed while attempting to load cache from $path_to_try" exception=restored else From 4725b788950ba815e7fb2ee423b32da4e5fa41ca Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Wed, 18 Jan 2023 13:33:15 +0100 Subject: [PATCH 348/387] Add parameters to const triangular types (#48331) --- stdlib/LinearAlgebra/src/triangular.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stdlib/LinearAlgebra/src/triangular.jl b/stdlib/LinearAlgebra/src/triangular.jl index bd21471deeedd8..d287f83c9be4d3 100644 --- a/stdlib/LinearAlgebra/src/triangular.jl +++ b/stdlib/LinearAlgebra/src/triangular.jl @@ -151,9 +151,9 @@ julia> UnitUpperTriangular(A) """ UnitUpperTriangular -const UpperOrUnitUpperTriangular{T} = Union{UpperTriangular{T}, UnitUpperTriangular{T}} -const LowerOrUnitLowerTriangular{T} = Union{LowerTriangular{T}, UnitLowerTriangular{T}} -const UpperOrLowerTriangular{T} = Union{UpperOrUnitUpperTriangular{T}, LowerOrUnitLowerTriangular{T}} +const UpperOrUnitUpperTriangular{T,S} = Union{UpperTriangular{T,S}, UnitUpperTriangular{T,S}} +const LowerOrUnitLowerTriangular{T,S} = Union{LowerTriangular{T,S}, UnitLowerTriangular{T,S}} +const UpperOrLowerTriangular{T,S} = Union{UpperOrUnitUpperTriangular{T,S}, LowerOrUnitLowerTriangular{T,S}} imag(A::UpperTriangular) = UpperTriangular(imag(A.data)) imag(A::LowerTriangular) = LowerTriangular(imag(A.data)) From 4e763466581eea139688ad27c4354410d728c7c8 Mon Sep 17 00:00:00 2001 From: Sukera <11753998+Seelengrab@users.noreply.github.com> Date: Wed, 18 Jan 2023 17:22:12 +0100 Subject: [PATCH 349/387] Add docstring for `err` in the REPL (#48300) * Add docstring for `err` in the REPL * Add `err` to Essentials docs Co-authored-by: Sukera --- base/docs/basedocs.jl | 8 ++++++++ doc/src/base/base.md | 1 + 2 files changed, 9 insertions(+) diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index 7d792a401fdab7..1dd29922462ac4 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -1456,6 +1456,14 @@ A variable referring to the last computed value, automatically set at the intera """ kw"ans" +""" + err + +A variable referring to the last thrown errors, automatically set at the interactive prompt. +The thrown errors are collected in a stack of exceptions. +""" +kw"err" + """ devnull diff --git a/doc/src/base/base.md b/doc/src/base/base.md index 5bb227137b24da..9a00a864907ec7 100644 --- a/doc/src/base/base.md +++ b/doc/src/base/base.md @@ -37,6 +37,7 @@ Base.which(::Any, ::Any) Base.methods Base.@show ans +err Base.active_project Base.set_active_project ``` From 9de3263f8b36a43546fdb96cd873629c3b73fd7c Mon Sep 17 00:00:00 2001 From: DilumAluthgeBot <43731525+DilumAluthgeBot@users.noreply.github.com> Date: Wed, 18 Jan 2023 13:24:24 -0500 Subject: [PATCH 350/387] =?UTF-8?q?=F0=9F=A4=96=20[master]=20Bump=20the=20?= =?UTF-8?q?Pkg=20stdlib=20from=205ae866151=20to=2067f39cd0a=20(#48335)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Pkg-5ae866151e0cfd0536d0bfbb66e7f14ea026bd31.tar.gz/md5 | 1 - .../Pkg-5ae866151e0cfd0536d0bfbb66e7f14ea026bd31.tar.gz/sha512 | 1 - .../Pkg-67f39cd0ae2cc737d2f4a2989d8a96ec9061dbf4.tar.gz/md5 | 1 + .../Pkg-67f39cd0ae2cc737d2f4a2989d8a96ec9061dbf4.tar.gz/sha512 | 1 + stdlib/Pkg.version | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 deps/checksums/Pkg-5ae866151e0cfd0536d0bfbb66e7f14ea026bd31.tar.gz/md5 delete mode 100644 deps/checksums/Pkg-5ae866151e0cfd0536d0bfbb66e7f14ea026bd31.tar.gz/sha512 create mode 100644 deps/checksums/Pkg-67f39cd0ae2cc737d2f4a2989d8a96ec9061dbf4.tar.gz/md5 create mode 100644 deps/checksums/Pkg-67f39cd0ae2cc737d2f4a2989d8a96ec9061dbf4.tar.gz/sha512 diff --git a/deps/checksums/Pkg-5ae866151e0cfd0536d0bfbb66e7f14ea026bd31.tar.gz/md5 b/deps/checksums/Pkg-5ae866151e0cfd0536d0bfbb66e7f14ea026bd31.tar.gz/md5 deleted file mode 100644 index 1853fc3d640299..00000000000000 --- a/deps/checksums/Pkg-5ae866151e0cfd0536d0bfbb66e7f14ea026bd31.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -5ff653fadbde2fe95ffe1d696facaa5a diff --git a/deps/checksums/Pkg-5ae866151e0cfd0536d0bfbb66e7f14ea026bd31.tar.gz/sha512 b/deps/checksums/Pkg-5ae866151e0cfd0536d0bfbb66e7f14ea026bd31.tar.gz/sha512 deleted file mode 100644 index b5f5bec511fcf9..00000000000000 --- a/deps/checksums/Pkg-5ae866151e0cfd0536d0bfbb66e7f14ea026bd31.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -88f7ba8852efaff0416c382166cc2bcdb4090fde15b90b6ec62831bd640c8cc8cf3124d00b4392bc2b7719f0b51cb791c812e4e6b1e4fa3edf1cc5414dcd8ef6 diff --git a/deps/checksums/Pkg-67f39cd0ae2cc737d2f4a2989d8a96ec9061dbf4.tar.gz/md5 b/deps/checksums/Pkg-67f39cd0ae2cc737d2f4a2989d8a96ec9061dbf4.tar.gz/md5 new file mode 100644 index 00000000000000..8b4f0f2b5e0ac3 --- /dev/null +++ b/deps/checksums/Pkg-67f39cd0ae2cc737d2f4a2989d8a96ec9061dbf4.tar.gz/md5 @@ -0,0 +1 @@ +0d9583ce0ce16e746d0eadca74faf3c3 diff --git a/deps/checksums/Pkg-67f39cd0ae2cc737d2f4a2989d8a96ec9061dbf4.tar.gz/sha512 b/deps/checksums/Pkg-67f39cd0ae2cc737d2f4a2989d8a96ec9061dbf4.tar.gz/sha512 new file mode 100644 index 00000000000000..142a75984bd1e6 --- /dev/null +++ b/deps/checksums/Pkg-67f39cd0ae2cc737d2f4a2989d8a96ec9061dbf4.tar.gz/sha512 @@ -0,0 +1 @@ +ae53756ea5642adeac286fae1498be17f4d3ad3779d77982c0d7b3dab3e8b13793a853b087f42abea0a4b67ec4d583fcdfd9cf8e28d5a95bfa5776ba227ab9f8 diff --git a/stdlib/Pkg.version b/stdlib/Pkg.version index b7b947b542e043..1f7d1c2ba6a56d 100644 --- a/stdlib/Pkg.version +++ b/stdlib/Pkg.version @@ -1,4 +1,4 @@ PKG_BRANCH = master -PKG_SHA1 = 5ae866151e0cfd0536d0bfbb66e7f14ea026bd31 +PKG_SHA1 = 67f39cd0ae2cc737d2f4a2989d8a96ec9061dbf4 PKG_GIT_URL := https://github.com/JuliaLang/Pkg.jl.git PKG_TAR_URL = https://api.github.com/repos/JuliaLang/Pkg.jl/tarball/$1 From c711f67304da9647e82a005871ca3460f20abfe8 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Thu, 19 Jan 2023 06:44:09 +0900 Subject: [PATCH 351/387] add test cases for module-wise `@max_methods` configuration (#48328) --- test/compiler/inference.jl | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index d3d974e874a6bf..71f9903f85324c 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -4467,7 +4467,7 @@ end end |> only === Union{} end -# Test that max_methods works as expected +# Test that a function-wise `@max_methods` works as expected Base.Experimental.@max_methods 1 function f_max_methods end f_max_methods(x::Int) = 1 f_max_methods(x::Float64) = 2 @@ -4475,6 +4475,17 @@ g_max_methods(x) = f_max_methods(x) @test only(Base.return_types(g_max_methods, Tuple{Int})) === Int @test only(Base.return_types(g_max_methods, Tuple{Any})) === Any +# Test that a module-wise `@max_methods` works as expected +module Test43370 +using Test +Base.Experimental.@max_methods 1 +f_max_methods(x::Int) = 1 +f_max_methods(x::Float64) = 2 +g_max_methods(x) = f_max_methods(x) +@test only(Base.return_types(g_max_methods, Tuple{Int})) === Int +@test only(Base.return_types(g_max_methods, Tuple{Any})) === Any +end + # Make sure return_type_tfunc doesn't accidentally cause bad inference if used # at top level. @test let From 1bff32b2f8d2d3972cb992ffc79850b4eac78304 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Thu, 19 Jan 2023 12:44:29 +0100 Subject: [PATCH 352/387] add a type assert to `read` on a `Cmd` (#48334) --- base/process.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/process.jl b/base/process.jl index 65d06d73dff0b8..55df523c1f7d2e 100644 --- a/base/process.jl +++ b/base/process.jl @@ -447,7 +447,7 @@ function read(cmd::AbstractCmd) procs = open(cmd, "r", devnull) bytes = read(procs.out) success(procs) || pipeline_error(procs) - return bytes + return bytes::Vector{UInt8} end """ From f9d1b85495089cae246c572bc8956d80d0fc2489 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Thu, 19 Jan 2023 08:33:25 -0500 Subject: [PATCH 353/387] irinterp: Remove redundant `is_inlineable_constant` check (#48345) The way that `is_inlineable_constant` is supposed to work is that during type inference, we allow a large `Const` type to persist in the inference domain and then at inlining time, we decide whether or not to remove the call and replace it by the constant (thus having to serialize the constant) or just leave the call as is (with the types eventually being widened). `irinterp` was being a bit overeager here, not even permitting large constants into the inference domain, which was hurting precision. We alraedy have an appropriate `is_inlineable_constant` call guarding the inlining of constants into the IR, so the move the one that checks it when returning from concrete eval. --- base/compiler/ssair/irinterp.jl | 4 +--- test/compiler/inline.jl | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index 661f76b61fd846..992eadde3e1016 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -156,9 +156,7 @@ function concrete_eval_invoke(interp::AbstractInterpreter, catch return Pair{Any, Bool}(Union{}, false) end - if is_inlineable_constant(value) - return Pair{Any, Bool}(Const(value), true) - end + return Pair{Any, Bool}(Const(value), true) else ir′ = codeinst_to_ir(interp, code) if ir′ !== nothing diff --git a/test/compiler/inline.jl b/test/compiler/inline.jl index 104fad5d2bbada..5de437846c093c 100644 --- a/test/compiler/inline.jl +++ b/test/compiler/inline.jl @@ -1896,3 +1896,17 @@ let src = code_typed1(make_issue47349(Val{4}()), (Any,)) make_issue47349(Val(4))((x,nothing,Int)) end |> only === Type{Int} end + +# Test that irinterp can make use of constant results even if they're big +# Check that pure functions with non-inlineable results still get deleted +struct BigSemi + x::NTuple{1024, Int} +end +@Base.assume_effects :total @noinline make_big_tuple(x::Int) = ntuple(x->x+1, 1024)::NTuple{1024, Int} +BigSemi(y::Int, x::Int) = BigSemi(make_big_tuple(x)) +function elim_full_ir(y) + bs = BigSemi(y, 10) + return Val{bs.x[1]}() +end + +@test fully_eliminated(elim_full_ir, Tuple{Int}) From b8ca7cfa2c996ff12ed0de6cc61f7fad4d3dfa88 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Thu, 19 Jan 2023 08:36:28 -0500 Subject: [PATCH 354/387] Improve effects for pairs(::NamedTuple) (#48344) The `new` syntax as well as the implicit inner constructor introduces an implicit `convert(fieldtype(T, n), x)` for every field. This convert is generally a no-op (because the fieldtype comes from the type parameter, which in turn comes from the type of `x`). However, the compiler cannot prove this and a `convert` call with unknown types taints all effects. Avoid that by manually avoiding the `convert` call. It is a bit ugly, but there isn't really another way to write it at the moment. In the future we may want to have some nicer way to disable the implicit generation of `convert` calls. Co-authored-by: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> --- base/essentials.jl | 35 ++++++++++++++++++++--------------- base/iterators.jl | 4 ++-- base/namedtuple.jl | 2 +- base/promotion.jl | 1 + test/namedtuple.jl | 1 + 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/base/essentials.jl b/base/essentials.jl index 61e9b1b25800d8..a9794f372a0d50 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -32,21 +32,6 @@ An `AbstractDict{K, V}` should be an iterator of `Pair{K, V}`. """ abstract type AbstractDict{K,V} end -""" - Iterators.Pairs(values, keys) <: AbstractDict{eltype(keys), eltype(values)} - -Transforms an indexable container into a Dictionary-view of the same data. -Modifying the key-space of the underlying data may invalidate this object. -""" -struct Pairs{K, V, I, A} <: AbstractDict{K, V} - data::A - itr::I -end -Pairs{K, V}(data::A, itr::I) where {K, V, I, A} = Pairs{K, V, I, A}(data, itr) -Pairs{K}(data::A, itr::I) where {K, I, A} = Pairs{K, eltype(A), I, A}(data, itr) -Pairs(data::A, itr::I) where {I, A} = Pairs{eltype(I), eltype(A), I, A}(data, itr) -pairs(::Type{NamedTuple}) = Pairs{Symbol, V, NTuple{N, Symbol}, NamedTuple{names, T}} where {V, N, names, T<:NTuple{N, Any}} - ## optional pretty printer: #const NamedTuplePair{N, V, names, T<:NTuple{N, Any}} = Pairs{Symbol, V, NTuple{N, Symbol}, NamedTuple{names, T}} #export NamedTuplePair @@ -310,6 +295,26 @@ macro eval(mod, ex) return Expr(:escape, Expr(:call, GlobalRef(Core, :eval), mod, Expr(:quote, ex))) end +# use `@eval` here to directly form `:new` expressions avoid implicit `convert`s +# in order to achieve better effects inference +@eval struct Pairs{K, V, I, A} <: AbstractDict{K, V} + data::A + itr::I + Pairs{K, V, I, A}(data, itr) where {K, V, I, A} = $(Expr(:new, :(Pairs{K, V, I, A}), :(convert(A, data)), :(convert(I, itr)))) + Pairs{K, V}(data::A, itr::I) where {K, V, I, A} = $(Expr(:new, :(Pairs{K, V, I, A}), :data, :itr)) + Pairs{K}(data::A, itr::I) where {K, I, A} = $(Expr(:new, :(Pairs{K, eltype(A), I, A}), :data, :itr)) + Pairs(data::A, itr::I) where {I, A} = $(Expr(:new, :(Pairs{eltype(I), eltype(A), I, A}), :data, :itr)) +end +pairs(::Type{NamedTuple}) = Pairs{Symbol, V, NTuple{N, Symbol}, NamedTuple{names, T}} where {V, N, names, T<:NTuple{N, Any}} + +""" + Iterators.Pairs(values, keys) <: AbstractDict{eltype(keys), eltype(values)} + +Transforms an indexable container into a Dictionary-view of the same data. +Modifying the key-space of the underlying data may invalidate this object. +""" +Pairs + argtail(x, rest...) = rest """ diff --git a/base/iterators.jl b/base/iterators.jl index db11e57e8b26e2..f2a9f23c9d0942 100644 --- a/base/iterators.jl +++ b/base/iterators.jl @@ -290,8 +290,8 @@ length(v::Pairs) = length(getfield(v, :itr)) axes(v::Pairs) = axes(getfield(v, :itr)) size(v::Pairs) = size(getfield(v, :itr)) -@propagate_inbounds function _pairs_elt(p::Pairs{K, V}, idx) where {K, V} - return Pair{K, V}(idx, getfield(p, :data)[idx]) +Base.@eval @propagate_inbounds function _pairs_elt(p::Pairs{K, V}, idx) where {K, V} + return $(Expr(:new, :(Pair{K, V}), :idx, :(getfield(p, :data)[idx]))) end @propagate_inbounds function iterate(p::Pairs{K, V}, state...) where {K, V} diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 24a32e6501720a..fe6f3f0e81ce3d 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -362,7 +362,7 @@ function merge(a::NamedTuple, itr) merge(a, NamedTuple{(names...,)}((vals...,))) end -keys(nt::NamedTuple{names}) where {names} = names +keys(nt::NamedTuple{names}) where {names} = names::Tuple{Vararg{Symbol}} values(nt::NamedTuple) = Tuple(nt) haskey(nt::NamedTuple, key::Union{Integer, Symbol}) = isdefined(nt, key) get(nt::NamedTuple, key::Union{Integer, Symbol}, default) = isdefined(nt, key) ? getfield(nt, key) : default diff --git a/base/promotion.jl b/base/promotion.jl index 7ee12de15c3db0..fb5c5b83864ae4 100644 --- a/base/promotion.jl +++ b/base/promotion.jl @@ -116,6 +116,7 @@ function typejoin(@nospecialize(a), @nospecialize(b)) if ai === bi || (isa(ai,Type) && isa(bi,Type) && ai <: bi && bi <: ai) aprimary = aprimary{ai} else + aprimary = aprimary::UnionAll # pushfirst!(vars, aprimary.var) _growbeg!(vars, 1) arrayset(false, vars, aprimary.var, 1) diff --git a/test/namedtuple.jl b/test/namedtuple.jl index 6333cfef3a1709..b2101944d423b1 100644 --- a/test/namedtuple.jl +++ b/test/namedtuple.jl @@ -361,6 +361,7 @@ for f in (Base.merge, Base.structdiff) end @test Core.Compiler.return_type(f, Tuple{NamedTuple, NamedTuple}) == NamedTuple end +@test Core.Compiler.is_foldable(Base.infer_effects(pairs, Tuple{NamedTuple})) # Test that merge/diff preserves nt field types let a = Base.NamedTuple{(:a, :b), Tuple{Any, Any}}((1, 2)), b = Base.NamedTuple{(:b,), Tuple{Float64}}(3) From 1c5fa2b9be772e37cc9a5fc54b69fa6a47cc4527 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Thu, 19 Jan 2023 10:09:31 -0500 Subject: [PATCH 355/387] irinterp: Allow for non-Const first argument (#48326) The first argument isn't special here in any way. The original code here just wanted to avoid calling is_all_const_arg with the first argument included, because that condition was already computed when determining `f`. This code pre-dated semi-concrete eval and is now in the wrong place. The changes on the inlining semi-concrete interpreted opaque closure is also added because now we allow irinterp on opaque closure inference. Co-authored-by: Shuhei Kadowaki --- base/compiler/abstractinterpretation.jl | 4 +-- base/compiler/ssair/inlining.jl | 33 +++++++++++++++++-------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index a38cf9f8778bb0..7761ef1ce6f90e 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -843,8 +843,8 @@ function concrete_eval_eligible(interp::AbstractInterpreter, return nothing end isoverlayed(method_table(interp)) && !is_nonoverlayed(result.effects) && return nothing - if f !== nothing && result.edge !== nothing && is_foldable(result.effects) - if is_all_const_arg(arginfo, #=start=#2) + if result.edge !== nothing && is_foldable(result.effects) + if f !== nothing && is_all_const_arg(arginfo, #=start=#2) return true else return false diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 87210ccae48893..40bd0d932aa070 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -1466,21 +1466,27 @@ function handle_const_prop_result!(cases::Vector{InliningCase}, return true end -function handle_semi_concrete_result!(cases::Vector{InliningCase}, result::SemiConcreteResult, - @nospecialize(info::CallInfo), flag::UInt8, state::InliningState; - allow_abstract::Bool) +function semiconcrete_result_item(result::SemiConcreteResult, + @nospecialize(info::CallInfo), flag::UInt8, state::InliningState) mi = result.mi - spec_types = mi.specTypes - allow_abstract || isdispatchtuple(spec_types) || return false - validate_sparams(mi.sparam_vals) || return false if !state.params.inlining || is_stmt_noinline(flag) et = InliningEdgeTracker(state.et, nothing) - item = compileable_specialization(mi, result.effects, et, info; + return compileable_specialization(mi, result.effects, et, info; compilesig_invokes=state.params.compilesig_invokes) - item === nothing && return false else - item = InliningTodo(mi, result.ir, result.effects) + return InliningTodo(mi, result.ir, result.effects) end +end + +function handle_semi_concrete_result!(cases::Vector{InliningCase}, result::SemiConcreteResult, + @nospecialize(info::CallInfo), flag::UInt8, state::InliningState; + allow_abstract::Bool) + mi = result.mi + spec_types = mi.specTypes + allow_abstract || isdispatchtuple(spec_types) || return false + validate_sparams(mi.sparam_vals) || return false + item = semiconcrete_result_item(result, info, flag, state) + item === nothing && return false push!(cases, InliningCase(spec_types, item)) return true end @@ -1538,7 +1544,14 @@ function handle_opaque_closure_call!(todo::Vector{Pair{Int,Any}}, elseif isa(result, ConcreteResult) item = concrete_result_item(result, info, state) else - item = analyze_method!(info.match, sig.argtypes, info, flag, state; allow_typevars=false) + if isa(result, SemiConcreteResult) + result = inlining_policy(state.interp, result, info, flag, result.mi, sig.argtypes) + end + if isa(result, SemiConcreteResult) + item = semiconcrete_result_item(result, info, flag, state) + else + item = analyze_method!(info.match, sig.argtypes, info, flag, state; allow_typevars=false) + end end handle_single_case!(todo, ir, idx, stmt, item, state.params) return nothing From fb97c8287f0bdaef5b9345dcd4dac6e2201fd09b Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Thu, 19 Jan 2023 04:25:15 +0000 Subject: [PATCH 356/387] Move `libstdc++` path into `LOADER_*_DEP_LIBS` After adding `libstdc++` probing into the Julia loader [0], we originally made the assumption that the `libstdc++` that is shipped with `julia` would always be co-located with `libjulia.so` [1]. This is not the case when building with `USE_SYSTEM_CSL=1`, however, where we sequester system libraries in `usr/lib/julia`, even at build-time. The path to `libstdc++.so` has already been getting altered when moving from build-time to install time via `stringreplace` [2], but after further thought, I decided that it would be better to just use the pre-existing `LOADER_*_DEP_LIBS` mechanism to communicate to the loader what the correct relative path to `libstdc++.so` is. This also allows the single `stringreplace` to update all of our "special" library paths. [0] https://github.com/JuliaLang/julia/pull/46976 [1] https://github.com/JuliaLang/julia/pull/46976/files#diff-8c5c98f26f3f7aac8905a1074c5bec11a57e9b9c7c556791deac5a3b27cc096fR379 [2] https://github.com/JuliaLang/julia/blob/master/Makefile#L430 --- Make.inc | 21 +++++++++++--- Makefile | 6 ---- cli/loader_lib.c | 72 +++++++++++++++++++++++++++++++----------------- 3 files changed, 64 insertions(+), 35 deletions(-) diff --git a/Make.inc b/Make.inc index 24f5964e9dcc0a..a7cd154aa347e4 100644 --- a/Make.inc +++ b/Make.inc @@ -1512,6 +1512,19 @@ LIBGCC_BUILD_DEPLIB := $(call dep_lib_path,$(build_libdir),$(build_shlibdir)/$(L endif LIBGCC_INSTALL_DEPLIB := $(call dep_lib_path,$(libdir),$(private_shlibdir)/$(LIBGCC_NAME)) +# We only bother to define this on Linux, as that's the only platform that does libstdc++ probing +# On all other platforms, the LIBSTDCXX_*_DEPLIB variables will be empty. +ifeq ($(OS),Linux) +LIBSTDCXX_NAME := libstdc++.so.6 +ifeq ($(USE_SYSTEM_CSL),1) +LIBSTDCXX_BUILD_DEPLIB := $(call dep_lib_path,$(build_libdir),$(build_private_shlibdir)/$(LIBSTDCXX_NAME)) +else +LIBSTDCXX_BUILD_DEPLIB := $(call dep_lib_path,$(build_libdir),$(build_shlibdir)/$(LIBSTDCXX_NAME)) +endif +LIBSTDCXX_INSTALL_DEPLIB := $(call dep_lib_path,$(libdir),$(private_shlibdir)/$(LIBSTDCXX_NAME)) +endif + + # USE_SYSTEM_LIBM and USE_SYSTEM_OPENLIBM causes it to get symlinked into build_private_shlibdir ifeq ($(USE_SYSTEM_LIBM),1) LIBM_BUILD_DEPLIB := $(call dep_lib_path,$(build_libdir),$(build_private_shlibdir)/$(LIBMNAME).$(SHLIB_EXT)) @@ -1534,10 +1547,10 @@ LIBM_INSTALL_DEPLIB := $(call dep_lib_path,$(libdir),$(private_shlibdir)/$(LIBMN # That second point will no longer be true for most deps once they are placed within Artifacts directories. # Note that we prefix `libjulia-codegen` and `libjulia-internal` with `@` to signify to the loader that it # should not automatically dlopen() it in its loading loop. -LOADER_BUILD_DEP_LIBS = $(LIBGCC_BUILD_DEPLIB):$(LIBM_BUILD_DEPLIB):@$(LIBJULIAINTERNAL_BUILD_DEPLIB):@$(LIBJULIACODEGEN_BUILD_DEPLIB): -LOADER_DEBUG_BUILD_DEP_LIBS = $(LIBGCC_BUILD_DEPLIB):$(LIBM_BUILD_DEPLIB):@$(LIBJULIAINTERNAL_DEBUG_BUILD_DEPLIB):@$(LIBJULIACODEGEN_DEBUG_BUILD_DEPLIB): -LOADER_INSTALL_DEP_LIBS = $(LIBGCC_INSTALL_DEPLIB):$(LIBM_INSTALL_DEPLIB):@$(LIBJULIAINTERNAL_INSTALL_DEPLIB):@$(LIBJULIACODEGEN_INSTALL_DEPLIB): -LOADER_DEBUG_INSTALL_DEP_LIBS = $(LIBGCC_INSTALL_DEPLIB):$(LIBM_INSTALL_DEPLIB):@$(LIBJULIAINTERNAL_DEBUG_INSTALL_DEPLIB):@$(LIBJULIACODEGEN_DEBUG_INSTALL_DEPLIB): +LOADER_BUILD_DEP_LIBS = $(LIBGCC_BUILD_DEPLIB):$(LIBM_BUILD_DEPLIB):@$(LIBSTDCXX_BUILD_DEPLIB):@$(LIBJULIAINTERNAL_BUILD_DEPLIB):@$(LIBJULIACODEGEN_BUILD_DEPLIB): +LOADER_DEBUG_BUILD_DEP_LIBS = $(LIBGCC_BUILD_DEPLIB):$(LIBM_BUILD_DEPLIB):@$(LIBSTDCXX_BUILD_DEPLIB):@$(LIBJULIAINTERNAL_DEBUG_BUILD_DEPLIB):@$(LIBJULIACODEGEN_DEBUG_BUILD_DEPLIB): +LOADER_INSTALL_DEP_LIBS = $(LIBGCC_INSTALL_DEPLIB):$(LIBM_INSTALL_DEPLIB):@$(LIBSTDCXX_INSTALL_DEPLIB):@$(LIBJULIAINTERNAL_INSTALL_DEPLIB):@$(LIBJULIACODEGEN_INSTALL_DEPLIB): +LOADER_DEBUG_INSTALL_DEP_LIBS = $(LIBGCC_INSTALL_DEPLIB):$(LIBM_INSTALL_DEPLIB):@$(LIBSTDCXX_INSTALL_DEPLIB):@$(LIBJULIAINTERNAL_DEBUG_INSTALL_DEPLIB):@$(LIBJULIACODEGEN_DEBUG_INSTALL_DEPLIB): # Colors for make ifndef VERBOSE diff --git a/Makefile b/Makefile index d41f3e50d9752a..edba3ebad7108e 100644 --- a/Makefile +++ b/Makefile @@ -425,12 +425,6 @@ ifeq ($(OS), Linux) -$(PATCHELF) --set-rpath '$$ORIGIN' $(DESTDIR)$(private_shlibdir)/libLLVM.$(SHLIB_EXT) endif - # Replace libstdc++ path, which is also moving from `lib` to `../lib/julia`. -ifeq ($(OS),Linux) - $(call stringreplace,$(DESTDIR)$(shlibdir)/libjulia.$(JL_MAJOR_MINOR_SHLIB_EXT),\*libstdc++\.so\.6$$,*$(call dep_lib_path,$(shlibdir),$(private_shlibdir)/libstdc++.so.6)) -endif - - ifneq ($(LOADER_BUILD_DEP_LIBS),$(LOADER_INSTALL_DEP_LIBS)) # Next, overwrite relative path to libjulia-internal in our loader if $$(LOADER_BUILD_DEP_LIBS) != $$(LOADER_INSTALL_DEP_LIBS) ifeq ($(JULIA_BUILD_MODE),release) diff --git a/cli/loader_lib.c b/cli/loader_lib.c index 3fed0c794a9009..11448583a79929 100644 --- a/cli/loader_lib.c +++ b/cli/loader_lib.c @@ -366,6 +366,50 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) { void *cxx_handle; + // We keep track of "special" libraries names (ones whose name is prefixed with `@`) + // which are libraries that we want to load in some special, custom way. + // The current list is: + // special_library_names = { + // libstdc++, + // libjulia-internal, + // libjulia-codegen, + // } + int special_idx = 0; + char * special_library_names[3] = {NULL}; + while (1) { + // try to find next colon character; if we can't, break out + char * colon = strchr(curr_dep, ':'); + if (colon == NULL) + break; + + // If this library name starts with `@`, don't open it here (but mark it as special) + if (curr_dep[0] == '@') { + if (special_idx > sizeof(special_library_names)/sizeof(char *)) { + jl_loader_print_stderr("ERROR: Too many special library names specified, check LOADER_BUILD_DEP_LIBS and friends!\n"); + exit(1); + } + special_library_names[special_idx] = curr_dep + 1; + special_idx += 1; + + // Chop the string at the colon so it's a valid-ending-string + *colon = '\0'; + } + + // Skip to next dep + curr_dep = colon + 1; + } + + // Assert that we have exactly the right number of special library names + if (special_idx != sizeof(special_library_names)/sizeof(char *)) { + jl_loader_print_stderr("ERROR: Too few special library names specified, check LOADER_BUILD_DEP_LIBS and friends!\n"); + exit(1); + } + + // Unpack our special library names. This is why ordering of library names matters. + char * bundled_libstdcxx_path = special_library_names[0]; + libjulia_internal = load_library(special_library_names[1], lib_dir, 1); + void *libjulia_codegen = load_library(special_library_names[2], lib_dir, 0); + #if defined(_OS_LINUX_) int do_probe = 1; int done_probe = 0; @@ -391,16 +435,10 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) { } } if (!done_probe) { - const static char bundled_path[256] = "\0*libstdc++.so.6"; - load_library(&bundled_path[2], lib_dir, 1); + load_library(bundled_libstdcxx_path, lib_dir, 1); } #endif - // We keep track of "special" libraries names (ones whose name is prefixed with `@`) - // which are libraries that we want to load in some special, custom way, such as - // `libjulia-internal` or `libjulia-codegen`. - int special_idx = 0; - char * special_library_names[2] = {NULL}; while (1) { // try to find next colon character; if we can't, break out char * colon = strchr(curr_dep, ':'); @@ -410,16 +448,8 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) { // Chop the string at the colon so it's a valid-ending-string *colon = '\0'; - // If this library name starts with `@`, don't open it here (but mark it as special) - if (curr_dep[0] == '@') { - if (special_idx > sizeof(special_library_names)/sizeof(char *)) { - jl_loader_print_stderr("ERROR: Too many special library names specified, check LOADER_BUILD_DEP_LIBS and friends!\n"); - exit(1); - } - special_library_names[special_idx] = curr_dep + 1; - special_idx += 1; - } - else { + // If this library name starts with `@`, don't open it here + if (curr_dep[0] != '@') { load_library(curr_dep, lib_dir, 1); } @@ -427,14 +457,6 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) { curr_dep = colon + 1; } - if (special_idx != sizeof(special_library_names)/sizeof(char *)) { - jl_loader_print_stderr("ERROR: Too few special library names specified, check LOADER_BUILD_DEP_LIBS and friends!\n"); - exit(1); - } - - // Unpack our special library names. This is why ordering of library names matters. - libjulia_internal = load_library(special_library_names[0], lib_dir, 1); - void *libjulia_codegen = load_library(special_library_names[1], lib_dir, 0); const char * const * codegen_func_names; const char *codegen_liberr; if (libjulia_codegen == NULL) { From 4e99860f76e48c58a259ea4da395c89d2a4182eb Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Thu, 19 Jan 2023 18:10:37 +0000 Subject: [PATCH 357/387] Load special libraries in-order The `DEPS_LIBS` RPATH-substitute mechanism contains a list of paths to load, and some of these paths are "special", in that they require more involved loading than simply `load_library()`. These libraries are thereby denoted by a `@` prefixing them. Previously, we made note of these libraries, then loaded them at the end of the loading loop, but with the addition of `libstdc++` it is now important to have the order of the libraries (including special libraries) to be obeyed by the loading loop, so I have inlined special library handling into the loading loop. In the future, we may wish to denote special libraries more explicitly than simply relying on there being exactly three libraries, with the ordering being mapped to `libstdc++`, `libjulia-internal`, and `libjulia-codegen`. --- Make.inc | 48 ++++++++++++++++--- cli/loader_lib.c | 118 ++++++++++++++++++++++++++--------------------- 2 files changed, 108 insertions(+), 58 deletions(-) diff --git a/Make.inc b/Make.inc index a7cd154aa347e4..ddea0733be6a74 100644 --- a/Make.inc +++ b/Make.inc @@ -1465,7 +1465,7 @@ JULIA_SYSIMG_release := $(build_private_libdir)/sys.$(SHLIB_EXT) JULIA_SYSIMG := $(JULIA_SYSIMG_$(JULIA_BUILD_MODE)) define dep_lib_path -$$($(PYTHON) $(call python_cygpath,$(JULIAHOME)/contrib/relative_path.py) $(1) $(2)) +$(shell $(PYTHON) $(call python_cygpath,$(JULIAHOME)/contrib/relative_path.py) $(1) $(2)) endef LIBJULIAINTERNAL_BUILD_DEPLIB := $(call dep_lib_path,$(build_libdir),$(build_shlibdir)/libjulia-internal.$(JL_MAJOR_SHLIB_EXT)) @@ -1538,6 +1538,8 @@ LIBM_INSTALL_DEPLIB := $(call dep_lib_path,$(libdir),$(private_shlibdir)/$(LIBMN # We list: # * libgcc_s, because FreeBSD needs to load ours, not the system one. # * libopenlibm, because Windows has an untrustworthy libm, and we want to use ours more than theirs +# * libstdc++, because while performing `libstdc++` probing we need to +# know the path to the bundled `libstdc++` library. # * libjulia-internal, which must always come second-to-last. # * libjulia-codegen, which must always come last # @@ -1546,11 +1548,45 @@ LIBM_INSTALL_DEPLIB := $(call dep_lib_path,$(libdir),$(private_shlibdir)/$(LIBMN # * install time relative paths are not equal to build time relative paths (../lib vs. ../lib/julia) # That second point will no longer be true for most deps once they are placed within Artifacts directories. # Note that we prefix `libjulia-codegen` and `libjulia-internal` with `@` to signify to the loader that it -# should not automatically dlopen() it in its loading loop. -LOADER_BUILD_DEP_LIBS = $(LIBGCC_BUILD_DEPLIB):$(LIBM_BUILD_DEPLIB):@$(LIBSTDCXX_BUILD_DEPLIB):@$(LIBJULIAINTERNAL_BUILD_DEPLIB):@$(LIBJULIACODEGEN_BUILD_DEPLIB): -LOADER_DEBUG_BUILD_DEP_LIBS = $(LIBGCC_BUILD_DEPLIB):$(LIBM_BUILD_DEPLIB):@$(LIBSTDCXX_BUILD_DEPLIB):@$(LIBJULIAINTERNAL_DEBUG_BUILD_DEPLIB):@$(LIBJULIACODEGEN_DEBUG_BUILD_DEPLIB): -LOADER_INSTALL_DEP_LIBS = $(LIBGCC_INSTALL_DEPLIB):$(LIBM_INSTALL_DEPLIB):@$(LIBSTDCXX_INSTALL_DEPLIB):@$(LIBJULIAINTERNAL_INSTALL_DEPLIB):@$(LIBJULIACODEGEN_INSTALL_DEPLIB): -LOADER_DEBUG_INSTALL_DEP_LIBS = $(LIBGCC_INSTALL_DEPLIB):$(LIBM_INSTALL_DEPLIB):@$(LIBSTDCXX_INSTALL_DEPLIB):@$(LIBJULIAINTERNAL_DEBUG_INSTALL_DEPLIB):@$(LIBJULIACODEGEN_DEBUG_INSTALL_DEPLIB): +# should not automatically dlopen() it in its loading loop, it is "special" and should happen later. +# We do the same for `libstdc++`, and explicitly place it _after_ `libgcc_s`, and `libm` since `libstdc++` +# may depend on those libraries (e.g. when USE_SYSTEM_LIBM=1) + +# Helper function to join a list with colons, then place an extra at the end. +define build_deplibs +$(subst $(SPACE),:,$(strip $(1))): +endef + +LOADER_BUILD_DEP_LIBS = $(call build_deplibs, \ + $(LIBGCC_BUILD_DEPLIB) \ + $(LIBM_BUILD_DEPLIB) \ + @$(LIBSTDCXX_BUILD_DEPLIB) \ + @$(LIBJULIAINTERNAL_BUILD_DEPLIB) \ + @$(LIBJULIACODEGEN_BUILD_DEPLIB) \ +) + +LOADER_DEBUG_BUILD_DEP_LIBS = $(call build_deplibs, \ + $(LIBGCC_BUILD_DEPLIB) \ + $(LIBM_BUILD_DEPLIB) \ + @$(LIBSTDCXX_BUILD_DEPLIB) \ + @$(LIBJULIAINTERNAL_DEBUG_BUILD_DEPLIB) \ + @$(LIBJULIACODEGEN_DEBUG_BUILD_DEPLIB) \ +) + +LOADER_INSTALL_DEP_LIBS = $(call build_deplibs, \ + $(LIBGCC_INSTALL_DEPLIB) \ + $(LIBM_INSTALL_DEPLIB) \ + @$(LIBSTDCXX_INSTALL_DEPLIB) \ + @$(LIBJULIAINTERNAL_INSTALL_DEPLIB) \ + @$(LIBJULIACODEGEN_INSTALL_DEPLIB) \ +) +LOADER_DEBUG_INSTALL_DEP_LIBS = $(call build_deplibs, \ + $(LIBGCC_INSTALL_DEPLIB) \ + $(LIBM_INSTALL_DEPLIB) \ + @$(LIBSTDCXX_INSTALL_DEPLIB) \ + @$(LIBJULIAINTERNAL_DEBUG_INSTALL_DEPLIB) \ + @$(LIBJULIACODEGEN_DEBUG_INSTALL_DEPLIB) \ +) # Colors for make ifndef VERBOSE diff --git a/cli/loader_lib.c b/cli/loader_lib.c index 11448583a79929..1fd28674bc8eb9 100644 --- a/cli/loader_lib.c +++ b/cli/loader_lib.c @@ -350,7 +350,8 @@ static char *libstdcxxprobe(void) } #endif -void * libjulia_internal = NULL; +void *libjulia_internal = NULL; +void *libjulia_codegen = NULL; __attribute__((constructor)) void jl_load_libjulia_internal(void) { // Only initialize this once if (libjulia_internal != NULL) { @@ -364,18 +365,14 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) { int deps_len = strlen(&dep_libs[1]); char *curr_dep = &dep_libs[1]; - void *cxx_handle; - // We keep track of "special" libraries names (ones whose name is prefixed with `@`) // which are libraries that we want to load in some special, custom way. // The current list is: - // special_library_names = { - // libstdc++, - // libjulia-internal, - // libjulia-codegen, - // } + // libstdc++ + // libjulia-internal + // libjulia-codegen + const int NUM_SPECIAL_LIBRARIES = 3; int special_idx = 0; - char * special_library_names[3] = {NULL}; while (1) { // try to find next colon character; if we can't, break out char * colon = strchr(curr_dep, ':'); @@ -384,15 +381,11 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) { // If this library name starts with `@`, don't open it here (but mark it as special) if (curr_dep[0] == '@') { - if (special_idx > sizeof(special_library_names)/sizeof(char *)) { + special_idx += 1; + if (special_idx > NUM_SPECIAL_LIBRARIES) { jl_loader_print_stderr("ERROR: Too many special library names specified, check LOADER_BUILD_DEP_LIBS and friends!\n"); exit(1); } - special_library_names[special_idx] = curr_dep + 1; - special_idx += 1; - - // Chop the string at the colon so it's a valid-ending-string - *colon = '\0'; } // Skip to next dep @@ -400,45 +393,18 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) { } // Assert that we have exactly the right number of special library names - if (special_idx != sizeof(special_library_names)/sizeof(char *)) { + if (special_idx != NUM_SPECIAL_LIBRARIES) { jl_loader_print_stderr("ERROR: Too few special library names specified, check LOADER_BUILD_DEP_LIBS and friends!\n"); exit(1); } - // Unpack our special library names. This is why ordering of library names matters. - char * bundled_libstdcxx_path = special_library_names[0]; - libjulia_internal = load_library(special_library_names[1], lib_dir, 1); - void *libjulia_codegen = load_library(special_library_names[2], lib_dir, 0); - -#if defined(_OS_LINUX_) - int do_probe = 1; - int done_probe = 0; - char *probevar = getenv("JULIA_PROBE_LIBSTDCXX"); - if (probevar) { - if (strcmp(probevar, "1") == 0 || strcmp(probevar, "yes") == 0) - do_probe = 1; - else if (strcmp(probevar, "0") == 0 || strcmp(probevar, "no") == 0) - do_probe = 0; - } - if (do_probe) { - char *cxxpath = libstdcxxprobe(); - if (cxxpath) { - cxx_handle = dlopen(cxxpath, RTLD_LAZY); - char *dlr = dlerror(); - if (dlr) { - jl_loader_print_stderr("ERROR: Unable to dlopen(cxxpath) in parent!\n"); - jl_loader_print_stderr3("Message: ", dlr, "\n"); - exit(1); - } - free(cxxpath); - done_probe = 1; - } - } - if (!done_probe) { - load_library(bundled_libstdcxx_path, lib_dir, 1); - } -#endif - + // Now that we've asserted that we have the right number of special + // libraries, actually run a loop over the deps loading them in-order. + // If it's a special library, we do slightly different things, especially + // for libstdc++, where we actually probe for a system libstdc++ and + // load that if it's newer. + special_idx = 0; + curr_dep = &dep_libs[1]; while (1) { // try to find next colon character; if we can't, break out char * colon = strchr(curr_dep, ':'); @@ -448,8 +414,56 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) { // Chop the string at the colon so it's a valid-ending-string *colon = '\0'; - // If this library name starts with `@`, don't open it here - if (curr_dep[0] != '@') { + // If this library name starts with `@`, it's a special library + // and requires special handling: + if (curr_dep[0] == '@') { + // Skip the `@` for future function calls. + curr_dep += 1; + + // First special library to be loaded is `libstdc++`; perform probing here. + if (special_idx == 0) { +#if defined(_OS_LINUX_) + int do_probe = 1; + int probe_successful = 0; + + // Check to see if the user has disabled libstdc++ probing + char *probevar = getenv("JULIA_PROBE_LIBSTDCXX"); + if (probevar) { + if (strcmp(probevar, "1") == 0 || strcmp(probevar, "yes") == 0) + do_probe = 1; + else if (strcmp(probevar, "0") == 0 || strcmp(probevar, "no") == 0) + do_probe = 0; + } + if (do_probe) { + char *cxxpath = libstdcxxprobe(); + if (cxxpath) { + void *cxx_handle = dlopen(cxxpath, RTLD_LAZY); + const char *dlr = dlerror(); + if (dlr) { + jl_loader_print_stderr("ERROR: Unable to dlopen(cxxpath) in parent!\n"); + jl_loader_print_stderr3("Message: ", dlr, "\n"); + exit(1); + } + free(cxxpath); + probe_successful = 1; + } + } + // If the probe rejected the system libstdc++ (or didn't find one!) + // just load our bundled libstdc++ as identified by curr_dep; + if (!probe_successful) { + load_library(curr_dep, lib_dir, 1); + } +#endif + } else if (special_idx == 1) { + // This special library is `libjulia-internal` + libjulia_internal = load_library(curr_dep, lib_dir, 1); + } else if (special_idx == 2) { + // This special library is `libjulia-codegen` + libjulia_codegen = load_library(curr_dep, lib_dir, 0); + } + special_idx++; + } else { + // Otherwise, just load it as "normal" load_library(curr_dep, lib_dir, 1); } From 87b8896fa36d37e988867f404a9b1caebdc98dda Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 19 Jan 2023 13:52:46 -0600 Subject: [PATCH 358/387] remove precompile mutation step from staticdata (#48309) Make sure things are properly ordered here, so that when serializing, nothing is mutating the system at the same time. Fix #48047 --- src/gf.c | 18 +++++-- src/precompile_utils.c | 4 ++ src/staticdata.c | 112 ++++++++++++++++++++++------------------- src/staticdata_utils.c | 60 +++++----------------- 4 files changed, 90 insertions(+), 104 deletions(-) diff --git a/src/gf.c b/src/gf.c index 57a62fe8846b13..10d8d888b08da8 100644 --- a/src/gf.c +++ b/src/gf.c @@ -281,11 +281,9 @@ jl_code_info_t *jl_type_infer(jl_method_instance_t *mi, size_t world, int force) return NULL; jl_task_t *ct = jl_current_task; if (ct->reentrant_inference == (uint16_t)-1) { - // TODO: We should avoid attempting to re-inter inference here at all - // and turn on this warning, but that requires further refactoring - // of the precompile code, so for now just catch that case here. - //jl_printf(JL_STDERR, "ERROR: Attempted to enter inference while writing out image."); - return NULL; + // We must avoid attempting to re-enter inference here + assert(0 && "attempted to enter inference while writing out image"); + abort(); } if (ct->reentrant_inference > 2) return NULL; @@ -487,6 +485,7 @@ int foreach_mtable_in_module( // this is the original/primary binding for the type (name/wrapper) jl_methtable_t *mt = tn->mt; if (mt != NULL && (jl_value_t*)mt != jl_nothing && mt != jl_type_type_mt && mt != jl_nonfunction_mt) { + assert(mt->module == m); if (!visit(mt, env)) return 0; } @@ -500,6 +499,15 @@ int foreach_mtable_in_module( return 0; } } + else if (jl_is_mtable(v)) { + jl_methtable_t *mt = (jl_methtable_t*)v; + if (mt->module == m && mt->name == name) { + // this is probably an external method table here, so let's + // assume so as there is no way to precisely distinguish them + if (!visit(mt, env)) + return 0; + } + } } } table = jl_atomic_load_relaxed(&m->bindings); diff --git a/src/precompile_utils.c b/src/precompile_utils.c index f251d00f76cfd5..9f52ce911a92f7 100644 --- a/src/precompile_utils.c +++ b/src/precompile_utils.c @@ -132,6 +132,8 @@ static int compile_all_collect__(jl_typemap_entry_t *ml, void *env) { jl_array_t *allmeths = (jl_array_t*)env; jl_method_t *m = ml->func.method; + if (m->external_mt) + return 1; if (m->source) { // method has a non-generated definition; can be compiled generically jl_array_ptr_1d_push(allmeths, (jl_value_t*)m); @@ -204,6 +206,8 @@ static int precompile_enq_specialization_(jl_method_instance_t *mi, void *closur static int precompile_enq_all_specializations__(jl_typemap_entry_t *def, void *closure) { jl_method_t *m = def->func.method; + if (m->external_mt) + return 1; if ((m->name == jl_symbol("__init__") || m->ccallable) && jl_is_dispatch_tupletype(m->sig)) { // ensure `__init__()` and @ccallables get strongly-hinted, specialized, and compiled jl_method_instance_t *mi = jl_specializations_get_linfo(m, m->sig, jl_emptysvec); diff --git a/src/staticdata.c b/src/staticdata.c index bdbe73f857f261..91c0b04bac5d06 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -2170,9 +2170,8 @@ JL_DLLEXPORT jl_value_t *jl_as_global_root(jl_value_t *val JL_MAYBE_UNROOTED) } static void jl_prepare_serialization_data(jl_array_t *mod_array, jl_array_t *newly_inferred, uint64_t worklist_key, - /* outputs */ jl_array_t **extext_methods, - jl_array_t **new_specializations, jl_array_t **method_roots_list, - jl_array_t **ext_targets, jl_array_t **edges) + /* outputs */ jl_array_t **extext_methods, jl_array_t **new_specializations, + jl_array_t **method_roots_list, jl_array_t **ext_targets, jl_array_t **edges) { // extext_methods: [method1, ...], worklist-owned "extending external" methods added to functions owned by modules outside the worklist // ext_targets: [invokesig1, callee1, matches1, ...] non-worklist callees of worklist-owned methods @@ -2180,24 +2179,19 @@ static void jl_prepare_serialization_data(jl_array_t *mod_array, jl_array_t *new // `invoke` dispatch: invokesig is signature, callee is MethodInstance // abstract call: callee is signature // edges: [caller1, ext_targets_indexes1, ...] for worklist-owned methods calling external methods - assert(edges_map == NULL); - JL_GC_PUSH1(&edges_map); - // Save the inferred code from newly inferred, external methods htable_new(&external_mis, 0); // we need external_mis until after `jl_collect_edges` finishes + // Save the inferred code from newly inferred, external methods *new_specializations = queue_external_cis(newly_inferred); - // Collect the new method roots - htable_t methods_with_newspecs; - htable_new(&methods_with_newspecs, 0); - jl_collect_methods(&methods_with_newspecs, *new_specializations); - *method_roots_list = jl_alloc_vec_any(0); - jl_collect_new_roots(*method_roots_list, &methods_with_newspecs, worklist_key); - htable_free(&methods_with_newspecs); // Collect method extensions and edges data - edges_map = jl_alloc_vec_any(0); + JL_GC_PUSH1(&edges_map); + if (edges) + edges_map = jl_alloc_vec_any(0); *extext_methods = jl_alloc_vec_any(0); + jl_collect_methtable_from_mod(jl_type_type_mt, *extext_methods); + jl_collect_methtable_from_mod(jl_nonfunction_mt, *extext_methods); size_t i, len = jl_array_len(mod_array); for (i = 0; i < len; i++) { jl_module_t *m = (jl_module_t*)jl_array_ptr_ref(mod_array, i); @@ -2205,15 +2199,23 @@ static void jl_prepare_serialization_data(jl_array_t *mod_array, jl_array_t *new if (m->parent == m) // some toplevel modules (really just Base) aren't actually jl_collect_extext_methods_from_mod(*extext_methods, m); } - jl_collect_methtable_from_mod(*extext_methods, jl_type_type_mt); - jl_collect_missing_backedges(jl_type_type_mt); - jl_collect_methtable_from_mod(*extext_methods, jl_nonfunction_mt); - jl_collect_missing_backedges(jl_nonfunction_mt); - // jl_collect_extext_methods_from_mod and jl_collect_missing_backedges also accumulate data in callers_with_edges. - // Process this to extract `edges` and `ext_targets`. - *ext_targets = jl_alloc_vec_any(0); - *edges = jl_alloc_vec_any(0); - jl_collect_edges(*edges, *ext_targets); + + if (edges) { + jl_collect_missing_backedges(jl_type_type_mt); + jl_collect_missing_backedges(jl_nonfunction_mt); + // jl_collect_extext_methods_from_mod and jl_collect_missing_backedges also accumulate data in callers_with_edges. + // Process this to extract `edges` and `ext_targets`. + *ext_targets = jl_alloc_vec_any(0); + *edges = jl_alloc_vec_any(0); + *method_roots_list = jl_alloc_vec_any(0); + // Collect the new method roots + htable_t methods_with_newspecs; + htable_new(&methods_with_newspecs, 0); + jl_collect_methods(&methods_with_newspecs, *new_specializations); + jl_collect_new_roots(*method_roots_list, &methods_with_newspecs, worklist_key); + htable_free(&methods_with_newspecs); + jl_collect_edges(*edges, *ext_targets); + } htable_free(&external_mis); assert(edges_map == NULL); // jl_collect_edges clears this when done @@ -2501,9 +2503,8 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_gc_enable(en); } -static void jl_write_header_for_incremental(ios_t *f, jl_array_t *worklist, jl_array_t **mod_array, jl_array_t **udeps, int64_t *srctextpos, int64_t *checksumpos) +static void jl_write_header_for_incremental(ios_t *f, jl_array_t *worklist, jl_array_t *mod_array, jl_array_t **udeps, int64_t *srctextpos, int64_t *checksumpos) { - *mod_array = jl_get_loaded_modules(); // __toplevel__ modules loaded in this session (from Base.loaded_modules_array) assert(jl_precompile_toplevel_module == NULL); jl_precompile_toplevel_module = (jl_module_t*)jl_array_ptr_ref(worklist, jl_array_len(worklist)-1); @@ -2519,7 +2520,7 @@ static void jl_write_header_for_incremental(ios_t *f, jl_array_t *worklist, jl_a // write description of requirements for loading (modules that must be pre-loaded if initialization is to succeed) // this can return errors during deserialize, // best to keep it early (before any actual initialization) - write_mod_list(f, *mod_array); + write_mod_list(f, mod_array); } JL_DLLEXPORT void jl_create_system_image(void **_native_data, jl_array_t *worklist, bool_t emit_split, @@ -2550,49 +2551,58 @@ JL_DLLEXPORT void jl_create_system_image(void **_native_data, jl_array_t *workli int64_t checksumpos_ff = 0; int64_t datastartpos = 0; JL_GC_PUSH6(&mod_array, &extext_methods, &new_specializations, &method_roots_list, &ext_targets, &edges); - if (worklist) { - jl_write_header_for_incremental(f, worklist, &mod_array, udeps, srctextpos, &checksumpos); - if (emit_split) { - checksumpos_ff = write_header(ff, 1); - write_uint8(ff, jl_cache_flags()); - write_mod_list(ff, mod_array); - } else { - checksumpos_ff = checksumpos; - } - { - // make sure we don't run any Julia code concurrently after this point - jl_gc_enable_finalizers(ct, 0); - assert(ct->reentrant_inference == 0); - ct->reentrant_inference = (uint16_t)-1; - } - jl_prepare_serialization_data(mod_array, newly_inferred, jl_worklist_key(worklist), &extext_methods, &new_specializations, &method_roots_list, &ext_targets, &edges); + if (worklist) { + mod_array = jl_get_loaded_modules(); // __toplevel__ modules loaded in this session (from Base.loaded_modules_array) // Generate _native_data` if (jl_options.outputo || jl_options.outputbc || jl_options.outputunoptbc || jl_options.outputasm) { + jl_prepare_serialization_data(mod_array, newly_inferred, jl_worklist_key(worklist), + &extext_methods, &new_specializations, NULL, NULL, NULL); jl_precompile_toplevel_module = (jl_module_t*)jl_array_ptr_ref(worklist, jl_array_len(worklist)-1); *_native_data = jl_precompile_worklist(worklist, extext_methods, new_specializations); jl_precompile_toplevel_module = NULL; + extext_methods = NULL; + new_specializations = NULL; } + jl_write_header_for_incremental(f, worklist, mod_array, udeps, srctextpos, &checksumpos); + if (emit_split) { + checksumpos_ff = write_header(ff, 1); + write_uint8(ff, jl_cache_flags()); + write_mod_list(ff, mod_array); + } + else { + checksumpos_ff = checksumpos; + } + } + else { + *_native_data = jl_precompile(jl_options.compile_enabled == JL_OPTIONS_COMPILE_ALL); + } + // Make sure we don't run any Julia code concurrently after this point + // since it will invalidate our serialization preparations + jl_gc_enable_finalizers(ct, 0); + assert(ct->reentrant_inference == 0); + ct->reentrant_inference = (uint16_t)-1; + if (worklist) { + jl_prepare_serialization_data(mod_array, newly_inferred, jl_worklist_key(worklist), + &extext_methods, &new_specializations, &method_roots_list, &ext_targets, &edges); if (!emit_split) { write_int32(f, 0); // No clone_targets write_padding(f, LLT_ALIGN(ios_pos(f), JL_CACHE_BYTE_ALIGNMENT) - ios_pos(f)); - } else { + } + else { write_padding(ff, LLT_ALIGN(ios_pos(ff), JL_CACHE_BYTE_ALIGNMENT) - ios_pos(ff)); } datastartpos = ios_pos(ff); - } else { - *_native_data = jl_precompile(jl_options.compile_enabled == JL_OPTIONS_COMPILE_ALL); } native_functions = *_native_data; jl_save_system_image_to_stream(ff, worklist, extext_methods, new_specializations, method_roots_list, ext_targets, edges); native_functions = NULL; - if (worklist) { - // Re-enable running julia code for postoutput hooks, atexit, etc. - jl_gc_enable_finalizers(ct, 1); - ct->reentrant_inference = 0; - jl_precompile_toplevel_module = NULL; - } + // make sure we don't run any Julia code concurrently before this point + // Re-enable running julia code for postoutput hooks, atexit, etc. + jl_gc_enable_finalizers(ct, 1); + ct->reentrant_inference = 0; + jl_precompile_toplevel_module = NULL; if (worklist) { // Go back and update the checksum in the header diff --git a/src/staticdata_utils.c b/src/staticdata_utils.c index d63d2baefaefa1..fc109836a03c0f 100644 --- a/src/staticdata_utils.c +++ b/src/staticdata_utils.c @@ -273,12 +273,12 @@ static void jl_collect_methods(htable_t *mset, jl_array_t *new_specializations) } } -static void jl_collect_new_roots(jl_array_t *roots, htable_t *mset, uint64_t key) +static void jl_collect_new_roots(jl_array_t *roots, const htable_t *mset, uint64_t key) { size_t i, sz = mset->size; int nwithkey; jl_method_t *m; - void **table = mset->table; + void *const *table = mset->table; jl_array_t *newroots = NULL; JL_GC_PUSH1(&newroots); for (i = 0; i < sz; i += 2) { @@ -369,6 +369,8 @@ static int jl_collect_methcache_from_mod(jl_typemap_entry_t *ml, void *closure) if (s && !jl_object_in_image((jl_value_t*)m->module)) { jl_array_ptr_1d_push(s, (jl_value_t*)m); } + if (edges_map == NULL) + return 1; jl_svec_t *specializations = m->specializations; size_t i, l = jl_svec_len(specializations); for (i = 0; i < l; i++) { @@ -379,9 +381,14 @@ static int jl_collect_methcache_from_mod(jl_typemap_entry_t *ml, void *closure) return 1; } -static void jl_collect_methtable_from_mod(jl_array_t *s, jl_methtable_t *mt) +static int jl_collect_methtable_from_mod(jl_methtable_t *mt, void *env) { - jl_typemap_visitor(mt->defs, jl_collect_methcache_from_mod, (void*)s); + if (!jl_object_in_image((jl_value_t*)mt)) + env = NULL; // do not collect any methods from here + jl_typemap_visitor(jl_atomic_load_relaxed(&mt->defs), jl_collect_methcache_from_mod, env); + if (env && edges_map) + jl_collect_missing_backedges(mt); + return 1; } // Collect methods of external functions defined by modules in the worklist @@ -389,50 +396,7 @@ static void jl_collect_methtable_from_mod(jl_array_t *s, jl_methtable_t *mt) // Also collect relevant backedges static void jl_collect_extext_methods_from_mod(jl_array_t *s, jl_module_t *m) { - if (s && !jl_object_in_image((jl_value_t*)m)) - s = NULL; // do not collect any methods - jl_svec_t *table = jl_atomic_load_relaxed(&m->bindings); - for (size_t i = 0; i < jl_svec_len(table); i++) { - jl_binding_t *b = (jl_binding_t*)jl_svec_ref(table, i); - if ((void*)b == jl_nothing) - break; - jl_sym_t *name = b->globalref->name; - if (b->owner == b && b->value && b->constp) { - jl_value_t *bv = jl_unwrap_unionall(b->value); - if (jl_is_datatype(bv)) { - jl_typename_t *tn = ((jl_datatype_t*)bv)->name; - if (tn->module == m && tn->name == name && tn->wrapper == b->value) { - jl_methtable_t *mt = tn->mt; - if (mt != NULL && - (jl_value_t*)mt != jl_nothing && - (mt != jl_type_type_mt && mt != jl_nonfunction_mt)) { - assert(mt->module == tn->module); - jl_collect_methtable_from_mod(s, mt); - if (s) - jl_collect_missing_backedges(mt); - } - } - } - else if (jl_is_module(b->value)) { - jl_module_t *child = (jl_module_t*)b->value; - if (child != m && child->parent == m && child->name == name) { - // this is the original/primary binding for the submodule - jl_collect_extext_methods_from_mod(s, (jl_module_t*)b->value); - } - } - else if (jl_is_mtable(b->value)) { - jl_methtable_t *mt = (jl_methtable_t*)b->value; - if (mt->module == m && mt->name == name) { - // this is probably an external method table, so let's assume so - // as there is no way to precisely distinguish them, - // and the rest of this serializer does not bother - // to handle any method tables specially - jl_collect_methtable_from_mod(s, (jl_methtable_t*)bv); - } - } - } - table = jl_atomic_load_relaxed(&m->bindings); - } + foreach_mtable_in_module(m, jl_collect_methtable_from_mod, s); } static void jl_record_edges(jl_method_instance_t *caller, arraylist_t *wq, jl_array_t *edges) From b029fbfe8a24a3cc381b2adbfaf2520a3e43cca4 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 19 Jan 2023 13:55:48 -0600 Subject: [PATCH 359/387] Wait for all tasks before writing precompile files (#46571) When the program goes to write out a precompile file, we would like for the process to first reach a point where it is not still running background tasks and work. This ensures that the precompile file is in a consistent state, and isn't forgetting or delaying intended work. In the future, we may want to add an `atexit` hook (after the other `atexit` hooks) which optionally calls this function for regular code too, probably under programmatic control and/or command line argument control for the user to decide. And we would need to decide how to close stdin first, so it doesn't continue to keep the program alive. Add uv_ref and uv_unref internal hooks for this. You probably really don't want to call these (as they may stop you from getting events on these objects also), but very specific internal things will need them for this to work. Also (mostly unrelated) rewrite a Profile test to conform to best-practices. Previously, the loop was expecting to observe the Profile test printing even though nothing kept it alive (there was no reads on stdin). We fix the design of that test, but also include a patch inside `jl_process_events` to ensure the loop is alive and will handle events, to avoid breaking anyone else who was relying on this pattern. To assist package authors fix errors, we automatically print a note if this new functionality is causing delays. They then need to ensure they are calling close explicitly (not relying solely on finalizers), when appropriate, and are cleaning up other resources (or calling the new `Base.uv_unref`) also. Fix #45170 --- base/libuv.jl | 3 ++ contrib/generate_precompile.jl | 1 + src/gc.c | 3 ++ src/jl_uv.c | 67 ++++++++++++++++++++++++++++++++- src/julia.h | 1 + src/partr.c | 36 +++++++++++++++++- src/precompile.c | 2 + stdlib/Profile/src/Profile.jl | 4 +- stdlib/Profile/test/runtests.jl | 35 ++++++++++------- test/precompile.jl | 11 ++++++ 10 files changed, 146 insertions(+), 17 deletions(-) diff --git a/base/libuv.jl b/base/libuv.jl index 64b228c6500e75..24a04f5bcad789 100644 --- a/base/libuv.jl +++ b/base/libuv.jl @@ -103,6 +103,9 @@ uv_error(prefix::AbstractString, c::Integer) = c < 0 ? throw(_UVError(prefix, c) eventloop() = ccall(:jl_global_event_loop, Ptr{Cvoid}, ()) +uv_unref(h::Ptr{Cvoid}) = ccall(:uv_unref, Cvoid, (Ptr{Cvoid},), h) +uv_ref(h::Ptr{Cvoid}) = ccall(:uv_ref, Cvoid, (Ptr{Cvoid},), h) + function process_events() return ccall(:jl_process_events, Int32, ()) end diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index cd357eaf556d89..0db045661b9ba1 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -316,6 +316,7 @@ generate_precompile_statements() = try # Make sure `ansi_enablecursor` is printe current = current == 4 ? 1 : current + 1 end end + close(t) end # Collect statements from running the script diff --git a/src/gc.c b/src/gc.c index 5653730b89abc1..fc2a4041910f57 100644 --- a/src/gc.c +++ b/src/gc.c @@ -2958,6 +2958,7 @@ static void jl_gc_queue_thread_local(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp } extern jl_value_t *cmpswap_names JL_GLOBALLY_ROOTED; +extern jl_task_t *wait_empty JL_GLOBALLY_ROOTED; // mark the initial root set static void mark_roots(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp) @@ -2996,6 +2997,8 @@ static void mark_roots(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp) gc_mark_queue_obj(gc_cache, sp, jl_emptytuple_type); if (cmpswap_names != NULL) gc_mark_queue_obj(gc_cache, sp, cmpswap_names); + if (wait_empty != NULL) + gc_mark_queue_obj(gc_cache, sp, wait_empty); gc_mark_queue_obj(gc_cache, sp, jl_global_roots_table); } diff --git a/src/jl_uv.c b/src/jl_uv.c index 97a6602c19d7a4..b34c3f51c6766c 100644 --- a/src/jl_uv.c +++ b/src/jl_uv.c @@ -30,6 +30,68 @@ extern "C" { #endif static uv_async_t signal_async; +static uv_timer_t wait_empty_worker; + +static void walk_print_cb(uv_handle_t *h, void *arg) +{ + if (!uv_is_active(h) || !uv_has_ref(h)) + return; + const char *type = uv_handle_type_name(h->type); + if (!type) + type = ""; + uv_os_fd_t fd; + if (h->type == UV_PROCESS) + fd = uv_process_get_pid((uv_process_t*)h); + else if (uv_fileno(h, &fd)) + fd = (uv_os_fd_t)-1; + const char *pad = " "; // 16 spaces + int npad = fd == -1 ? 0 : snprintf(NULL, 0, "%zd", (size_t)fd); + if (npad < 0) + npad = 0; + npad += strlen(type); + pad += npad < strlen(pad) ? npad : strlen(pad); + if (fd == -1) + jl_safe_printf(" %s %s@%p->%p\n", type, pad, (void*)h, (void*)h->data); + else + jl_safe_printf(" %s[%zd] %s@%p->%p\n", type, (size_t)fd, pad, (void*)h, (void*)h->data); +} + +static void wait_empty_func(uv_timer_t *t) +{ + // make sure this is hidden now, since we would auto-unref it later + uv_unref((uv_handle_t*)&signal_async); + if (!uv_loop_alive(t->loop)) + return; + jl_safe_printf("\n[pid %zd] waiting for IO to finish:\n" + " TYPE[FD/PID] @UV_HANDLE_T->DATA\n", + (size_t)uv_os_getpid()); + uv_walk(jl_io_loop, walk_print_cb, NULL); + jl_gc_collect(JL_GC_FULL); +} + +void jl_wait_empty_begin(void) +{ + JL_UV_LOCK(); + if (wait_empty_worker.type != UV_TIMER && jl_io_loop) { + // try to purge anything that is just waiting for cleanup + jl_io_loop->stop_flag = 0; + uv_run(jl_io_loop, UV_RUN_NOWAIT); + uv_timer_init(jl_io_loop, &wait_empty_worker); + uv_update_time(jl_io_loop); + uv_timer_start(&wait_empty_worker, wait_empty_func, 10, 15000); + uv_unref((uv_handle_t*)&wait_empty_worker); + } + JL_UV_UNLOCK(); +} + +void jl_wait_empty_end(void) +{ + JL_UV_LOCK(); + uv_close((uv_handle_t*)&wait_empty_worker, NULL); + JL_UV_UNLOCK(); +} + + static void jl_signal_async_cb(uv_async_t *hdl) { @@ -49,6 +111,7 @@ jl_mutex_t jl_uv_mutex; void jl_init_uv(void) { uv_async_init(jl_io_loop, &signal_async, jl_signal_async_cb); + uv_unref((uv_handle_t*)&signal_async); JL_MUTEX_INIT(&jl_uv_mutex); // a file-scope initializer can be used instead } @@ -110,7 +173,7 @@ static void jl_uv_closeHandle(uv_handle_t *handle) ct->world_age = last_age; return; } - if (handle == (uv_handle_t*)&signal_async) + if (handle == (uv_handle_t*)&signal_async || handle == (uv_handle_t*)&wait_empty_worker) return; free(handle); } @@ -213,7 +276,9 @@ JL_DLLEXPORT int jl_process_events(void) if (jl_atomic_load_relaxed(&jl_uv_n_waiters) == 0 && jl_mutex_trylock(&jl_uv_mutex)) { JL_PROBE_RT_START_PROCESS_EVENTS(ct); loop->stop_flag = 0; + uv_ref((uv_handle_t*)&signal_async); // force the loop alive int r = uv_run(loop, UV_RUN_NOWAIT); + uv_unref((uv_handle_t*)&signal_async); JL_PROBE_RT_FINISH_PROCESS_EVENTS(ct); JL_UV_UNLOCK(); return r; diff --git a/src/julia.h b/src/julia.h index a32859364eb4d6..03efa773d026c5 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1760,6 +1760,7 @@ JL_DLLEXPORT void jl_init_with_image(const char *julia_bindir, JL_DLLEXPORT const char *jl_get_default_sysimg_path(void); JL_DLLEXPORT int jl_is_initialized(void); JL_DLLEXPORT void jl_atexit_hook(int status); +JL_DLLEXPORT void jl_task_wait_empty(void); JL_DLLEXPORT void jl_postoutput_hook(void); JL_DLLEXPORT void JL_NORETURN jl_exit(int status); JL_DLLEXPORT void JL_NORETURN jl_raise(int signo); diff --git a/src/partr.c b/src/partr.c index 3840164d6f7347..3e71cb6627e07a 100644 --- a/src/partr.c +++ b/src/partr.c @@ -281,6 +281,27 @@ static int check_empty(jl_value_t *checkempty) return jl_apply_generic(checkempty, NULL, 0) == jl_true; } +jl_task_t *wait_empty JL_GLOBALLY_ROOTED; +void jl_wait_empty_begin(void); +void jl_wait_empty_end(void); + +void jl_task_wait_empty(void) +{ + jl_task_t *ct = jl_current_task; + if (jl_atomic_load_relaxed(&ct->tid) == 0 && jl_base_module) { + jl_wait_empty_begin(); + jl_value_t *f = jl_get_global(jl_base_module, jl_symbol("wait")); + wait_empty = ct; + size_t lastage = ct->world_age; + ct->world_age = jl_atomic_load_acquire(&jl_world_counter); + if (f) + jl_apply_generic(f, NULL, 0); + ct->world_age = lastage; + wait_empty = NULL; + jl_wait_empty_end(); + } +} + static int may_sleep(jl_ptls_t ptls) JL_NOTSAFEPOINT { // sleep_check_state is only transitioned from not_sleeping to sleeping @@ -312,7 +333,7 @@ JL_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q, jl_cpu_pause(); jl_ptls_t ptls = ct->ptls; - if (sleep_check_after_threshold(&start_cycles) || (!jl_atomic_load_relaxed(&_threadedregion) && ptls->tid == 0)) { + if (sleep_check_after_threshold(&start_cycles) || (ptls->tid == 0 && (!jl_atomic_load_relaxed(&_threadedregion) || wait_empty))) { // acquire sleep-check lock jl_atomic_store_relaxed(&ptls->sleep_check_state, sleeping); jl_fence(); // [^store_buffering_1] @@ -409,6 +430,14 @@ JL_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q, int8_t gc_state = jl_gc_safe_enter(ptls); uv_mutex_lock(&ptls->sleep_lock); while (may_sleep(ptls)) { + if (ptls->tid == 0 && wait_empty) { + task = wait_empty; + if (jl_atomic_load_relaxed(&ptls->sleep_check_state) != not_sleeping) { + jl_atomic_store_relaxed(&ptls->sleep_check_state, not_sleeping); // let other threads know they don't need to wake us + JL_PROBE_RT_SLEEP_CHECK_TASK_WAKE(ptls); + } + break; + } uv_cond_wait(&ptls->wake_signal, &ptls->sleep_lock); // TODO: help with gc work here, if applicable } @@ -417,6 +446,11 @@ JL_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q, JULIA_DEBUG_SLEEPWAKE( ptls->sleep_leave = cycleclock() ); jl_gc_safe_leave(ptls, gc_state); // contains jl_gc_safepoint start_cycles = 0; + if (task) { + assert(task == wait_empty); + wait_empty = NULL; + return task; + } } else { // maybe check the kernel for new messages too diff --git a/src/precompile.c b/src/precompile.c index bfc123cf3fda83..ff5edb9a745f30 100644 --- a/src/precompile.c +++ b/src/precompile.c @@ -75,6 +75,8 @@ JL_DLLEXPORT void jl_write_compiler_output(void) return; } + jl_task_wait_empty(); + if (!jl_module_init_order) { jl_printf(JL_STDERR, "WARNING: --output requested, but no modules defined during run\n"); return; diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index 59686a50a760c4..46d56a879cf6db 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -156,7 +156,9 @@ function __init__() # used, if not manually initialized before that. @static if !Sys.iswindows() # triggering a profile via signals is not implemented on windows - PROFILE_PRINT_COND[] = Base.AsyncCondition() + cond = Base.AsyncCondition() + Base.uv_unref(cond.handle) + PROFILE_PRINT_COND[] = cond ccall(:jl_set_peek_cond, Cvoid, (Ptr{Cvoid},), PROFILE_PRINT_COND[].handle) errormonitor(Threads.@spawn(profile_printing_listener())) end diff --git a/stdlib/Profile/test/runtests.jl b/stdlib/Profile/test/runtests.jl index 2a39640d215ed3..8c2031a9797f27 100644 --- a/stdlib/Profile/test/runtests.jl +++ b/stdlib/Profile/test/runtests.jl @@ -198,41 +198,48 @@ if Sys.isbsd() || Sys.islinux() @testset "SIGINFO/SIGUSR1 profile triggering" begin let cmd = Base.julia_cmd() script = """ - x = rand(1000, 1000) - println(stderr, "started") - while true - x * x - yield() - end + print(stderr, "started\n") + eof(stdin) + close(t) """ iob = Base.BufferStream() - p = run(pipeline(`$cmd -e $script`, stderr = iob, stdout = devnull), wait = false) + notify_exit = Base.PipeEndpoint() + p = run(pipeline(`$cmd -e $script`, stdin=notify_exit, stderr=iob, stdout=devnull), wait=false) t = Timer(120) do t # should be under 10 seconds, so give it 2 minutes then report failure println("KILLING BY PROFILE TEST WATCHDOG\n") kill(p, Base.SIGTERM) sleep(10) kill(p, Base.SIGKILL) - close(iob) + close(p) end try - s = readuntil(iob, "started", keep = true) + s = readuntil(iob, "started", keep=true) @assert occursin("started", s) @assert process_running(p) - for _ in 1:2 - sleep(2.5) + for i in 1:2 + i > 1 && sleep(5) if Sys.isbsd() kill(p, 29) # SIGINFO elseif Sys.islinux() kill(p, 10) # SIGUSR1 end - s = readuntil(iob, "Overhead ╎", keep = true) + s = readuntil(iob, "Overhead ╎", keep=true) @test process_running(p) + readavailable(iob) @test occursin("Overhead ╎", s) end - finally - kill(p, Base.SIGKILL) + close(notify_exit) # notify test finished + s = read(iob, String) # consume test output + wait(p) # wait for test completion + close(t) + catch + close(notify_exit) + errs = read(iob, String) # consume test output + isempty(errs) || println("CHILD STDERR after test failure: ", errs) + wait(p) # wait for test completion close(t) + rethrow() end end end diff --git a/test/precompile.jl b/test/precompile.jl index a553dd5b34a317..0febfecb78b698 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -261,6 +261,10 @@ precompile_test_harness(false) do dir # check that @ccallable works from precompiled modules Base.@ccallable Cint f35014(x::Cint) = x+Cint(1) + + # check that Tasks work from serialized state + ch1 = Channel(x -> nothing) + ch2 = Channel(x -> (push!(x, 2); nothing), Inf) end """) # Issue #12623 @@ -310,6 +314,13 @@ precompile_test_harness(false) do dir @test Foo.layout2 == Any[Ptr{Int8}(0), Ptr{Int16}(0), Ptr{Int32}(-1)] @test typeof.(Foo.layout2) == [Ptr{Int8}, Ptr{Int16}, Ptr{Int32}] @test Foo.layout3 == ["ab", "cd", "ef", "gh", "ij"] + + @test !isopen(Foo.ch1) + @test !isopen(Foo.ch2) + @test !isready(Foo.ch1) + @test isready(Foo.ch2) + @test take!(Foo.ch2) === 2 + @test !isready(Foo.ch2) end @eval begin function ccallable_test() From 4cab76ce52850f67c8f89a95a8c55c463c933ea3 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Thu, 19 Jan 2023 21:38:42 +0100 Subject: [PATCH 360/387] allow extensions to be loaded from non top level env (#48352) --- base/loading.jl | 1 - test/loading.jl | 43 ++++++++++++++++++++++++++++--------------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/base/loading.jl b/base/loading.jl index 02bbd6fcff7f84..0d26912a12e7b2 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -1112,7 +1112,6 @@ function insert_extension_triggers(pkg::PkgId) pkg.uuid === nothing && return for env in load_path() insert_extension_triggers(env, pkg) - break # For now, only insert triggers for packages in the first load_path. end end diff --git a/test/loading.jl b/test/loading.jl index a7e48d6b02160b..f98f08103c9d74 100644 --- a/test/loading.jl +++ b/test/loading.jl @@ -1000,24 +1000,37 @@ end try tmp = mktempdir() push!(empty!(DEPOT_PATH), joinpath(tmp, "depot")) - proj = joinpath(@__DIR__, "project", "Extensions", "HasDepWithExtensions.jl") - for compile in (`--compiled-modules=no`, ``, ``) # Once when requiring precomilation, once where it is already precompiled - cmd = `$(Base.julia_cmd()) $compile --project=$proj --startup-file=no -e ' - begin - using HasExtensions - # Base.get_extension(HasExtensions, :Extension) === nothing || error("unexpectedly got an extension") - HasExtensions.ext_loaded && error("ext_loaded set") - using HasDepWithExtensions - # Base.get_extension(HasExtensions, :Extension).extvar == 1 || error("extvar in Extension not set") - HasExtensions.ext_loaded || error("ext_loaded not set") - HasExtensions.ext_folder_loaded && error("ext_folder_loaded set") - HasDepWithExtensions.do_something() || error("do_something errored") - using ExtDep2 - HasExtensions.ext_folder_loaded || error("ext_folder_loaded not set") + function gen_extension_cmd(compile) + ```$(Base.julia_cmd()) $compile --startup-file=no -e ' + begin + using HasExtensions + # Base.get_extension(HasExtensions, :Extension) === nothing || error("unexpectedly got an extension") + HasExtensions.ext_loaded && error("ext_loaded set") + using HasDepWithExtensions + # Base.get_extension(HasExtensions, :Extension).extvar == 1 || error("extvar in Extension not set") + HasExtensions.ext_loaded || error("ext_loaded not set") + HasExtensions.ext_folder_loaded && error("ext_folder_loaded set") + HasDepWithExtensions.do_something() || error("do_something errored") + using ExtDep2 + HasExtensions.ext_folder_loaded || error("ext_folder_loaded not set") end - '` + ' + ``` + end + + for compile in (`--compiled-modules=no`, ``, ``) # Once when requiring precomilation, once where it is already precompiled + cmd = gen_extension_cmd(compile) + withenv("JULIA_LOAD_PATH" => proj) do + @test success(cmd) + end + end + + # 48351 + sep = Sys.iswindows() ? ';' : ':' + withenv("JULIA_LOAD_PATH" => join([mktempdir(), proj], sep)) do + cmd = gen_extension_cmd(``) @test success(cmd) end finally From 6d8f54ad37f4db1b1873846ca2cde22f06d5dff0 Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Thu, 19 Jan 2023 17:37:23 -0500 Subject: [PATCH 361/387] doc: fix manual description of regex escapes (#48239) String interpolation happens before the regex is processed, so there is no guarantee that interpolated text will be properly escaped. --- doc/src/manual/strings.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/src/manual/strings.md b/doc/src/manual/strings.md index 32f4c29af93110..f73093e9c0b919 100644 --- a/doc/src/manual/strings.md +++ b/doc/src/manual/strings.md @@ -1038,8 +1038,11 @@ true ``` Note the use of the `\Q...\E` escape sequence. All characters between the `\Q` and the `\E` -are interpreted as literal characters (after string interpolation). This escape sequence can -be useful when interpolating, possibly malicious, user input. +are interpreted as literal characters. This is convenient for matching characters that +would otherwise be regex metacharacters. However, caution is needed when using this feature +together with string interpolation, since the interpolated string might itself contain +the `\E` sequence, unexpectedly terminating literal matching. User inputs need to be sanitized +before inclusion in a regex. ## [Byte Array Literals](@id man-byte-array-literals) From b83e22530341ee1225d9c04e51306d6748c3ab33 Mon Sep 17 00:00:00 2001 From: Andy Dienes Date: Thu, 19 Jan 2023 21:55:22 -0500 Subject: [PATCH 362/387] add docs for broadcast_shape --- base/broadcast.jl | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/base/broadcast.jl b/base/broadcast.jl index f7e7a3725c9ca3..801991f09184f9 100644 --- a/base/broadcast.jl +++ b/base/broadcast.jl @@ -512,6 +512,19 @@ julia> Broadcast.combine_axes(1, 1, 1) @inline combine_axes(A, B) = broadcast_shape(axes(A), axes(B)) combine_axes(A) = axes(A) +""" + broadcast_shape(As...) -> Tuple + +Determine the result axes for broadcasting across all axes (size Tuples) in `As`. + +```jldoctest +julia> Broadcast.broadcast_shape((1,2), (2,1)) +(2, 2) + +julia> Broadcast.broadcast_shape((1,), (1,5), (4,5,3)) +(4, 5, 3) +``` +""" # shape (i.e., tuple-of-indices) inputs broadcast_shape(shape::Tuple) = shape broadcast_shape(shape::Tuple, shape1::Tuple, shapes::Tuple...) = broadcast_shape(_bcs(shape, shape1), shapes...) From 57101cfddb67dc5285610ffb038d0683a44f1d1f Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Fri, 20 Jan 2023 04:04:59 -0500 Subject: [PATCH 363/387] compact: Don't try to kill the same edge twice (#48343) In IR like: ``` goto if not true goto if not true ``` our implementation of `kill_edge!` goes through recursively to kill all newly unreachable blocks. However, it was still attempting to schedule the newly unreachable block. Then, when it got to the next GotoIfNot, it wsa again attempting to kill the same edge, which would fail, because the edge had already been removed from the CFG. Fix that by telling IncrementalCompact not to attempt scheduling any blocks that were newly discovered to be dead. --- base/compiler/ssair/ir.jl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index 1dfcc7365ac1ea..1d6be5a2b09d84 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -1189,6 +1189,12 @@ function kill_edge!(compact::IncrementalCompact, active_bb::Int, from::Int, to:: compact.result[stmt][:inst] = nothing end compact.result[last(stmts)][:inst] = ReturnNode() + else + # Tell compaction to not schedule this block. A value of -2 here + # indicates that the block is not to be scheduled, but there should + # still be an (unreachable) BB inserted into the final IR to avoid + # disturbing the BB numbering. + compact.bb_rename_succ[to] = -2 end else # Remove this edge from all phi nodes in `to` block @@ -1531,7 +1537,7 @@ function iterate_compact(compact::IncrementalCompact) resize!(compact, old_result_idx) end bb = compact.ir.cfg.blocks[active_bb] - if compact.cfg_transforms_enabled && active_bb > 1 && active_bb <= length(compact.bb_rename_succ) && compact.bb_rename_succ[active_bb] == -1 + if compact.cfg_transforms_enabled && active_bb > 1 && active_bb <= length(compact.bb_rename_succ) && compact.bb_rename_succ[active_bb] <= -1 # Dead block, so kill the entire block. compact.idx = last(bb.stmts) # Pop any remaining insertion nodes From ba69cbaa8e04657b6f4dceced76696575053f28b Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Fri, 20 Jan 2023 12:53:36 -0300 Subject: [PATCH 364/387] Add .clangd file for clang language server (#48214) --- .clangd | 2 ++ .gitignore | 1 + 2 files changed, 3 insertions(+) create mode 100644 .clangd diff --git a/.clangd b/.clangd new file mode 100644 index 00000000000000..534bd8fa45fb93 --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Add: [-I., -I.., -Iflisp, -Isupport, -I../support, -I../usr/include, -I../../usr/include, -Wall,] diff --git a/.gitignore b/.gitignore index 836a35781cd6fa..0368b7d19efa00 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ .idea/* .vscode/* *.heapsnapshot +.cache # Buildkite: Ignore the entire .buildkite directory /.buildkite From 1481a899c911e830b94540c1cfdb87ea11dab5a5 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Fri, 20 Jan 2023 12:46:23 -0500 Subject: [PATCH 365/387] Take "needs" labels seriously, block merge if labels are assigned (#48359) --- .github/workflows/LabelCheck.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/LabelCheck.yml diff --git a/.github/workflows/LabelCheck.yml b/.github/workflows/LabelCheck.yml new file mode 100644 index 00000000000000..194b0c92065c92 --- /dev/null +++ b/.github/workflows/LabelCheck.yml @@ -0,0 +1,19 @@ +name: Labels + +permissions: + contents: read +on: + pull_request: + types: [labeled, unlabeled, opened, reopened, edited, synchronize] +jobs: + enforce-labels: + name: Check for blocking labels + runs-on: ubuntu-latest + timeout-minutes: 2 + steps: + - uses: yogevbd/enforce-label-action@2.2.2 + with: + # REQUIRED_LABELS_ANY: "bug,enhancement,skip-changelog" + # REQUIRED_LABELS_ANY_DESCRIPTION: "Select at least one label ['bug','enhancement','skip-changelog']" + BANNED_LABELS: "needs docs,needs compat annotation,needs more info,needs nanosoldier run,needs news,needs pkgeval,needs tests,DO NOT MERGE" + BANNED_LABELS_DESCRIPTION: "A PR should not be merged with `needs *` or `DO NOT MERGE` labels" From 62c1137e38c6838045140531a85abd9f9d8de1ee Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Fri, 20 Jan 2023 22:16:10 -0600 Subject: [PATCH 366/387] mac: add missing dSYM files (#48355) Fixes #47744 --- Makefile | 7 ++++++- cli/Makefile | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index edba3ebad7108e..c080f0d144cf6c 100644 --- a/Makefile +++ b/Makefile @@ -284,13 +284,18 @@ ifneq ($(DARWIN_FRAMEWORK),1) ifeq ($(OS),Darwin) ifeq ($(JULIA_BUILD_MODE),release) -cp -a $(build_libdir)/libjulia.*.dSYM $(DESTDIR)$(libdir) + -cp -a $(build_libdir)/libjulia-internal.*.dSYM $(DESTDIR)$(private_libdir) + -cp -a $(build_libdir)/libjulia-codegen.*.dSYM $(DESTDIR)$(private_libdir) -cp -a $(build_private_libdir)/sys.dylib.dSYM $(DESTDIR)$(private_libdir) else ifeq ($(JULIA_BUILD_MODE),debug) -cp -a $(build_libdir)/libjulia-debug.*.dSYM $(DESTDIR)$(libdir) + -cp -a $(build_libdir)/libjulia-internal-debug.*.dSYM $(DESTDIR)$(private_libdir) + -cp -a $(build_libdir)/libjulia-codegen-debug.*.dSYM $(DESTDIR)$(private_libdir) -cp -a $(build_private_libdir)/sys-debug.dylib.dSYM $(DESTDIR)$(private_libdir) endif endif +# Copy over shared library file for libjulia.* for suffix in $(JL_TARGETS) ; do \ for lib in $(build_libdir)/lib$${suffix}.*$(SHLIB_EXT)*; do \ if [ "$${lib##*.}" != "dSYM" ]; then \ @@ -299,7 +304,7 @@ endif done \ done else - # libjulia in Darwin framework has special location and name +# libjulia in Darwin framework has special location and name ifeq ($(JULIA_BUILD_MODE),release) $(INSTALL_M) $(build_libdir)/libjulia.$(SOMAJOR).$(SOMINOR).dylib $(DESTDIR)$(prefix)/$(framework_dylib) @$(DSYMUTIL) -o $(DESTDIR)$(prefix)/$(framework_resources)/$(FRAMEWORK_NAME).dSYM $(DESTDIR)$(prefix)/$(framework_dylib) diff --git a/cli/Makefile b/cli/Makefile index d360a98412cf61..5c2de8f2ae6d00 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -113,6 +113,7 @@ $(build_shlibdir)/libjulia.$(JL_MAJOR_MINOR_SHLIB_EXT): $(LIB_OBJS) $(SRCDIR)/li @$(call PRINT_LINK, $(CC) $(call IMPLIB_FLAGS,$@.tmp) $(LOADER_CFLAGS) -DLIBRARY_EXPORTS -shared $(SHIPFLAGS) $(LIB_OBJS) -o $@ \ $(JLIBLDFLAGS) $(LOADER_LDFLAGS) $(call SONAME_FLAGS,libjulia.$(JL_MAJOR_SHLIB_EXT))) @$(INSTALL_NAME_CMD)libjulia.$(JL_MAJOR_SHLIB_EXT) $@ + @$(DSYMUTIL) $@ ifeq ($(OS), WINNT) @# Note that if the objcopy command starts getting too long, we can use `@file` to read @# command-line options from `file` instead. @@ -123,6 +124,7 @@ $(build_shlibdir)/libjulia-debug.$(JL_MAJOR_MINOR_SHLIB_EXT): $(LIB_DOBJS) $(SRC @$(call PRINT_LINK, $(CC) $(call IMPLIB_FLAGS,$@.tmp) $(LOADER_CFLAGS) -DLIBRARY_EXPORTS -shared $(DEBUGFLAGS) $(LIB_DOBJS) -o $@ \ $(JLIBLDFLAGS) $(LOADER_LDFLAGS) $(call SONAME_FLAGS,libjulia-debug.$(JL_MAJOR_SHLIB_EXT))) @$(INSTALL_NAME_CMD)libjulia-debug.$(JL_MAJOR_SHLIB_EXT) $@ + @$(DSYMUTIL) $@ ifeq ($(OS), WINNT) @$(call PRINT_ANALYZE, $(OBJCOPY) $(build_libdir)/$(notdir $@).tmp.a $(STRIP_EXPORTED_FUNCS) $(build_libdir)/$(notdir $@).a && rm $(build_libdir)/$(notdir $@).tmp.a) endif From 26dd3185f6e0894a1e43a747ba445dbb47dfcf16 Mon Sep 17 00:00:00 2001 From: CodeReclaimers Date: Sun, 22 Jan 2023 00:09:02 -0500 Subject: [PATCH 367/387] devdocs: correct some function names and locations (#48289) --- doc/src/devdocs/eval.md | 2 +- doc/src/devdocs/init.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/src/devdocs/eval.md b/doc/src/devdocs/eval.md index 1aea5161ad23a6..6a153c67daa137 100644 --- a/doc/src/devdocs/eval.md +++ b/doc/src/devdocs/eval.md @@ -25,7 +25,7 @@ The 10,000 foot view of the whole process is as follows: 1. The user starts `julia`. 2. The C function `main()` from `cli/loader_exe.c` gets called. This function processes the command line arguments, filling in the `jl_options` struct and setting the variable `ARGS`. It then initializes - Julia (by calling [`julia_init` in `task.c`](https://github.com/JuliaLang/julia/blob/master/src/task.c), + Julia (by calling [`julia_init` in `init.c`](https://github.com/JuliaLang/julia/blob/master/src/init.c), which may load a previously compiled [sysimg](@ref dev-sysimg)). Finally, it passes off control to Julia by calling [`Base._start()`](https://github.com/JuliaLang/julia/blob/master/base/client.jl). 3. When `_start()` takes over control, the subsequent sequence of commands depends on the command diff --git a/doc/src/devdocs/init.md b/doc/src/devdocs/init.md index 348e69f673f806..981a19b13fcf35 100644 --- a/doc/src/devdocs/init.md +++ b/doc/src/devdocs/init.md @@ -6,9 +6,9 @@ How does the Julia runtime execute `julia -e 'println("Hello World!")'` ? Execution starts at [`main()` in `cli/loader_exe.c`](https://github.com/JuliaLang/julia/blob/master/cli/loader_exe.c), which calls `jl_load_repl()` in [`cli/loader_lib.c`](https://github.com/JuliaLang/julia/blob/master/cli/loader_lib.c) -which loads a few libraries, eventually calling [`repl_entrypoint()` in `src/jlapi.c`](https://github.com/JuliaLang/julia/blob/master/src/jlapi.c). +which loads a few libraries, eventually calling [`jl_repl_entrypoint()` in `src/jlapi.c`](https://github.com/JuliaLang/julia/blob/master/src/jlapi.c). -`repl_entrypoint()` calls [`libsupport_init()`](https://github.com/JuliaLang/julia/blob/master/src/support/libsupportinit.c) +`jl_repl_entrypoint()` calls [`libsupport_init()`](https://github.com/JuliaLang/julia/blob/master/src/support/libsupportinit.c) to set the C library locale and to initialize the "ios" library (see [`ios_init_stdstreams()`](https://github.com/JuliaLang/julia/blob/master/src/support/ios.c) and [Legacy `ios.c` library](@ref Legacy-ios.c-library)). @@ -20,7 +20,7 @@ or early initialization. Other options are handled later by [`exec_options()` in ## `julia_init()` -[`julia_init()` in `task.c`](https://github.com/JuliaLang/julia/blob/master/src/task.c) is called +[`julia_init()` in `init.c`](https://github.com/JuliaLang/julia/blob/master/src/init.c) is called by `main()` and calls [`_julia_init()` in `init.c`](https://github.com/JuliaLang/julia/blob/master/src/init.c). `_julia_init()` begins by calling `libsupport_init()` again (it does nothing the second time). From 13fe91c72df32db1bfa9ba5fbae4bbec5e6ffe8f Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Sat, 21 Jan 2023 23:11:06 -0600 Subject: [PATCH 368/387] Update base/broadcast.jl --- base/broadcast.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/base/broadcast.jl b/base/broadcast.jl index 801991f09184f9..8465b50ddc3333 100644 --- a/base/broadcast.jl +++ b/base/broadcast.jl @@ -525,6 +525,7 @@ julia> Broadcast.broadcast_shape((1,), (1,5), (4,5,3)) (4, 5, 3) ``` """ +function broadcast_shape end # shape (i.e., tuple-of-indices) inputs broadcast_shape(shape::Tuple) = shape broadcast_shape(shape::Tuple, shape1::Tuple, shapes::Tuple...) = broadcast_shape(_bcs(shape, shape1), shapes...) From b1aa5a84c17b5d204ced63db0062c83d4e7113b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Selva?= <82722340+jpselva@users.noreply.github.com> Date: Sun, 22 Jan 2023 02:13:31 -0300 Subject: [PATCH 369/387] [Dates] Fix nonexistent method OverflowError() in steprange_last (#48242) --- stdlib/Dates/src/ranges.jl | 2 +- stdlib/Dates/test/ranges.jl | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/stdlib/Dates/src/ranges.jl b/stdlib/Dates/src/ranges.jl index d089e25dac9ce9..c4299c7b02be51 100644 --- a/stdlib/Dates/src/ranges.jl +++ b/stdlib/Dates/src/ranges.jl @@ -43,7 +43,7 @@ function Base.steprange_last(start::T, step, stop) where T<:TimeType else diff = stop - start if (diff > zero(diff)) != (stop > start) - throw(OverflowError()) + throw(OverflowError("Difference between stop and start overflowed")) end remain = stop - (start + step * len(start, stop, step)) last = stop - remain diff --git a/stdlib/Dates/test/ranges.jl b/stdlib/Dates/test/ranges.jl index 1593502535a07b..d4339dcde51d45 100644 --- a/stdlib/Dates/test/ranges.jl +++ b/stdlib/Dates/test/ranges.jl @@ -603,4 +603,12 @@ end @test length(r) == 366 end +# Issue #48209 +@testset "steprange_last overflow" begin + epoch = Date(Date(1) - Day(1)) + dmax = epoch + Day(typemax(fieldtype(Day, :value))) + dmin = epoch + Day(typemin(fieldtype(Day, :value))) + @test_throws OverflowError StepRange(dmin, Day(1), dmax) +end + end # RangesTest module From 1ecf73efa85506d688aaa2e6bf4fdc43c050c543 Mon Sep 17 00:00:00 2001 From: Miles Cranmer Date: Sun, 22 Jan 2023 00:16:25 -0500 Subject: [PATCH 370/387] build: turn off debug config default for TSAN (#48210) Fixes #48031 --- contrib/tsan/Make.user.tsan | 3 --- 1 file changed, 3 deletions(-) diff --git a/contrib/tsan/Make.user.tsan b/contrib/tsan/Make.user.tsan index 01c9874a85182b..b192c36e4cfee8 100644 --- a/contrib/tsan/Make.user.tsan +++ b/contrib/tsan/Make.user.tsan @@ -11,6 +11,3 @@ USE_BINARYBUILDER_LLVM=1 override SANITIZE=1 override SANITIZE_THREAD=1 - -# default to a debug build for better line number reporting -override JULIA_BUILD_MODE=debug From cb74765c40ed8ada3ef686a6f2c6463bf2969ed5 Mon Sep 17 00:00:00 2001 From: Michael Abbott <32575566+mcabbott@users.noreply.github.com> Date: Sat, 21 Jan 2023 21:20:11 -0800 Subject: [PATCH 371/387] add `@fastmath maximum` (#48153) --- base/fastmath.jl | 30 +++++++++++++++++++++++++++++- test/fastmath.jl | 25 +++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/base/fastmath.jl b/base/fastmath.jl index 5f905b86554f44..a969bcaaa6ae0a 100644 --- a/base/fastmath.jl +++ b/base/fastmath.jl @@ -84,7 +84,12 @@ const fast_op = :sinh => :sinh_fast, :sqrt => :sqrt_fast, :tan => :tan_fast, - :tanh => :tanh_fast) + :tanh => :tanh_fast, + # reductions + :maximum => :maximum_fast, + :minimum => :minimum_fast, + :maximum! => :maximum!_fast, + :minimum! => :minimum!_fast) const rewrite_op = Dict(:+= => :+, @@ -366,4 +371,27 @@ for f in (:^, :atan, :hypot, :log) end end +# Reductions + +maximum_fast(a; kw...) = Base.reduce(max_fast, a; kw...) +minimum_fast(a; kw...) = Base.reduce(min_fast, a; kw...) + +maximum_fast(f, a; kw...) = Base.mapreduce(f, max_fast, a; kw...) +minimum_fast(f, a; kw...) = Base.mapreduce(f, min_fast, a; kw...) + +Base.reducedim_init(f, ::typeof(max_fast), A::AbstractArray, region) = + Base.reducedim_init(f, max, A::AbstractArray, region) +Base.reducedim_init(f, ::typeof(min_fast), A::AbstractArray, region) = + Base.reducedim_init(f, min, A::AbstractArray, region) + +maximum!_fast(r::AbstractArray, A::AbstractArray; kw...) = + maximum!_fast(identity, r, A; kw...) +minimum!_fast(r::AbstractArray, A::AbstractArray; kw...) = + minimum!_fast(identity, r, A; kw...) + +maximum!_fast(f::Function, r::AbstractArray, A::AbstractArray; init::Bool=true) = + Base.mapreducedim!(f, max_fast, Base.initarray!(r, f, max, init, A), A) +minimum!_fast(f::Function, r::AbstractArray, A::AbstractArray; init::Bool=true) = + Base.mapreducedim!(f, min_fast, Base.initarray!(r, f, min, init, A), A) + end diff --git a/test/fastmath.jl b/test/fastmath.jl index e93fb93330b4f5..8755e727db0927 100644 --- a/test/fastmath.jl +++ b/test/fastmath.jl @@ -207,6 +207,31 @@ end @test @fastmath(cis(third)) ≈ cis(third) end end + +@testset "reductions" begin + @test @fastmath(maximum([1,2,3])) == 3 + @test @fastmath(minimum([1,2,3])) == 1 + @test @fastmath(maximum(abs2, [1,2,3+0im])) == 9 + @test @fastmath(minimum(sqrt, [1,2,3])) == 1 + @test @fastmath(maximum(Float32[4 5 6; 7 8 9])) == 9.0f0 + @test @fastmath(minimum(Float32[4 5 6; 7 8 9])) == 4.0f0 + + @test @fastmath(maximum(Float32[4 5 6; 7 8 9]; dims=1)) == Float32[7.0 8.0 9.0] + @test @fastmath(minimum(Float32[4 5 6; 7 8 9]; dims=2)) == Float32[4.0; 7.0;;] + @test @fastmath(maximum(abs, [4+im -5 6-im; -7 8 -9]; dims=1)) == [7.0 8.0 9.0] + @test @fastmath(minimum(cbrt, [4 -5 6; -7 8 -9]; dims=2)) == cbrt.([-5; -9;;]) + + x = randn(3,4,5) + x1 = sum(x; dims=1) + x23 = sum(x; dims=(2,3)) + @test @fastmath(maximum!(x1, x)) ≈ maximum(x; dims=1) + @test x1 ≈ maximum(x; dims=1) + @test @fastmath(minimum!(x23, x)) ≈ minimum(x; dims=(2,3)) + @test x23 ≈ minimum(x; dims=(2,3)) + @test @fastmath(maximum!(abs, x23, x .+ im)) ≈ maximum(abs, x .+ im; dims=(2,3)) + @test @fastmath(minimum!(abs2, x1, x .+ im)) ≈ minimum(abs2, x .+ im; dims=1) +end + @testset "issue #10544" begin a = fill(1.,2,2) b = fill(1.,2,2) From b47837e6723df28bc5a5abe6c17ae4bba762ee09 Mon Sep 17 00:00:00 2001 From: Steve Kelly Date: Sun, 22 Jan 2023 00:22:33 -0500 Subject: [PATCH 372/387] docs: mark `locations` in `Libdl.find_library` as optional (#48193) These location argument is optional, but were not marked as such in the `find_library` doc strings. --- base/libdl.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/libdl.jl b/base/libdl.jl index 4f29260bb24f82..fdf6103d1800b6 100644 --- a/base/libdl.jl +++ b/base/libdl.jl @@ -185,7 +185,7 @@ function dlclose(p::Nothing) end """ - find_library(names, locations) + find_library(names [, locations]) Searches for the first library in `names` in the paths in the `locations` list, `DL_LOAD_PATH`, or system library paths (in that order) which can successfully be dlopen'd. From 583aaa3b56eeb41576401bd7e0190a328ab76615 Mon Sep 17 00:00:00 2001 From: Moritz Schauer Date: Sun, 22 Jan 2023 06:30:48 +0100 Subject: [PATCH 373/387] Amend docstring about compat entries of weakdeps (#48206) --- doc/src/manual/code-loading.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/src/manual/code-loading.md b/doc/src/manual/code-loading.md index 6ff91282641618..d3806ee180f32e 100644 --- a/doc/src/manual/code-loading.md +++ b/doc/src/manual/code-loading.md @@ -351,11 +351,15 @@ Since the primary environment is typically the environment of a project you're w ### [Package Extensions](@id man-extensions) -A package "extension" is a module that is automatically loaded when a specified set of other packages (its "extension dependencies") are loaded in the current Julia session. The extension dependencies of an extension are a subset of those packages listed under the `[weakdeps]` section of a Project file. Extensions are defined under the `[extensions]` section in the project file: +A package "extension" is a module that is automatically loaded when a specified set of other packages (its "extension dependencies") are loaded in the current Julia session. Extensions are defined under the `[extensions]` section in the project file. The extension dependencies of an extension are a subset of those packages listed under the `[weakdeps]` section of the project file. Those packages can have compat entries like other packages. ```toml name = "MyPackage" +[compat] +ExtDep = "1.0" +OtherExtDep = "1.0" + [weakdeps] ExtDep = "c9a23..." # uuid OtherExtDep = "862e..." # uuid From 29498798ebda5d37bc8f5300a3d0eeaa5ee0a44c Mon Sep 17 00:00:00 2001 From: t-bltg Date: Sun, 22 Jan 2023 06:32:45 +0100 Subject: [PATCH 374/387] make `dlopen`ing `libssp` non fatal (#48333) --- .../src/CompilerSupportLibraries_jll.jl | 5 +---- stdlib/CompilerSupportLibraries_jll/test/runtests.jl | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl b/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl index 0068414f942e87..097659e01b3962 100644 --- a/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl +++ b/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl @@ -21,8 +21,6 @@ libstdcxx_handle = C_NULL libstdcxx_path = "" libgomp_handle = C_NULL libgomp_path = "" -libssp_handle = C_NULL -libssp_path = "" if Sys.iswindows() if arch(HostPlatform()) == "x86_64" @@ -64,8 +62,7 @@ function __init__() global libgomp_handle = dlopen(libgomp) global libgomp_path = dlpath(libgomp_handle) @static if libc(HostPlatform()) != "musl" - global libssp_handle = dlopen(libssp) - global libssp_path = dlpath(libssp_handle) + dlopen(libssp; throw_error = false) end global artifact_dir = dirname(Sys.BINDIR) LIBPATH[] = dirname(libgcc_s_path) diff --git a/stdlib/CompilerSupportLibraries_jll/test/runtests.jl b/stdlib/CompilerSupportLibraries_jll/test/runtests.jl index 840a36bdd8d495..85cf132c3a5bd5 100644 --- a/stdlib/CompilerSupportLibraries_jll/test/runtests.jl +++ b/stdlib/CompilerSupportLibraries_jll/test/runtests.jl @@ -1,13 +1,10 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -using Test, CompilerSupportLibraries_jll, Base.BinaryPlatforms +using Test, CompilerSupportLibraries_jll @testset "CompilerSupportLibraries_jll" begin @test isfile(CompilerSupportLibraries_jll.libgcc_s_path) @test isfile(CompilerSupportLibraries_jll.libgfortran_path) @test isfile(CompilerSupportLibraries_jll.libstdcxx_path) @test isfile(CompilerSupportLibraries_jll.libgomp_path) - if libc(HostPlatform()) != "musl" - @test isfile(CompilerSupportLibraries_jll.libssp_path) - end end From 7630606f16a3c35c54ed151d6cc31fcdb4e6def2 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sun, 22 Jan 2023 00:37:04 -0500 Subject: [PATCH 375/387] Further tweaks to LimitedAccuracy lattice operations (#48126) As discussed in #48045: 1. Switch the order of `causes` inclusion to make wider elements have fewer causes. 2. Fix two typos 3. Restore property that equal ulimited types will be preserved (though with potentially updated `causes` lists). --- base/compiler/typelattice.jl | 2 +- base/compiler/typelimits.jl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/base/compiler/typelattice.jl b/base/compiler/typelattice.jl index 831219e70a29cb..33d4d37e9c936b 100644 --- a/base/compiler/typelattice.jl +++ b/base/compiler/typelattice.jl @@ -415,7 +415,7 @@ function ⊑(lattice::InferenceLattice, @nospecialize(a), @nospecialize(b)) # a and b's unlimited types are equal. isa(a, LimitedAccuracy) || return false # b is limited, so ε smaller - return a.causes ⊆ b.causes + return b.causes ⊆ a.causes end function ⊑(lattice::OptimizerLattice, @nospecialize(a), @nospecialize(b)) diff --git a/base/compiler/typelimits.jl b/base/compiler/typelimits.jl index c09cf4e5d0f91b..ed9db007bdbc8d 100644 --- a/base/compiler/typelimits.jl +++ b/base/compiler/typelimits.jl @@ -422,8 +422,7 @@ end # Approximated types are lattice equal. Merge causes. if suba && subb - causes = merge_causes(causesa, causesb) - issimplertype(lattice, typeb, typea) && return LimitedAccuracy(typeb, causesb) + return LimitedAccuracy(typeb, merge_causes(causesa, causesb)) elseif suba issimplertype(lattice, typeb, typea) && return LimitedAccuracy(typeb, causesb) causes = causesb @@ -453,6 +452,7 @@ end subb = ⊑(lattice, typeb, typea) end + suba && subb && return LimitedAccuracy(typea, causes) subb && issimplertype(lattice, typea, typeb) && return LimitedAccuracy(typea, causes) return LimitedAccuracy(tmerge(widenlattice(lattice), typea, typeb), causes) end From f8493c718d28bdbdbc132773c6ef4b4850591add Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Mon, 23 Jan 2023 10:53:38 +0100 Subject: [PATCH 376/387] set max_methods = 1 for REPL methods (#48330) * set max_methods = 1 for REPL methods --- stdlib/REPL/src/LineEdit.jl | 2 +- stdlib/REPL/src/REPL.jl | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/stdlib/REPL/src/LineEdit.jl b/stdlib/REPL/src/LineEdit.jl index 348defe79d1975..0dffcc6c1e2762 100644 --- a/stdlib/REPL/src/LineEdit.jl +++ b/stdlib/REPL/src/LineEdit.jl @@ -452,7 +452,7 @@ function refresh_multi_line(termbuf::TerminalBuffer, terminal::UnixTerminal, buf # Write out the prompt string lindent = write_prompt(termbuf, prompt, hascolor(terminal))::Int # Count the '\n' at the end of the line if the terminal emulator does (specific to DOS cmd prompt) - miscountnl = @static Sys.iswindows() ? (isa(Terminals.pipe_reader(terminal), Base.TTY) && !Base.ispty(Terminals.pipe_reader(terminal))) : false + miscountnl = @static Sys.iswindows() ? (isa(Terminals.pipe_reader(terminal), Base.TTY) && !(Base.ispty(Terminals.pipe_reader(terminal)))::Bool) : false # Now go through the buffer line by line seek(buf, 0) diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index 9c8712e0d41fc1..e3912a48df429c 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -14,6 +14,7 @@ Run Evaluate Print Loop (REPL) module REPL Base.Experimental.@optlevel 1 +Base.Experimental.@max_methods 1 using Base.Meta, Sockets import InteractiveUtils From 134f3e7dfaa04511a2f81f4a40cdc85f4e433706 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Mon, 23 Jan 2023 10:06:02 -0500 Subject: [PATCH 377/387] Switch remaining julia code bool envs to get_bool_envs (#48383) --- base/errorshow.jl | 9 +++------ contrib/generate_precompile.jl | 2 +- stdlib/LinearAlgebra/test/special.jl | 2 +- stdlib/Profile/src/Profile.jl | 2 +- test/choosetests.jl | 6 +----- 5 files changed, 7 insertions(+), 14 deletions(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index 8f34424b359a80..e99253656d4e4f 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -372,12 +372,9 @@ function showerror_nostdio(err, msg::AbstractString) ccall(:jl_printf, Cint, (Ptr{Cvoid},Cstring), stderr_stream, "\n") end -stacktrace_expand_basepaths()::Bool = - tryparse(Bool, get(ENV, "JULIA_STACKTRACE_EXPAND_BASEPATHS", "false")) === true -stacktrace_contract_userdir()::Bool = - tryparse(Bool, get(ENV, "JULIA_STACKTRACE_CONTRACT_HOMEDIR", "true")) === true -stacktrace_linebreaks()::Bool = - tryparse(Bool, get(ENV, "JULIA_STACKTRACE_LINEBREAKS", "false")) === true +stacktrace_expand_basepaths()::Bool = Base.get_bool_env("JULIA_STACKTRACE_EXPAND_BASEPATHS", false) === true +stacktrace_contract_userdir()::Bool = Base.get_bool_env("JULIA_STACKTRACE_CONTRACT_HOMEDIR", true) === true +stacktrace_linebreaks()::Bool = Base.get_bool_env("JULIA_STACKTRACE_LINEBREAKS", false) === true function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=()) is_arg_types = isa(ex.args, DataType) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index 0db045661b9ba1..76956fac24f183 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -23,7 +23,7 @@ const PARALLEL_PRECOMPILATION = true const debug_output = devnull # or stdout # Disable fancy printing -const fancyprint = (stdout isa Base.TTY) && (get(ENV, "CI", nothing) != "true") +const fancyprint = (stdout isa Base.TTY) && Base.get_bool_env("CI", false) !== true ## CTRL_C = '\x03' diff --git a/stdlib/LinearAlgebra/test/special.jl b/stdlib/LinearAlgebra/test/special.jl index 465c9ad5a4951d..df845ba3110da8 100644 --- a/stdlib/LinearAlgebra/test/special.jl +++ b/stdlib/LinearAlgebra/test/special.jl @@ -291,7 +291,7 @@ end @testset "concatenations of annotated types" begin N = 4 # The tested annotation types - testfull = Bool(parse(Int,(get(ENV, "JULIA_TESTFULL", "0")))) + testfull = Base.get_bool_env("JULIA_TESTFULL", false) utriannotations = (UpperTriangular, UnitUpperTriangular) ltriannotations = (LowerTriangular, UnitLowerTriangular) triannotations = (utriannotations..., ltriannotations...) diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index 46d56a879cf6db..518dc54c7f757b 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -38,7 +38,7 @@ function profile_printing_listener() while true wait(PROFILE_PRINT_COND[]) peek_report[]() - if get(ENV, "JULIA_PROFILE_PEEK_HEAP_SNAPSHOT", nothing) === "1" + if Base.get_bool_env("JULIA_PROFILE_PEEK_HEAP_SNAPSHOT", false) === true println(stderr, "Saving heap snapshot...") fname = take_heap_snapshot() println(stderr, "Heap snapshot saved to `$(fname)`") diff --git a/test/choosetests.jl b/test/choosetests.jl index 23b3ab8dd342a9..34737fe255343f 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -180,11 +180,7 @@ function choosetests(choices = []) net_required_for = filter!(in(tests), NETWORK_REQUIRED_LIST) net_on = true - JULIA_TEST_NETWORKING_AVAILABLE = get(ENV, "JULIA_TEST_NETWORKING_AVAILABLE", "") |> - strip |> - lowercase |> - s -> tryparse(Bool, s) |> - x -> x === true + JULIA_TEST_NETWORKING_AVAILABLE = Base.get_bool_env("JULIA_TEST_NETWORKING_AVAILABLE", false) === true # If the `JULIA_TEST_NETWORKING_AVAILABLE` environment variable is set to `true`, we # always set `net_on` to `true`. # Otherwise, we set `net_on` to true if and only if networking is actually available. From b8adb2ede505e2d9eb2cfcd03caa64da643c9f22 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Tue, 24 Jan 2023 05:57:16 -0500 Subject: [PATCH 378/387] Relax type restriction in ismutationfree and isidentityfree. (#48357) Fixes #48353. --- base/reflection.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/base/reflection.jl b/base/reflection.jl index 50a4b131c2720a..75a8916e3ae328 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -634,13 +634,14 @@ is reachable from this type (either in the type itself) or through any fields. Note that the type itself need not be immutable. For example, an empty mutable type is `ismutabletype`, but also `ismutationfree`. """ -function ismutationfree(@nospecialize(t::Type)) +function ismutationfree(@nospecialize(t)) t = unwrap_unionall(t) if isa(t, DataType) return datatype_ismutationfree(t) elseif isa(t, Union) return ismutationfree(t.a) && ismutationfree(t.b) end + # TypeVar, etc. return false end @@ -652,7 +653,7 @@ datatype_isidentityfree(dt::DataType) = (@_total_meta; (dt.flags & 0x0200) == 0x Determine whether type `T` is identity free in the sense that this type or any reachable through its fields has non-content-based identity. """ -function isidentityfree(@nospecialize(t::Type)) +function isidentityfree(@nospecialize(t)) t = unwrap_unionall(t) if isa(t, DataType) return datatype_isidentityfree(t) From 596ce6542624e9b8c3782b19936e2226f307e118 Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Tue, 24 Jan 2023 09:05:18 -0500 Subject: [PATCH 379/387] fix #47658, state stack overflow on unions containing typevars (#48221) --- src/subtype.c | 34 ++++++++++++++++++++++++++++++++++ test/subtype.jl | 24 +++++++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/subtype.c b/src/subtype.c index 19edb5e51bf70c..44cba7bed9da46 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -1243,6 +1243,19 @@ static int subtype_tuple(jl_datatype_t *xd, jl_datatype_t *yd, jl_stenv_t *e, in return ans; } +static int equal_unions(jl_uniontype_t *x, jl_uniontype_t *y, jl_stenv_t *e) +{ + jl_value_t *saved=NULL; jl_savedenv_t se; + JL_GC_PUSH1(&saved); + save_env(e, &saved, &se); + int eq = forall_exists_equal(x->a, y->a, e) && forall_exists_equal(x->b, y->b, e); + if (!eq) + restore_env(e, saved, &se); + free_env(&se); + JL_GC_POP(); + return eq; +} + // `param` means we are currently looking at a parameter of a type constructor // (as opposed to being outside any type constructor, or comparing variable bounds). // this is used to record the positions where type variables occur for the @@ -1432,6 +1445,27 @@ static int forall_exists_equal(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) (is_definite_length_tuple_type(x) && is_indefinite_length_tuple_type(y))) return 0; + if (jl_is_uniontype(x) && jl_is_uniontype(y)) { + // For 2 unions, try a more efficient greedy algorithm that compares the unions + // componentwise. If it returns `false`, we forget it and proceed with the usual + // algorithm. If it returns `true` we try returning `true`, but need to come back + // here to try the usual algorithm if subtyping later fails. + jl_unionstate_t *state = &e->Runions; + jl_saved_unionstate_t oldRunions; push_unionstate(&oldRunions, state); + if (state->depth >= state->used) { + statestack_set(state, state->used, 0); + state->used++; + } + int ui = statestack_get(state, state->depth); + state->depth++; + if (ui == 0) { + state->more = state->depth; // memorize that this was the deepest available choice + if (equal_unions((jl_uniontype_t*)x, (jl_uniontype_t*)y, e)) + return 1; + pop_unionstate(state, &oldRunions); + } + } + jl_saved_unionstate_t oldLunions; push_unionstate(&oldLunions, &e->Lunions); e->Lunions.used = 0; int sub; diff --git a/test/subtype.jl b/test/subtype.jl index 7236d3438a692f..40c60670110fbe 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -2362,6 +2362,16 @@ end Tuple{Type{Tuple{Val{T},T}}, Val{T}} where T, Union{}) +@test only(intersection_env(Val{Union{Val{Val{T}} where {T},Int}}, Val{Union{T,Int}} where T)[2]) === Val{Val{T}} where {T} + +# issue 47654 +Vec47654{T} = Union{AbstractVector{T}, AbstractVector{Union{T,Nothing}}} +struct Wrapper47654{T, V<:Vec47654{T}} + v::V +end +abstract type P47654{A} end +@test Wrapper47654{P47654, Vector{Union{P47654,Nothing}}} <: Wrapper47654 + @testset "known subtype/intersect issue" begin #issue 45874 # Causes a hang due to jl_critical_error calling back into malloc... @@ -2390,7 +2400,7 @@ end @test_broken (Tuple{Q,Int} where Q<:Int) <: Tuple{T,T} where T # issue 24333 - @test_broken (Type{Union{Ref,Cvoid}} <: Type{Union{T,Cvoid}} where T) + @test (Type{Union{Ref,Cvoid}} <: Type{Union{T,Cvoid}} where T) # issue 22123 t1 = Ref{Ref{Ref{Union{Int64, T}}} where T} @@ -2401,6 +2411,18 @@ end @test_broken (Tuple{T1,T1} where T1<:(Val{T2} where T2)) <: (Tuple{Val{S},Val{S}} where S) end +# issue #47658 +let T = Ref{NTuple{8, Ref{Union{Int, P}}}} where P, + S = Ref{NTuple{8, Ref{Union{Int, P}}}} where P + # note T and S are identical but we need 2 copies to avoid being fooled by pointer equality + @test T <: Union{Int, S} +end + +# try to fool a greedy algorithm that picks X=Int, Y=String here +@test Tuple{Ref{Union{Int,String}}, Ref{Union{Int,String}}} <: Tuple{Ref{Union{X,Y}}, Ref{X}} where {X,Y} +# this slightly more complex case has been broken since 1.0 (worked in 0.6) +@test_broken Tuple{Ref{Union{Int,String,Missing}}, Ref{Union{Int,String}}} <: Tuple{Ref{Union{X,Y}}, Ref{X}} where {X,Y} + @test !(Tuple{Any, Any, Any} <: Tuple{Any, Vararg{T}} where T) abstract type MyAbstract47877{C}; end From f9d04735dddd37de83d341760eaee6f6e6db0ffc Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Tue, 24 Jan 2023 09:26:59 -0600 Subject: [PATCH 380/387] binomial(x,k) for non-integer x (#48124) * binomial(x,k) for non-integer x * correct PR number in NEWS * return 0 for k < 0 * comment on further generalizations --- NEWS.md | 5 +++-- base/intfuncs.jl | 31 +++++++++++++++++++++++++++++++ test/intfuncs.jl | 8 ++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 058c35ae55a66f..6b2234b2fba68f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -30,8 +30,9 @@ New library functions New library features -------------------- -The `initialized=true` keyword assignment for `sortperm!` and `partialsortperm!` -is now a no-op ([#47979]). It previously exposed unsafe behavior ([#47977]). +* The `initialized=true` keyword assignment for `sortperm!` and `partialsortperm!` + is now a no-op ([#47979]). It previously exposed unsafe behavior ([#47977]). +* `binomial(x, k)` now supports non-integer `x` ([#48124]). Standard library changes ------------------------ diff --git a/base/intfuncs.jl b/base/intfuncs.jl index dca0cddb939878..98a70981965005 100644 --- a/base/intfuncs.jl +++ b/base/intfuncs.jl @@ -1102,3 +1102,34 @@ Base.@assume_effects :terminates_locally function binomial(n::T, k::T) where T<: end copysign(x, sgn) end + +""" + binomial(x::Number, k::Integer) + +The generalized binomial coefficient, defined for `k ≥ 0` by +the polynomial +```math +\\frac{1}{k!} \\prod_{j=0}^{k-1} (x - j) +``` +When `k < 0` it returns zero. + +For the case of integer `x`, this is equivalent to the ordinary +integer binomial coefficient +```math +\\binom{n}{k} = \\frac{n!}{k! (n-k)!} +``` + +Further generalizations to non-integer `k` are mathematically possible, but +involve the Gamma function and/or the beta function, which are +not provided by the Julia standard library but are available +in external packages such as [SpecialFunctions.jl](https://github.com/JuliaMath/SpecialFunctions.jl). + +# External links +* [Binomial coefficient](https://en.wikipedia.org/wiki/Binomial_coefficient) on Wikipedia. +""" +function binomial(x::Number, k::Integer) + k < 0 && return zero(x)/one(k) + # we don't use prod(i -> (x-i+1), 1:k) / factorial(k), + # and instead divide each term by i, to avoid spurious overflow. + return prod(i -> (x-(i-1))/i, OneTo(k), init=oneunit(x)/one(k)) +end diff --git a/test/intfuncs.jl b/test/intfuncs.jl index 7f0261e1756177..2215cbaf36a569 100644 --- a/test/intfuncs.jl +++ b/test/intfuncs.jl @@ -554,6 +554,14 @@ end for x in ((false,false), (false,true), (true,false), (true,true)) @test binomial(x...) == (x != (false,true)) end + + # binomial(x,k) for non-integer x + @test @inferred(binomial(10.0,3)) === 120.0 + @test @inferred(binomial(10//1,3)) === 120//1 + @test binomial(2.5,3) ≈ 5//16 === binomial(5//2,3) + @test binomial(2.5,0) == 1.0 + @test binomial(35.0, 30) ≈ binomial(35, 30) # naive method overflows + @test binomial(2.5,-1) == 0.0 end # concrete-foldability From dfab7be1f8bf8fd4400e7f61954b9596eacf4de0 Mon Sep 17 00:00:00 2001 From: Diogo Netto <61364108+d-netto@users.noreply.github.com> Date: Tue, 24 Jan 2023 14:07:52 -0500 Subject: [PATCH 381/387] GC mark-loop rewrite (#47292) ## Previous work Since https://github.com/JuliaLang/julia/pull/21590, the GC mark-loop was implemented by keeping two manually managed stacks: one of which contained iterator states used to keep track of the object currently being marked. As an example, to mark arrays, we would pop the corresponding iterator state from the stack, iterate over the array until we found an unmarked reference, and if so, we would update the iterator state (to reflect the index we left off), "repush" it into the stack and proceed with marking the reference we just found. ## This PR This PR eliminates the need of keeping the iterator states by modifying the object graph traversal code. We keep a single stack of `jl_value_t *` currently being processed. To mark an object, we first pop it from the stack, push all unmarked references into the stack and proceed with marking. I believe this doesn't break any invariant from the generational GC. Indeed, the age bits are set after marking (if the object survived one GC cycle it's moved to the old generation), so this new traversal scheme wouldn't change the fact of whether an object had references to old objects or not. Furthermore, we must not update GC metadata for objects in the `remset`, and we ensure this by calling `gc_mark_outrefs` in `gc_queue_remset` with `meta_updated` set to 1. ## Additional advantages 1. There are no recursive function calls in the GC mark-loop code (one of the reasons why https://github.com/JuliaLang/julia/pull/21590 was implemented). 2. Keeping a single GC queue will **greatly** simplify work-stealing in the multi-threaded GC we are working on (c.f. https://github.com/JuliaLang/julia/pull/45639). 3. Arrays of references, for example, are now marked on a regular stride fashion, which could help with hardware prefetching. 4. We can easily modify the traversal mode (to breadth first, for example) by only changing the `jl_gc_markqueue_t`(from LIFO to FIFO, for example) methods without touching the mark-loop itself, which could enable further exploration on the GC in the future. Since this PR changes the mark-loop graph traversal code, there are some changes in the heap-snapshot, though I'm not familiar with that PR. Some benchmark results are here: https://hackmd.io/@Idnmfpb3SxK98-OsBtRD5A/H1V6QSzvs. --- src/gc-debug.c | 175 +--- src/gc.c | 1953 +++++++++++++++++------------------------- src/gc.h | 202 +---- src/julia_internal.h | 6 +- src/julia_threads.h | 21 +- src/partr.c | 2 +- src/support/dtypes.h | 1 + 7 files changed, 865 insertions(+), 1495 deletions(-) diff --git a/src/gc-debug.c b/src/gc-debug.c index 011788fbc7b2e6..a233b18d7dcfcd 100644 --- a/src/gc-debug.c +++ b/src/gc-debug.c @@ -198,21 +198,23 @@ static void restore(void) static void gc_verify_track(jl_ptls_t ptls) { - jl_gc_mark_cache_t *gc_cache = &ptls->gc_cache; do { - jl_gc_mark_sp_t sp; - gc_mark_sp_init(gc_cache, &sp); + jl_gc_markqueue_t mq; + mq.current = mq.start = ptls->mark_queue.start; + mq.end = ptls->mark_queue.end; + mq.current_chunk = mq.chunk_start = ptls->mark_queue.chunk_start; + mq.chunk_end = ptls->mark_queue.chunk_end; arraylist_push(&lostval_parents_done, lostval); jl_safe_printf("Now looking for %p =======\n", lostval); clear_mark(GC_CLEAN); - gc_mark_queue_all_roots(ptls, &sp); - gc_mark_queue_finlist(gc_cache, &sp, &to_finalize, 0); - for (int i = 0; i < gc_n_threads; i++) { + gc_mark_queue_all_roots(ptls, &mq); + gc_mark_finlist(&mq, &to_finalize, 0); + for (int i = 0; i < gc_n_threads;i++) { jl_ptls_t ptls2 = gc_all_tls_states[i]; - gc_mark_queue_finlist(gc_cache, &sp, &ptls2->finalizers, 0); + gc_mark_finlist(&mq, &ptls2->finalizers, 0); } - gc_mark_queue_finlist(gc_cache, &sp, &finalizer_list_marked, 0); - gc_mark_loop(ptls, sp); + gc_mark_finlist(&mq, &finalizer_list_marked, 0); + gc_mark_loop_(ptls, &mq); if (lostval_parents.len == 0) { jl_safe_printf("Could not find the missing link. We missed a toplevel root. This is odd.\n"); break; @@ -246,22 +248,24 @@ static void gc_verify_track(jl_ptls_t ptls) void gc_verify(jl_ptls_t ptls) { - jl_gc_mark_cache_t *gc_cache = &ptls->gc_cache; - jl_gc_mark_sp_t sp; - gc_mark_sp_init(gc_cache, &sp); + jl_gc_markqueue_t mq; + mq.current = mq.start = ptls->mark_queue.start; + mq.end = ptls->mark_queue.end; + mq.current_chunk = mq.chunk_start = ptls->mark_queue.chunk_start; + mq.chunk_end = ptls->mark_queue.chunk_end; lostval = NULL; lostval_parents.len = 0; lostval_parents_done.len = 0; clear_mark(GC_CLEAN); gc_verifying = 1; - gc_mark_queue_all_roots(ptls, &sp); - gc_mark_queue_finlist(gc_cache, &sp, &to_finalize, 0); - for (int i = 0; i < gc_n_threads; i++) { + gc_mark_queue_all_roots(ptls, &mq); + gc_mark_finlist(&mq, &to_finalize, 0); + for (int i = 0; i < gc_n_threads;i++) { jl_ptls_t ptls2 = gc_all_tls_states[i]; - gc_mark_queue_finlist(gc_cache, &sp, &ptls2->finalizers, 0); + gc_mark_finlist(&mq, &ptls2->finalizers, 0); } - gc_mark_queue_finlist(gc_cache, &sp, &finalizer_list_marked, 0); - gc_mark_loop(ptls, sp); + gc_mark_finlist(&mq, &finalizer_list_marked, 0); + gc_mark_loop_(ptls, &mq); int clean_len = bits_save[GC_CLEAN].len; for(int i = 0; i < clean_len + bits_save[GC_OLD].len; i++) { jl_taggedvalue_t *v = (jl_taggedvalue_t*)bits_save[i >= clean_len ? GC_OLD : GC_CLEAN].items[i >= clean_len ? i - clean_len : i]; @@ -500,7 +504,7 @@ int jl_gc_debug_check_other(void) return gc_debug_alloc_check(&jl_gc_debug_env.other); } -void jl_gc_debug_print_status(void) +void jl_gc_debug_print_status(void) JL_NOTSAFEPOINT { uint64_t pool_count = jl_gc_debug_env.pool.num; uint64_t other_count = jl_gc_debug_env.other.num; @@ -509,7 +513,7 @@ void jl_gc_debug_print_status(void) pool_count + other_count, pool_count, other_count, gc_num.pause); } -void jl_gc_debug_critical_error(void) +void jl_gc_debug_critical_error(void) JL_NOTSAFEPOINT { jl_gc_debug_print_status(); if (!jl_gc_debug_env.wait_for_debugger) @@ -1264,9 +1268,9 @@ int gc_slot_to_arrayidx(void *obj, void *_slot) JL_NOTSAFEPOINT return (slot - start) / elsize; } -// Print a backtrace from the bottom (start) of the mark stack up to `sp` -// `pc_offset` will be added to `sp` for convenience in the debugger. -NOINLINE void gc_mark_loop_unwind(jl_ptls_t ptls, jl_gc_mark_sp_t sp, int pc_offset) +// Print a backtrace from the `mq->start` of the mark queue up to `mq->current` +// `offset` will be added to `mq->current` for convenience in the debugger. +NOINLINE void gc_mark_loop_unwind(jl_ptls_t ptls, jl_gc_markqueue_t *mq, int offset) { jl_jmp_buf *old_buf = jl_get_safe_restore(); jl_jmp_buf buf; @@ -1276,123 +1280,14 @@ NOINLINE void gc_mark_loop_unwind(jl_ptls_t ptls, jl_gc_mark_sp_t sp, int pc_off jl_set_safe_restore(old_buf); return; } - void **top = sp.pc + pc_offset; - jl_gc_mark_data_t *data_top = sp.data; - sp.data = ptls->gc_cache.data_stack; - sp.pc = ptls->gc_cache.pc_stack; - int isroot = 1; - while (sp.pc < top) { - void *pc = *sp.pc; - const char *prefix = isroot ? "r--" : " `-"; - isroot = 0; - if (pc == gc_mark_label_addrs[GC_MARK_L_marked_obj]) { - gc_mark_marked_obj_t *data = gc_repush_markdata(&sp, gc_mark_marked_obj_t); - if ((jl_gc_mark_data_t *)data > data_top) { - jl_safe_printf("Mark stack unwind overflow -- ABORTING !!!\n"); - break; - } - jl_safe_printf("%p: Root object: %p :: %p (bits: %d)\n of type ", - (void*)data, (void*)data->obj, (void*)data->tag, (int)data->bits); - jl_((void*)data->tag); - isroot = 1; - } - else if (pc == gc_mark_label_addrs[GC_MARK_L_scan_only]) { - gc_mark_marked_obj_t *data = gc_repush_markdata(&sp, gc_mark_marked_obj_t); - if ((jl_gc_mark_data_t *)data > data_top) { - jl_safe_printf("Mark stack unwind overflow -- ABORTING !!!\n"); - break; - } - jl_safe_printf("%p: Queued root: %p :: %p (bits: %d)\n of type ", - (void*)data, (void*)data->obj, (void*)data->tag, (int)data->bits); - jl_((void*)data->tag); - isroot = 1; - } - else if (pc == gc_mark_label_addrs[GC_MARK_L_finlist]) { - gc_mark_finlist_t *data = gc_repush_markdata(&sp, gc_mark_finlist_t); - if ((jl_gc_mark_data_t *)data > data_top) { - jl_safe_printf("Mark stack unwind overflow -- ABORTING !!!\n"); - break; - } - jl_safe_printf("%p: Finalizer list from %p to %p\n", - (void*)data, (void*)data->begin, (void*)data->end); - isroot = 1; - } - else if (pc == gc_mark_label_addrs[GC_MARK_L_objarray]) { - gc_mark_objarray_t *data = gc_repush_markdata(&sp, gc_mark_objarray_t); - if ((jl_gc_mark_data_t *)data > data_top) { - jl_safe_printf("Mark stack unwind overflow -- ABORTING !!!\n"); - break; - } - jl_safe_printf("%p: %s Array in object %p :: %p -- [%p, %p)\n of type ", - (void*)data, prefix, (void*)data->parent, ((void**)data->parent)[-1], - (void*)data->begin, (void*)data->end); - jl_(jl_typeof(data->parent)); - } - else if (pc == gc_mark_label_addrs[GC_MARK_L_obj8]) { - gc_mark_obj8_t *data = gc_repush_markdata(&sp, gc_mark_obj8_t); - if ((jl_gc_mark_data_t *)data > data_top) { - jl_safe_printf("Mark stack unwind overflow -- ABORTING !!!\n"); - break; - } - jl_datatype_t *vt = (jl_datatype_t*)jl_typeof(data->parent); - uint8_t *desc = (uint8_t*)jl_dt_layout_ptrs(vt->layout); - jl_safe_printf("%p: %s Object (8bit) %p :: %p -- [%d, %d)\n of type ", - (void*)data, prefix, (void*)data->parent, ((void**)data->parent)[-1], - (int)(data->begin - desc), (int)(data->end - desc)); - jl_(jl_typeof(data->parent)); - } - else if (pc == gc_mark_label_addrs[GC_MARK_L_obj16]) { - gc_mark_obj16_t *data = gc_repush_markdata(&sp, gc_mark_obj16_t); - if ((jl_gc_mark_data_t *)data > data_top) { - jl_safe_printf("Mark stack unwind overflow -- ABORTING !!!\n"); - break; - } - jl_datatype_t *vt = (jl_datatype_t*)jl_typeof(data->parent); - uint16_t *desc = (uint16_t*)jl_dt_layout_ptrs(vt->layout); - jl_safe_printf("%p: %s Object (16bit) %p :: %p -- [%d, %d)\n of type ", - (void*)data, prefix, (void*)data->parent, ((void**)data->parent)[-1], - (int)(data->begin - desc), (int)(data->end - desc)); - jl_(jl_typeof(data->parent)); - } - else if (pc == gc_mark_label_addrs[GC_MARK_L_obj32]) { - gc_mark_obj32_t *data = gc_repush_markdata(&sp, gc_mark_obj32_t); - if ((jl_gc_mark_data_t *)data > data_top) { - jl_safe_printf("Mark stack unwind overflow -- ABORTING !!!\n"); - break; - } - jl_datatype_t *vt = (jl_datatype_t*)jl_typeof(data->parent); - uint32_t *desc = (uint32_t*)jl_dt_layout_ptrs(vt->layout); - jl_safe_printf("%p: %s Object (32bit) %p :: %p -- [%d, %d)\n of type ", - (void*)data, prefix, (void*)data->parent, ((void**)data->parent)[-1], - (int)(data->begin - desc), (int)(data->end - desc)); - jl_(jl_typeof(data->parent)); - } - else if (pc == gc_mark_label_addrs[GC_MARK_L_stack]) { - gc_mark_stackframe_t *data = gc_repush_markdata(&sp, gc_mark_stackframe_t); - if ((jl_gc_mark_data_t *)data > data_top) { - jl_safe_printf("Mark stack unwind overflow -- ABORTING !!!\n"); - break; - } - jl_safe_printf("%p: %s Stack frame %p -- %d of %d (%s)\n", - (void*)data, prefix, (void*)data->s, (int)data->i, - (int)data->nroots >> 1, - (data->nroots & 1) ? "indirect" : "direct"); - } - else if (pc == gc_mark_label_addrs[GC_MARK_L_module_binding]) { - // module_binding - gc_mark_binding_t *data = gc_repush_markdata(&sp, gc_mark_binding_t); - if ((jl_gc_mark_data_t *)data > data_top) { - jl_safe_printf("Mark stack unwind overflow -- ABORTING !!!\n"); - break; - } - jl_safe_printf("%p: %s Module (bindings) %p -- [%p, %p)\n", - (void*)data, prefix, (void*)data->parent, - (void*)data->begin, (void*)data->end); - } - else { - jl_safe_printf("Unknown pc %p --- ABORTING !!!\n", pc); - break; - } + jl_value_t **start = mq->start; + jl_value_t **end = mq->current + offset; + for (; start < end; start++) { + jl_value_t *obj = *start; + jl_taggedvalue_t *o = jl_astaggedvalue(obj); + jl_safe_printf("Queued object: %p :: (tag: %zu) (bits: %zu)\n", obj, + (uintptr_t)o->header, ((uintptr_t)o->header & 3)); + jl_((void*)(jl_datatype_t *)(o->header & ~(uintptr_t)0xf)); } jl_set_safe_restore(old_buf); } diff --git a/src/gc.c b/src/gc.c index fc2a4041910f57..d1cc57cf787de4 100644 --- a/src/gc.c +++ b/src/gc.c @@ -112,17 +112,6 @@ JL_DLLEXPORT void jl_gc_set_cb_notify_external_free(jl_gc_cb_notify_external_fre jl_gc_deregister_callback(&gc_cblist_notify_external_free, (jl_gc_cb_func_t)cb); } -// Save/restore local mark stack to/from thread-local storage. - -STATIC_INLINE void export_gc_state(jl_ptls_t ptls, jl_gc_mark_sp_t *sp) { - ptls->gc_mark_sp = *sp; -} - -STATIC_INLINE void import_gc_state(jl_ptls_t ptls, jl_gc_mark_sp_t *sp) { - // Has the stack been reallocated in the meantime? - *sp = ptls->gc_mark_sp; -} - // Protect all access to `finalizer_list_marked` and `to_finalize`. // For accessing `ptls->finalizers`, the lock is needed if a thread // is going to realloc the buffer (of its own list) or accessing the @@ -208,16 +197,16 @@ void jl_gc_wait_for_the_world(jl_ptls_t* gc_all_tls_states, int gc_n_threads) jl_wake_libuv(); for (int i = 0; i < gc_n_threads; i++) { jl_ptls_t ptls2 = gc_all_tls_states[i]; - if (ptls2 == NULL) - continue; - // This acquire load pairs with the release stores - // in the signal handler of safepoint so we are sure that - // all the stores on those threads are visible. - // We're currently also using atomic store release in mutator threads - // (in jl_gc_state_set), but we may want to use signals to flush the - // memory operations on those threads lazily instead. - while (!jl_atomic_load_relaxed(&ptls2->gc_state) || !jl_atomic_load_acquire(&ptls2->gc_state)) - jl_cpu_pause(); // yield? + if (ptls2 != NULL) { + // This acquire load pairs with the release stores + // in the signal handler of safepoint so we are sure that + // all the stores on those threads are visible. + // We're currently also using atomic store release in mutator threads + // (in jl_gc_state_set), but we may want to use signals to flush the + // memory operations on those threads lazily instead. + while (!jl_atomic_load_relaxed(&ptls2->gc_state) || !jl_atomic_load_acquire(&ptls2->gc_state)) + jl_cpu_pause(); // yield? + } } } @@ -522,7 +511,7 @@ void jl_gc_run_all_finalizers(jl_task_t *ct) schedule_all_finalizers(&finalizer_list_marked); for (int i = 0; i < gc_n_threads; i++) { jl_ptls_t ptls2 = gc_all_tls_states[i]; - if (ptls2) + if (ptls2 != NULL) schedule_all_finalizers(&ptls2->finalizers); } gc_n_threads = 0; @@ -596,7 +585,7 @@ JL_DLLEXPORT void jl_finalize_th(jl_task_t *ct, jl_value_t *o) gc_all_tls_states = jl_atomic_load_relaxed(&jl_all_tls_states); for (int i = 0; i < gc_n_threads; i++) { jl_ptls_t ptls2 = gc_all_tls_states[i]; - if (ptls2) + if (ptls2 != NULL) finalize_object(&ptls2->finalizers, o, &copied_list, jl_atomic_load_relaxed(&ct->tid) != i); } finalize_object(&finalizer_list_marked, o, &copied_list, 0); @@ -636,7 +625,7 @@ static void gc_sweep_foreign_objs(void) assert(gc_n_threads); for (int i = 0; i < gc_n_threads; i++) { jl_ptls_t ptls2 = gc_all_tls_states[i]; - if (ptls2) + if (ptls2 != NULL) gc_sweep_foreign_objs_in_list(&ptls2->sweep_objs); } } @@ -769,7 +758,7 @@ static void gc_sync_all_caches_nolock(jl_ptls_t ptls) assert(gc_n_threads); for (int t_i = 0; t_i < gc_n_threads; t_i++) { jl_ptls_t ptls2 = gc_all_tls_states[t_i]; - if (ptls2) + if (ptls2 != NULL) gc_sync_cache_nolock(ptls, &ptls2->gc_cache); } } @@ -788,21 +777,13 @@ STATIC_INLINE void gc_queue_big_marked(jl_ptls_t ptls, bigval_t *hdr, ptls->gc_cache.nbig_obj = nobj + 1; } -// `gc_setmark_tag` can be called concurrently on multiple threads. -// In all cases, the function atomically sets the mark bits and returns -// the GC bits set as well as if the tag was unchanged by this thread. -// All concurrent calls on the same object are guaranteed to be setting the -// bits to the same value. -// For normal objects, this is the bits with only `GC_MARKED` changed to `1` -// For buffers, this is the bits of the owner object. -// For `mark_reset_age`, this is `GC_MARKED` with `GC_OLD` cleared. -// The return value is `1` if the object was not marked before. -// Returning `0` can happen if another thread marked it in parallel. -STATIC_INLINE int gc_setmark_tag(jl_taggedvalue_t *o, uint8_t mark_mode, - uintptr_t tag, uint8_t *bits) JL_NOTSAFEPOINT -{ - assert(!gc_marked(tag)); +// Atomically set the mark bit for object and return whether it was previously unmarked +FORCE_INLINE int gc_try_setmark_tag(jl_taggedvalue_t *o, uint8_t mark_mode) JL_NOTSAFEPOINT +{ assert(gc_marked(mark_mode)); + uintptr_t tag = o->header; + if (gc_marked(tag)) + return 0; if (mark_reset_age) { // Reset the object as if it was just allocated mark_mode = GC_MARKED; @@ -814,7 +795,6 @@ STATIC_INLINE int gc_setmark_tag(jl_taggedvalue_t *o, uint8_t mark_mode, tag = tag | mark_mode; assert((tag & 0x3) == mark_mode); } - *bits = mark_mode; tag = jl_atomic_exchange_relaxed((_Atomic(uintptr_t)*)&o->header, tag); verify_val(jl_valueof(o)); return !gc_marked(tag); @@ -897,15 +877,12 @@ STATIC_INLINE void gc_setmark(jl_ptls_t ptls, jl_taggedvalue_t *o, STATIC_INLINE void gc_setmark_buf_(jl_ptls_t ptls, void *o, uint8_t mark_mode, size_t minsz) JL_NOTSAFEPOINT { jl_taggedvalue_t *buf = jl_astaggedvalue(o); - uintptr_t tag = buf->header; - if (gc_marked(tag)) - return; - uint8_t bits; + uint8_t bits = (gc_old(buf->header) && !mark_reset_age) ? GC_OLD_MARKED : GC_MARKED;; // If the object is larger than the max pool size it can't be a pool object. // This should be accurate most of the time but there might be corner cases // where the size estimate is a little off so we do a pool lookup to make // sure. - if (__likely(gc_setmark_tag(buf, mark_mode, tag, &bits)) && !gc_verifying) { + if (__likely(gc_try_setmark_tag(buf, mark_mode)) && !gc_verifying) { if (minsz <= GC_MAX_SZCLASS) { jl_gc_pagemeta_t *page = page_metadata(buf); if (page) { @@ -953,7 +930,7 @@ void jl_gc_force_mark_old(jl_ptls_t ptls, jl_value_t *v) JL_NOTSAFEPOINT jl_gc_queue_root(v); } -static inline void maybe_collect(jl_ptls_t ptls) +STATIC_INLINE void maybe_collect(jl_ptls_t ptls) { if (jl_atomic_load_relaxed(&ptls->gc_num.allocd) >= 0 || jl_gc_debug_check_other()) { jl_gc_collect(JL_GC_AUTO); @@ -980,14 +957,14 @@ static void clear_weak_refs(void) assert(gc_n_threads); for (int i = 0; i < gc_n_threads; i++) { jl_ptls_t ptls2 = gc_all_tls_states[i]; - if (ptls2 == NULL) - continue; - size_t n, l = ptls2->heap.weak_refs.len; - void **lst = ptls2->heap.weak_refs.items; - for (n = 0; n < l; n++) { - jl_weakref_t *wr = (jl_weakref_t*)lst[n]; - if (!gc_marked(jl_astaggedvalue(wr->value)->bits.gc)) - wr->value = (jl_value_t*)jl_nothing; + if (ptls2 != NULL) { + size_t n, l = ptls2->heap.weak_refs.len; + void **lst = ptls2->heap.weak_refs.items; + for (n = 0; n < l; n++) { + jl_weakref_t *wr = (jl_weakref_t*)lst[n]; + if (!gc_marked(jl_astaggedvalue(wr->value)->bits.gc)) + wr->value = (jl_value_t*)jl_nothing; + } } } } @@ -997,27 +974,27 @@ static void sweep_weak_refs(void) assert(gc_n_threads); for (int i = 0; i < gc_n_threads; i++) { jl_ptls_t ptls2 = gc_all_tls_states[i]; - if (ptls2 == NULL) - continue; - size_t n = 0; - size_t ndel = 0; - size_t l = ptls2->heap.weak_refs.len; - void **lst = ptls2->heap.weak_refs.items; - if (l == 0) - continue; - while (1) { - jl_weakref_t *wr = (jl_weakref_t*)lst[n]; - if (gc_marked(jl_astaggedvalue(wr)->bits.gc)) - n++; - else - ndel++; - if (n >= l - ndel) - break; - void *tmp = lst[n]; - lst[n] = lst[n + ndel]; - lst[n + ndel] = tmp; + if (ptls2 != NULL) { + size_t n = 0; + size_t ndel = 0; + size_t l = ptls2->heap.weak_refs.len; + void **lst = ptls2->heap.weak_refs.items; + if (l == 0) + continue; + while (1) { + jl_weakref_t *wr = (jl_weakref_t*)lst[n]; + if (gc_marked(jl_astaggedvalue(wr)->bits.gc)) + n++; + else + ndel++; + if (n >= l - ndel) + break; + void *tmp = lst[n]; + lst[n] = lst[n + ndel]; + lst[n + ndel] = tmp; + } + ptls2->heap.weak_refs.len -= ndel; } - ptls2->heap.weak_refs.len -= ndel; } } @@ -1025,7 +1002,7 @@ static void sweep_weak_refs(void) // big value list // Size includes the tag and the tag is not cleared!! -static inline jl_value_t *jl_gc_big_alloc_inner(jl_ptls_t ptls, size_t sz) +STATIC_INLINE jl_value_t *jl_gc_big_alloc_inner(jl_ptls_t ptls, size_t sz) { maybe_collect(ptls); size_t offs = offsetof(bigval_t, header); @@ -1057,7 +1034,6 @@ static inline jl_value_t *jl_gc_big_alloc_inner(jl_ptls_t ptls, size_t sz) JL_DLLEXPORT jl_value_t *jl_gc_big_alloc(jl_ptls_t ptls, size_t sz) { jl_value_t *val = jl_gc_big_alloc_inner(ptls, sz); - maybe_record_alloc_to_profile(val, sz, jl_gc_unknown_type_tag); return val; } @@ -1118,9 +1094,8 @@ static void sweep_big(jl_ptls_t ptls, int sweep_full) JL_NOTSAFEPOINT assert(gc_n_threads); for (int i = 0; i < gc_n_threads; i++) { jl_ptls_t ptls2 = gc_all_tls_states[i]; - if (ptls2 == NULL) - continue; - sweep_big_list(sweep_full, &ptls2->heap.big_objects); + if (ptls2 != NULL) + sweep_big_list(sweep_full, &ptls2->heap.big_objects); } if (sweep_full) { bigval_t **last_next = sweep_big_list(sweep_full, &big_objects_marked); @@ -1189,7 +1164,7 @@ static void reset_thread_gc_counts(void) JL_NOTSAFEPOINT gc_all_tls_states = jl_atomic_load_relaxed(&jl_all_tls_states); for (int i = 0; i < gc_n_threads; i++) { jl_ptls_t ptls = gc_all_tls_states[i]; - if (ptls) { + if (ptls != NULL) { memset(&ptls->gc_num, 0, sizeof(ptls->gc_num)); jl_atomic_store_relaxed(&ptls->gc_num.allocd, -(int64_t)gc_num.interval); } @@ -1238,32 +1213,32 @@ static void sweep_malloced_arrays(void) JL_NOTSAFEPOINT assert(gc_n_threads); for (int t_i = 0; t_i < gc_n_threads; t_i++) { jl_ptls_t ptls2 = gc_all_tls_states[t_i]; - if (ptls2 == NULL) - continue; - mallocarray_t *ma = ptls2->heap.mallocarrays; - mallocarray_t **pma = &ptls2->heap.mallocarrays; - while (ma != NULL) { - mallocarray_t *nxt = ma->next; - int bits = jl_astaggedvalue(ma->a)->bits.gc; - if (gc_marked(bits)) { - pma = &ma->next; - } - else { - *pma = nxt; - assert(ma->a->flags.how == 2); - jl_gc_free_array(ma->a); - ma->next = ptls2->heap.mafreelist; - ptls2->heap.mafreelist = ma; + if (ptls2 != NULL) { + mallocarray_t *ma = ptls2->heap.mallocarrays; + mallocarray_t **pma = &ptls2->heap.mallocarrays; + while (ma != NULL) { + mallocarray_t *nxt = ma->next; + int bits = jl_astaggedvalue(ma->a)->bits.gc; + if (gc_marked(bits)) { + pma = &ma->next; + } + else { + *pma = nxt; + assert(ma->a->flags.how == 2); + jl_gc_free_array(ma->a); + ma->next = ptls2->heap.mafreelist; + ptls2->heap.mafreelist = ma; + } + gc_time_count_mallocd_array(bits); + ma = nxt; } - gc_time_count_mallocd_array(bits); - ma = nxt; } } gc_time_mallocd_array_end(); } // pool allocation -static inline jl_taggedvalue_t *reset_page(jl_ptls_t ptls2, const jl_gc_pool_t *p, jl_gc_pagemeta_t *pg, jl_taggedvalue_t *fl) JL_NOTSAFEPOINT +STATIC_INLINE jl_taggedvalue_t *reset_page(jl_ptls_t ptls2, const jl_gc_pool_t *p, jl_gc_pagemeta_t *pg, jl_taggedvalue_t *fl) JL_NOTSAFEPOINT { assert(GC_PAGE_OFFSET >= sizeof(void*)); pg->nfree = (GC_PAGE_SZ - GC_PAGE_OFFSET) / p->osize; @@ -1310,7 +1285,7 @@ static NOINLINE jl_taggedvalue_t *add_page(jl_gc_pool_t *p) JL_NOTSAFEPOINT } // Size includes the tag and the tag is not cleared!! -static inline jl_value_t *jl_gc_pool_alloc_inner(jl_ptls_t ptls, int pool_offset, +STATIC_INLINE jl_value_t *jl_gc_pool_alloc_inner(jl_ptls_t ptls, int pool_offset, int osize) { // Use the pool offset instead of the pool address as the argument @@ -1328,7 +1303,7 @@ static inline jl_value_t *jl_gc_pool_alloc_inner(jl_ptls_t ptls, int pool_offset jl_atomic_load_relaxed(&ptls->gc_num.poolalloc) + 1); // first try to use the freelist jl_taggedvalue_t *v = p->freelist; - if (v) { + if (v != NULL) { jl_taggedvalue_t *next = v->next; p->freelist = next; if (__unlikely(gc_page_data(v) != gc_page_data(next))) { @@ -1348,8 +1323,8 @@ static inline jl_value_t *jl_gc_pool_alloc_inner(jl_ptls_t ptls, int pool_offset // If there's no pages left or the current page is used up, // we need to use the slow path. char *cur_page = gc_page_data((char*)v - 1); - if (__unlikely(!v || cur_page + GC_PAGE_SZ < (char*)next)) { - if (v) { + if (__unlikely(v == NULL || cur_page + GC_PAGE_SZ < (char*)next)) { + if (v != NULL) { // like the freelist case, // but only update the page metadata when it is full jl_gc_pagemeta_t *pg = jl_assume(page_metadata((char*)v - 1)); @@ -1359,7 +1334,7 @@ static inline jl_value_t *jl_gc_pool_alloc_inner(jl_ptls_t ptls, int pool_offset v = *(jl_taggedvalue_t**)cur_page; } // Not an else!! - if (!v) + if (v == NULL) v = add_page(p); next = (jl_taggedvalue_t*)((char*)v + osize); } @@ -1373,7 +1348,6 @@ JL_DLLEXPORT jl_value_t *jl_gc_pool_alloc(jl_ptls_t ptls, int pool_offset, int osize) { jl_value_t *val = jl_gc_pool_alloc_inner(ptls, pool_offset, osize); - maybe_record_alloc_to_profile(val, osize, jl_gc_unknown_type_tag); return val; } @@ -1519,7 +1493,7 @@ static jl_taggedvalue_t **sweep_page(jl_gc_pool_t *p, jl_gc_pagemeta_t *pg, jl_t } // the actual sweeping over all allocated pages in a memory pool -static inline void sweep_pool_page(jl_taggedvalue_t ***pfl, jl_gc_pagemeta_t *pg, int sweep_full) JL_NOTSAFEPOINT +STATIC_INLINE void sweep_pool_page(jl_taggedvalue_t ***pfl, jl_gc_pagemeta_t *pg, int sweep_full) JL_NOTSAFEPOINT { int p_n = pg->pool_n; int t_n = pg->thread_n; @@ -1530,7 +1504,7 @@ static inline void sweep_pool_page(jl_taggedvalue_t ***pfl, jl_gc_pagemeta_t *pg } // sweep over a pagetable0 for all allocated pages -static inline int sweep_pool_pagetable0(jl_taggedvalue_t ***pfl, pagetable0_t *pagetable0, int sweep_full) JL_NOTSAFEPOINT +STATIC_INLINE int sweep_pool_pagetable0(jl_taggedvalue_t ***pfl, pagetable0_t *pagetable0, int sweep_full) JL_NOTSAFEPOINT { unsigned ub = 0; unsigned alloc = 0; @@ -1554,7 +1528,7 @@ static inline int sweep_pool_pagetable0(jl_taggedvalue_t ***pfl, pagetable0_t *p } // sweep over pagetable1 for all pagetable0 that may contain allocated pages -static inline int sweep_pool_pagetable1(jl_taggedvalue_t ***pfl, pagetable1_t *pagetable1, int sweep_full) JL_NOTSAFEPOINT +STATIC_INLINE int sweep_pool_pagetable1(jl_taggedvalue_t ***pfl, pagetable1_t *pagetable1, int sweep_full) JL_NOTSAFEPOINT { unsigned ub = 0; unsigned alloc = 0; @@ -1583,7 +1557,7 @@ static void sweep_pool_pagetable(jl_taggedvalue_t ***pfl, int sweep_full) JL_NOT { if (REGION2_PG_COUNT == 1) { // compile-time optimization pagetable1_t *pagetable1 = memory_map.meta1[0]; - if (pagetable1) + if (pagetable1 != NULL) sweep_pool_pagetable1(pfl, pagetable1, sweep_full); return; } @@ -1682,10 +1656,10 @@ static void gc_sweep_pool(int sweep_full) // null out terminal pointers of free lists for (int t_i = 0; t_i < n_threads; t_i++) { jl_ptls_t ptls2 = gc_all_tls_states[t_i]; - if (ptls2 == NULL) - continue; - for (int i = 0; i < JL_GC_N_POOLS; i++) { - *pfl[t_i * JL_GC_N_POOLS + i] = NULL; + if (ptls2 != NULL) { + for (int i = 0; i < JL_GC_N_POOLS; i++) { + *pfl[t_i * JL_GC_N_POOLS + i] = NULL; + } } } @@ -1761,7 +1735,7 @@ static void *volatile gc_findval; // for usage from gdb, for finding the gc-root // Handle the case where the stack is only partially copied. STATIC_INLINE uintptr_t gc_get_stack_addr(void *_addr, uintptr_t offset, - uintptr_t lb, uintptr_t ub) + uintptr_t lb, uintptr_t ub) JL_NOTSAFEPOINT { uintptr_t addr = (uintptr_t)_addr; if (addr >= lb && addr < ub) @@ -1770,901 +1744,548 @@ STATIC_INLINE uintptr_t gc_get_stack_addr(void *_addr, uintptr_t offset, } STATIC_INLINE uintptr_t gc_read_stack(void *_addr, uintptr_t offset, - uintptr_t lb, uintptr_t ub) + uintptr_t lb, uintptr_t ub) JL_NOTSAFEPOINT { uintptr_t real_addr = gc_get_stack_addr(_addr, offset, lb, ub); return *(uintptr_t*)real_addr; } JL_NORETURN NOINLINE void gc_assert_datatype_fail(jl_ptls_t ptls, jl_datatype_t *vt, - jl_gc_mark_sp_t sp) + jl_gc_markqueue_t *mq) JL_NOTSAFEPOINT { jl_safe_printf("GC error (probable corruption) :\n"); jl_gc_debug_print_status(); jl_(vt); jl_gc_debug_critical_error(); - gc_mark_loop_unwind(ptls, sp, 0); + gc_mark_loop_unwind(ptls, mq, 0); abort(); } -// This stores the label address in the mark loop function. -// We can't directly store that to a global array so we need some hack to get that. -// See the call to `gc_mark_loop` in init with a `NULL` `ptls`. -void *gc_mark_label_addrs[_GC_MARK_L_MAX]; - -// Double the local mark stack (both pc and data) -static void NOINLINE gc_mark_stack_resize(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp) JL_NOTSAFEPOINT +// Check if `nptr` is tagged for `old + refyoung`, +// Push the object to the remset and update the `nptr` counter if necessary. +STATIC_INLINE void gc_mark_push_remset(jl_ptls_t ptls, jl_value_t *obj, + uintptr_t nptr) JL_NOTSAFEPOINT { - jl_gc_mark_data_t *old_data = gc_cache->data_stack; - void **pc_stack = sp->pc_start; - size_t stack_size = (char*)sp->pc_end - (char*)pc_stack; - ptrdiff_t datadiff = (char*)sp->data - (char*)old_data; - gc_cache->data_stack = (jl_gc_mark_data_t *)realloc_s(old_data, stack_size * 2 * sizeof(jl_gc_mark_data_t)); - sp->data = (jl_gc_mark_data_t *)((char*)gc_cache->data_stack + datadiff); - - sp->pc_start = gc_cache->pc_stack = (void**)realloc_s(pc_stack, stack_size * 2 * sizeof(void*)); - gc_cache->pc_stack_end = sp->pc_end = sp->pc_start + stack_size * 2; - sp->pc = sp->pc_start + (sp->pc - pc_stack); + if (__unlikely((nptr & 0x3) == 0x3)) { + ptls->heap.remset_nptr += nptr >> 2; + arraylist_t *remset = ptls->heap.remset; + size_t len = remset->len; + if (__unlikely(len >= remset->max)) { + arraylist_push(remset, obj); + } + else { + remset->len = len + 1; + remset->items[len] = obj; + } + } } -// Push a work item to the stack. The type of the work item is marked with `pc`. -// The data needed is in `data` and is of size `data_size`. -// If there isn't enough space on the stack, the stack will be resized with the stack -// lock held. The caller should invalidate any local cache of the stack addresses that's not -// in `gc_cache` or `sp` -// The `sp` will be updated on return if `inc` is true. -STATIC_INLINE void gc_mark_stack_push(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, - void *pc, void *data, size_t data_size, int inc) JL_NOTSAFEPOINT +// Double the mark queue +static NOINLINE void gc_markqueue_resize(jl_gc_markqueue_t *mq) JL_NOTSAFEPOINT { - assert(data_size <= sizeof(jl_gc_mark_data_t)); - if (__unlikely(sp->pc == sp->pc_end)) - gc_mark_stack_resize(gc_cache, sp); - *sp->pc = pc; - memcpy(sp->data, data, data_size); - if (inc) { - sp->data = (jl_gc_mark_data_t *)(((char*)sp->data) + data_size); - sp->pc++; - } + jl_value_t **old_start = mq->start; + size_t old_queue_size = (mq->end - mq->start); + size_t offset = (mq->current - old_start); + mq->start = (jl_value_t **)realloc_s(old_start, 2 * old_queue_size * sizeof(jl_value_t *)); + mq->current = (mq->start + offset); + mq->end = (mq->start + 2 * old_queue_size); } -// Check if the reference is non-NULL and atomically set the mark bit. -// Update `*nptr`, which is the `nptr` field of the parent item, if the object is young. -// Return the tag (with GC bits cleared) and the GC bits in `*ptag` and `*pbits`. -// Return whether the object needs to be scanned / have metadata updated. -STATIC_INLINE int gc_try_setmark(jl_value_t *obj, uintptr_t *nptr, - uintptr_t *ptag, uint8_t *pbits) JL_NOTSAFEPOINT +// Push a work item to the queue +STATIC_INLINE void gc_markqueue_push(jl_gc_markqueue_t *mq, jl_value_t *obj) JL_NOTSAFEPOINT { - if (!obj) - return 0; - jl_taggedvalue_t *o = jl_astaggedvalue(obj); - uintptr_t tag = o->header; - if (!gc_marked(tag)) { - uint8_t bits; - int res = gc_setmark_tag(o, GC_MARKED, tag, &bits); - if (!gc_old(bits)) - *nptr = *nptr | 1; - *ptag = tag & ~(uintptr_t)0xf; - *pbits = bits; - return __likely(res); - } - else if (!gc_old(tag)) { - *nptr = *nptr | 1; - } - return 0; + if (__unlikely(mq->current == mq->end)) + gc_markqueue_resize(mq); + *mq->current = obj; + mq->current++; } -// Queue a finalizer list to be scanned in the mark loop. Start marking from index `start`. -void gc_mark_queue_finlist(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, - arraylist_t *list, size_t start) +// Pop from the mark queue +STATIC_INLINE jl_value_t *gc_markqueue_pop(jl_gc_markqueue_t *mq) { - size_t len = list->len; - if (len <= start) - return; - jl_value_t **items = (jl_value_t**)list->items; - gc_mark_finlist_t markdata = {items + start, items + len}; - gc_mark_stack_push(gc_cache, sp, gc_mark_label_addrs[GC_MARK_L_finlist], - &markdata, sizeof(markdata), 1); + jl_value_t *obj = NULL; + if (mq->current != mq->start) { + mq->current--; + obj = *mq->current; + } + return obj; } -// Queue a object to be scanned. The object should already be marked and the GC metadata -// should already be updated for it. Only scanning of the object should be performed. -STATIC_INLINE void gc_mark_queue_scan_obj(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, - jl_value_t *obj) +// Double the chunk queue +static NOINLINE void gc_chunkqueue_resize(jl_gc_markqueue_t *mq) JL_NOTSAFEPOINT { - jl_taggedvalue_t *o = jl_astaggedvalue(obj); - uintptr_t tag = o->header; - uint8_t bits = tag & 0xf; - tag = tag & ~(uintptr_t)0xf; - gc_mark_marked_obj_t data = {obj, tag, bits}; - gc_mark_stack_push(gc_cache, sp, gc_mark_label_addrs[GC_MARK_L_scan_only], - &data, sizeof(data), 1); + jl_gc_chunk_t *old_start = mq->chunk_start; + size_t old_queue_size = (mq->chunk_end - mq->chunk_start); + size_t offset = (mq->current_chunk - old_start); + mq->chunk_start = (jl_gc_chunk_t *)realloc_s(old_start, 2 * old_queue_size * sizeof(jl_gc_chunk_t)); + mq->current_chunk = (mq->chunk_start + offset); + mq->chunk_end = (mq->chunk_start + 2 * old_queue_size); } -// Mark and queue a object to be scanned. -// The object will be marked atomically which can also happen concurrently. -// It will be queued if the object wasn't marked already (or concurrently by another thread) -// Returns whether the object is young. -STATIC_INLINE int gc_mark_queue_obj(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, void *_obj) JL_NOTSAFEPOINT +// Push chunk `*c` into chunk queue +STATIC_INLINE void gc_chunkqueue_push(jl_gc_markqueue_t *mq, jl_gc_chunk_t *c) JL_NOTSAFEPOINT { - jl_value_t *obj = (jl_value_t*)jl_assume(_obj); - uintptr_t nptr = 0; - uintptr_t tag = 0; - uint8_t bits = 0; - if (!gc_try_setmark(obj, &nptr, &tag, &bits)) - return (int)nptr; - gc_mark_marked_obj_t data = {obj, tag, bits}; - gc_mark_stack_push(gc_cache, sp, gc_mark_label_addrs[GC_MARK_L_marked_obj], - &data, sizeof(data), 1); - return (int)nptr; + if (__unlikely(mq->current_chunk == mq->chunk_end)) + gc_chunkqueue_resize(mq); + *mq->current_chunk = *c; + mq->current_chunk++; } -int jl_gc_mark_queue_obj_explicit(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, jl_value_t *obj) +// Pop chunk from chunk queue +STATIC_INLINE jl_gc_chunk_t gc_chunkqueue_pop(jl_gc_markqueue_t *mq) JL_NOTSAFEPOINT { - return gc_mark_queue_obj(gc_cache, sp, obj); + jl_gc_chunk_t c = {.cid = GC_empty_chunk}; + if (mq->current_chunk != mq->chunk_start) { + mq->current_chunk--; + c = *mq->current_chunk; + } + return c; } -JL_DLLEXPORT int jl_gc_mark_queue_obj(jl_ptls_t ptls, jl_value_t *obj) +// Enqueue an unmarked obj. last bit of `nptr` is set if `_obj` is young +STATIC_INLINE void gc_try_claim_and_push(jl_gc_markqueue_t *mq, void *_obj, + uintptr_t *nptr) JL_NOTSAFEPOINT { - return gc_mark_queue_obj(&ptls->gc_cache, &ptls->gc_mark_sp, obj); + if (_obj == NULL) + return; + jl_value_t *obj = (jl_value_t *)jl_assume(_obj); + jl_taggedvalue_t *o = jl_astaggedvalue(obj); + if (!gc_old(o->header) && nptr) + *nptr |= 1; + if (gc_try_setmark_tag(o, GC_MARKED)) + gc_markqueue_push(mq, obj); } -JL_DLLEXPORT void jl_gc_mark_queue_objarray(jl_ptls_t ptls, jl_value_t *parent, - jl_value_t **objs, size_t nobjs) +// Mark object with 8bit field descriptors +STATIC_INLINE jl_value_t *gc_mark_obj8(jl_ptls_t ptls, char *obj8_parent, uint8_t *obj8_begin, + uint8_t *obj8_end, uintptr_t nptr) JL_NOTSAFEPOINT { - gc_mark_objarray_t data = { parent, objs, objs + nobjs, 1, - jl_astaggedvalue(parent)->bits.gc & 2 }; - gc_mark_stack_push(&ptls->gc_cache, &ptls->gc_mark_sp, - gc_mark_label_addrs[GC_MARK_L_objarray], - &data, sizeof(data), 1); + (void)jl_assume(obj8_begin < obj8_end); + jl_gc_markqueue_t *mq = &ptls->mark_queue; + jl_value_t **slot = NULL; + jl_value_t *new_obj = NULL; + for (; obj8_begin < obj8_end; obj8_begin++) { + slot = &((jl_value_t**)obj8_parent)[*obj8_begin]; + new_obj = *slot; + if (new_obj != NULL) { + verify_parent2("object", obj8_parent, slot, "field(%d)", + gc_slot_to_fieldidx(obj8_parent, slot, (jl_datatype_t*)jl_typeof(obj8_parent))); + if (obj8_begin + 1 != obj8_end) { + gc_try_claim_and_push(mq, new_obj, &nptr); + } + else { + // Unroll marking of last item to avoid pushing + // and popping it right away + jl_taggedvalue_t *o = jl_astaggedvalue(new_obj); + nptr |= !gc_old(o->header); + if (!gc_try_setmark_tag(o, GC_MARKED)) new_obj = NULL; + } + gc_heap_snapshot_record_object_edge((jl_value_t*)obj8_parent, slot); + } + } + gc_mark_push_remset(ptls, (jl_value_t *)obj8_parent, nptr); + return new_obj; } - -// Check if `nptr` is tagged for `old + refyoung`, -// Push the object to the remset and update the `nptr` counter if necessary. -STATIC_INLINE void gc_mark_push_remset(jl_ptls_t ptls, jl_value_t *obj, uintptr_t nptr) JL_NOTSAFEPOINT +// Mark object with 16bit field descriptors +STATIC_INLINE jl_value_t *gc_mark_obj16(jl_ptls_t ptls, char *obj16_parent, uint16_t *obj16_begin, + uint16_t *obj16_end, uintptr_t nptr) JL_NOTSAFEPOINT { - if (__unlikely((nptr & 0x3) == 0x3)) { - ptls->heap.remset_nptr += nptr >> 2; - arraylist_t *remset = ptls->heap.remset; - size_t len = remset->len; - if (__unlikely(len >= remset->max)) { - arraylist_push(remset, obj); - } - else { - remset->len = len + 1; - remset->items[len] = obj; + (void)jl_assume(obj16_begin < obj16_end); + jl_gc_markqueue_t *mq = &ptls->mark_queue; + jl_value_t **slot = NULL; + jl_value_t *new_obj = NULL; + for (; obj16_begin < obj16_end; obj16_begin++) { + slot = &((jl_value_t **)obj16_parent)[*obj16_begin]; + new_obj = *slot; + if (new_obj != NULL) { + verify_parent2("object", obj16_parent, slot, "field(%d)", + gc_slot_to_fieldidx(obj16_parent, slot, (jl_datatype_t*)jl_typeof(obj16_parent))); + gc_try_claim_and_push(mq, new_obj, &nptr); + if (obj16_begin + 1 != obj16_end) { + gc_try_claim_and_push(mq, new_obj, &nptr); + } + else { + // Unroll marking of last item to avoid pushing + // and popping it right away + jl_taggedvalue_t *o = jl_astaggedvalue(new_obj); + nptr |= !gc_old(o->header); + if (!gc_try_setmark_tag(o, GC_MARKED)) new_obj = NULL; + } + gc_heap_snapshot_record_object_edge((jl_value_t*)obj16_parent, slot); } } + gc_mark_push_remset(ptls, (jl_value_t *)obj16_parent, nptr); + return new_obj; } -// Scan a dense array of object references, see `gc_mark_objarray_t` -STATIC_INLINE int gc_mark_scan_objarray(jl_ptls_t ptls, jl_gc_mark_sp_t *sp, - gc_mark_objarray_t *objary, - jl_value_t **begin, jl_value_t **end, - jl_value_t **pnew_obj, uintptr_t *ptag, uint8_t *pbits) +// Mark object with 32bit field descriptors +STATIC_INLINE jl_value_t *gc_mark_obj32(jl_ptls_t ptls, char *obj32_parent, uint32_t *obj32_begin, + uint32_t *obj32_end, uintptr_t nptr) JL_NOTSAFEPOINT { - (void)jl_assume(objary == (gc_mark_objarray_t*)sp->data); - for (; begin < end; begin += objary->step) { - *pnew_obj = *begin; - if (*pnew_obj) { - verify_parent2("obj array", objary->parent, begin, "elem(%d)", - gc_slot_to_arrayidx(objary->parent, begin)); - gc_heap_snapshot_record_array_edge(objary->parent, begin); - } - if (!gc_try_setmark(*pnew_obj, &objary->nptr, ptag, pbits)) - continue; - begin += objary->step; - // Found an object to mark - if (begin < end) { - // Haven't done with this one yet. Update the content and push it back - objary->begin = begin; - gc_repush_markdata(sp, gc_mark_objarray_t); - } - else { - // Finished scanning this one, finish up by checking the GC invariance - // and let the next item replacing the current one directly. - gc_mark_push_remset(ptls, objary->parent, objary->nptr); - } - return 1; - } - gc_mark_push_remset(ptls, objary->parent, objary->nptr); - return 0; -} - -// Scan a sparse array of object references, see `gc_mark_objarray_t` -STATIC_INLINE int gc_mark_scan_array8(jl_ptls_t ptls, jl_gc_mark_sp_t *sp, - gc_mark_array8_t *ary8, - jl_value_t **begin, jl_value_t **end, - uint8_t *elem_begin, uint8_t *elem_end, - jl_value_t **pnew_obj, uintptr_t *ptag, uint8_t *pbits) -{ - (void)jl_assume(ary8 == (gc_mark_array8_t*)sp->data); - size_t elsize = ((jl_array_t*)ary8->elem.parent)->elsize / sizeof(jl_value_t*); - for (; begin < end; begin += elsize) { - for (; elem_begin < elem_end; elem_begin++) { - jl_value_t **slot = &begin[*elem_begin]; - *pnew_obj = *slot; - if (*pnew_obj) { - verify_parent2("array", ary8->elem.parent, slot, "elem(%d)", - gc_slot_to_arrayidx(ary8->elem.parent, begin)); - gc_heap_snapshot_record_array_edge(ary8->elem.parent, slot); - } - if (!gc_try_setmark(*pnew_obj, &ary8->elem.nptr, ptag, pbits)) - continue; - elem_begin++; - // Found an object to mark - if (elem_begin < elem_end) { - // Haven't done with this one yet. Update the content and push it back - ary8->elem.begin = elem_begin; - ary8->begin = begin; - gc_repush_markdata(sp, gc_mark_array8_t); + (void)jl_assume(obj32_begin < obj32_end); + jl_gc_markqueue_t *mq = &ptls->mark_queue; + jl_value_t **slot = NULL; + jl_value_t *new_obj = NULL; + for (; obj32_begin < obj32_end; obj32_begin++) { + slot = &((jl_value_t **)obj32_parent)[*obj32_begin]; + new_obj = *slot; + if (new_obj != NULL) { + verify_parent2("object", obj32_parent, slot, "field(%d)", + gc_slot_to_fieldidx(obj32_parent, slot, (jl_datatype_t*)jl_typeof(obj32_parent))); + if (obj32_begin + 1 != obj32_end) { + gc_try_claim_and_push(mq, new_obj, &nptr); } else { - begin += elsize; - if (begin < end) { - // Haven't done with this array yet. Reset the content and push it back - ary8->elem.begin = ary8->rebegin; - ary8->begin = begin; - gc_repush_markdata(sp, gc_mark_array8_t); - } - else { - // Finished scanning this one, finish up by checking the GC invariance - // and let the next item replacing the current one directly. - gc_mark_push_remset(ptls, ary8->elem.parent, ary8->elem.nptr); - } + // Unroll marking of last item to avoid pushing + // and popping it right away + jl_taggedvalue_t *o = jl_astaggedvalue(new_obj); + nptr |= !gc_old(o->header); + if (!gc_try_setmark_tag(o, GC_MARKED)) new_obj = NULL; + } + gc_heap_snapshot_record_object_edge((jl_value_t*)obj32_parent, slot); + } + } + return new_obj; +} + +// Mark object array +STATIC_INLINE void gc_mark_objarray(jl_ptls_t ptls, jl_value_t *obj_parent, jl_value_t **obj_begin, + jl_value_t **obj_end, uint32_t step, uintptr_t nptr) JL_NOTSAFEPOINT +{ + jl_gc_markqueue_t *mq = &ptls->mark_queue; + jl_value_t *new_obj; + // Decide whether need to chunk objary + (void)jl_assume(step > 0); + size_t nobjs = (obj_end - obj_begin) / step; + if (nobjs > MAX_REFS_AT_ONCE) { + jl_gc_chunk_t c = {GC_objary_chunk, obj_parent, obj_begin + step * MAX_REFS_AT_ONCE, + obj_end, NULL, NULL, + step, nptr}; + gc_chunkqueue_push(mq, &c); + obj_end = obj_begin + step * MAX_REFS_AT_ONCE; + } + for (; obj_begin < obj_end; obj_begin += step) { + new_obj = *obj_begin; + if (new_obj != NULL) { + verify_parent2("obj array", obj_parent, obj_begin, "elem(%d)", + gc_slot_to_arrayidx(obj_parent, obj_begin)); + gc_try_claim_and_push(mq, new_obj, &nptr); + gc_heap_snapshot_record_array_edge(obj_parent, &new_obj); + } + } + gc_mark_push_remset(ptls, obj_parent, nptr); +} + +// Mark array with 8bit field descriptors +STATIC_INLINE void gc_mark_array8(jl_ptls_t ptls, jl_value_t *ary8_parent, jl_value_t **ary8_begin, + jl_value_t **ary8_end, uint8_t *elem_begin, uint8_t *elem_end, + uintptr_t nptr) JL_NOTSAFEPOINT +{ + jl_gc_markqueue_t *mq = &ptls->mark_queue; + jl_value_t *new_obj; + size_t elsize = ((jl_array_t *)ary8_parent)->elsize / sizeof(jl_value_t *); + // Decide whether need to chunk ary8 + size_t nrefs = (ary8_end - ary8_begin) / elsize; + if (nrefs > MAX_REFS_AT_ONCE) { + jl_gc_chunk_t c = {GC_ary8_chunk, ary8_parent, ary8_begin + elsize * MAX_REFS_AT_ONCE, + ary8_end, elem_begin, elem_end, + 0, nptr}; + gc_chunkqueue_push(mq, &c); + ary8_end = ary8_begin + elsize * MAX_REFS_AT_ONCE; + } + for (; ary8_begin < ary8_end; ary8_begin += elsize) { + for (uint8_t *pindex = elem_begin; pindex < elem_end; pindex++) { + new_obj = ary8_begin[*pindex]; + if (new_obj != NULL) { + verify_parent2("array", ary8_parent, &new_obj, "elem(%d)", + gc_slot_to_arrayidx(ary8_parent, ary8_begin)); + gc_try_claim_and_push(mq, new_obj, &nptr); + gc_heap_snapshot_record_array_edge(ary8_parent, &new_obj); } - return 1; } - elem_begin = ary8->rebegin; - } - gc_mark_push_remset(ptls, ary8->elem.parent, ary8->elem.nptr); - return 0; -} - -// Scan a sparse array of object references, see `gc_mark_objarray_t` -STATIC_INLINE int gc_mark_scan_array16(jl_ptls_t ptls, jl_gc_mark_sp_t *sp, - gc_mark_array16_t *ary16, - jl_value_t **begin, jl_value_t **end, - uint16_t *elem_begin, uint16_t *elem_end, - jl_value_t **pnew_obj, uintptr_t *ptag, uint8_t *pbits) -{ - (void)jl_assume(ary16 == (gc_mark_array16_t*)sp->data); - size_t elsize = ((jl_array_t*)ary16->elem.parent)->elsize / sizeof(jl_value_t*); - for (; begin < end; begin += elsize) { - for (; elem_begin < elem_end; elem_begin++) { - jl_value_t **slot = &begin[*elem_begin]; - *pnew_obj = *slot; - if (*pnew_obj) { - verify_parent2("array", ary16->elem.parent, slot, "elem(%d)", - gc_slot_to_arrayidx(ary16->elem.parent, begin)); - gc_heap_snapshot_record_array_edge(ary16->elem.parent, slot); + } + gc_mark_push_remset(ptls, ary8_parent, nptr); +} + +// Mark array with 16bit field descriptors +STATIC_INLINE void gc_mark_array16(jl_ptls_t ptls, jl_value_t *ary16_parent, jl_value_t **ary16_begin, + jl_value_t **ary16_end, uint16_t *elem_begin, uint16_t *elem_end, + uintptr_t nptr) JL_NOTSAFEPOINT +{ + jl_gc_markqueue_t *mq = &ptls->mark_queue; + jl_value_t *new_obj; + size_t elsize = ((jl_array_t *)ary16_parent)->elsize / sizeof(jl_value_t *); + // Decide whether need to chunk ary16 + size_t nrefs = (ary16_end - ary16_begin) / elsize; + if (nrefs > MAX_REFS_AT_ONCE) { + jl_gc_chunk_t c = {GC_ary16_chunk, ary16_parent, ary16_begin + elsize * MAX_REFS_AT_ONCE, + ary16_end, elem_begin, elem_end, + 0, nptr}; + gc_chunkqueue_push(mq, &c); + ary16_end = ary16_begin + elsize * MAX_REFS_AT_ONCE; + } + for (; ary16_begin < ary16_end; ary16_begin += elsize) { + for (uint16_t *pindex = elem_begin; pindex < elem_end; pindex++) { + new_obj = ary16_begin[*pindex]; + if (new_obj != NULL) { + verify_parent2("array", ary16_parent, &new_obj, "elem(%d)", + gc_slot_to_arrayidx(ary16_parent, ary16_begin)); + gc_try_claim_and_push(mq, new_obj, &nptr); + gc_heap_snapshot_record_array_edge(ary16_parent, &new_obj); } - if (!gc_try_setmark(*pnew_obj, &ary16->elem.nptr, ptag, pbits)) - continue; - elem_begin++; - // Found an object to mark - if (elem_begin < elem_end) { - // Haven't done with this one yet. Update the content and push it back - ary16->elem.begin = elem_begin; - ary16->begin = begin; - gc_repush_markdata(sp, gc_mark_array16_t); + } + } + gc_mark_push_remset(ptls, ary16_parent, nptr); +} + +// Mark chunk of large array +STATIC_INLINE void gc_mark_chunk(jl_ptls_t ptls, jl_gc_markqueue_t *mq, jl_gc_chunk_t *c) JL_NOTSAFEPOINT +{ + switch (c->cid) { + case GC_objary_chunk: { + jl_value_t *obj_parent = c->parent; + jl_value_t **obj_begin = c->begin; + jl_value_t **obj_end = c->end; + uint32_t step = c->step; + uintptr_t nptr = c->nptr; + gc_mark_objarray(ptls, obj_parent, obj_begin, obj_end, step, + nptr); + break; + } + case GC_ary8_chunk: { + jl_value_t *ary8_parent = c->parent; + jl_value_t **ary8_begin = c->begin; + jl_value_t **ary8_end = c->end; + uint8_t *elem_begin = (uint8_t *)c->elem_begin; + uint8_t *elem_end = (uint8_t *)c->elem_end; + uintptr_t nptr = c->nptr; + gc_mark_array8(ptls, ary8_parent, ary8_begin, ary8_end, elem_begin, elem_end, + nptr); + break; + } + case GC_ary16_chunk: { + jl_value_t *ary16_parent = c->parent; + jl_value_t **ary16_begin = c->begin; + jl_value_t **ary16_end = c->end; + uint16_t *elem_begin = (uint16_t *)c->elem_begin; + uint16_t *elem_end = (uint16_t *)c->elem_end; + uintptr_t nptr = c->nptr; + gc_mark_array16(ptls, ary16_parent, ary16_begin, ary16_end, elem_begin, elem_end, + nptr); + break; + } + case GC_finlist_chunk: { + jl_value_t **fl_begin = c->begin; + jl_value_t **fl_end = c->end; + gc_mark_finlist_(mq, fl_begin, fl_end); + break; + } + default: { + // `empty-chunk` should be checked by caller + jl_safe_printf("GC internal error: chunk mismatch cid=%d\n", c->cid); + abort(); + } + } +} + +// Mark gc frame +STATIC_INLINE void gc_mark_stack(jl_ptls_t ptls, jl_gcframe_t *s, uint32_t nroots, uintptr_t offset, + uintptr_t lb, uintptr_t ub) JL_NOTSAFEPOINT +{ + jl_gc_markqueue_t *mq = &ptls->mark_queue; + jl_value_t *new_obj; + uint32_t nr = nroots >> 2; + while (1) { + jl_value_t ***rts = (jl_value_t ***)(((void **)s) + 2); + for (uint32_t i = 0; i < nr; i++) { + if (nroots & 1) { + void **slot = (void **)gc_read_stack(&rts[i], offset, lb, ub); + new_obj = (jl_value_t *)gc_read_stack(slot, offset, lb, ub); } else { - begin += elsize; - if (begin < end) { - // Haven't done with this array yet. Reset the content and push it back - ary16->elem.begin = ary16->rebegin; - ary16->begin = begin; - gc_repush_markdata(sp, gc_mark_array16_t); - } - else { - // Finished scanning this one, finish up by checking the GC invariance - // and let the next item replacing the current one directly. - gc_mark_push_remset(ptls, ary16->elem.parent, ary16->elem.nptr); + new_obj = (jl_value_t *)gc_read_stack(&rts[i], offset, lb, ub); + if (gc_ptr_tag(new_obj, 1)) { + // handle tagged pointers in finalizer list + new_obj = (jl_value_t *)gc_ptr_clear_tag(new_obj, 1); + // skip over the finalizer fptr + i++; } + if (gc_ptr_tag(new_obj, 2)) + continue; + } + if (new_obj != NULL) { + gc_try_claim_and_push(mq, new_obj, NULL); + gc_heap_snapshot_record_frame_to_object_edge(s, new_obj); + } + } + jl_gcframe_t *sprev = (jl_gcframe_t *)gc_read_stack(&s->prev, offset, lb, ub); + if (sprev == NULL) + break; + gc_heap_snapshot_record_frame_to_frame_edge(s, sprev); + s = sprev; + uintptr_t new_nroots = gc_read_stack(&s->nroots, offset, lb, ub); + assert(new_nroots <= UINT32_MAX); + nroots = (uint32_t)new_nroots; + nr = nroots >> 2; + } +} + +// Mark exception stack +STATIC_INLINE void gc_mark_excstack(jl_ptls_t ptls, jl_excstack_t *excstack, size_t itr) JL_NOTSAFEPOINT +{ + jl_gc_markqueue_t *mq = &ptls->mark_queue; + jl_value_t *new_obj; + while (itr > 0) { + size_t bt_size = jl_excstack_bt_size(excstack, itr); + jl_bt_element_t *bt_data = jl_excstack_bt_data(excstack, itr); + for (size_t bt_index = 0; bt_index < bt_size; + bt_index += jl_bt_entry_size(bt_data + bt_index)) { + jl_bt_element_t *bt_entry = bt_data + bt_index; + if (jl_bt_is_native(bt_entry)) + continue; + // Found an extended backtrace entry: iterate over any + // GC-managed values inside. + size_t njlvals = jl_bt_num_jlvals(bt_entry); + for (size_t jlval_index = 0; jlval_index < njlvals; jlval_index++) { + new_obj = jl_bt_entry_jlvalue(bt_entry, jlval_index); + gc_try_claim_and_push(mq, new_obj, NULL); + gc_heap_snapshot_record_frame_to_object_edge(bt_entry, new_obj); } - return 1; } - elem_begin = ary16->rebegin; + // The exception comes last - mark it + new_obj = jl_excstack_exception(excstack, itr); + itr = jl_excstack_next(excstack, itr); + gc_try_claim_and_push(mq, new_obj, NULL); + gc_heap_snapshot_record_frame_to_object_edge(excstack, new_obj); } - gc_mark_push_remset(ptls, ary16->elem.parent, ary16->elem.nptr); - return 0; } +// Mark module binding +STATIC_INLINE void gc_mark_module_binding(jl_ptls_t ptls, jl_module_t *mb_parent, jl_binding_t **mb_begin, + jl_binding_t **mb_end, uintptr_t nptr, + uint8_t bits) JL_NOTSAFEPOINT +{ + jl_gc_markqueue_t *mq = &ptls->mark_queue; + for (; mb_begin < mb_end; mb_begin++) { + jl_binding_t *b = *mb_begin; + if (b == (jl_binding_t *)jl_nothing) + continue; + verify_parent1("module", mb_parent, mb_begin, "binding_buff"); + gc_try_claim_and_push(mq, b, &nptr); + } + jl_value_t *bindings = (jl_value_t *)jl_atomic_load_relaxed(&mb_parent->bindings); + gc_try_claim_and_push(mq, bindings, &nptr); + jl_value_t *bindingkeyset = (jl_value_t *)jl_atomic_load_relaxed(&mb_parent->bindingkeyset); + gc_try_claim_and_push(mq, bindingkeyset, &nptr); + gc_try_claim_and_push(mq, (jl_value_t *)mb_parent->parent, &nptr); + size_t nusings = mb_parent->usings.len; + if (nusings > 0) { + // this is only necessary because bindings for "using" modules + // are added only when accessed. therefore if a module is replaced + // after "using" it but before accessing it, this array might + // contain the only reference. + jl_value_t *obj_parent = (jl_value_t *)mb_parent; + jl_value_t **objary_begin = (jl_value_t **)mb_parent->usings.items; + jl_value_t **objary_end = objary_begin + nusings; + gc_mark_objarray(ptls, obj_parent, objary_begin, objary_end, 1, nptr); + } + else { + gc_mark_push_remset(ptls, (jl_value_t *)mb_parent, nptr); + } +} -// Scan an object with 8bits field descriptors. see `gc_mark_obj8_t` -STATIC_INLINE int gc_mark_scan_obj8(jl_ptls_t ptls, jl_gc_mark_sp_t *sp, gc_mark_obj8_t *obj8, - char *parent, uint8_t *begin, uint8_t *end, - jl_value_t **pnew_obj, uintptr_t *ptag, uint8_t *pbits) +void gc_mark_finlist_(jl_gc_markqueue_t *mq, jl_value_t **fl_begin, jl_value_t **fl_end) { - (void)jl_assume(obj8 == (gc_mark_obj8_t*)sp->data); - (void)jl_assume(begin < end); - for (; begin < end; begin++) { - jl_value_t **slot = &((jl_value_t**)parent)[*begin]; - *pnew_obj = *slot; - if (*pnew_obj) { - verify_parent2("object", parent, slot, "field(%d)", - gc_slot_to_fieldidx(parent, slot, (jl_datatype_t*)jl_typeof(parent))); - gc_heap_snapshot_record_object_edge((jl_value_t*)parent, slot); - } - if (!gc_try_setmark(*pnew_obj, &obj8->nptr, ptag, pbits)) + jl_value_t *new_obj; + // Decide whether need to chunk finlist + size_t nrefs = (fl_end - fl_begin); + if (nrefs > MAX_REFS_AT_ONCE) { + jl_gc_chunk_t c = {GC_finlist_chunk, NULL, fl_begin + MAX_REFS_AT_ONCE, fl_end, 0, 0, 0, 0}; + gc_chunkqueue_push(mq, &c); + fl_end = fl_begin + MAX_REFS_AT_ONCE; + } + for (; fl_begin < fl_end; fl_begin++) { + new_obj = *fl_begin; + if (__unlikely(!new_obj)) continue; - begin++; - // Found an object to mark - if (begin < end) { - // Haven't done with this one yet. Update the content and push it back - obj8->begin = begin; - gc_repush_markdata(sp, gc_mark_obj8_t); - } - else { - // Finished scanning this one, finish up by checking the GC invariance - // and let the next item replacing the current one directly. - gc_mark_push_remset(ptls, obj8->parent, obj8->nptr); + if (gc_ptr_tag(new_obj, 1)) { + new_obj = (jl_value_t *)gc_ptr_clear_tag(new_obj, 1); + fl_begin++; + assert(fl_begin < fl_end); } - return 1; - } - gc_mark_push_remset(ptls, obj8->parent, obj8->nptr); - return 0; -} - -// Scan an object with 16bits field descriptors. see `gc_mark_obj16_t` -STATIC_INLINE int gc_mark_scan_obj16(jl_ptls_t ptls, jl_gc_mark_sp_t *sp, gc_mark_obj16_t *obj16, - char *parent, uint16_t *begin, uint16_t *end, - jl_value_t **pnew_obj, uintptr_t *ptag, uint8_t *pbits) JL_NOTSAFEPOINT -{ - (void)jl_assume(obj16 == (gc_mark_obj16_t*)sp->data); - (void)jl_assume(begin < end); - for (; begin < end; begin++) { - jl_value_t **slot = &((jl_value_t**)parent)[*begin]; - *pnew_obj = *slot; - if (*pnew_obj) { - verify_parent2("object", parent, slot, "field(%d)", - gc_slot_to_fieldidx(parent, slot, (jl_datatype_t*)jl_typeof(parent))); - gc_heap_snapshot_record_object_edge((jl_value_t*)parent, slot); - } - if (!gc_try_setmark(*pnew_obj, &obj16->nptr, ptag, pbits)) + if (gc_ptr_tag(new_obj, 2)) continue; - begin++; - // Found an object to mark - if (begin < end) { - // Haven't done with this one yet. Update the content and push it back - obj16->begin = begin; - gc_repush_markdata(sp, gc_mark_obj16_t); - } - else { - // Finished scanning this one, finish up by checking the GC invariance - // and let the next item replacing the current one directly. - gc_mark_push_remset(ptls, obj16->parent, obj16->nptr); - } - return 1; - } - gc_mark_push_remset(ptls, obj16->parent, obj16->nptr); - return 0; -} - -// Scan an object with 32bits field descriptors. see `gc_mark_obj32_t` -STATIC_INLINE int gc_mark_scan_obj32(jl_ptls_t ptls, jl_gc_mark_sp_t *sp, gc_mark_obj32_t *obj32, - char *parent, uint32_t *begin, uint32_t *end, - jl_value_t **pnew_obj, uintptr_t *ptag, uint8_t *pbits) -{ - (void)jl_assume(obj32 == (gc_mark_obj32_t*)sp->data); - (void)jl_assume(begin < end); - for (; begin < end; begin++) { - jl_value_t **slot = &((jl_value_t**)parent)[*begin]; - *pnew_obj = *slot; - if (*pnew_obj) { - verify_parent2("object", parent, slot, "field(%d)", - gc_slot_to_fieldidx(parent, slot, (jl_datatype_t*)jl_typeof(parent))); - gc_heap_snapshot_record_object_edge((jl_value_t*)parent, slot); - } - if (!gc_try_setmark(*pnew_obj, &obj32->nptr, ptag, pbits)) - continue; - begin++; - // Found an object to mark - if (begin < end) { - // Haven't done with this one yet. Update the content and push it back - obj32->begin = begin; - gc_repush_markdata(sp, gc_mark_obj32_t); - } - else { - // Finished scanning this one, finish up by checking the GC invariance - // and let the next item replacing the current one directly. - gc_mark_push_remset(ptls, obj32->parent, obj32->nptr); - } - return 1; + gc_try_claim_and_push(mq, new_obj, NULL); } - gc_mark_push_remset(ptls, obj32->parent, obj32->nptr); - return 0; } -#if defined(__GNUC__) && !defined(_OS_EMSCRIPTEN_) -# define gc_mark_laddr(name) (&&name) -# define gc_mark_jmp(ptr) goto *(ptr) -#else -#define gc_mark_laddr(name) ((void*)(uintptr_t)GC_MARK_L_##name) -#define gc_mark_jmp(ptr) do { \ - switch ((int)(uintptr_t)ptr) { \ - case GC_MARK_L_marked_obj: \ - goto marked_obj; \ - case GC_MARK_L_scan_only: \ - goto scan_only; \ - case GC_MARK_L_finlist: \ - goto finlist; \ - case GC_MARK_L_objarray: \ - goto objarray; \ - case GC_MARK_L_array8: \ - goto array8; \ - case GC_MARK_L_array16: \ - goto array16; \ - case GC_MARK_L_obj8: \ - goto obj8; \ - case GC_MARK_L_obj16: \ - goto obj16; \ - case GC_MARK_L_obj32: \ - goto obj32; \ - case GC_MARK_L_stack: \ - goto stack; \ - case GC_MARK_L_excstack: \ - goto excstack; \ - case GC_MARK_L_module_binding: \ - goto module_binding; \ - default: \ - abort(); \ - } \ - } while (0) -#endif - -// This is the main marking loop. -// It uses an iterative (mostly) Depth-first search (DFS) to mark all the objects. -// Instead of using the native stack, two stacks are manually maintained, -// one (fixed-size) pc stack which stores the return address and one (variable-size) -// data stack which stores the local variables needed by the scanning code. -// Using a manually maintained stack has a few advantages -// -// 1. We can resize the stack as we go and never worry about stack overflow -// This is especitally useful when enters the GC in a deep call stack. -// It also removes the very deep GC call stack in a profile. -// 2. We can minimize the number of local variables to save on the stack. -// This includes minimizing the sizes of the stack frames and only saving variables -// that have been changed before making "function calls" (i.e. `goto mark;`) -// 3. We can perform end-of-loop tail-call optimization for common cases. -// 4. The marking can be interrupted more easily since all the states are maintained -// in a well-defined format already. -// This will be useful if we want to have incremental marking again. -// 5. The frames can be stolen by another thread more easily and it is not necessary -// to copy works to be stolen to another queue. Useful for parallel marking. -// (Will still require synchronization in stack popping of course.) -// 6. A flat function (i.e. no or very few function calls) also give the compiler -// opportunity to keep more states in registers that doesn't have to be spilled as often. -// -// We use two stacks so that the thief on another thread can steal the fixed sized pc stack -// and use that to figure out the size of the struct on the variable size data stack. -// -// The main disadvantages are that we bypass some stack-based CPU optimizations including the -// stack engine and return address prediction. -// Using two stacks also double the number of operations on the stack pointer -// though we still only need to use one of them (the pc stack pointer) for bounds check. -// In general, it seems that the reduction of stack memory ops and instructions count -// have a larger positive effect on the performance. =) - -// As a general guide we do not want to make non-inlined function calls in this function -// if possible since a large number of registers has to be spilled when that happens. -// This is especially true on on X86 which doesn't have many (any?) -// callee saved general purpose registers. -// (OTOH, the spill will likely make use of the stack engine which is otherwise idle so -// the performance impact is minimum as long as it's not in the hottest path) - -// There are three external entry points to the loop, corresponding to label -// `marked_obj`, `scan_only` and `finlist` (see the corresponding functions -// `gc_mark_queue_obj`, `gc_mark_queue_scan_obj` and `gc_mark_queue_finlist` above). -// The scanning of the object starts with `goto mark`, which updates the metadata and scans -// the object whose information is stored in `new_obj`, `tag` and `bits`. -// The branches in `mark` will dispatch the object to one of the scan "loop"s to be scanned -// as either a normal julia object or one of the special objects with specific storage format. -// Each of the scan "loop" will perform a DFS of the object in the following way -// -// 1. When encountering an pointer (julia object reference) slots, load, perform NULL check -// and atomically set the mark bits to determine if the object needs to be scanned. -// 2. If yes, it'll push itself back onto the mark stack (after updating fields that are changed) -// using `gc_repush_markdata` to increment the stack pointers. -// This step can also be replaced by a tail call by finishing up the marking of the current -// object when the end of the current object is reached. -// 3. Jump to `mark`. The marking of the current object will be resumed after the child is -// scanned by popping the stack frame back. -// -// Some of the special object scannings use BFS to simplify the code (Task and Module). - -// The jumps from the dispatch to the scan "loop"s are done by first pushing a frame -// to the stacks while only increment the data stack pointer before jumping to the loop -// This way the scan "loop" gets exactly what it expects after a stack pop. -// Additional optimizations are done for some of the common cases by skipping -// the unnecessary data stack pointer increment and the load from the stack -// (i.e. store to load forwarding). See `objary_loaded`, `obj8_loaded` and `obj16_loaded`. -JL_EXTENSION NOINLINE void gc_mark_loop(jl_ptls_t ptls, jl_gc_mark_sp_t sp) -{ - if (__unlikely(ptls == NULL)) { - gc_mark_label_addrs[GC_MARK_L_marked_obj] = gc_mark_laddr(marked_obj); - gc_mark_label_addrs[GC_MARK_L_scan_only] = gc_mark_laddr(scan_only); - gc_mark_label_addrs[GC_MARK_L_finlist] = gc_mark_laddr(finlist); - gc_mark_label_addrs[GC_MARK_L_objarray] = gc_mark_laddr(objarray); - gc_mark_label_addrs[GC_MARK_L_array8] = gc_mark_laddr(array8); - gc_mark_label_addrs[GC_MARK_L_array16] = gc_mark_laddr(array16); - gc_mark_label_addrs[GC_MARK_L_obj8] = gc_mark_laddr(obj8); - gc_mark_label_addrs[GC_MARK_L_obj16] = gc_mark_laddr(obj16); - gc_mark_label_addrs[GC_MARK_L_obj32] = gc_mark_laddr(obj32); - gc_mark_label_addrs[GC_MARK_L_stack] = gc_mark_laddr(stack); - gc_mark_label_addrs[GC_MARK_L_excstack] = gc_mark_laddr(excstack); - gc_mark_label_addrs[GC_MARK_L_module_binding] = gc_mark_laddr(module_binding); - return; - } - - jl_value_t *new_obj = NULL; - uintptr_t tag = 0; - uint8_t bits = 0; - int meta_updated = 0; - - gc_mark_objarray_t *objary; - jl_value_t **objary_begin; - jl_value_t **objary_end; - - gc_mark_array8_t *ary8; - gc_mark_array16_t *ary16; - - gc_mark_obj8_t *obj8; - char *obj8_parent; - uint8_t *obj8_begin; - uint8_t *obj8_end; - - gc_mark_obj16_t *obj16; - char *obj16_parent; - uint16_t *obj16_begin; - uint16_t *obj16_end; - -pop: - if (sp.pc == sp.pc_start) { - // TODO: stealing form another thread +// Mark finalizer list (or list of objects following same format) +void gc_mark_finlist(jl_gc_markqueue_t *mq, arraylist_t *list, size_t start) +{ + size_t len = list->len; + if (len <= start) return; - } - sp.pc--; - gc_mark_jmp(*sp.pc); // computed goto - -marked_obj: { - // An object that has been marked and needs have metadata updated and scanned. - gc_mark_marked_obj_t *obj = gc_pop_markdata(&sp, gc_mark_marked_obj_t); - new_obj = obj->obj; - tag = obj->tag; - bits = obj->bits; - goto mark; - } - -scan_only: { - // An object that has been marked and needs to be scanned. - gc_mark_marked_obj_t *obj = gc_pop_markdata(&sp, gc_mark_marked_obj_t); - new_obj = obj->obj; - tag = obj->tag; - bits = obj->bits; - meta_updated = 1; - goto mark; - } - -objarray: - objary = gc_pop_markdata(&sp, gc_mark_objarray_t); - objary_begin = objary->begin; - objary_end = objary->end; -objarray_loaded: - if (gc_mark_scan_objarray(ptls, &sp, objary, objary_begin, objary_end, - &new_obj, &tag, &bits)) - goto mark; - goto pop; - -array8: - ary8 = gc_pop_markdata(&sp, gc_mark_array8_t); - objary_begin = ary8->begin; - objary_end = ary8->end; - obj8_begin = ary8->elem.begin; - obj8_end = ary8->elem.end; -array8_loaded: - if (gc_mark_scan_array8(ptls, &sp, ary8, objary_begin, objary_end, obj8_begin, obj8_end, - &new_obj, &tag, &bits)) - goto mark; - goto pop; - -array16: - ary16 = gc_pop_markdata(&sp, gc_mark_array16_t); - objary_begin = ary16->begin; - objary_end = ary16->end; - obj16_begin = ary16->elem.begin; - obj16_end = ary16->elem.end; -array16_loaded: - if (gc_mark_scan_array16(ptls, &sp, ary16, objary_begin, objary_end, obj16_begin, obj16_end, - &new_obj, &tag, &bits)) - goto mark; - goto pop; - -obj8: - obj8 = gc_pop_markdata(&sp, gc_mark_obj8_t); - obj8_parent = (char*)obj8->parent; - obj8_begin = obj8->begin; - obj8_end = obj8->end; -obj8_loaded: - if (gc_mark_scan_obj8(ptls, &sp, obj8, obj8_parent, obj8_begin, obj8_end, - &new_obj, &tag, &bits)) - goto mark; - goto pop; - -obj16: - obj16 = gc_pop_markdata(&sp, gc_mark_obj16_t); - obj16_parent = (char*)obj16->parent; - obj16_begin = obj16->begin; - obj16_end = obj16->end; -obj16_loaded: - if (gc_mark_scan_obj16(ptls, &sp, obj16, obj16_parent, obj16_begin, obj16_end, - &new_obj, &tag, &bits)) - goto mark; - goto pop; - -obj32: { - gc_mark_obj32_t *obj32 = gc_pop_markdata(&sp, gc_mark_obj32_t); - char *parent = (char*)obj32->parent; - uint32_t *begin = obj32->begin; - uint32_t *end = obj32->end; - if (gc_mark_scan_obj32(ptls, &sp, obj32, parent, begin, end, &new_obj, &tag, &bits)) - goto mark; - goto pop; - } - -stack: { - // Scan the stack. see `gc_mark_stackframe_t` - // The task object this stack belongs to is being scanned separately as a normal - // 8bit field descriptor object. - gc_mark_stackframe_t *stack = gc_pop_markdata(&sp, gc_mark_stackframe_t); - jl_gcframe_t *s = stack->s; - uint32_t i = stack->i; - uint32_t nroots = stack->nroots; - uintptr_t offset = stack->offset; - uintptr_t lb = stack->lb; - uintptr_t ub = stack->ub; - uint32_t nr = nroots >> 2; - uintptr_t nptr = 0; - while (1) { - jl_value_t ***rts = (jl_value_t***)(((void**)s) + 2); - for (; i < nr; i++) { - if (nroots & 1) { - void **slot = (void**)gc_read_stack(&rts[i], offset, lb, ub); - new_obj = (jl_value_t*)gc_read_stack(slot, offset, lb, ub); - } - else { - new_obj = (jl_value_t*)gc_read_stack(&rts[i], offset, lb, ub); - if (gc_ptr_tag(new_obj, 1)) { - // handle tagged pointers in finalizer list - new_obj = gc_ptr_clear_tag(new_obj, 1); - // skip over the finalizer fptr - i++; - } - if (gc_ptr_tag(new_obj, 2)) - continue; - } - if (!gc_try_setmark(new_obj, &nptr, &tag, &bits)) - continue; - gc_heap_snapshot_record_frame_to_object_edge(s, new_obj); - i++; - if (i < nr) { - // Haven't done with this one yet. Update the content and push it back - stack->i = i; - gc_repush_markdata(&sp, gc_mark_stackframe_t); - } - // TODO stack addresses needs copy stack handling - else if ((s = (jl_gcframe_t*)gc_read_stack(&s->prev, offset, lb, ub))) { - gc_heap_snapshot_record_frame_to_frame_edge(stack->s, s); - stack->s = s; - stack->i = 0; - uintptr_t new_nroots = gc_read_stack(&s->nroots, offset, lb, ub); - assert(new_nroots <= UINT32_MAX); - stack->nroots = (uint32_t)new_nroots; - gc_repush_markdata(&sp, gc_mark_stackframe_t); - } - goto mark; - } - s = (jl_gcframe_t*)gc_read_stack(&s->prev, offset, lb, ub); - // walk up one stack frame - if (s != 0) { - gc_heap_snapshot_record_frame_to_frame_edge(stack->s, s); - stack->s = s; - i = 0; - uintptr_t new_nroots = gc_read_stack(&s->nroots, offset, lb, ub); - assert(new_nroots <= UINT32_MAX); - nroots = stack->nroots = (uint32_t)new_nroots; - nr = nroots >> 2; - continue; - } - goto pop; - } - } + jl_value_t **fl_begin = (jl_value_t **)list->items + start; + jl_value_t **fl_end = (jl_value_t **)list->items + len; + gc_mark_finlist_(mq, fl_begin, fl_end); +} -excstack: { - // Scan an exception stack - gc_mark_excstack_t *stackitr = gc_pop_markdata(&sp, gc_mark_excstack_t); - jl_excstack_t *excstack = stackitr->s; - size_t itr = stackitr->itr; - size_t bt_index = stackitr->bt_index; - size_t jlval_index = stackitr->jlval_index; - while (itr > 0) { - size_t bt_size = jl_excstack_bt_size(excstack, itr); - jl_bt_element_t *bt_data = jl_excstack_bt_data(excstack, itr); - for (; bt_index < bt_size; bt_index += jl_bt_entry_size(bt_data + bt_index)) { - jl_bt_element_t *bt_entry = bt_data + bt_index; - if (jl_bt_is_native(bt_entry)) - continue; - // Found an extended backtrace entry: iterate over any - // GC-managed values inside. - size_t njlvals = jl_bt_num_jlvals(bt_entry); - while (jlval_index < njlvals) { - new_obj = jl_bt_entry_jlvalue(bt_entry, jlval_index); - gc_heap_snapshot_record_frame_to_object_edge(bt_entry, new_obj); - uintptr_t nptr = 0; - jlval_index += 1; - if (gc_try_setmark(new_obj, &nptr, &tag, &bits)) { - stackitr->itr = itr; - stackitr->bt_index = bt_index; - stackitr->jlval_index = jlval_index; - gc_repush_markdata(&sp, gc_mark_excstack_t); - goto mark; - } - } - jlval_index = 0; - } - // The exception comes last - mark it - new_obj = jl_excstack_exception(excstack, itr); - gc_heap_snapshot_record_frame_to_object_edge(excstack, new_obj); - itr = jl_excstack_next(excstack, itr); - bt_index = 0; - jlval_index = 0; - uintptr_t nptr = 0; - if (gc_try_setmark(new_obj, &nptr, &tag, &bits)) { - stackitr->itr = itr; - stackitr->bt_index = bt_index; - stackitr->jlval_index = jlval_index; - gc_repush_markdata(&sp, gc_mark_excstack_t); - goto mark; - } - } - goto pop; - } - -module_binding: { - // Scan a module. see `gc_mark_binding_t` - // Other fields of the module will be scanned after the bindings are scanned - gc_mark_binding_t *binding = gc_pop_markdata(&sp, gc_mark_binding_t); - jl_binding_t **begin = binding->begin; - jl_binding_t **end = binding->end; - for (; begin < end; begin++) { - jl_binding_t *b = *begin; - if (b == (jl_binding_t*)jl_nothing) - continue; - verify_parent1("module", binding->parent, begin, "binding_buff"); - // Record the size used for the box for non-const bindings - gc_heap_snapshot_record_module_to_binding(binding->parent, b); - if (gc_try_setmark((jl_value_t*)b, &binding->nptr, &tag, &bits)) { - begin++; - binding->begin = begin; - gc_repush_markdata(&sp, gc_mark_binding_t); - new_obj = (jl_value_t*)b; - goto mark; - } - } - binding->begin = begin; - jl_module_t *m = binding->parent; - jl_value_t *bindings = (jl_value_t*)jl_atomic_load_relaxed(&m->bindings); - if (gc_try_setmark(bindings, &binding->nptr, &tag, &bits)) { - gc_repush_markdata(&sp, gc_mark_binding_t); - new_obj = (jl_value_t*)bindings; - goto mark; - } - jl_value_t *bindingkeyset = (jl_value_t*)jl_atomic_load_relaxed(&m->bindingkeyset); - if (gc_try_setmark(bindingkeyset, &binding->nptr, &tag, &bits)) { - gc_repush_markdata(&sp, gc_mark_binding_t); - new_obj = bindingkeyset; - goto mark; - } - int scanparent = gc_try_setmark((jl_value_t*)m->parent, &binding->nptr, &tag, &bits); - size_t nusings = m->usings.len; - if (nusings) { - // this is only necessary because bindings for "using" modules - // are added only when accessed. therefore if a module is replaced - // after "using" it but before accessing it, this array might - // contain the only reference. - objary_begin = (jl_value_t**)m->usings.items; - objary_end = objary_begin + nusings; - gc_mark_objarray_t data = {(jl_value_t*)m, objary_begin, objary_end, 1, binding->nptr}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(objarray), - &data, sizeof(data), 0); - // gc_mark_scan_objarray will eventually handle the remset for m - if (!scanparent) { - objary = (gc_mark_objarray_t*)sp.data; - goto objarray_loaded; - } - sp.data = (jl_gc_mark_data_t *)(((char*)sp.data) + sizeof(data)); - sp.pc++; - } - else { - // done with m - gc_mark_push_remset(ptls, (jl_value_t*)m, binding->nptr); - } - if (scanparent) { - new_obj = (jl_value_t*)m->parent; - goto mark; - } - goto pop; - } +JL_DLLEXPORT int jl_gc_mark_queue_obj(jl_ptls_t ptls, jl_value_t *obj) +{ + int may_claim = gc_try_setmark_tag(jl_astaggedvalue(obj), GC_MARKED); + if (may_claim) + gc_markqueue_push(&ptls->mark_queue, obj); + return may_claim; +} -finlist: { - // Scan a finalizer (or format compatible) list. see `gc_mark_finlist_t` - gc_mark_finlist_t *finlist = gc_pop_markdata(&sp, gc_mark_finlist_t); - jl_value_t **begin = finlist->begin; - jl_value_t **end = finlist->end; - for (; begin < end; begin++) { - new_obj = *begin; - if (__unlikely(!new_obj)) - continue; - if (gc_ptr_tag(new_obj, 1)) { - new_obj = (jl_value_t*)gc_ptr_clear_tag(new_obj, 1); - begin++; - assert(begin < end); - } - if (gc_ptr_tag(new_obj, 2)) - continue; - uintptr_t nptr = 0; - if (!gc_try_setmark(new_obj, &nptr, &tag, &bits)) - continue; - begin++; - // Found an object to mark - if (begin < end) { - // Haven't done with this one yet. Update the content and push it back - finlist->begin = begin; - gc_repush_markdata(&sp, gc_mark_finlist_t); - } - goto mark; - } - goto pop; - } +JL_DLLEXPORT void jl_gc_mark_queue_objarray(jl_ptls_t ptls, jl_value_t *parent, + jl_value_t **objs, size_t nobjs) +{ + uintptr_t nptr = (nobjs << 2) & (jl_astaggedvalue(parent)->bits.gc & 3); + gc_mark_objarray(ptls, parent, objs, objs + nobjs, 1, nptr); +} -mark: { - // Generic scanning entry point. - // Expects `new_obj`, `tag` and `bits` to be set correctly. -#ifdef JL_DEBUG_BUILD +// Enqueue and mark all outgoing references from `new_obj` which have not been marked +// yet. `meta_updated` is mostly used to make sure we don't update metadata twice for +// objects which have been enqueued into the `remset` +FORCE_INLINE void gc_mark_outrefs(jl_ptls_t ptls, jl_gc_markqueue_t *mq, void *_new_obj, + int meta_updated) +{ + jl_value_t *new_obj = (jl_value_t *)_new_obj; + mark_obj: { + #ifdef JL_DEBUG_BUILD if (new_obj == gc_findval) jl_raise_debugger(); -#endif + #endif jl_taggedvalue_t *o = jl_astaggedvalue(new_obj); - jl_datatype_t *vt = (jl_datatype_t*)tag; - int foreign_alloc = 0; + jl_datatype_t *vt = (jl_datatype_t *)(o->header & ~(uintptr_t)0xf); + uint8_t bits = (gc_old(o->header) && !mark_reset_age) ? GC_OLD_MARKED : GC_MARKED; int update_meta = __likely(!meta_updated && !gc_verifying); + int foreign_alloc = 0; if (update_meta && jl_object_in_image(new_obj)) { foreign_alloc = 1; update_meta = 0; } - meta_updated = 0; // Symbols are always marked assert(vt != jl_symbol_type); if (vt == jl_simplevector_type) { size_t l = jl_svec_len(new_obj); jl_value_t **data = jl_svec_data(new_obj); - size_t dtsz = l * sizeof(void*) + sizeof(jl_svec_t); + size_t dtsz = l * sizeof(void *) + sizeof(jl_svec_t); if (update_meta) gc_setmark(ptls, o, bits, dtsz); else if (foreign_alloc) objprofile_count(vt, bits == GC_OLD_MARKED, dtsz); + jl_value_t *objary_parent = new_obj; + jl_value_t **objary_begin = data; + jl_value_t **objary_end = data + l; + uint32_t step = 1; uintptr_t nptr = (l << 2) | (bits & GC_OLD); - objary_begin = data; - objary_end = data + l; - gc_mark_objarray_t markdata = {new_obj, objary_begin, objary_end, 1, nptr}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(objarray), - &markdata, sizeof(markdata), 0); - objary = (gc_mark_objarray_t*)sp.data; - goto objarray_loaded; + gc_mark_objarray(ptls, objary_parent, objary_begin, objary_end, step, nptr); } else if (vt->name == jl_array_typename) { - jl_array_t *a = (jl_array_t*)new_obj; + jl_array_t *a = (jl_array_t *)new_obj; jl_array_flags_t flags = a->flags; if (update_meta) { if (flags.pooled) @@ -2672,9 +2293,10 @@ mark: { else gc_setmark_big(ptls, o, bits); } - else if (foreign_alloc) + else if (foreign_alloc) { objprofile_count(vt, bits == GC_OLD_MARKED, sizeof(jl_array_t)); - if (flags.how ==0){ + } + if (flags.how == 0) { void *data_ptr = (char*)a + sizeof(jl_array_t) +jl_array_ndimwords(a->flags.ndims) * sizeof(size_t); gc_heap_snapshot_record_hidden_edge(new_obj, data_ptr, jl_array_nbytes(a), 2); } @@ -2702,103 +2324,83 @@ mark: { else if (flags.how == 3) { jl_value_t *owner = jl_array_data_owner(a); uintptr_t nptr = (1 << 2) | (bits & GC_OLD); + gc_try_claim_and_push(mq, owner, &nptr); gc_heap_snapshot_record_internal_array_edge(new_obj, owner); - int markowner = gc_try_setmark(owner, &nptr, &tag, &bits); gc_mark_push_remset(ptls, new_obj, nptr); - if (markowner) { - new_obj = owner; - goto mark; - } - goto pop; + return; } - if (a->data == NULL || jl_array_len(a) == 0) - goto pop; + if (!a->data || jl_array_len(a) == 0) + return; if (flags.ptrarray) { - if ((jl_datatype_t*)jl_tparam0(vt) == jl_symbol_type) - goto pop; + if ((jl_datatype_t *)jl_tparam0(vt) == jl_symbol_type) + return; size_t l = jl_array_len(a); + jl_value_t *objary_parent = new_obj; + jl_value_t **objary_begin = (jl_value_t **)a->data; + jl_value_t **objary_end = objary_begin + l; + uint32_t step = 1; uintptr_t nptr = (l << 2) | (bits & GC_OLD); - objary_begin = (jl_value_t**)a->data; - objary_end = objary_begin + l; - gc_mark_objarray_t markdata = {new_obj, objary_begin, objary_end, 1, nptr}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(objarray), - &markdata, sizeof(markdata), 0); - objary = (gc_mark_objarray_t*)sp.data; - goto objarray_loaded; + gc_mark_objarray(ptls, objary_parent, objary_begin, objary_end, step, nptr); } else if (flags.hasptr) { - jl_datatype_t *et = (jl_datatype_t*)jl_tparam0(vt); + jl_datatype_t *et = (jl_datatype_t *)jl_tparam0(vt); const jl_datatype_layout_t *layout = et->layout; unsigned npointers = layout->npointers; - unsigned elsize = a->elsize / sizeof(jl_value_t*); + unsigned elsize = a->elsize / sizeof(jl_value_t *); size_t l = jl_array_len(a); + jl_value_t *objary_parent = new_obj; + jl_value_t **objary_begin = (jl_value_t **)a->data; + jl_value_t **objary_end = objary_begin + l * elsize; + uint32_t step = elsize; uintptr_t nptr = ((l * npointers) << 2) | (bits & GC_OLD); - objary_begin = (jl_value_t**)a->data; - objary_end = objary_begin + l * elsize; if (npointers == 1) { // TODO: detect anytime time stride is uniform? objary_begin += layout->first_ptr; - gc_mark_objarray_t markdata = {new_obj, objary_begin, objary_end, elsize, nptr}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(objarray), - &markdata, sizeof(markdata), 0); - objary = (gc_mark_objarray_t*)sp.data; - goto objarray_loaded; + gc_mark_objarray(ptls, objary_parent, objary_begin, objary_end, step, nptr); } else if (layout->fielddesc_type == 0) { - obj8_begin = (uint8_t*)jl_dt_layout_ptrs(layout); - obj8_end = obj8_begin + npointers; - gc_mark_array8_t markdata = {objary_begin, objary_end, obj8_begin, {new_obj, obj8_begin, obj8_end, nptr}}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(array8), - &markdata, sizeof(markdata), 0); - ary8 = (gc_mark_array8_t*)sp.data; - goto array8_loaded; + uint8_t *obj8_begin = (uint8_t *)jl_dt_layout_ptrs(layout); + uint8_t *obj8_end = obj8_begin + npointers; + gc_mark_array8(ptls, objary_parent, objary_begin, objary_end, obj8_begin, + obj8_end, nptr); } else if (layout->fielddesc_type == 1) { - obj16_begin = (uint16_t*)jl_dt_layout_ptrs(layout); - obj16_end = obj16_begin + npointers; - gc_mark_array16_t markdata = {objary_begin, objary_end, obj16_begin, {new_obj, obj16_begin, obj16_end, nptr}}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(array16), - &markdata, sizeof(markdata), 0); - ary16 = (gc_mark_array16_t*)sp.data; - goto array16_loaded; + uint16_t *obj16_begin = (uint16_t *)jl_dt_layout_ptrs(layout); + uint16_t *obj16_end = obj16_begin + npointers; + gc_mark_array16(ptls, objary_parent, objary_begin, objary_end, obj16_begin, + obj16_end, nptr); } else { assert(0 && "unimplemented"); } } - goto pop; } else if (vt == jl_module_type) { if (update_meta) gc_setmark(ptls, o, bits, sizeof(jl_module_t)); else if (foreign_alloc) objprofile_count(vt, bits == GC_OLD_MARKED, sizeof(jl_module_t)); - jl_module_t *m = (jl_module_t*)new_obj; - jl_svec_t *bindings = jl_atomic_load_relaxed(&m->bindings); + jl_module_t *mb_parent = (jl_module_t *)new_obj; + jl_svec_t *bindings = jl_atomic_load_relaxed(&mb_parent->bindings); jl_binding_t **table = (jl_binding_t**)jl_svec_data(bindings); size_t bsize = jl_svec_len(bindings); - uintptr_t nptr = ((bsize + m->usings.len + 1) << 2) | (bits & GC_OLD); - gc_mark_binding_t markdata = {m, table + 1, table + bsize, nptr}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(module_binding), - &markdata, sizeof(markdata), 0); - sp.data = (jl_gc_mark_data_t *)(((char*)sp.data) + sizeof(markdata)); - goto module_binding; + uintptr_t nptr = ((bsize + mb_parent->usings.len + 1) << 2) | (bits & GC_OLD); + jl_binding_t **mb_begin = table + 1; + jl_binding_t **mb_end = table + bsize; + gc_mark_module_binding(ptls, mb_parent, mb_begin, mb_end, nptr, bits); } else if (vt == jl_task_type) { if (update_meta) gc_setmark(ptls, o, bits, sizeof(jl_task_t)); else if (foreign_alloc) objprofile_count(vt, bits == GC_OLD_MARKED, sizeof(jl_task_t)); - jl_task_t *ta = (jl_task_t*)new_obj; + jl_task_t *ta = (jl_task_t *)new_obj; gc_scrub_record_task(ta); if (gc_cblist_task_scanner) { - export_gc_state(ptls, &sp); int16_t tid = jl_atomic_load_relaxed(&ta->tid); - gc_invoke_callbacks(jl_gc_cb_task_scanner_t, - gc_cblist_task_scanner, - (ta, tid != -1 && ta == gc_all_tls_states[tid]->root_task)); - import_gc_state(ptls, &sp); + gc_invoke_callbacks(jl_gc_cb_task_scanner_t, gc_cblist_task_scanner, + (ta, tid != -1 && ta == gc_all_tls_states[tid]->root_task)); } -#ifdef COPY_STACKS + #ifdef COPY_STACKS void *stkbuf = ta->stkbuf; if (stkbuf && ta->copy_stack) { gc_setmark_buf_(ptls, stkbuf, bits, ta->bufsz); @@ -2807,14 +2409,14 @@ mark: { // TODO: edge to stack data // TODO: synthetic node for stack data (how big is it?) } -#endif + #endif jl_gcframe_t *s = ta->gcstack; size_t nroots; uintptr_t offset = 0; uintptr_t lb = 0; uintptr_t ub = (uintptr_t)-1; -#ifdef COPY_STACKS - if (stkbuf && ta->copy_stack && ta->ptls == NULL) { + #ifdef COPY_STACKS + if (stkbuf && ta->copy_stack && !ta->ptls) { int16_t tid = jl_atomic_load_relaxed(&ta->tid); assert(tid >= 0); jl_ptls_t ptls2 = gc_all_tls_states[tid]; @@ -2822,38 +2424,38 @@ mark: { lb = ub - ta->copy_stack; offset = (uintptr_t)stkbuf - lb; } -#endif - if (s) { + #endif + if (s != NULL) { nroots = gc_read_stack(&s->nroots, offset, lb, ub); gc_heap_snapshot_record_task_to_frame_edge(ta, s); - assert(nroots <= UINT32_MAX); - gc_mark_stackframe_t stackdata = {s, 0, (uint32_t)nroots, offset, lb, ub}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(stack), - &stackdata, sizeof(stackdata), 1); + gc_mark_stack(ptls, s, (uint32_t)nroots, offset, lb, ub); } if (ta->excstack) { - gc_heap_snapshot_record_task_to_frame_edge(ta, ta->excstack); - gc_setmark_buf_(ptls, ta->excstack, bits, sizeof(jl_excstack_t) + - sizeof(uintptr_t)*ta->excstack->reserved_size); - gc_mark_excstack_t stackdata = {ta->excstack, ta->excstack->top, 0, 0}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(excstack), - &stackdata, sizeof(stackdata), 1); + jl_excstack_t *excstack = ta->excstack; + gc_heap_snapshot_record_task_to_frame_edge(ta, excstack); + size_t itr = ta->excstack->top; + gc_setmark_buf_(ptls, excstack, bits, + sizeof(jl_excstack_t) + + sizeof(uintptr_t) * excstack->reserved_size); + gc_mark_excstack(ptls, excstack, itr); } const jl_datatype_layout_t *layout = jl_task_type->layout; assert(layout->fielddesc_type == 0); assert(layout->nfields > 0); uint32_t npointers = layout->npointers; - obj8_begin = (uint8_t*)jl_dt_layout_ptrs(layout); - obj8_end = obj8_begin + npointers; + char *obj8_parent = (char *)ta; + uint8_t *obj8_begin = (uint8_t *)jl_dt_layout_ptrs(layout); + uint8_t *obj8_end = obj8_begin + npointers; // assume tasks always reference young objects: set lowest bit uintptr_t nptr = (npointers << 2) | 1 | bits; - gc_mark_obj8_t markdata = {new_obj, obj8_begin, obj8_end, nptr}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(obj8), - &markdata, sizeof(markdata), 0); - obj8 = (gc_mark_obj8_t*)sp.data; - obj8_parent = (char*)ta; - goto obj8_loaded; + new_obj = gc_mark_obj8(ptls, obj8_parent, obj8_begin, obj8_end, nptr); + if (new_obj != NULL) { + if (!meta_updated) + goto mark_obj; + else + gc_markqueue_push(mq, new_obj); + } } else if (vt == jl_string_type) { size_t dtsz = jl_string_len(new_obj) + sizeof(size_t) + 1; @@ -2861,145 +2463,216 @@ mark: { gc_setmark(ptls, o, bits, dtsz); else if (foreign_alloc) objprofile_count(vt, bits == GC_OLD_MARKED, dtsz); - goto pop; } else { if (__unlikely(!jl_is_datatype(vt))) - gc_assert_datatype_fail(ptls, vt, sp); + gc_assert_datatype_fail(ptls, vt, mq); size_t dtsz = jl_datatype_size(vt); if (update_meta) gc_setmark(ptls, o, bits, dtsz); else if (foreign_alloc) objprofile_count(vt, bits == GC_OLD_MARKED, dtsz); if (vt == jl_weakref_type) - goto pop; + return; const jl_datatype_layout_t *layout = vt->layout; uint32_t npointers = layout->npointers; if (npointers == 0) - goto pop; - uintptr_t nptr = npointers << 2 | (bits & GC_OLD); - assert((layout->nfields > 0 || layout->fielddesc_type == 3) && "opaque types should have been handled specially"); + return; + uintptr_t nptr = (npointers << 2 | (bits & GC_OLD)); + assert((layout->nfields > 0 || layout->fielddesc_type == 3) && + "opaque types should have been handled specially"); if (layout->fielddesc_type == 0) { - obj8_parent = (char*)new_obj; - obj8_begin = (uint8_t*)jl_dt_layout_ptrs(layout); - obj8_end = obj8_begin + npointers; + char *obj8_parent = (char *)new_obj; + uint8_t *obj8_begin = (uint8_t *)jl_dt_layout_ptrs(layout); + uint8_t *obj8_end = obj8_begin + npointers; assert(obj8_begin < obj8_end); - gc_mark_obj8_t markdata = {new_obj, obj8_begin, obj8_end, nptr}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(obj8), - &markdata, sizeof(markdata), 0); - obj8 = (gc_mark_obj8_t*)sp.data; - goto obj8_loaded; + new_obj = gc_mark_obj8(ptls, obj8_parent, obj8_begin, obj8_end, nptr); + if (new_obj != NULL) { + if (!meta_updated) + goto mark_obj; + else + gc_markqueue_push(mq, new_obj); + } } else if (layout->fielddesc_type == 1) { - obj16_parent = (char*)new_obj; - obj16_begin = (uint16_t*)jl_dt_layout_ptrs(layout); - obj16_end = obj16_begin + npointers; + char *obj16_parent = (char *)new_obj; + uint16_t *obj16_begin = (uint16_t *)jl_dt_layout_ptrs(layout); + uint16_t *obj16_end = obj16_begin + npointers; assert(obj16_begin < obj16_end); - gc_mark_obj16_t markdata = {new_obj, obj16_begin, obj16_end, nptr}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(obj16), - &markdata, sizeof(markdata), 0); - obj16 = (gc_mark_obj16_t*)sp.data; - goto obj16_loaded; + new_obj = gc_mark_obj16(ptls, obj16_parent, obj16_begin, obj16_end, nptr); + if (new_obj != NULL) { + if (!meta_updated) + goto mark_obj; + else + gc_markqueue_push(mq, new_obj); + } } else if (layout->fielddesc_type == 2) { // This is very uncommon // Do not do store to load forwarding to save some code size - uint32_t *obj32_begin = (uint32_t*)jl_dt_layout_ptrs(layout); + char *obj32_parent = (char *)new_obj; + uint32_t *obj32_begin = (uint32_t *)jl_dt_layout_ptrs(layout); uint32_t *obj32_end = obj32_begin + npointers; - gc_mark_obj32_t markdata = {new_obj, obj32_begin, obj32_end, nptr}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(obj32), - &markdata, sizeof(markdata), 0); - sp.data = (jl_gc_mark_data_t *)(((char*)sp.data) + sizeof(markdata)); - goto obj32; + assert(obj32_begin < obj32_end); + new_obj = gc_mark_obj32(ptls, obj32_parent, obj32_begin, obj32_end, nptr); + if (new_obj != NULL) { + if (!meta_updated) + goto mark_obj; + else + gc_markqueue_push(mq, new_obj); + } } else { assert(layout->fielddesc_type == 3); - jl_fielddescdyn_t *desc = (jl_fielddescdyn_t*)jl_dt_layout_fields(layout); + jl_fielddescdyn_t *desc = (jl_fielddescdyn_t *)jl_dt_layout_fields(layout); int old = jl_astaggedvalue(new_obj)->bits.gc & 2; - export_gc_state(ptls, &sp); uintptr_t young = desc->markfunc(ptls, new_obj); - import_gc_state(ptls, &sp); if (old && young) gc_mark_push_remset(ptls, new_obj, young * 4 + 3); - goto pop; } } } } -static void jl_gc_queue_thread_local(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, - jl_ptls_t ptls2) +// Used in gc-debug +void gc_mark_loop_(jl_ptls_t ptls, jl_gc_markqueue_t *mq) +{ + while (1) { + void *new_obj = (void *)gc_markqueue_pop(&ptls->mark_queue); + // No more objects to mark + if (new_obj == NULL) { + // TODO: work-stealing comes here... + return; + } + gc_mark_outrefs(ptls, mq, new_obj, 0); + } +} + +// Drain items from worker's own chunkqueue +void gc_drain_own_chunkqueue(jl_ptls_t ptls, jl_gc_markqueue_t *mq) +{ + jl_gc_chunk_t c = {.cid = GC_empty_chunk}; + do { + c = gc_chunkqueue_pop(mq); + if (c.cid != GC_empty_chunk) { + gc_mark_chunk(ptls, mq, &c); + gc_mark_loop_(ptls, mq); + } + } while (c.cid != GC_empty_chunk); +} + +// Main mark loop. Single stack (allocated on the heap) of `jl_value_t *` +// is used to keep track of processed items. Maintaning this stack (instead of +// native one) avoids stack overflow when marking deep objects and +// makes it easier to implement parallel marking via work-stealing +JL_EXTENSION NOINLINE void gc_mark_loop(jl_ptls_t ptls) +{ + gc_mark_loop_(ptls, &ptls->mark_queue); + gc_drain_own_chunkqueue(ptls, &ptls->mark_queue); +} + +static void gc_premark(jl_ptls_t ptls2) +{ + arraylist_t *remset = ptls2->heap.remset; + ptls2->heap.remset = ptls2->heap.last_remset; + ptls2->heap.last_remset = remset; + ptls2->heap.remset->len = 0; + ptls2->heap.remset_nptr = 0; + // avoid counting remembered objects + // in `perm_scanned_bytes` + size_t len = remset->len; + void **items = remset->items; + for (size_t i = 0; i < len; i++) { + jl_value_t *item = (jl_value_t *)items[i]; + objprofile_count(jl_typeof(item), 2, 0); + jl_astaggedvalue(item)->bits.gc = GC_OLD_MARKED; + } +} + +static void gc_queue_thread_local(jl_gc_markqueue_t *mq, jl_ptls_t ptls2) { jl_task_t *task; task = ptls2->root_task; - if (task) { - gc_mark_queue_obj(gc_cache, sp, task); + if (task != NULL) { + gc_try_claim_and_push(mq, task, NULL); gc_heap_snapshot_record_root((jl_value_t*)task, "root task"); } task = jl_atomic_load_relaxed(&ptls2->current_task); - if (task) { - gc_mark_queue_obj(gc_cache, sp, task); + if (task != NULL) { + gc_try_claim_and_push(mq, task, NULL); gc_heap_snapshot_record_root((jl_value_t*)task, "current task"); } task = ptls2->next_task; - if (task) { - gc_mark_queue_obj(gc_cache, sp, task); + if (task != NULL) { + gc_try_claim_and_push(mq, task, NULL); gc_heap_snapshot_record_root((jl_value_t*)task, "next task"); } task = ptls2->previous_task; - if (task) { // shouldn't be necessary, but no reason not to - gc_mark_queue_obj(gc_cache, sp, task); + if (task != NULL) { + gc_try_claim_and_push(mq, task, NULL); gc_heap_snapshot_record_root((jl_value_t*)task, "previous task"); } if (ptls2->previous_exception) { - gc_mark_queue_obj(gc_cache, sp, ptls2->previous_exception); + gc_try_claim_and_push(mq, ptls2->previous_exception, NULL); gc_heap_snapshot_record_root((jl_value_t*)ptls2->previous_exception, "previous exception"); } } +static void gc_queue_bt_buf(jl_gc_markqueue_t *mq, jl_ptls_t ptls2) +{ + jl_bt_element_t *bt_data = ptls2->bt_data; + size_t bt_size = ptls2->bt_size; + for (size_t i = 0; i < bt_size; i += jl_bt_entry_size(bt_data + i)) { + jl_bt_element_t *bt_entry = bt_data + i; + if (jl_bt_is_native(bt_entry)) + continue; + size_t njlvals = jl_bt_num_jlvals(bt_entry); + for (size_t j = 0; j < njlvals; j++) + gc_try_claim_and_push(mq, jl_bt_entry_jlvalue(bt_entry, j), NULL); + } +} + +static void gc_queue_remset(jl_ptls_t ptls, jl_ptls_t ptls2) +{ + size_t len = ptls2->heap.last_remset->len; + void **items = ptls2->heap.last_remset->items; + for (size_t i = 0; i < len; i++) { + // Objects in the `remset` are already marked, + // so a `gc_try_claim_and_push` wouldn't work here + gc_mark_outrefs(ptls, &ptls->mark_queue, (jl_value_t *)items[i], 1); + } +} + extern jl_value_t *cmpswap_names JL_GLOBALLY_ROOTED; extern jl_task_t *wait_empty JL_GLOBALLY_ROOTED; // mark the initial root set -static void mark_roots(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp) +static void gc_mark_roots(jl_gc_markqueue_t *mq) { // modules - gc_mark_queue_obj(gc_cache, sp, jl_main_module); + gc_try_claim_and_push(mq, jl_main_module, NULL); gc_heap_snapshot_record_root((jl_value_t*)jl_main_module, "main_module"); - // invisible builtin values - if (jl_an_empty_vec_any != NULL) - gc_mark_queue_obj(gc_cache, sp, jl_an_empty_vec_any); - if (jl_module_init_order != NULL) - gc_mark_queue_obj(gc_cache, sp, jl_module_init_order); + gc_try_claim_and_push(mq, jl_an_empty_vec_any, NULL); + gc_try_claim_and_push(mq, jl_module_init_order, NULL); for (size_t i = 0; i < jl_current_modules.size; i += 2) { if (jl_current_modules.table[i + 1] != HT_NOTFOUND) { - gc_mark_queue_obj(gc_cache, sp, jl_current_modules.table[i]); + gc_try_claim_and_push(mq, jl_current_modules.table[i], NULL); gc_heap_snapshot_record_root((jl_value_t*)jl_current_modules.table[i], "top level module"); } } - gc_mark_queue_obj(gc_cache, sp, jl_anytuple_type_type); + gc_try_claim_and_push(mq, jl_anytuple_type_type, NULL); for (size_t i = 0; i < N_CALL_CACHE; i++) { jl_typemap_entry_t *v = jl_atomic_load_relaxed(&call_cache[i]); - if (v != NULL) { - gc_mark_queue_obj(gc_cache, sp, v); - } - } - if (jl_all_methods != NULL) { - gc_mark_queue_obj(gc_cache, sp, jl_all_methods); + gc_try_claim_and_push(mq, v, NULL); } - if (_jl_debug_method_invalidation != NULL) - gc_mark_queue_obj(gc_cache, sp, _jl_debug_method_invalidation); - if (jl_build_ids != NULL) - gc_mark_queue_obj(gc_cache, sp, jl_build_ids); - + gc_try_claim_and_push(mq, jl_all_methods, NULL); + gc_try_claim_and_push(mq, _jl_debug_method_invalidation, NULL); + gc_try_claim_and_push(mq, jl_build_ids, NULL); // constants - gc_mark_queue_obj(gc_cache, sp, jl_emptytuple_type); - if (cmpswap_names != NULL) - gc_mark_queue_obj(gc_cache, sp, cmpswap_names); - if (wait_empty != NULL) - gc_mark_queue_obj(gc_cache, sp, wait_empty); - gc_mark_queue_obj(gc_cache, sp, jl_global_roots_table); + gc_try_claim_and_push(mq, jl_emptytuple_type, NULL); + gc_try_claim_and_push(mq, cmpswap_names, NULL); + gc_try_claim_and_push(mq, jl_global_roots_table, NULL); } // find unmarked objects that need to be finalized from the finalizer list "list". @@ -3134,47 +2807,6 @@ JL_DLLEXPORT int64_t jl_gc_live_bytes(void) return live_bytes; } -static void jl_gc_premark(jl_ptls_t ptls2) -{ - arraylist_t *remset = ptls2->heap.remset; - ptls2->heap.remset = ptls2->heap.last_remset; - ptls2->heap.last_remset = remset; - ptls2->heap.remset->len = 0; - ptls2->heap.remset_nptr = 0; - - // avoid counting remembered objects & bindings twice - // in `perm_scanned_bytes` - size_t len = remset->len; - void **items = remset->items; - for (size_t i = 0; i < len; i++) { - jl_value_t *item = (jl_value_t*)items[i]; - objprofile_count(jl_typeof(item), 2, 0); - jl_astaggedvalue(item)->bits.gc = GC_OLD_MARKED; - } -} - -static void jl_gc_queue_remset(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, jl_ptls_t ptls2) -{ - size_t len = ptls2->heap.last_remset->len; - void **items = ptls2->heap.last_remset->items; - for (size_t i = 0; i < len; i++) - gc_mark_queue_scan_obj(gc_cache, sp, (jl_value_t*)items[i]); -} - -static void jl_gc_queue_bt_buf(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, jl_ptls_t ptls2) -{ - jl_bt_element_t *bt_data = ptls2->bt_data; - size_t bt_size = ptls2->bt_size; - for (size_t i = 0; i < bt_size; i += jl_bt_entry_size(bt_data + i)) { - jl_bt_element_t *bt_entry = bt_data + i; - if (jl_bt_is_native(bt_entry)) - continue; - size_t njlvals = jl_bt_num_jlvals(bt_entry); - for (size_t j = 0; j < njlvals; j++) - gc_mark_queue_obj(gc_cache, sp, jl_bt_entry_jlvalue(bt_entry, j)); - } -} - size_t jl_maxrss(void); // Only one thread should be running in this function @@ -3182,9 +2814,7 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) { combine_thread_gc_counts(&gc_num); - jl_gc_mark_cache_t *gc_cache = &ptls->gc_cache; - jl_gc_mark_sp_t sp; - gc_mark_sp_init(gc_cache, &sp); + jl_gc_markqueue_t *mq = &ptls->mark_queue; uint64_t gc_start_time = jl_hrtime(); int64_t last_perm_scanned_bytes = perm_scanned_bytes; @@ -3196,33 +2826,30 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) for (int t_i = 0; t_i < gc_n_threads; t_i++) { jl_ptls_t ptls2 = gc_all_tls_states[t_i]; if (ptls2 != NULL) - jl_gc_premark(ptls2); + gc_premark(ptls2); } assert(gc_n_threads); for (int t_i = 0; t_i < gc_n_threads; t_i++) { jl_ptls_t ptls2 = gc_all_tls_states[t_i]; - if (ptls2 == NULL) - continue; - // 2.1. mark every object in the `last_remsets` - jl_gc_queue_remset(gc_cache, &sp, ptls2); - // 2.2. mark every thread local root - jl_gc_queue_thread_local(gc_cache, &sp, ptls2); - // 2.3. mark any managed objects in the backtrace buffer - // TODO: treat these as roots for gc_heap_snapshot_record - jl_gc_queue_bt_buf(gc_cache, &sp, ptls2); + if (ptls2 != NULL) { + // 2.1. mark every thread local root + gc_queue_thread_local(mq, ptls2); + // 2.2. mark any managed objects in the backtrace buffer + // TODO: treat these as roots for gc_heap_snapshot_record + gc_queue_bt_buf(mq, ptls2); + // 2.3. mark every object in the `last_remsets` and `rem_binding` + gc_queue_remset(ptls, ptls2); + } } // 3. walk roots - mark_roots(gc_cache, &sp); + gc_mark_roots(mq); if (gc_cblist_root_scanner) { - export_gc_state(ptls, &sp); gc_invoke_callbacks(jl_gc_cb_root_scanner_t, gc_cblist_root_scanner, (collection)); - import_gc_state(ptls, &sp); } - gc_mark_loop(ptls, sp); - gc_mark_sp_init(gc_cache, &sp); + gc_mark_loop(ptls); gc_num.since_sweep += gc_num.allocd; JL_PROBE_GC_MARK_END(scanned_bytes, perm_scanned_bytes); gc_settime_premark_end(); @@ -3243,9 +2870,8 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) assert(gc_n_threads); for (int i = 0; i < gc_n_threads; i++) { jl_ptls_t ptls2 = gc_all_tls_states[i]; - if (ptls2 == NULL) - continue; - sweep_finalizer_list(&ptls2->finalizers); + if (ptls2 != NULL) + sweep_finalizer_list(&ptls2->finalizers); } if (prev_sweep_full) { sweep_finalizer_list(&finalizer_list_marked); @@ -3254,15 +2880,13 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) assert(gc_n_threads); for (int i = 0; i < gc_n_threads; i++) { jl_ptls_t ptls2 = gc_all_tls_states[i]; - if (ptls2 == NULL) - continue; - gc_mark_queue_finlist(gc_cache, &sp, &ptls2->finalizers, 0); + if (ptls2 != NULL) + gc_mark_finlist(mq, &ptls2->finalizers, 0); } - gc_mark_queue_finlist(gc_cache, &sp, &finalizer_list_marked, orig_marked_len); + gc_mark_finlist(mq, &finalizer_list_marked, orig_marked_len); // "Flush" the mark stack before flipping the reset_age bit // so that the objects are not incorrectly reset. - gc_mark_loop(ptls, sp); - gc_mark_sp_init(gc_cache, &sp); + gc_mark_loop(ptls); // Conservative marking relies on age to tell allocated objects // and freelist entries apart. mark_reset_age = !jl_gc_conservative_gc_support_enabled(); @@ -3270,8 +2894,8 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) // `to_finalize` list. These objects are only reachable from this list // and should not be referenced by any old objects so this won't break // the GC invariant. - gc_mark_queue_finlist(gc_cache, &sp, &to_finalize, 0); - gc_mark_loop(ptls, sp); + gc_mark_finlist(mq, &to_finalize, 0); + gc_mark_loop(ptls); mark_reset_age = 0; gc_settime_postmark_end(); @@ -3297,9 +2921,8 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) assert(gc_n_threads); for (int i = 0; i < gc_n_threads; i++) { jl_ptls_t ptls2 = gc_all_tls_states[i]; - if (ptls2 == NULL) - continue; - nptr += ptls2->heap.remset_nptr; + if (ptls2 != NULL) + nptr += ptls2->heap.remset_nptr; } // many pointers in the intergen frontier => "quick" mark is not quick @@ -3349,7 +2972,7 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) promoted_bytes = 0; } scanned_bytes = 0; - // 5. start sweeping + // 6. start sweeping uint64_t start_sweep_time = jl_hrtime(); JL_PROBE_GC_SWEEP_BEGIN(sweep_full); sweep_weak_refs(); @@ -3370,7 +2993,7 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) gc_num.sweep_time = sweep_time; // sweeping is over - // 6. if it is a quick sweep, put back the remembered objects in queued state + // 7. if it is a quick sweep, put back the remembered objects in queued state // so that we don't trigger the barrier again on them. assert(gc_n_threads); for (int t_i = 0; t_i < gc_n_threads; t_i++) { @@ -3400,7 +3023,6 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) } #endif - _report_gc_finished(pause, gc_num.freed, sweep_full, recollect); gc_final_pause_end(gc_start_time, gc_end_time); @@ -3417,17 +3039,18 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) live_bytes += -gc_num.freed + gc_num.since_sweep; if (collection == JL_GC_AUTO) { - // If the current interval is larger than half the live data decrease the interval - int64_t half = live_bytes/2; - if (gc_num.interval > half) gc_num.interval = half; - // But never go below default - if (gc_num.interval < default_collect_interval) gc_num.interval = default_collect_interval; + // If the current interval is larger than half the live data decrease the interval + int64_t half = live_bytes/2; + if (gc_num.interval > half) gc_num.interval = half; + // But never go below default + if (gc_num.interval < default_collect_interval) gc_num.interval = default_collect_interval; } if (gc_num.interval + live_bytes > max_total_memory) { if (live_bytes < max_total_memory) { gc_num.interval = max_total_memory - live_bytes; - } else { + } + else { // We can't stay under our goal so let's go back to // the minimum interval and hope things get better gc_num.interval = default_collect_interval; @@ -3541,16 +3164,15 @@ JL_DLLEXPORT void jl_gc_collect(jl_gc_collection_t collection) errno = last_errno; } -void gc_mark_queue_all_roots(jl_ptls_t ptls, jl_gc_mark_sp_t *sp) +void gc_mark_queue_all_roots(jl_ptls_t ptls, jl_gc_markqueue_t *mq) { - jl_gc_mark_cache_t *gc_cache = &ptls->gc_cache; assert(gc_n_threads); for (size_t i = 0; i < gc_n_threads; i++) { jl_ptls_t ptls2 = gc_all_tls_states[i]; if (ptls2) - jl_gc_queue_thread_local(gc_cache, sp, ptls2); + gc_queue_thread_local(mq, ptls2); } - mark_roots(gc_cache, sp); + gc_mark_roots(mq); } // allocator entry points @@ -3586,10 +3208,16 @@ void jl_init_thread_heap(jl_ptls_t ptls) gc_cache->perm_scanned_bytes = 0; gc_cache->scanned_bytes = 0; gc_cache->nbig_obj = 0; - size_t init_size = 1024; - gc_cache->pc_stack = (void**)malloc_s(init_size * sizeof(void*)); - gc_cache->pc_stack_end = gc_cache->pc_stack + init_size; - gc_cache->data_stack = (jl_gc_mark_data_t *)malloc_s(init_size * sizeof(jl_gc_mark_data_t)); + + // Initialize GC mark-queue + size_t init_size = (1 << 18); + jl_gc_markqueue_t *mq = &ptls->mark_queue; + mq->start = (jl_value_t **)malloc_s(init_size * sizeof(jl_value_t *)); + mq->current = mq->start; + mq->end = mq->start + init_size; + size_t cq_init_size = (1 << 14); + mq->current_chunk = mq->chunk_start = (jl_gc_chunk_t *)malloc_s(cq_init_size * sizeof(jl_gc_chunk_t)); + mq->chunk_end = mq->chunk_start + cq_init_size; memset(&ptls->gc_num, 0, sizeof(ptls->gc_num)); jl_atomic_store_relaxed(&ptls->gc_num.allocd, -(int64_t)gc_num.interval); @@ -3631,9 +3259,6 @@ void jl_gc_init(void) if (high_water_mark < max_total_memory) max_total_memory = high_water_mark; - - jl_gc_mark_sp_t sp = {NULL, NULL, NULL, NULL}; - gc_mark_loop(NULL, sp); t_start = jl_hrtime(); } @@ -3657,7 +3282,7 @@ JL_DLLEXPORT void *jl_gc_counted_malloc(size_t sz) { jl_gcframe_t **pgcstack = jl_get_pgcstack(); jl_task_t *ct = jl_current_task; - if (pgcstack && ct->world_age) { + if (pgcstack != NULL && ct->world_age) { jl_ptls_t ptls = ct->ptls; maybe_collect(ptls); jl_atomic_store_relaxed(&ptls->gc_num.allocd, @@ -3672,7 +3297,7 @@ JL_DLLEXPORT void *jl_gc_counted_calloc(size_t nm, size_t sz) { jl_gcframe_t **pgcstack = jl_get_pgcstack(); jl_task_t *ct = jl_current_task; - if (pgcstack && ct->world_age) { + if (pgcstack != NULL && ct->world_age) { jl_ptls_t ptls = ct->ptls; maybe_collect(ptls); jl_atomic_store_relaxed(&ptls->gc_num.allocd, @@ -3688,7 +3313,7 @@ JL_DLLEXPORT void jl_gc_counted_free_with_size(void *p, size_t sz) jl_gcframe_t **pgcstack = jl_get_pgcstack(); jl_task_t *ct = jl_current_task; free(p); - if (pgcstack && ct->world_age) { + if (pgcstack != NULL && ct->world_age) { jl_ptls_t ptls = ct->ptls; jl_atomic_store_relaxed(&ptls->gc_num.freed, jl_atomic_load_relaxed(&ptls->gc_num.freed) + sz); @@ -3701,7 +3326,7 @@ JL_DLLEXPORT void *jl_gc_counted_realloc_with_old_size(void *p, size_t old, size { jl_gcframe_t **pgcstack = jl_get_pgcstack(); jl_task_t *ct = jl_current_task; - if (pgcstack && ct->world_age) { + if (pgcstack != NULL && ct->world_age) { jl_ptls_t ptls = ct->ptls; maybe_collect(ptls); if (sz < old) diff --git a/src/gc.h b/src/gc.h index 35767b75ea3f83..930f7f3c305949 100644 --- a/src/gc.h +++ b/src/gc.h @@ -42,7 +42,6 @@ extern "C" { typedef struct { uint64_t num; uint64_t next; - uint64_t min; uint64_t interv; uint64_t max; @@ -83,162 +82,26 @@ typedef struct { uint64_t total_mark_time; } jl_gc_num_t; -enum { - GC_MARK_L_marked_obj, - GC_MARK_L_scan_only, - GC_MARK_L_finlist, - GC_MARK_L_objarray, - GC_MARK_L_array8, - GC_MARK_L_array16, - GC_MARK_L_obj8, - GC_MARK_L_obj16, - GC_MARK_L_obj32, - GC_MARK_L_stack, - GC_MARK_L_excstack, - GC_MARK_L_module_binding, - _GC_MARK_L_MAX -}; - -// The following structs (`gc_mark_*_t`) contain iterator state used for the -// scanning of various object types. -// -// The `nptr` member records the number of pointers slots referenced by -// an object to be used in the full collection heuristics as well as whether the object -// references young objects. -// `nptr >> 2` is the number of pointers fields referenced by the object. -// The lowest bit of `nptr` is set if the object references young object. -// The 2nd lowest bit of `nptr` is the GC old bits of the object after marking. -// A `0x3` in the low bits means that the object needs to be in the remset. - -// An generic object that's marked and needs to be scanned -// The metadata might need update too (depend on the PC) -typedef struct { - jl_value_t *obj; // The object - uintptr_t tag; // The tag with the GC bits masked out - uint8_t bits; // The GC bits after tagging (`bits & 1 == 1`) -} gc_mark_marked_obj_t; - -// An object array. This can come from an array, svec, or the using array or a module -typedef struct { - jl_value_t *parent; // The parent object to trigger write barrier on. - jl_value_t **begin; // The first slot to be scanned. - jl_value_t **end; // The end address (after the last slot to be scanned) - uint32_t step; // Number of pointers to jump between marks - uintptr_t nptr; // See notes about `nptr` above. -} gc_mark_objarray_t; - -// A normal object with 8bits field descriptors -typedef struct { - jl_value_t *parent; // The parent object to trigger write barrier on. - uint8_t *begin; // Current field descriptor. - uint8_t *end; // End of field descriptor. - uintptr_t nptr; // See notes about `nptr` above. -} gc_mark_obj8_t; - -// A normal object with 16bits field descriptors -typedef struct { - jl_value_t *parent; // The parent object to trigger write barrier on. - uint16_t *begin; // Current field descriptor. - uint16_t *end; // End of field descriptor. - uintptr_t nptr; // See notes about `nptr` above. -} gc_mark_obj16_t; - -// A normal object with 32bits field descriptors -typedef struct { - jl_value_t *parent; // The parent object to trigger write barrier on. - uint32_t *begin; // Current field descriptor. - uint32_t *end; // End of field descriptor. - uintptr_t nptr; // See notes about `nptr` above. -} gc_mark_obj32_t; - -typedef struct { - jl_value_t **begin; // The first slot to be scanned. - jl_value_t **end; // The end address (after the last slot to be scanned) - uint8_t *rebegin; - gc_mark_obj8_t elem; -} gc_mark_array8_t; - -typedef struct { - jl_value_t **begin; // The first slot to be scanned. - jl_value_t **end; // The end address (after the last slot to be scanned) - uint16_t *rebegin; - gc_mark_obj16_t elem; -} gc_mark_array16_t; - -// Stack frame -typedef struct { - jl_gcframe_t *s; // The current stack frame - uint32_t i; // The current slot index in the frame - uint32_t nroots; // `nroots` fields in the frame - // Parameters to mark the copy_stack range. - uintptr_t offset; - uintptr_t lb; - uintptr_t ub; -} gc_mark_stackframe_t; - -// Exception stack data -typedef struct { - jl_excstack_t *s; // Stack of exceptions - size_t itr; // Iterator into exception stack - size_t bt_index; // Current backtrace buffer entry index - size_t jlval_index; // Index into GC managed values for current bt entry -} gc_mark_excstack_t; - -// Module bindings. This is also the beginning of module scanning. -// The loop will start marking other references in a module after the bindings are marked -typedef struct { - jl_module_t *parent; // The parent module to trigger write barrier on. - jl_binding_t **begin; // The first slot to be scanned. - jl_binding_t **end; // The end address (after the last slot to be scanned) - uintptr_t nptr; // See notes about `nptr` above. -} gc_mark_binding_t; - -// Finalizer (or object) list -typedef struct { - jl_value_t **begin; - jl_value_t **end; -} gc_mark_finlist_t; - -// This is used to determine the max size of the data objects on the data stack. -// We'll use this size to determine the size of the data stack corresponding to a -// PC stack size. Since the data objects are not all of the same size, we'll waste -// some memory on the data stack this way but that size is unlikely going to be significant. -union _jl_gc_mark_data { - gc_mark_marked_obj_t marked; - gc_mark_objarray_t objarray; - gc_mark_array8_t array8; - gc_mark_array16_t array16; - gc_mark_obj8_t obj8; - gc_mark_obj16_t obj16; - gc_mark_obj32_t obj32; - gc_mark_stackframe_t stackframe; - gc_mark_excstack_t excstackframe; - gc_mark_binding_t binding; - gc_mark_finlist_t finlist; -}; - -// Pop a data struct from the mark data stack (i.e. decrease the stack pointer) -// This should be used after dispatch and therefore the pc stack pointer is already popped from -// the stack. -STATIC_INLINE void *gc_pop_markdata_(jl_gc_mark_sp_t *sp, size_t size) -{ - jl_gc_mark_data_t *data = (jl_gc_mark_data_t *)(((char*)sp->data) - size); - sp->data = data; - return data; -} -#define gc_pop_markdata(sp, type) ((type*)gc_pop_markdata_(sp, sizeof(type))) - -// Re-push a frame to the mark stack (both data and pc) -// The data and pc are expected to be on the stack (or updated in place) already. -// Mainly useful to pause the current scanning in order to scan an new object. -STATIC_INLINE void *gc_repush_markdata_(jl_gc_mark_sp_t *sp, size_t size) JL_NOTSAFEPOINT -{ - jl_gc_mark_data_t *data = sp->data; - sp->pc++; - sp->data = (jl_gc_mark_data_t *)(((char*)sp->data) + size); - return data; -} -#define gc_repush_markdata(sp, type) ((type*)gc_repush_markdata_(sp, sizeof(type))) +typedef enum { + GC_empty_chunk, + GC_objary_chunk, + GC_ary8_chunk, + GC_ary16_chunk, + GC_finlist_chunk, +} gc_chunk_id_t; + +typedef struct _jl_gc_chunk_t { + gc_chunk_id_t cid; + struct _jl_value_t *parent; + struct _jl_value_t **begin; + struct _jl_value_t **end; + void *elem_begin; + void *elem_end; + uint32_t step; + uintptr_t nptr; +} jl_gc_chunk_t; + +#define MAX_REFS_AT_ONCE (1 << 16) // layout for big (>2k) objects @@ -507,23 +370,16 @@ STATIC_INLINE void gc_big_object_link(bigval_t *hdr, bigval_t **list) JL_NOTSAFE *list = hdr; } -STATIC_INLINE void gc_mark_sp_init(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp) -{ - sp->pc = gc_cache->pc_stack; - sp->data = gc_cache->data_stack; - sp->pc_start = gc_cache->pc_stack; - sp->pc_end = gc_cache->pc_stack_end; -} - -void gc_mark_queue_all_roots(jl_ptls_t ptls, jl_gc_mark_sp_t *sp); -void gc_mark_queue_finlist(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, - arraylist_t *list, size_t start); -void gc_mark_loop(jl_ptls_t ptls, jl_gc_mark_sp_t sp); +void gc_mark_queue_all_roots(jl_ptls_t ptls, jl_gc_markqueue_t *mq); +void gc_mark_finlist_(jl_gc_markqueue_t *mq, jl_value_t **fl_begin, + jl_value_t **fl_end) JL_NOTSAFEPOINT; +void gc_mark_finlist(jl_gc_markqueue_t *mq, arraylist_t *list, + size_t start) JL_NOTSAFEPOINT; +void gc_mark_loop_(jl_ptls_t ptls, jl_gc_markqueue_t *mq); +void gc_mark_loop(jl_ptls_t ptls); void sweep_stack_pools(void); void jl_gc_debug_init(void); -extern void *gc_mark_label_addrs[_GC_MARK_L_MAX]; - // GC pages void jl_gc_init_page(void); @@ -608,7 +464,6 @@ static inline void gc_verify_tags(void) } #endif - #ifdef GC_VERIFY extern jl_value_t *lostval; void gc_verify(jl_ptls_t ptls); @@ -649,10 +504,9 @@ extern int gc_verifying; #define gc_verifying (0) #endif - int gc_slot_to_fieldidx(void *_obj, void *slot, jl_datatype_t *vt) JL_NOTSAFEPOINT; int gc_slot_to_arrayidx(void *_obj, void *begin) JL_NOTSAFEPOINT; -NOINLINE void gc_mark_loop_unwind(jl_ptls_t ptls, jl_gc_mark_sp_t sp, int pc_offset); +NOINLINE void gc_mark_loop_unwind(jl_ptls_t ptls, jl_gc_markqueue_t *mq, int offset) JL_NOTSAFEPOINT; #ifdef GC_DEBUG_ENV JL_DLLEXPORT extern jl_gc_debug_env_t jl_gc_debug_env; diff --git a/src/julia_internal.h b/src/julia_internal.h index 7565967b0a270e..8f7b49239c5e31 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -576,8 +576,8 @@ STATIC_INLINE void jl_gc_wb_buf(void *parent, void *bufptr, size_t minsz) JL_NOT } } -void jl_gc_debug_print_status(void); -JL_DLLEXPORT void jl_gc_debug_critical_error(void); +void jl_gc_debug_print_status(void) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_gc_debug_critical_error(void) JL_NOTSAFEPOINT; void jl_print_gc_stats(JL_STREAM *s); void jl_gc_reset_alloc_count(void); uint32_t jl_get_gs_ctr(void); @@ -1190,7 +1190,7 @@ size_t rec_backtrace_ctx_dwarf(jl_bt_element_t *bt_data, size_t maxsize, bt_cont #endif JL_DLLEXPORT jl_value_t *jl_get_backtrace(void); void jl_critical_error(int sig, int si_code, bt_context_t *context, jl_task_t *ct); -JL_DLLEXPORT void jl_raise_debugger(void); +JL_DLLEXPORT void jl_raise_debugger(void) JL_NOTSAFEPOINT; int jl_getFunctionInfo(jl_frame_t **frames, uintptr_t pointer, int skipC, int noInline) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_gdblookup(void* ip) JL_NOTSAFEPOINT; void jl_print_native_codeloc(uintptr_t ip) JL_NOTSAFEPOINT; diff --git a/src/julia_threads.h b/src/julia_threads.h index 5874225c12eac2..719de95c9e3752 100644 --- a/src/julia_threads.h +++ b/src/julia_threads.h @@ -170,16 +170,14 @@ typedef struct { arraylist_t free_stacks[JL_N_STACK_POOLS]; } jl_thread_heap_t; -// Cache of thread local change to global metadata during GC -// This is sync'd after marking. -typedef union _jl_gc_mark_data jl_gc_mark_data_t; - typedef struct { - void **pc; // Current stack address for the pc (up growing) - jl_gc_mark_data_t *data; // Current stack address for the data (up growing) - void **pc_start; // Cached value of `gc_cache->pc_stack` - void **pc_end; // Cached value of `gc_cache->pc_stack_end` -} jl_gc_mark_sp_t; + struct _jl_gc_chunk_t *chunk_start; + struct _jl_gc_chunk_t *current_chunk; + struct _jl_gc_chunk_t *chunk_end; + struct _jl_value_t **start; + struct _jl_value_t **current; + struct _jl_value_t **end; +} jl_gc_markqueue_t; typedef struct { // thread local increment of `perm_scanned_bytes` @@ -197,9 +195,6 @@ typedef struct { // this makes sure that a single objects can only appear once in // the lists (the mark bit cannot be flipped to `0` without sweeping) void *big_obj[1024]; - void **pc_stack; - void **pc_stack_end; - jl_gc_mark_data_t *data_stack; } jl_gc_mark_cache_t; struct _jl_bt_element_t; @@ -265,9 +260,9 @@ typedef struct _jl_tls_states_t { #endif jl_thread_t system_id; arraylist_t finalizers; + jl_gc_markqueue_t mark_queue; jl_gc_mark_cache_t gc_cache; arraylist_t sweep_objs; - jl_gc_mark_sp_t gc_mark_sp; // Saved exception for previous *external* API call or NULL if cleared. // Access via jl_exception_occurred(). struct _jl_value_t *previous_exception; diff --git a/src/partr.c b/src/partr.c index 3e71cb6627e07a..4faff409c711a8 100644 --- a/src/partr.c +++ b/src/partr.c @@ -78,7 +78,7 @@ JL_DLLEXPORT int jl_set_task_threadpoolid(jl_task_t *task, int8_t tpid) JL_NOTSA // GC functions used extern int jl_gc_mark_queue_obj_explicit(jl_gc_mark_cache_t *gc_cache, - jl_gc_mark_sp_t *sp, jl_value_t *obj) JL_NOTSAFEPOINT; + jl_gc_markqueue_t *mq, jl_value_t *obj) JL_NOTSAFEPOINT; // parallel task runtime // --- diff --git a/src/support/dtypes.h b/src/support/dtypes.h index d49ae0b22b5f95..891c091413084d 100644 --- a/src/support/dtypes.h +++ b/src/support/dtypes.h @@ -117,6 +117,7 @@ typedef intptr_t ssize_t; #define LLT_FREE(x) free(x) #define STATIC_INLINE static inline +#define FORCE_INLINE static inline __attribute__((always_inline)) #if defined(_OS_WINDOWS_) && !defined(_COMPILER_GCC_) # define NOINLINE __declspec(noinline) From 30d11a3b2f4a557fe24cc7ba205b01dd7f0c3bc9 Mon Sep 17 00:00:00 2001 From: Martin Holters Date: Tue, 24 Jan 2023 22:16:49 +0100 Subject: [PATCH 382/387] Fix `apply_type_tfunc` for `Union{T::TypeVar}` (#48384) The type parameters to `Union` may be `Type`s or `TypeVar`s, but `apply_type_tfunc` failed to recognize the latter as valid in the single-argument case. --- base/compiler/tfuncs.jl | 2 +- test/compiler/inference.jl | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 1ff427a480a7d7..1673929df11292 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -1713,7 +1713,7 @@ const _tvarnames = Symbol[:_A, :_B, :_C, :_D, :_E, :_F, :_G, :_H, :_I, :_J, :_K, end end if largs == 1 # Union{T} --> T - u1 = typeintersect(widenconst(args[1]), Type) + u1 = typeintersect(widenconst(args[1]), Union{Type,TypeVar}) valid_as_lattice(u1) || return Bottom return u1 end diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 71f9903f85324c..f70b1f73f55ad9 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -2732,9 +2732,9 @@ end |> only === Int # correct `apply_type` inference of `NamedTuple{(), <:Any}` @test (() -> NamedTuple{(), <:Any})() isa UnionAll -# Don't pessimize apply_type to anything worse than Type and yield Bottom for invalid Unions +# Don't pessimize apply_type to anything worse than Type (or TypeVar) and yield Bottom for invalid Unions @test only(Base.return_types(Core.apply_type, Tuple{Type{Union}})) == Type{Union{}} -@test only(Base.return_types(Core.apply_type, Tuple{Type{Union},Any})) == Type +@test only(Base.return_types(Core.apply_type, Tuple{Type{Union},Any})) == Union{Type,TypeVar} @test only(Base.return_types(Core.apply_type, Tuple{Type{Union},Any,Any})) == Type @test only(Base.return_types(Core.apply_type, Tuple{Type{Union},Int})) == Union{} @test only(Base.return_types(Core.apply_type, Tuple{Type{Union},Any,Int})) == Union{} @@ -4715,3 +4715,6 @@ f_no_bail_effects_any(x::Any) = x f_no_bail_effects_any(x::NamedTuple{(:x,), Tuple{Any}}) = getfield(x, 1) g_no_bail_effects_any(x::Any) = f_no_bail_effects_any(x) @test Core.Compiler.is_total(Base.infer_effects(g_no_bail_effects_any, Tuple{Any})) + +# issue #48374 +@test (() -> Union{<:Nothing})() == Nothing From 6c3808bab35dce245937809bb7d1b4be8c9ddc1d Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Wed, 25 Jan 2023 08:31:11 +0100 Subject: [PATCH 383/387] Add a test verifying codegen of non-concrete varargs (#48395) This was issue #34459, fixed by #46953. --- test/compiler/codegen.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/compiler/codegen.jl b/test/compiler/codegen.jl index 6f8c31d6c4015f..2244db0f6fe9c3 100644 --- a/test/compiler/codegen.jl +++ b/test/compiler/codegen.jl @@ -797,3 +797,10 @@ f48085(@nospecialize x...) = length(x) # Make sure that the bounds check is elided in tuple iteration @test !occursin("call void @", get_llvm(iterate, Tuple{NTuple{4, Float64}, Int})) + +# issue #34459 +function f34459(args...) + Base.pointerset(args[1], 1, 1, 1) + return +end +@test !occursin("jl_f_tuple", get_llvm(f34459, Tuple{Ptr{Int}, Type{Int}}, true, false, false)) From 9b1ffbbe1bce1eaf3327535ae3e46e923e8d9aff Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Wed, 25 Jan 2023 21:52:55 +0900 Subject: [PATCH 384/387] NFC: simplify `code_escapes` a bit (#48399) --- test/compiler/EscapeAnalysis/EAUtils.jl | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/test/compiler/EscapeAnalysis/EAUtils.jl b/test/compiler/EscapeAnalysis/EAUtils.jl index 902af88a3b9364..51b4b66c22643a 100644 --- a/test/compiler/EscapeAnalysis/EAUtils.jl +++ b/test/compiler/EscapeAnalysis/EAUtils.jl @@ -41,13 +41,7 @@ function code_escapes(@nospecialize(f), @nospecialize(types=Base.default_tt(f)); interp::Core.Compiler.AbstractInterpreter = Core.Compiler.NativeInterpreter(world), debuginfo::Symbol = :none, optimize::Bool = true) - ft = Core.Typeof(f) - if isa(types, Type) - u = unwrap_unionall(types) - tt = rewrap_unionall(Tuple{ft, u.parameters...}, types) - else - tt = Tuple{ft, types...} - end + tt = Base.signature_type(f, types) interp = EscapeAnalyzer(interp, tt, optimize) results = Base.code_typed_by_type(tt; optimize=true, world, interp) isone(length(results)) || throw(ArgumentError("`code_escapes` only supports single analysis result")) From e536c77f4dc693aafc48af910b4fd86b487e900d Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Wed, 25 Jan 2023 14:06:33 -0800 Subject: [PATCH 385/387] Silence missing gfortran errors in `Make.inc` (#48403) When we attempt to invoke `gfortran` to determine the local `libgfortran` ABI version that we should attempt to mimic, if `gfortran` is not installed we get a harmless error printed out to `stderr`: ``` /bin/sh: line 1: gfortran: command not found ``` This should silence these errors, as they're not useful to us. --- Make.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Make.inc b/Make.inc index ddea0733be6a74..bb1922c32bc441 100644 --- a/Make.inc +++ b/Make.inc @@ -1125,7 +1125,7 @@ USE_BINARYBUILDER ?= 0 endif # Auto-detect triplet once, create different versions that we use as defaults below for each BB install target -FC_VERSION := $(shell $(FC) -dM -E - < /dev/null | grep __GNUC__ | cut -d' ' -f3) +FC_VERSION := $(shell $(FC) -dM -E - < /dev/null 2>/dev/null | grep __GNUC__ | cut -d' ' -f3) ifeq ($(USEGCC)$(FC_VERSION),1) FC_OR_CC_VERSION := $(shell $(CC) -dumpfullversion -dumpversion 2>/dev/null | cut -d'.' -f1) # n.b. clang's __GNUC__ macro pretends to be gcc 4.2.1, so leave it as the empty string here if the compiler is not certain to be GCC From b6db2fdacbd4f663e220d4f4d190c4ed53ad8763 Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Thu, 26 Jan 2023 16:42:54 +0400 Subject: [PATCH 386/387] Axes tip in `AbstractArray` interface (#47221) Update the `AbstractArray` interface to mention that the axes of an array should, in general, be their own axes. This is required for internal consistency, and if this is not enforced (e.g. by choosing `UnitRange`s as axes instead of `Base.IdentityUnitRange`s), one may come across unexpected bugs (see https://juliaarrays.github.io/OffsetArrays.jl/stable/internals/#Wrapping-other-offset-array-types for an example). Co-authored-by: Viral B. Shah --- doc/src/manual/interfaces.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/manual/interfaces.md b/doc/src/manual/interfaces.md index 70a662e263da80..bcb15da69dedff 100644 --- a/doc/src/manual/interfaces.md +++ b/doc/src/manual/interfaces.md @@ -233,7 +233,7 @@ ourselves, we can officially define it as a subtype of an [`AbstractArray`](@ref | `similar(A, dims::Dims)` | `similar(A, eltype(A), dims)` | Return a mutable array with the same element type and size *dims* | | `similar(A, ::Type{S}, dims::Dims)` | `Array{S}(undef, dims)` | Return a mutable array with the specified element type and size | | **Non-traditional indices** | **Default definition** | **Brief description** | -| `axes(A)` | `map(OneTo, size(A))` | Return a tuple of `AbstractUnitRange{<:Integer}` of valid indices | +| `axes(A)` | `map(OneTo, size(A))` | Return a tuple of `AbstractUnitRange{<:Integer}` of valid indices. The axes should be their own axes, that is `axes.(axes(A),1) == axes(A)` should be satisfied. | | `similar(A, ::Type{S}, inds)` | `similar(A, S, Base.to_shape(inds))` | Return a mutable array with the specified indices `inds` (see below) | | `similar(T::Union{Type,Function}, inds)` | `T(Base.to_shape(inds))` | Return an array similar to `T` with the specified indices `inds` (see below) | From 1a0b92cff5cf55c45e9863ab648e340476cb8b59 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Fri, 27 Jan 2023 01:09:50 +0900 Subject: [PATCH 387/387] irinterp: use `optimizer_lattice` (#48412) Previously it uses `typeinf_lattice`, but it should use `optimizer_lattice` instead since irinterp is working on `IRCode` and otherwise it fails to handle `MaybeUndef`. --- base/compiler/ssair/irinterp.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index 992eadde3e1016..7b2df1b39dd517 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -104,11 +104,11 @@ struct IRInterpretationState lazydomtree::LazyDomtree function IRInterpretationState(interp::AbstractInterpreter, ir::IRCode, mi::MethodInstance, world::UInt, argtypes::Vector{Any}) - argtypes = va_process_argtypes(typeinf_lattice(interp), argtypes, mi) + argtypes = va_process_argtypes(optimizer_lattice(interp), argtypes, mi) for i = 1:length(argtypes) argtypes[i] = widenslotwrapper(argtypes[i]) end - argtypes_refined = Bool[!⊑(typeinf_lattice(interp), ir.argtypes[i], argtypes[i]) for i = 1:length(argtypes)] + argtypes_refined = Bool[!⊑(optimizer_lattice(interp), ir.argtypes[i], argtypes[i]) for i = 1:length(argtypes)] empty!(ir.argtypes) append!(ir.argtypes, argtypes) tpdum = TwoPhaseDefUseMap(length(ir.stmts)) @@ -268,7 +268,7 @@ function reprocess_instruction!(interp::AbstractInterpreter, # Handled at the very end return false elseif isa(inst, PiNode) - rt = tmeet(typeinf_lattice(interp), argextype(inst.val, ir), widenconst(inst.typ)) + rt = tmeet(optimizer_lattice(interp), argextype(inst.val, ir), widenconst(inst.typ)) elseif inst === nothing return false elseif isa(inst, GlobalRef) @@ -277,7 +277,7 @@ function reprocess_instruction!(interp::AbstractInterpreter, ccall(:jl_, Cvoid, (Any,), inst) error() end - if rt !== nothing && !⊑(typeinf_lattice(interp), typ, rt) + if rt !== nothing && !⊑(optimizer_lattice(interp), typ, rt) ir.stmts[idx][:type] = rt return true end @@ -444,7 +444,7 @@ function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IR end inst = ir.stmts[idx][:inst]::ReturnNode rt = argextype(inst.val, ir) - ultimate_rt = tmerge(typeinf_lattice(interp), ultimate_rt, rt) + ultimate_rt = tmerge(optimizer_lattice(interp), ultimate_rt, rt) end end