diff --git a/.gitignore b/.gitignore index 3cbb42a1efd13f..a4e7f0ac40d5be 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ /dist /dist-extras /julia +/julia.bat /usr /oprofile_data /usr-staging diff --git a/.travis.yml b/.travis.yml index 1f45c08d32008b..55647c26f467d1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -73,23 +73,18 @@ before_install: export PATH="$(brew --prefix ccache)/libexec:$PATH"; BAR="pv -i 30"; contrib/travis_fastfail.sh || exit 1; - brew tap staticfloat/julia > /dev/null; - brew rm --force $(brew deps --HEAD julia); - brew install -v ccache gcc gmp mpfr pcre2 staticfloat/julia/openblas-julia staticfloat/julia/suite-sparse-julia staticfloat/juliadeps/libgfortran; - BUILDOPTS="-j3 USECLANG=1 USECCACHE=1 USE_BINARYBUILDER_LLVM=1 USE_BINARYBUILDER_OPENBLAS=1 BINARYBUILDER_LLVM_ASSERTS=1"; - BUILDOPTS="$BUILDOPTS LLVM_CONFIG=$TRAVIS_BUILD_DIR/usr/tools/llvm-config LLVM_SIZE=$TRAVIS_BUILD_DIR/usr/tools/llvm-size"; - BUILDOPTS="$BUILDOPTS VERBOSE=1 USE_BLAS64=0 SUITESPARSE_INC=-I$(brew --prefix suite-sparse-julia)/include FORCE_ASSERTIONS=1"; - BUILDOPTS="$BUILDOPTS LIBBLAS=-lopenblas LIBBLASNAME=libopenblas LIBLAPACK=-lopenblas LIBLAPACKNAME=libopenblas"; - for lib in SUITESPARSE BLAS LAPACK GMP MPFR LIBUNWIND; do + brew rm --force gcc gmp mpfr pcre2; + brew install -v gcc gmp mpfr pcre2; + BUILDOPTS="-j3 USECLANG=1 USECCACHE=1 VERBOSE=1 FORCE_ASSERTIONS=1"; + for proj in LLVM LLVM_ASSERTS OPENBLAS SUITESPARSE OPENLIBM; do + BUILDOPTS="$BUILDOPTS USE_BINARYBUILDER_${proj}=1"; + done; + for lib in GMP MPFR LIBUNWIND; do BUILDOPTS="$BUILDOPTS USE_SYSTEM_$lib=1"; done; - export LDFLAGS="-L$(brew --prefix openblas-julia)/lib -L$(brew --prefix suite-sparse-julia)/lib"; spawn_DYLD_FALLBACK_LIBRARY_PATH="/usr/local/lib:/lib:/usr/lib"; - spawn_DYLD_FALLBACK_LIBRARY_PATH+=":$(brew --prefix openblas-julia)/lib"; - spawn_DYLD_FALLBACK_LIBRARY_PATH+=":$(brew --prefix suite-sparse-julia)/lib"; export JULIA_MACOS_SPAWN="DYLD_FALLBACK_LIBRARY_PATH=\"$spawn_DYLD_FALLBACK_LIBRARY_PATH\" \$1"; export BUILDOPTS="$BUILDOPTS spawn=\$(JULIA_MACOS_SPAWN)"; - make $BUILDOPTS -C contrib -f repackage_system_suitesparse4.make; export JULIA_CPU_THREADS=2; export JULIA_TEST_MAXRSS_MB=600; TESTSTORUN="all --skip linalg/triangular subarray"; fi # TODO: re enable these if possible without timing out @@ -138,9 +133,9 @@ script: - popd # test that the embedding code works on our installation - mkdir /tmp/embedding-test && - make check -C /tmp/julia/share/julia/test/embedding \ - JULIA="/tmp/julia/bin/julia" \ - BIN=/tmp/embedding-test \ + make check -C /tmp/julia/share/julia/test/embedding + JULIA="/tmp/julia/bin/julia" + BIN=/tmp/embedding-test "$(cd julia2 && make print-CC)" # restore initial state and prepare for travis caching - mv julia2 julia && diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2d9dd615c0d6f3..12b8de9fd12f6f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,6 +2,8 @@ Hi! If you are new to the Julia community: welcome, and thanks for trying Julia. Please be sure to respect our [community standards](https://julialang.org/community/standards) in all interactions. +If you are already familiar with Julia itself, this blog post by Katharine Hyatt on [Making your first Julia pull request](http://kshyatt.github.io/post/firstjuliapr/) is a great way to get started. + ## Learning Julia [The learning page](https://julialang.org/learning) has a great list of resources for new and experienced users alike. [This tutorial video](https://www.youtube.com/watch?v=vWkgEddb4-A) is one recommended starting point, as is the "[Invitation to Julia](https://www.youtube.com/watch?v=gQ1y5NUD_RI)" workshop video from JuliaCon 2015 ([slide materials here](https://github.com/dpsanders/invitation_to_julia)). The [Julia documentation](https://docs.julialang.org/en/latest) covers the language and core library features, and is searchable. @@ -12,7 +14,7 @@ Hi! If you are new to the Julia community: welcome, and thanks for trying Julia. - Contributing code? Be sure to review the [contributor checklist](https://github.com/JuliaLang/julia/blob/master/CONTRIBUTING.md#contributor-checklist) for helpful tips on the tools we use to build Julia. -- Library feature requests are generally not accepted on this issue tracker. New libraries should be developed as [packages](https://docs.julialang.org/en/latest/manual/packages#Package-Development-1). Discuss ideas for libraries at the [Julia Discourse forum](https://discourse.julialang.org). Doing so will often lead to pointers to existing projects and bring together collaborators with common interests. +- Library feature requests are generally not accepted on this issue tracker. New libraries should be developed as [packages](https://julialang.github.io/Pkg.jl/v1/creating-packages/). Discuss ideas for libraries at the [Julia Discourse forum](https://discourse.julialang.org). Doing so will often lead to pointers to existing projects and bring together collaborators with common interests. ## Contributor Checklist @@ -56,7 +58,7 @@ A useful bug report filed as a GitHub issue provides information about how to re ### Contributing a Julia package -Julia has a built-in [package manager](https://github.com/JuliaLang/METADATA.jl) based on `git`. A number of [packages](http://pkg.julialang.org) across many domains are already available for Julia. Developers are encouraged to provide their libraries as a Julia package. The Julia manual provides instructions on [creating Julia packages](https://docs.julialang.org/en/latest/manual/packages). +Julia has a built-in [package manager](https://julialang.github.io/Pkg.jl/v1/) based on `git`. A number of [packages](http://pkg.julialang.org) across many domains are already available for Julia. Developers are encouraged to provide their libraries as a Julia package. The manual provides instructions on [creating Julia packages](https://julialang.github.io/Pkg.jl/v1/creating-packages/). For developers who need to wrap C libraries so that they can be called from Julia, the [Clang.jl](https://github.com/ihnorton/Clang.jl) package can help generate the wrappers automatically from the C header files. @@ -308,20 +310,17 @@ Julia if you made code-changes that Revise cannot handle. * Julia - **Homepage:** - **Community:** - - **IRC:** - **Source code:** - - **Git clone URL:** - - **Documentation:** - - **Status:** + - **Documentation:** - **Code coverage:** * Design of Julia - - [Julia: A Fresh Approach to Numerical Computing](http://arxiv.org/pdf/1411.1607v3.pdf) - - [Julia: A Fast Dynamic Language for Technical Computing](https://julialang.org/images/julia-dynamic-2012-tr.pdf) - - [All Julia Publications](https://julialang.org/publications) + - [Julia: A Fresh Approach to Numerical Computing](https://julialang.org/research/julia-fresh-approach-BEKS.pdf) + - [Julia: Dynamism and Performance Reconciled by Design](http://janvitek.org/pubs/oopsla18b.pdf) + - [All Julia Publications](https://julialang.org/research) * Using GitHub - [Using Julia with GitHub (video)](http://www.youtube.com/watch?v=wnFYV3ZKtOg&feature=youtu.be) - [Using Julia on GitHub (notes for video)](https://gist.github.com/2712118#file_Julia_git_pull_request.md) - [General GitHub documentation](http://help.github.com) - - [GitHub pull request documentation](http://help.github.com/send-pull-requests) + - [GitHub pull request documentation](https://help.github.com/articles/creating-a-pull-request/) diff --git a/LICENSE.md b/LICENSE.md index f565217ae39265..cf719b9a47c111 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -3,7 +3,7 @@ of the compiler (the contents of src/), most of the standard library (base/), and some utilities (most of the rest of the files in this repository). See below for exceptions. -> Copyright (c) 2009-2018: Jeff Bezanson, Stefan Karpinski, Viral B. Shah, +> Copyright (c) 2009-2019: Jeff Bezanson, Stefan Karpinski, Viral B. Shah, > and other contributors: > > https://github.com/JuliaLang/julia/contributors diff --git a/Make.inc b/Make.inc index e9cb2411981e54..c85e0dd9a04e7b 100644 --- a/Make.inc +++ b/Make.inc @@ -231,12 +231,6 @@ includedir_rel := $(shell $(JULIAHOME)/contrib/relative_path.sh $(bindir) $(incl INSTALL_F := $(JULIAHOME)/contrib/install.sh 644 INSTALL_M := $(JULIAHOME)/contrib/install.sh 755 -# BinaryBuilder options -USE_BINARYBUILDER_OPENBLAS := 0 -USE_BINARYBUILDER_LLVM := 0 -# Use the Assertions build -BINARYBUILDER_LLVM_ASSERTS := 0 - # LLVM Options LLVMROOT := $(build_prefix) LLVM_ASSERTIONS := 0 @@ -284,16 +278,10 @@ OPENBLAS_DYNAMIC_ARCH := 1 override CROSS_COMPILE:=$(XC_HOST)- ifneq (,$(findstring mingw,$(XC_HOST))) override OS := WINNT -STD_LIB_PATH := $(shell LANG=C $(CROSS_COMPILE)gcc -print-search-dirs | grep programs | sed -e "s/^programs: =//") -STD_LIB_PATH := $(STD_LIB_PATH):$(shell LANG=C $(CROSS_COMPILE)gcc -print-search-dirs | grep libraries | sed -e "s/^libraries: =//") -ifneq (,$(findstring CYGWIN,$(BUILD_OS))) # the cygwin-mingw32 compiler lies about it search directory paths -STD_LIB_PATH := $(shell echo '$(STD_LIB_PATH)' | sed -e "s!/lib/!/bin/!g") -endif else $(error "unknown XC_HOST variable set") endif endif -STD_LIB_PATH ?= $(PATH) JLDOWNLOAD := $(JULIAHOME)/deps/tools/jldownload JLCHECKSUM := $(JULIAHOME)/deps/tools/jlchecksum @@ -936,6 +924,24 @@ else UTF8PROC_INC := $(build_includedir) endif + +# BinaryBuilder options. We default to "on" for all the projects listed in BB_PROJECTS, +# but only if contrib/normalize_triplet.py works for our requested triplet +ifeq ($(shell python $(JULIAHOME)/contrib/normalize_triplet.py $(or $(XC_HOST),$(XC_HOST),$(BUILD_MACHINE)) >/dev/null 2>/dev/null; echo $$?),0) +USE_BINARYBUILDER := 1 +else +USE_BINARYBUILDER := 0 +endif + +# This is the set of projects that BinaryBuilder dependencies are hooked up for. +BB_PROJECTS := OPENBLAS LLVM SUITESPARSE OPENLIBM GMP MBEDTLS LIBSSH2 MPFR CURL LIBGIT2 PCRE LIBUV UNWIND +$(foreach proj,$(BB_PROJECTS),$(eval USE_BINARYBUILDER_$(proj) = $(USE_BINARYBUILDER))) + +# Use the Assertions build +BINARYBUILDER_LLVM_ASSERTS := 0 + + + # OS specific stuff # install_name_tool @@ -1002,7 +1008,7 @@ OSLIBS += -Wl,--version-script=$(JULIAHOME)/src/julia.expmap endif endif endif -JLDFLAGS := -Wl,-Bdynamic +JLDFLAGS := -Wl,-Bdynamic -Wl,-no-undefined ifeq (-Bsymbolic-functions, $(shell $(LD) --help | grep -o -e "-Bsymbolic-functions")) JLIBLDFLAGS := -Wl,-Bsymbolic-functions else @@ -1014,7 +1020,7 @@ endif ifeq ($(OS), FreeBSD) JLDFLAGS := -Wl,-Bdynamic -OSLIBS += -lelf -lkvm -lrt +OSLIBS += -lelf -lkvm -lrt -lpthread # Tweak order of libgcc_s in DT_NEEDED, # make it loaded first to @@ -1169,7 +1175,6 @@ endif # Make tricks define dir_target -$$(subst $$(abspath $(JULIAHOME))/,,$$(abspath $(1))): $$(abspath $(1)) $$(abspath $(1)): @mkdir -p $$@ endef @@ -1188,7 +1193,6 @@ ifeq ($(BUILD_OS), WINNT) else -rm -r $$(abspath $(2)/$(3)) endif -$$(subst $$(abspath $(JULIAHOME))/,,$$(abspath $(2)/$(3))): $$(abspath $(2)/$(3)) $$(abspath $(2)/$(3)): | $$(abspath $(2)) ifeq ($$(BUILD_OS), WINNT) @cmd //C mklink //J $$(call mingw_to_dos,$(2)/$(3),cd $(2) &&) $$(call mingw_to_dos,$(1),) @@ -1222,8 +1226,6 @@ endif exec = $(shell $(call spawn,$(1))) -pathsearch = $(firstword $(wildcard $(addsuffix /$(1),$(subst :, ,$(2))))) - JULIA_BUILD_MODE := release JULIA_LIBSUFFIX:= ifeq (,$(findstring release,$(MAKECMDGOALS))) @@ -1277,7 +1279,17 @@ PRINT_JULIA = echo '$(subst ','\'',$(1))'; $(1) endif +define newline # a literal \n + + +endef + # Makefile debugging trick: # call print-VARIABLE to see the runtime value of any variable +# (hardened against any special characters appearing in the output) print-%: - @echo '$*=$($*)' + @echo '$*=$(subst ','\'',$(subst $(newline),\n,$($*)))' + +# Literal values that are hard to use in Makefiles otherwise: +COMMA:=, +SPACE:=$(eval) $(eval) diff --git a/Makefile b/Makefile index ea67778254cbe7..fff72ad050ac1d 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ all: debug release 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 ui doc deps stdlib test test/embedding test/llvmpasses) -BUILDDIRMAKE := $(addsuffix /Makefile,$(BUILDDIRS)) +BUILDDIRMAKE := $(addsuffix /Makefile,$(BUILDDIRS)) $(BUILDROOT)/sysimage.mk DIRS := $(DIRS) $(BUILDDIRS) $(BUILDDIRMAKE): | $(BUILDDIRS) @# add Makefiles to the build directories for convenience (pointing back to the source location of each) @@ -47,7 +47,10 @@ $(BUILDROOT)/doc/_build/html/en/index.html: $(shell find $(BUILDROOT)/base $(BUI @$(MAKE) docs julia-symlink: julia-ui-$(JULIA_BUILD_MODE) -ifneq ($(OS),WINNT) +ifeq ($(OS),WINNT) + @echo '@"%~dp0"\'"$(shell $(JULIAHOME)/contrib/relative_path.sh "$(BUILDROOT)" "$(JULIA_EXECUTABLE)" | tr / '\\')" '%*' > $(BUILDROOT)/julia.bat + chmod a+x $(BUILDROOT)/julia.bat +else ifndef JULIA_VAGRANT_BUILD @ln -sf "$(shell $(JULIAHOME)/contrib/relative_path.sh "$(BUILDROOT)" "$(JULIA_EXECUTABLE)")" $(BUILDROOT)/julia endif @@ -74,16 +77,13 @@ julia-src-release julia-src-debug : julia-src-% : julia-deps julia_flisp.boot.in julia-ui-release julia-ui-debug : julia-ui-% : julia-src-% @$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT)/ui julia-$* -julia-sysimg : julia-base julia-ui-$(JULIA_BUILD_MODE) - @$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT) $(build_private_libdir)/sys.ji JULIA_EXECUTABLE='$(JULIA_EXECUTABLE)' - -julia-sysimg-release : julia-stdlib julia-sysimg julia-ui-release - @$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT) $(build_private_libdir)/sys.$(SHLIB_EXT) +julia-sysimg-ji : julia-stdlib julia-base julia-ui-$(JULIA_BUILD_MODE) | $(build_private_libdir) + @$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT) -f sysimage.mk sysimg-ji JULIA_EXECUTABLE='$(JULIA_EXECUTABLE)' -julia-sysimg-debug : julia-stdlib julia-sysimg julia-ui-debug - @$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT) $(build_private_libdir)/sys-debug.$(SHLIB_EXT) +julia-sysimg-release julia-sysimg-debug : julia-sysimg-% : julia-sysimg-ji julia-ui-% + @$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT) -f sysimage.mk sysimg-$* -julia-debug julia-release : julia-% : julia-ui-% julia-sysimg-% julia-symlink julia-libccalltest julia-libllvmcalltest julia-base-cache +julia-debug julia-release : julia-% : julia-sysimg-% julia-symlink julia-libccalltest julia-libllvmcalltest julia-base-cache debug release : % : julia-% @@ -140,83 +140,9 @@ $(build_sysconfdir)/julia/startup.jl: $(JULIAHOME)/etc/startup.jl | $(build_sysc @echo Creating usr/etc/julia/startup.jl @cp $< $@ -$(build_datarootdir)/julia/julia-config.jl : $(JULIAHOME)/contrib/julia-config.jl | $(build_datarootdir)/julia +$(build_datarootdir)/julia/julia-config.jl: $(JULIAHOME)/contrib/julia-config.jl | $(build_datarootdir)/julia $(INSTALL_M) $< $(dir $@) -$(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 $@ \ - $(WHOLE_ARCHIVE) $< $(NO_WHOLE_ARCHIVE) \ - $(if $(findstring -debug,$(notdir $@)),-ljulia-debug,-ljulia) \ - $$([ $(OS) = WINNT ] && echo '' -lssp)) - @$(INSTALL_NAME_CMD)$(notdir $@) $@ - @$(DSYMUTIL) $@ - -COMPILER_SRCS := $(addprefix $(JULIAHOME)/, \ - base/boot.jl \ - base/docs/core.jl \ - base/abstractarray.jl \ - base/abstractdict.jl \ - base/array.jl \ - base/bitarray.jl \ - base/bitset.jl \ - base/bool.jl \ - base/ctypes.jl \ - base/error.jl \ - base/essentials.jl \ - base/expr.jl \ - base/generator.jl \ - base/int.jl \ - base/indices.jl \ - base/iterators.jl \ - base/namedtuple.jl \ - base/number.jl \ - base/operators.jl \ - base/options.jl \ - base/pair.jl \ - base/pointer.jl \ - base/promotion.jl \ - base/range.jl \ - base/reflection.jl \ - base/traits.jl \ - base/refvalue.jl \ - base/tuple.jl) -COMPILER_SRCS += $(shell find $(JULIAHOME)/base/compiler -name \*.jl) -# sort these to remove duplicates -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) -RELBUILDROOT := $(shell $(JULIAHOME)/contrib/relative_path.sh "$(JULIAHOME)/base" "$(BUILDROOT)/base/") - -$(build_private_libdir)/corecompiler.ji: $(COMPILER_SRCS) | $(build_private_libdir) - @$(call PRINT_JULIA, cd $(JULIAHOME)/base && \ - $(call spawn,$(JULIA_EXECUTABLE)) -C "$(JULIA_CPU_TARGET)" --output-ji $(call cygpath_w,$@).tmp \ - --startup-file=no -g0 -O0 compiler/compiler.jl) - @mv $@.tmp $@ - -COMMA:=, -$(build_private_libdir)/sys.ji: $(build_private_libdir)/corecompiler.ji $(JULIAHOME)/VERSION $(BASE_SRCS) $(STDLIB_SRCS) - @$(call PRINT_JULIA, cd $(JULIAHOME)/base && \ - if ! $(call spawn,$(JULIA_EXECUTABLE)) -g1 -O0 -C "$(JULIA_CPU_TARGET)" --output-ji $(call cygpath_w,$@).tmp $(JULIA_SYSIMG_BUILD_FLAGS) \ - --startup-file=no --warn-overwrite=yes --sysimage $(call cygpath_w,$<) sysimg.jl $(RELBUILDROOT); then \ - echo '*** This error might be fixed by running `make clean`. If the error persists$(COMMA) try `make cleanall`. ***'; \ - false; \ - fi ) - @mv $@.tmp $@ - -define sysimg_builder -$$(build_private_libdir)/sys$1-o.a $$(build_private_libdir)/sys$1-bc.a : $$(build_private_libdir)/sys$1-%.a : $$(build_private_libdir)/sys.ji - @$$(call PRINT_JULIA, cd $$(JULIAHOME)/base && \ - if ! $$(call spawn,$3) $2 -C "$$(JULIA_CPU_TARGET)" --output-$$* $$(call cygpath_w,$$@).tmp $$(JULIA_SYSIMG_BUILD_FLAGS) \ - --startup-file=no --warn-overwrite=yes --sysimage $$(call cygpath_w,$$<) $$(call cygpath_w,$$(JULIAHOME)/contrib/generate_precompile.jl) $(JULIA_PRECOMPILE); then \ - echo '*** This error is usually fixed by running `make clean`. If the error persists$$(COMMA) try `make cleanall`. ***'; \ - false; \ - fi ) - @mv $$@.tmp $$@ -.SECONDARY: $$(build_private_libdir)/sys$1-o.a $(build_private_libdir)/sys$1-bc.a # request Make to keep these files around -endef -$(eval $(call sysimg_builder,,-O3,$(JULIA_EXECUTABLE_release))) -$(eval $(call sysimg_builder,-debug,-O0,$(JULIA_EXECUTABLE_debug))) - $(build_depsbindir)/stringreplace: $(JULIAHOME)/contrib/stringreplace.c | $(build_depsbindir) @$(call PRINT_CC, $(HOSTCC) -o $(build_depsbindir)/stringreplace $(JULIAHOME)/contrib/stringreplace.c) @@ -284,14 +210,28 @@ $(eval $(call std_so,libquadmath)) endif # FreeBSD ifeq ($(OS),WINNT) +# find the standard .dll folders +ifeq ($(XC_HOST),) +STD_LIB_PATH ?= $(PATH) +else +STD_LIB_PATH := $(shell LANG=C $(CC) -print-search-dirs | grep programs | sed -e "s/^programs: =//") +STD_LIB_PATH += :$(shell LANG=C $(CC) -print-search-dirs | grep libraries | sed -e "s/^libraries: =//") +ifneq (,$(findstring CYGWIN,$(BUILD_OS))) # the cygwin-mingw32 compiler lies about it search directory paths +STD_LIB_PATH := $(shell echo '$(STD_LIB_PATH)' | sed -e "s!/lib/!/bin/!g") +endif +endif + +pathsearch = $(firstword $(wildcard $(addsuffix /$(1),$(subst :, ,$(2))))) + define std_dll -julia-deps: | $$(build_bindir)/lib$(1).dll $$(build_depsbindir)/lib$(1).dll +julia-deps-libs: | $$(build_bindir)/lib$(1).dll $$(build_depsbindir)/lib$(1).dll $$(build_bindir)/lib$(1).dll: | $$(build_bindir) - cp $$(call pathsearch,lib$(1).dll,$$(STD_LIB_PATH)) $$(build_bindir) + cp $$(or $$(call pathsearch,lib$(1).dll,$$(STD_LIB_PATH)),$$(error can't find lib$1.dll)) $$(build_bindir) $$(build_depsbindir)/lib$(1).dll: | $$(build_depsbindir) - cp $$(call pathsearch,lib$(1).dll,$$(STD_LIB_PATH)) $$(build_depsbindir) + cp $$(or $$(call pathsearch,lib$(1).dll,$$(STD_LIB_PATH)),$$(error can't find lib$1.dll)) $$(build_depsbindir) JL_TARGETS += $(1) endef +julia-deps: julia-deps-libs # Given a list of space-separated libraries, return the first library name that is # correctly found through `pathsearch`. @@ -387,8 +327,6 @@ ifeq ($(BUNDLE_DEBUG_LIBS),1) $(INSTALL_M) $(build_private_libdir)/sys-debug.$(SHLIB_EXT) $(DESTDIR)$(private_libdir) endif - # Copy in system image build script - $(INSTALL_M) $(JULIAHOME)/contrib/build_sysimg.jl $(DESTDIR)$(datarootdir)/julia/ # Copy in all .jl sources as well cp -R -L $(build_datarootdir)/julia $(DESTDIR)$(datarootdir)/ # Copy documentation @@ -406,7 +344,7 @@ endif mkdir -p $(DESTDIR)$(datarootdir)/icons/hicolor/scalable/apps/ $(INSTALL_F) $(JULIAHOME)/contrib/julia.svg $(DESTDIR)$(datarootdir)/icons/hicolor/scalable/apps/ -touch -c $(DESTDIR)$(datarootdir)/icons/hicolor/ - -gtk-update-icon-cache $(DESTDIR)$(datarootdir)/icons/hicolor/ + -gtk-update-icon-cache --ignore-theme-index $(DESTDIR)$(datarootdir)/icons/hicolor/ mkdir -p $(DESTDIR)$(datarootdir)/applications/ $(INSTALL_F) $(JULIAHOME)/contrib/julia.desktop $(DESTDIR)$(datarootdir)/applications/ # Install appdata file @@ -492,11 +430,13 @@ ifeq ($(OS), WINNT) cd $(BUILDROOT)/julia-$(JULIA_COMMIT) && find * | sed -e 's/\//\\/g' -e 's/$$/\r/g' > etc/uninstall.log # build nsis package - cd $(BUILDROOT) && $(call spawn,$(JULIAHOME)/dist-extras/nsis/makensis.exe) -NOCD -DVersion=$(JULIA_VERSION) -DArch=$(ARCH) -DCommit=$(JULIA_COMMIT) -DMUI_ICON="$(call cygpath_w,$(JULIAHOME)/contrib/windows/julia.ico)" $(call cygpath_w,$(JULIAHOME)/contrib/windows/build-installer.nsi) + cd $(BUILDROOT) && $(call spawn,$(JULIAHOME)/dist-extras/nsis/makensis.exe) -NOCD -DVersion=$(JULIA_VERSION) -DArch=$(ARCH) -DCommit=$(JULIA_COMMIT) -DMUI_ICON="$(call cygpath_w,$(JULIAHOME)/contrib/windows/julia.ico)" $(call cygpath_w,$(JULIAHOME)/contrib/windows/build-installer.nsi) | iconv -f latin1 # compress nsis installer and combine with 7zip self-extracting header cd $(BUILDROOT) && $(JULIAHOME)/dist-extras/7z a -mx9 "julia-install-$(JULIA_COMMIT)-$(ARCH).7z" julia-installer.exe cd $(BUILDROOT) && cat $(JULIAHOME)/contrib/windows/7zS.sfx $(JULIAHOME)/contrib/windows/7zSFX-config.txt "julia-install-$(JULIA_COMMIT)-$(ARCH).7z" > "$(JULIA_BINARYDIST_FILENAME).exe" + chmod a+x "$(BUILDROOT)/$(JULIA_BINARYDIST_FILENAME).exe" + -rm -f $(BUILDROOT)/julia-install-$(JULIA_COMMIT)-$(ARCH).7z -rm -f $(BUILDROOT)/julia-installer.exe else cd $(BUILDROOT) && $(TAR) zcvf $(JULIA_BINARYDIST_FILENAME).tar.gz julia-$(JULIA_COMMIT) @@ -582,9 +522,9 @@ distcleanall: cleanall @-$(MAKE) -C $(BUILDROOT)/doc cleanall .PHONY: default debug release check-whitespace release-candidate \ - julia-debug julia-release julia-deps \ + julia-debug julia-release julia-stdlib julia-deps julia-deps-libs \ julia-ui-release julia-ui-debug julia-src-release julia-src-debug \ - julia-symlink julia-base julia-sysimg julia-sysimg-release julia-sysimg-debug \ + julia-symlink julia-base julia-sysimg julia-sysimg-ji julia-sysimg-release julia-sysimg-debug \ test testall testall1 test clean distcleanall cleanall clean-* \ run-julia run-julia-debug run-julia-release run \ install binary-dist light-source-dist.tmp light-source-dist \ @@ -594,8 +534,11 @@ test: check-whitespace $(JULIA_BUILD_MODE) @$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT)/test default JULIA_BUILD_MODE=$(JULIA_BUILD_MODE) JULIA_SYSIMG=$(build_private_libdir)/sys$(JULIA_LIBSUFFIX).$(SHLIB_EXT) + testall: check-whitespace $(JULIA_BUILD_MODE) - cp $(JULIA_SYSIMG) $(BUILDROOT)/local.$(SHLIB_EXT) && $(call spawn, $(JULIA_EXECUTABLE) -J $(call cygpath_w,$(BUILDROOT)/local.$(SHLIB_EXT)) -e 'true' && rm $(BUILDROOT)/local.$(SHLIB_EXT)) + cp $(JULIA_SYSIMG) $(BUILDROOT)/local.$(SHLIB_EXT) + $(call spawn,$(JULIA_EXECUTABLE) -J $(call cygpath_w,$(BUILDROOT)/local.$(SHLIB_EXT)) -e 'true') + rm $(BUILDROOT)/local.$(SHLIB_EXT) @$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT)/test all JULIA_BUILD_MODE=$(JULIA_BUILD_MODE) testall1: check-whitespace $(JULIA_BUILD_MODE) diff --git a/NEWS.md b/NEWS.md index 252d32f78937d4..dcf8eeaa16b4b4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,8 +4,15 @@ Julia v1.2 Release Notes New language features --------------------- -* The `extrema` function now accepts a function argument in the same manner as `minimum` and - `maximum` ([#30323]). + * Argument splatting (`x...`) can now be used in calls to the `new` pseudo-function in + constructors ([#30577]). + + * Objects created by calling `skipmissing` on an array can now be indexed using indices + from the parent at non-missing positions. This allows functions such as + `findall`, `findfirst`, `argmin`/`argmax` and `findmin`/`findmax` to work with these + objects, returning the index of matching non-missing elements in the parent ([#31008]). + + * `inv(::Missing)` has now been added and returns `missing` ([#31408]). Multi-threading changes ----------------------- @@ -15,7 +22,8 @@ Multi-threading changes Language changes ---------------- - +* Empty entries in `JULIA_DEPOT_PATH` are now expanded to default depot entries ([#31009]). +* `Enum` now behaves like a scalar when used in broadcasting ([#30670]). Command-line option changes --------------------------- @@ -25,18 +33,38 @@ New library functions --------------------- * `getipaddrs()` function returns all the IP addresses of the local machine ([#30349]) +* Added `Base.hasproperty` and `Base.hasfield` ([#28850]). +* One argument `!=(x)`, `>(x)`, `>=(x)`, `<(x)`, `<=(x)` has been added for currying, + similar to the existing `==(x)` and `isequal(x)` methods ([#30915]). Standard library changes ------------------------ +* The `extrema` function now accepts a function argument in the same manner as `minimum` and + `maximum` ([#30323]). +* `hasmethod` can now check for matching keyword argument names ([#30712]). +* `startswith` and `endswith` now accept a `Regex` for the second argument ([#29790]). +* `retry` supports arbitrary callable objects ([#30382]). +* `filter` now supports `SkipMissing`-wrapped arrays ([#31235]). +* A no-argument construct to `Ptr{T}` has been added which constructs a null pointer ([#30919]) +* `strip` now accepts a function argument in the same manner as `lstrip` and `rstrip` ([#31211]) +* `mktempdir` now accepts a `prefix` keyword argument to customize the file name ([#31230], [#22922]) #### LinearAlgebra * Added keyword arguments `rtol`, `atol` to `pinv` and `nullspace` ([#29998]). +* `UniformScaling` instances are now callable such that e.g. `I(3)` will produce a `Diagonal` matrix ([#30298]). +* Eigenvalues λ of general matrices are now sorted lexicographically by (Re λ, Im λ) ([#21598]). +* `one` for structured matrices (`Diagonal`, `Bidiagonal`, `Tridiagonal`, `Symtridiagonal`) now preserves + structure and type. ([#29777]) +* `diagm(v)` is now a shorthand for `diagm(0 => v)`. ([#31125]). #### SparseArrays * performance improvements for sparse matrix-matrix multiplication ([#30372]). +* Sparse vector outer products are more performant and maintain sparsity in products of the + form `kron(u, v')`, `u * v'`, and `u .* v'` where `u` and `v` are sparse vectors or column + views. ([#24980]) #### Dates @@ -48,18 +76,47 @@ Standard library changes * Added keyword argument `ssh` to `addprocs` to specify the ssh client path ([#30614]). +#### Miscellaneous + +* Since environment variables on Windows are case-insensitive, `ENV` now converts its keys + to uppercase for display, iteration, and copying ([#30593]). + +* Build system now prefers downloading prebuilt binary tarballs for most dependencies on + supported systems, disable by setting `USE_BINARYBUILDER=0` at `make` time ([#31441]). + External dependencies --------------------- * libgit2 has been updated to v0.27.7 ([#30584]). +* OpenBLAS has been updated to v0.3.5 ([#30583]). +* MbedTLS has been updated to v2.16.0 ([#30618]). +* libunwind has been updated to v1.3.1 ([#30724]). Deprecated or removed --------------------- +[#21598]: https://github.com/JuliaLang/julia/issues/21598 +[#24980]: https://github.com/JuliaLang/julia/issues/24980 +[#28850]: https://github.com/JuliaLang/julia/issues/28850 +[#29777]: https://github.com/JuliaLang/julia/issues/29777 +[#29790]: https://github.com/JuliaLang/julia/issues/29790 [#29998]: https://github.com/JuliaLang/julia/issues/29998 [#30061]: https://github.com/JuliaLang/julia/issues/30061 [#30200]: https://github.com/JuliaLang/julia/issues/30200 +[#30298]: https://github.com/JuliaLang/julia/issues/30298 [#30323]: https://github.com/JuliaLang/julia/issues/30323 [#30349]: https://github.com/JuliaLang/julia/issues/30349 +[#30372]: https://github.com/JuliaLang/julia/issues/30372 +[#30382]: https://github.com/JuliaLang/julia/issues/30382 +[#30577]: https://github.com/JuliaLang/julia/issues/30577 +[#30583]: https://github.com/JuliaLang/julia/issues/30583 +[#30584]: https://github.com/JuliaLang/julia/issues/30584 +[#30593]: https://github.com/JuliaLang/julia/issues/30593 +[#30618]: https://github.com/JuliaLang/julia/issues/30618 +[#30670]: https://github.com/JuliaLang/julia/issues/30670 +[#30712]: https://github.com/JuliaLang/julia/issues/30712 +[#30724]: https://github.com/JuliaLang/julia/issues/30724 +[#30915]: https://github.com/JuliaLang/julia/issues/30915 +[#30919]: https://github.com/JuliaLang/julia/issues/30919 diff --git a/README.md b/README.md index 3bf47feefbd844..eb3925409a0736 100644 --- a/README.md +++ b/README.md @@ -100,9 +100,17 @@ By default you will be building the latest unstable version of Julia. However, m git checkout v1.0.3 -Now run `make` to build the `julia` executable. To perform a parallel build, use `make -j N` and supply the maximum number of concurrent processes. (See [Platform Specific Build Notes](https://github.com/JuliaLang/julia#platform-specific-build-notes) for details.) -When compiled the first time, it will automatically download and build its [external dependencies](#required-build-tools-and-external-libraries). -This takes a while, but only has to be done once. If the defaults in the build do not work for you, and you need to set specific make parameters, you can save them in `Make.user`, and place the file in the root of your Julia source. The build will automatically check for the existence of `Make.user` and use it if it exists. +Now run `make` to build the `julia` executable. To perform a parallel build, use `make -j N` and supply the maximum number of concurrent processes. (See [Platform Specific Build Notes](https://github.com/JuliaLang/julia#platform-specific-build-notes) for details.) If the defaults in the build do not work for you, and you need to set specific make parameters, you can save them in `Make.user`, and place the file in the root of your Julia source. The build will automatically check for the existence of `Make.user` and use it if it exists. + +When compiled the first time, the build will automatically download and build its [external dependencies](#required-build-tools-and-external-libraries). +This takes a while, but only has to be done once. Alternatively, pre-built external dependencies can be used by adding the following in `Make.user` +``` +USE_BINARYBUILDER_LLVM=1 +USE_BINARYBUILDER_OPENBLAS=1 +USE_BINARYBUILDER_SUITESPARSE=1 +``` +this avoids the long compilation times associated with building the external dependencies manually. + Building Julia requires 5GiB of disk space and approximately 2GiB of virtual memory. You can create out-of-tree builds of Julia by specifying `make O= configure` on the command line. This will create a directory mirror, with all of the necessary Makefiles to build Julia, in the specified directory. These builds will share the source files in Julia and `deps/srccache`. Each out-of-tree build directory can have its own `Make.user` file to override the global `Make.user` file in the top-level folder. @@ -139,7 +147,7 @@ if they complete without error, you should be in good shape to start using Julia You can read about [getting started](https://docs.julialang.org/en/stable/manual/getting-started/) in the manual. -If you are building a Julia package for distribution on Linux, OS X, +If you are building a Julia package for distribution on Linux, macOS, or Windows, take a look at the detailed notes in [DISTRIBUTING.md](https://github.com/JuliaLang/julia/blob/master/DISTRIBUTING.md). @@ -183,7 +191,7 @@ latest version. b. To delete existing binaries of `julia` and all its dependencies, delete the `./usr` directory _in the source tree_. -3. If you've updated OS X recently, be sure to run `xcode-select --install` to update the command line tools. +3. If you've updated macOS recently, be sure to run `xcode-select --install` to update the command line tools. Otherwise, you could run into errors for missing headers and libraries, such as ```ld: library not found for -lcrt1.10.6.o```. @@ -211,8 +219,6 @@ Julia does not install anything outside the directory it was cloned into. Julia ### Linux -#### General - * GCC version 4.7 or later is required to build Julia. * To use external shared libraries not in the system library search path, set `USE_SYSTEM_XXX=1` and `LDFLAGS=-Wl,-rpath,/path/to/dir/contains/libXXX.so` in `Make.user`. * Instead of setting `LDFLAGS`, putting the library directory into the environment variable `LD_LIBRARY_PATH` (at both compile and run time) also works. @@ -227,40 +233,21 @@ For example, to build for Pentium 4, set `MARCH=pentium4` and install the necess You can also set `MARCH=native` for a maximum-performance build customized for the current machine CPU. - -#### Ubuntu - -The [julia-deps PPA](https://launchpad.net/~staticfloat/+archive/julia-deps/) contains updated packages for Julia dependencies if you want to use system libraries instead of having them downloaded and built during the build process. See [System Provided Libraries](#system-provided-libraries). - -#### RHEL/CentOS 6 - -On RHEL/CentOS 6 systems, the default compiler (`gcc` 4.4) is too old to build Julia. - -Install or contact your systems administrator to install a more recent version of `gcc`. The [Scientific Linux Developer Toolset](http://linux.web.cern.ch/linux/devtoolset/) works well. - - #### Linux Build Troubleshooting Problem | Possible Solution ------------------------|--------------------- - OpenBLAS build failure | Set one of the following build options in `Make.user` and build again:
  • `OPENBLAS_TARGET_ARCH=BARCELONA` (AMD CPUs) or `OPENBLAS_TARGET_ARCH=NEHALEM` (Intel CPUs)
      Set `OPENBLAS_DYNAMIC_ARCH = 0` to disable compiling multiple architectures in a single binary.
  • `OPENBLAS_NO_AVX2 = 1` disables AVX2 instructions, allowing OpenBLAS to compile with `OPENBLAS_DYNAMIC_ARCH = 1` using old versions of binutils
  • `USE_SYSTEM_BLAS=1` uses the system provided `libblas`
    • Set `LIBBLAS=-lopenblas` and `LIBBLASNAME=libopenblas` to force the use of the system provided OpenBLAS when multiple BLAS versions are installed.

If you get an error that looks like ```../kernel/x86_64/dgemm_kernel_4x4_haswell.S:1709: Error: no such instruction: `vpermpd $ 0xb1,%ymm0,%ymm0'```, then you need to set `OPENBLAS_DYNAMIC_ARCH = 0` or `OPENBLAS_NO_AVX2 = 1`, or you need a newer version of `binutils` (2.18 or newer). ([Issue #7653](https://github.com/JuliaLang/julia/issues/7653)) + OpenBLAS build failure | Set one of the following build options in `Make.user` and build again:

  • `OPENBLAS_TARGET_ARCH=BARCELONA` (AMD CPUs) or `OPENBLAS_TARGET_ARCH=NEHALEM` (Intel CPUs)
      Set `OPENBLAS_DYNAMIC_ARCH = 0` to disable compiling multiple architectures in a single binary.
  • `OPENBLAS_NO_AVX2 = 1` disables AVX2 instructions, allowing OpenBLAS to compile with `OPENBLAS_DYNAMIC_ARCH = 1` using old versions of binutils
  • `USE_SYSTEM_BLAS=1` uses the system provided `libblas`
    • Set `LIBBLAS=-lopenblas` and `LIBBLASNAME=libopenblas` to force the use of the system provided OpenBLAS when multiple BLAS versions are installed.

If you get an error that looks like ```../kernel/x86_64/dgemm_kernel_4x4_haswell.S:1709: Error: no such instruction: `vpermpd $ 0xb1,%ymm0,%ymm0'```, then you need to set `OPENBLAS_DYNAMIC_ARCH = 0` or `OPENBLAS_NO_AVX2 = 1`, or you need a newer version of `binutils` (2.18 or newer). ([Issue #7653](https://github.com/JuliaLang/julia/issues/7653))

If the linker cannot find `gfortran` and you get an error like `julia /usr/bin/x86_64-linux-gnu-ld: cannot find -lgfortran`, check the path with `gfortran -print-file-name=libgfortran.so` and use the output to export something similar to this: `export LDFLAGS=-L/usr/lib/gcc/x86_64-linux-gnu/8/`. See [Issue #6150](https://github.com/JuliaLang/julia/issues/6150#issuecomment-37546803).

Illegal Instruction error | Check if your CPU supports AVX while your OS does not (e.g. through virtualization, as described in [this issue](https://github.com/JuliaLang/julia/issues/3263)). -### OS X +### macOS You need to have the current Xcode command line utilities installed: run `xcode-select --install` in the terminal. -You will need to rerun this terminal command after each OS X update, otherwise you may run into errors involving missing libraries or headers. -You will also need a 64-bit gfortran to compile Julia dependencies. The gfortran-4.7 (and newer) compilers in Brew, Fink, and MacPorts work for building Julia. - -Clang is now used by default to build Julia on OS X 10.7 and above. On OS X 10.6, the Julia build will automatically use `gcc`. -On current systems, we recommend that you install the command line tools as described above. Older systems do not have a separate command line tools package from Apple, and will require a full Xcode install. On these, you will need at least Xcode 4.3.3. In Xcode prior to v5.0, you can alternatively go to Preferences -> Downloads and select the Command Line Utilities. These steps will ensure that clang v3.1 is installed, which is the minimum version of `clang` required to build Julia. +You will need to rerun this terminal command after each macOS update, otherwise you may run into errors involving missing libraries or headers. +You will also need a 64-bit gfortran to compile Julia dependencies. The gfortran-4.7 (and newer) compilers in Homebrew work for building Julia. If you have set `LD_LIBRARY_PATH` or `DYLD_LIBRARY_PATH` in your `.bashrc` or equivalent, Julia may be unable to find various libraries that come bundled with it. These environment variables need to be unset for Julia to work. -If you see build failures in OpenBLAS or if you prefer to experiment, you can use the Apple provided BLAS in vecLib by building with `USE_SYSTEM_BLAS=1`. Julia does not use the Apple provided LAPACK, as it is too old. - -When building Julia, or its dependencies, libraries installed by third party package managers can redirect the compiler to use an incompatible version of the software it is looking for. One example of this happening is when a piece of software called the "linker" gives an error involving "Undefined symbols." If that happens, you can usually figure out what software package is causing the error from the names in the error text. This sort of error can be bypassed by, temporarily, uninstalling the offending package. If the offending package cannot be uninstalled by itself, it may be possible to just uninstall the development headers (for example: a package ending in "-dev" in Fink). - ### FreeBSD Clang is the default compiler on FreeBSD 11.0-RELEASE and above. @@ -294,7 +281,7 @@ Julia can be developed in an isolated Vagrant environment. See [the Vagrant READ Building Julia requires that the following software be installed: - **[GNU make]** — building dependencies. -- **[gcc & g++][gcc]** (>= 4.7) or **[Clang][clang]** (>= 3.1, Xcode 4.3.3 on OS X) — compiling and linking C, C++. +- **[gcc & g++][gcc]** (>= 4.7) or **[Clang][clang]** (>= 3.1, Xcode 4.3.3 on macOS) — compiling and linking C, C++. - **[libatomic][gcc]** — provided by **[gcc]** and needed to support atomic operations. - **[python]** (>=2.7) — needed to build LLVM. - **[gfortran]** — compiling and linking Fortran libraries. diff --git a/README.windows.md b/README.windows.md index a13c1ba5a86b09..4493aff973620b 100644 --- a/README.windows.md +++ b/README.windows.md @@ -166,10 +166,7 @@ for the former instructions for compiling using MSYS2. ### Cross-compiling from Unix You can also use MinGW-w64 cross compilers to build a Windows version of Julia from -Linux, Mac, or the Windows Subsystem for Linux (WSL). Note that when compiling in -WSL, you should use the Linux file system environment, not the `/mnt/` emulated Windows -paths, since time stamps in `/mnt/` do not work properly as required by configure -scripts and makefiles (see https://github.com/Microsoft/BashOnWindows/issues/1939). +Linux, Mac, or the Windows Subsystem for Linux (WSL). For maximum compatibility with packages that use [WinRPM.jl]( https://github.com/JuliaLang/WinRPM.jl) for binary dependencies on Windows, it diff --git a/base/Base.jl b/base/Base.jl new file mode 100644 index 00000000000000..ceae769a123752 --- /dev/null +++ b/base/Base.jl @@ -0,0 +1,400 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +baremodule Base + +using Core.Intrinsics, Core.IR + +const is_primary_base_module = ccall(:jl_module_parent, Ref{Module}, (Any,), Base) === Core.Main +ccall(:jl_set_istopmod, Cvoid, (Any, Bool), Base, is_primary_base_module) + +# Try to help prevent users from shooting them-selves in the foot +# with ambiguities by defining a few common and critical operations +# (and these don't need the extra convert code) +getproperty(x::Module, f::Symbol) = getfield(x, f) +setproperty!(x::Module, f::Symbol, v) = setfield!(x, f, v) +getproperty(x::Type, f::Symbol) = getfield(x, f) +setproperty!(x::Type, f::Symbol, v) = setfield!(x, f, v) +getproperty(x::Tuple, f::Int) = getfield(x, f) +setproperty!(x::Tuple, f::Int, v) = setfield!(x, f, v) # to get a decent error + +getproperty(Core.@nospecialize(x), f::Symbol) = getfield(x, f) +setproperty!(x, f::Symbol, v) = setfield!(x, f, convert(fieldtype(typeof(x), f), v)) + +function include_relative end +function include(mod::Module, path::AbstractString) + local result + if INCLUDE_STATE === 1 + result = _include1(mod, path) + elseif INCLUDE_STATE === 2 + result = _include(mod, path) + elseif INCLUDE_STATE === 3 + result = include_relative(mod, path) + end + result +end +function include(path::AbstractString) + local result + if INCLUDE_STATE === 1 + result = _include1(Base, path) + elseif INCLUDE_STATE === 2 + result = _include(Base, path) + else + # to help users avoid error (accidentally evaluating into Base), this is not allowed + error("Base.include(string) is discontinued, use `include(fname)` or `Base.include(@__MODULE__, fname)` instead.") + end + result +end +const _included_files = Array{Tuple{Module,String},1}() +function _include1(mod::Module, path) + Core.Compiler.push!(_included_files, (mod, ccall(:jl_prepend_cwd, Any, (Any,), path))) + Core.include(mod, path) +end +let SOURCE_PATH = "" + # simple, race-y TLS, relative include + global _include + function _include(mod::Module, path) + prev = SOURCE_PATH + path = normpath(joinpath(dirname(prev), path)) + push!(_included_files, (mod, abspath(path))) + SOURCE_PATH = path + result = Core.include(mod, path) + SOURCE_PATH = prev + result + end +end +INCLUDE_STATE = 1 # include = Core.include + +include("coreio.jl") + +eval(x) = Core.eval(Base, x) +eval(m::Module, x) = Core.eval(m, x) + +# init core docsystem +import Core: @doc, @__doc__, WrappedException, @int128_str, @uint128_str, @big_str, @cmd +if isdefined(Core, :Compiler) + import Core.Compiler.CoreDocs + Core.atdoc!(CoreDocs.docm) +end + +include("exports.jl") + +if false + # simple print definitions for debugging. enable these if something + # goes wrong during bootstrap before printing code is available. + # otherwise, they just just eventually get (noisily) overwritten later + global show, print, println + show(io::IO, x) = Core.show(io, x) + print(io::IO, a...) = Core.print(io, a...) + println(io::IO, x...) = Core.println(io, x...) +end + +""" + time_ns() + +Get the time in nanoseconds. The time corresponding to 0 is undefined, and wraps every 5.8 years. +""" +time_ns() = ccall(:jl_hrtime, UInt64, ()) + +start_base_include = time_ns() + +## Load essential files and libraries +include("essentials.jl") +include("ctypes.jl") +include("gcutils.jl") +include("generator.jl") +include("reflection.jl") +include("options.jl") + +# core operations & types +include("promotion.jl") +include("tuple.jl") +include("expr.jl") +include("pair.jl") +include("traits.jl") +include("range.jl") +include("error.jl") + +# core numeric operations & types +include("bool.jl") +include("number.jl") +include("int.jl") +include("operators.jl") +include("pointer.jl") +include("refvalue.jl") +include("refpointer.jl") +include("checked.jl") +using .Checked + +# array structures +include("indices.jl") +include("array.jl") +include("abstractarray.jl") +include("subarray.jl") +include("views.jl") +include("baseext.jl") + +include("ntuple.jl") + +include("abstractdict.jl") + +include("iterators.jl") +using .Iterators: zip, enumerate +using .Iterators: Flatten, Filter, product # for generators + +include("namedtuple.jl") + +# numeric operations +include("hashing.jl") +include("rounding.jl") +using .Rounding +include("float.jl") +include("twiceprecision.jl") +include("complex.jl") +include("rational.jl") +include("multinverses.jl") +using .MultiplicativeInverses +include("abstractarraymath.jl") +include("arraymath.jl") + +# SIMD loops +include("simdloop.jl") +using .SimdLoop + +# map-reduce operators +include("reduce.jl") + +## core structures +include("reshapedarray.jl") +include("reinterpretarray.jl") +include("bitarray.jl") +include("bitset.jl") + +if !isdefined(Core, :Compiler) + include("docs/core.jl") + Core.atdoc!(CoreDocs.docm) +end + +include("multimedia.jl") +using .Multimedia + +# Some type +include("some.jl") + +include("dict.jl") +include("abstractset.jl") +include("set.jl") + +include("char.jl") +include("strings/basic.jl") +include("strings/string.jl") +include("strings/substring.jl") + +# For OS specific stuff +include(string((length(Core.ARGS)>=2 ? Core.ARGS[2] : ""), "build_h.jl")) # include($BUILDROOT/base/build_h.jl) +include(string((length(Core.ARGS)>=2 ? Core.ARGS[2] : ""), "version_git.jl")) # include($BUILDROOT/base/version_git.jl) + +include("osutils.jl") +include("c.jl") + +# Core I/O +include("io.jl") +include("iostream.jl") +include("iobuffer.jl") + +# strings & printing +include("intfuncs.jl") +include("strings/strings.jl") +include("parse.jl") +include("shell.jl") +include("regex.jl") +include("show.jl") +include("arrayshow.jl") +include("methodshow.jl") + +# multidimensional arrays +include("cartesian.jl") +using .Cartesian +include("multidimensional.jl") +include("permuteddimsarray.jl") +using .PermutedDimsArrays + +include("broadcast.jl") +using .Broadcast +using .Broadcast: broadcasted, broadcasted_kwsyntax, materialize, materialize! + +# missing values +include("missing.jl") + +# version +include("version.jl") + +# system & environment +include("sysinfo.jl") +include("libc.jl") +using .Libc: getpid, gethostname, time + +const DL_LOAD_PATH = String[] +if Sys.isapple() + push!(DL_LOAD_PATH, "@loader_path/julia") + push!(DL_LOAD_PATH, "@loader_path") +end + +include("env.jl") + +# Scheduling +include("linked_list.jl") +include("condition.jl") +include("threads.jl") +include("lock.jl") +include("task.jl") +include("weakkeydict.jl") + +# Logging +include("logging.jl") +using .CoreLogging + +# functions defined in Random +function rand end +function randn end + +# I/O +include("libuv.jl") +include("asyncevent.jl") +include("stream.jl") +include("filesystem.jl") +using .Filesystem +include("process.jl") +include("grisu/grisu.jl") +include("secretbuffer.jl") + +# core math functions +include("floatfuncs.jl") +include("math.jl") +using .Math +const (√)=sqrt +const (∛)=cbrt + +INCLUDE_STATE = 2 # include = _include (from lines above) + +# reduction along dims +include("reducedim.jl") # macros in this file relies on string.jl +include("accumulate.jl") + +# basic data structures +include("ordering.jl") +using .Order + +# Combinatorics +include("sort.jl") +using .Sort + +# Fast math +include("fastmath.jl") +using .FastMath + +function deepcopy_internal end + +# enums +include("Enums.jl") +using .Enums + +# BigInts and BigFloats +include("gmp.jl") +using .GMP + +include("mpfr.jl") +using .MPFR + +include("combinatorics.jl") + +# more hashing definitions +include("hashing2.jl") + +# irrational mathematical constants +include("irrationals.jl") +include("mathconstants.jl") +using .MathConstants: ℯ, π, pi + +# (s)printf macros +include("printf.jl") +# import .Printf + +# metaprogramming +include("meta.jl") + +# concurrency and parallelism +include("channels.jl") + +# utilities +include("deepcopy.jl") +include("download.jl") +include("summarysize.jl") +include("errorshow.jl") + +# Stack frames and traces +include("stacktraces.jl") +using .StackTraces + +include("initdefs.jl") + +# worker threads +include("threadcall.jl") + +# code loading +include("uuid.jl") +include("loading.jl") + +# misc useful functions & macros +include("util.jl") + +include("asyncmap.jl") + +# deprecated functions +include("deprecated.jl") + +# Some basic documentation +include("docs/basedocs.jl") + +include("client.jl") + +# Documentation -- should always be included last in sysimg. +include("docs/Docs.jl") +using .Docs +if isdefined(Core, :Compiler) && is_primary_base_module + Docs.loaddocs(Core.Compiler.CoreDocs.DOCS) +end + +end_base_include = time_ns() + +if is_primary_base_module +function __init__() + # try to ensuremake sure OpenBLAS does not set CPU affinity (#1070, #9639) + if !haskey(ENV, "OPENBLAS_MAIN_FREE") && !haskey(ENV, "GOTOBLAS_MAIN_FREE") + ENV["OPENBLAS_MAIN_FREE"] = "1" + end + # And try to prevent openblas from starting too many threads, unless/until specifically requested + if !haskey(ENV, "OPENBLAS_NUM_THREADS") && !haskey(ENV, "OMP_NUM_THREADS") + cpu_threads = Sys.CPU_THREADS::Int + if cpu_threads > 8 # always at most 8 + ENV["OPENBLAS_NUM_THREADS"] = "8" + elseif haskey(ENV, "JULIA_CPU_THREADS") # or exactly as specified + ENV["OPENBLAS_NUM_THREADS"] = cpu_threads + end # otherwise, trust that openblas will pick CPU_THREADS anyways, without any intervention + end + # for the few uses of Libc.rand in Base: + Libc.srand() + # Base library init + reinit_stdio() + Multimedia.reinit_displays() # since Multimedia.displays uses stdout as fallback + # initialize loading + init_depot_path() + init_load_path() + nothing +end + +INCLUDE_STATE = 3 # include = include_relative +end + +const tot_time_stdlib = RefValue(0.0) + +end # baremodule Base diff --git a/base/Enums.jl b/base/Enums.jl index 87f99e7114789e..d27473d3909725 100644 --- a/base/Enums.jl +++ b/base/Enums.jl @@ -5,14 +5,57 @@ module Enums import Core.Intrinsics.bitcast export Enum, @enum -function basetype end +function namemap end +""" + Enum{T<:Integer} + +The abstract supertype of all enumerated types defined with [`@enum`](@ref). +""" abstract type Enum{T<:Integer} end +basetype(::Type{<:Enum{T}}) where {T<:Integer} = T + (::Type{T})(x::Enum{T2}) where {T<:Integer,T2<:Integer} = T(bitcast(T2, x))::T Base.cconvert(::Type{T}, x::Enum{T2}) where {T<:Integer,T2<:Integer} = T(x) Base.write(io::IO, x::Enum{T}) where {T<:Integer} = write(io, T(x)) -Base.read(io::IO, ::Type{T}) where {T<:Enum} = T(read(io, Enums.basetype(T))) +Base.read(io::IO, ::Type{T}) where {T<:Enum} = T(read(io, basetype(T))) + +Base.isless(x::T, y::T) where {T<:Enum} = isless(basetype(T)(x), basetype(T)(y)) + +Base.Symbol(x::Enum) = namemap(typeof(x))[Integer(x)]::Symbol + +Base.print(io::IO, x::Enum) = print(io, Symbol(x)) + +function Base.show(io::IO, x::Enum) + sym = Symbol(x) + if !get(io, :compact, false) + from = get(io, :module, Main) + def = typeof(x).name.module + if from === nothing || !Base.isvisible(sym, def, from) + show(io, def) + print(io, ".") + end + end + print(io, sym) +end + +function Base.show(io::IO, ::MIME"text/plain", x::Enum) + print(io, x, "::") + show(IOContext(io, :compact => true), typeof(x)) + print(io, " = ") + show(io, Integer(x)) +end + +function Base.show(io::IO, ::MIME"text/plain", t::Type{<:Enum}) + print(io, "Enum ") + Base.show_datatype(io, t) + print(io, ":") + for x in instances(t) + print(io, "\n", Symbol(x), " = ") + show(io, Integer(x)) + end +end # generate code to test whether expr is in the given set of values function membershiptest(expr, values) @@ -26,6 +69,9 @@ function membershiptest(expr, values) end end +# give Enum types scalar behavior in broadcasting +Base.broadcastable(x::Enum) = Ref(x) + @noinline enum_argument_error(typename, x) = throw(ArgumentError(string("invalid value for Enum $(typename): $x"))) """ @@ -66,7 +112,7 @@ To list all the instances of an enum use `instances`, e.g. ```jldoctest fruitenum julia> instances(Fruit) -(apple::Fruit = 1, orange::Fruit = 2, kiwi::Fruit = 3) +(apple, orange, kiwi) ``` """ macro enum(T, syms...) @@ -84,7 +130,9 @@ macro enum(T, syms...) elseif !isa(T, Symbol) throw(ArgumentError("invalid type expression for enum $T")) end - vals = Vector{Tuple{Symbol,Integer}}() + values = basetype[] + seen = Set{Symbol}() + namemap = Dict{basetype,Symbol}() lo = hi = 0 i = zero(basetype) hasexpr = false @@ -95,7 +143,7 @@ macro enum(T, syms...) for s in syms s isa LineNumberNode && continue if isa(s, Symbol) - if i == typemin(basetype) && !isempty(vals) + if i == typemin(basetype) && !isempty(values) throw(ArgumentError("overflow in value \"$s\" of Enum $typename")) end elseif isa(s, Expr) && @@ -112,10 +160,18 @@ macro enum(T, syms...) throw(ArgumentError(string("invalid argument for Enum ", typename, ": ", s))) end if !Base.isidentifier(s) - throw(ArgumentError("invalid name for Enum $typename; \"$s\" is not a valid identifier.")) + throw(ArgumentError("invalid name for Enum $typename; \"$s\" is not a valid identifier")) end - push!(vals, (s,i)) - if length(vals) == 1 + if hasexpr && haskey(namemap, i) + throw(ArgumentError("both $s and $(namemap[i]) have value $i in Enum $typename; values must be unique")) + end + namemap[i] = s + push!(values, i) + if s in seen + throw(ArgumentError("name \"$s\" in Enum $typename is not unique")) + end + push!(seen, s) + if length(values) == 1 lo = hi = i else lo = min(lo, i) @@ -123,10 +179,6 @@ macro enum(T, syms...) end i += oneunit(i) end - values = basetype[i[2] for i in vals] - if hasexpr && values != unique(values) - throw(ArgumentError("values for Enum $typename are not unique")) - end blk = quote # enum definition Base.@__doc__(primitive type $(esc(typename)) <: Enum{$(basetype)} $(sizeof(basetype) * 8) end) @@ -134,42 +186,15 @@ macro enum(T, syms...) $(membershiptest(:x, values)) || enum_argument_error($(Expr(:quote, typename)), x) return bitcast($(esc(typename)), convert($(basetype), x)) end - Enums.basetype(::Type{$(esc(typename))}) = $(esc(basetype)) + Enums.namemap(::Type{$(esc(typename))}) = $(esc(namemap)) Base.typemin(x::Type{$(esc(typename))}) = $(esc(typename))($lo) Base.typemax(x::Type{$(esc(typename))}) = $(esc(typename))($hi) - Base.isless(x::$(esc(typename)), y::$(esc(typename))) = isless($basetype(x), $basetype(y)) - let insts = ntuple(i->$(esc(typename))($values[i]), $(length(vals))) + let insts = ntuple(i->$(esc(typename))($values[i]), $(length(values))) Base.instances(::Type{$(esc(typename))}) = insts end - function Base.print(io::IO, x::$(esc(typename))) - for (sym, i) in $vals - if i == $(basetype)(x) - print(io, sym); break - end - end - end - function Base.show(io::IO, x::$(esc(typename))) - if get(io, :compact, false) - print(io, x) - else - print(io, x, "::") - show(IOContext(io, :compact => true), typeof(x)) - print(io, " = ") - show(io, $basetype(x)) - end - end - function Base.show(io::IO, ::MIME"text/plain", t::Type{$(esc(typename))}) - print(io, "Enum ") - Base.show_datatype(io, t) - print(io, ":") - for (sym, i) in $vals - print(io, "\n", sym, " = ") - show(io, i) - end - end end if isa(typename, Symbol) - for (sym,i) in vals + for (i, sym) in namemap push!(blk.args, :(const $(esc(sym)) = $(esc(typename))($i))) end end diff --git a/base/Makefile b/base/Makefile index acdd4bb9f82d6c..91c7c9b015fd04 100644 --- a/base/Makefile +++ b/base/Makefile @@ -14,7 +14,7 @@ endif all: $(addprefix $(BUILDDIR)/,pcre_h.jl errno_h.jl build_h.jl.phony file_constants.jl uv_constants.jl version_git.jl.phony) -PCRE_CONST := 0x[0-9a-fA-F]+|[0-9]+ +PCRE_CONST := 0x[0-9a-fA-F]+|[0-9]+|\([\-0-9]+\) ifeq ($(USE_SYSTEM_PCRE), 1) PCRE_INCL_PATH := $(shell $(PCRE_CONFIG) --prefix)/include/pcre2.h else @@ -22,7 +22,7 @@ else endif $(BUILDDIR)/pcre_h.jl: $(PCRE_INCL_PATH) - @$(call PRINT_PERL, $(CPP) -D PCRE2_CODE_UNIT_WIDTH=8 -dM $< | perl -nle '/^\s*#define\s+PCRE2_(\w*)\s*\(?($(PCRE_CONST))\)?u?\s*$$/ and print "const $$1 = UInt32($$2)"' | LC_ALL=C sort > $@) + @$(call PRINT_PERL, $(CPP) -D PCRE2_CODE_UNIT_WIDTH=8 -dM $< | perl -nle '/^\s*#define\s+PCRE2_(\w*)\s*\(?($(PCRE_CONST))\)?u?\s*$$/ and print "const $$1 = $$2 % UInt32"' | LC_ALL=C sort > $@) $(BUILDDIR)/errno_h.jl: @$(call PRINT_PERL, echo '#include ' | $(CPP) -dM - | perl -nle 'print "const $$1 = Int32($$2)" if /^#define\s+(E\w+)\s+(\d+)\s*$$/' | LC_ALL=C sort > $@) @@ -194,12 +194,11 @@ $(eval $(call symlink_system_library,libcolamd,SUITESPARSE)) $(eval $(call symlink_system_library,libumfpack,SUITESPARSE)) $(eval $(call symlink_system_library,libspqr,SUITESPARSE)) $(eval $(call symlink_system_library,libsuitesparseconfig,SUITESPARSE)) -ifneq ($(DISABLE_LIBUNWIND),0) -$(eval $(call symlink_system_library,libunwind,LIBUNWIND)) -endif +# EXCLUDED LIBRARIES (installed/used, but not vendored for use with dlopen): +# libunwind endif # WINNT -symlink_libLLVM: $(build_private_libdir)/libLLVM.dylib +symlink_libLLVM: $(build_private_libdir)/libLLVM.$(SHLIB_EXT) ifneq ($(USE_SYSTEM_LLVM),0) LLVM_CONFIG_HOST_LIBS := $(shell $(LLVM_CONFIG_HOST) --libfiles) # HACK: llvm-config doesn't correctly point to shared libs on all platforms @@ -207,7 +206,7 @@ LLVM_CONFIG_HOST_LIBS := $(shell $(LLVM_CONFIG_HOST) --libfiles) else LLVM_CONFIG_HOST_LIBS := $(shell $(LLVM_CONFIG_HOST) --libdir)/libLLVM.$(SHLIB_EXT) endif -$(build_private_libdir)/libLLVM.dylib: +$(build_private_libdir)/libLLVM.$(SHLIB_EXT): REALPATH=$(LLVM_CONFIG_HOST_LIBS) && \ $(call resolve_path,REALPATH) && \ [ -e "$$REALPATH" ] && \ diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 6b8f6b92b0bd64..b589214f158e58 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -35,7 +35,7 @@ julia> size(A, 2) 3 ``` """ -size(t::AbstractArray{T,N}, d) where {T,N} = d <= N ? size(t)[d] : 1 +size(t::AbstractArray{T,N}, d) where {T,N} = d::Integer <= N ? size(t)[d] : 1 """ axes(A, d) @@ -54,7 +54,7 @@ Base.OneTo(6) """ function axes(A::AbstractArray{T,N}, d) where {T,N} @_inline_meta - d <= N ? axes(A)[d] : OneTo(1) + d::Integer <= N ? axes(A)[d] : OneTo(1) end """ @@ -86,6 +86,8 @@ has_offset_axes(A) = _tuple_any(x->first(x)!=1, axes(A)) has_offset_axes(A...) = _tuple_any(has_offset_axes, A) has_offset_axes(::Colon) = false +require_one_based_indexing(A...) = !has_offset_axes(A...) || throw(ArgumentError("offset arrays are not supported but got an array with index other than 1")) + # Performance optimization: get rid of a branch on `d` in `axes(A, d)` # for d=1. 1d arrays are heavily used, and the first dimension comes up # in other applications. @@ -164,6 +166,13 @@ eachindex(itrs...) = keys(itrs...) # eachindex iterates over all indices. IndexCartesian definitions are later. eachindex(A::AbstractVector) = (@_inline_meta(); axes1(A)) +@noinline function throw_eachindex_mismatch(::IndexLinear, A...) + throw(DimensionMismatch("all inputs to eachindex must have the same indices, got $(join(LinearIndices.(A), ", ", " and "))")) +end +@noinline function throw_eachindex_mismatch(::IndexCartesian, A...) + throw(DimensionMismatch("all inputs to eachindex must have the same axes, got $(join(axes.(A), ", ", " and "))")) +end + """ eachindex(A...) @@ -553,8 +562,8 @@ elements since `BitArray`s are both mutable and can support 1-dimensional arrays ```julia-repl julia> similar(trues(10,10), 2) 2-element BitArray{1}: - false - false + 0 + 0 ``` Since `BitArray`s can only store elements of type [`Bool`](@ref), however, if you request a @@ -667,7 +676,7 @@ function copyto!(dest::AbstractArray, src) y = iterate(destiter) for x in src y === nothing && - throw(ArgumentError(string("destination has fewer elements than required"))) + throw(ArgumentError("destination has fewer elements than required")) dest[y[1]] = x y = iterate(destiter, y[2]) end @@ -698,7 +707,7 @@ function copyto!(dest::AbstractArray, dstart::Integer, src, sstart::Integer) end if y === nothing throw(ArgumentError(string("source has fewer elements than required, ", - "expected at least ",sstart,", got ",sstart-1))) + "expected at least ",sstart,", got ",sstart-1))) end i = Int(dstart) while y != nothing @@ -2009,7 +2018,8 @@ concatenate_setindex!(R, X::AbstractArray, I...) = (R[I...] = X) function map!(f::F, dest::AbstractArray, A::AbstractArray) where F for (i,j) in zip(eachindex(dest),eachindex(A)) - dest[i] = f(A[j]) + val = f(@inbounds A[j]) + @inbounds dest[i] = val end return dest end @@ -2049,7 +2059,9 @@ map(f, ::AbstractSet) = error("map is not defined on sets") ## 2 argument function map!(f::F, dest::AbstractArray, A::AbstractArray, B::AbstractArray) where F for (i, j, k) in zip(eachindex(dest), eachindex(A), eachindex(B)) - dest[i] = f(A[j], B[k]) + @inbounds a, b = A[j], B[k] + val = f(a, b) + @inbounds dest[i] = val end return dest end @@ -2057,11 +2069,18 @@ end ## N argument @inline ith_all(i, ::Tuple{}) = () -@inline ith_all(i, as) = (as[1][i], ith_all(i, tail(as))...) +function ith_all(i, as) + @_propagate_inbounds_meta + return (as[1][i], ith_all(i, tail(as))...) +end function map_n!(f::F, dest::AbstractArray, As) where F - for i = LinearIndices(As[1]) - dest[i] = f(ith_all(i, As)...) + idxs1 = LinearIndices(As[1]) + @boundscheck LinearIndices(dest) == idxs1 && all(x -> LinearIndices(x) == idxs1, As) + for i = idxs1 + @inbounds I = ith_all(i, As) + val = f(I...) + @inbounds dest[i] = val end return dest end diff --git a/base/abstractarraymath.jl b/base/abstractarraymath.jl index eb435b786a8318..9c7b098ff0d428 100644 --- a/base/abstractarraymath.jl +++ b/base/abstractarraymath.jl @@ -5,7 +5,6 @@ isreal(x::AbstractArray) = all(isreal,x) iszero(x::AbstractArray) = all(iszero,x) isreal(x::AbstractArray{<:Real}) = true -all(::typeof(isinteger), ::AbstractArray{<:Integer}) = true ## Constructors ## @@ -122,7 +121,7 @@ julia> selectdim(A, 2, 3) """ @inline selectdim(A::AbstractArray, d::Integer, i) = _selectdim(A, d, i, setindex(map(Slice, axes(A)), i, d)) @noinline function _selectdim(A, d, i, idxs) - d >= 1 || throw(ArgumentError("dimension must be ≥ 1")) + d >= 1 || throw(ArgumentError("dimension must be ≥ 1, got $d")) nd = ndims(A) d > nd && (i == 1 || throw(BoundsError(A, (ntuple(k->Colon(),d-1)..., i)))) return view(A, idxs...) @@ -214,27 +213,27 @@ julia> circshift(b, (-1,0)) julia> a = BitArray([true, true, false, false, true]) 5-element BitArray{1}: - true - true - false - false - true + 1 + 1 + 0 + 0 + 1 julia> circshift(a, 1) 5-element BitArray{1}: - true - true - true - false - false + 1 + 1 + 1 + 0 + 0 julia> circshift(a, -1) 5-element BitArray{1}: - true - false - false - true - true + 1 + 0 + 0 + 1 + 1 ``` See also [`circshift!`](@ref). diff --git a/base/abstractdict.jl b/base/abstractdict.jl index e864f403ae5b6d..cddf03b60c5352 100644 --- a/base/abstractdict.jl +++ b/base/abstractdict.jl @@ -563,7 +563,7 @@ end empty(d::IdDict, ::Type{K}, ::Type{V}) where {K, V} = IdDict{K,V}() function rehash!(d::IdDict, newsz = length(d.ht)) - d.ht = ccall(:jl_idtable_rehash, Any, (Any, Csize_t), d.ht, newsz) + d.ht = ccall(:jl_idtable_rehash, Vector{Any}, (Any, Csize_t), d.ht, newsz) d end @@ -578,7 +578,7 @@ function sizehint!(d::IdDict, newsz) end function setindex!(d::IdDict{K,V}, @nospecialize(val), @nospecialize(key)) where {K, V} - !isa(key, K) && throw(ArgumentError("$key is not a valid key for type $K")) + !isa(key, K) && throw(ArgumentError("$(limitrepr(key)) is not a valid key for type $K")) val = convert(V, val) if d.ndel >= ((3*length(d.ht))>>2) rehash!(d, max(length(d.ht)>>1, 32)) @@ -701,3 +701,33 @@ function iterate(s::IdSet, state...) ((k, _), i) = y return (k, i) end + +""" + map!(f, values(dict::AbstractDict)) + +Modifies `dict` by transforming each value from `val` to `f(val)`. +Note that the type of `dict` cannot be changed: if `f(val)` is not an instance of the key type +of `dict` then it will be converted to the key type if possible and otherwise raise an error. + +# Examples +```jldoctest +julia> d = Dict(:a => 1, :b => 2) +Dict{Symbol,Int64} with 2 entries: + :a => 1 + :b => 2 + +julia> map!(v -> v-1, values(d)) +Dict{Symbol,Int64} with 2 entries: + :a => 0 + :b => 1 + ``` +""" +function map!(f, iter::ValueIterator) + # This is the naive fallback which requires hash evaluations + # Contrary to the example Dict has an implementation which does not require hash evaluations + dict = iter.dict + for (key, val) in pairs(dict) + dict[key] = f(val) + end + return iter +end diff --git a/base/array.jl b/base/array.jl index d573a1aa39d4d0..a80f4813881582 100644 --- a/base/array.jl +++ b/base/array.jl @@ -124,7 +124,7 @@ eltype(::Type) = Any eltype(::Type{Bottom}) = throw(ArgumentError("Union{} does not have elements")) eltype(x) = eltype(typeof(x)) -import Core: arraysize, arrayset, arrayref +import Core: arraysize, arrayset, arrayref, const_arrayref vect() = Vector{Any}() vect(X::T...) where {T} = T[ X[i] for i = 1:length(X) ] @@ -151,7 +151,7 @@ function vect(X...) return copyto!(Vector{T}(undef, length(X)), X) end -size(a::Array, d) = arraysize(a, d) +size(a::Array, d::Integer) = arraysize(a, convert(Int, d)) size(a::Vector) = (arraysize(a,1),) size(a::Matrix) = (arraysize(a,1), arraysize(a,2)) size(a::Array{<:Any,N}) where {N} = (@_inline_meta; ntuple(M -> size(a, M), Val(N))) @@ -192,7 +192,8 @@ julia> Base.bitsunionsize(Union{Float64, UInt8, Int128}) function bitsunionsize(u::Union) sz = Ref{Csize_t}(0) algn = Ref{Csize_t}(0) - @assert ccall(:jl_islayout_inline, Cint, (Any, Ptr{Csize_t}, Ptr{Csize_t}), u, sz, algn) != Cint(0) + isunboxed = ccall(:jl_islayout_inline, Cint, (Any, Ptr{Csize_t}, Ptr{Csize_t}), u, sz, algn) + @assert isunboxed != Cint(0) return sz[] end @@ -267,7 +268,7 @@ offset `do`. Return `dest`. """ function copyto!(dest::Array{T}, doffs::Integer, src::Array{T}, soffs::Integer, n::Integer) where T n == 0 && return dest - n > 0 || _throw_argerror(n) + n > 0 || _throw_argerror() if soffs < 1 || doffs < 1 || soffs+n-1 > length(src) || doffs+n-1 > length(dest) throw(BoundsError()) end @@ -276,10 +277,11 @@ function copyto!(dest::Array{T}, doffs::Integer, src::Array{T}, soffs::Integer, end # Outlining this because otherwise a catastrophic inference slowdown -# occurs, see discussion in #27874 -function _throw_argerror(n) +# occurs, see discussion in #27874. +# It is also mitigated by using a constant string. +function _throw_argerror() @_noinline_meta - throw(ArgumentError(string("tried to copy n=", n, " elements, but n should be nonnegative"))) + throw(ArgumentError("Number of elements to copy must be nonnegative.")) end copyto!(dest::Array{T}, src::Array{T}) where {T} = copyto!(dest, 1, src, 1, length(src)) @@ -289,7 +291,7 @@ copyto!(dest::Array{T}, src::Array{T}) where {T} = copyto!(dest, 1, src, 1, leng function fill!(dest::Array{T}, x) where T @_noinline_meta xT = convert(T, x) - for i in 1:length(dest) + for i in eachindex(dest) @inbounds dest[i] = xT end return dest @@ -361,16 +363,7 @@ end getindex(::Type{Any}) = Vector{Any}() function fill!(a::Union{Array{UInt8}, Array{Int8}}, x::Integer) - ccall(:memset, Ptr{Cvoid}, (Ptr{Cvoid}, Cint, Csize_t), a, x, length(a)) - return a -end - -function fill!(a::Array{T}, x) where T<:Union{Integer,AbstractFloat} - @_noinline_meta - xT = convert(T, x) - for i in eachindex(a) - @inbounds a[i] = xT - end + ccall(:memset, Ptr{Cvoid}, (Ptr{Cvoid}, Cint, Csize_t), a, convert(eltype(a), x), length(a)) return a end @@ -399,8 +392,8 @@ dims)` will return an array filled with the result of evaluating `Foo()` once. """ fill(v, dims::DimOrInd...) = fill(v, dims) fill(v, dims::NTuple{N, Union{Integer, OneTo}}) where {N} = fill(v, map(to_dim, dims)) -fill(v, dims::NTuple{N, Integer}) where {N} = fill!(Array{typeof(v),N}(undef, dims), v) -fill(v, dims::Tuple{}) = fill!(Array{typeof(v),0}(undef, dims), v) +fill(v, dims::NTuple{N, Integer}) where {N} = (a=Array{typeof(v),N}(undef, dims); fill!(a, v); a) +fill(v, dims::Tuple{}) = (a=Array{typeof(v),0}(undef, dims); fill!(a, v); a) """ zeros([T=Float64,] dims...) @@ -448,13 +441,21 @@ for (fname, felt) in ((:zeros, :zero), (:ones, :one)) $fname(::Type{T}, dims::DimOrInd...) where {T} = $fname(T, dims) $fname(dims::Tuple{Vararg{DimOrInd}}) = $fname(Float64, dims) $fname(::Type{T}, dims::NTuple{N, Union{Integer, OneTo}}) where {T,N} = $fname(T, map(to_dim, dims)) - $fname(::Type{T}, dims::NTuple{N, Integer}) where {T,N} = fill!(Array{T,N}(undef, map(to_dim, dims)), $felt(T)) - $fname(::Type{T}, dims::Tuple{}) where {T} = fill!(Array{T}(undef), $felt(T)) + function $fname(::Type{T}, dims::NTuple{N, Integer}) where {T,N} + a = Array{T,N}(undef, dims) + fill!(a, $felt(T)) + return a + end + function $fname(::Type{T}, dims::Tuple{}) where {T} + a = Array{T}(undef) + fill!(a, $felt(T)) + return a + end end end function _one(unit::T, x::AbstractMatrix) where T - @assert !has_offset_axes(x) + require_one_based_indexing(x) m,n = size(x) m==n || throw(DimensionMismatch("multiplicative identity defined only for square matrices")) # Matrix{T}(I, m, m) @@ -770,7 +771,7 @@ function setindex! end function setindex!(A::Array, X::AbstractArray, I::AbstractVector{Int}) @_propagate_inbounds_meta @boundscheck setindex_shape_check(X, length(I)) - @assert !has_offset_axes(X) + require_one_based_indexing(X) X′ = unalias(A, X) I′ = unalias(A, I) count = 1 @@ -887,7 +888,7 @@ Use [`push!`](@ref) to add individual items to `collection` which are not alread themselves in another collection. The result of the preceding example is equivalent to `push!([1, 2, 3], 4, 5, 6)`. """ -function append!(a::Array{<:Any,1}, items::AbstractVector) +function append!(a::Vector, items::AbstractVector) itemindices = eachindex(items) n = length(itemindices) _growend!(a, n) @@ -899,7 +900,7 @@ append!(a::Vector, iter) = _append!(a, IteratorSize(iter), iter) push!(a::Vector, iter...) = append!(a, iter) function _append!(a, ::Union{HasLength,HasShape}, iter) - @assert !has_offset_axes(a) + require_one_based_indexing(a) n = length(a) resize!(a, n+length(iter)) @inbounds for (i,item) in zip(n+1:length(a), iter) @@ -931,7 +932,7 @@ julia> prepend!([3],[1,2]) """ function prepend! end -function prepend!(a::Array{<:Any,1}, items::AbstractVector) +function prepend!(a::Vector, items::AbstractVector) itemindices = eachindex(items) n = length(itemindices) _growbeg!(a, n) @@ -947,7 +948,7 @@ prepend!(a::Vector, iter) = _prepend!(a, IteratorSize(iter), iter) pushfirst!(a::Vector, iter...) = prepend!(a, iter) function _prepend!(a, ::Union{HasLength,HasShape}, iter) - @assert !has_offset_axes(a) + require_one_based_indexing(a) n = length(iter) _growbeg!(a, n) i = 0 @@ -1581,10 +1582,10 @@ and [`pairs(A)`](@ref). ```jldoctest julia> A = [false, false, true, false] 4-element Array{Bool,1}: - false - false - true - false + 0 + 0 + 1 + 0 julia> findnext(A, 1) 3 @@ -1593,8 +1594,8 @@ julia> findnext(A, 4) # returns nothing, but not printed in the REPL julia> A = [false false; true false] 2×2 Array{Bool,2}: - false false - true false + 0 0 + 1 0 julia> findnext(A, CartesianIndex(1, 1)) CartesianIndex(2, 1) @@ -1603,10 +1604,11 @@ CartesianIndex(2, 1) function findnext(A, start) l = last(keys(A)) i = start - while i <= l - if A[i] - return i - end + i > l && return nothing + while true + A[i] && return i + i == l && break + # nextind(A, l) can throw/overflow i = nextind(A, i) end return nothing @@ -1626,10 +1628,10 @@ and [`pairs(A)`](@ref). ```jldoctest julia> A = [false, false, true, false] 4-element Array{Bool,1}: - false - false - true - false + 0 + 0 + 1 + 0 julia> findfirst(A) 3 @@ -1638,8 +1640,8 @@ julia> findfirst(falses(3)) # returns nothing, but not printed in the REPL julia> A = [false false; true false] 2×2 Array{Bool,2}: - false false - true false + 0 0 + 1 0 julia> findfirst(A) CartesianIndex(2, 1) @@ -1684,10 +1686,11 @@ CartesianIndex(1, 1) function findnext(testf::Function, A, start) l = last(keys(A)) i = start - while i <= l - if testf(A[i]) - return i - end + i > l && return nothing + while true + testf(A[i]) && return i + i == l && break + # nextind(A, l) can throw/overflow i = nextind(A, i) end return nothing @@ -1739,6 +1742,13 @@ end findfirst(testf::Function, A::Union{AbstractArray, AbstractString}) = findnext(testf, A, first(keys(A))) +function findfirst(p::Union{Fix2{typeof(isequal),T},Fix2{typeof(==),T}}, r::StepRange{T,S}) where {T,S} + first(r) <= p.x <= last(r) || return nothing + d = convert(S, p.x - first(r)) + iszero(d % step(r)) || return nothing + return d ÷ step(r) + 1 +end + """ findprev(A, i) @@ -1752,10 +1762,10 @@ and [`pairs(A)`](@ref). ```jldoctest julia> A = [false, false, true, true] 4-element Array{Bool,1}: - false - false - true - true + 0 + 0 + 1 + 1 julia> findprev(A, 3) 3 @@ -1764,8 +1774,8 @@ julia> findprev(A, 1) # returns nothing, but not printed in the REPL julia> A = [false false; true true] 2×2 Array{Bool,2}: - false false - true true + 0 0 + 1 1 julia> findprev(A, CartesianIndex(2, 1)) CartesianIndex(2, 1) @@ -1773,8 +1783,12 @@ CartesianIndex(2, 1) """ function findprev(A, start) i = start - while i >= first(keys(A)) + f = first(keys(A)) + i < f && return nothing + while true A[i] && return i + i == f && break + # prevind(A, f) can throw/underflow i = prevind(A, i) end return nothing @@ -1793,10 +1807,10 @@ and [`pairs(A)`](@ref). ```jldoctest julia> A = [true, false, true, false] 4-element Array{Bool,1}: - true - false - true - false + 1 + 0 + 1 + 0 julia> findlast(A) 3 @@ -1807,8 +1821,8 @@ julia> findlast(A) # returns nothing, but not printed in the REPL julia> A = [true false; true false] 2×2 Array{Bool,2}: - true false - true false + 1 0 + 1 0 julia> findlast(A) CartesianIndex(2, 1) @@ -1860,8 +1874,12 @@ CartesianIndex(2, 1) """ function findprev(testf::Function, A, start) i = start - while i >= first(keys(A)) + f = first(keys(A)) + i < f && return nothing + while true testf(A[i]) && return i + i == f && break + # prevind(A, f) can throw/underflow i = prevind(A, i) end return nothing @@ -1977,10 +1995,10 @@ and [`pairs(A)`](@ref). ```jldoctest julia> A = [true, false, false, true] 4-element Array{Bool,1}: - true - false - false - true + 1 + 0 + 0 + 1 julia> findall(A) 2-element Array{Int64,1}: @@ -1989,8 +2007,8 @@ julia> findall(A) julia> A = [true false; false true] 2×2 Array{Bool,2}: - true false - false true + 1 0 + 0 1 julia> findall(A) 2-element Array{CartesianIndex{2},1}: diff --git a/base/arraymath.jl b/base/arraymath.jl index d86dff975fe96a..5a8e1287f3232d 100644 --- a/base/arraymath.jl +++ b/base/arraymath.jl @@ -204,8 +204,8 @@ end """ rotl90(A, k) -Rotate matrix `A` left 90 degrees an integer `k` number of times. -If `k` is zero or a multiple of four, this is equivalent to a `copy`. +Left-rotate matrix `A` 90 degrees counterclockwise an integer `k` number of times. +If `k` is a multiple of four (including zero), this is equivalent to a `copy`. # Examples ```jldoctest @@ -244,8 +244,8 @@ end """ rotr90(A, k) -Rotate matrix `A` right 90 degrees an integer `k` number of times. If `k` is zero or a -multiple of four, this is equivalent to a `copy`. +Right-rotate matrix `A` 90 degrees clockwise an integer `k` number of times. +If `k` is a multiple of four (including zero), this is equivalent to a `copy`. # Examples ```jldoctest diff --git a/base/arrayshow.jl b/base/arrayshow.jl index 9b1b7197f7f41b..f96102062fd7ec 100644 --- a/base/arrayshow.jl +++ b/base/arrayshow.jl @@ -410,14 +410,14 @@ _show_nonempty(::IO, ::AbstractVector, ::String) = _show_nonempty(io::IO, X::AbstractArray{T,0} where T, prefix::String) = print_array(io, X) # NOTE: it's not clear how this method could use the :typeinfo attribute -_show_empty(io::IO, X::Array{T}) where {T} = print(io, "Array{", T, "}(", join(size(X),','), ')') +_show_empty(io::IO, X::Array{T}) where {T} = print(io, "Array{", T, "}(undef,", join(size(X),','), ')') _show_empty(io, X) = nothing # by default, we don't know this constructor # typeinfo aware (necessarily) function show(io::IO, X::AbstractArray) ndims(X) == 1 && return show_vector(io, X) prefix = typeinfo_prefix(io, X) - io = IOContext(io, :typeinfo => eltype(X), :compact => get(io, :compact, true)) + io = IOContext(io, :typeinfo => eltype(X)) isempty(X) ? _show_empty(io, X) : _show_nonempty(io, X, prefix) @@ -431,7 +431,7 @@ end function show_vector(io::IO, v, opn='[', cls=']') print(io, typeinfo_prefix(io, v)) # directly or indirectly, the context now knows about eltype(v) - io = IOContext(io, :typeinfo => eltype(v), :compact => get(io, :compact, true)) + io = IOContext(io, :typeinfo => eltype(v)) limited = get(io, :limit, false) if limited && length(v) > 20 inds = axes1(v) diff --git a/base/asyncevent.jl b/base/asyncevent.jl new file mode 100644 index 00000000000000..2d5083bb1eed52 --- /dev/null +++ b/base/asyncevent.jl @@ -0,0 +1,247 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +## async event notifications + +""" + AsyncCondition() + +Create a async condition that wakes up tasks waiting for it +(by calling [`wait`](@ref) on the object) +when notified from C by a call to `uv_async_send`. +Waiting tasks are woken with an error when the object is closed (by [`close`](@ref). +Use [`isopen`](@ref) to check whether it is still active. +""" +mutable struct AsyncCondition + handle::Ptr{Cvoid} + cond::Condition + isopen::Bool + + function AsyncCondition() + this = new(Libc.malloc(_sizeof_uv_async), Condition(), true) + associate_julia_struct(this.handle, this) + finalizer(uvfinalize, this) + err = ccall(:uv_async_init, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}), + eventloop(), this, uv_jl_asynccb::Ptr{Cvoid}) + if err != 0 + #TODO: this codepath is currently not tested + Libc.free(this.handle) + this.handle = C_NULL + throw(_UVError("uv_async_init", err)) + end + return this + end +end + +""" + AsyncCondition(callback::Function) + +Create a async condition that calls the given `callback` function. The `callback` is passed one argument, +the async condition object itself. +""" +function AsyncCondition(cb::Function) + async = AsyncCondition() + waiter = Task(function() + while isopen(async) + success = try + wait(async) + true + catch exc # ignore possible exception on close() + isa(exc, EOFError) || rethrow() + end + success && cb(async) + end + end) + # must start the task right away so that it can wait for the AsyncCondition before + # we re-enter the event loop. this avoids a race condition. see issue #12719 + yield(waiter) + return async +end + +## timer-based notifications + +""" + Timer(delay; interval = 0) + +Create a timer that wakes up tasks waiting for it (by calling [`wait`](@ref) on the timer object). + +Waiting tasks are woken after an initial delay of `delay` seconds, and then repeating with the given +`interval` in seconds. If `interval` is equal to `0`, the timer is only triggered once. When +the timer is closed (by [`close`](@ref) waiting tasks are woken with an error. Use [`isopen`](@ref) +to check whether a timer is still active. +""" +mutable struct Timer + handle::Ptr{Cvoid} + cond::Condition + isopen::Bool + + function Timer(timeout::Real; interval::Real = 0.0) + timeout ≥ 0 || throw(ArgumentError("timer cannot have negative timeout of $timeout seconds")) + interval ≥ 0 || throw(ArgumentError("timer cannot have negative repeat interval of $interval seconds")) + + this = new(Libc.malloc(_sizeof_uv_timer), Condition(), true) + err = ccall(:uv_timer_init, Cint, (Ptr{Cvoid}, Ptr{Cvoid}), eventloop(), this) + if err != 0 + #TODO: this codepath is currently not tested + Libc.free(this.handle) + this.handle = C_NULL + throw(_UVError("uv_timer_init", err)) + end + + associate_julia_struct(this.handle, this) + finalizer(uvfinalize, this) + + ccall(:jl_uv_update_time, Cvoid, (Ptr{Cvoid},), eventloop()) + ccall(:jl_uv_timer_start, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, UInt64, UInt64), + this, uv_jl_timercb::Ptr{Cvoid}, + UInt64(round(timeout * 1000)) + 1, UInt64(round(interval * 1000))) + return this + end +end + +unsafe_convert(::Type{Ptr{Cvoid}}, t::Timer) = t.handle +unsafe_convert(::Type{Ptr{Cvoid}}, async::AsyncCondition) = async.handle + +function wait(t::Union{Timer, AsyncCondition}) + isopen(t) || throw(EOFError()) + stream_wait(t, t.cond) +end + +isopen(t::Union{Timer, AsyncCondition}) = t.isopen + +function close(t::Union{Timer, AsyncCondition}) + if t.handle != C_NULL && isopen(t) + t.isopen = false + isa(t, Timer) && ccall(:jl_uv_timer_stop, Cint, (Ptr{Cvoid},), t) + ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), t) + end + nothing +end + +function uvfinalize(t::Union{Timer, AsyncCondition}) + if t.handle != C_NULL + disassociate_julia_struct(t.handle) # not going to call the usual close hooks + close(t) + t.handle = C_NULL + end + t.isopen = false + nothing +end + +function _uv_hook_close(t::Union{Timer, AsyncCondition}) + uvfinalize(t) + notify_error(t.cond, EOFError()) + nothing +end + +function uv_asynccb(handle::Ptr{Cvoid}) + async = @handle_as handle AsyncCondition + notify(async.cond) + nothing +end + +function uv_timercb(handle::Ptr{Cvoid}) + t = @handle_as handle Timer + if ccall(:uv_timer_get_repeat, UInt64, (Ptr{Cvoid},), t) == 0 + # timer is stopped now + close(t) + end + notify(t.cond) + nothing +end + +""" + sleep(seconds) + +Block the current task for a specified number of seconds. The minimum sleep time is 1 +millisecond or input of `0.001`. +""" +function sleep(sec::Real) + sec ≥ 0 || throw(ArgumentError("cannot sleep for $sec seconds")) + wait(Timer(sec)) + nothing +end + +# timer with repeated callback +""" + Timer(callback::Function, delay; interval = 0) + +Create a timer that wakes up tasks waiting for it (by calling [`wait`](@ref) on the timer object) and +calls the function `callback`. + +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 timer +is only triggered once. The function `callback` is called with a single argument, the timer itself. +When the timer is closed (by [`close`](@ref) waiting tasks are woken with an error. Use [`isopen`](@ref) +to check whether a timer is still active. + +# Examples + +Here the first number is printed after a delay of two seconds, then the following numbers are printed quickly. + +```julia-repl +julia> begin + i = 0 + cb(timer) = (global i += 1; println(i)) + t = Timer(cb, 2, interval = 0.2) + wait(t) + sleep(0.5) + close(t) + end +1 +2 +3 +``` +""" +function Timer(cb::Function, timeout::Real; interval::Real = 0.0) + t = Timer(timeout, interval = interval) + waiter = Task(function() + while isopen(t) + success = try + wait(t) + true + catch exc # ignore possible exception on close() + isa(exc, EOFError) || rethrow() + false + end + success && cb(t) + end + end) + # must start the task right away so that it can wait for the Timer before + # we re-enter the event loop. this avoids a race condition. see issue #12719 + yield(waiter) + return t +end + +""" + timedwait(testcb::Function, secs::Float64; pollint::Float64=0.1) + +Waits until `testcb` returns `true` or for `secs` seconds, whichever is earlier. +`testcb` is polled every `pollint` seconds. +""" +function timedwait(testcb::Function, secs::Float64; pollint::Float64=0.1) + pollint > 0 || throw(ArgumentError("cannot set pollint to $pollint seconds")) + start = time() + done = Channel(1) + timercb(aw) = begin + try + if testcb() + put!(done, :ok) + elseif (time() - start) > secs + put!(done, :timed_out) + end + catch e + put!(done, :error) + finally + isready(done) && close(aw) + end + end + + if !testcb() + t = Timer(timercb, pollint, interval = pollint) + ret = fetch(done) + close(t) + else + ret = :ok + end + ret +end diff --git a/base/baseext.jl b/base/baseext.jl new file mode 100644 index 00000000000000..4f60be88f603df --- /dev/null +++ b/base/baseext.jl @@ -0,0 +1,35 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +# extensions to Core types to add features in Base + +# hook up VecElement constructor to Base.convert +VecElement{T}(arg) where {T} = VecElement{T}(convert(T, arg)) +convert(::Type{T}, arg) where {T<:VecElement} = T(arg) +convert(::Type{T}, arg::T) where {T<:VecElement} = arg + +# ## dims-type-converting Array constructors for convenience +# type and dimensionality specified, accepting dims as series of Integers +Vector{T}(::UndefInitializer, m::Integer) where {T} = Vector{T}(undef, Int(m)) +Matrix{T}(::UndefInitializer, m::Integer, n::Integer) where {T} = Matrix{T}(undef, Int(m), Int(n)) +Array{T,N}(::UndefInitializer, d::Vararg{Integer,N}) where {T,N} = Array{T,N}(undef, convert(Tuple{Vararg{Int}}, d)) +# type but not dimensionality specified, accepting dims as series of Integers +Array{T}(::UndefInitializer, m::Integer) where {T} = Array{T,1}(undef, Int(m)) +Array{T}(::UndefInitializer, m::Integer, n::Integer) where {T} = Array{T,2}(undef, Int(m), Int(n)) +Array{T}(::UndefInitializer, m::Integer, n::Integer, o::Integer) where {T} = Array{T,3}(undef, Int(m), Int(n), Int(o)) +Array{T}(::UndefInitializer, d::Integer...) where {T} = Array{T}(undef, convert(Tuple{Vararg{Int}}, d)) +# dimensionality but not type specified, accepting dims as series of Integers +Vector(::UndefInitializer, m::Integer) = Vector{Any}(undef, Int(m)) +Matrix(::UndefInitializer, m::Integer, n::Integer) = Matrix{Any}(undef, Int(m), Int(n)) +# Dimensions as a single tuple +Array{T}(::UndefInitializer, d::NTuple{N,Integer}) where {T,N} = Array{T,N}(undef, convert(Tuple{Vararg{Int}}, d)) +Array{T,N}(::UndefInitializer, d::NTuple{N,Integer}) where {T,N} = Array{T,N}(undef, convert(Tuple{Vararg{Int}}, d)) +# empty vector constructor +Vector() = Vector{Any}(undef, 0) + +# Array constructors for nothing and missing +# type and dimensionality specified +Array{T,N}(::Nothing, d...) where {T,N} = fill!(Array{T,N}(undef, d...), nothing) +Array{T,N}(::Missing, d...) where {T,N} = fill!(Array{T,N}(undef, d...), missing) +# type but not dimensionality specified +Array{T}(::Nothing, d...) where {T} = fill!(Array{T}(undef, d...), nothing) +Array{T}(::Missing, d...) where {T} = fill!(Array{T}(undef, d...), missing) diff --git a/base/bitarray.jl b/base/bitarray.jl index a93909a4bbbbd6..a7549e832cbb7f 100644 --- a/base/bitarray.jl +++ b/base/bitarray.jl @@ -77,7 +77,7 @@ length(B::BitArray) = B.len size(B::BitVector) = (B.len,) size(B::BitArray) = B.dims -@inline function size(B::BitVector, d) +@inline function size(B::BitVector, d::Integer) d < 1 && throw_boundserror(size(B), d) ifelse(d == 1, B.len, 1) end @@ -369,8 +369,8 @@ Create a `BitArray` with all values set to `false`. ```jldoctest julia> falses(2,3) 2×3 BitArray{2}: - false false false - false false false + 0 0 0 + 0 0 0 ``` """ falses(dims::DimOrInd...) = falses(dims) @@ -387,8 +387,8 @@ Create a `BitArray` with all values set to `true`. ```jldoctest julia> trues(2,3) 2×3 BitArray{2}: - true true true - true true true + 1 1 1 + 1 1 1 ``` """ trues(dims::DimOrInd...) = trues(dims) @@ -524,22 +524,22 @@ The shape is inferred from the `itr` object. ```jldoctest julia> BitArray([1 0; 0 1]) 2×2 BitArray{2}: - true false - false true + 1 0 + 0 1 julia> BitArray(x+y == 3 for x = 1:2, y = 1:3) 2×3 BitArray{2}: - false true false - true false false + 0 1 0 + 1 0 0 julia> BitArray(x+y == 3 for x = 1:2 for y = 1:3) 6-element BitArray{1}: - false - true - false - true - false - false + 0 + 1 + 0 + 1 + 0 + 0 ``` """ BitArray(itr) = gen_bitarray(IteratorSize(itr), itr) @@ -741,7 +741,7 @@ function append!(B::BitVector, items::BitVector) return B end -append!(B::BitVector, items::AbstractVector{Bool}) = append!(B, BitArray(items)) +append!(B::BitVector, items) = append!(B, BitArray(items)) append!(A::Vector{Bool}, items::BitVector) = append!(A, Array(items)) function prepend!(B::BitVector, items::BitVector) @@ -761,7 +761,7 @@ function prepend!(B::BitVector, items::BitVector) return B end -prepend!(B::BitVector, items::AbstractVector{Bool}) = prepend!(B, BitArray(items)) +prepend!(B::BitVector, items) = prepend!(B, BitArray(items)) prepend!(A::Vector{Bool}, items::BitVector) = prepend!(A, Array(items)) function sizehint!(B::BitVector, sz::Integer) @@ -1260,27 +1260,27 @@ values. If `n < 0`, elements are shifted backwards. Equivalent to ```jldoctest julia> B = BitVector([true, false, true, false, false]) 5-element BitArray{1}: - true - false - true - false - false + 1 + 0 + 1 + 0 + 0 julia> B >> 1 5-element BitArray{1}: - false - true - false - true - false + 0 + 1 + 0 + 1 + 0 julia> B >> -1 5-element BitArray{1}: - false - true - false - false - false + 0 + 1 + 0 + 0 + 0 ``` """ (>>)(B::BitVector, i::Union{Int, UInt}) = B >>> i @@ -1298,27 +1298,27 @@ values. If `n < 0`, elements are shifted forwards. Equivalent to ```jldoctest julia> B = BitVector([true, false, true, false, false]) 5-element BitArray{1}: - true - false - true - false - false + 1 + 0 + 1 + 0 + 0 julia> B << 1 5-element BitArray{1}: - false - true - false - false - false + 0 + 1 + 0 + 0 + 0 julia> B << -1 5-element BitArray{1}: - false - true - false - true - false + 0 + 1 + 0 + 1 + 0 ``` """ (<<)(B::BitVector, i::Int) = (i >=0 ? B << unsigned(i) : B >> unsigned(-i)) diff --git a/base/bool.jl b/base/bool.jl index 950540a0395465..988bf874b1ebef 100644 --- a/base/bool.jl +++ b/base/bool.jl @@ -27,7 +27,7 @@ missing julia> .![true false true] 1×3 BitArray{2}: - false true false + 0 1 0 ``` """ function !(x::Bool) @@ -67,24 +67,16 @@ false julia> [true; true; false] .⊻ [true; false; false] 3-element BitArray{1}: - false - true - false + 0 + 1 + 0 ``` """ xor(x::Bool, y::Bool) = (x != y) ->>(x::Bool, c::Unsigned) = Int(x) >> c -<<(x::Bool, c::Unsigned) = Int(x) << c ->>>(x::Bool, c::Unsigned) = Int(x) >>> c - ->>(x::Bool, c::Int) = Int(x) >> c -<<(x::Bool, c::Int) = Int(x) << c ->>>(x::Bool, c::Int) = Int(x) >>> c - ->>(x::Bool, c::Integer) = Int(x) >> c -<<(x::Bool, c::Integer) = Int(x) << c ->>>(x::Bool, c::Integer) = Int(x) >>> c +>>(x::Bool, c::UInt) = Int(x) >> c +<<(x::Bool, c::UInt) = Int(x) << c +>>>(x::Bool, c::UInt) = Int(x) >>> c signbit(x::Bool) = false sign(x::Bool) = x diff --git a/base/boot.jl b/base/boot.jl index 63c7f4b5b4821b..c502762e272b7e 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -65,6 +65,9 @@ #mutable struct MethodInstance #end +#mutable struct CodeInstance +#end + #mutable struct CodeInfo #end @@ -90,8 +93,7 @@ #end #struct LineInfoNode -# mod::Module -# method::Symbol +# method::Any # file::Symbol # line::Int # inlined_at::Int @@ -375,13 +377,13 @@ eval(Core, :(PiNode(val, typ) = $(Expr(:new, :PiNode, :val, :typ)))) eval(Core, :(PhiCNode(values::Array{Any, 1}) = $(Expr(:new, :PhiCNode, :values)))) eval(Core, :(UpsilonNode(val) = $(Expr(:new, :UpsilonNode, :val)))) eval(Core, :(UpsilonNode() = $(Expr(:new, :UpsilonNode)))) -eval(Core, :(LineInfoNode(mod::Module, method::Symbol, file::Symbol, line::Int, inlined_at::Int) = - $(Expr(:new, :LineInfoNode, :mod, :method, :file, :line, :inlined_at)))) +eval(Core, :(LineInfoNode(@nospecialize(method), file::Symbol, line::Int, inlined_at::Int) = + $(Expr(:new, :LineInfoNode, :method, :file, :line, :inlined_at)))) Module(name::Symbol=:anonymous, std_imports::Bool=true) = ccall(:jl_f_new_module, Ref{Module}, (Any, Bool), name, std_imports) -function Task(@nospecialize(f), reserved_stack::Int=0) - return ccall(:jl_new_task, Ref{Task}, (Any, Int), f, reserved_stack) +function _Task(@nospecialize(f), reserved_stack::Int, completion_future) + return ccall(:jl_new_task, Ref{Task}, (Any, Any, Int), f, completion_future, reserved_stack) end # simple convert for use by constructors of types in Core @@ -443,11 +445,11 @@ Symbol(s::Symbol) = s # module providing the IR object model module IR -export CodeInfo, MethodInstance, GotoNode, +export CodeInfo, MethodInstance, CodeInstance, GotoNode, NewvarNode, SSAValue, Slot, SlotNumber, TypedSlot, PiNode, PhiNode, PhiCNode, UpsilonNode, LineInfoNode -import Core: CodeInfo, MethodInstance, GotoNode, +import Core: CodeInfo, MethodInstance, CodeInstance, GotoNode, NewvarNode, SSAValue, Slot, SlotNumber, TypedSlot, PiNode, PhiNode, PhiCNode, UpsilonNode, LineInfoNode @@ -548,33 +550,8 @@ NamedTuple{names}(args::Tuple) where {names} = NamedTuple{names,typeof(args)}(ar using .Intrinsics: sle_int, add_int -macro generated() - return Expr(:generated) -end - -function NamedTuple{names,T}(args::T) where {names, T <: Tuple} - if @generated - N = nfields(names) - flds = Array{Any,1}(undef, N) - i = 1 - while sle_int(i, N) - arrayset(false, flds, :(getfield(args, $i)), i) - i = add_int(i, 1) - end - Expr(:new, :(NamedTuple{names,T}), flds...) - else - N = nfields(names) - NT = NamedTuple{names,T} - flds = Array{Any,1}(undef, N) - i = 1 - while sle_int(i, N) - arrayset(false, flds, getfield(args, i), i) - i = add_int(i, 1) - end - ccall(:jl_new_structv, Any, (Any, Ptr{Cvoid}, UInt32), NT, - ccall(:jl_array_ptr, Ptr{Cvoid}, (Any,), flds), toUInt32(N))::NT - end -end +eval(Core, :(NamedTuple{names,T}(args::T) where {names, T <: Tuple} = + $(Expr(:splatnew, :(NamedTuple{names,T}), :args)))) # constructors for built-in types @@ -747,6 +724,7 @@ Int64(x::Ptr) = Int64(UInt32(x)) UInt64(x::Ptr) = UInt64(UInt32(x)) end Ptr{T}(x::Union{Int,UInt,Ptr}) where {T} = bitcast(Ptr{T}, x) +Ptr{T}() where {T} = Ptr{T}(0) Signed(x::UInt8) = Int8(x) Unsigned(x::Int8) = UInt8(x) diff --git a/base/broadcast.jl b/base/broadcast.jl index 194bbd489a5151..4d5ca6b92fe197 100644 --- a/base/broadcast.jl +++ b/base/broadcast.jl @@ -222,7 +222,8 @@ _eachindex(t::Tuple) = CartesianIndices(t) Base.ndims(::Broadcasted{<:Any,<:NTuple{N,Any}}) where {N} = N Base.ndims(::Type{<:Broadcasted{<:Any,<:NTuple{N,Any}}}) where {N} = N -Base.length(bc::Broadcasted) = prod(map(length, axes(bc))) +Base.size(bc::Broadcasted) = map(length, axes(bc)) +Base.length(bc::Broadcasted) = prod(size(bc)) function Base.iterate(bc::Broadcasted) iter = eachindex(bc) @@ -384,13 +385,45 @@ end ## logic for deciding the BroadcastStyle -# combine_styles operates on values (arbitrarily many) +""" + combine_styles(cs...) -> BroadcastStyle + +Decides which `BroadcastStyle` to use for any number of value arguments. +Uses [`BroadcastStyle`](@ref) to get the style for each argument, and uses +[`result_style`](@ref) to combine styles. + +# Examples + +```jldoctest +julia> Broadcast.combine_styles([1], [1 2; 3 4]) +Base.Broadcast.DefaultArrayStyle{2}() +``` +""" +function combine_styles end + combine_styles() = DefaultArrayStyle{0}() combine_styles(c) = result_style(BroadcastStyle(typeof(c))) combine_styles(c1, c2) = result_style(combine_styles(c1), combine_styles(c2)) @inline combine_styles(c1, c2, cs...) = result_style(combine_styles(c1), combine_styles(c2, cs...)) -# result_style works on types (singletons and pairs), and leverages `BroadcastStyle` +""" + result_style(s1::BroadcastStyle[, s2::BroadcastStyle]) -> BroadcastStyle + +Takes one or two `BroadcastStyle`s and combines them using [`BroadcastStyle`](@ref) to +determine a common `BroadcastStyle`. + +# Examples + +```jldoctest +julia> Broadcast.result_style(Broadcast.DefaultArrayStyle{0}(), Broadcast.DefaultArrayStyle{3}()) +Base.Broadcast.DefaultArrayStyle{3}() + +julia> Broadcast.result_style(Broadcast.Unknown(), Broadcast.DefaultArrayStyle{1}()) +Base.Broadcast.DefaultArrayStyle{1}() +``` +""" +function result_style end + result_style(s::BroadcastStyle) = s result_style(s1::S, s2::S) where S<:BroadcastStyle = S() # Test both orders so users typically only have to declare one order @@ -418,6 +451,20 @@ One of these should be undefined (and thus return Broadcast.Unknown).""") end # Indices utilities + +""" + combine_axes(As...) -> Tuple + +Determine the result axes for broadcasting across all values in `As`. + +```jldoctest +julia> Broadcast.combine_axes([1], [1 2; 3 4; 5 6]) +(Base.OneTo(3), Base.OneTo(2)) + +julia> Broadcast.combine_axes(1, 1, 1) +() +``` +""" @inline combine_axes(A, B...) = broadcast_shape(axes(A), combine_axes(B...)) combine_axes(A) = axes(A) @@ -605,11 +652,9 @@ julia> Broadcast.broadcastable("hello") # Strings break convention of matching i Base.RefValue{String}("hello") ``` """ -broadcastable(x::Union{Symbol,AbstractString,Function,UndefInitializer,Nothing,RoundingMode,Missing,Val}) = Ref(x) -broadcastable(x::Ptr) = Ref(x) +broadcastable(x::Union{Symbol,AbstractString,Function,UndefInitializer,Nothing,RoundingMode,Missing,Val,Ptr,Regex}) = Ref(x) broadcastable(::Type{T}) where {T} = Ref{Type{T}}(T) broadcastable(x::Union{AbstractArray,Number,Ref,Tuple,Broadcasted}) = x -broadcastable(r::Regex) = Ref(r) # Default to collecting iterables — which will error for non-iterables broadcastable(x) = collect(x) broadcastable(::Union{AbstractDict, NamedTuple}) = throw(ArgumentError("broadcasting over dictionaries and `NamedTuple`s is reserved")) @@ -919,6 +964,15 @@ end @noinline throwdm(axdest, axsrc) = throw(DimensionMismatch("destination axes $axdest are not compatible with source axes $axsrc")) +function restart_copyto_nonleaf!(newdest, dest, bc, val, I, iter, state, count) + # Function barrier that makes the copying to newdest type stable + for II in Iterators.take(iter, count) + newdest[II] = dest[II] + end + newdest[I] = val + return copyto_nonleaf!(newdest, bc, iter, state, count+1) +end + function copyto_nonleaf!(dest, bc::Broadcasted, iter, state, count) T = eltype(dest) while true @@ -932,11 +986,7 @@ function copyto_nonleaf!(dest, bc::Broadcasted, iter, state, count) # This element type doesn't fit in dest. Allocate a new dest with wider eltype, # copy over old values, and continue newdest = Base.similar(dest, promote_typejoin(T, typeof(val))) - for II in Iterators.take(iter, count) - newdest[II] = dest[II] - end - newdest[I] = val - return copyto_nonleaf!(newdest, bc, iter, state, count+1) + return restart_copyto_nonleaf!(newdest, dest, bc, val, I, iter, state, count) end count += 1 end diff --git a/base/c.jl b/base/c.jl index f64bfe548a99ae..8674a555a0c92a 100644 --- a/base/c.jl +++ b/base/c.jl @@ -39,7 +39,7 @@ unsafe_convert(::Type{Ptr{Cvoid}}, cf::CFunction) = cf.ptr @cfunction(callable, ReturnType, (ArgumentTypes...,)) -> Ptr{Cvoid} @cfunction(\$callable, ReturnType, (ArgumentTypes...,)) -> CFunction -Generate a C-callable function pointer from the Julia function `closure` +Generate a C-callable function pointer from the Julia function `callable` for the given type signature. To pass the return value to a `ccall`, use the argument type `Ptr{Cvoid}` in the signature. @@ -183,7 +183,7 @@ Calling [`Ref(array[, index])`](@ref Ref) is generally preferable to this functi """ function pointer end -pointer(p::Cstring) = convert(Ptr{UInt8}, p) +pointer(p::Cstring) = convert(Ptr{Cchar}, p) pointer(p::Cwstring) = convert(Ptr{Cwchar_t}, p) # comparisons against pointers (mainly to support `cstr==C_NULL`) @@ -203,7 +203,7 @@ function cconvert(::Type{Cwstring}, s::AbstractString) return v end -eltype(::Type{Cstring}) = UInt8 +eltype(::Type{Cstring}) = Cchar eltype(::Type{Cwstring}) = Cwchar_t containsnul(p::Ptr, len) = @@ -292,7 +292,7 @@ transcode(T, src::String) = transcode(T, codeunits(src)) transcode(::Type{String}, src) = String(transcode(UInt8, src)) function transcode(::Type{UInt16}, src::AbstractVector{UInt8}) - @assert !has_offset_axes(src) + require_one_based_indexing(src) dst = UInt16[] i, n = 1, length(src) n > 0 || return dst @@ -343,7 +343,7 @@ function transcode(::Type{UInt16}, src::AbstractVector{UInt8}) end function transcode(::Type{UInt8}, src::AbstractVector{UInt16}) - @assert !has_offset_axes(src) + require_one_based_indexing(src) n = length(src) n == 0 && return UInt8[] diff --git a/base/channels.jl b/base/channels.jl index e8cf5a977dfbe4..27f1719341375f 100644 --- a/base/channels.jl +++ b/base/channels.jl @@ -23,39 +23,30 @@ Other constructors: * `Channel(sz)`: equivalent to `Channel{Any}(sz)` """ mutable struct Channel{T} <: AbstractChannel{T} - cond_take::Condition # waiting for data to become available - cond_put::Condition # waiting for a writeable slot + cond_take::Threads.Condition # waiting for data to become available + cond_wait::Threads.Condition # waiting for data to become maybe available + cond_put::Threads.Condition # waiting for a writeable slot state::Symbol - excp::Union{Exception, Nothing} # exception to be thrown when state != :open + excp::Union{Exception, Nothing} # exception to be thrown when state != :open data::Vector{T} sz_max::Int # maximum size of channel - # Used when sz_max == 0, i.e., an unbuffered channel. - waiters::Int - takers::Vector{Task} - putters::Vector{Task} - - function Channel{T}(sz::Float64) where T - if sz == Inf - Channel{T}(typemax(Int)) - else - Channel{T}(convert(Int, sz)) - end - end function Channel{T}(sz::Integer) where T if sz < 0 throw(ArgumentError("Channel size must be either 0, a positive integer or Inf")) end - ch = new(Condition(), Condition(), :open, nothing, Vector{T}(), sz, 0) - if sz == 0 - ch.takers = Vector{Task}() - ch.putters = Vector{Task}() - end - return ch + lock = ReentrantLock() + cond_put, cond_take = Threads.Condition(lock), Threads.Condition(lock) + cond_wait = (sz == 0 ? Threads.Condition(lock) : cond_take) # wait is distinct from take iff unbuffered + return new(cond_take, cond_wait, cond_put, :open, nothing, Vector{T}(), sz) end end +function Channel{T}(sz::Float64) where T + sz = (sz == Inf ? typemax(Int) : convert(Int, sz)) + return Channel{T}(sz) +end Channel(sz) = Channel{Any}(sz) # special constructors @@ -122,22 +113,30 @@ isbuffered(c::Channel) = c.sz_max==0 ? false : true function check_channel_state(c::Channel) if !isopen(c) - c.excp !== nothing && throw(c.excp) + excp = c.excp + excp !== nothing && throw(excp) throw(closed_exception()) end end """ - close(c::Channel) + close(c::Channel[, excp::Exception]) -Close a channel. An exception is thrown by: +Close a channel. An exception (optionally given by `excp`), is thrown by: * [`put!`](@ref) on a closed channel. * [`take!`](@ref) and [`fetch`](@ref) on an empty, closed channel. """ -function close(c::Channel) - c.state = :closed - c.excp = closed_exception() - notify_error(c) +function close(c::Channel, excp::Exception=closed_exception()) + lock(c) + try + c.state = :closed + c.excp = excp + notify_error(c.cond_take, excp) + notify_error(c.cond_wait, excp) + notify_error(c.cond_put, excp) + finally + unlock(c) + end nothing end isopen(c::Channel) = (c.state == :open) @@ -195,7 +194,7 @@ Stacktrace: function bind(c::Channel, task::Task) ref = WeakRef(c) register_taskdone_hook(task, tsk->close_chnl_on_taskdone(tsk, ref)) - c + return c end """ @@ -225,17 +224,34 @@ function channeled_tasks(n::Int, funcs...; ctypes=fill(Any,n), csizes=fill(0,n)) end function close_chnl_on_taskdone(t::Task, ref::WeakRef) - if ref.value !== nothing - c = ref.value - !isopen(c) && return - if istaskfailed(t) - c.state = :closed - c.excp = task_result(t) - notify_error(c) + c = ref.value + if c isa Channel + isopen(c) || return + cleanup = () -> try + isopen(c) || return + if istaskfailed(t) + excp = task_result(t) + if excp isa Exception + close(c, excp) + return + end + end + close(c) + return + finally + unlock(c) + end + if trylock(c) + # can't use `lock`, since attempts to task-switch to wait for it + # will just silently fail and leave us with broken state + cleanup() else - close(c) + # so schedule this to happen once we are finished destroying our task + # (on a new Task) + @async (lock(c); cleanup()) end end + nothing end struct InvalidStateException <: Exception @@ -257,33 +273,39 @@ task. function put!(c::Channel{T}, v) where T check_channel_state(c) v = convert(T, v) - isbuffered(c) ? put_buffered(c,v) : put_unbuffered(c,v) + return isbuffered(c) ? put_buffered(c, v) : put_unbuffered(c, v) end function put_buffered(c::Channel, v) - while length(c.data) == c.sz_max - wait(c.cond_put) + lock(c) + try + while length(c.data) == c.sz_max + check_channel_state(c) + wait(c.cond_put) + end + push!(c.data, v) + # notify all, since some of the waiters may be on a "fetch" call. + notify(c.cond_take, nothing, true, false) + finally + unlock(c) end - push!(c.data, v) - - # notify all, since some of the waiters may be on a "fetch" call. - notify(c.cond_take, nothing, true, false) - v + return v end function put_unbuffered(c::Channel, v) - if length(c.takers) == 0 - push!(c.putters, current_task()) - c.waiters > 0 && notify(c.cond_take, nothing, false, false) - - try - wait() - catch - filter!(x->x!=current_task(), c.putters) - rethrow() + lock(c) + taker = try + while isempty(c.cond_take.waitq) + check_channel_state(c) + notify(c.cond_wait) + wait(c.cond_put) end + # unfair scheduled version of: notify(c.cond_take, v, false, false); yield() + popfirst!(c.cond_take.waitq) + finally + unlock(c) end - taker = popfirst!(c.takers) + # unfair version of: schedule(taker, v); yield() yield(taker, v) # immediately give taker a chance to run, but don't block the current task return v end @@ -298,8 +320,16 @@ remove the item. `fetch` is unsupported on an unbuffered (0-size) channel. """ fetch(c::Channel) = isbuffered(c) ? fetch_buffered(c) : fetch_unbuffered(c) function fetch_buffered(c::Channel) - wait(c) - c.data[1] + lock(c) + try + while isempty(c.data) + check_channel_state(c) + wait(c.cond_take) + end + return c.data[1] + finally + unlock(c) + end end fetch_unbuffered(c::Channel) = throw(ErrorException("`fetch` is not supported on an unbuffered Channel.")) @@ -314,32 +344,31 @@ task. """ take!(c::Channel) = isbuffered(c) ? take_buffered(c) : take_unbuffered(c) function take_buffered(c::Channel) - wait(c) - v = popfirst!(c.data) - notify(c.cond_put, nothing, false, false) # notify only one, since only one slot has become available for a put!. - v + lock(c) + try + while isempty(c.data) + check_channel_state(c) + wait(c.cond_take) + end + v = popfirst!(c.data) + notify(c.cond_put, nothing, false, false) # notify only one, since only one slot has become available for a put!. + return v + finally + unlock(c) + end end popfirst!(c::Channel) = take!(c) # 0-size channel function take_unbuffered(c::Channel{T}) where T - check_channel_state(c) - push!(c.takers, current_task()) + lock(c) try - if length(c.putters) > 0 - let refputter = Ref(popfirst!(c.putters)) - return Base.try_yieldto(refputter) do putter - # if we fail to start putter, put it back in the queue - putter === current_task || pushfirst!(c.putters, putter) - end::T - end - else - return wait()::T - end - catch - filter!(x->x!=current_task(), c.takers) - rethrow() + check_channel_state(c) + notify(c.cond_put, nothing, false, false) + return wait(c.cond_take)::T + finally + unlock(c) end end @@ -353,39 +382,26 @@ For unbuffered channels returns `true` if there are tasks waiting on a [`put!`](@ref). """ isready(c::Channel) = n_avail(c) > 0 -n_avail(c::Channel) = isbuffered(c) ? length(c.data) : length(c.putters) +n_avail(c::Channel) = isbuffered(c) ? length(c.data) : length(c.cond_put.waitq) -wait(c::Channel) = isbuffered(c) ? wait_impl(c) : wait_unbuffered(c) -function wait_impl(c::Channel) - while !isready(c) - check_channel_state(c) - wait(c.cond_take) - end - nothing -end +lock(c::Channel) = lock(c.cond_take) +unlock(c::Channel) = unlock(c.cond_take) +trylock(c::Channel) = trylock(c.cond_take) -function wait_unbuffered(c::Channel) - c.waiters += 1 +function wait(c::Channel) + isready(c) && return + lock(c) try - wait_impl(c) + while !isready(c) + check_channel_state(c) + wait(c.cond_wait) + end finally - c.waiters -= 1 + unlock(c) end nothing end -function notify_error(c::Channel, err) - notify_error(c.cond_take, err) - notify_error(c.cond_put, err) - - # release tasks on a `wait()/yieldto()` call (on unbuffered channels) - if !isbuffered(c) - waiters = filter!(t->(t.state == :runnable), vcat(c.takers, c.putters)) - foreach(t->schedule(t, err; error=true), waiters) - end -end -notify_error(c::Channel) = notify_error(c, c.excp) - eltype(::Type{Channel{T}}) where {T} = T show(io::IO, c::Channel) = print(io, "$(typeof(c))(sz_max:$(c.sz_max),sz_curr:$(n_avail(c)))") @@ -394,7 +410,7 @@ function iterate(c::Channel, state=nothing) try return (take!(c), nothing) catch e - if isa(e, InvalidStateException) && e.state==:closed + if isa(e, InvalidStateException) && e.state == :closed return nothing else rethrow() diff --git a/base/char.jl b/base/char.jl index 3f9d60380212b9..6d7fdb045efc36 100644 --- a/base/char.jl +++ b/base/char.jl @@ -187,7 +187,7 @@ typemax(::Type{Char}) = reinterpret(Char, typemax(UInt32)) typemin(::Type{Char}) = reinterpret(Char, typemin(UInt32)) size(c::AbstractChar) = () -size(c::AbstractChar,d) = convert(Int, d) < 1 ? throw(BoundsError()) : 1 +size(c::AbstractChar, d::Integer) = d < 1 ? throw(BoundsError()) : 1 ndims(c::AbstractChar) = 0 ndims(::Type{<:AbstractChar}) = 0 length(c::AbstractChar) = 1 diff --git a/base/checked.jl b/base/checked.jl index 9b11e13caf7962..840015861923fc 100644 --- a/base/checked.jl +++ b/base/checked.jl @@ -87,7 +87,7 @@ function checked_neg(x::T) where T<:Integer checked_sub(T(0), x) end throw_overflowerr_negation(x) = (@_noinline_meta; - throw(OverflowError("checked arithmetic: cannot compute -x for x = $x::$(typeof(x))"))) + throw(OverflowError(Base.invokelatest(string, "checked arithmetic: cannot compute -x for x = ", x, "::", typeof(x))))) if BrokenSignedInt != Union{} function checked_neg(x::BrokenSignedInt) r = -x @@ -151,7 +151,7 @@ end throw_overflowerr_binaryop(op, x, y) = (@_noinline_meta; - throw(OverflowError("$x $op $y overflowed for type $(typeof(x))"))) + throw(OverflowError(Base.invokelatest(string, x, " ", op, " ", y, " overflowed for type ", typeof(x))))) """ Base.checked_add(x, y) diff --git a/base/client.jl b/base/client.jl index 95e23a47ace384..73030847188bac 100644 --- a/base/client.jl +++ b/base/client.jl @@ -89,29 +89,42 @@ function ip_matches_func(ip, func::Symbol) return false end +function scrub_repl_backtrace(bt) + if bt !== nothing + # remove REPL-related frames from interactive printing + eval_ind = findlast(addr->ip_matches_func(addr, :eval), bt) + if eval_ind !== nothing + return bt[1:eval_ind-1] + end + end + return bt +end + function display_error(io::IO, er, bt) printstyled(io, "ERROR: "; bold=true, color=Base.error_color()) - # remove REPL-related frames from interactive printing - eval_ind = findlast(addr->ip_matches_func(addr, :eval), bt) - if eval_ind !== nothing - bt = bt[1:eval_ind-1] - end - showerror(IOContext(io, :limit => true), er, bt) + showerror(IOContext(io, :limit => true), er, scrub_repl_backtrace(bt)) println(io) end +function display_error(io::IO, stack::Vector) + printstyled(io, "ERROR: "; bold=true, color=Base.error_color()) + show_exception_stack(IOContext(io, :limit => true), Any[ (x[1], scrub_repl_backtrace(x[2])) for x in stack ]) +end +display_error(stack::Vector) = display_error(stderr, stack) display_error(er, bt) = display_error(stderr, er, bt) display_error(er) = display_error(er, []) -function eval_user_input(@nospecialize(ast), show_value::Bool) - errcount, lasterr, bt = 0, (), nothing +function eval_user_input(errio, @nospecialize(ast), show_value::Bool) + errcount = 0 + lasterr = nothing while true try if have_color print(color_normal) end - if errcount > 0 - invokelatest(display_error, lasterr, bt) - errcount, lasterr = 0, () + if lasterr !== nothing + invokelatest(display_error, errio, lasterr) + errcount = 0 + lasterr = nothing else ast = Meta.lower(Main, ast) value = Core.eval(Main, ast) @@ -123,38 +136,53 @@ function eval_user_input(@nospecialize(ast), show_value::Bool) try invokelatest(display, value) catch - println(stderr, "Evaluation succeeded, but an error occurred while showing value of type ", typeof(value), ":") + @error "Evaluation succeeded, but an error occurred while displaying the value" typeof(value) rethrow() end println() end end break - catch err + catch if errcount > 0 - println(stderr, "SYSTEM: show(lasterr) caused an error") + @error "SYSTEM: display_error(errio, lasterr) caused an error" end - errcount, lasterr = errcount+1, err + errcount += 1 + lasterr = catch_stack() if errcount > 2 - println(stderr, "WARNING: it is likely that something important is broken, and Julia will not be able to continue normally") + @error "It is likely that something important is broken, and Julia will not be able to continue normally" errcount break end - bt = catch_backtrace() end end isa(stdin, TTY) && println() nothing end +function _parse_input_line_core(s::String, filename::String) + ex = ccall(:jl_parse_all, Any, (Ptr{UInt8}, Csize_t, Ptr{UInt8}, Csize_t), + s, sizeof(s), filename, sizeof(filename)) + if ex isa Expr && ex.head === :toplevel + if isempty(ex.args) + return nothing + end + last = ex.args[end] + if last isa Expr && (last.head === :error || last.head === :incomplete) + # if a parse error happens in the middle of a multi-line input + # return only the error, so that none of the input is evaluated. + return last + end + end + return ex +end + function parse_input_line(s::String; filename::String="none", depwarn=true) # For now, assume all parser warnings are depwarns ex = if depwarn - ccall(:jl_parse_input_line, Any, (Ptr{UInt8}, Csize_t, Ptr{UInt8}, Csize_t), - s, sizeof(s), filename, sizeof(filename)) + _parse_input_line_core(s, filename) else with_logger(NullLogger()) do - ccall(:jl_parse_input_line, Any, (Ptr{UInt8}, Csize_t, Ptr{UInt8}, Csize_t), - s, sizeof(s), filename, sizeof(filename)) + _parse_input_line_core(s, filename) end end return ex @@ -265,8 +293,8 @@ function exec_options(opts) end try include(Main, PROGRAM_FILE) - catch err - invokelatest(display_error, err, catch_backtrace()) + catch + invokelatest(display_error, catch_stack()) if !is_interactive exit(1) end @@ -375,11 +403,11 @@ function run_main_repl(interactive::Bool, quiet::Bool, banner::Bool, history_fil # if we get back a list of statements, eval them sequentially # as if we had parsed them sequentially for stmt in ex.args - eval_user_input(stmt, true) + eval_user_input(stderr, stmt, true) end body = ex.args else - eval_user_input(ex, true) + eval_user_input(stderr, ex, true) end else while isopen(input) || !eof(input) @@ -388,7 +416,7 @@ function run_main_repl(interactive::Bool, quiet::Bool, banner::Bool, history_fil flush(stdout) end try - eval_user_input(parse_input_line(input), true) + eval_user_input(stderr, parse_input_line(input), true) catch err isa(err, InterruptException) ? print("\n\n") : rethrow() end @@ -434,8 +462,8 @@ function _start() @eval Main import Base.MainInclude: eval, include try exec_options(JLOptions()) - catch err - invokelatest(display_error, err, catch_backtrace()) + catch + invokelatest(display_error, catch_stack()) exit(1) end if is_interactive && have_color diff --git a/base/combinatorics.jl b/base/combinatorics.jl index ce5ccdaa6da6e0..225fc9dace8d39 100644 --- a/base/combinatorics.jl +++ b/base/combinatorics.jl @@ -16,7 +16,7 @@ end function factorial_lookup(n::Integer, table, lim) n < 0 && throw(DomainError(n, "`n` must not be negative.")) - n > lim && throw(OverflowError(string(n, " is too large to look up in the table"))) + n > lim && throw(OverflowError(string(n, " is too large to look up in the table; consider using `factorial(big(", n, "))` instead"))) n == 0 && return one(n) @inbounds f = table[n] return oftype(n, f) @@ -63,8 +63,39 @@ isperm(p::Tuple{}) = true isperm(p::Tuple{Int}) = p[1] == 1 isperm(p::Tuple{Int,Int}) = ((p[1] == 1) & (p[2] == 2)) | ((p[1] == 2) & (p[2] == 1)) +# swap columns i and j of a, in-place +function swapcols!(a::AbstractMatrix, i, j) + i == j && return + cols = axes(a,2) + @boundscheck i in cols || throw(BoundsError(a, (:,i))) + @boundscheck j in cols || throw(BoundsError(a, (:,j))) + for k in axes(a,1) + @inbounds a[k,i],a[k,j] = a[k,j],a[k,i] + end +end +# like permute!! applied to each row of a, in-place in a (overwriting p). +function permutecols!!(a::AbstractMatrix, p::AbstractVector{<:Integer}) + require_one_based_indexing(a, p) + count = 0 + start = 0 + while count < length(p) + ptr = start = findnext(!iszero, p, start+1)::Int + next = p[start] + count += 1 + while next != start + swapcols!(a, ptr, next) + p[ptr] = 0 + ptr = next + next = p[next] + count += 1 + end + p[ptr] = 0 + end + a +end + function permute!!(a, p::AbstractVector{<:Integer}) - @assert !has_offset_axes(a, p) + require_one_based_indexing(a, p) count = 0 start = 0 while count < length(a) @@ -115,7 +146,7 @@ julia> A permute!(a, p::AbstractVector) = permute!!(a, copymutable(p)) function invpermute!!(a, p::AbstractVector{<:Integer}) - @assert !has_offset_axes(a, p) + require_one_based_indexing(a, p) count = 0 start = 0 while count < length(a) @@ -196,7 +227,7 @@ julia> B[invperm(v)] ``` """ function invperm(a::AbstractVector) - @assert !has_offset_axes(a) + require_one_based_indexing(a) b = zero(a) # similar vector of zeros n = length(a) @inbounds for (i, j) in enumerate(a) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index b01ad3a271fca5..f7e8876301146e 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -16,7 +16,8 @@ const _REF_NAME = Ref.body.name call_result_unused(frame::InferenceState, pc::LineNum=frame.currpc) = isexpr(frame.src.code[frame.currpc], :call) && isempty(frame.ssavalue_uses[pc]) -function abstract_call_gf_by_type(@nospecialize(f), argtypes::Vector{Any}, @nospecialize(atype), sv::InferenceState) +function abstract_call_gf_by_type(@nospecialize(f), argtypes::Vector{Any}, @nospecialize(atype), sv::InferenceState, + max_methods = sv.params.MAX_METHODS) atype_params = unwrap_unionall(atype).parameters ft = unwrap_unionall(atype_params[1]) # TODO: ccall jl_first_argument_datatype here isa(ft, DataType) || return Any # the function being called is unknown. can't properly handle this backedge right now @@ -41,12 +42,12 @@ function abstract_call_gf_by_type(@nospecialize(f), argtypes::Vector{Any}, @nosp splitsigs = switchtupleunion(atype) applicable = Any[] for sig_n in splitsigs - xapplicable = _methods_by_ftype(sig_n, sv.params.MAX_METHODS, sv.params.world, min_valid, max_valid) + xapplicable = _methods_by_ftype(sig_n, max_methods, sv.params.world, min_valid, max_valid) xapplicable === false && return Any append!(applicable, xapplicable) end else - applicable = _methods_by_ftype(atype, sv.params.MAX_METHODS, sv.params.world, min_valid, max_valid) + applicable = _methods_by_ftype(atype, max_methods, sv.params.world, min_valid, max_valid) if applicable === false # this means too many methods matched # (assume this will always be true, so we don't compute / update valid age in this case) @@ -61,10 +62,16 @@ function abstract_call_gf_by_type(@nospecialize(f), argtypes::Vector{Any}, @nosp edges = Any[] nonbot = 0 # the index of the only non-Bottom inference result if > 0 seen = 0 # number of signatures actually inferred + istoplevel = sv.linfo.def isa Module for i in 1:napplicable match = applicable[i]::SimpleVector method = match[3]::Method sig = match[1] + if istoplevel && !isdispatchtuple(sig) + # only infer concrete call sites in top-level expressions + rettype = Any + break + end sigtuple = unwrap_unionall(sig)::DataType splitunions = false this_rt = Bottom @@ -82,7 +89,8 @@ function abstract_call_gf_by_type(@nospecialize(f), argtypes::Vector{Any}, @nosp this_rt === Any && break end else - this_rt, edgecycle, edge = abstract_call_method(method, sig, match[2]::SimpleVector, sv) + this_rt, edgecycle1, edge = abstract_call_method(method, sig, match[2]::SimpleVector, sv) + edgecycle |= edgecycle1::Bool if edge !== nothing push!(edges, edge) end @@ -142,55 +150,71 @@ function abstract_call_gf_by_type(@nospecialize(f), argtypes::Vector{Any}, @nosp return rettype end + +function const_prop_profitable(@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 + const_prop_profitable(b) && return true + end + elseif !isa(arg, Const) || (isa(arg.val, Symbol) || isa(arg.val, Type) || (!isa(arg.val, String) && isimmutable(arg.val))) + # don't consider mutable values or Strings useful constants + return true + end + return false +end + function abstract_call_method_with_const_args(@nospecialize(rettype), @nospecialize(f), argtypes::Vector{Any}, match::SimpleVector, sv::InferenceState) method = match[3]::Method nargs::Int = method.nargs method.isva && (nargs -= 1) length(argtypes) >= nargs || return Any haveconst = false + allconst = true + # see if any or all of the arguments are constant and propagating constants may be worthwhile for a in argtypes a = widenconditional(a) - if has_nontrivial_const_info(a) - # have new information from argtypes that wasn't available from the signature - if !isa(a, Const) || (isa(a.val, Symbol) || isa(a.val, Type) || (!isa(a.val, String) && isimmutable(a.val))) - # don't consider mutable values or Strings useful constants - haveconst = true - break - end + if allconst && !isa(a, Const) && !isconstType(a) && !isa(a, PartialStruct) + allconst = false + end + if !haveconst && has_nontrivial_const_info(a) && const_prop_profitable(a) + haveconst = true + end + if haveconst && !allconst + break end end haveconst || improvable_via_constant_propagation(rettype) || return Any sig = match[1] sparams = match[2]::SimpleVector - code = code_for_method(method, sig, sparams, sv.params.world) - code === nothing && return Any - code = code::MethodInstance - # decide if it's likely to be worthwhile - declared_inline = isdefined(method, :source) && ccall(:jl_ast_flag_inlineable, Bool, (Any,), method.source) - cache_inlineable = declared_inline - if isdefined(code, :inferred) && !cache_inlineable - cache_inf = code.inferred - if !(cache_inf === nothing) - cache_src_inferred = ccall(:jl_ast_flag_inferred, Bool, (Any,), cache_inf) - cache_src_inlineable = ccall(:jl_ast_flag_inlineable, Bool, (Any,), cache_inf) - cache_inlineable = cache_src_inferred && cache_src_inlineable - end + force_inference = allconst || sv.params.aggressive_constant_propagation + if istopfunction(f, :getproperty) || istopfunction(f, :setproperty!) + force_inference = true end - if !cache_inlineable && !sv.params.aggressive_constant_propagation - tm = _topmod(sv) - if !istopfunction(f, :getproperty) && !istopfunction(f, :setproperty!) - # in this case, see if all of the arguments are constants - for a in argtypes - a = widenconditional(a) - if !isa(a, Const) && !isconstType(a) - return Any - end + mi = specialize_method(method, sig, sparams, !force_inference) + mi === nothing && return Any + mi = mi::MethodInstance + # decide if it's likely to be worthwhile + if !force_inference + code = inf_for_methodinstance(mi, sv.params.world) + declared_inline = isdefined(method, :source) && ccall(:jl_ast_flag_inlineable, Bool, (Any,), method.source) + cache_inlineable = declared_inline + if isdefined(code, :inferred) && !cache_inlineable + cache_inf = code.inferred + if !(cache_inf === nothing) + cache_src_inferred = ccall(:jl_ast_flag_inferred, Bool, (Any,), cache_inf) + cache_src_inlineable = ccall(:jl_ast_flag_inlineable, Bool, (Any,), cache_inf) + cache_inlineable = cache_src_inferred && cache_src_inlineable end end + if !cache_inlineable + return Any + end end - inf_result = cache_lookup(code, argtypes, sv.params.cache) + inf_result = cache_lookup(mi, argtypes, sv.params.cache) if inf_result === nothing - inf_result = InferenceResult(code, argtypes) + inf_result = InferenceResult(mi, argtypes) frame = InferenceState(inf_result, #=cache=#false, sv.params) frame.limited = true frame.parent = sv @@ -198,7 +222,7 @@ function abstract_call_method_with_const_args(@nospecialize(rettype), @nospecial typeinf(frame) || return Any end result = inf_result.result - isa(result, InferenceState) && return Any # TODO: is this recursive constant inference? + isa(result, InferenceState) && return Any # TODO: unexpected, is this recursive constant inference? add_backedge!(inf_result.linfo, sv) return result end @@ -219,7 +243,7 @@ function abstract_call_method(method::Method, @nospecialize(sig), sparams::Simpl # necessary in order to retrieve this field from the generated `CodeInfo`, if it exists. # The other `CodeInfo`s we inspect will already have this field inflated, so we just # access it directly instead (to avoid regeneration). - method2 = method_for_inference_heuristics(method, sig, sparams, sv.params.world) # Union{Method, Nothing} + method2 = method_for_inference_heuristics(method, sig, sparams) # Union{Method, Nothing} sv_method2 = sv.src.method_for_inference_limit_heuristics # limit only if user token match sv_method2 isa Method || (sv_method2 = nothing) # Union{Method, Nothing} while !(infstate === nothing) @@ -377,7 +401,7 @@ end # Union of Tuples of the same length is converted to Tuple of Unions. # returns an array of types function precise_container_type(@nospecialize(typ), vtypes::VarTable, sv::InferenceState) - if isa(typ, PartialTuple) + if isa(typ, PartialStruct) && typ.typ.name === Tuple.name return typ.fields end @@ -390,6 +414,12 @@ function precise_container_type(@nospecialize(typ), vtypes::VarTable, sv::Infere tti0 = widenconst(typ) tti = unwrap_unionall(tti0) + if isa(tti, DataType) && tti.name === NamedTuple_typename + tti0 = tti.parameters[2] + while isa(tti0, TypeVar) + tti0 = tti0.ub + end + end if isa(tti, Union) utis = uniontypes(tti) if _any(t -> !isa(t, DataType) || !(t <: Tuple) || !isknownlength(t), utis) @@ -439,7 +469,7 @@ function abstract_iteration(@nospecialize(itertype), vtypes::VarTable, sv::Infer return Any[Vararg{Any}] end iteratef = getfield(Main.Base, :iterate) - stateordonet = abstract_call(iteratef, (), Any[Const(iteratef), itertype], vtypes, sv) + stateordonet = abstract_call(iteratef, nothing, Any[Const(iteratef), itertype], vtypes, sv) # Return Bottom if this is not an iterator. # WARNING: Changes to the iteration protocol must be reflected here, # this is not just an optimization. @@ -458,7 +488,7 @@ function abstract_iteration(@nospecialize(itertype), vtypes::VarTable, sv::Infer valtype = stateordonet.parameters[1] statetype = stateordonet.parameters[2] push!(ret, valtype) - stateordonet = abstract_call(iteratef, (), Any[Const(iteratef), itertype, statetype], vtypes, sv) + stateordonet = abstract_call(iteratef, nothing, Any[Const(iteratef), itertype, statetype], vtypes, sv) stateordonet = widenconst(stateordonet) end if stateordonet === Nothing @@ -475,7 +505,7 @@ function abstract_iteration(@nospecialize(itertype), vtypes::VarTable, sv::Infer end valtype = tmerge(valtype, nounion.parameters[1]) statetype = tmerge(statetype, nounion.parameters[2]) - stateordonet = abstract_call(iteratef, (), Any[Const(iteratef), itertype, statetype], vtypes, sv) + stateordonet = abstract_call(iteratef, nothing, Any[Const(iteratef), itertype, statetype], vtypes, sv) stateordonet = widenconst(stateordonet) end push!(ret, Vararg{valtype}) @@ -483,9 +513,11 @@ function abstract_iteration(@nospecialize(itertype), vtypes::VarTable, sv::Infer end # do apply(af, fargs...), where af is a function value -function abstract_apply(@nospecialize(aft), aargtypes::Vector{Any}, vtypes::VarTable, sv::InferenceState) - if !isa(aft, Const) && (!isType(aft) || has_free_typevars(aft)) - if !isconcretetype(aft) || (aft <: Builtin) +function abstract_apply(@nospecialize(aft), aargtypes::Vector{Any}, vtypes::VarTable, sv::InferenceState, + max_methods = sv.params.MAX_METHODS) + aftw = widenconst(aft) + if !isa(aft, Const) && (!isType(aftw) || has_free_typevars(aftw)) + if !isconcretetype(aftw) || (aftw <: Builtin) # non-constant function of unknown type: bail now, # since it seems unlikely that abstract_call will be able to do any better after splitting # this also ensures we don't call abstract_call_gf_by_type below on an IntrinsicFunction or Builtin @@ -516,12 +548,12 @@ function abstract_apply(@nospecialize(aft), aargtypes::Vector{Any}, vtypes::VarT end for ct in ctypes if isa(aft, Const) - rt = abstract_call(aft.val, (), ct, vtypes, sv) + rt = abstract_call(aft.val, nothing, ct, vtypes, sv, max_methods) elseif isconstType(aft) - rt = abstract_call(aft.parameters[1], (), ct, vtypes, sv) + rt = abstract_call(aft.parameters[1], nothing, ct, vtypes, sv, max_methods) else astype = argtypes_to_type(ct) - rt = abstract_call_gf_by_type(nothing, ct, astype, sv) + rt = abstract_call_gf_by_type(nothing, ct, astype, sv, max_methods) end res = tmerge(res, rt) if res === Any @@ -562,9 +594,9 @@ function pure_eval_call(@nospecialize(f), argtypes::Vector{Any}, @nospecialize(a end end -function abstract_call(@nospecialize(f), fargs::Union{Tuple{},Vector{Any}}, argtypes::Vector{Any}, vtypes::VarTable, sv::InferenceState) +function abstract_call(@nospecialize(f), fargs::Union{Nothing,Vector{Any}}, argtypes::Vector{Any}, vtypes::VarTable, sv::InferenceState, max_methods = sv.params.MAX_METHODS) if f === _apply - return abstract_apply(argtypes[2], argtypes[3:end], vtypes, sv) + return abstract_apply(argtypes[2], argtypes[3:end], vtypes, sv, max_methods) end la = length(argtypes) @@ -720,7 +752,7 @@ function abstract_call(@nospecialize(f), fargs::Union{Tuple{},Vector{Any}}, argt return Any elseif is_return_type(f) rt_rt = return_type_tfunc(argtypes, vtypes, sv) - if rt_rt !== NOT_FOUND + if rt_rt !== nothing return rt_rt end elseif length(argtypes) == 2 && istopfunction(f, :!) @@ -745,7 +777,7 @@ function abstract_call(@nospecialize(f), fargs::Union{Tuple{},Vector{Any}}, argt if length(fargs) == 3 fargs = Any[<:, fargs[3], fargs[2]] else - fargs = () + fargs = nothing end argtypes = Any[typeof(<:), argtypes[3], argtypes[2]] rty = abstract_call(<:, fargs, argtypes, vtypes, sv) @@ -773,11 +805,11 @@ function abstract_call(@nospecialize(f), fargs::Union{Tuple{},Vector{Any}}, argt return Type # don't try to infer these function edges directly -- it won't actually come up with anything useful end - return abstract_call_gf_by_type(f, argtypes, atype, sv) + return abstract_call_gf_by_type(f, argtypes, atype, sv, max_methods) end # wrapper around `abstract_call` for first computing if `f` is available -function abstract_eval_call(fargs::Union{Tuple{},Vector{Any}}, argtypes::Vector{Any}, vtypes::VarTable, sv::InferenceState) +function abstract_eval_call(fargs::Union{Nothing,Vector{Any}}, argtypes::Vector{Any}, vtypes::VarTable, sv::InferenceState) #print("call ", e.args[1], argtypes, "\n\n") for x in argtypes x === Bottom && return Bottom @@ -851,25 +883,10 @@ function abstract_eval_cfunction(e::Expr, vtypes::VarTable, sv::InferenceState) # this may be the wrong world for the call, # but some of the result is likely to be valid anyways # and that may help generate better codegen - abstract_eval_call((), at, vtypes, sv) + abstract_eval_call(nothing, at, vtypes, sv) nothing end -# convert an inferred static parameter value to the inferred type of a static_parameter expression -function sparam_type(@nospecialize(val)) - if isa(val, TypeVar) - if Any <: val.ub - # static param bound to typevar - # if the tvar is not known to refer to anything more specific than Any, - # the static param might actually be an integer, symbol, etc. - return Any - else - return UnionAll(val, Type{val}) - end - end - return AbstractEvalConstant(val) -end - function abstract_eval(@nospecialize(e), vtypes::VarTable, sv::InferenceState) if isa(e, QuoteNode) return AbstractEvalConstant((e::QuoteNode).value) @@ -892,28 +909,42 @@ function abstract_eval(@nospecialize(e), vtypes::VarTable, sv::InferenceState) t = instanceof_tfunc(abstract_eval(e.args[1], vtypes, sv))[1] if isconcretetype(t) && !t.mutable args = Vector{Any}(undef, length(e.args)-1) - isconst = true + ats = Vector{Any}(undef, length(e.args)-1) + anyconst = false + allconst = true for i = 2:length(e.args) at = abstract_eval(e.args[i], vtypes, sv) + if !anyconst + anyconst = has_nontrivial_const_info(at) + end + ats[i-1] = at if at === Bottom t = Bottom - isconst = false + allconst = anyconst = false break elseif at isa Const if !(at.val isa fieldtype(t, i - 1)) t = Bottom - isconst = false + allconst = anyconst = false break end args[i-1] = at.val else - isconst = false + allconst = false end end - if isconst - t = Const(ccall(:jl_new_structv, Any, (Any, Ptr{Cvoid}, UInt32), t, args, length(args))) + # For now, don't allow partially initialized Const/PartialStruct + if t !== Bottom && fieldcount(t) == length(ats) + if allconst + t = Const(ccall(:jl_new_structv, Any, (Any, Ptr{Cvoid}, UInt32), t, args, length(args))) + elseif anyconst + t = PartialStruct(t, ats) + end end end + elseif e.head === :splatnew + t = instanceof_tfunc(abstract_eval(e.args[1], vtypes, sv))[1] + # TODO: improve elseif e.head === :& abstract_eval(e.args[1], vtypes, sv) t = Any @@ -932,8 +963,8 @@ function abstract_eval(@nospecialize(e), vtypes::VarTable, sv::InferenceState) elseif e.head === :static_parameter n = e.args[1] t = Any - if 1 <= n <= length(sv.sp) - t = sparam_type(sv.sp[n]) + if 1 <= n <= length(sv.sptypes) + t = sv.sptypes[n] end elseif e.head === :method t = (length(e.args) == 1) ? Any : Nothing @@ -967,9 +998,9 @@ function abstract_eval(@nospecialize(e), vtypes::VarTable, sv::InferenceState) end elseif isa(sym, Expr) && sym.head === :static_parameter n = sym.args[1] - if 1 <= n <= length(sv.sp) - val = sv.sp[n] - if !isa(val, TypeVar) + if 1 <= n <= length(sv.sptypes) + spty = sv.sptypes[n] + if isa(spty, Const) t = Const(true) end end @@ -1027,7 +1058,7 @@ function typeinf_local(frame::InferenceState) delete!(W, pc) frame.currpc = pc frame.cur_hand = frame.handler_at[pc] - frame.stmt_edges[pc] === () || empty!(frame.stmt_edges[pc]) + frame.stmt_edges[pc] === nothing || empty!(frame.stmt_edges[pc]) stmt = frame.src.code[pc] changes = s[pc]::VarTable t = nothing @@ -1075,7 +1106,7 @@ function typeinf_local(frame::InferenceState) elseif hd === :return pc´ = n + 1 rt = widenconditional(abstract_eval(stmt.args[1], s[pc], frame)) - if !isa(rt, Const) && !isa(rt, Type) && !isa(rt, PartialTuple) + if !isa(rt, Const) && !isa(rt, Type) && !isa(rt, PartialStruct) # only propagate information we know we can store # and is valid inter-procedurally rt = widenconst(rt) @@ -1097,9 +1128,8 @@ function typeinf_local(frame::InferenceState) end elseif hd === :enter l = stmt.args[1]::Int - frame.cur_hand = (l, frame.cur_hand) + frame.cur_hand = Pair{Any,Any}(l, frame.cur_hand) # propagate type info to exception handler - l = frame.cur_hand[1] old = s[l] new = s[pc]::Array{Any,1} newstate_catch = stupdate!(old, new) @@ -1114,7 +1144,7 @@ function typeinf_local(frame::InferenceState) frame.handler_at[l] = frame.cur_hand elseif hd === :leave for i = 1:((stmt.args[1])::Int) - frame.cur_hand = frame.cur_hand[2] + frame.cur_hand = (frame.cur_hand::Pair{Any,Any}).second end else if hd === :(=) @@ -1130,7 +1160,7 @@ function typeinf_local(frame::InferenceState) if isa(fname, Slot) changes = StateUpdate(fname, VarState(Any, false), changes) end - elseif hd === :inbounds || hd === :meta || hd === :simdloop + elseif hd === :inbounds || hd === :meta || hd === :loopinfo else t = abstract_eval(stmt, changes, frame) t === Bottom && break @@ -1140,11 +1170,11 @@ function typeinf_local(frame::InferenceState) frame.src.ssavaluetypes[pc] = t end end - if frame.cur_hand !== () && isa(changes, StateUpdate) + if frame.cur_hand !== nothing && isa(changes, StateUpdate) # propagate new type info to exception handler # the handling for Expr(:enter) propagates all changes from before the try/catch # so this only needs to propagate any changes - l = frame.cur_hand[1] + l = frame.cur_hand.first::Int if stupdate1!(s[l]::VarTable, changes::StateUpdate) !== false if l < frame.pc´´ frame.pc´´ = l diff --git a/base/compiler/bootstrap.jl b/base/compiler/bootstrap.jl index 6d889d67ee0679..b1fb73f6b659ea 100644 --- a/base/compiler/bootstrap.jl +++ b/base/compiler/bootstrap.jl @@ -6,7 +6,7 @@ # since we won't be able to specialize & infer them at runtime let fs = Any[typeinf_ext, typeinf, typeinf_edge, pure_eval_call, run_passes], - world = ccall(:jl_get_world_counter, UInt, ()) + world = get_world_counter() for x in T_FFUNC_VAL push!(fs, x[3]) end diff --git a/base/compiler/compiler.jl b/base/compiler/compiler.jl index b12bb8c2e892df..c2d97e40cded49 100644 --- a/base/compiler/compiler.jl +++ b/base/compiler/compiler.jl @@ -5,7 +5,7 @@ getfield(getfield(Main, :Core), :eval)(getfield(Main, :Core), :(baremodule Compi using Core.Intrinsics, Core.IR import Core: print, println, show, write, unsafe_write, stdout, stderr, - _apply, svec, apply_type, Builtin, IntrinsicFunction, MethodInstance + _apply, svec, apply_type, Builtin, IntrinsicFunction, MethodInstance, CodeInstance const getproperty = getfield const setproperty! = setfield! diff --git a/base/compiler/inferenceresult.jl b/base/compiler/inferenceresult.jl index ac60ed16c21bfa..a466a00df89dfe 100644 --- a/base/compiler/inferenceresult.jl +++ b/base/compiler/inferenceresult.jl @@ -9,20 +9,15 @@ mutable struct InferenceResult result # ::Type, or InferenceState if WIP src #::Union{CodeInfo, OptimizationState, Nothing} # if inferred copy is available function InferenceResult(linfo::MethodInstance, given_argtypes = nothing) - if isdefined(linfo, :inferred_const) - result = Const(linfo.inferred_const) - else - result = linfo.rettype - end argtypes, overridden_by_const = matching_cache_argtypes(linfo, given_argtypes) - return new(linfo, argtypes, overridden_by_const, result, nothing) + return new(linfo, argtypes, overridden_by_const, Any, nothing) end end function is_argtype_match(@nospecialize(given_argtype), @nospecialize(cache_argtype), overridden_by_const::Bool) - if isa(given_argtype, Const) || isa(given_argtype, PartialTuple) + if isa(given_argtype, Const) || isa(given_argtype, PartialStruct) return is_lattice_equal(given_argtype, cache_argtype) end return !overridden_by_const @@ -66,7 +61,7 @@ function matching_cache_argtypes(linfo::MethodInstance, ::Nothing) nargs::Int = toplevel ? 0 : linfo.def.nargs cache_argtypes = Vector{Any}(undef, nargs) # First, if we're dealing with a varargs method, then we set the last element of `args` - # to the appropriate `Tuple` type or `PartialTuple` instance. + # to the appropriate `Tuple` type or `PartialStruct` instance. if !toplevel && linfo.def.isva if linfo.specTypes == Tuple if nargs > 1 diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index ff188f08c7b3f0..2d6d2373e23615 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -5,8 +5,8 @@ const LineNum = Int mutable struct InferenceState params::Params # describes how to compute the result result::InferenceResult # remember where to put the result - linfo::MethodInstance # used here for the tuple (specTypes, env, Method) and world-age validity - sp::SimpleVector # static parameters + linfo::MethodInstance + sptypes::Vector{Any} # types of static parameter slottypes::Vector{Any} mod::Module currpc::LineNum @@ -25,7 +25,7 @@ mutable struct InferenceState pc´´::LineNum nstmts::Int # current exception handler info - cur_hand #::Tuple{LineNum, Tuple{LineNum, ...}} + cur_hand #::Union{Nothing, Pair{LineNum, prev_handler}} handler_at::Vector{Any} n_handlers::Int # ssavalue sparsity and restart info @@ -48,17 +48,17 @@ mutable struct InferenceState code = src.code::Array{Any,1} toplevel = !isa(linfo.def, Method) - sp = spvals_from_meth_instance(linfo::MethodInstance) + sp = sptypes_from_meth_instance(linfo::MethodInstance) nssavalues = src.ssavaluetypes::Int src.ssavaluetypes = Any[ NOT_FOUND for i = 1:nssavalues ] n = length(code) - s_edges = Any[ () for i = 1:n ] - s_types = Any[ () for i = 1:n ] + s_edges = Any[ nothing for i = 1:n ] + s_types = Any[ nothing for i = 1:n ] # initial types - nslots = length(src.slotnames) + nslots = length(src.slotflags) argtypes = result.argtypes nargs = length(argtypes) s_argtypes = VarTable(undef, nslots) @@ -73,8 +73,8 @@ mutable struct InferenceState ssavalue_uses = find_ssavalue_uses(code, nssavalues) # exception handlers - cur_hand = () - handler_at = Any[ () for i=1:n ] + cur_hand = nothing + handler_at = Any[ nothing for i=1:n ] n_handlers = 0 W = BitSet() @@ -87,13 +87,8 @@ mutable struct InferenceState inmodule = linfo.def::Module end - if cached && !toplevel - min_valid = min_world(linfo.def) - max_valid = max_world(linfo.def) - else - min_valid = typemax(UInt) - max_valid = typemin(UInt) - end + min_valid = UInt(1) + max_valid = get_world_counter() frame = new( params, result, linfo, sp, slottypes, inmodule, 0, @@ -120,9 +115,9 @@ function InferenceState(result::InferenceResult, cached::Bool, params::Params) return InferenceState(result, src, cached, params) end -function spvals_from_meth_instance(linfo::MethodInstance) +function sptypes_from_meth_instance(linfo::MethodInstance) toplevel = !isa(linfo.def, Method) - if !toplevel && isempty(linfo.sparam_vals) && !isempty(linfo.def.sparam_syms) + if !toplevel && isempty(linfo.sparam_vals) && isa(linfo.def.sig, UnionAll) # linfo is unspecialized sp = Any[] sig = linfo.def.sig @@ -130,17 +125,35 @@ function spvals_from_meth_instance(linfo::MethodInstance) push!(sp, sig.var) sig = sig.body end - sp = svec(sp...) else - sp = linfo.sparam_vals - if _any(t->isa(t,TypeVar), sp) - sp = collect(Any, sp) - end + sp = collect(Any, linfo.sparam_vals) end - if !isa(sp, SimpleVector) - for i = 1:length(sp) - v = sp[i] - if v isa TypeVar + 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 + for j = 1:i-1 + temp = temp.body + end + Pi = temp.var + while temp isa UnionAll + temp = temp.body + end + sigtypes = temp.parameters + for j = 1:length(sigtypes) + tj = sigtypes[j] + if isType(tj) && tj.parameters[1] === Pi + fromArg = j + break + end + end + if fromArg > 0 + ty = fieldtype(linfo.specTypes, fromArg) + else ub = v.ub while ub isa TypeVar ub = ub.ub @@ -155,10 +168,17 @@ function spvals_from_meth_instance(linfo::MethodInstance) if has_free_typevars(lb) lb = Bottom end - sp[i] = TypeVar(v.name, lb, ub) + if Any <: ub && lb <: Bottom + ty = Any + else + tv = TypeVar(v.name, lb, ub) + ty = UnionAll(tv, Type{tv}) + end end + else + ty = Const(v) end - sp = svec(sp...) + sp[i] = ty end return sp end @@ -169,15 +189,12 @@ _topmod(sv::InferenceState) = _topmod(sv.mod) function update_valid_age!(min_valid::UInt, max_valid::UInt, sv::InferenceState) sv.min_valid = max(sv.min_valid, min_valid) sv.max_valid = min(sv.max_valid, max_valid) - @assert(!isa(sv.linfo.def, Method) || - !sv.cached || - sv.min_valid <= sv.params.world <= sv.max_valid, + @assert(sv.min_valid <= sv.params.world <= sv.max_valid, "invalid age range update") nothing end update_valid_age!(edge::InferenceState, sv::InferenceState) = update_valid_age!(edge.min_valid, edge.max_valid, sv) -update_valid_age!(li::MethodInstance, sv::InferenceState) = update_valid_age!(min_world(li), max_world(li), sv) function record_ssa_assign(ssa_id::Int, @nospecialize(new), frame::InferenceState) old = frame.src.ssavaluetypes[ssa_id] @@ -186,7 +203,7 @@ function record_ssa_assign(ssa_id::Int, @nospecialize(new), frame::InferenceStat W = frame.ip s = frame.stmt_types for r in frame.ssavalue_uses[ssa_id] - if s[r] !== () # s[r] === () => unreached statement + if s[r] !== nothing # s[r] === nothing => unreached statement if r < frame.pc´´ frame.pc´´ = r end @@ -201,24 +218,24 @@ function add_cycle_backedge!(frame::InferenceState, caller::InferenceState, curr update_valid_age!(frame, caller) backedge = (caller, currpc) contains_is(frame.cycle_backedges, backedge) || push!(frame.cycle_backedges, backedge) + add_backedge!(frame.linfo, caller) return frame end # temporarily accumulate our edges to later add as backedges in the callee function add_backedge!(li::MethodInstance, caller::InferenceState) isa(caller.linfo.def, Method) || return # don't add backedges to toplevel exprs - if caller.stmt_edges[caller.currpc] === () + if caller.stmt_edges[caller.currpc] === nothing caller.stmt_edges[caller.currpc] = [] end push!(caller.stmt_edges[caller.currpc], li) - update_valid_age!(li, caller) nothing end # used to temporarily accumulate our no method errors to later add as backedges in the callee method table function add_mt_backedge!(mt::Core.MethodTable, @nospecialize(typ), caller::InferenceState) isa(caller.linfo.def, Method) || return # don't add backedges to toplevel exprs - if caller.stmt_edges[caller.currpc] === () + if caller.stmt_edges[caller.currpc] === nothing caller.stmt_edges[caller.currpc] = [] end push!(caller.stmt_edges[caller.currpc], mt) diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index a42d02b8b70b4c..a2081562744961 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -13,12 +13,12 @@ mutable struct OptimizationState min_valid::UInt max_valid::UInt params::Params - sp::SimpleVector # static parameters + sptypes::Vector{Any} # static parameters slottypes::Vector{Any} const_api::Bool function OptimizationState(frame::InferenceState) s_edges = frame.stmt_edges[1] - if s_edges === () + if s_edges === nothing s_edges = [] frame.stmt_edges[1] = s_edges end @@ -27,7 +27,7 @@ mutable struct OptimizationState s_edges::Vector{Any}, src, frame.mod, frame.nargs, frame.min_valid, frame.max_valid, - frame.params, frame.sp, frame.slottypes, false) + frame.params, frame.sptypes, frame.slottypes, false) end function OptimizationState(linfo::MethodInstance, src::CodeInfo, params::Params) @@ -37,8 +37,11 @@ mutable struct OptimizationState if nssavalues isa Int src.ssavaluetypes = Any[ Any for i = 1:nssavalues ] end - nslots = length(src.slotnames) - slottypes = Any[ Any for i = 1:nslots ] + nslots = length(src.slotflags) + slottypes = src.slottypes + if slottypes === nothing + slottypes = Any[ Any for i = 1:nslots ] + end s_edges = [] # cache some useful state computations toplevel = !isa(linfo.def, Method) @@ -53,8 +56,8 @@ mutable struct OptimizationState return new(linfo, s_edges::Vector{Any}, src, inmodule, nargs, - min_world(linfo), max_world(linfo), - params, spvals_from_meth_instance(linfo), slottypes, false) + UInt(1), get_world_counter(), + params, sptypes_from_meth_instance(linfo), slottypes, false) end end @@ -73,7 +76,7 @@ end # This is implied by `SLOT_USEDUNDEF`. # If this is not set, all the uses are (statically) dominated by the defs. # In particular, if a slot has `AssignedOnce && !StaticUndef`, it is an SSA. -const SLOT_STATICUNDEF = 1 +const SLOT_STATICUNDEF = 1 # slot might be used before it is defined (structurally) const SLOT_ASSIGNEDONCE = 16 # slot is assigned to only once const SLOT_USEDUNDEF = 32 # slot has uses that might raise UndefVarError # const SLOT_CALLED = 64 @@ -86,8 +89,8 @@ const _PURE_BUILTINS = Any[tuple, svec, ===, typeof, nfields] # known to be effect-free if the are nothrow const _PURE_OR_ERROR_BUILTINS = [ fieldtype, apply_type, isa, UnionAll, - getfield, arrayref, isdefined, Core.sizeof, - Core.kwfunc, ifelse, Core._typevar + getfield, arrayref, const_arrayref, isdefined, Core.sizeof, + Core.kwfunc, ifelse, Core._typevar, (<:) ] const TOP_TUPLE = GlobalRef(Core, :tuple) @@ -101,19 +104,21 @@ _topmod(sv::OptimizationState) = _topmod(sv.mod) function update_valid_age!(min_valid::UInt, max_valid::UInt, sv::OptimizationState) sv.min_valid = max(sv.min_valid, min_valid) sv.max_valid = min(sv.max_valid, max_valid) - @assert(!isa(sv.linfo.def, Method) || - (sv.min_valid == typemax(UInt) && sv.max_valid == typemin(UInt)) || - sv.min_valid <= sv.params.world <= sv.max_valid, + @assert(sv.min_valid <= sv.params.world <= sv.max_valid, "invalid age range update") nothing end -update_valid_age!(li::MethodInstance, sv::OptimizationState) = update_valid_age!(min_world(li), max_world(li), sv) - function add_backedge!(li::MethodInstance, caller::OptimizationState) + #TODO: deprecate this? isa(caller.linfo.def, Method) || return # don't add backedges to toplevel exprs push!(caller.calledges, li) - update_valid_age!(li, caller) + nothing +end + +function add_backedge!(li::CodeInstance, caller::OptimizationState) + update_valid_age!(min_world(li), max_world(li), caller) + add_backedge!(li.def, caller) nothing end @@ -135,7 +140,7 @@ function isinlineable(m::Method, me::OptimizationState, bonus::Int=0) end end if !inlineable - inlineable = inline_worthy(me.src.code, me.src, me.sp, me.slottypes, me.params, cost_threshold + bonus) + inlineable = inline_worthy(me.src.code, me.src, me.sptypes, me.slottypes, me.params, cost_threshold + bonus) end return inlineable end @@ -148,11 +153,11 @@ function stmt_affects_purity(@nospecialize(stmt), ir) return false end if isa(stmt, GotoIfNot) - t = argextype(stmt.cond, ir, ir.spvals) + t = argextype(stmt.cond, ir, ir.sptypes) return !(t ⊑ Bool) end if isa(stmt, Expr) - return stmt.head != :simdloop && stmt.head != :enter + return stmt.head != :loopinfo && stmt.head != :enter end return true end @@ -175,7 +180,7 @@ function optimize(opt::OptimizationState, @nospecialize(result)) proven_pure = true for i in 1:length(ir.stmts) stmt = ir.stmts[i] - if stmt_affects_purity(stmt, ir) && !stmt_effect_free(stmt, ir.types[i], ir, ir.spvals) + if stmt_affects_purity(stmt, ir) && !stmt_effect_free(stmt, ir.types[i], ir, ir.sptypes) proven_pure = false break end @@ -260,27 +265,30 @@ function is_pure_intrinsic_infer(f::IntrinsicFunction) f === Intrinsics.cglobal) # cglobal lookup answer changes at runtime end +# whether `f` is effect free if nothrow +intrinsic_effect_free_if_nothrow(f) = f === Intrinsics.pointerref || is_pure_intrinsic_infer(f) + ## Computing the cost of a function body # saturating sum (inputs are nonnegative), prevents overflow with typemax(Int) below plus_saturate(x::Int, y::Int) = max(x, y, x+y) # known return type -isknowntype(@nospecialize T) = (T == Union{}) || isconcretetype(T) +isknowntype(@nospecialize T) = (T === Union{}) || isconcretetype(T) -function statement_cost(ex::Expr, line::Int, src::CodeInfo, spvals::SimpleVector, slottypes::Vector{Any}, params::Params) +function statement_cost(ex::Expr, line::Int, src::CodeInfo, sptypes::Vector{Any}, slottypes::Vector{Any}, params::Params) head = ex.head if is_meta_expr_head(head) return 0 elseif head === :call farg = ex.args[1] - ftyp = argextype(farg, src, spvals, slottypes) + ftyp = argextype(farg, src, sptypes, slottypes) if ftyp === IntrinsicFunction && farg isa SSAValue # if this comes from code that was already inlined into another function, # Consts have been widened. try to recover in simple cases. farg = src.code[farg.id] if isa(farg, GlobalRef) || isa(farg, QuoteNode) || isa(farg, IntrinsicFunction) || isexpr(farg, :static_parameter) - ftyp = argextype(farg, src, spvals, slottypes) + ftyp = argextype(farg, src, sptypes, slottypes) end end f = singleton_type(ftyp) @@ -301,8 +309,8 @@ function statement_cost(ex::Expr, line::Int, src::CodeInfo, spvals::SimpleVector # tuple iteration/destructuring makes that impossible # return plus_saturate(argcost, isknowntype(extyp) ? 1 : params.inline_nonleaf_penalty) return 0 - elseif f === Main.Core.arrayref && length(ex.args) >= 3 - atyp = argextype(ex.args[3], src, spvals, slottypes) + elseif (f === Main.Core.arrayref || f === Main.Core.const_arrayref) && length(ex.args) >= 3 + atyp = argextype(ex.args[3], src, sptypes, slottypes) return isknowntype(atyp) ? 4 : params.inline_nonleaf_penalty end fidx = find_tfunc(f) @@ -325,7 +333,7 @@ function statement_cost(ex::Expr, line::Int, src::CodeInfo, spvals::SimpleVector elseif head === :return a = ex.args[1] if a isa Expr - return statement_cost(a, -1, src, spvals, slottypes, params) + return statement_cost(a, -1, src, sptypes, slottypes, params) end return 0 elseif head === :(=) @@ -336,7 +344,7 @@ function statement_cost(ex::Expr, line::Int, src::CodeInfo, spvals::SimpleVector end a = ex.args[2] if a isa Expr - cost = plus_saturate(cost, statement_cost(a, -1, src, spvals, slottypes, params)) + cost = plus_saturate(cost, statement_cost(a, -1, src, sptypes, slottypes, params)) end return cost elseif head === :copyast @@ -357,13 +365,13 @@ function statement_cost(ex::Expr, line::Int, src::CodeInfo, spvals::SimpleVector return 0 end -function inline_worthy(body::Array{Any,1}, src::CodeInfo, spvals::SimpleVector, slottypes::Vector{Any}, +function inline_worthy(body::Array{Any,1}, src::CodeInfo, sptypes::Vector{Any}, slottypes::Vector{Any}, params::Params, cost_threshold::Integer=params.inline_cost_threshold) bodycost::Int = 0 for line = 1:length(body) stmt = body[line] if stmt isa Expr - thiscost = statement_cost(stmt, line, src, spvals, slottypes, params)::Int + thiscost = statement_cost(stmt, line, src, sptypes, slottypes, params)::Int elseif stmt isa GotoNode # loops are generally always expensive # but assume that forward jumps are already counted for from @@ -378,11 +386,11 @@ function inline_worthy(body::Array{Any,1}, src::CodeInfo, spvals::SimpleVector, return true end -function is_known_call(e::Expr, @nospecialize(func), src, spvals::SimpleVector, slottypes::Vector{Any} = empty_slottypes) +function is_known_call(e::Expr, @nospecialize(func), src, sptypes::Vector{Any}, slottypes::Vector{Any} = empty_slottypes) if e.head !== :call return false end - f = argextype(e.args[1], src, spvals, slottypes) + f = argextype(e.args[1], src, sptypes, slottypes) return isa(f, Const) && f.val === func end diff --git a/base/compiler/params.jl b/base/compiler/params.jl index 91b9ee35ee9b61..46c086210dbbdc 100644 --- a/base/compiler/params.jl +++ b/base/compiler/params.jl @@ -56,6 +56,8 @@ struct Params tuple_splat) end function Params(world::UInt) + world == typemax(UInt) && (world = get_world_counter()) # workaround for bad callers + @assert world <= get_world_counter() inlining = inlining_enabled() return new(Vector{InferenceResult}(), world, true, diff --git a/base/compiler/ssair/domtree.jl b/base/compiler/ssair/domtree.jl index 27c9af8b44fd1a..14302c1c199248 100644 --- a/base/compiler/ssair/domtree.jl +++ b/base/compiler/ssair/domtree.jl @@ -28,9 +28,13 @@ end bb_unreachable(domtree::DomTree, bb::Int) = bb != 1 && domtree.nodes[bb].level == 1 function update_level!(domtree::Vector{DomTreeNode}, node::Int, level::Int) - domtree[node] = DomTreeNode(level, domtree[node].children) - foreach(domtree[node].children) do child - update_level!(domtree, child, level+1) + worklist = Tuple{Int, Int}[(node, level)] + while !isempty(worklist) + (node, level) = pop!(worklist) + domtree[node] = DomTreeNode(level, domtree[node].children) + foreach(domtree[node].children) do child + push!(worklist, (child, level+1)) + end end end @@ -177,13 +181,11 @@ begin parent = 0 while !isempty(worklist) (parent, current_node) = pop!(worklist) + dfs.reverse[current_node] != 0 && continue dfs.reverse[current_node] = dfs_num dfs.numbering[dfs_num] = current_node dfs.parents[dfs_num] = parent for succ in cfg.blocks[current_node].succs - dfs.reverse[succ] != 0 && continue - # Mark things that are currently in the worklist - dfs.reverse[succ] = 1 push!(worklist, (dfs_num, succ)) end dfs_num += 1 @@ -215,6 +217,29 @@ begin nothing end + function snca_compress_worklist!( + state::Vector{Node}, ancestors::Vector{DFSNumber}, + v::DFSNumber, last_linked::DFSNumber) + # TODO: There is a smarter way to do this + u = ancestors[v] + worklist = Tuple{Int, Int}[(u,v)] + @assert u < v + while !isempty(worklist) + u, v = last(worklist) + if u >= last_linked + if ancestors[u] >= last_linked + push!(worklist, (ancestors[u], u)) + continue + end + if state[u].label < state[v].label + state[v] = Node(state[v].semi, state[u].label) + end + ancestors[v] = ancestors[u] + end + pop!(worklist) + end + end + """ The main Semi-NCA algrithm. Matches Figure 2.8 in [LG05]. Note that the pseudocode in [LG05] is not entirely accurate. @@ -239,7 +264,7 @@ begin # SLT would, but never simultaneously, so we could still # do this. ancestors = D.parents - for w ∈ reverse(_drop(preorder(D), 1)) + for w::DFSNumber ∈ reverse(_drop(preorder(D), 1)) # LLVM initializes this to the parent, the paper initializes this to # `w`, but it doesn't really matter (the parent is a predecessor, # so at worst we'll discover it below). Save a memory reference here. @@ -257,7 +282,13 @@ begin # `ancestor[v] != 0` check in the `eval` implementation in # figure 2.6 if vdfs >= last_linked - snca_compress!(state, ancestors, vdfs, last_linked) + # For performance, if the number of ancestors is small + # avoid the extra allocation of the worklist. + if length(ancestors) <= 32 + snca_compress!(state, ancestors, vdfs, last_linked) + else + snca_compress_worklist!(state, ancestors, vdfs, last_linked) + end end semi_w = min(semi_w, state[vdfs].label) end diff --git a/base/compiler/ssair/driver.jl b/base/compiler/ssair/driver.jl index dd66199f2e4ddb..8f00e39cab03bd 100644 --- a/base/compiler/ssair/driver.jl +++ b/base/compiler/ssair/driver.jl @@ -1,7 +1,6 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license using Core: LineInfoNode -const NullLineInfo = LineInfoNode(@__MODULE__, Symbol(""), Symbol(""), 0, 0) if false import Base: Base, @show @@ -103,10 +102,9 @@ function just_construct_ssa(ci::CodeInfo, code::Vector{Any}, nargs::Int, sv::Opt defuse_insts = scan_slot_def_use(nargs, ci, code) @timeit "domtree 1" domtree = construct_domtree(cfg) ir = let code = Any[nothing for _ = 1:length(code)] - argtypes = sv.slottypes[1:(nargs+1)] - IRCode(code, Any[], ci.codelocs, flags, cfg, collect(LineInfoNode, ci.linetable), argtypes, meta, sv.sp) + IRCode(code, Any[], ci.codelocs, flags, cfg, collect(LineInfoNode, ci.linetable), sv.slottypes, meta, sv.sptypes) end - @timeit "construct_ssa" ir = construct_ssa!(ci, code, ir, domtree, defuse_insts, nargs, sv.sp, sv.slottypes) + @timeit "construct_ssa" ir = construct_ssa!(ci, code, ir, domtree, defuse_insts, nargs, sv.sptypes, sv.slottypes) return ir end diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 1b7cbebac5caa3..42ef67b8063244 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -6,6 +6,16 @@ struct InvokeData types0 end +struct Signature + f::Any + ft::Any + atypes::Vector{Any} + atype::Type + Signature(f, ft, atypes) = new(f, ft, atypes) + Signature(f, ft, atypes, atype) = new(f, ft, atypes, atype) +end +with_atype(sig::Signature) = Signature(sig.f, sig.ft, sig.atypes, argtypes_to_type(sig.atypes)) + struct InliningTodo idx::Int # The statement to replace # Properties of the call - these determine how arguments @@ -23,6 +33,7 @@ struct InliningTodo # simpler inlining algorithm. This flag determines whether that's allowed linear_inline_eligible::Bool end +isinvoke(inl::InliningTodo) = inl.isinvoke struct ConstantCase val::Any @@ -45,21 +56,21 @@ struct UnionSplit idx::Int # The statement to replace fully_covered::Bool atype # ::Type - isinvoke::Bool cases::Vector{Pair{Any, Any}} bbs::Vector{Int} - UnionSplit(idx::Int, fully_covered::Bool, @nospecialize(atype), isinvoke::Bool, + UnionSplit(idx::Int, fully_covered::Bool, @nospecialize(atype), cases::Vector{Pair{Any, Any}}) = - new(idx, fully_covered, atype, isinvoke, cases, Int[]) + new(idx, fully_covered, atype, cases, Int[]) end +isinvoke(inl::UnionSplit) = false function ssa_inlining_pass!(ir::IRCode, linetable::Vector{LineInfoNode}, sv::OptimizationState) # Go through the function, performing simple ininlingin (e.g. replacing call by constants # and analyzing legality of inlining). - @timeit "analysis" todo = assemble_inline_todo!(ir, linetable, sv) + @timeit "analysis" todo = assemble_inline_todo!(ir, sv) isempty(todo) && return ir # Do the actual inlining for every call we identified - @timeit "execution" ir = batch_inline!(todo, ir, linetable, sv) + @timeit "execution" ir = batch_inline!(todo, ir, linetable, sv.src.propagate_inbounds) return ir end @@ -157,14 +168,18 @@ function cfg_inline_item!(item::InliningTodo, state::CFGInliningState, from_unio for (old_block, new_block) in enumerate(bb_rename_range) if old_block != 1 || need_split_before p = state.new_cfg_blocks[new_block].preds - map!(p, p) do old_pred_block - return old_pred_block == 0 ? 0 : bb_rename_range[old_pred_block] + let bb_rename_range = bb_rename_range + map!(p, p) do old_pred_block + return old_pred_block == 0 ? 0 : bb_rename_range[old_pred_block] + end end end if new_block != last(new_block_range) s = state.new_cfg_blocks[new_block].succs - map!(s, s) do old_succ_block - return bb_rename_range[old_succ_block] + let bb_rename_range = bb_rename_range + map!(s, s) do old_succ_block + return bb_rename_range[old_succ_block] + end end end end @@ -289,7 +304,7 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector # Append the linetable of the inlined function to our line table inlined_at = Int(compact.result_lines[idx]) for entry in item.linetable - push!(linetable, LineInfoNode(entry.mod, entry.method, entry.file, entry.line, + push!(linetable, LineInfoNode(entry.method, entry.file, entry.line, (entry.inlined_at > 0 ? entry.inlined_at + linetable_offset : inlined_at))) end if item.isva @@ -477,7 +492,7 @@ function ir_inline_unionsplit!(compact::IncrementalCompact, idx::Int, nothing end -function batch_inline!(todo::Vector{Any}, ir::IRCode, linetable::Vector{LineInfoNode}, sv::OptimizationState) +function batch_inline!(todo::Vector{Any}, ir::IRCode, linetable::Vector{LineInfoNode}, propagate_inbounds::Bool) # Compute the new CFG first (modulo statement ranges, which will be computed below) state = CFGInliningState(ir) for item in todo @@ -493,7 +508,7 @@ function batch_inline!(todo::Vector{Any}, ir::IRCode, linetable::Vector{LineInfo finish_cfg_inline!(state) boundscheck = inbounds_option() - if boundscheck === :default && sv.src.propagate_inbounds + if boundscheck === :default && propagate_inbounds boundscheck = :propagate end @@ -525,7 +540,7 @@ function batch_inline!(todo::Vector{Any}, ir::IRCode, linetable::Vector{LineInfo argexprs[aidx] = insert_node_here!(compact, aexpr, compact_exprtype(compact, aexpr), compact.result_lines[idx]) end end - if item.isinvoke + if isinvoke(item) argexprs = rewrite_invoke_exprargs!((node, typ)->insert_node_here!(compact, node, typ, compact.result_lines[idx]), argexprs) end @@ -558,41 +573,46 @@ function batch_inline!(todo::Vector{Any}, ir::IRCode, linetable::Vector{LineInfo return ir end -function _spec_lambda(@nospecialize(atype), sv::OptimizationState, @nospecialize(invoke_data)) +function spec_lambda(@nospecialize(atype), sv::OptimizationState, @nospecialize(invoke_data)) + min_valid = UInt[typemin(UInt)] + max_valid = UInt[typemax(UInt)] if invoke_data === nothing - return ccall(:jl_get_spec_lambda, Any, (Any, UInt), atype, sv.params.world) + mi = ccall(:jl_get_spec_lambda, Any, (Any, UInt, Ptr{UInt}, Ptr{UInt}), atype, sv.params.world, min_valid, max_valid) else invoke_data = invoke_data::InvokeData atype <: invoke_data.types0 || return nothing - return ccall(:jl_get_invoke_lambda, Any, (Any, Any, Any, UInt), - invoke_data.mt, invoke_data.entry, atype, sv.params.world) + mi = ccall(:jl_get_invoke_lambda, Any, (Any, Any, Any, UInt), + invoke_data.mt, invoke_data.entry, atype, sv.params.world) + #XXX: compute min/max_valid end -end - -function spec_lambda(@nospecialize(atype), sv::OptimizationState, @nospecialize(invoke_data)) - linfo = _spec_lambda(atype, sv, invoke_data) - linfo !== nothing && add_backedge!(linfo, sv) - linfo + mi !== nothing && add_backedge!(mi::MethodInstance, sv) + update_valid_age!(min_valid[1], max_valid[1], sv) + return mi end # This assumes the caller has verified that all arguments to the _apply call are Tuples. -function rewrite_apply_exprargs!(ir::IRCode, idx::Int, argexprs::Vector{Any}, atypes::Vector{Any}, sv::OptimizationState) +function rewrite_apply_exprargs!(ir::IRCode, idx::Int, argexprs::Vector{Any}, atypes::Vector{Any}) new_argexprs = Any[argexprs[2]] new_atypes = Any[atypes[2]] # loop over original arguments and flatten any known iterators for i in 3:length(argexprs) def = argexprs[i] def_type = atypes[i] - if def_type isa PartialTuple + if def_type isa PartialStruct + # def_type.typ <: Tuple is assumed def_atypes = def_type.fields else def_atypes = Any[] - if isa(def_type, Const) # && isa(def_type.val, Tuple) is implied + if isa(def_type, Const) # && isa(def_type.val, Union{Tuple, SimpleVector}) is implied for p in def_type.val push!(def_atypes, Const(p)) end else - for p in widenconst(def_type).parameters + ti = widenconst(def_type) + if ti.name === NamedTuple_typename + ti = ti.parameters[2] + end + for p in ti.parameters if isa(p, DataType) && isdefined(p, :instance) # replace singleton types with their equivalent Const object p = Const(p.instance) @@ -606,8 +626,12 @@ function rewrite_apply_exprargs!(ir::IRCode, idx::Int, argexprs::Vector{Any}, at # now push flattened types into new_atypes and getfield exprs into new_argexprs for j in 1:length(def_atypes) def_atype = def_atypes[j] - new_call = Expr(:call, Core.getfield, def, j) - new_argexpr = insert_node!(ir, idx, def_atype, new_call) + if isa(def_atype, Const) && is_inlineable_constant(def_atype.val) + new_argexpr = quoted(def_atype.val) + else + new_call = Expr(:call, Core.getfield, def, j) + new_argexpr = insert_node!(ir, idx, def_atype, new_call) + end push!(new_argexprs, new_argexpr) push!(new_atypes, def_atype) end @@ -631,9 +655,10 @@ function singleton_type(@nospecialize(ft)) return nothing end -function analyze_method!(idx::Int, @nospecialize(f), @nospecialize(ft), @nospecialize(metharg), methsp::SimpleVector, - method::Method, stmt::Expr, atypes::Vector{Any}, sv::OptimizationState, @nospecialize(atype_unlimited), +function analyze_method!(idx::Int, sig::Signature, @nospecialize(metharg), methsp::SimpleVector, + method::Method, stmt::Expr, sv::OptimizationState, isinvoke::Bool, invoke_data::Union{InvokeData,Nothing}, @nospecialize(stmttyp)) + f, ft, atypes, atype_unlimited = sig.f, sig.ft, sig.atypes, sig.atype methsig = method.sig # Check whether this call just evaluates to a constant @@ -659,46 +684,46 @@ function analyze_method!(idx::Int, @nospecialize(f), @nospecialize(ft), @nospeci isa(methsp[i], TypeVar) && return nothing end - # Find the linfo for this methods - linfo = code_for_method(method, metharg, methsp, sv.params.world, true) # Union{Nothing, MethodInstance} - if !isa(linfo, MethodInstance) + # See if there exists a specialization for this method signature + mi = specialize_method(method, metharg, methsp, true) # Union{Nothing, MethodInstance} + if !isa(mi, MethodInstance) return spec_lambda(atype_unlimited, sv, invoke_data) end - if invoke_api(linfo) == 2 - # in this case function can be inlined to a constant - add_backedge!(linfo, sv) - return ConstantCase(quoted(linfo.inferred_const), method, Any[methsp...], metharg) - end - - isconst, inferred = find_inferred(linfo, atypes, sv, stmttyp) + isconst, src = find_inferred(mi, atypes, sv, stmttyp) if isconst - return ConstantCase(inferred, method, Any[methsp...], metharg) + add_backedge!(mi, sv) + return ConstantCase(src, method, Any[methsp...], metharg) end - if inferred === nothing + if src === nothing return spec_lambda(atype_unlimited, sv, invoke_data) end - src_inferred = ccall(:jl_ast_flag_inferred, Bool, (Any,), inferred) - src_inlineable = ccall(:jl_ast_flag_inlineable, Bool, (Any,), inferred) + src_inferred = ccall(:jl_ast_flag_inferred, Bool, (Any,), src) + src_inlineable = ccall(:jl_ast_flag_inlineable, Bool, (Any,), src) if !(src_inferred && src_inlineable) return spec_lambda(atype_unlimited, sv, invoke_data) end # At this point we're committed to performing the inlining, add the backedge - add_backedge!(linfo, sv) + add_backedge!(mi, sv) - if isa(inferred, CodeInfo) - src = inferred - ast = copy_exprargs(inferred.code) - else - src = ccall(:jl_uncompress_ast, Any, (Any, Any), method, inferred::Vector{UInt8})::CodeInfo - ast = src.code + if !isa(src, CodeInfo) + src = ccall(:jl_uncompress_ast, Any, (Any, Ptr{Cvoid}, Any), method, C_NULL, src::Vector{UInt8})::CodeInfo end @timeit "inline IR inflation" begin - ir2, inline_linetable = inflate_ir(src, linfo), src.linetable + ir2 = inflate_ir(src, mi) + # prepare inlining linetable with method instance information + inline_linetable = Vector{LineInfoNode}(undef, length(src.linetable)) + for i = 1:length(src.linetable) + entry = src.linetable[i] + if entry.inlined_at === 0 && entry.method === method + entry = LineInfoNode(mi, entry.file, entry.line, entry.inlined_at) + end + inline_linetable[i] = entry + end end #verify_ir(ir2) @@ -773,136 +798,207 @@ function handle_single_case!(ir::IRCode, stmt::Expr, idx::Int, @nospecialize(cas nothing end -function assemble_inline_todo!(ir::IRCode, linetable::Vector{LineInfoNode}, sv::OptimizationState) - # todo = (inline_idx, (isva, isinvoke, na), method, spvals, inline_linetable, inline_ir, lie) - todo = Any[] - for idx in 1:length(ir.stmts) - stmt = ir.stmts[idx] - isexpr(stmt, :call) || continue - eargs = stmt.args - isempty(eargs) && continue - arg1 = eargs[1] - - ft = argextype(arg1, ir, sv.sp) - has_free_typevars(ft) && continue - f = singleton_type(ft) - f === Core.Intrinsics.llvmcall && continue - f === Core.Intrinsics.cglobal && continue - - atypes = Vector{Any}(undef, length(stmt.args)) - atypes[1] = ft - ok = true - for i = 2:length(stmt.args) - a = argextype(stmt.args[i], ir, sv.sp) - (a === Bottom || isvarargtype(a)) && (ok = false; break) - atypes[i] = a +function is_valid_type_for_apply_rewrite(@nospecialize(typ), params::Params) + if isa(typ, Const) && isa(typ.val, SimpleVector) + length(typ.val) > params.MAX_TUPLE_SPLAT && return false + for p in typ.val + is_inlineable_constant(p) || return false end - ok || continue - - # Check if we match any of the early inliners - calltype = ir.types[idx] - res = early_inline_special_case(ir, f, ft, stmt, atypes, sv, calltype) - if res !== nothing - ir.stmts[idx] = res - continue + return true + end + typ = widenconst(typ) + if isa(typ, DataType) && typ.name === NamedTuple_typename + typ = typ.parameters[2] + while isa(typ, TypeVar) + typ = typ.ub end + end + isa(typ, DataType) || return false + if typ.name === Tuple.name + return !isvatuple(typ) && length(typ.parameters) <= params.MAX_TUPLE_SPLAT + else + return false + end +end - if f !== Core.invoke && f !== Core._apply && - (isa(f, IntrinsicFunction) || ft ⊑ IntrinsicFunction || isa(f, Builtin) || ft ⊑ Builtin) - # No inlining for builtins (other than what's handled in the early inliner) - # TODO: this test is wrong if we start to handle Unions of function types later - continue +function inline_splatnew!(ir::IRCode, idx) + stmt = ir.stmts[idx] + ty = ir.types[idx] + nf = nfields_tfunc(ty) + if nf isa Const + eargs = stmt.args + tup = eargs[2] + tt = argextype(tup, ir, ir.sptypes) + tnf = nfields_tfunc(tt) + if tnf isa Const && tnf.val <= nf.val + n = tnf.val + new_argexprs = Any[eargs[1]] + for j = 1:n + atype = getfield_tfunc(tt, Const(j)) + new_call = Expr(:call, Core.getfield, tup, j) + new_argexpr = insert_node!(ir, idx, atype, new_call) + push!(new_argexprs, new_argexpr) + end + stmt.head = :new + stmt.args = new_argexprs end + end +end - # Special handling for Core.invoke and Core._apply, which can follow the normal inliner - # logic with modified inlining target - isinvoke = false - - # Handle _apply - ok = true - while f === Core._apply - # Try to figure out the signature of the function being called - # and if rewrite_apply_exprargs can deal with this form - for i = 3:length(atypes) - typ = atypes[i] - typ = widenconst(typ) - # TODO: We could basically run the iteration protocol here - if !isa(typ, DataType) || typ.name !== Tuple.name || - isvatuple(typ) || length(typ.parameters) > sv.params.MAX_TUPLE_SPLAT - ok = false - break - end - end - ok || break - # Independent of whether we can inline, the above analysis allows us to rewrite - # this apply call to a regular call - ft = atypes[2] - if length(atypes) == 3 && ft isa Const && ft.val === Core.tuple && atypes[3] ⊑ Tuple - # rewrite `((t::Tuple)...,)` to `t` - ir.stmts[idx] = stmt.args[3] - ok = false - break +function call_sig(ir::IRCode, stmt::Expr) + isempty(stmt.args) && return nothing + ft = argextype(stmt.args[1], ir, ir.sptypes) + has_free_typevars(ft) && return nothing + f = singleton_type(ft) + f === Core.Intrinsics.llvmcall && return nothing + f === Core.Intrinsics.cglobal && return nothing + + atypes = Vector{Any}(undef, length(stmt.args)) + atypes[1] = ft + ok = true + for i = 2:length(stmt.args) + a = argextype(stmt.args[i], ir, ir.sptypes) + (a === Bottom || isvarargtype(a)) && return nothing + atypes[i] = a + end + + Signature(f, ft, atypes) +end + +function inline_apply!(ir::IRCode, idx::Int, sig::Signature, params::Params) + stmt = ir.stmts[idx] + while sig.f === Core._apply + atypes = sig.atypes + # Try to figure out the signature of the function being called + # and if rewrite_apply_exprargs can deal with this form + for i = 3:length(atypes) + # TODO: We could basically run the iteration protocol here + if !is_valid_type_for_apply_rewrite(atypes[i], params) + return nothing end - stmt.args, atypes = rewrite_apply_exprargs!(ir, idx, stmt.args, atypes, sv) - ok = !has_free_typevars(ft) - ok || break - f = singleton_type(ft) end - ok || continue - - if f !== Core.invoke && (isa(f, IntrinsicFunction) || ft ⊑ IntrinsicFunction || isa(f, Builtin) || ft ⊑ Builtin) - # TODO: this test is wrong if we start to handle Unions of function types later - continue + # Independent of whether we can inline, the above analysis allows us to rewrite + # this apply call to a regular call + ft = atypes[2] + if length(atypes) == 3 && ft isa Const && ft.val === Core.tuple && atypes[3] ⊑ Tuple + # rewrite `((t::Tuple)...,)` to `t` + ir.stmts[idx] = stmt.args[3] + return nothing end + stmt.args, atypes = rewrite_apply_exprargs!(ir, idx, stmt.args, atypes) + has_free_typevars(ft) && return nothing + f = singleton_type(ft) + sig = Signature(f, ft, atypes) + end + sig +end - # Handle invoke - invoke_data = nothing - if f === Core.invoke && length(atypes) >= 3 - res = compute_invoke_data(atypes, stmt.args, sv) - res === nothing && continue - (f, ft, atypes, argexprs, invoke_data) = res - end - isinvoke = (invoke_data !== nothing) +# 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 inline_invoke!(ir::IRCode, idx::Int, sig::Signature, invoke_data::InvokeData, sv::OptimizationState, todo::Vector{Any}) + stmt = ir.stmts[idx] + calltype = ir.types[idx] + method = invoke_data.entry.func + (metharg, methsp) = ccall(:jl_type_intersection_with_env, Any, (Any, Any), + sig.atype, method.sig)::SimpleVector + methsp = methsp::SimpleVector + result = analyze_method!(idx, sig, metharg, methsp, method, stmt, sv, true, invoke_data, + calltype) + handle_single_case!(ir, stmt, idx, result, true, todo, sv) + return nothing +end + +# Handles all analysis and inlining of intrinsics and builtins. In particular, +# this method does not access the method table or otherwise process generic +# functions. +function process_simple!(ir::IRCode, idx::Int, params::Params) + stmt = ir.stmts[idx] + stmt isa Expr || return nothing + if stmt.head === :splatnew + inline_splatnew!(ir, idx) + return nothing + end - atype = argtypes_to_type(atypes) + stmt.head === :call || return nothing - # In :invoke, make sure that the arguments we're passing are a subtype of the - # signature we're invoking. - (invoke_data === nothing || atype <: invoke_data.types0) || continue + sig = call_sig(ir, stmt) + sig === nothing && return nothing - # Bail out here if inlining is disabled - sv.params.inlining || continue + # Handle _apply + sig = inline_apply!(ir, idx, sig, params) + sig === nothing && return nothing - # Special case inliners for regular functions - if late_inline_special_case!(ir, idx, stmt, atypes, f, ft) || is_return_type(f) - continue - end + # Check if we match any of the early inliners + calltype = ir.types[idx] + res = early_inline_special_case(ir, sig, stmt, params, calltype) + if res !== nothing + ir.stmts[idx] = res + return nothing + end + + # Bail out here if inlining is disabled + params.inlining || return nothing + + # Handle invoke + invoke_data = nothing + if sig.f === Core.invoke && length(sig.atypes) >= 3 + res = compute_invoke_data(sig.atypes, params) + res === nothing && return nothing + (sig, invoke_data) = res + elseif is_builtin(sig) + # No inlining for builtins (other than what was previously handled) + return nothing + end + + sig = with_atype(sig) + + # In :invoke, make sure that the arguments we're passing are a subtype of the + # signature we're invoking. + (invoke_data === nothing || sig.atype <: invoke_data.types0) || return nothing + + # Special case inliners for regular functions + if late_inline_special_case!(ir, sig, idx, stmt) || is_return_type(sig.f) + return nothing + end + return (sig, invoke_data) +end + +function assemble_inline_todo!(ir::IRCode, sv::OptimizationState) + # todo = (inline_idx, (isva, isinvoke, na), method, spvals, inline_linetable, inline_ir, lie) + todo = Any[] + for idx in 1:length(ir.stmts) + r = process_simple!(ir, idx, sv.params) + r === nothing && continue + + stmt = ir.stmts[idx] + calltype = ir.types[idx] + (sig, invoke_data) = r # Ok, now figure out what method to call if invoke_data !== nothing - method = invoke_data.entry.func - (metharg, methsp) = ccall(:jl_type_intersection_with_env, Any, (Any, Any), - atype, method.sig)::SimpleVector - methsp = methsp::SimpleVector - result = analyze_method!(idx, f, ft, metharg, methsp, method, stmt, atypes, sv, atype, isinvoke, invoke_data, - calltype) - handle_single_case!(ir, stmt, idx, result, isinvoke, todo, sv) + inline_invoke!(ir, idx, sig, invoke_data, sv, todo) continue end # Regular case: Perform method matching min_valid = UInt[typemin(UInt)] max_valid = UInt[typemax(UInt)] - meth = _methods_by_ftype(atype, sv.params.MAX_METHODS, sv.params.world, min_valid, max_valid) + meth = _methods_by_ftype(sig.atype, sv.params.MAX_METHODS, sv.params.world, min_valid, max_valid) if meth === false || length(meth) == 0 # No applicable method, or too many applicable methods continue end + update_valid_age!(min_valid[1], max_valid[1], sv) cases = Pair{Any, Any}[] # TODO: This could be better signature_union = Union{Any[match[1]::Type for match in meth]...} - signature_fully_covered = atype <: signature_union + signature_fully_covered = sig.atype <: signature_union fully_covered = signature_fully_covered split_out_sigs = Any[] @@ -913,7 +1009,9 @@ function assemble_inline_todo!(ir::IRCode, linetable::Vector{LineInfoNode}, sv:: fully_covered = false continue end - case = analyze_method!(idx, f, ft, metharg, methsp, method, stmt, atypes, sv, metharg, isinvoke, invoke_data, calltype) + case_sig = Signature(sig.f, sig.ft, sig.atypes, metharg) + case = analyze_method!(idx, case_sig, metharg, methsp, method, + stmt, sv, false, nothing, calltype) if case === nothing fully_covered = false continue @@ -923,11 +1021,11 @@ function assemble_inline_todo!(ir::IRCode, linetable::Vector{LineInfoNode}, sv:: end # Now, if profitable union split the atypes into dispatch tuples and match the appropriate method - nu = countunionsplit(atypes) + nu = countunionsplit(sig.atypes) if nu != 1 && nu <= sv.params.MAX_UNION_SPLITTING fully_covered = true - for sig in UnionSplitSignature(atypes) - metharg′ = argtypes_to_type(sig) + for union_sig in UnionSplitSignature(sig.atypes) + metharg′ = argtypes_to_type(union_sig) if !isdispatchtuple(metharg′) fully_covered = false continue @@ -939,7 +1037,8 @@ function assemble_inline_todo!(ir::IRCode, linetable::Vector{LineInfoNode}, sv:: for (i, match) in enumerate(meth) (metharg, methsp, method) = (match[1]::Type, match[2]::SimpleVector, match[3]::Method) metharg′ <: method.sig || continue - case = analyze_method!(idx, f, ft, metharg′, methsp, method, stmt, atypes, sv, metharg′, isinvoke, invoke_data, + case_sig = Signature(sig.f, sig.ft, sig.atypes, metharg′) + case = analyze_method!(idx, case_sig, metharg′, methsp, method, stmt, sv, false, nothing, calltype) if case !== nothing found_any = true @@ -961,7 +1060,8 @@ function assemble_inline_todo!(ir::IRCode, linetable::Vector{LineInfoNode}, sv:: methsp = meth[1][2]::SimpleVector method = meth[1][3]::Method fully_covered = true - case = analyze_method!(idx, f, ft, metharg, methsp, method, stmt, atypes, sv, atype, isinvoke, invoke_data, calltype) + case = analyze_method!(idx, sig, metharg, methsp, method, + stmt, sv, false, nothing, calltype) case === nothing && continue push!(cases, Pair{Any,Any}(metharg, case)) end @@ -970,11 +1070,11 @@ function assemble_inline_todo!(ir::IRCode, linetable::Vector{LineInfoNode}, sv:: # be able to do the inlining now (for constant cases), or push it directly # onto the todo list if fully_covered && length(cases) == 1 - handle_single_case!(ir, stmt, idx, cases[1][2], isinvoke, todo, sv) + handle_single_case!(ir, stmt, idx, cases[1][2], false, todo, sv) continue end length(cases) == 0 && continue - push!(todo, UnionSplit(idx, fully_covered, atype, isinvoke, cases)) + push!(todo, UnionSplit(idx, fully_covered, sig.atype, cases)) end todo end @@ -993,7 +1093,7 @@ function linear_inline_eligible(ir::IRCode) return true end -function compute_invoke_data(@nospecialize(atypes), argexprs::Vector{Any}, sv::OptimizationState) +function compute_invoke_data(@nospecialize(atypes), params::Params) ft = widenconst(atypes[2]) invoke_tt = widenconst(atypes[3]) mt = argument_mt(ft) @@ -1011,17 +1111,15 @@ function compute_invoke_data(@nospecialize(atypes), argexprs::Vector{Any}, sv::O invoke_tt = invoke_tt.parameters[1] invoke_types = rewrap_unionall(Tuple{ft, unwrap_unionall(invoke_tt).parameters...}, invoke_tt) invoke_entry = ccall(:jl_gf_invoke_lookup, Any, (Any, UInt), - invoke_types, sv.params.world) + invoke_types, params.world) invoke_entry === nothing && return nothing + #XXX: update_valid_age!(min_valid[1], max_valid[1], sv) invoke_data = InvokeData(mt, invoke_entry, invoke_types) atype0 = atypes[2] - argexpr0 = argexprs[2] atypes = atypes[4:end] - argexprs = argexprs[4:end] pushfirst!(atypes, atype0) - pushfirst!(argexprs, argexpr0) - f = isdefined(ft, :instance) ? ft.instance : nothing - return svec(f, ft, atypes, argexprs, invoke_data) + f = singleton_type(ft) + return (Signature(f, ft, atypes), invoke_data) end # Check for a number of functions known to be pure @@ -1032,8 +1130,9 @@ function ispuretopfunction(@nospecialize(f)) istopfunction(f, :promote_type) end -function early_inline_special_case(ir::IRCode, @nospecialize(f), @nospecialize(ft), e::Expr, atypes::Vector{Any}, sv::OptimizationState, +function early_inline_special_case(ir::IRCode, s::Signature, e::Expr, params::Params, @nospecialize(etype)) + f, ft, atypes = s.f, s.ft, s.atypes if (f === typeassert || ft ⊑ typeof(typeassert)) && length(atypes) == 3 # typeassert(x::S, T) => x, when S<:T a3 = atypes[3] @@ -1045,7 +1144,7 @@ function early_inline_special_case(ir::IRCode, @nospecialize(f), @nospecialize(f end end - if sv.params.inlining + if params.inlining if isa(etype, Const) # || isconstType(etype) val = etype.val is_inlineable_constant(val) || return nothing @@ -1067,8 +1166,9 @@ function early_inline_special_case(ir::IRCode, @nospecialize(f), @nospecialize(f return nothing end -function late_inline_special_case!(ir::IRCode, idx::Int, stmt::Expr, atypes::Vector{Any}, @nospecialize(f), @nospecialize(ft)) +function late_inline_special_case!(ir::IRCode, sig::Signature, idx::Int, stmt::Expr) typ = ir.types[idx] + f, ft, atypes = sig.f, sig.ft, sig.atypes if length(atypes) == 3 && istopfunction(f, :!==) # special-case inliner for !== that precedes _methods_by_ftype union splitting # and that works, even though inference generally avoids inferring the `!==` Method @@ -1158,7 +1258,7 @@ function ssa_substitute_op!(@nospecialize(val), arg_replacements::Vector{Any}, return urs[] end -function find_inferred(linfo::MethodInstance, @nospecialize(atypes), sv::OptimizationState, @nospecialize(rettype)) +function find_inferred(mi::MethodInstance, @nospecialize(atypes), sv::OptimizationState, @nospecialize(rettype)) # see if the method has a InferenceResult in the current cache # or an existing inferred code info store in `.inferred` haveconst = false @@ -1170,22 +1270,28 @@ function find_inferred(linfo::MethodInstance, @nospecialize(atypes), sv::Optimiz end end if haveconst || improvable_via_constant_propagation(rettype) - inf_result = cache_lookup(linfo, atypes, sv.params.cache) # Union{Nothing, InferenceResult} + inf_result = cache_lookup(mi, atypes, sv.params.cache) # Union{Nothing, InferenceResult} else inf_result = nothing end + #XXX: update_valid_age!(min_valid[1], max_valid[1], sv) if isa(inf_result, InferenceResult) let inferred_src = inf_result.src if isa(inferred_src, CodeInfo) return svec(false, inferred_src) end if isa(inferred_src, Const) && is_inlineable_constant(inferred_src.val) - add_backedge!(linfo, sv) return svec(true, quoted(inferred_src.val),) end end end - if isdefined(linfo, :inferred) + + linfo = inf_for_methodinstance(mi, sv.params.world) + if linfo isa CodeInstance + if invoke_api(linfo) == 2 + # in this case function can be inlined to a constant + return svec(true, quoted(linfo.rettype_const)) + end return svec(false, linfo.inferred) end return svec(false, nothing) diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index 024b3500fae401..738da73d936295 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -53,7 +53,7 @@ struct CFG index::Vector{Int} # map from instruction => basic-block number # TODO: make this O(1) instead of O(log(n_blocks))? end -copy(c::CFG) = CFG(copy(c.blocks), copy(c.index)) +copy(c::CFG) = CFG(BasicBlock[copy(b) for b in c.blocks], copy(c.index)) function block_for_inst(index::Vector{Int}, inst::Int) return searchsortedfirst(index, inst, lt=(<=)) @@ -213,7 +213,7 @@ struct IRCode lines::Vector{Int32} flags::Vector{UInt8} argtypes::Vector{Any} - spvals::SimpleVector + sptypes::Vector{Any} linetable::Vector{LineInfoNode} cfg::CFG new_nodes::Vector{NewNode} @@ -221,15 +221,15 @@ struct IRCode function IRCode(stmts::Vector{Any}, types::Vector{Any}, lines::Vector{Int32}, flags::Vector{UInt8}, cfg::CFG, linetable::Vector{LineInfoNode}, argtypes::Vector{Any}, meta::Vector{Any}, - spvals::SimpleVector) - return new(stmts, types, lines, flags, argtypes, spvals, linetable, cfg, NewNode[], meta) + sptypes::Vector{Any}) + return new(stmts, types, lines, flags, argtypes, sptypes, linetable, cfg, NewNode[], meta) end function IRCode(ir::IRCode, stmts::Vector{Any}, types::Vector{Any}, lines::Vector{Int32}, flags::Vector{UInt8}, cfg::CFG, new_nodes::Vector{NewNode}) - return new(stmts, types, lines, flags, ir.argtypes, ir.spvals, ir.linetable, cfg, new_nodes, ir.meta) + return new(stmts, types, lines, flags, ir.argtypes, ir.sptypes, ir.linetable, cfg, new_nodes, ir.meta) end end -copy(code::IRCode) = IRCode(code, copy(code.stmts), copy(code.types), +copy(code::IRCode) = IRCode(code, copy_exprargs(code.stmts), copy(code.types), copy(code.lines), copy(code.flags), copy(code.cfg), copy(code.new_nodes)) function getindex(x::IRCode, s::SSAValue) @@ -325,7 +325,7 @@ function getindex(x::UseRef) end function is_relevant_expr(e::Expr) - return e.head in (:call, :invoke, :new, :(=), :(&), + return e.head in (:call, :invoke, :new, :splatnew, :(=), :(&), :gc_preserve_begin, :gc_preserve_end, :foreigncall, :isdefined, :copyast, :undefcheck, :throw_undef_if_not, @@ -780,8 +780,10 @@ function renumber_ssa2(val::SSAValue, ssanums::Vector{Any}, used_ssa::Vector{Int if do_rename_ssa val = ssanums[id] end - if isa(val, SSAValue) && used_ssa !== nothing - used_ssa[val.id] += 1 + if isa(val, SSAValue) + if used_ssa !== nothing + used_ssa[val.id] += 1 + end end return val end @@ -904,7 +906,7 @@ function process_node!(compact::IncrementalCompact, result::Vector{Any}, elseif isa(stmt, Expr) stmt = renumber_ssa2!(stmt, ssa_rename, used_ssas, late_fixup, result_idx, do_rename_ssa)::Expr if compact.allow_cfg_transforms && isexpr(stmt, :enter) - stmt.args[1] = compact.bb_rename[stmt.args[1]] + stmt.args[1] = compact.bb_rename[stmt.args[1]::Int] end result[result_idx] = stmt result_idx += 1 @@ -913,15 +915,18 @@ function process_node!(compact::IncrementalCompact, result::Vector{Any}, # type equality. We may want to consider using == in either a separate pass or if # performance turns out ok stmt = renumber_ssa2!(stmt, ssa_rename, used_ssas, late_fixup, result_idx, do_rename_ssa)::PiNode - if !isa(stmt.val, AnySSAValue) && !isa(stmt.val, GlobalRef) - valtyp = isa(stmt.val, QuoteNode) ? typeof(stmt.val.value) : typeof(stmt.val) + pi_val = stmt.val + if isa(pi_val, SSAValue) + if stmt.typ === compact.result_types[pi_val.id] + ssa_rename[idx] = pi_val + return result_idx + end + elseif !isa(pi_val, AnySSAValue) && !isa(pi_val, GlobalRef) + valtyp = isa(pi_val, QuoteNode) ? typeof(pi_val.value) : typeof(pi_val) if valtyp === stmt.typ - ssa_rename[idx] = stmt.val + ssa_rename[idx] = pi_val return result_idx end - elseif isa(stmt.val, SSAValue) && stmt.typ === compact.result_types[stmt.val.id] - ssa_rename[idx] = stmt.val - return result_idx end result[result_idx] = stmt result_idx += 1 @@ -1067,6 +1072,9 @@ function iterate(it::CompactPeekIterator, (idx, aidx, bidx)::NTuple{3, Int}=(it. end function iterate(compact::IncrementalCompact, (idx, active_bb)::Tuple{Int, Int}=(compact.idx, 1)) + # Create label to dodge recursion so that we don't stack overflow + @label restart + old_result_idx = compact.result_idx if idx > length(compact.ir.stmts) && (compact.new_nodes_idx > length(compact.perm)) return nothing @@ -1130,7 +1138,10 @@ function iterate(compact::IncrementalCompact, (idx, active_bb)::Tuple{Int, Int}= active_bb += 1 end compact.idx = idx + 1 - (old_result_idx == compact.result_idx) && return iterate(compact, (idx + 1, active_bb)) + if old_result_idx == compact.result_idx + idx += 1 + @goto restart + end if !isassigned(compact.result, old_result_idx) @assert false end @@ -1143,7 +1154,7 @@ function maybe_erase_unused!(extra_worklist, compact, idx, callback = x->nothing if compact_exprtype(compact, SSAValue(idx)) === Bottom effect_free = false else - effect_free = stmt_effect_free(stmt, compact.result_types[idx], compact, compact.ir.spvals) + effect_free = stmt_effect_free(stmt, compact.result_types[idx], compact, compact.ir.sptypes) end if effect_free for ops in userefs(stmt) @@ -1195,7 +1206,6 @@ function fixup_node(compact::IncrementalCompact, @nospecialize(stmt)) return compact.ssa_rename[stmt.id] else urs = userefs(stmt) - urs === () && return stmt for ur in urs val = ur[] if isa(val, NewSSAValue) diff --git a/base/compiler/ssair/legacy.jl b/base/compiler/ssair/legacy.jl index 45a974ab56721e..88bfd419a842b4 100644 --- a/base/compiler/ssair/legacy.jl +++ b/base/compiler/ssair/legacy.jl @@ -1,19 +1,17 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -inflate_ir(ci::CodeInfo) = inflate_ir(ci, Core.svec(), Any[ Any for i = 1:length(ci.slotnames) ]) - function inflate_ir(ci::CodeInfo, linfo::MethodInstance) - spvals = spvals_from_meth_instance(linfo) + sptypes = sptypes_from_meth_instance(linfo) if ci.inferred argtypes, _ = matching_cache_argtypes(linfo, nothing) else - argtypes = Any[ Any for i = 1:length(ci.slotnames) ] + argtypes = Any[ Any for i = 1:length(ci.slotflags) ] end - return inflate_ir(ci, spvals, argtypes) + return inflate_ir(ci, sptypes, argtypes) end -function inflate_ir(ci::CodeInfo, spvals::SimpleVector, argtypes::Vector{Any}) - code = copy_exprargs(ci.code) +function inflate_ir(ci::CodeInfo, sptypes::Vector{Any}, argtypes::Vector{Any}) + code = copy_exprargs(ci.code) # TODO: this is a huge hot-spot for i = 1:length(code) if isa(code[i], Expr) code[i] = normalize_expr(code[i]) @@ -46,14 +44,13 @@ function inflate_ir(ci::CodeInfo, spvals::SimpleVector, argtypes::Vector{Any}) end ssavaluetypes = ci.ssavaluetypes isa Vector{Any} ? copy(ci.ssavaluetypes) : Any[ Any for i = 1:(ci.ssavaluetypes::Int) ] ir = IRCode(code, ssavaluetypes, copy(ci.codelocs), copy(ci.ssaflags), cfg, collect(LineInfoNode, ci.linetable), - argtypes, Any[], spvals) + argtypes, Any[], sptypes) return ir end function replace_code_newstyle!(ci::CodeInfo, ir::IRCode, nargs::Int) @assert isempty(ir.new_nodes) # All but the first `nargs` slots will now be unused - resize!(ci.slotnames, nargs+1) resize!(ci.slotflags, nargs+1) ci.code = ir.stmts ci.codelocs = ir.lines @@ -92,3 +89,6 @@ function replace_code_newstyle!(ci::CodeInfo, ir::IRCode, nargs::Int) end end end + +# used by some tests +inflate_ir(ci::CodeInfo) = inflate_ir(ci, Any[], Any[ Any for i = 1:length(ci.slotflags) ]) diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index d291e85a661b8b..f93e490f566138 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -130,7 +130,7 @@ function simple_walk(compact::IncrementalCompact, @nospecialize(defssa#=::AnySSA return defssa end if isa(def.val, SSAValue) - if isa(defssa, OldSSAValue) && !already_inserted(compact, defssa) + if is_old(compact, defssa) defssa = OldSSAValue(def.val.id) else defssa = def.val @@ -140,7 +140,11 @@ function simple_walk(compact::IncrementalCompact, @nospecialize(defssa#=::AnySSA end elseif isa(def, AnySSAValue) pi_callback(def, defssa) - defssa = def + if isa(def, SSAValue) && is_old(compact, defssa) + defssa = OldSSAValue(def.id) + else + defssa = def + end elseif isa(def, Union{PhiNode, PhiCNode, Expr, GlobalRef}) return defssa else @@ -187,21 +191,21 @@ function walk_to_defs(compact::IncrementalCompact, @nospecialize(defssa), @nospe def = compact[defssa] if isa(def, PhiNode) push!(visited_phinodes, defssa) - possible_predecessors = let def=def, typeconstraint=typeconstraint - collect(Iterators.filter(1:length(def.edges)) do n - isassigned(def.values, n) || return false - val = def.values[n] - if isa(defssa, OldSSAValue) && isa(val, SSAValue) - val = OldSSAValue(val.id) - end - edge_typ = widenconst(compact_exprtype(compact, val)) - return typeintersect(edge_typ, typeconstraint) !== Union{} - end) + possible_predecessors = Int[] + for n in 1:length(def.edges) + isassigned(def.values, n) || continue + val = def.values[n] + if is_old(compact, defssa) && isa(val, SSAValue) + val = OldSSAValue(val.id) + end + edge_typ = widenconst(compact_exprtype(compact, val)) + typeintersect(edge_typ, typeconstraint) === Union{} && continue + push!(possible_predecessors, n) end for n in possible_predecessors pred = def.edges[n] val = def.values[n] - if isa(defssa, OldSSAValue) && isa(val, SSAValue) + if is_old(compact, defssa) && isa(val, SSAValue) val = OldSSAValue(val.id) end if isa(val, AnySSAValue) @@ -281,7 +285,7 @@ function lift_leaves(compact::IncrementalCompact, @nospecialize(stmt), end if is_tuple_call(compact, def) && isa(field, Int) && 1 <= field < length(def.args) lifted = def.args[1+field] - if isa(leaf, OldSSAValue) && isa(lifted, SSAValue) + if is_old(compact, leaf) && isa(lifted, SSAValue) lifted = OldSSAValue(lifted.id) end if isa(lifted, GlobalRef) || isa(lifted, Expr) @@ -292,7 +296,7 @@ function lift_leaves(compact::IncrementalCompact, @nospecialize(stmt), lifted_leaves[leaf_key] = RefValue{Any}(lifted) continue elseif isexpr(def, :new) - typ = types(compact)[leaf] + typ = widenconst(types(compact)[leaf]) if isa(typ, UnionAll) typ = unwrap_unionall(typ) end @@ -320,7 +324,7 @@ function lift_leaves(compact::IncrementalCompact, @nospecialize(stmt), compact[leaf] = def end lifted = def.args[1+field] - if isa(leaf, OldSSAValue) && isa(lifted, SSAValue) + if is_old(compact, leaf) && isa(lifted, SSAValue) lifted = OldSSAValue(lifted.id) end if isa(lifted, GlobalRef) || isa(lifted, Expr) @@ -339,7 +343,7 @@ function lift_leaves(compact::IncrementalCompact, @nospecialize(stmt), # N.B.: This can be a bit dangerous because it can lead to # infinite loops if we accidentally insert a node just ahead # of where we are - if isa(leaf, OldSSAValue) && (isa(field, Int) || isa(field, Symbol)) + if is_old(compact, leaf) && (isa(field, Int) || isa(field, Symbol)) (isa(typ, DataType) && (!typ.abstract)) || return nothing @assert !typ.mutable # If there's the potential for an undefref error on access, we cannot insert a getfield @@ -425,6 +429,12 @@ struct LiftedPhi need_argupdate::Bool end +function is_old(compact, @nospecialize(old_node_ssa)) + isa(old_node_ssa, OldSSAValue) && + !is_pending(compact, old_node_ssa) && + !already_inserted(compact, old_node_ssa) +end + function perform_lifting!(compact::IncrementalCompact, visited_phinodes::Vector{Any}, @nospecialize(cache_key), lifting_cache::IdDict{Pair{AnySSAValue, Any}, AnySSAValue}, @@ -455,7 +465,7 @@ function perform_lifting!(compact::IncrementalCompact, isassigned(old_node.values, i) || continue val = old_node.values[i] orig_val = val - if isa(old_node_ssa, OldSSAValue) && !is_pending(compact, old_node_ssa) && !already_inserted(compact, old_node_ssa) && isa(val, SSAValue) + if is_old(compact, old_node_ssa) && isa(val, SSAValue) val = OldSSAValue(val.id) end if isa(val, Union{NewSSAValue, SSAValue, OldSSAValue}) @@ -520,8 +530,10 @@ function getfield_elim_pass!(ir::IRCode, domtree::DomTree) # Step 1: Check whether the statement we're looking at is a getfield/setfield! if is_known_call(stmt, setfield!, compact) is_setfield = true + 4 <= length(stmt.args) <= 5 || continue elseif is_known_call(stmt, getfield, compact) is_getfield = true + 3 <= length(stmt.args) <= 4 || continue elseif is_known_call(stmt, isa, compact) # TODO continue @@ -688,10 +700,14 @@ function getfield_elim_pass!(ir::IRCode, domtree::DomTree) compact[idx] = val === nothing ? nothing : val.x end - # Copy the use count, `finish` may modify it and for our predicate - # below we need it consistent with the state of the IR here. + + non_dce_finish!(compact) + # Copy the use count, `simple_dce!` may modify it and for our predicate + # below we need it consistent with the state of the IR here (after tracking + # phi node arguments, but before dce). used_ssas = copy(compact.used_ssas) - ir = finish(compact) + simple_dce!(compact) + ir = complete(compact) # Now go through any mutable structs and see which ones we can eliminate for (idx, (intermediaries, defuse)) in defuses intermediaries = collect(intermediaries) @@ -813,15 +829,16 @@ function adce_erase!(phi_uses, extra_worklist, compact, idx) end end -function count_uses(stmt, uses) +function count_uses(@nospecialize(stmt), uses::Vector{Int}) for ur in userefs(stmt) - if isa(ur[], SSAValue) - uses[ur[].id] += 1 + use = ur[] + if isa(use, SSAValue) + uses[use.id] += 1 end end end -function mark_phi_cycles(compact, safe_phis, phi) +function mark_phi_cycles(compact::IncrementalCompact, safe_phis::BitSet, phi::Int) worklist = Int[] push!(worklist, phi) while !isempty(worklist) @@ -848,7 +865,7 @@ function adce_pass!(ir::IRCode) end non_dce_finish!(compact) for phi in all_phis - count_uses(compact.result[phi], phi_uses) + count_uses(compact.result[phi]::PhiNode, phi_uses) end # Perform simple DCE for unused values extra_worklist = Int[] @@ -864,7 +881,7 @@ function adce_pass!(ir::IRCode) changed = true while changed changed = false - safe_phis = IdSet{Int}() + safe_phis = BitSet() for phi in all_phis # Save any phi cycles that have non-phi uses if compact.used_ssas[phi] - phi_uses[phi] != 0 @@ -882,7 +899,7 @@ function adce_pass!(ir::IRCode) end end end - complete(compact) + return complete(compact) end function type_lift_pass!(ir::IRCode) diff --git a/base/compiler/ssair/queries.jl b/base/compiler/ssair/queries.jl index b66504af1c8251..d0155002504f62 100644 --- a/base/compiler/ssair/queries.jl +++ b/base/compiler/ssair/queries.jl @@ -3,38 +3,41 @@ """ Determine whether a statement is side-effect-free, i.e. may be removed if it has no uses. """ -function stmt_effect_free(@nospecialize(stmt), @nospecialize(rt), src, spvals::SimpleVector) - isa(stmt, Union{PiNode, PhiNode}) && return true - isa(stmt, Union{ReturnNode, GotoNode, GotoIfNot}) && return false - isa(stmt, GlobalRef) && return isdefined(stmt.mod, stmt.name) +function stmt_effect_free(@nospecialize(stmt), @nospecialize(rt), src, sptypes::Vector{Any}) + isa(stmt, PiNode) && return true + isa(stmt, PhiNode) && return true + isa(stmt, ReturnNode) && return false + isa(stmt, GotoNode) && return false + isa(stmt, GotoIfNot) && return false isa(stmt, Slot) && return false # Slots shouldn't occur in the IR at this point, but let's be defensive here + isa(stmt, GlobalRef) && return isdefined(stmt.mod, stmt.name) if isa(stmt, Expr) e = stmt::Expr head = e.head if head === :static_parameter - etyp = sparam_type(spvals[e.args[1]]) + etyp = sptypes[e.args[1]] # if we aren't certain enough about the type, it might be an UndefVarError at runtime - return isa(etyp, Const) || issingletontype(widenconst(etyp)) + return isa(etyp, Const) end ea = e.args if head === :call - f = argextype(ea[1], src, spvals) + f = argextype(ea[1], src, sptypes) f = singleton_type(f) f === nothing && return false is_return_type(f) && return true if isa(f, IntrinsicFunction) - is_pure_intrinsic_infer(f) || return false + intrinsic_effect_free_if_nothrow(f) || return false return intrinsic_nothrow(f) || intrinsic_nothrow(f, - Any[argextype(ea[i], src, spvals) for i = 2:length(ea)]) + Any[argextype(ea[i], src, sptypes) for i = 2:length(ea)]) end contains_is(_PURE_BUILTINS, f) && return true contains_is(_PURE_OR_ERROR_BUILTINS, f) || return false rt === Bottom && return false - return _builtin_nothrow(f, Any[argextype(ea[i], src, spvals) for i = 2:length(ea)], rt) + return _builtin_nothrow(f, Any[argextype(ea[i], src, sptypes) for i = 2:length(ea)], rt) elseif head === :new a = ea[1] - typ = argextype(a, src, spvals) + typ = argextype(a, src, sptypes) # `Expr(:new)` of unknown type could raise arbitrary TypeError. typ, isexact = instanceof_tfunc(typ) isexact || return false @@ -42,7 +45,7 @@ function stmt_effect_free(@nospecialize(stmt), @nospecialize(rt), src, spvals::S typ = typ::DataType fieldcount(typ) >= length(ea) - 1 || return false for fld_idx in 1:(length(ea) - 1) - eT = argextype(ea[fld_idx + 1], src, spvals) + eT = argextype(ea[fld_idx + 1], src, sptypes) fT = fieldtype(typ, fld_idx) eT ⊑ fT || return false end @@ -50,7 +53,7 @@ function stmt_effect_free(@nospecialize(stmt), @nospecialize(rt), src, spvals::S elseif head === :isdefined || head === :the_exception || head === :copyast || head === :inbounds || head === :boundscheck return true else - # e.g. :simdloop + # e.g. :loopinfo return false end end @@ -71,10 +74,10 @@ function compact_exprtype(compact::IncrementalCompact, @nospecialize(value)) elseif isa(value, Argument) return compact.ir.argtypes[value.n] end - return argextype(value, compact.ir, compact.ir.spvals) + return argextype(value, compact.ir, compact.ir.sptypes) end -is_tuple_call(ir::IRCode, @nospecialize(def)) = isa(def, Expr) && is_known_call(def, tuple, ir, ir.spvals) +is_tuple_call(ir::IRCode, @nospecialize(def)) = isa(def, Expr) && is_known_call(def, tuple, ir, ir.sptypes) is_tuple_call(compact::IncrementalCompact, @nospecialize(def)) = isa(def, Expr) && is_known_call(def, tuple, compact) function is_known_call(e::Expr, @nospecialize(func), src::IncrementalCompact) if e.head !== :call diff --git a/base/compiler/ssair/show.jl b/base/compiler/ssair/show.jl index d51b086c8de40d..fe2e244a8821cc 100644 --- a/base/compiler/ssair/show.jl +++ b/base/compiler/ssair/show.jl @@ -153,6 +153,12 @@ function default_expr_type_printer(io::IO, @nospecialize(typ), used::Bool) nothing end +normalize_method_name(m::Method) = m.name +normalize_method_name(m::MethodInstance) = (m.def::Method).name +normalize_method_name(m::Symbol) = m +normalize_method_name(m) = Symbol("") +@noinline method_name(m::LineInfoNode) = normalize_method_name(m.method) + # converts the linetable for line numbers # into a list in the form: # 1 outer-most-frame @@ -266,7 +272,7 @@ function compute_ir_line_annotations(code::Union{IRCode, CodeInfo}) # be a line number mismatch in inner most frame. Ignore those if length(last_stack) == length(stack) && first_mismatch == length(stack) last_entry, entry = linetable[last_stack[end]], linetable[stack[end]] - if last_entry.method == entry.method && last_entry.file == entry.file + if method_name(last_entry) === method_name(entry) && last_entry.file === entry.file first_mismatch = nothing end end @@ -299,13 +305,14 @@ function compute_ir_line_annotations(code::Union{IRCode, CodeInfo}) print(buf, "│") end end - print(buf, "╷"^max(0,depth-last_depth-stole_one)) + print(buf, "╷"^max(0, depth - last_depth - stole_one)) if printing_depth != 0 if length(stack) == printing_depth - loc_method = String(linetable[line].method) + loc_method = line else - loc_method = String(linetable[stack[printing_depth+1]].method) + loc_method = stack[printing_depth + 1] end + loc_method = method_name(linetable[loc_method]) end loc_method = string(" "^printing_depth, loc_method) end @@ -326,14 +333,14 @@ Base.show(io::IO, code::IRCode) = show_ir(io, code) lineinfo_disabled(io::IO, linestart::String, lineidx::Int32) = "" -function DILineInfoPrinter(linetable::Vector) +function DILineInfoPrinter(linetable::Vector, showtypes::Bool=false) context = LineInfoNode[] context_depth = Ref(0) indent(s::String) = s^(max(context_depth[], 1) - 1) function emit_lineinfo_update(io::IO, linestart::String, lineidx::Int32) # internal configuration options: linecolor = :yellow - collapse = true + collapse = showtypes ? false : true indent_all = true # convert lineidx to a vector if lineidx < 0 @@ -373,11 +380,11 @@ function DILineInfoPrinter(linetable::Vector) # if so, drop all existing calls to it from the top of the context # AND check if instead the context was previously printed that way # but now has removed the recursive frames - let method = context[nctx].method - if (nctx < nframes && DI[nframes - nctx].method === method) || - (nctx < length(context) && context[nctx + 1].method === method) + let method = method_name(context[nctx]) + if (nctx < nframes && method_name(DI[nframes - nctx]) === method) || + (nctx < length(context) && method_name(context[nctx + 1]) === method) update_line_only = true - while nctx > 0 && context[nctx].method === method + while nctx > 0 && method_name(context[nctx]) === method nctx -= 1 end end @@ -388,9 +395,9 @@ function DILineInfoPrinter(linetable::Vector) # compute the new inlining depth if collapse npops = 1 - let Prev = context[nctx + 1].method + let Prev = method_name(context[nctx + 1]) for i = (nctx + 2):length(context) - Next = context[i].method + Next = method_name(context[i]) Prev === Next || (npops += 1) Prev = Next end @@ -402,9 +409,8 @@ function DILineInfoPrinter(linetable::Vector) if !update_line_only && nctx < nframes let CtxLine = context[nctx + 1], FrameLine = DI[nframes - nctx] - if CtxLine.file == FrameLine.file && - CtxLine.method == FrameLine.method && - CtxLine.mod == FrameLine.mod + if CtxLine.file === FrameLine.file && + method_name(CtxLine) === method_name(FrameLine) update_line_only = true end end @@ -426,12 +432,12 @@ function DILineInfoPrinter(linetable::Vector) if frame.line != typemax(frame.line) && frame.line != 0 print(io, linestart) Base.with_output_color(linecolor, io) do io - print(io, indent("│"), " @ ", frame.file, ":", frame.line, " within `", frame.method, "'") + print(io, indent("│"), " @ ", frame.file, ":", frame.line, " within `", method_name(frame), "'") if collapse - method = frame.method + method = method_name(frame) while nctx < nframes frame = DI[nframes - nctx] - frame.method === method || break + method_name(frame) === method || break nctx += 1 push!(context, frame) print(io, " @ ", frame.file, ":", frame.line) @@ -444,23 +450,33 @@ function DILineInfoPrinter(linetable::Vector) # now print the rest of the new frames while nctx < nframes frame = DI[nframes - nctx] + nctx += 1 + started = false + if showtypes && !isa(frame.method, Symbol) && nctx != 1 + print(io, linestart) + Base.with_output_color(linecolor, io) do io + print(io, indent("│")) + print(io, "┌ invoke ", frame.method) + println(io) + end + started = true + end print(io, linestart) Base.with_output_color(linecolor, io) do io print(io, indent("│")) - nctx += 1 push!(context, frame) context_depth[] += 1 - nctx != 1 && print(io, "┌") + nctx != 1 && print(io, started ? "│" : "┌") print(io, " @ ", frame.file) if frame.line != typemax(frame.line) && frame.line != 0 print(io, ":", frame.line) end - print(io, " within `", frame.method, "'") + print(io, " within `", method_name(frame), "'") if collapse - method = frame.method + method = method_name(frame) while nctx < nframes frame = DI[nframes - nctx] - frame.method === method || break + method_name(frame) === method || break nctx += 1 push!(context, frame) print(io, " @ ", frame.file, ":", frame.line) @@ -471,10 +487,10 @@ function DILineInfoPrinter(linetable::Vector) end # FOR DEBUGGING `collapse`: # this double-checks the computation of context_depth - #let Prev = context[1].method, + #let Prev = method_name(context[1]), # depth2 = 1 # for i = 2:nctx - # Next = context[i].method + # Next = method_name(context[i]) # (collapse && Prev === Next) || (depth2 += 1) # Prev = Next # end @@ -565,7 +581,7 @@ function show_ir(io::IO, code::IRCode, expr_type_printer=default_expr_type_print printstyled(io, "\e[$(start_column)G$(rail)\e[1G", color = :light_black) print(io, bb_guard_rail) ssa_guard = " "^(maxlength_idx + 4 + (i - 1)) - entry_label = "$(ssa_guard)$(entry.method) at $(entry.file):$(entry.line) " + entry_label = "$(ssa_guard)$(method_name(entry)) at $(entry.file):$(entry.line) " hline = string("─"^(start_column-length(entry_label)-length(bb_guard_rail)+max_depth-i), "┐") printstyled(io, string(entry_label, hline), "\n"; color=:light_black) bb_guard_rail = bb_guard_rail_cont diff --git a/base/compiler/ssair/slot2ssa.jl b/base/compiler/ssair/slot2ssa.jl index c1dd86e1fa20fe..3cfee355bea039 100644 --- a/base/compiler/ssair/slot2ssa.jl +++ b/base/compiler/ssair/slot2ssa.jl @@ -45,7 +45,7 @@ end @inline slot_id(s) = isa(s, SlotNumber) ? (s::SlotNumber).id : (s::TypedSlot).id function scan_slot_def_use(nargs::Int, ci::CodeInfo, code::Vector{Any}) - nslots = length(ci.slotnames) + nslots = length(ci.slotflags) result = SlotInfo[SlotInfo() for i = 1:nslots] # Set defs for arguments for var in result[1:(1+nargs)] @@ -211,14 +211,14 @@ struct DelayedTyp end # maybe use expr_type? -function typ_for_val(@nospecialize(x), ci::CodeInfo, spvals::SimpleVector, idx::Int, slottypes::Vector{Any}) +function typ_for_val(@nospecialize(x), ci::CodeInfo, sptypes::Vector{Any}, idx::Int, slottypes::Vector{Any}) if isa(x, Expr) if x.head === :static_parameter - return sparam_type(spvals[x.args[1]]) + return sptypes[x.args[1]] elseif x.head === :boundscheck return Bool elseif x.head === :copyast - return typ_for_val(x.args[1], ci, spvals, idx, slottypes) + return typ_for_val(x.args[1], ci, sptypes, idx, slottypes) end return ci.ssavaluetypes[idx] end @@ -545,7 +545,7 @@ function compute_live_ins(cfg::CFG, defuse) BlockLiveness(bb_defs, bb_uses) end -function recompute_type(node::Union{PhiNode, PhiCNode}, ci::CodeInfo, ir::IRCode, spvals::SimpleVector, slottypes::Vector{Any}) +function recompute_type(node::Union{PhiNode, PhiCNode}, ci::CodeInfo, ir::IRCode, sptypes::Vector{Any}, slottypes::Vector{Any}) new_typ = Union{} for i = 1:length(node.values) if isa(node, PhiNode) && !isassigned(node.values, i) @@ -554,7 +554,7 @@ function recompute_type(node::Union{PhiNode, PhiCNode}, ci::CodeInfo, ir::IRCode end continue end - typ = typ_for_val(node.values[i], ci, spvals, -1, slottypes) + typ = typ_for_val(node.values[i], ci, sptypes, -1, slottypes) was_maybe_undef = false if isa(typ, MaybeUndef) typ = typ.typ @@ -569,7 +569,7 @@ function recompute_type(node::Union{PhiNode, PhiCNode}, ci::CodeInfo, ir::IRCode return new_typ end -function construct_ssa!(ci::CodeInfo, code::Vector{Any}, ir::IRCode, domtree::DomTree, defuse, nargs::Int, spvals::SimpleVector, +function construct_ssa!(ci::CodeInfo, code::Vector{Any}, ir::IRCode, domtree::DomTree, defuse, nargs::Int, sptypes::Vector{Any}, slottypes::Vector{Any}) cfg = ir.cfg left = Int[] @@ -580,13 +580,13 @@ function construct_ssa!(ci::CodeInfo, code::Vector{Any}, ir::IRCode, domtree::Do end end - exc_handlers = IdDict{Int, Int}() + exc_handlers = IdDict{Int, Tuple{Int, Int}}() # Record the correct exception handler for all cricitcal sections for (enter_block, exc) in catch_entry_blocks - exc_handlers[enter_block+1] = exc + exc_handlers[enter_block+1] = (enter_block, exc) # TODO: Cut off here if the terminator is a leave corresponding to this enter for block in dominated(domtree, enter_block+1) - exc_handlers[block] = exc + exc_handlers[block] = (enter_block, exc) end end @@ -615,7 +615,7 @@ function construct_ssa!(ci::CodeInfo, code::Vector{Any}, ir::IRCode, domtree::Do fixup_uses!(ir, ci, code, slot.uses, idx, nothing) else val = code[slot.defs[]].args[2] - typ = typ_for_val(val, ci, spvals, slot.defs[], slottypes) + typ = typ_for_val(val, ci, sptypes, slot.defs[], slottypes) ssaval = SSAValue(make_ssa!(ci, code, slot.defs[], idx, typ)) fixup_uses!(ir, ci, code, slot.uses, idx, ssaval) end @@ -654,13 +654,13 @@ function construct_ssa!(ci::CodeInfo, code::Vector{Any}, ir::IRCode, domtree::Do undef_token else SSAValue(-1) - end for x in 1:length(ci.slotnames) + end for x in 1:length(ci.slotflags) ] worklist = Tuple{Int, Int, Vector{Any}}[(1, 0, initial_incoming_vals)] visited = BitSet() type_refine_phi = BitSet() @timeit "SSA Rename" while !isempty(worklist) - (item, pred, incoming_vals) = pop!(worklist) + (item::Int, pred, incoming_vals) = pop!(worklist) # Rename existing phi nodes first, because their uses occur on the edge # TODO: This isn't necessary if inlining stops replacing arguments by slots. for idx in cfg.blocks[item].stmts @@ -697,7 +697,7 @@ function construct_ssa!(ci::CodeInfo, code::Vector{Any}, ir::IRCode, domtree::Do if isa(incoming_val, NewSSAValue) push!(type_refine_phi, ssaval.id) end - typ = incoming_val == undef_token ? MaybeUndef(Union{}) : typ_for_val(incoming_val, ci, spvals, -1, slottypes) + typ = incoming_val == undef_token ? MaybeUndef(Union{}) : typ_for_val(incoming_val, ci, sptypes, -1, slottypes) old_entry = ir.new_nodes[ssaval.id] if isa(typ, DelayedTyp) push!(type_refine_phi, ssaval.id) @@ -743,7 +743,7 @@ function construct_ssa!(ci::CodeInfo, code::Vector{Any}, ir::IRCode, domtree::Do if isexpr(stmt, :(=)) && isa(stmt.args[1], SlotNumber) id = slot_id(stmt.args[1]) val = stmt.args[2] - typ = typ_for_val(val, ci, spvals, idx, slottypes) + typ = typ_for_val(val, ci, 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 @@ -752,8 +752,9 @@ function construct_ssa!(ci::CodeInfo, code::Vector{Any}, ir::IRCode, domtree::Do code[idx] = nothing incoming_vals[id] = undef_token end - if haskey(exc_handlers, item) - exc = exc_handlers[item] + eidx = item + while haskey(exc_handlers, eidx) + (eidx, exc) = exc_handlers[eidx] cidx = findfirst(x->slot_id(x[1]) == id, phicnodes[exc]) if cidx !== nothing node = UpsilonNode(incoming_vals[id]) @@ -825,7 +826,7 @@ function construct_ssa!(ci::CodeInfo, code::Vector{Any}, ir::IRCode, domtree::Do new_idx = ssa.id node = ir.new_nodes[new_idx] for i = 1:length(node.node.values) - orig_typ = typ = typ_for_val(node.node.values[i], ci, spvals, -1, slottypes) + orig_typ = typ = typ_for_val(node.node.values[i], ci, sptypes, -1, slottypes) @assert !isa(typ, MaybeUndef) while isa(typ, DelayedTyp) typ = types(ir)[typ.phi::NewSSAValue] @@ -843,7 +844,7 @@ function construct_ssa!(ci::CodeInfo, code::Vector{Any}, ir::IRCode, domtree::Do changed = false for new_idx in type_refine_phi node = ir.new_nodes[new_idx] - new_typ = recompute_type(node.node, ci, ir, spvals, slottypes) + new_typ = recompute_type(node.node, ci, ir, sptypes, slottypes) if !(node.typ ⊑ new_typ) || !(new_typ ⊑ node.typ) ir.new_nodes[new_idx] = NewNode(node.pos, node.attach_after, new_typ, node.node, node.line) changed = true diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 27ca1f4bc2f129..38d5321b3fca79 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -95,28 +95,14 @@ 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) -function fptoui_tfunc(@nospecialize(x)) - T = widenconst(x) - T === Float64 && return UInt64 - T === Float32 && return UInt32 - T === Float16 && return UInt16 - return Any -end -function fptosi_tfunc(@nospecialize(x)) - T = widenconst(x) - T === Float64 && return Int64 - T === Float32 && return Int32 - T === Float16 && return Int16 - return Any -end ## conversion ## 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, 1, 2, fptoui_tfunc, 1) -add_tfunc(fptosi, 1, 2, fptosi_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) @@ -307,12 +293,14 @@ end add_tfunc(isdefined, 1, 2, isdefined_tfunc, 1) function sizeof_nothrow(@nospecialize(x)) if isa(x, Const) - if !isa(x, Type) + x = x.val + if !isa(x, Type) || x === DataType return true end - x = x.val elseif isa(x, Conditional) return true + else + x = widenconst(x) end isconstType(x) && (x = x.parameters[1]) if isa(x, Union) @@ -339,21 +327,23 @@ function sizeof_tfunc(@nospecialize(x),) isa(x, Const) && return _const_sizeof(x.val) isa(x, Conditional) && return _const_sizeof(Bool) isconstType(x) && return _const_sizeof(x.parameters[1]) + x = widenconst(x) x !== DataType && isconcretetype(x) && return _const_sizeof(x) return Int end add_tfunc(Core.sizeof, 1, 1, sizeof_tfunc, 0) -add_tfunc(nfields, 1, 1, - function (@nospecialize(x),) - isa(x, Const) && return Const(nfields(x.val)) - isa(x, Conditional) && return Const(0) - if isa(x, DataType) && !x.abstract && !(x.name === Tuple.name && isvatuple(x)) - if !(x.name === _NAMEDTUPLE_NAME && !isconcretetype(x)) - return Const(length(x.types)) - end +function nfields_tfunc(@nospecialize(x)) + isa(x, Const) && return Const(nfields(x.val)) + isa(x, Conditional) && return Const(0) + x = widenconst(x) + if isa(x, DataType) && !x.abstract && !(x.name === Tuple.name && isvatuple(x)) + if !(x.name === _NAMEDTUPLE_NAME && !isconcretetype(x)) + return Const(length(x.types)) end - return Int - end, 0) + end + return Int +end +add_tfunc(nfields, 1, 1, nfields_tfunc, 0) add_tfunc(Core._expr, 1, INT_INF, (@nospecialize args...)->Expr, 100) function typevar_tfunc(@nospecialize(n), @nospecialize(lb_arg), @nospecialize(ub_arg)) lb = Union{} @@ -419,6 +409,23 @@ add_tfunc(pointerref, 3, 3, end, 4) add_tfunc(pointerset, 4, 4, (@nospecialize(a), @nospecialize(v), @nospecialize(i), @nospecialize(align)) -> a, 5) +# more accurate typeof_tfunc for vararg tuples abstract only in length +function typeof_concrete_vararg(t::DataType) + np = length(t.parameters) + for i = 1:np + p = t.parameters[i] + if i == np && isvarargtype(p) + pp = unwrap_unionall(p) + if isconcretetype(pp.parameters[1]) && pp.parameters[2] isa TypeVar + return rewrap_unionall(Type{Tuple{t.parameters[1:np-1]..., pp}}, p) + end + elseif !isconcretetype(p) + break + end + end + return nothing +end + function typeof_tfunc(@nospecialize(t)) isa(t, Const) && return Const(typeof(t.val)) t = widenconst(t) @@ -426,28 +433,40 @@ function typeof_tfunc(@nospecialize(t)) tp = t.parameters[1] if issingletontype(tp) return Const(typeof(tp)) - else - return Type end elseif isa(t, DataType) - if isconcretetype(t) || isvarargtype(t) + if isconcretetype(t) return Const(t) elseif t === Any return DataType else + if t.name === Tuple.name + tt = typeof_concrete_vararg(t) + tt === nothing || return tt + end return Type{<:t} end elseif isa(t, Union) a = widenconst(typeof_tfunc(t.a)) b = widenconst(typeof_tfunc(t.b)) return Union{a, b} - elseif isa(t, TypeVar) && !(Any <: t.ub) + elseif isa(t, TypeVar) && !(Any === t.ub) return typeof_tfunc(t.ub) elseif isa(t, UnionAll) - return rewrap_unionall(widenconst(typeof_tfunc(unwrap_unionall(t))), t) - else - return DataType # typeof(anything)::DataType + u = unwrap_unionall(t) + if isa(u, DataType) && !u.abstract + if u.name === Tuple.name + uu = typeof_concrete_vararg(u) + if uu !== nothing + return rewrap_unionall(uu, t) + end + else + return rewrap_unionall(Type{u}, t) + end + end + return rewrap_unionall(widenconst(typeof_tfunc(u)), t) end + return DataType # typeof(anything)::DataType end add_tfunc(typeof, 1, 1, typeof_tfunc, 0) @@ -484,12 +503,14 @@ function isa_tfunc(@nospecialize(v), @nospecialize(tt)) if isexact && isnotbrokensubtype(v, t) return Const(true) end - elseif isa(v, Const) || isa(v, Conditional) || isdispatchelem(v) - # this tests for knowledge of a leaftype appearing on the LHS - # (ensuring the isa is precise) - return Const(false) else + if isa(v, Const) || isa(v, Conditional) + # this and the `isdispatchelem` below test for knowledge of a + # leaftype appearing on the LHS (ensuring the isa is precise) + return Const(false) + end v = widenconst(v) + isdispatchelem(v) && return Const(false) if typeintersect(v, t) === Bottom # similar to `isnotbrokensubtype` check above, `typeintersect(v, t)` # can't be trusted for kind types so we do an extra check here @@ -695,9 +716,12 @@ function getfield_tfunc(@nospecialize(s00), @nospecialize(name)) end end s = typeof(sv) - elseif isa(s, PartialTuple) + elseif isa(s, PartialStruct) if isa(name, Const) nv = name.val + if isa(nv, Symbol) + nv = fieldindex(widenconst(s), nv, false) + end if isa(nv, Int) && 1 <= nv <= length(s.fields) return s.fields[nv] end @@ -928,6 +952,9 @@ function apply_type_nothrow(argtypes::Array{Any, 1}, @nospecialize(rt)) return true end +const _tvarnames = Symbol[:_A, :_B, :_C, :_D, :_E, :_F, :_G, :_H, :_I, :_J, :_K, :_L, :_M, + :_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...) if isa(headtypetype, Const) @@ -985,6 +1012,7 @@ function apply_type_tfunc(@nospecialize(headtypetype), @nospecialize args...) canconst = true tparams = Any[] outervars = Any[] + varnamectr = 1 for i = 1:largs ai = widenconditional(args[i]) if isType(ai) @@ -1023,7 +1051,9 @@ function apply_type_tfunc(@nospecialize(headtypetype), @nospecialize args...) # ai = ai.body # end else - v = TypeVar(:_) + tvname = varnamectr <= length(_tvarnames) ? _tvarnames[varnamectr] : :_Z + varnamectr += 1 + v = TypeVar(tvname) push!(tparams, v) push!(outervars, v) end @@ -1064,6 +1094,7 @@ function invoke_tfunc(@nospecialize(ft), @nospecialize(types), @nospecialize(arg if entry === nothing return Any end + # XXX: update_valid_age!(min_valid[1], max_valid[1], sv) meth = entry.func (ti, env) = ccall(:jl_type_intersection_with_env, Any, (Any, Any), argtype, meth.sig)::SimpleVector rt, edge = typeinf_edge(meth::Method, ti, env, sv) @@ -1112,7 +1143,7 @@ function tuple_tfunc(atypes::Vector{Any}) typ = Tuple{params...} # replace a singleton type with its equivalent Const object isdefined(typ, :instance) && return Const(typ.instance) - return anyinfo ? PartialTuple(typ, atypes) : typ + return anyinfo ? PartialStruct(typ, atypes) : typ end function array_type_undefable(@nospecialize(a)) @@ -1156,7 +1187,7 @@ function _builtin_nothrow(@nospecialize(f), argtypes::Array{Any,1}, @nospecializ # Check that the element type is compatible with the element we're assigning (argtypes[3] ⊑ a.parameters[1]::Type) || return false return true - elseif f === arrayref + elseif f === arrayref || f === const_arrayref return array_builtin_common_nothrow(argtypes, 3) elseif f === Core._expr length(argtypes) >= 1 || return false @@ -1176,6 +1207,9 @@ function _builtin_nothrow(@nospecialize(f), argtypes::Array{Any,1}, @nospecializ elseif f === isa length(argtypes) == 2 || return false return argtypes[2] ⊑ Type + elseif f === (<:) + length(argtypes) == 2 || return false + return argtypes[1] ⊑ Type && argtypes[2] ⊑ Type elseif f === UnionAll return length(argtypes) == 2 && (argtypes[1] ⊑ TypeVar && argtypes[2] ⊑ Type) @@ -1213,7 +1247,7 @@ function builtin_tfunction(@nospecialize(f), argtypes::Array{Any,1}, return Bottom end return argtypes[2] - elseif f === arrayref + elseif f === arrayref || f === const_arrayref if length(argtypes) < 3 isva && return Any return Bottom @@ -1314,6 +1348,13 @@ function intrinsic_nothrow(f::IntrinsicFunction, argtypes::Array{Any, 1}) return den_val !== -1 || (isa(argtypes[1], Const) && argtypes[1].val !== typemin(typeof(den_val))) end + if f === Intrinsics.pointerref + # Nothrow as long as the types are ok. N.B.: dereferencability is not + # modeled here, but can cause errors (e.g. ReadOnlyMemoryError). We follow LLVM here + # in that it is legal to remove unused non-volatile loads. + length(argtypes) == 3 || return false + return argtypes[1] ⊑ Ptr && argtypes[2] ⊑ Int && argtypes[3] ⊑ Int + end return true end @@ -1335,11 +1376,11 @@ function return_type_tfunc(argtypes::Vector{Any}, vtypes::VarTable, sv::Inferenc end astype = argtypes_to_type(argtypes_vec) if isa(aft, Const) - rt = abstract_call(aft.val, (), argtypes_vec, vtypes, sv) + rt = abstract_call(aft.val, nothing, argtypes_vec, vtypes, sv, -1) elseif isconstType(aft) - rt = abstract_call(aft.parameters[1], (), argtypes_vec, vtypes, sv) + rt = abstract_call(aft.parameters[1], nothing, argtypes_vec, vtypes, sv, -1) else - rt = abstract_call_gf_by_type(nothing, argtypes_vec, astype, sv) + rt = abstract_call_gf_by_type(nothing, argtypes_vec, astype, sv, -1) end if isa(rt, Const) # output was computed to be constant @@ -1361,7 +1402,7 @@ function return_type_tfunc(argtypes::Vector{Any}, vtypes::VarTable, sv::Inferenc end end end - return NOT_FOUND + return nothing end # N.B.: typename maps type equivalence classes to a single value diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index cdf56df177d09d..12290f4dfc780c 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -1,7 +1,5 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -const COMPILER_TEMP_SYM = Symbol("#temp#") - # build (and start inferring) the inference frame for the linfo function typeinf(result::InferenceResult, cached::Bool, params::Params) frame = InferenceState(result, cached, params) @@ -56,21 +54,27 @@ function typeinf(frame::InferenceState) end end end - if cached - for caller in results - cache_result(caller, min_valid, max_valid) - end - end end - # if we aren't cached, we don't need this edge - # but our caller might, so let's just make it anyways - for caller in frames - finalize_backedges(caller) + if max_valid == get_world_counter() + max_valid = typemax(UInt) end - if max_valid == typemax(UInt) - for caller in frames - store_backedges(caller) + for caller in frames + caller.min_valid = min_valid + caller.max_valid = max_valid + caller.src.min_world = min_valid % Int + caller.src.max_world = max_valid % Int + if cached + cache_result(caller.result, min_valid, max_valid) + end + if max_valid == typemax(UInt) + # if we aren't cached, we don't need this edge + # but our caller might, so let's just make it anyways + for caller in frames + store_backedges(caller) + end end + # finalize and record the linfo result + caller.inferred = true end return true end @@ -86,33 +90,28 @@ function cache_result(result::InferenceResult, min_valid::UInt, max_valid::UInt) end # check if the existing linfo metadata is also sufficient to describe the current inference result - # to decide if it is worth caching it again (which would also clear any generated code) + # to decide if it is worth caching this already_inferred = !result.linfo.inInference - if isdefined(result.linfo, :inferred) - inf = result.linfo.inferred - if !isa(inf, CodeInfo) || (inf::CodeInfo).inferred - if min_world(result.linfo) == min_valid && max_world(result.linfo) == max_valid - already_inferred = true - end - end + if inf_for_methodinstance(result.linfo, min_valid, max_valid) isa CodeInstance + already_inferred = true end - # don't store inferred code if we've decided to interpret this function - if !already_inferred && invoke_api(result.linfo) != 4 + # TODO: also don't store inferred code if we've previously decided to interpret this function + if !already_inferred inferred_result = result.src if inferred_result isa Const # use constant calling convention - inferred_const = (result.src::Const).val + rettype_const = (result.src::Const).val const_flags = 0x3 else if isa(result.result, Const) - inferred_const = (result.result::Const).val + rettype_const = (result.result::Const).val const_flags = 0x2 elseif isconstType(result.result) - inferred_const = result.result.parameters[1] + rettype_const = result.result.parameters[1] const_flags = 0x2 else - inferred_const = nothing + rettype_const = nothing const_flags = 0x00 end if !toplevel && inferred_result isa CodeInfo @@ -121,6 +120,9 @@ function cache_result(result::InferenceResult, min_valid::UInt, max_valid::UInt) ccall(:jl_isa_compileable_sig, Int32, (Any, Any), result.linfo.specTypes, def) != 0) if cache_the_tree # compress code for non-toplevel thunks + nslots = length(inferred_result.slotflags) + resize!(inferred_result.slottypes, nslots) + resize!(inferred_result.slotnames, nslots) inferred_result = ccall(:jl_compress_ast, Any, (Any, Any), def, inferred_result) else inferred_result = nothing @@ -130,13 +132,9 @@ function cache_result(result::InferenceResult, min_valid::UInt, max_valid::UInt) if !isa(inferred_result, Union{CodeInfo, Vector{UInt8}}) inferred_result = nothing end - cache = ccall(:jl_set_method_inferred, Ref{MethodInstance}, (Any, Any, Any, Any, Int32, UInt, UInt), - result.linfo, widenconst(result.result), inferred_const, inferred_result, + ccall(:jl_set_method_inferred, Ref{CodeInstance}, (Any, Any, Any, Any, Int32, UInt, UInt), + result.linfo, widenconst(result.result), rettype_const, inferred_result, const_flags, min_valid, max_valid) - if cache !== result.linfo - result.linfo.inInference = false - result.linfo = cache - end end result.linfo.inInference = false nothing @@ -172,24 +170,13 @@ function finish(src::CodeInfo) nothing end -function finalize_backedges(me::InferenceState) - # update all of the (cycle) callers with real backedges - # by traversing the temporary list of backedges - for (i, _) in me.cycle_backedges - add_backedge!(me.linfo, i) - end - - # finalize and record the linfo result - me.inferred = true - nothing -end - -# add the real backedges +# record the backedges function store_backedges(frame::InferenceState) toplevel = !isa(frame.linfo.def, Method) if !toplevel && (frame.cached || frame.parent !== nothing) caller = frame.result.linfo for edges in frame.stmt_edges + edges === nothing && continue i = 1 while i <= length(edges) to = edges[i] @@ -220,6 +207,8 @@ function widen_all_consts!(src::CodeInfo) end end + src.rettype = widenconst(src.rettype) + return src end @@ -307,6 +296,8 @@ function type_annotate!(sv::InferenceState) # compute the required type for each slot # to hold all of the items assigned into it record_slot_assign!(sv) + sv.src.slottypes = sv.slottypes + sv.src.rettype = sv.bestguess # annotate variables load types # remove dead code optimization @@ -314,7 +305,7 @@ function type_annotate!(sv::InferenceState) src = sv.src states = sv.stmt_types nargs = sv.nargs - nslots = length(states[1]) + nslots = length(states[1]::Array{Any,1}) undefs = fill(false, nslots) body = src.code::Array{Any,1} nexpr = length(body) @@ -339,7 +330,7 @@ function type_annotate!(sv::InferenceState) st_i = states[i] expr = body[i] if isa(st_i, VarTable) - # st_i === () => unreached statement (see issue #7836) + # st_i === nothing => unreached statement (see issue #7836) if isa(expr, Expr) annotate_slot_load!(expr, st_i, sv, undefs) elseif isa(expr, Slot) @@ -458,20 +449,14 @@ end # compute (and cache) an inferred AST and return the current best estimate of the result type function typeinf_edge(method::Method, @nospecialize(atypes), sparams::SimpleVector, caller::InferenceState) - code = code_for_method(method, atypes, sparams, caller.params.world) - code === nothing && return Any, nothing - code = code::MethodInstance - if isdefined(code, :inferred) - # return rettype if the code is already inferred - # staged functions make this hard since they have two "inferred" conditions, - # so need to check whether the code itself is also inferred - inf = code.inferred - if !isa(inf, CodeInfo) || (inf::CodeInfo).inferred - if isdefined(code, :inferred_const) - return AbstractEvalConstant(code.inferred_const), code - else - return code.rettype, code - end + mi = specialize_method(method, atypes, sparams)::MethodInstance + code = inf_for_methodinstance(mi, caller.params.world) + if code isa CodeInstance # return existing rettype if the code is already inferred + update_valid_age!(min_world(code), max_world(code), caller) + if isdefined(code, :rettype_const) + return Const(code.rettype_const), mi + else + return code.rettype, mi end end if !caller.cached && caller.parent === nothing @@ -479,28 +464,31 @@ function typeinf_edge(method::Method, @nospecialize(atypes), sparams::SimpleVect # (if we asked resolve_call_cyle, it might instead detect that there is a cycle that it can't merge) frame = false else - frame = resolve_call_cycle!(code, caller) + frame = resolve_call_cycle!(mi, caller) end if frame === false # completely new - code.inInference = true - result = InferenceResult(code) + mi.inInference = true + result = InferenceResult(mi) frame = InferenceState(result, #=cached=#true, caller.params) # always use the cache for edge targets if frame === nothing # can't get the source for this, so we know nothing - code.inInference = false + mi.inInference = false return Any, nothing end if caller.cached || caller.limited # don't involve uncached functions in cycle resolution frame.parent = caller end typeinf(frame) - return widenconst_bestguess(frame.bestguess), frame.inferred ? frame.linfo : nothing + update_valid_age!(frame, caller) + return widenconst_bestguess(frame.bestguess), frame.inferred ? mi : nothing elseif frame === true # unresolvable cycle return Any, nothing end + # return the current knowledge about this cycle frame = frame::InferenceState + update_valid_age!(frame, caller) return widenconst_bestguess(frame.bestguess), nothing end @@ -513,10 +501,9 @@ end # compute an inferred AST and return type function typeinf_code(method::Method, @nospecialize(atypes), sparams::SimpleVector, run_optimizer::Bool, params::Params) - code = code_for_method(method, atypes, sparams, params.world) - code === nothing && return (nothing, Any) + mi = specialize_method(method, atypes, sparams)::MethodInstance ccall(:jl_typeinf_begin, Cvoid, ()) - result = InferenceResult(code) + result = InferenceResult(mi) frame = InferenceState(result, false, params) frame === nothing && return (nothing, Any) if typeinf(frame) && run_optimizer @@ -530,77 +517,76 @@ function typeinf_code(method::Method, @nospecialize(atypes), sparams::SimpleVect end # compute (and cache) an inferred AST and return type -function typeinf_ext(linfo::MethodInstance, params::Params) - method = linfo.def::Method +function typeinf_ext(mi::MethodInstance, params::Params) + method = mi.def::Method for i = 1:2 # test-and-lock-and-test i == 2 && ccall(:jl_typeinf_begin, Cvoid, ()) - if isdefined(linfo, :inferred) + code = inf_for_methodinstance(mi, params.world) + if code isa CodeInstance # see if this code already exists in the cache - # staged functions make this hard since they have two "inferred" conditions, - # so need to check whether the code itself is also inferred - if min_world(linfo) <= params.world <= max_world(linfo) - inf = linfo.inferred - if invoke_api(linfo) == 2 - tree = ccall(:jl_new_code_info_uninit, Ref{CodeInfo}, ()) - tree.code = Any[ Expr(:return, quoted(linfo.inferred_const)) ] - tree.method_for_inference_limit_heuristics = nothing - tree.slotnames = Any[ COMPILER_TEMP_SYM for i = 1:method.nargs ] - tree.slotflags = fill(0x00, Int(method.nargs)) - tree.ssavaluetypes = 0 - tree.codelocs = Int32[1] - tree.linetable = [LineInfoNode(method.module, method.name, method.file, Int(method.line), 0)] - tree.inferred = true - tree.ssaflags = UInt8[] - tree.pure = true - tree.inlineable = true - i == 2 && ccall(:jl_typeinf_end, Cvoid, ()) - return svec(linfo, tree) - elseif isa(inf, CodeInfo) - if inf.inferred - i == 2 && ccall(:jl_typeinf_end, Cvoid, ()) - return svec(linfo, inf) - end - elseif isa(inf, Vector{UInt8}) - inf = uncompressed_ast(linfo.def::Method, inf) - if inf.inferred - i == 2 && ccall(:jl_typeinf_end, Cvoid, ()) - return svec(linfo, inf) - end + inf = code.inferred + if invoke_api(code) == 2 + i == 2 && ccall(:jl_typeinf_end, Cvoid, ()) + tree = ccall(:jl_new_code_info_uninit, Ref{CodeInfo}, ()) + tree.code = Any[ Expr(:return, quoted(code.rettype_const)) ] + nargs = Int(method.nargs) + tree.slotnames = ccall(:jl_uncompress_argnames, Vector{Symbol}, (Any,), method.slot_syms) + tree.slotflags = fill(0x00, nargs) + tree.ssavaluetypes = 1 + tree.codelocs = Int32[1] + tree.linetable = [LineInfoNode(method, method.file, Int(method.line), 0)] + tree.inferred = true + tree.ssaflags = UInt8[0] + tree.pure = true + tree.inlineable = true + tree.parent = mi + tree.rettype = typeof(code.rettype_const) + tree.min_world = code.min_world + tree.max_world = code.max_world + return tree + elseif isa(inf, CodeInfo) + i == 2 && ccall(:jl_typeinf_end, Cvoid, ()) + if !(inf.min_world == code.min_world && + inf.max_world == code.max_world && + inf.rettype === code.rettype) + inf = copy(inf) + inf.min_world = code.min_world + inf.max_world = code.max_world + inf.rettype = code.rettype end + return inf + elseif isa(inf, Vector{UInt8}) + i == 2 && ccall(:jl_typeinf_end, Cvoid, ()) + inf = _uncompressed_ast(code, inf) + return inf end end end - linfo.inInference = true - frame = InferenceState(InferenceResult(linfo), #=cached=#true, params) - frame === nothing && return svec(nothing, nothing) + mi.inInference = true + frame = InferenceState(InferenceResult(mi), #=cached=#true, params) + frame === nothing && return nothing typeinf(frame) ccall(:jl_typeinf_end, Cvoid, ()) - frame.src.inferred || return svec(nothing, nothing) - return svec(frame.result.linfo, frame.src) + frame.src.inferred || return nothing + return frame.src end # compute (and cache) an inferred AST and return the inferred return type function typeinf_type(method::Method, @nospecialize(atypes), sparams::SimpleVector, params::Params) if contains_is(unwrap_unionall(atypes).parameters, Union{}) - return Union{} + return Union{} # don't ask: it does weird and unnecessary things, if it occurs during bootstrap end - code = code_for_method(method, atypes, sparams, params.world) - code === nothing && return nothing - code = code::MethodInstance + mi = specialize_method(method, atypes, sparams)::MethodInstance for i = 1:2 # test-and-lock-and-test i == 2 && ccall(:jl_typeinf_begin, Cvoid, ()) - if isdefined(code, :inferred) + code = inf_for_methodinstance(mi, params.world) + if code isa CodeInstance # see if this rettype already exists in the cache - # staged functions make this hard since they have two "inferred" conditions, - # so need to check whether the code itself is also inferred - inf = code.inferred - if !isa(inf, CodeInfo) || (inf::CodeInfo).inferred - i == 2 && ccall(:jl_typeinf_end, Cvoid, ()) - return code.rettype - end + i == 2 && ccall(:jl_typeinf_end, Cvoid, ()) + return code.rettype end end - frame = InferenceResult(code) + frame = InferenceResult(mi) typeinf(frame, true, params) ccall(:jl_typeinf_end, Cvoid, ()) frame.result isa InferenceState && return nothing @@ -610,9 +596,9 @@ end @timeit function typeinf_ext(linfo::MethodInstance, world::UInt) if isa(linfo.def, Method) # method lambda - infer this specialization via the method cache - return typeinf_ext(linfo, Params(world)) + src = typeinf_ext(linfo, Params(world)) else - src = linfo.inferred::CodeInfo + src = linfo.uninferred::CodeInfo if !src.inferred # toplevel lambda - infer directly ccall(:jl_typeinf_begin, Cvoid, ()) @@ -621,14 +607,12 @@ end frame = InferenceState(result, src, #=cached=#true, Params(world)) typeinf(frame) @assert frame.inferred # TODO: deal with this better - @assert frame.linfo === linfo - linfo.rettype = widenconst(frame.bestguess) src = frame.src end ccall(:jl_typeinf_end, Cvoid, ()) end - return svec(linfo, src) end + return src end diff --git a/base/compiler/typelattice.jl b/base/compiler/typelattice.jl index 015961f6a9a825..931d56bffd66df 100644 --- a/base/compiler/typelattice.jl +++ b/base/compiler/typelattice.jl @@ -70,7 +70,7 @@ struct StateUpdate state::VarTable end -struct PartialTuple +struct PartialStruct typ fields::Vector{Any} # elements are other type lattice members end @@ -125,8 +125,8 @@ function ⊑(@nospecialize(a), @nospecialize(b)) elseif isa(b, Conditional) return false end - if isa(a, PartialTuple) - if isa(b, PartialTuple) + if isa(a, PartialStruct) + if isa(b, PartialStruct) if !(length(a.fields) == length(b.fields) && a.typ <: b.typ) return false end @@ -137,11 +137,18 @@ function ⊑(@nospecialize(a), @nospecialize(b)) return true end return isa(b, Type) && a.typ <: b - elseif isa(b, PartialTuple) + elseif isa(b, PartialStruct) if isa(a, Const) nfields(a.val) == length(b.fields) || return false + widenconst(b).name === widenconst(a).name || return false + # We can skip the subtype check if b is a Tuple, since in that + # case, the ⊑ of the elements is sufficient. + if b.typ.name !== Tuple.name && !(widenconst(a) <: widenconst(b)) + return false + end for i in 1:nfields(a.val) # XXX: let's handle varargs later + isdefined(a.val, i) || return false ⊑(Const(getfield(a.val, i)), b.fields[i]) || return false end return true @@ -173,15 +180,16 @@ end # `a ⊑ b && b ⊑ a` but with extra performance optimizations. function is_lattice_equal(@nospecialize(a), @nospecialize(b)) a === b && return true - if isa(a, PartialTuple) - isa(b, PartialTuple) || return false + if isa(a, PartialStruct) + isa(b, PartialStruct) || return false length(a.fields) == length(b.fields) || return false + widenconst(a) == widenconst(b) || return false for i in 1:length(a.fields) is_lattice_equal(a.fields[i], b.fields[i]) || return false end return true end - isa(b, PartialTuple) && return false + isa(b, PartialStruct) && return false a isa Const && return false b isa Const && return false return a ⊑ b && b ⊑ a @@ -200,7 +208,7 @@ function widenconst(c::Const) end widenconst(m::MaybeUndef) = widenconst(m.typ) widenconst(c::PartialTypeVar) = TypeVar -widenconst(t::PartialTuple) = t.typ +widenconst(t::PartialStruct) = t.typ widenconst(@nospecialize(t)) = t issubstate(a::VarState, b::VarState) = (a.typ ⊑ b.typ && a.undef <= b.undef) @@ -228,7 +236,7 @@ function widenconditional(typ::Conditional) end end -function stupdate!(state::Tuple{}, changes::StateUpdate) +function stupdate!(state::Nothing, changes::StateUpdate) newst = copy(changes.state) if isa(changes.var, Slot) changeid = slot_id(changes.var::Slot) @@ -288,9 +296,9 @@ function stupdate!(state::VarTable, changes::VarTable) return newstate end -stupdate!(state::Tuple{}, changes::VarTable) = copy(changes) +stupdate!(state::Nothing, changes::VarTable) = copy(changes) -stupdate!(state::Tuple{}, changes::Tuple{}) = false +stupdate!(state::Nothing, changes::Nothing) = false function stupdate1!(state::VarTable, change::StateUpdate) if !isa(change.var, Slot) diff --git a/base/compiler/typelimits.jl b/base/compiler/typelimits.jl index f8220375e4518f..980cfe5f2115b0 100644 --- a/base/compiler/typelimits.jl +++ b/base/compiler/typelimits.jl @@ -5,6 +5,7 @@ ######################### const MAX_TYPEUNION_COMPLEXITY = 3 +const MAX_TYPEUNION_LENGTH = 3 const MAX_INLINE_CONST_SIZE = 256 ######################### @@ -233,6 +234,7 @@ function type_more_complex(@nospecialize(t), @nospecialize(c), sources::SimpleVe if isa(c, DataType) && t.name === c.name cP = c.parameters length(cP) < length(tP) && return true + length(cP) > length(tP) && !isvarargtype(tP[end]) && depth == 1 && return false ntail = length(cP) - length(tP) # assume parameters were dropped from the tuple head # allow creating variation within a nested tuple, but only so deep if t.name === Tuple.name && tupledepth > 0 @@ -315,6 +317,27 @@ function tmerge(@nospecialize(typea), @nospecialize(typeb)) end return Bool end + if (isa(typea, PartialStruct) || isa(typea, Const)) && + (isa(typeb, PartialStruct) || isa(typeb, Const)) && + widenconst(typea) === widenconst(typeb) + + typea_nfields = nfields_tfunc(typea) + typeb_nfields = nfields_tfunc(typeb) + if !isa(typea_nfields, Const) || !isa(typea_nfields, Const) || typea_nfields.val !== typeb_nfields.val + return widenconst(typea) + end + + type_nfields = typea_nfields.val::Int + fields = Vector{Any}(undef, type_nfields) + anyconst = false + for i = 1:type_nfields + fields[i] = tmerge(getfield_tfunc(typea, Const(i)), + getfield_tfunc(typeb, Const(i))) + anyconst |= has_nontrivial_const_info(fields[i]) + end + return anyconst ? PartialStruct(widenconst(typea), fields) : + widenconst(typea) + end # no special type-inference lattice, join the types typea, typeb = widenconst(typea), widenconst(typeb) typea === typeb && return typea @@ -373,7 +396,7 @@ function tmerge(@nospecialize(typea), @nospecialize(typeb)) end end u = Union{types...} - if unioncomplexity(u) <= MAX_TYPEUNION_COMPLEXITY + if unionlen(u) <= MAX_TYPEUNION_LENGTH && unioncomplexity(u) <= MAX_TYPEUNION_COMPLEXITY # don't let type unions get too big, if the above didn't reduce it enough return u end @@ -400,7 +423,7 @@ function tuplemerge(a::DataType, b::DataType) p = Vector{Any}(undef, lt + vt) for i = 1:lt ui = Union{ap[i], bp[i]} - if unioncomplexity(ui) < MAX_TYPEUNION_COMPLEXITY + if unionlen(ui) <= MAX_TYPEUNION_LENGTH && unioncomplexity(ui) <= MAX_TYPEUNION_COMPLEXITY p[i] = ui else p[i] = Any diff --git a/base/compiler/typeutils.jl b/base/compiler/typeutils.jl index c77f329ef23417..60c7d76f354b5c 100644 --- a/base/compiler/typeutils.jl +++ b/base/compiler/typeutils.jl @@ -33,7 +33,7 @@ function issingletontype(@nospecialize t) end function has_nontrivial_const_info(@nospecialize t) - isa(t, PartialTuple) && return true + isa(t, PartialStruct) && return true return isa(t, Const) && !isdefined(typeof(t.val), :instance) && !(isa(t.val, Type) && issingletontype(t.val)) end @@ -146,10 +146,13 @@ end # unioncomplexity estimates the number of calls to `tmerge` to obtain the given type by # counting the Union instances, taking also into account those hidden in a Tuple or UnionAll -unioncomplexity(u::Union) = 1 + unioncomplexity(u.a) + unioncomplexity(u.b) +function unioncomplexity(u::Union) + inner = max(unioncomplexity(u.a), unioncomplexity(u.b)) + return inner == 0 ? 0 : 1 + inner +end function unioncomplexity(t::DataType) t.name === Tuple.name || return 0 - c = 0 + c = 1 for ti in t.parameters ci = unioncomplexity(ti) if ci > c diff --git a/base/compiler/utilities.jl b/base/compiler/utilities.jl index a953e5c0d60217..9daee10c2c7e59 100644 --- a/base/compiler/utilities.jl +++ b/base/compiler/utilities.jl @@ -59,7 +59,7 @@ end # Meta expression head, these generally can't be deleted even when they are # in a dead branch but can be ignored when analyzing uses/liveness. -is_meta_expr_head(head::Symbol) = (head === :inbounds || head === :boundscheck || head === :meta || head === :simdloop) +is_meta_expr_head(head::Symbol) = (head === :inbounds || head === :boundscheck || head === :meta || head === :loopinfo) sym_isless(a::Symbol, b::Symbol) = ccall(:strcmp, Int32, (Ptr{UInt8}, Ptr{UInt8}), a, b) < 0 @@ -81,11 +81,12 @@ end # MethodInstance/CodeInfo # ########################### -function invoke_api(li::MethodInstance) +function invoke_api(li::CodeInstance) return ccall(:jl_invoke_api, Cint, (Any,), li) end function get_staged(li::MethodInstance) + may_invoke_generator(li) || return nothing try # user code might throw errors – ignore them return ccall(:jl_code_for_staged, Any, (Any,), li)::CodeInfo @@ -96,46 +97,49 @@ end function retrieve_code_info(linfo::MethodInstance) m = linfo.def::Method + c = nothing if isdefined(m, :generator) # user code might throw errors – ignore them - return get_staged(linfo) - else - # TODO: post-inference see if we can swap back to the original arrays? + c = get_staged(linfo) + end + if c === nothing && isdefined(m, :source) src = m.source if isa(src, Array{UInt8,1}) - c = ccall(:jl_uncompress_ast, Any, (Any, Any), m, src) + c = ccall(:jl_uncompress_ast, Any, (Any, Ptr{Cvoid}, Any), m, C_NULL, src) else c = copy(src::CodeInfo) end end - return c::CodeInfo + if c isa CodeInfo + c.parent = linfo + return c + end end -function code_for_method(method::Method, @nospecialize(atypes), sparams::SimpleVector, world::UInt, preexisting::Bool=false) - if world < min_world(method) || world > max_world(method) - return nothing - end - if isdefined(method, :generator) && !isdispatchtuple(atypes) - # don't call staged functions on abstract types. - # (see issues #8504, #10230) - # we can't guarantee that their type behavior is monotonic. - return nothing - end +function inf_for_methodinstance(mi::MethodInstance, min_world::UInt, max_world::UInt=min_world) + inf = ccall(:jl_rettype_inferred, Ptr{Nothing}, (Any, UInt, UInt), mi, min_world, max_world) + inf == C_NULL && return nothing + return unsafe_pointer_to_objref(inf)::CodeInstance +end + + +# get a handle to the unique specialization object representing a particular instantiation of a call +function specialize_method(method::Method, @nospecialize(atypes), sparams::SimpleVector, preexisting::Bool=false) if preexisting if method.specializations !== nothing # check cached specializations # for an existing result stored there - return ccall(:jl_specializations_lookup, Any, (Any, Any, UInt), method, atypes, world) + return ccall(:jl_specializations_lookup, Any, (Any, Any), method, atypes) end return nothing end - return ccall(:jl_specializations_get_linfo, Ref{MethodInstance}, (Any, Any, Any, UInt), method, atypes, sparams, world) + return ccall(:jl_specializations_get_linfo, Ref{MethodInstance}, (Any, Any, Any), method, atypes, sparams) end # This function is used for computing alternate limit heuristics -function method_for_inference_heuristics(method::Method, @nospecialize(sig), sparams::SimpleVector, world::UInt) - if isdefined(method, :generator) && method.generator.expand_early - method_instance = code_for_method(method, sig, sparams, world, false) +function method_for_inference_heuristics(method::Method, @nospecialize(sig), sparams::SimpleVector) + if isdefined(method, :generator) && method.generator.expand_early && may_invoke_generator(method, sig, sparams) + method_instance = specialize_method(method, sig, sparams, false) if isa(method_instance, MethodInstance) cinfo = get_staged(method_instance) if isa(cinfo, CodeInfo) @@ -149,18 +153,18 @@ function method_for_inference_heuristics(method::Method, @nospecialize(sig), spa return nothing end -argextype(@nospecialize(x), state) = argextype(x, state.src, state.sp, state.slottypes) +argextype(@nospecialize(x), state) = argextype(x, state.src, state.sptypes, state.slottypes) const empty_slottypes = Any[] -function argextype(@nospecialize(x), src, spvals::SimpleVector, slottypes::Vector{Any} = empty_slottypes) +function argextype(@nospecialize(x), src, sptypes::Vector{Any}, slottypes::Vector{Any} = empty_slottypes) if isa(x, Expr) if x.head === :static_parameter - return sparam_type(spvals[x.args[1]]) + return sptypes[x.args[1]] elseif x.head === :boundscheck return Bool elseif x.head === :copyast - return argextype(x.args[1], src, spvals, slottypes) + return argextype(x.args[1], src, sptypes, slottypes) end @assert false "argextype only works on argument-position values" elseif isa(x, SlotNumber) diff --git a/base/compiler/validation.jl b/base/compiler/validation.jl index 567d06861ff1fe..965db00dd5aa25 100644 --- a/base/compiler/validation.jl +++ b/base/compiler/validation.jl @@ -11,6 +11,7 @@ const VALID_EXPR_HEADS = IdDict{Any,Any}( :method => 1:4, :const => 1:1, :new => 1:typemax(Int), + :splatnew => 2:2, :return => 1:1, :unreachable => 0:0, :the_exception => 0:0, @@ -25,7 +26,7 @@ const VALID_EXPR_HEADS = IdDict{Any,Any}( :foreigncall => 3:typemax(Int), :cfunction => 5:5, :isdefined => 1:1, - :simdloop => 1:1, + :loopinfo => 0:typemax(Int), :gc_preserve_begin => 0:typemax(Int), :gc_preserve_end => 0:typemax(Int), :thunk => 1:1, @@ -40,7 +41,7 @@ const INVALID_RVALUE = "invalid RHS value" const INVALID_RETURN = "invalid argument to :return" const INVALID_CALL_ARG = "invalid :call argument" const EMPTY_SLOTNAMES = "slotnames field is empty" -const SLOTFLAGS_MISMATCH = "length(slotnames) != length(slotflags)" +const SLOTFLAGS_MISMATCH = "length(slotnames) < length(slotflags)" const SSAVALUETYPES_MISMATCH = "not all SSAValues in AST have a type in ssavaluetypes" const SSAVALUETYPES_MISMATCH_UNINFERRED = "uninferred CodeInfo ssavaluetypes field does not equal the number of present SSAValues" const NON_TOP_LEVEL_METHOD = "encountered `Expr` head `:method` in non-top-level code (i.e. `nargs` > 0)" @@ -142,7 +143,7 @@ function validate_code!(errors::Vector{>:InvalidCodeError}, c::CodeInfo, is_top_ head === :inbounds || head === :foreigncall || head === :cfunction || head === :const || head === :enter || head === :leave || head == :pop_exception || head === :method || head === :global || head === :static_parameter || - head === :new || head === :thunk || head === :simdloop || + head === :new || head === :splatnew || head === :thunk || head === :loopinfo || head === :throw_undef_if_not || head === :unreachable validate_val!(x) else @@ -167,7 +168,7 @@ function validate_code!(errors::Vector{>:InvalidCodeError}, c::CodeInfo, is_top_ nslotflags = length(c.slotflags) nssavals = length(c.code) !is_top_level && nslotnames == 0 && push!(errors, InvalidCodeError(EMPTY_SLOTNAMES)) - nslotnames != nslotflags && push!(errors, InvalidCodeError(SLOTFLAGS_MISMATCH, (nslotnames, nslotflags))) + nslotnames < nslotflags && push!(errors, InvalidCodeError(SLOTFLAGS_MISMATCH, (nslotnames, nslotflags))) if c.inferred nssavaluetypes = length(c.ssavaluetypes) nssavaluetypes < nssavals && push!(errors, InvalidCodeError(SSAVALUETYPES_MISMATCH, (nssavals, nssavaluetypes))) @@ -224,7 +225,7 @@ end function is_valid_rvalue(@nospecialize(x)) is_valid_argument(x) && return true - if isa(x, Expr) && x.head in (:new, :the_exception, :isdefined, :call, :invoke, :foreigncall, :cfunction, :gc_preserve_begin, :copyast) + if isa(x, Expr) && x.head in (:new, :splatnew, :the_exception, :isdefined, :call, :invoke, :foreigncall, :cfunction, :gc_preserve_begin, :copyast) return true end return false diff --git a/base/complex.jl b/base/complex.jl index f34290d30bc4c7..878c4201d3a2e9 100644 --- a/base/complex.jl +++ b/base/complex.jl @@ -35,7 +35,7 @@ const ComplexF16 = Complex{Float16} Complex{T}(x::Real) where {T<:Real} = Complex{T}(x,0) Complex{T}(z::Complex) where {T<:Real} = Complex{T}(real(z),imag(z)) (::Type{T})(z::Complex) where {T<:Real} = - isreal(z) ? T(real(z))::T : throw(InexactError(Symbol(string(T)), T, z)) + isreal(z) ? T(real(z))::T : throw(InexactError(nameof(T), T, z)) Complex(z::Complex) = z @@ -262,6 +262,7 @@ abs2(z::Complex) = real(z)*real(z) + imag(z)*imag(z) inv(z::Complex) = conj(z)/abs2(z) inv(z::Complex{<:Integer}) = inv(float(z)) ++(z::Complex) = Complex(+real(z), +imag(z)) -(z::Complex) = Complex(-real(z), -imag(z)) +(z::Complex, w::Complex) = Complex(real(z) + real(w), imag(z) + imag(w)) -(z::Complex, w::Complex) = Complex(real(z) - real(w), imag(z) - imag(w)) @@ -952,10 +953,14 @@ function atanh(z::Complex{T}) where T<:AbstractFloat return Complex(copysign(zero(x),x), copysign(oftype(y,pi)/2, y)) end return Complex(real(1/z), copysign(oftype(y,pi)/2, y)) - elseif ax==1 + end + β = copysign(one(T), x) + z *= β + x, y = reim(z) + if x == 1 if y == 0 - ξ = copysign(oftype(x,Inf),x) - η = zero(y) + ξ = oftype(x, Inf) + η = y else ym = ay+ρ ξ = log(sqrt(sqrt(4+y*y))/sqrt(ym)) @@ -970,7 +975,7 @@ function atanh(z::Complex{T}) where T<:AbstractFloat end η = angle(Complex((1-x)*(1+x)-ysq, 2y))/2 end - Complex(ξ, η) + β * Complex(ξ, η) end atanh(z::Complex) = atanh(float(z)) diff --git a/base/condition.jl b/base/condition.jl new file mode 100644 index 00000000000000..177dfa93a5af2f --- /dev/null +++ b/base/condition.jl @@ -0,0 +1,155 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +## thread/task locking abstraction + +""" + AbstractLock + +Abstract supertype describing types that +implement the synchronization primitives: +[`lock`](@ref), [`trylock`](@ref), [`unlock`](@ref), and [`islocked`](@ref). +""" +abstract type AbstractLock end +function lock end +function unlock end +function trylock end +function islocked end +unlockall(l::AbstractLock) = unlock(l) # internal function for implementing `wait` +relockall(l::AbstractLock, token::Nothing) = lock(l) # internal function for implementing `wait` +assert_havelock(l::AbstractLock) = assert_havelock(l, Threads.threadid()) +assert_havelock(l::AbstractLock, tid::Integer) = + (islocked(l) && tid == Threads.threadid()) ? nothing : error("concurrency violation detected") +assert_havelock(l::AbstractLock, tid::Task) = + (islocked(l) && tid === current_task()) ? nothing : error("concurrency violation detected") +assert_havelock(l::AbstractLock, tid::Nothing) = error("concurrency violation detected") + +""" + AlwaysLockedST + +This struct does not implement a real lock, but instead +pretends to be always locked on the original thread it was allocated on, +and simply ignores all other interactions. +It also does not synchronize tasks; for that use a real lock such as [`RecursiveLock`](@ref). +This can be used in the place of a real lock to, instead, simply and cheaply assert +that the operation is only occurring on a single cooperatively-scheduled thread. +It is thus functionally equivalent to allocating a real, recursive, task-unaware lock +immediately calling `lock` on it, and then never calling a matching `unlock`, +except that calling `lock` from another thread will throw a concurrency violation exception. +""" +struct AlwaysLockedST <: AbstractLock + ownertid::Int16 + AlwaysLockedST() = new(Threads.threadid()) +end +assert_havelock(l::AlwaysLockedST) = assert_havelock(l, l.ownertid) +lock(l::AlwaysLockedST) = assert_havelock(l) +unlock(l::AlwaysLockedST) = assert_havelock(l) +trylock(l::AlwaysLockedST) = l.ownertid == Threads.threadid() +islocked(::AlwaysLockedST) = true + + +## condition variables + +""" + GenericCondition + +Abstract implementation of a condition object +for synchonizing tasks objects with a given lock. +""" +struct GenericCondition{L<:AbstractLock} + waitq::InvasiveLinkedList{Task} + lock::L + + GenericCondition{L}() where {L<:AbstractLock} = new{L}(InvasiveLinkedList{Task}(), L()) + GenericCondition{L}(l::L) where {L<:AbstractLock} = new{L}(InvasiveLinkedList{Task}(), l) + GenericCondition(l::AbstractLock) = new{typeof(l)}(InvasiveLinkedList{Task}(), l) +end + +assert_havelock(c::GenericCondition) = assert_havelock(c.lock) +lock(c::GenericCondition) = lock(c.lock) +unlock(c::GenericCondition) = unlock(c.lock) +trylock(c::GenericCondition) = trylock(c.lock) +islocked(c::GenericCondition) = islocked(c.lock) + +""" + wait([x]) + +Block the current task until some event occurs, depending on the type of the argument: + +* [`Channel`](@ref): Wait for a value to be appended to the channel. +* [`Condition`](@ref): Wait for [`notify`](@ref) on a condition. +* `Process`: Wait for a process or process chain to exit. The `exitcode` field of a process + can be used to determine success or failure. +* [`Task`](@ref): Wait for a `Task` to finish. If the task fails with an exception, the + exception is propagated (re-thrown in the task that called `wait`). +* [`RawFD`](@ref): Wait for changes on a file descriptor (see the `FileWatching` package). + +If no argument is passed, the task blocks for an undefined period. A task can only be +restarted by an explicit call to [`schedule`](@ref) or [`yieldto`](@ref). + +Often `wait` is called within a `while` loop to ensure a waited-for condition is met before +proceeding. +""" +function wait(c::GenericCondition) + ct = current_task() + assert_havelock(c) + push!(c.waitq, ct) + token = unlockall(c.lock) + try + return wait() + catch + list_deletefirst!(c.waitq, ct) + rethrow() + finally + relockall(c.lock, token) + end +end + +""" + notify(condition, val=nothing; all=true, error=false) + +Wake up tasks waiting for a condition, passing them `val`. If `all` is `true` (the default), +all waiting tasks are woken, otherwise only one is. If `error` is `true`, the passed value +is raised as an exception in the woken tasks. + +Return the count of tasks woken up. Return 0 if no tasks are waiting on `condition`. +""" +notify(c::GenericCondition, @nospecialize(arg = nothing); all=true, error=false) = notify(c, arg, all, error) +function notify(c::GenericCondition, @nospecialize(arg), all, error) + assert_havelock(c) + cnt = 0 + while !isempty(c.waitq) + t = popfirst!(c.waitq) + schedule(t, arg, error=error) + cnt += 1 + all || break + end + return cnt +end + +notify_error(c::GenericCondition, err) = notify(c, err, true, true) + +n_waiters(c::GenericCondition) = length(c.waitq) + +""" + isempty(condition) + +Return `true` if no tasks are waiting on the condition, `false` otherwise. +""" +isempty(c::GenericCondition) = isempty(c.waitq) + + +# default (Julia v1.0) is currently single-threaded +# (although it uses MT-safe versions, when possible) +""" + Condition() + +Create an edge-triggered event source that tasks can wait for. Tasks that call [`wait`](@ref) on a +`Condition` are suspended and queued. Tasks are woken up when [`notify`](@ref) is later called on +the `Condition`. Edge triggering means that only tasks waiting at the time [`notify`](@ref) is +called can be woken up. For level-triggered notifications, you must keep extra state to keep +track of whether a notification has happened. The [`Channel`](@ref) and [`Event`](@ref) types do +this, and can be used for level-triggered events. + +This object is NOT thread-safe. See [`Threads.Condition`](@ref) for a thread-safe version. +""" +const Condition = GenericCondition{AlwaysLockedST} diff --git a/base/coreio.jl b/base/coreio.jl index da955e838a015a..d2be652731e436 100644 --- a/base/coreio.jl +++ b/base/coreio.jl @@ -4,6 +4,9 @@ print(xs...) = print(stdout::IO, xs...) println(xs...) = println(stdout::IO, xs...) println(io::IO) = print(io, '\n') +function show end +function repr end + struct DevNull <: IO end const devnull = DevNull() isreadable(::DevNull) = false diff --git a/base/deepcopy.jl b/base/deepcopy.jl index 7ed68bbf2b6acf..5bf1311a13603c 100644 --- a/base/deepcopy.jl +++ b/base/deepcopy.jl @@ -27,7 +27,7 @@ updated as appropriate before returning. """ deepcopy(x) = deepcopy_internal(x, IdDict())::typeof(x) -deepcopy_internal(x::Union{Symbol,Core.MethodInstance,Method,GlobalRef,DataType,Union,Task}, +deepcopy_internal(x::Union{Symbol,Core.MethodInstance,Method,GlobalRef,DataType,Union,UnionAll,Task}, stackdict::IdDict) = x deepcopy_internal(x::Tuple, stackdict::IdDict) = ntuple(i->deepcopy_internal(x[i], stackdict), length(x)) diff --git a/base/deprecated.jl b/base/deprecated.jl index 7d765ae86a135b..130c86c1d65c8e 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -15,10 +15,20 @@ # is only printed the first time for each call place. """ - @deprecate old new + @deprecate old new [ex=true] The first argument `old` is the signature of the deprecated method, the second one -`new` is the call which replaces it. @deprecate exports the function. +`new` is the call which replaces it. `@deprecate` exports `old` unless the optional +third argument is `false`. + +# Examples +```jldoctest +julia> @deprecate old(x) new(x) +old (generic function with 1 method) + +julia> @deprecate old(x) new(x) false +old (generic function with 1 method) +``` """ macro deprecate(old, new, ex=true) meta = Expr(:meta, :noinline) @@ -164,6 +174,10 @@ function promote_eltype_op end # @deprecate one(i::CartesianIndex) oneunit(i) # @deprecate one(::Type{I}) where I<:CartesianIndex oneunit(I) + +@deprecate reindex(V, idxs, subidxs) reindex(idxs, subidxs) false +@deprecate substrides(parent::AbstractArray, strds::Tuple, I::Tuple) substrides(strds, I) false + # TODO: deprecate these one(::CartesianIndex{N}) where {N} = one(CartesianIndex{N}) one(::Type{CartesianIndex{N}}) where {N} = CartesianIndex(ntuple(x -> 1, Val(N))) diff --git a/base/dict.jl b/base/dict.jl index fb27cc10cc04de..9cd3fe5009de1a 100644 --- a/base/dict.jl +++ b/base/dict.jl @@ -23,8 +23,7 @@ end function show(io::IO, t::AbstractDict{K,V}) where V where K recur_io = IOContext(io, :SHOWN_SET => t, - :typeinfo => eltype(t), - :compact => get(io, :compact, true)) + :typeinfo => eltype(t)) limit::Bool = get(io, :limit, false) # show in a Julia-syntax-like form: Dict(k=>v, ...) @@ -194,7 +193,7 @@ function rehash!(h::Dict{K,V}, newsz = length(h.keys)) where V where K vals = Vector{V}(undef, newsz) age0 = h.age count = 0 - maxprobe = h.maxprobe + maxprobe = 0 for i = 1:sz @inbounds if olds[i] == 0x1 @@ -372,7 +371,7 @@ end function setindex!(h::Dict{K,V}, v0, key0) where V where K key = convert(K, key0) if !isequal(key, key0) - throw(ArgumentError("$key0 is not a valid key for type $K")) + throw(ArgumentError("$(limitrepr(key0)) is not a valid key for type $K")) end setindex!(h, v0, key) end @@ -439,7 +438,7 @@ get!(f::Function, collection, key) function get!(default::Callable, h::Dict{K,V}, key0) where V where K key = convert(K, key0) if !isequal(key, key0) - throw(ArgumentError("$key0 is not a valid key for type $K")) + throw(ArgumentError("$(limitrepr(key0)) is not a valid key for type $K")) end return get!(default, h, key) end @@ -658,18 +657,22 @@ end function skip_deleted(h::Dict, i) L = length(h.slots) - @inbounds while i<=L && !isslotfilled(h,i) - i += 1 + for i = i:L + @inbounds if isslotfilled(h,i) + return i + end end - return i + return nothing end function skip_deleted_floor!(h::Dict) idx = skip_deleted(h, h.idxfloor) - h.idxfloor = idx + if idx !== nothing + h.idxfloor = idx + end idx end -@propagate_inbounds _iterate(t::Dict{K,V}, i) where {K,V} = i > length(t.vals) ? nothing : (Pair{K,V}(t.keys[i],t.vals[i]), i+1) +@propagate_inbounds _iterate(t::Dict{K,V}, i) where {K,V} = i === nothing ? nothing : (Pair{K,V}(t.keys[i],t.vals[i]), i == typemax(Int) ? nothing : i+1) @propagate_inbounds function iterate(t::Dict) _iterate(t, skip_deleted_floor!(t)) end @@ -678,15 +681,28 @@ end isempty(t::Dict) = (t.count == 0) length(t::Dict) = t.count -@propagate_inbounds function iterate(v::Union{KeySet{<:Any, <:Dict}, ValueIterator{<:Dict}}, - i=v.dict.idxfloor) +@propagate_inbounds function Base.iterate(v::T, i::Union{Int,Nothing}=v.dict.idxfloor) where T <: Union{KeySet{<:Any, <:Dict}, ValueIterator{<:Dict}} + i === nothing && return nothing # This is to catch nothing returned when i = typemax i = skip_deleted(v.dict, i) - i > length(v.dict.vals) && return nothing - (v isa KeySet ? v.dict.keys[i] : v.dict.vals[i], i+1) + i === nothing && return nothing # This is to catch nothing returned by skip_deleted + vals = T <: KeySet ? v.dict.keys : v.dict.vals + (@inbounds vals[i], i == typemax(Int) ? nothing : i+1) end filter!(f, d::Dict) = filter_in_one_pass!(f, d) +function map!(f, iter::ValueIterator{<:Dict}) + dict = iter.dict + vals = dict.vals + # @inbounds is here so the it gets propigated to isslotfiled + @inbounds for i = dict.idxfloor:lastindex(vals) + if isslotfilled(dict, i) + vals[i] = f(vals[i]) + end + end + return iter +end + struct ImmutableDict{K,V} <: AbstractDict{K,V} parent::ImmutableDict{K,V} key::K diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index 34859af7096591..4549c098438d16 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -75,7 +75,7 @@ kw"abstract type" """ module -`module` declares a `Module`, which is a separate global variable workspace. Within a +`module` declares a [`Module`](@ref), which is a separate global variable workspace. Within a module, you can control which names from other modules are visible (via importing), and specify which of your names are intended to be public (via exporting). Modules allow you to create top-level definitions without worrying about name conflicts @@ -104,7 +104,7 @@ kw"module" baremodule `baremodule` declares a module that does not contain `using Base` -or a definition of `eval`. It does still import `Core`. +or a definition of [`eval`](@ref Base.eval). It does still import `Core`. """ kw"baremodule" @@ -129,10 +129,13 @@ kw"primitive type" """ macro -`macro` defines a method to include generated code in the final body of a program. A -macro maps a tuple of arguments to a returned expression, and the resulting expression -is compiled directly rather than requiring a runtime `eval` call. Macro arguments may -include expressions, literal values, and symbols. For example: +`macro` defines a method for inserting generated code into a program. +A macro maps a sequence of argument expressions to a returned expression, and the +resulting expression is substituted directly into the program at the point where +the macro is invoked. +Macros are a way to run generated code without calling [`eval`](@ref Base.eval), since the generated +code instead simply becomes part of the surrounding program. +Macro arguments may include expressions, literal values, and symbols. # Examples ```jldoctest @@ -197,6 +200,91 @@ julia> z """ kw"global" +""" + = + +`=` is the assignment operator. +* For variable `a` and expression `b`, `a = b` makes `a` refer to the value of `b`. +* For functions `f(x)`, `f(x) = x` defines a new function constant `f`, or adds a new method to `f` if `f` is already defined; this usage is equivalent to `function f(x); x; end`. +* `a[i] = v` calls [`setindex!`](@ref)`(a,v,i)`. +* `a.b = c` calls [`setproperty!`](@ref)`(a,:b,c)`. +* Inside a function call, `f(a=b)` passes `b` as the value of keyword argument `a`. +* Inside parentheses with commas, `(a=1,)` constructs a [`NamedTuple`](@ref). + +# Examples +Assigning `a` to `b` does not create a copy of `b`; instead use [`copy`](@ref) or [`deepcopy`](@ref). + +```jldoctest +julia> b = [1]; a = b; b[1] = 2; a +1-element Array{Int64,1}: + 2 + +julia> b = [1]; a = copy(b); b[1] = 2; a +1-element Array{Int64,1}: + 1 + +``` +Collections passed to functions are also not copied. Functions can modify (mutate) the contents of the objects their arguments refer to. (The names of functions which do this are conventionally suffixed with '!'.) +```jldoctest +julia> function f!(x); x[:] .+= 1; end +f! (generic function with 1 method) + +julia> a = [1]; f!(a); a +1-element Array{Int64,1}: + 2 + +``` +Assignment can operate on multiple variables in parallel, taking values from an iterable: +```jldoctest +julia> a, b = 4, 5 +(4, 5) + +julia> a, b = 1:3 +1:3 + +julia> a, b +(1, 2) + +``` +Assignment can operate on multiple variables in series, and will return the value of the right-hand-most expression: +```jldoctest +julia> a = [1]; b = [2]; c = [3]; a = b = c +1-element Array{Int64,1}: + 3 + +julia> b[1] = 2; a, b, c +([2], [2], [2]) + +``` +Assignment at out-of-bounds indices does not grow a collection. If the collection is a [`Vector`](@ref) it can instead be grown with [`push!`](@ref) or [`append!`](@ref). +```jldoctest +julia> a = [1, 1]; a[3] = 2 +ERROR: BoundsError: attempt to access 2-element Array{Int64,1} at index [3] +[...] + +julia> push!(a, 2, 3) +4-element Array{Int64,1}: + 1 + 1 + 2 + 3 + +``` +Assigning `[]` does not eliminate elements from a collection; instead use [`filter!`](@ref). +```jldoctest +julia> a = collect(1:3); a[a .<= 1] = [] +ERROR: DimensionMismatch("tried to assign 0 elements to 1 destinations") +[...] + +julia> filter!(x -> x > 1, a) # in-place & thus more efficient than a = a[a .> 1] +2-element Array{Int64,1}: + 2 + 3 + +``` +""" +kw"=" + """ let @@ -221,8 +309,8 @@ kw"let" """ quote -`quote` creates multiple expression objects in a block without using the explicit `Expr` -constructor. For example: +`quote` creates multiple expression objects in a block without using the explicit +[`Expr`](@ref) constructor. For example: ```julia ex = quote @@ -237,6 +325,34 @@ For other purposes, `:( ... )` and `quote .. end` blocks are treated identically """ kw"quote" +""" + Expr(head::Symbol, args...) + +A type representing compound expressions in parsed julia code (ASTs). +Each expression consists of a `head` `Symbol` identifying which kind of +expression it is (e.g. a call, for loop, conditional statement, etc.), +and subexpressions (e.g. the arguments of a call). +The subexpressions are stored in a `Vector{Any}` field called `args`. + +See the manual chapter on [Metaprogramming](@ref) and the developer +documentation [Julia ASTs](@ref). + +# Examples +```jldoctest +julia> Expr(:call, :+, 1, 2) +:(1 + 2) + +julia> dump(:(a ? b : c)) +Expr + head: Symbol if + args: Array{Any}((3,)) + 1: Symbol a + 2: Symbol b + 3: Symbol c +``` +""" +Expr + """ ' @@ -314,7 +430,9 @@ kw"function" """ return -`return` can be used in function bodies to exit early and return a given value, e.g. +`return x` causes the enclosing function to exit early, passing the given value `x` +back to its caller. `return` by itself with no value is equivalent to `return nothing` +(see [`nothing`](@ref)). ```julia function compare(a, b) @@ -340,12 +458,15 @@ function test2(xs) end end ``` -In the first example, the return breaks out of its enclosing function as soon as it hits +In the first example, the return breaks out of `test1` as soon as it hits an even number, so `test1([5,6,7])` returns `12`. You might expect the second example to behave the same way, but in fact the `return` there only breaks out of the *inner* function (inside the `do` block) and gives a value back to `map`. `test2([5,6,7])` then returns `[5,12,7]`. + +When used in a top-level expression (i.e. outside any function), `return` causes +the entire current top-level expression to terminate early. """ kw"return" @@ -376,7 +497,7 @@ kw"if", kw"elseif", kw"else" """ for -`for` loops repeatedly evaluate the body of the loop by +`for` loops repeatedly evaluate a block of statements while iterating over a sequence of values. # Examples @@ -394,8 +515,8 @@ kw"for" """ while -`while` loops repeatedly evaluate a conditional expression, and continues evaluating the -body of the while loop so long as the expression remains `true`. If the condition +`while` loops repeatedly evaluate a conditional expression, and continue evaluating the +body of the while loop as long as the expression remains true. If the condition expression is false when the while loop is first reached, the body is never evaluated. # Examples @@ -442,19 +563,23 @@ kw"end" """ try/catch -A `try`/`catch` statement allows for `Exception`s to be tested for. For example, a -customized square root function can be written to automatically call either the real or -complex square root method on demand using `Exception`s: +A `try`/`catch` statement allows intercepting errors (exceptions) thrown +by [`throw`](@ref) so that program execution can continue. +For example, the following code attempts to write a file, but warns the user +and proceeds instead of terminating execution if the file cannot be written: ```julia -f(x) = try - sqrt(x) +try + open("/danger", "w") do f + println(f, "Hello") + end catch - sqrt(complex(x, 0)) + @warn "Could not write file." end ``` -`try`/`catch` statements also allow the `Exception` to be saved in a variable, e.g. `catch y`. +The syntax `catch e` (where `e` is any variable) assigns the thrown +exception object to the given variable within the `catch` block. The power of the `try`/`catch` construct lies in the ability to unwind a deeply nested computation immediately to a much higher level in the stack of calling functions. @@ -530,7 +655,9 @@ kw"continue" """ do -Create an anonymous function. For example: +Create an anonymous function and pass it as the first argument to +a function call. +For example: ```julia map(1:10) do x @@ -743,7 +870,7 @@ The `where` keyword creates a type that is an iterated union of other types, ove values of some variable. For example `Vector{T} where T<:Real` includes all [`Vector`](@ref)s where the element type is some kind of `Real` number. -The variable bound defaults to `Any` if it is omitted: +The variable bound defaults to [`Any`](@ref) if it is omitted: ```julia Vector{T} where T # short for `where T<:Any` @@ -783,7 +910,7 @@ kw"ans" devnull Used in a stream redirect to discard all data written to it. Essentially equivalent to -/dev/null on Unix or NUL on Windows. Usage: +`/dev/null` on Unix or `NUL` on Windows. Usage: ```julia run(pipeline(`cat test.txt`, devnull)) @@ -811,7 +938,7 @@ nothing """ Core.TypeofBottom -The singleton type containing only the value `Union{}`. +The singleton type containing only the value `Union{}` (which represents the empty type). """ Core.TypeofBottom @@ -820,6 +947,7 @@ Core.TypeofBottom Abstract type of all functions. +# Examples ```jldoctest julia> isa(+, Function) true @@ -845,7 +973,7 @@ ReadOnlyMemoryError Generic error type. The error message, in the `.msg` field, may provide more specific details. -# Example +# Examples ```jldoctest julia> ex = ErrorException("I've done a bad thing"); @@ -868,6 +996,22 @@ Core.WrappedException UndefRefError() The item or field is not defined for the given object. + +# Examples +```jldoctest +julia> struct MyType + a::Vector{Int} + MyType() = new() + end + +julia> A = MyType() +MyType(#undef) + +julia> A.a +ERROR: UndefRefError: access to undefined reference +Stacktrace: +[...] +``` """ UndefRefError @@ -1058,6 +1202,20 @@ UndefVarError UndefKeywordError(var::Symbol) The required keyword argument `var` was not assigned in a function call. + +# Examples +```jldoctest; filter = r"Stacktrace:(\\n \\[[0-9]+\\].*)*" +julia> function my_func(;my_arg) + return my_arg + 1 + end +my_func (generic function with 1 method) + +julia> my_func() +ERROR: UndefKeywordError: keyword argument my_arg not assigned +Stacktrace: + [1] my_func() at ./REPL[1]:2 + [2] top-level scope at REPL[2]:1 +``` """ UndefKeywordError @@ -1215,7 +1373,7 @@ Unsigned """ Bool <: Integer -Boolean type. +Boolean type, containing the values `true` and `false`. """ Bool @@ -1248,10 +1406,41 @@ for bit in (8, 16, 32, 64, 128) end end +""" + Symbol + +The type of object used to represent identifiers in parsed julia code (ASTs). +Also often used as a name or label to identify an entity (e.g. as a dictionary key). +`Symbol`s can be entered using the `:` quote operator: +```jldoctest +julia> :name +:name + +julia> typeof(:name) +Symbol + +julia> x = 42 +42 + +julia> eval(:x) +42 +``` +`Symbol`s can also be constructed from strings or other values by calling the +constructor `Symbol(x...)`. + +`Symbol`s are immutable and should be compared using `===`. +The implementation re-uses the same object for all `Symbol`s with the same name, +so comparison tends to be efficient (it can just compare pointers). + +Unlike strings, `Symbol`s are "atomic" or "scalar" entities that do not support +iteration over characters. +""" +Symbol + """ Symbol(x...) -> Symbol -Create a `Symbol` by concatenating the string representations of the arguments together. +Create a [`Symbol`](@ref) by concatenating the string representations of the arguments together. # Examples ```jldoctest @@ -1262,7 +1451,7 @@ julia> Symbol("day", 4) :day4 ``` """ -Symbol +Symbol(x...) """ tuple(xs...) @@ -1272,7 +1461,7 @@ Construct a tuple of the given objects. # Examples ```jldoctest julia> tuple(1, 'a', pi) -(1, 'a', π = 3.1415926535897...) +(1, 'a', π) ``` """ tuple @@ -1351,9 +1540,13 @@ typeof isdefined(object, s::Symbol) isdefined(object, index::Int) -Tests whether an assignable location is defined. The arguments can be a module and a symbol +Tests whether a global variable or object field is defined. The arguments can be a module and a symbol or a composite object and field name (as a symbol) or index. +To test whether an array element is defined, use [`isassigned`](@ref) instead. + +See also [`@isdefined`](@ref). + # Examples ```jldoctest julia> isdefined(Base, :sum) @@ -1588,6 +1781,13 @@ julia> Array{Float64,1}(undef, 3) """ undef +""" + Ptr{T}() + +Creates a null pointer to type `T`. +""" +Ptr{T}() + """ +(x, y...) @@ -1712,7 +1912,7 @@ AssertionError """ LoadError(file::AbstractString, line::Int, error) -An error occurred while `include`ing, `require`ing, or [`using`](@ref) a file. The error specifics +An error occurred while [`include`](@ref Base.include)ing, [`require`](@ref Base.require)ing, or [`using`](@ref) a file. The error specifics should be available in the `.error` field. """ LoadError @@ -1852,16 +2052,19 @@ See the manual section on [Tuple Types](@ref). """ Tuple -""" -The base library of Julia. -""" -kw"Base" - """ typeassert(x, type) -Throw a TypeError unless `x isa type`. +Throw a [`TypeError`](@ref) unless `x isa type`. The syntax `x::type` calls this function. + +# Examples +```jldoctest +julia> typeassert(2.5, Int) +ERROR: TypeError: in typeassert, expected Int64, got Float64 +Stacktrace: +[...] +``` """ typeassert @@ -1943,4 +2146,36 @@ Union type of [`StridedVector`](@ref) and [`StridedMatrix`](@ref) with elements """ StridedVecOrMat +""" + Module + +A `Module` is a separate global variable workspace. See [`module`](@ref) and the [manual section about modules](@ref modules) for details. +""" +Module + +""" + Core + +`Core` is the module that contains all identifiers considered "built in" to the language, i.e. part of the core language and not libraries. Every module implicitly specifies `using Core`, since you can't do anything without those definitions. +""" +Core.Core + +""" + Main + +`Main` is the top-level module, and Julia starts with `Main` set as the current module. Variables defined at the prompt go in `Main`, and `varinfo` lists variables in `Main`. +```jldoctest +julia> @__MODULE__ +Main +``` +""" +Main.Main + +""" + Base + +The base library of Julia. `Base` is a module that contains basic functionality (the contents of `base/`). All modules implicitly contain `using Base`, since this is needed in the vast majority of cases. +""" +Base.Base + end diff --git a/base/download.jl b/base/download.jl index 7ee4a73adbe0f6..5e2a2442e7e1eb 100644 --- a/base/download.jl +++ b/base/download.jl @@ -3,7 +3,7 @@ # file downloading if Sys.iswindows() - function download(url::AbstractString, filename::AbstractString) + function download_powershell(url::AbstractString, filename::AbstractString) ps = joinpath(get(ENV, "SYSTEMROOT", "C:\\Windows"), "System32\\WindowsPowerShell\\v1.0\\powershell.exe") tls12 = "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12" client = "New-Object System.Net.Webclient" @@ -19,27 +19,53 @@ if Sys.iswindows() end pipeline_error(proc) end - filename + return filename end -else - function download(url::AbstractString, filename::AbstractString) - if Sys.which("curl") !== nothing - run(`curl -g -L -f -o $filename $url`) - elseif Sys.which("wget") !== nothing - try - run(`wget -O $filename $url`) - catch - rm(filename, force=true) # wget always creates a file - rethrow() - end - elseif Sys.which("fetch") !== nothing - run(`fetch -f $filename $url`) - else - error("no download agent available; install curl, wget, or fetch") +end + +function find_curl() + if Sys.isapple() && Sys.isexecutable("/usr/bin/curl") + "/usr/bin/curl" + elseif Sys.iswindows() && Sys.isexecutable(joinpath(get(ENV, "SYSTEMROOT", "C:\\Windows"), "System32\\curl.exe")) + joinpath(get(ENV, "SYSTEMROOT", "C:\\Windows"), "System32\\curl.exe") + elseif Sys.which("curl") !== nothing + "curl" + else + nothing + end +end + +function download_curl(curl_exe::AbstractString, url::AbstractString, filename::AbstractString) + err = PipeBuffer() + process = run(pipeline(`$curl_exe -s -S -g -L -f -o $filename $url`, stderr=err), wait=false) + if !success(process) + stderr = readline(err) + error(stderr) + end + return filename +end + +function download(url::AbstractString, filename::AbstractString) + curl_exe = find_curl() + if curl_exe !== nothing + return download_curl(curl_exe, url, filename) + elseif Sys.iswindows() + return download_powershell(url, filename) + elseif Sys.which("wget") !== nothing + try + run(`wget -O $filename $url`) + catch + rm(filename, force=true) # wget always creates a file + rethrow() end - filename + elseif Sys.which("fetch") !== nothing + run(`fetch -f $filename $url`) + else + error("No download agent available; install curl, wget, or fetch.") end + return filename end + function download(url::AbstractString) filename = tempname() download(url, filename) diff --git a/base/env.jl b/base/env.jl index ffeb9d409c1100..8f5256f25915ee 100644 --- a/base/env.jl +++ b/base/env.jl @@ -15,9 +15,7 @@ if Sys.iswindows() end val = zeros(UInt16,len) ret = ccall(:GetEnvironmentVariableW,stdcall,UInt32,(Ptr{UInt16},Ptr{UInt16},UInt32),var,val,len) - if (ret == 0 && len != 1) || ret != len-1 || val[end] != 0 - error(string("getenv: ", str, ' ', len, "-1 != ", ret, ": ", Libc.FormatMessage())) - end + windowserror(:getenv, (ret == 0 && len != 1) || ret != len-1 || val[end] != 0) pop!(val) # NUL return transcode(String, val) end @@ -27,14 +25,14 @@ if Sys.iswindows() val = cwstring(sval) if overwrite || !_hasenv(var) ret = ccall(:SetEnvironmentVariableW,stdcall,Int32,(Ptr{UInt16},Ptr{UInt16}),var,val) - systemerror(:setenv, ret == 0) + windowserror(:setenv, ret == 0) end end function _unsetenv(svar::AbstractString) var = cwstring(svar) ret = ccall(:SetEnvironmentVariableW,stdcall,Int32,(Ptr{UInt16},Ptr{UInt16}),var,C_NULL) - systemerror(:setenv, ret == 0) + windowserror(:setenv, ret == 0) end else # !windows _getenv(var::AbstractString) = ccall(:getenv, Cstring, (Cstring,), var) @@ -70,6 +68,11 @@ struct EnvDict <: AbstractDict{String,String}; end Reference to the singleton `EnvDict`, providing a dictionary interface to system environment variables. + +(On Windows, system environment variables are case-insensitive, and `ENV` correspondingly converts +all keys to uppercase for display, iteration, and copying. Portable code should not rely on the +ability to distinguish variables by case, and should beware that setting an ostensibly lowercase +variable may result in an uppercase `ENV` key.) """ const ENV = EnvDict() @@ -85,6 +88,16 @@ push!(::EnvDict, kv::Pair{<:AbstractString}) = setindex!(ENV, kv.second, kv.firs if Sys.iswindows() GESW() = (pos = ccall(:GetEnvironmentStringsW,stdcall,Ptr{UInt16},()); (pos,pos)) + function winuppercase(s::AbstractString) + isempty(s) && return s + LOCALE_INVARIANT = 0x0000007f + LCMAP_UPPERCASE = 0x00000200 + ws = transcode(UInt16, String(s)) + result = ccall(:LCMapStringW, stdcall, Cint, (UInt32, UInt32, Ptr{UInt16}, Cint, Ptr{UInt16}, Cint), + LOCALE_INVARIANT, LCMAP_UPPERCASE, ws, length(ws), ws, length(ws)) + systemerror(:LCMapStringW, iszero(result)) + return transcode(String, ws) + end function iterate(hash::EnvDict, block::Tuple{Ptr{UInt16},Ptr{UInt16}} = GESW()) if unsafe_load(block[1]) == 0 ccall(:FreeEnvironmentStringsW, stdcall, Int32, (Ptr{UInt16},), block[2]) @@ -100,7 +113,7 @@ if Sys.iswindows() if m === nothing error("malformed environment entry: $env") end - return (Pair{String,String}(m.captures[1], m.captures[2]), (pos+(len+1)*2, blk)) + return (Pair{String,String}(winuppercase(m.captures[1]), m.captures[2]), (pos+(len+1)*2, blk)) end else # !windows function iterate(::EnvDict, i=0) diff --git a/base/error.jl b/base/error.jl index ff13dbef2ec354..bc04f89d6f8c35 100644 --- a/base/error.jl +++ b/base/error.jl @@ -133,6 +133,21 @@ Raises a `SystemError` for `errno` with the descriptive string `sysfunc` if `ift """ systemerror(p, b::Bool; extrainfo=nothing) = b ? throw(Main.Base.SystemError(string(p), Libc.errno(), extrainfo)) : nothing + +## system errors from Windows API functions +struct WindowsErrorInfo + errnum::UInt32 + extrainfo +end +""" + windowserror(sysfunc, iftrue) + +Like [`systemerror`](@ref), but for Windows API functions that use [`GetLastError`](@ref) instead +of setting [`errno`](@ref). +""" +windowserror(p, b::Bool; extrainfo=nothing) = b ? throw(Main.Base.SystemError(string(p), Libc.errno(), WindowsErrorInfo(Libc.GetLastError(), extrainfo))) : nothing + + ## assertion macro ## @@ -206,13 +221,16 @@ length(ebo::ExponentialBackOff) = ebo.n eltype(::Type{ExponentialBackOff}) = Float64 """ - retry(f::Function; delays=ExponentialBackOff(), check=nothing) -> Function + retry(f; delays=ExponentialBackOff(), check=nothing) -> Function Return an anonymous function that calls function `f`. If an exception arises, `f` is repeatedly called again, each time `check` returns `true`, after waiting the number of seconds specified in `delays`. `check` should input `delays`'s current state and the `Exception`. +!!! compat "Julia 1.2" + Before Julia 1.2 this signature was restricted to `f::Function`. + # Examples ```julia retry(f, delays=fill(5.0, 3)) @@ -222,7 +240,7 @@ retry(http_get, check=(s,e)->e.status == "503")(url) retry(read, check=(s,e)->isa(e, IOError))(io, 128; all=false) ``` """ -function retry(f::Function; delays=ExponentialBackOff(), check=nothing) +function retry(f; delays=ExponentialBackOff(), check=nothing) (args...; kwargs...) -> begin y = iterate(delays) while y !== nothing diff --git a/base/errorshow.jl b/base/errorshow.jl index aa0a99458cd81e..8453de56d31be5 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -85,20 +85,13 @@ function showerror(io::IO, ex, bt; backtrace=true) end function showerror(io::IO, ex::LoadError, bt; backtrace=true) - print(io, "LoadError: ") - showerror(io, ex.error, bt, backtrace=backtrace) - print(io, "\nin expression starting at $(ex.file):$(ex.line)") + print(io, "Error while loading expression starting at ", ex.file, ":", ex.line) end showerror(io::IO, ex::LoadError) = showerror(io, ex, []) -function showerror(io::IO, ex::InitError, bt; backtrace=true) - print(io, "InitError: ") - showerror(io, ex.error, bt, backtrace=backtrace) - print(io, "\nduring initialization of module ", ex.mod) -end -showerror(io::IO, ex::InitError) = showerror(io, ex, []) +showerror(io::IO, ex::InitError) = print(io, "InitError during initialization of module ", ex.mod) -function showerror(io::IO, ex::DomainError, bt; backtrace=true) +function showerror(io::IO, ex::DomainError) if isa(ex.val, AbstractArray) compact = get(io, :compact, true) limit = get(io, :limit, true) @@ -110,17 +103,24 @@ function showerror(io::IO, ex::DomainError, bt; backtrace=true) if isdefined(ex, :msg) print(io, ":\n", ex.msg) end - backtrace && show_backtrace(io, bt) nothing end function showerror(io::IO, ex::SystemError) - if ex.extrainfo === nothing - print(io, "SystemError: $(ex.prefix): $(Libc.strerror(ex.errnum))") + if @static(Sys.iswindows() ? ex.extrainfo isa WindowsErrorInfo : false) + errstring = Libc.FormatMessage(ex.extrainfo.errnum) + extrainfo = ex.extrainfo.extrainfo + else + errstring = Libc.strerror(ex.errnum) + extrainfo = ex.extrainfo + end + if extrainfo === nothing + print(io, "SystemError: $(ex.prefix): ", errstring) else - print(io, "SystemError (with $(ex.extrainfo)): $(ex.prefix): $(Libc.strerror(ex.errnum))") + print(io, "SystemError (with $extrainfo): $(ex.prefix): ", errstring) end end + showerror(io::IO, ::DivideError) = print(io, "DivideError: integer division error") showerror(io::IO, ::StackOverflowError) = print(io, "StackOverflowError:") showerror(io::IO, ::UndefRefError) = print(io, "UndefRefError: access to undefined reference") @@ -136,9 +136,9 @@ showerror(io::IO, ex::KeyError) = (print(io, "KeyError: key "); show(io, ex.key); print(io, " not found")) showerror(io::IO, ex::InterruptException) = print(io, "InterruptException:") -showerror(io::IO, ex::ArgumentError) = print(io, "ArgumentError: $(ex.msg)") -showerror(io::IO, ex::AssertionError) = print(io, "AssertionError: $(ex.msg)") -showerror(io::IO, ex::OverflowError) = print(io, "OverflowError: $(ex.msg)") +showerror(io::IO, ex::ArgumentError) = print(io, "ArgumentError: ", ex.msg) +showerror(io::IO, ex::AssertionError) = print(io, "AssertionError: ", ex.msg) +showerror(io::IO, ex::OverflowError) = print(io, "OverflowError: ", ex.msg) showerror(io::IO, ex::UndefKeywordError) = print(io, "UndefKeywordError: keyword argument $(ex.var) not assigned") @@ -238,7 +238,7 @@ function showerror(io::IO, ex::MethodError) end if (ex.world != typemax(UInt) && hasmethod(ex.f, arg_types) && !hasmethod(ex.f, arg_types, world = ex.world)) - curworld = ccall(:jl_get_world_counter, UInt, ()) + curworld = get_world_counter() println(io) print(io, "The applicable method may be too new: running in world age $(ex.world), while current world is $(curworld).") end @@ -390,7 +390,7 @@ function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=() end end - if right_matches > 0 || length(ex.args) < 2 + if right_matches > 0 || length(arg_types_param) < 2 if length(t_i) < length(sig) # If the methods args is longer than input then the method # arguments is printed as not a match @@ -438,9 +438,9 @@ function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=() end end end - if ex.world < min_world(method) + if ex.world < reinterpret(UInt, method.primary_world) print(iob, " (method too new to be called from this world context.)") - elseif ex.world > max_world(method) + elseif ex.world > reinterpret(UInt, method.deleted_world) print(iob, " (method deleted before this world age.)") end # TODO: indicate if it's in the wrong world @@ -632,9 +632,17 @@ function process_backtrace(t::Vector, limit::Int=typemax(Int); skipC = true) return ret end -@noinline function throw_eachindex_mismatch(::IndexLinear, A...) - throw(DimensionMismatch("all inputs to eachindex must have the same indices, got $(join(LinearIndices.(A), ", ", " and "))")) -end -@noinline function throw_eachindex_mismatch(::IndexCartesian, A...) - throw(DimensionMismatch("all inputs to eachindex must have the same axes, got $(join(axes.(A), ", ", " and "))")) +function show_exception_stack(io::IO, stack::Vector) + # Display exception stack with the top of the stack first. This ordering + # means that the user doesn't have to scroll up in the REPL to discover the + # root cause. + nexc = length(stack) + for i = nexc:-1:1 + if nexc != i + printstyled(io, "caused by [exception ", i, "]\n", color=:light_black) + end + exc, bt = stack[i] + showerror(io, exc, bt, backtrace = bt!==nothing) + println(io) + end end diff --git a/base/essentials.jl b/base/essentials.jl index 30c575f7c50bdc..6a7f1eb999473a 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -6,7 +6,20 @@ const Callable = Union{Function,Type} const Bottom = Union{} +""" + AbstractSet{T} + +Supertype for set-like types whose elements are of type `T`. +[`Set`](@ref), [`BitSet`](@ref) and other types are subtypes of this. +""" abstract type AbstractSet{T} end + +""" + AbstractDict{K, V} + +Supertype for dictionary-like types with keys of type `K` and values of type `V`. +[`Dict`](@ref), [`IdDict`](@ref) and other types are subtypes of this. +""" abstract type AbstractDict{K,V} end # The real @inline macro is not available until after array.jl, so this @@ -170,7 +183,23 @@ macro eval(mod, ex) end argtail(x, rest...) = rest + +""" + tail(x::Tuple)::Tuple + +Return a `Tuple` consisting of all but the first component of `x`. + +# Examples +```jldoctest +julia> Base.tail((1,2,3)) +(2, 3) + +julia> Base.tail(()) +ERROR: ArgumentError: Cannot call tail on an empty tuple. +``` +""" tail(x::Tuple) = argtail(x...) +tail(::Tuple{}) = throw(ArgumentError("Cannot call tail on an empty tuple.")) tuple_type_head(T::Type) = (@_pure_meta; fieldtype(T::Type{<:Tuple}, 1)) @@ -280,6 +309,13 @@ convert(::Type{Tuple{Vararg{V}}}, x::Tuple{Vararg{V}}) where {V} = x convert(T::Type{Tuple{Vararg{V}}}, x::Tuple) where {V} = (convert(tuple_type_head(T), x[1]), convert(T, tail(x))...) +# used for splatting in `new` +convert_prefix(::Type{Tuple{}}, x::Tuple) = x +convert_prefix(::Type{<:AtLeast1}, x::Tuple{}) = x +convert_prefix(::Type{T}, x::T) where {T<:AtLeast1} = x +convert_prefix(::Type{T}, x::AtLeast1) where {T<:AtLeast1} = + (convert(tuple_type_head(T), x[1]), convert_prefix(tuple_type_tail(T), tail(x))...) + # TODO: the following definitions are equivalent (behaviorally) to the above method # I think they may be faster / more efficient for inference, # if we could enable them, but are they? @@ -408,7 +444,7 @@ If `DataType` `T` does not have a specific size, an error is thrown. ```jldoctest julia> sizeof(AbstractArray) -ERROR: argument is an abstract type; size is indeterminate +ERROR: Abstract type AbstractArray does not have a definite size. Stacktrace: [...] ``` @@ -434,7 +470,7 @@ end """ esc(e) -Only valid in the context of an `Expr` returned from a macro. Prevents the macro hygiene +Only valid in the context of an [`Expr`](@ref) returned from a macro. Prevents the macro hygiene pass from turning embedded variables into gensym variables. See the [Macros](@ref man-macros) section of the Metaprogramming chapter of the manual for more details and examples. """ @@ -570,7 +606,7 @@ eltype(::Type{SimpleVector}) = Any keys(v::SimpleVector) = OneTo(length(v)) isempty(v::SimpleVector) = (length(v) == 0) axes(v::SimpleVector) = (OneTo(length(v)),) -axes(v::SimpleVector, d) = d <= 1 ? axes(v)[d] : OneTo(1) +axes(v::SimpleVector, d::Integer) = d <= 1 ? axes(v)[d] : OneTo(1) function ==(v1::SimpleVector, v2::SimpleVector) length(v1)==length(v2) || return false @@ -643,19 +679,31 @@ function append_any(xs...) end for j in 1:lx y = @inbounds x[j] - arrayset(true, out, y, i) + arrayset(false, out, y, i) i += 1 end elseif x isa Tuple - lx = length(x) + lx = nfields(x) if i + lx - 1 > l ladd = lx > 16 ? lx : 16 _growend!(out, ladd) l += ladd end for j in 1:lx - y = @inbounds x[j] - arrayset(true, out, y, i) + y = getfield(x, j, false) + arrayset(false, out, y, i) + i += 1 + end + elseif x isa NamedTuple + lx = nfields(x) + if i + lx - 1 > l + ladd = lx > 16 ? lx : 16 + _growend!(out, ladd) + l += ladd + end + for j in 1:lx + y = getfield(x, j, false) + arrayset(false, out, y, i) i += 1 end elseif x isa Array @@ -666,8 +714,8 @@ function append_any(xs...) l += ladd end for j in 1:lx - y = arrayref(true, x, j) - arrayset(true, out, y, i) + y = arrayref(false, x, j) + arrayset(false, out, y, i) i += 1 end else @@ -676,7 +724,7 @@ function append_any(xs...) _growend!(out, 16) l += 16 end - arrayset(true, out, y, i) + arrayset(false, out, y, i) i += 1 end end @@ -856,6 +904,12 @@ next element and the new iteration state should be returned. """ function iterate end +""" + isiterable(T) -> Bool + +Test if type `T` is an iterable collection type or not, +that is whether it has an `iterate` method or not. +""" function isiterable(T)::Bool return hasmethod(iterate, Tuple{T}) end diff --git a/base/event.jl b/base/event.jl deleted file mode 100644 index 0c73e09e3deeab..00000000000000 --- a/base/event.jl +++ /dev/null @@ -1,563 +0,0 @@ -# This file is a part of Julia. License is MIT: https://julialang.org/license - -## thread/task locking abstraction - -""" - AbstractLock - -Abstract supertype describing types that -implement the synchronization primitives: -[`lock`](@ref), [`trylock`](@ref), [`unlock`](@ref), and [`islocked`](@ref). -""" -abstract type AbstractLock end -function lock end -function unlock end -function trylock end -function islocked end -unlockall(l::AbstractLock) = unlock(l) # internal function for implementing `wait` -relockall(l::AbstractLock, token::Nothing) = lock(l) # internal function for implementing `wait` -assert_havelock(l::AbstractLock) = assert_havelock(l, Threads.threadid()) -assert_havelock(l::AbstractLock, tid::Integer) = - (islocked(l) && tid == Threads.threadid()) ? nothing : error("concurrency violation detected") -assert_havelock(l::AbstractLock, tid::Task) = - (islocked(l) && tid === current_task()) ? nothing : error("concurrency violation detected") -assert_havelock(l::AbstractLock, tid::Nothing) = error("concurrency violation detected") - -""" - AlwaysLockedST - -This struct does not implement a real lock, but instead -pretends to be always locked on the original thread it was allocated on, -and simply ignores all other interactions. -It also does not synchronize tasks; for that use a real lock such as [`RecursiveLock`](@ref). -This can be used in the place of a real lock to, instead, simply and cheaply assert -that the operation is only occurring on a single cooperatively-scheduled thread. -It is thus functionally equivalent to allocating a real, recursive, task-unaware lock -immediately calling `lock` on it, and then never calling a matching `unlock`, -except that calling `lock` from another thread will throw a concurrency violation exception. -""" -struct AlwaysLockedST <: AbstractLock - ownertid::Int16 - AlwaysLockedST() = new(Threads.threadid()) -end -assert_havelock(l::AlwaysLockedST) = assert_havelock(l, l.ownertid) -lock(l::AlwaysLockedST) = assert_havelock(l) -unlock(l::AlwaysLockedST) = assert_havelock(l) -trylock(l::AlwaysLockedST) = l.ownertid == Threads.threadid() -islocked(::AlwaysLockedST) = true - - -## condition variables - -""" - GenericCondition - -Abstract implementation of a condition object -for synchonizing tasks objects with a given lock. -""" -struct GenericCondition{L<:AbstractLock} - waitq::Vector{Any} - lock::L - - GenericCondition{L}() where {L<:AbstractLock} = new{L}([], L()) - GenericCondition{L}(l::L) where {L<:AbstractLock} = new{L}([], l) - GenericCondition(l::AbstractLock) = new{typeof(l)}([], l) -end - -assert_havelock(c::GenericCondition) = assert_havelock(c.lock) -lock(c::GenericCondition) = lock(c.lock) -unlock(c::GenericCondition) = unlock(c.lock) -trylock(c::GenericCondition) = trylock(c.lock) -islocked(c::GenericCondition) = islocked(c.lock) - -""" - wait([x]) - -Block the current task until some event occurs, depending on the type of the argument: - -* [`Channel`](@ref): Wait for a value to be appended to the channel. -* [`Condition`](@ref): Wait for [`notify`](@ref) on a condition. -* `Process`: Wait for a process or process chain to exit. The `exitcode` field of a process - can be used to determine success or failure. -* [`Task`](@ref): Wait for a `Task` to finish. If the task fails with an exception, the - exception is propagated (re-thrown in the task that called `wait`). -* [`RawFD`](@ref): Wait for changes on a file descriptor (see the `FileWatching` package). - -If no argument is passed, the task blocks for an undefined period. A task can only be -restarted by an explicit call to [`schedule`](@ref) or [`yieldto`](@ref). - -Often `wait` is called within a `while` loop to ensure a waited-for condition is met before -proceeding. -""" -function wait(c::GenericCondition) - ct = current_task() - assert_havelock(c) - push!(c.waitq, ct) - token = unlockall(c.lock) - - try - return wait() - catch - filter!(x->x!==ct, c.waitq) - rethrow() - finally - relockall(c.lock, token) - end -end - -""" - notify(condition, val=nothing; all=true, error=false) - -Wake up tasks waiting for a condition, passing them `val`. If `all` is `true` (the default), -all waiting tasks are woken, otherwise only one is. If `error` is `true`, the passed value -is raised as an exception in the woken tasks. - -Return the count of tasks woken up. Return 0 if no tasks are waiting on `condition`. -""" -notify(c::GenericCondition, @nospecialize(arg = nothing); all=true, error=false) = notify(c, arg, all, error) -function notify(c::GenericCondition, @nospecialize(arg), all, error) - assert_havelock(c) - cnt = 0 - if all - cnt = length(c.waitq) - for t in c.waitq - schedule(t, arg, error=error) - end - empty!(c.waitq) - elseif !isempty(c.waitq) - cnt = 1 - t = popfirst!(c.waitq) - schedule(t, arg, error=error) - end - return cnt -end - -notify_error(c::GenericCondition, err) = notify(c, err, true, true) - -n_waiters(c::GenericCondition) = length(c.waitq) - -""" - isempty(condition) - -Return `true` if no tasks are waiting on the condition, `false` otherwise. -""" -isempty(c::GenericCondition) = isempty(c.waitq) - - -# default (Julia v1.0) is currently single-threaded -# (although it uses MT-safe versions, when possible) -""" - Condition() - -Create an edge-triggered event source that tasks can wait for. Tasks that call [`wait`](@ref) on a -`Condition` are suspended and queued. Tasks are woken up when [`notify`](@ref) is later called on -the `Condition`. Edge triggering means that only tasks waiting at the time [`notify`](@ref) is -called can be woken up. For level-triggered notifications, you must keep extra state to keep -track of whether a notification has happened. The [`Channel`](@ref) and [`Event`](@ref) types do -this, and can be used for level-triggered events. - -This object is NOT thread-safe. See [`Threads.Condition`](@ref) for a thread-safe version. -""" -const Condition = GenericCondition{AlwaysLockedST} - - -## scheduler and work queue - -global const Workqueue = Task[] - -function enq_work(t::Task) - t.state == :runnable || error("schedule: Task not runnable") - ccall(:uv_stop, Cvoid, (Ptr{Cvoid},), eventloop()) - push!(Workqueue, t) - t.state = :queued - return t -end - -schedule(t::Task) = enq_work(t) - -""" - schedule(t::Task, [val]; error=false) - -Add a [`Task`](@ref) to the scheduler's queue. This causes the task to run constantly when the system -is otherwise idle, unless the task performs a blocking operation such as [`wait`](@ref). - -If a second argument `val` is provided, it will be passed to the task (via the return value of -[`yieldto`](@ref)) when it runs again. If `error` is `true`, the value is raised as an exception in -the woken task. - -# Examples -```jldoctest -julia> a5() = sum(i for i in 1:1000); - -julia> b = Task(a5); - -julia> istaskstarted(b) -false - -julia> schedule(b); - -julia> yield(); - -julia> istaskstarted(b) -true - -julia> istaskdone(b) -true -``` -""" -function schedule(t::Task, arg; error=false) - # schedule a task to be (re)started with the given value or exception - if error - t.exception = arg - else - t.result = arg - end - return enq_work(t) -end - -# fast version of `schedule(t, arg); wait()` -function schedule_and_wait(t::Task, arg=nothing) - t.state == :runnable || error("schedule: Task not runnable") - if isempty(Workqueue) - return yieldto(t, arg) - else - t.result = arg - push!(Workqueue, t) - t.state = :queued - end - return wait() -end - -""" - yield() - -Switch to the scheduler to allow another scheduled task to run. A task that calls this -function is still runnable, and will be restarted immediately if there are no other runnable -tasks. -""" -yield() = (enq_work(current_task()); wait()) - -""" - yield(t::Task, arg = nothing) - -A fast, unfair-scheduling version of `schedule(t, arg); yield()` which -immediately yields to `t` before calling the scheduler. -""" -function yield(t::Task, @nospecialize x = nothing) - t.state == :runnable || error("schedule: Task not runnable") - t.result = x - enq_work(current_task()) - return try_yieldto(ensure_rescheduled, Ref(t)) -end - -""" - yieldto(t::Task, arg = nothing) - -Switch to the given task. The first time a task is switched to, the task's function is -called with no arguments. On subsequent switches, `arg` is returned from the task's last -call to `yieldto`. This is a low-level call that only switches tasks, not considering states -or scheduling in any way. Its use is discouraged. -""" -function yieldto(t::Task, @nospecialize x = nothing) - t.result = x - return try_yieldto(identity, Ref(t)) -end - -function try_yieldto(undo, reftask::Ref{Task}) - try - ccall(:jl_switchto, Cvoid, (Any,), reftask) - catch - undo(reftask[]) - rethrow() - end - ct = current_task() - exc = ct.exception - if exc !== nothing - ct.exception = nothing - throw(exc) - end - result = ct.result - ct.result = nothing - return result -end - -# yield to a task, throwing an exception in it -function throwto(t::Task, @nospecialize exc) - t.exception = exc - return yieldto(t) -end - -function ensure_rescheduled(othertask::Task) - ct = current_task() - if ct !== othertask && othertask.state == :runnable - # we failed to yield to othertask - # return it to the head of the queue to be scheduled later - pushfirst!(Workqueue, othertask) - othertask.state = :queued - end - if ct.state == :queued - # if the current task was queued, - # also need to return it to the runnable state - # before throwing an error - i = findfirst(t->t===ct, Workqueue) - i === nothing || deleteat!(Workqueue, i) - ct.state = :runnable - end - nothing -end - -@noinline function poptask() - t = popfirst!(Workqueue) - if t.state != :queued - # assume this somehow got queued twice, - # probably broken now, but try discarding this switch and keep going - # can't throw here, because it's probably not the fault of the caller to wait - # and don't want to use print() here, because that may try to incur a task switch - ccall(:jl_safe_printf, Cvoid, (Ptr{UInt8}, Int32...), - "\nWARNING: Workqueue inconsistency detected: popfirst!(Workqueue).state != :queued\n") - return - end - t.state = :runnable - return Ref(t) -end - -function wait() - while true - if isempty(Workqueue) - c = process_events(true) - if c == 0 && eventloop() != C_NULL && isempty(Workqueue) - # if there are no active handles and no runnable tasks, just - # wait for signals. - pause() - end - else - reftask = poptask() - if reftask !== nothing - result = try_yieldto(ensure_rescheduled, reftask) - process_events(false) - # return when we come out of the queue - return result - end - end - end - # unreachable -end - -if Sys.iswindows() - pause() = ccall(:Sleep, stdcall, Cvoid, (UInt32,), 0xffffffff) -else - pause() = ccall(:pause, Cvoid, ()) -end - - -## async event notifications - -""" - AsyncCondition() - -Create a async condition that wakes up tasks waiting for it -(by calling [`wait`](@ref) on the object) -when notified from C by a call to `uv_async_send`. -Waiting tasks are woken with an error when the object is closed (by [`close`](@ref). -Use [`isopen`](@ref) to check whether it is still active. -""" -mutable struct AsyncCondition - handle::Ptr{Cvoid} - cond::Condition - isopen::Bool - - function AsyncCondition() - this = new(Libc.malloc(_sizeof_uv_async), Condition(), true) - associate_julia_struct(this.handle, this) - finalizer(uvfinalize, this) - err = ccall(:uv_async_init, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}), - eventloop(), this, uv_jl_asynccb::Ptr{Cvoid}) - if err != 0 - #TODO: this codepath is currently not tested - Libc.free(this.handle) - this.handle = C_NULL - throw(_UVError("uv_async_init", err)) - end - return this - end -end - -""" - AsyncCondition(callback::Function) - -Create a async condition that calls the given `callback` function. The `callback` is passed one argument, -the async condition object itself. -""" -function AsyncCondition(cb::Function) - async = AsyncCondition() - waiter = Task(function() - while isopen(async) - success = try - wait(async) - true - catch exc # ignore possible exception on close() - isa(exc, EOFError) || rethrow() - end - success && cb(async) - end - end) - # must start the task right away so that it can wait for the AsyncCondition before - # we re-enter the event loop. this avoids a race condition. see issue #12719 - yield(waiter) - return async -end - -## timer-based notifications - -""" - Timer(delay; interval = 0) - -Create a timer that wakes up tasks waiting for it (by calling [`wait`](@ref) on the timer object). - -Waiting tasks are woken after an initial delay of `delay` seconds, and then repeating with the given -`interval` in seconds. If `interval` is equal to `0`, the timer is only triggered once. When -the timer is closed (by [`close`](@ref) waiting tasks are woken with an error. Use [`isopen`](@ref) -to check whether a timer is still active. -""" -mutable struct Timer - handle::Ptr{Cvoid} - cond::Condition - isopen::Bool - - function Timer(timeout::Real; interval::Real = 0.0) - timeout ≥ 0 || throw(ArgumentError("timer cannot have negative timeout of $timeout seconds")) - interval ≥ 0 || throw(ArgumentError("timer cannot have negative repeat interval of $interval seconds")) - - this = new(Libc.malloc(_sizeof_uv_timer), Condition(), true) - err = ccall(:uv_timer_init, Cint, (Ptr{Cvoid}, Ptr{Cvoid}), eventloop(), this) - if err != 0 - #TODO: this codepath is currently not tested - Libc.free(this.handle) - this.handle = C_NULL - throw(_UVError("uv_timer_init", err)) - end - - associate_julia_struct(this.handle, this) - finalizer(uvfinalize, this) - - ccall(:uv_update_time, Cvoid, (Ptr{Cvoid},), eventloop()) - ccall(:uv_timer_start, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, UInt64, UInt64), - this, uv_jl_timercb::Ptr{Cvoid}, - UInt64(round(timeout * 1000)) + 1, UInt64(round(interval * 1000))) - return this - end -end - -unsafe_convert(::Type{Ptr{Cvoid}}, t::Timer) = t.handle -unsafe_convert(::Type{Ptr{Cvoid}}, async::AsyncCondition) = async.handle - -function wait(t::Union{Timer, AsyncCondition}) - isopen(t) || throw(EOFError()) - stream_wait(t, t.cond) -end - -isopen(t::Union{Timer, AsyncCondition}) = t.isopen - -function close(t::Union{Timer, AsyncCondition}) - if t.handle != C_NULL && isopen(t) - t.isopen = false - isa(t, Timer) && ccall(:uv_timer_stop, Cint, (Ptr{Cvoid},), t) - ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), t) - end - nothing -end - -function uvfinalize(t::Union{Timer, AsyncCondition}) - if t.handle != C_NULL - disassociate_julia_struct(t.handle) # not going to call the usual close hooks - close(t) - t.handle = C_NULL - end - t.isopen = false - nothing -end - -function _uv_hook_close(t::Union{Timer, AsyncCondition}) - uvfinalize(t) - notify_error(t.cond, EOFError()) - nothing -end - -function uv_asynccb(handle::Ptr{Cvoid}) - async = @handle_as handle AsyncCondition - notify(async.cond) - nothing -end - -function uv_timercb(handle::Ptr{Cvoid}) - t = @handle_as handle Timer - if ccall(:uv_timer_get_repeat, UInt64, (Ptr{Cvoid},), t) == 0 - # timer is stopped now - close(t) - end - notify(t.cond) - nothing -end - -""" - sleep(seconds) - -Block the current task for a specified number of seconds. The minimum sleep time is 1 -millisecond or input of `0.001`. -""" -function sleep(sec::Real) - sec ≥ 0 || throw(ArgumentError("cannot sleep for $sec seconds")) - wait(Timer(sec)) - nothing -end - -# timer with repeated callback -""" - Timer(callback::Function, delay; interval = 0) - -Create a timer that wakes up tasks waiting for it (by calling [`wait`](@ref) on the timer object) and -calls the function `callback`. - -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 timer -is only triggered once. The function `callback` is called with a single argument, the timer itself. -When the timer is closed (by [`close`](@ref) waiting tasks are woken with an error. Use [`isopen`](@ref) -to check whether a timer is still active. - -# Examples - -Here the first number is printed after a delay of two seconds, then the following numbers are printed quickly. - -```julia-repl -julia> begin - i = 0 - cb(timer) = (global i += 1; println(i)) - t = Timer(cb, 2, interval = 0.2) - wait(t) - sleep(0.5) - close(t) - end -1 -2 -3 -``` -""" -function Timer(cb::Function, timeout::Real; interval::Real = 0.0) - t = Timer(timeout, interval = interval) - waiter = Task(function() - while isopen(t) - success = try - wait(t) - true - catch exc # ignore possible exception on close() - isa(exc, EOFError) || rethrow() - false - end - success && cb(t) - end - end) - # must start the task right away so that it can wait for the Timer before - # we re-enter the event loop. this avoids a race condition. see issue #12719 - yield(waiter) - return t -end diff --git a/base/exports.jl b/base/exports.jl index 74ef52ea28e779..8a26eb8fdc73f4 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -700,7 +700,9 @@ export fieldnames, fieldcount, fieldtypes, + hasfield, propertynames, + hasproperty, isabstracttype, isbitstype, isprimitivetype, diff --git a/base/expr.jl b/base/expr.jl index c27d5bc6b3b058..c6a02e4571692b 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -266,7 +266,7 @@ function pushmeta!(ex::Expr, sym::Symbol, args::Any...) end inner = ex - while inner.head == :macrocall + while inner.head === :macrocall inner = inner.args[end]::Expr end @@ -284,7 +284,7 @@ popmeta!(body, sym) = _getmeta(body, sym, true) peekmeta(body, sym) = _getmeta(body, sym, false) function _getmeta(body::Expr, sym::Symbol, delete::Bool) - body.head == :block || return false, [] + body.head === :block || return false, [] _getmeta(body.args, sym, delete) end _getmeta(arg, sym, delete::Bool) = (false, []) @@ -319,19 +319,19 @@ function findmetaarg(metaargs, sym) end function is_short_function_def(ex) - ex.head == :(=) || return false + ex.head === :(=) || return false while length(ex.args) >= 1 && isa(ex.args[1], Expr) - (ex.args[1].head == :call) && return true - (ex.args[1].head == :where || ex.args[1].head == :(::)) || return false + (ex.args[1].head === :call) && return true + (ex.args[1].head === :where || ex.args[1].head === :(::)) || return false ex = ex.args[1] end return false end function findmeta(ex::Expr) - if ex.head == :function || is_short_function_def(ex) + if ex.head === :function || is_short_function_def(ex) body::Expr = ex.args[2] - body.head == :block || error(body, " is not a block expression") + body.head === :block || error(body, " is not a block expression") return findmeta_block(ex.args) end error(ex, " is not a function expression") @@ -343,9 +343,9 @@ function findmeta_block(exargs, argsmatch=args->true) for i = 1:length(exargs) a = exargs[i] if isa(a, Expr) - if (a::Expr).head == :meta && argsmatch((a::Expr).args) + if (a::Expr).head === :meta && argsmatch((a::Expr).args) return i, exargs - elseif (a::Expr).head == :block + elseif (a::Expr).head === :block idx, exa = findmeta_block(a.args, argsmatch) if idx != 0 return idx, exa diff --git a/base/fastmath.jl b/base/fastmath.jl index 0dccf8ba4bfc2f..7ad6fc9bd3e60d 100644 --- a/base/fastmath.jl +++ b/base/fastmath.jl @@ -105,9 +105,9 @@ function make_fastmath(expr::Expr) elseif isa(var, Expr) && var.head === :ref # array reference arr = var.args[1] - inds = tuple(var.args[2:end]...) + inds = var.args[2:end] arrvar = gensym() - indvars = tuple([gensym() for i in inds]...) + indvars = Any[gensym() for i in inds] expr = quote $(Expr(:(=), arrvar, arr)) $(Expr(:(=), Expr(:tuple, indvars...), Expr(:tuple, inds...))) diff --git a/base/file.jl b/base/file.jl index 7ebf4535ef00a7..b7f98c73237747 100644 --- a/base/file.jl +++ b/base/file.jl @@ -416,28 +416,29 @@ function touch(path::AbstractString) path end +const temp_prefix = "jl_" + if Sys.iswindows() function tempdir() temppath = Vector{UInt16}(undef, 32767) - lentemppath = ccall(:GetTempPathW,stdcall,UInt32,(UInt32,Ptr{UInt16}),length(temppath),temppath) - if lentemppath >= length(temppath) || lentemppath == 0 - error("GetTempPath failed: $(Libc.FormatMessage())") - end - resize!(temppath,lentemppath) + lentemppath = ccall(:GetTempPathW, stdcall, UInt32, (UInt32, Ptr{UInt16}), length(temppath), temppath) + windowserror("GetTempPath", lentemppath >= length(temppath) || lentemppath == 0) + resize!(temppath, lentemppath) return transcode(String, temppath) end -const temp_prefix = cwstring("jl_") function _win_tempname(temppath::AbstractString, uunique::UInt32) tempp = cwstring(temppath) + temppfx = cwstring(temp_prefix) tname = Vector{UInt16}(undef, 32767) - uunique = ccall(:GetTempFileNameW,stdcall,UInt32,(Ptr{UInt16},Ptr{UInt16},UInt32,Ptr{UInt16}), tempp,temp_prefix,uunique,tname) - lentname = something(findfirst(iszero,tname), 0)-1 - if uunique == 0 || lentname <= 0 - error("GetTempFileName failed: $(Libc.FormatMessage())") - end - resize!(tname,lentname) + uunique = ccall(:GetTempFileNameW, stdcall, UInt32, + (Ptr{UInt16}, Ptr{UInt16}, UInt32, Ptr{UInt16}), + tempp, temppfx, uunique, tname) + windowserror("GetTempFileName", uunique == 0) + lentname = something(findfirst(iszero, tname)) + @assert lentname > 0 + resize!(tname, lentname - 1) return transcode(String, tname) end @@ -446,22 +447,6 @@ function mktemp(parent=tempdir()) return (filename, Base.open(filename, "r+")) end -function mktempdir(parent=tempdir()) - seed::UInt32 = Libc.rand(UInt32) - while true - if (seed & typemax(UInt16)) == 0 - seed += 1 - end - filename = _win_tempname(parent, seed) - ret = ccall(:_wmkdir, Int32, (Ptr{UInt16},), cwstring(filename)) - if ret == 0 - return filename - end - systemerror(:mktempdir, Libc.errno()!=Libc.EEXIST) - seed += 1 - end -end - function tempname() parent = tempdir() seed::UInt32 = rand(UInt32) @@ -481,7 +466,7 @@ else # !windows # Obtain a temporary filename. function tempname() d = get(ENV, "TMPDIR", C_NULL) # tempnam ignores TMPDIR on darwin - p = ccall(:tempnam, Cstring, (Cstring,Cstring), d, :julia) + p = ccall(:tempnam, Cstring, (Cstring, Cstring), d, temp_prefix) systemerror(:tempnam, p == C_NULL) s = unsafe_string(p) Libc.free(p) @@ -493,19 +478,12 @@ tempdir() = dirname(tempname()) # Create and return the name of a temporary file along with an IOStream function mktemp(parent=tempdir()) - b = joinpath(parent, "tmpXXXXXX") + b = joinpath(parent, temp_prefix * "XXXXXX") p = ccall(:mkstemp, Int32, (Cstring,), b) # modifies b systemerror(:mktemp, p == -1) return (b, fdio(p, true)) end -# Create and return the name of a temporary directory -function mktempdir(parent=tempdir()) - b = joinpath(parent, "tmpXXXXXX") - p = ccall(:mkdtemp, Cstring, (Cstring,), b) - systemerror(:mktempdir, p == C_NULL) - return unsafe_string(p) -end end # os-test @@ -540,12 +518,37 @@ is an open file object for this path. mktemp(parent) """ - mktempdir(parent=tempdir()) + mktempdir(parent=tempdir(); prefix=$(repr(temp_prefix))) -Create a temporary directory in the `parent` directory and return its path. +Create a temporary directory in the `parent` directory with a name +constructed from the given prefix and a random suffix, and return its path. +Additionally, any trailing `X` characters may be replaced with random characters. If `parent` does not exist, throw an error. """ -mktempdir(parent) +function mktempdir(parent=tempdir(); prefix=temp_prefix) + if isempty(parent) || occursin(path_separator_re, parent[end:end]) + # append a path_separator only if parent didn't already have one + tpath = "$(parent)$(prefix)XXXXXX" + else + tpath = "$(parent)$(path_separator)$(prefix)XXXXXX" + end + + req = Libc.malloc(_sizeof_uv_fs) + try + ret = ccall(:uv_fs_mkdtemp, Int32, + (Ptr{Cvoid}, Ptr{Cvoid}, Cstring, Ptr{Cvoid}), + eventloop(), req, tpath, C_NULL) + if ret < 0 + ccall(:uv_fs_req_cleanup, Cvoid, (Ptr{Cvoid},), req) + uv_error("mktempdir", ret) + end + path = unsafe_string(ccall(:jl_uv_fs_t_path, Cstring, (Ptr{Cvoid},), req)) + ccall(:uv_fs_req_cleanup, Cvoid, (Ptr{Cvoid},), req) + return path + finally + Libc.free(req) + end +end """ @@ -570,13 +573,13 @@ function mktemp(fn::Function, parent=tempdir()) end """ - mktempdir(f::Function, parent=tempdir()) + mktempdir(f::Function, parent=tempdir(); prefix=$(repr(temp_prefix))) -Apply the function `f` to the result of [`mktempdir(parent)`](@ref) and remove the -temporary directory upon completion. +Apply the function `f` to the result of [`mktempdir(parent; prefix)`](@ref) and remove the +temporary directory all of its contents upon completion. """ -function mktempdir(fn::Function, parent=tempdir()) - tmpdir = mktempdir(parent) +function mktempdir(fn::Function, parent=tempdir(); prefix=temp_prefix) + tmpdir = mktempdir(parent; prefix=prefix) try fn(tmpdir) finally @@ -620,7 +623,7 @@ function readdir(path::AbstractString) uv_readdir_req = zeros(UInt8, ccall(:jl_sizeof_uv_fs_t, Int32, ())) # defined in sys.c, to call uv_fs_readdir, which sets errno on error. - err = ccall(:uv_fs_scandir, Int32, (Ptr{Cvoid}, Ptr{UInt8}, Cstring, Cint, Ptr{Cvoid}), + err = ccall(:jl_uv_fs_scandir, Int32, (Ptr{Cvoid}, Ptr{UInt8}, Cstring, Cint, Ptr{Cvoid}), eventloop(), uv_readdir_req, path, 0, C_NULL) err < 0 && throw(SystemError("unable to read directory $path", -err)) #uv_error("unable to read directory $path", err) @@ -805,7 +808,7 @@ Return the target location a symbolic link `path` points to. function readlink(path::AbstractString) req = Libc.malloc(_sizeof_uv_fs) try - ret = ccall(:uv_fs_readlink, Int32, + ret = ccall(:jl_uv_fs_readlink, Int32, (Ptr{Cvoid}, Ptr{Cvoid}, Cstring, Ptr{Cvoid}), eventloop(), req, path, C_NULL) if ret < 0 @@ -813,7 +816,7 @@ function readlink(path::AbstractString) uv_error("readlink", ret) @assert false end - tgt = unsafe_string(ccall(:jl_uv_fs_t_ptr, Ptr{Cchar}, (Ptr{Cvoid},), req)) + tgt = unsafe_string(ccall(:jl_uv_fs_t_ptr, Cstring, (Ptr{Cvoid},), req)) ccall(:uv_fs_req_cleanup, Cvoid, (Ptr{Cvoid},), req) return tgt finally diff --git a/base/filesystem.jl b/base/filesystem.jl index 536e303bf398bf..7c252099afa025 100644 --- a/base/filesystem.jl +++ b/base/filesystem.jl @@ -42,7 +42,7 @@ import .Base: IOError, _UVError, _sizeof_uv_fs, check_open, close, eof, eventloop, fd, isopen, bytesavailable, position, read, read!, readavailable, seek, seekend, show, skip, stat, unsafe_read, unsafe_write, write, transcode, uv_error, - rawhandle, OS_HANDLE, INVALID_OS_HANDLE + rawhandle, OS_HANDLE, INVALID_OS_HANDLE, windowserror if Sys.iswindows() import .Base: cwstring @@ -73,7 +73,7 @@ function open(path::AbstractString, flags::Integer, mode::Integer=0) req = Libc.malloc(_sizeof_uv_fs) local handle try - ret = ccall(:uv_fs_open, Int32, + ret = ccall(:jl_uv_fs_open, Int32, (Ptr{Cvoid}, Ptr{Cvoid}, Cstring, Int32, Int32, Ptr{Cvoid}), eventloop(), req, path, flags, mode, C_NULL) handle = ccall(:jl_uv_fs_result, Cssize_t, (Ptr{Cvoid},), req) @@ -102,13 +102,19 @@ function close(f::File) return nothing end -# sendfile is the most efficient way to copy a file (or any file descriptor) +# sendfile is the most efficient way to copy from a file descriptor function sendfile(dst::File, src::File, src_offset::Int64, bytes::Int) check_open(dst) check_open(src) - err = ccall(:jl_fs_sendfile, Int32, (OS_HANDLE, OS_HANDLE, Int64, Csize_t), - src.handle, dst.handle, src_offset, bytes) - uv_error("sendfile", err) + while true + result = ccall(:jl_fs_sendfile, Int32, (OS_HANDLE, OS_HANDLE, Int64, Csize_t), + src.handle, dst.handle, src_offset, bytes) + uv_error("sendfile", result) + nsent = result + bytes -= nsent + src_offset += nsent + bytes <= 0 && break + end nothing end @@ -125,7 +131,7 @@ write(f::File, c::UInt8) = write(f, Ref{UInt8}(c)) function truncate(f::File, n::Integer) check_open(f) req = Libc.malloc(_sizeof_uv_fs) - err = ccall(:uv_fs_ftruncate, Int32, + err = ccall(:jl_uv_fs_ftruncate, Int32, (Ptr{Cvoid}, Ptr{Cvoid}, OS_HANDLE, Int64, Ptr{Cvoid}), eventloop(), req, f.handle, n, C_NULL) Libc.free(req) @@ -136,7 +142,7 @@ end function futime(f::File, atime::Float64, mtime::Float64) check_open(f) req = Libc.malloc(_sizeof_uv_fs) - err = ccall(:uv_fs_futime, Int32, + err = ccall(:jl_uv_fs_futime, Int32, (Ptr{Cvoid}, Ptr{Cvoid}, OS_HANDLE, Float64, Float64, Ptr{Cvoid}), eventloop(), req, f.handle, atime, mtime, C_NULL) Libc.free(req) diff --git a/base/float.jl b/base/float.jl index 28254437c8c089..5cd74bf2c2a6d4 100644 --- a/base/float.jl +++ b/base/float.jl @@ -841,8 +841,7 @@ eps(::AbstractFloat) ## byte order swaps for arbitrary-endianness serialization/deserialization ## -bswap(x::Float32) = bswap_int(x) -bswap(x::Float64) = bswap_int(x) +bswap(x::IEEEFloat) = bswap_int(x) # bit patterns reinterpret(::Type{Unsigned}, x::Float64) = reinterpret(UInt64, x) diff --git a/base/floatfuncs.jl b/base/floatfuncs.jl index 22118c5c62f250..9c90137e0d4fce 100644 --- a/base/floatfuncs.jl +++ b/base/floatfuncs.jl @@ -231,7 +231,8 @@ the square root of [`eps`](@ref) of the type of `x` or `y`, whichever is bigger This corresponds to requiring equality of about half of the significand digits. Otherwise, e.g. for integer arguments or if an `atol > 0` is supplied, `rtol` defaults to zero. -`x` and `y` may also be arrays of numbers, in which case `norm` defaults to `vecnorm` but +`x` and `y` may also be arrays of numbers, in which case `norm` defaults to the usual +`norm` function in LinearAlgebra, but may be changed by passing a `norm::Function` keyword argument. (For numbers, `norm` is the same thing as `abs`.) When `x` and `y` are arrays, if `norm(x-y)` is not finite (i.e. `±Inf` or `NaN`), the comparison falls back to checking whether all elements of `x` and `y` are diff --git a/base/gmp.jl b/base/gmp.jl index dea7da7edd1081..ddc1256c504cae 100644 --- a/base/gmp.jl +++ b/base/gmp.jl @@ -81,6 +81,14 @@ julia> big"313" """ BigInt(x) +""" + ALLOC_OVERFLOW_FUNCTION + +A reference that holds a boolean, if true, indicating julia is linked with a patched GMP that +does not abort on huge allocation and throws OutOfMemoryError instead. +""" +const ALLOC_OVERFLOW_FUNCTION = Ref(false) + function __init__() try if version().major != VERSION.major || bits_per_limb() != BITS_PER_LIMB @@ -95,12 +103,23 @@ function __init__() cglobal(:jl_gc_counted_malloc), cglobal(:jl_gc_counted_realloc_with_old_size), cglobal(:jl_gc_counted_free_with_size)) - ZERO.alloc, ZERO.size, ZERO.d = 0, 0, C_NULL ONE.alloc, ONE.size, ONE.d = 1, 1, pointer(_ONE) catch ex Base.showerror_nostdio(ex, "WARNING: Error during initialization of module GMP") end + # This only works with a patched version of GMP, ignore otherwise + try + ccall((:__gmp_set_alloc_overflow_function, :libgmp), Cvoid, + (Ptr{Cvoid},), + cglobal(:jl_throw_out_of_memory_error)) + ALLOC_OVERFLOW_FUNCTION[] = true + catch ex + # ErrorException("ccall: could not find function...") + if typeof(ex) != ErrorException + rethrow() + end + end end @@ -321,7 +340,7 @@ function (::Type{T})(x::BigInt) where T<:Base.BitUnsigned if sizeof(T) < sizeof(Limb) convert(T, convert(Limb,x)) else - 0 <= x.size <= cld(sizeof(T),sizeof(Limb)) || throw(InexactError(Symbol(string(T)), T, x)) + 0 <= x.size <= cld(sizeof(T),sizeof(Limb)) || throw(InexactError(nameof(T), T, x)) x % T end end @@ -332,9 +351,9 @@ function (::Type{T})(x::BigInt) where T<:Base.BitSigned SLimb = typeof(Signed(one(Limb))) convert(T, convert(SLimb, x)) else - 0 <= n <= cld(sizeof(T),sizeof(Limb)) || throw(InexactError(Symbol(string(T)), T, x)) + 0 <= n <= cld(sizeof(T),sizeof(Limb)) || throw(InexactError(nameof(T), T, x)) y = x % T - ispos(x) ⊻ (y > 0) && throw(InexactError(Symbol(string(T)), T, x)) # catch overflow + ispos(x) ⊻ (y > 0) && throw(InexactError(nameof(T), T, x)) # catch overflow y end end @@ -394,6 +413,8 @@ function big end big(::Type{<:Integer}) = BigInt big(::Type{<:Rational}) = Rational{BigInt} +big(n::Integer) = convert(BigInt, n) + # Binary ops for (fJ, fC) in ((:+, :add), (:-,:sub), (:*, :mul), (:fld, :fdiv_q), (:div, :tdiv_q), (:mod, :fdiv_r), (:rem, :tdiv_r), diff --git a/base/indices.jl b/base/indices.jl index cef5b422475237..4f3e8f533fbf55 100644 --- a/base/indices.jl +++ b/base/indices.jl @@ -260,14 +260,14 @@ indexing behaviors. This must return either an `Int` or an `AbstractArray` of `Int`s. """ to_index(i::Integer) = convert(Int,i)::Int -to_index(i::Bool) = throw(ArgumentError("invalid index: $i of type $(typeof(i))")) +to_index(i::Bool) = throw(ArgumentError("invalid index: $i of type Bool")) to_index(I::AbstractArray{Bool}) = LogicalIndex(I) to_index(I::AbstractArray) = I to_index(I::AbstractArray{Union{}}) = I to_index(I::AbstractArray{<:Union{AbstractArray, Colon}}) = - throw(ArgumentError("invalid index: $I of type $(typeof(I))")) + throw(ArgumentError("invalid index: $(limitrepr(I)) of type $(typeof(I))")) to_index(::Colon) = throw(ArgumentError("colons must be converted by to_indices(...)")) -to_index(i) = throw(ArgumentError("invalid index: $i of type $(typeof(i))")) +to_index(i) = throw(ArgumentError("invalid index: $(limitrepr(i)) of type $(typeof(i))")) # The general to_indices is mostly defined in multidimensional.jl, but this # definition is required for bootstrap: diff --git a/base/initdefs.jl b/base/initdefs.jl index 2952b27e379202..bf56e4c84f72de 100644 --- a/base/initdefs.jl +++ b/base/initdefs.jl @@ -41,16 +41,63 @@ isinteractive() = (is_interactive::Bool) ## package depots (registries, packages, environments) ## +""" + DEPOT_PATH + +A stack of "depot" locations where the package manager, as well as Julia's code +loading mechanisms, look for package registries, installed packages, named +environments, repo clones, cached compiled package images, and configuration +files. By default it includes: + +1. `~/.julia` where `~` is the user home as appropriate on the system; +2. an architecture-specific shared system directory, e.g. `/usr/local/share/julia`; +3. an architecture-independent shared system directory, e.g. `/usr/share/julia`. + +So `DEPOT_PATH` might be: +```julia +[joinpath(homedir(), ".julia"), "/usr/local/share/julia", "/usr/share/julia"] +``` +The first entry is the "user depot" and should be writable by and owned by the +current user. The user depot is where: registries are cloned, new package versions +are installed, named environments are created and updated, package repos are cloned, +newly compiled package image files are saved, log files are written, development +packages are checked out by default, and global configuration data is saved. Later +entries in the depot path are treated as read-only and are appropriate for +registries, packages, etc. installed and managed by system administrators. + +`DEPOT_PATH` is populated based on the [`JULIA_DEPOT_PATH`](@ref JULIA_DEPOT_PATH) +environment variable if set. + +See also: +[`JULIA_DEPOT_PATH`](@ref JULIA_DEPOT_PATH), and +[Code Loading](@ref Code-Loading). +""" const DEPOT_PATH = String[] +function append_default_depot_path!(DEPOT_PATH) + path = joinpath(homedir(), ".julia") + path in DEPOT_PATH || push!(DEPOT_PATH, path) + path = abspath(Sys.BINDIR, "..", "local", "share", "julia") + path in DEPOT_PATH || push!(DEPOT_PATH, path) + path = abspath(Sys.BINDIR, "..", "share", "julia") + path in DEPOT_PATH || push!(DEPOT_PATH, path) +end + function init_depot_path() + empty!(DEPOT_PATH) if haskey(ENV, "JULIA_DEPOT_PATH") - depots = split(ENV["JULIA_DEPOT_PATH"], Sys.iswindows() ? ';' : ':') - append!(empty!(DEPOT_PATH), map(expanduser, depots)) + str = ENV["JULIA_DEPOT_PATH"] + isempty(str) && return + for path in split(str, Sys.iswindows() ? ';' : ':') + if isempty(path) + append_default_depot_path!(DEPOT_PATH) + else + path = expanduser(path) + path in DEPOT_PATH || push!(DEPOT_PATH, path) + end + end else - push!(empty!(DEPOT_PATH), joinpath(homedir(), ".julia")) - push!(DEPOT_PATH, abspath(Sys.BINDIR, "..", "local", "share", "julia")) - push!(DEPOT_PATH, abspath(Sys.BINDIR, "..", "share", "julia")) + append_default_depot_path!(DEPOT_PATH) end end @@ -76,7 +123,36 @@ const DEFAULT_LOAD_PATH = ["@", "@v#.#", "@stdlib"] LOAD_PATH An array of paths for `using` and `import` statements to consider as project -environments or package directories when loading code. See [Code Loading](@ref Code-Loading). +environments or package directories when loading code. It is populated based on +the [`JULIA_LOAD_PATH`](@ref JULIA_LOAD_PATH) environment variable if set; +otherwise it defaults to `["@", "@v#.#", "@stdlib"]`. Entries starting with `@` +have special meanings: + +- `@` refers to the "current active environment", the initial value of which is + initially determined by the [`JULIA_PROJECT`](@ref JULIA_PROJECT) environment + variable or the `--project` command-line option. + +- `@stdlib` expands to the absolute path of the current Julia installation's + standard library directory. + +- `@name` refers to a named environment, which are stored in depots (see + [`JULIA_DEPOT_PATH`](@ref JULIA_DEPOT_PATH)) under the `environments` + subdirectory. The user's named environments are stored in + `~/.julia/environments` so `@name` would refer to the environment in + `~/.julia/environments/name` if it exists and contains a `Project.toml` file. + If `name` contains `#` characters, then they are replaced with the major, minor + and patch components of the Julia version number. For example, if you are + running Julia 1.2 then `@v#.#` expands to `@v1.2` and will look for an + environment by that name, typically at `~/.julia/environments/v1.2`. + +The fully expanded value of `LOAD_PATH` that is searched for projects and packages +can be seen by calling the `Base.load_path()` function. + +See also: +[`JULIA_LOAD_PATH`](@ref JULIA_LOAD_PATH), +[`JULIA_PROJECT`](@ref JULIA_PROJECT), +[`JULIA_DEPOT_PATH`](@ref JULIA_DEPOT_PATH), and +[Code Loading](@ref Code-Loading). """ const LOAD_PATH = copy(DEFAULT_LOAD_PATH) const HOME_PROJECT = Ref{Union{String,Nothing}}(nothing) @@ -119,6 +195,7 @@ function parse_load_path(str::String) env = current_project() env === nothing && continue end + env = expanduser(env) env in envs || push!(envs, env) end end diff --git a/base/int.jl b/base/int.jl index 8de57b0f18cacc..93901166b39218 100644 --- a/base/int.jl +++ b/base/int.jl @@ -184,11 +184,7 @@ fld(x::Unsigned, y::Signed) = div(x, y) - (signbit(y) & (rem(x, y) != 0)) rem(x, y, RoundDown) The reduction of `x` modulo `y`, or equivalently, the remainder of `x` after floored -division by `y`, i.e. -```julia -x - y*fld(x,y) -``` -if computed without intermediate rounding. +division by `y`, i.e. `x - y*fld(x,y)` if computed without intermediate rounding. The result will have the same sign as `y`, and magnitude less than `abs(y)` (with some exceptions, see note below). @@ -763,19 +759,97 @@ if Core.sizeof(Int) == 4 return (lolo & 0xffffffffffffffff) + UInt128(w1) << 64 end + function _setbit(x::UInt128, i) + # faster version of `return x | (UInt128(1) << i)` + j = i >> 5 + y = UInt128(one(UInt32) << (i & 0x1f)) + if j == 0 + return x | y + elseif j == 1 + return x | (y << 32) + elseif j == 2 + return x | (y << 64) + elseif j == 3 + return x | (y << 96) + end + return x + end + + function divrem(x::UInt128, y::UInt128) + iszero(y) && throw(DivideError()) + if (x >> 64) % UInt64 == 0 + if (y >> 64) % UInt64 == 0 + # fast path: upper 64 bits are zero, so we can fallback to UInt64 division + q64, x64 = divrem(x % UInt64, y % UInt64) + return UInt128(q64), UInt128(x64) + else + # this implies y>x, so + return zero(UInt128), x + end + end + n = leading_zeros(y) - leading_zeros(x) + q = zero(UInt128) + ys = y << n + while n >= 0 + # ys == y * 2^n + if ys <= x + x -= ys + q = _setbit(q, n) + if (x >> 64) % UInt64 == 0 + # exit early, similar to above fast path + if (y >> 64) % UInt64 == 0 + q64, x64 = divrem(x % UInt64, y % UInt64) + q |= q64 + x = UInt128(x64) + end + return q, x + end + end + ys >>>= 1 + n -= 1 + end + return q, x + end + function div(x::Int128, y::Int128) (x == typemin(Int128)) & (y == -1) && throw(DivideError()) return Int128(div(BigInt(x), BigInt(y)))::Int128 end - function div(x::UInt128, y::UInt128) - return UInt128(div(BigInt(x), BigInt(y)))::UInt128 - end + div(x::UInt128, y::UInt128) = divrem(x, y)[1] function rem(x::Int128, y::Int128) return Int128(rem(BigInt(x), BigInt(y)))::Int128 end + function rem(x::UInt128, y::UInt128) - return UInt128(rem(BigInt(x), BigInt(y)))::UInt128 + iszero(y) && throw(DivideError()) + if (x >> 64) % UInt64 == 0 + if (y >> 64) % UInt64 == 0 + # fast path: upper 64 bits are zero, so we can fallback to UInt64 division + return UInt128(rem(x % UInt64, y % UInt64)) + else + # this implies y>x, so + return x + end + end + n = leading_zeros(y) - leading_zeros(x) + ys = y << n + while n >= 0 + # ys == y * 2^n + if ys <= x + x -= ys + if (x >> 64) % UInt64 == 0 + # exit early, similar to above fast path + if (y >> 64) % UInt64 == 0 + x = UInt128(rem(x % UInt64, y % UInt64)) + end + return x + end + end + ys >>>= 1 + n -= 1 + end + return x end function mod(x::Int128, y::Int128) diff --git a/base/intfuncs.jl b/base/intfuncs.jl index 8da0845258d137..db1a1ae1212516 100644 --- a/base/intfuncs.jl +++ b/base/intfuncs.jl @@ -175,8 +175,9 @@ to_power_type(x) = convert(Base._return_type(*, Tuple{typeof(x), typeof(x)}), x) "\nConvert input to float."))) @noinline throw_domerr_powbysq(::Integer, p) = throw(DomainError(p, string("Cannot raise an integer x to a negative power ", p, '.', - "\nMake x a float by adding a zero decimal (e.g., 2.0^$p instead ", - "of 2^$p), or write 1/x^$(-p), float(x)^$p, or (x//1)^$p"))) + "\nMake x or $p a float by adding a zero decimal ", + "(e.g., 2.0^$p or 2^$(float(p)) instead of 2^$p), ", + "or write 1/x^$(-p), float(x)^$p, x^float($p) or (x//1)^$p"))) @noinline throw_domerr_powbysq(::AbstractMatrix, p) = throw(DomainError(p, string("Cannot raise an integer matrix x to a negative power ", p, '.', "\nMake x a float matrix by adding a zero decimal ", @@ -409,12 +410,12 @@ const powers_of_ten = [ 0x000000e8d4a51000, 0x000009184e72a000, 0x00005af3107a4000, 0x00038d7ea4c68000, 0x002386f26fc10000, 0x016345785d8a0000, 0x0de0b6b3a7640000, 0x8ac7230489e80000, ] -function ndigits0z(x::Base.BitUnsigned64) +function bit_ndigits0z(x::Base.BitUnsigned64) lz = (sizeof(x)<<3)-leading_zeros(x) nd = (1233*lz)>>12+1 nd -= x < powers_of_ten[nd] end -function ndigits0z(x::UInt128) +function bit_ndigits0z(x::UInt128) n = 0 while x > 0x8ac7230489e80000 x = div(x,0x8ac7230489e80000) @@ -423,16 +424,20 @@ function ndigits0z(x::UInt128) return n + ndigits0z(UInt64(x)) end -ndigits0z(x::BitSigned) = ndigits0z(unsigned(abs(x))) - +ndigits0z(x::BitSigned) = bit_ndigits0z(unsigned(abs(x))) +ndigits0z(x::BitUnsigned) = bit_ndigits0z(x) ndigits0z(x::Integer) = ndigits0zpb(x, 10) ## ndigits with specified base ## # The suffix "nb" stands for "negative base" function ndigits0znb(x::Integer, b::Integer) - # precondition: b < -1 && !(typeof(x) <: Unsigned) d = 0 + if x isa Unsigned + d += (x != 0)::Bool + x = -signed(fld(x, -b)) + end + # precondition: b < -1 && !(typeof(x) <: Unsigned) while x != 0 x = cld(x,b) d += 1 @@ -441,7 +446,6 @@ function ndigits0znb(x::Integer, b::Integer) end # do first division before conversion with signed here, which can otherwise overflow -ndigits0znb(x::Unsigned, b::Integer) = ndigits0znb(-signed(fld(x, -b)), b) + (x != 0) ndigits0znb(x::Bool, b::Integer) = x % Int # The suffix "pb" stands for "positive base" @@ -451,11 +455,11 @@ function ndigits0zpb(x::Integer, b::Integer) b = Int(b) x = abs(x) if x isa Base.BitInteger - x = unsigned(x) + 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 == 16 && return sizeof(x)<<1 - leading_zeros(x)>>2 - b == 10 && return ndigits0z(x) + b == 10 && return bit_ndigits0z(x) end d = 0 @@ -537,11 +541,11 @@ julia> ndigits(123, pad=5) 5 ``` """ -ndigits(x::Integer; base::Integer=10, pad::Int=1) = max(pad, ndigits0z(x, base)) +ndigits(x::Integer; base::Integer=10, pad::Integer=1) = max(pad, ndigits0z(x, base)) ## integer to string functions ## -function bin(x::Unsigned, pad::Int, neg::Bool) +function bin(x::Unsigned, pad::Integer, neg::Bool) i = neg + max(pad,sizeof(x)<<3-leading_zeros(x)) a = StringVector(i) while i > neg @@ -553,7 +557,7 @@ function bin(x::Unsigned, pad::Int, neg::Bool) String(a) end -function oct(x::Unsigned, pad::Int, neg::Bool) +function oct(x::Unsigned, pad::Integer, neg::Bool) i = neg + max(pad,div((sizeof(x)<<3)-leading_zeros(x)+2,3)) a = StringVector(i) while i > neg @@ -565,7 +569,7 @@ function oct(x::Unsigned, pad::Int, neg::Bool) String(a) end -function dec(x::Unsigned, pad::Int, neg::Bool) +function dec(x::Unsigned, pad::Integer, neg::Bool) i = neg + ndigits(x, base=10, pad=pad) a = StringVector(i) while i > neg @@ -577,7 +581,7 @@ function dec(x::Unsigned, pad::Int, neg::Bool) String(a) end -function hex(x::Unsigned, pad::Int, neg::Bool) +function hex(x::Unsigned, pad::Integer, neg::Bool) i = neg + max(pad,(sizeof(x)<<1)-(leading_zeros(x)>>2)) a = StringVector(i) while i > neg @@ -593,9 +597,9 @@ end const base36digits = ['0':'9';'a':'z'] const base62digits = ['0':'9';'A':'Z';'a':'z'] -function _base(b::Int, x::Integer, pad::Int, neg::Bool) +function _base(b::Integer, x::Integer, pad::Integer, neg::Bool) (x >= 0) | (b < 0) || throw(DomainError(x, "For negative `x`, `b` must be negative.")) - 2 <= abs(b) <= 62 || throw(ArgumentError("base must satisfy 2 ≤ abs(base) ≤ 62, got $b")) + 2 <= abs(b) <= 62 || throw(DomainError(b, "base must satisfy 2 ≤ abs(base) ≤ 62")) digits = abs(b) <= 36 ? base36digits : base62digits i = neg + ndigits(x, base=b, pad=pad) a = StringVector(i) @@ -644,7 +648,7 @@ function string(n::Integer; base::Integer = 10, pad::Integer = 1) (n_positive, neg) = split_sign(n) hex(n_positive, pad, neg) else - _base(Int(base), base > 0 ? unsigned(abs(n)) : convert(Signed, n), Int(pad), (base>0) & (n<0)) + _base(base, base > 0 ? unsigned(abs(n)) : convert(Signed, n), pad, (base>0) & (n<0)) end end @@ -745,7 +749,7 @@ julia> digits!([2,2,2,2,2,2], 10, base = 2) ``` """ function digits!(a::AbstractVector{T}, n::Integer; base::Integer = 10) where T<:Integer - 2 <= abs(base) || throw(ArgumentError("base must be ≥ 2 or ≤ -2, got $base")) + 2 <= abs(base) || throw(DomainError(base, "base must be ≥ 2 or ≤ -2")) hastypemax(T) && abs(base) - 1 > typemax(T) && throw(ArgumentError("type $T too small for base $base")) isempty(a) && return a @@ -803,7 +807,7 @@ julia> factorial(6) 720 julia> factorial(21) -ERROR: OverflowError: 21 is too large to look up in the table +ERROR: OverflowError: 21 is too large to look up in the table; consider using `factorial(big(21))` instead Stacktrace: [...] diff --git a/base/io.jl b/base/io.jl index 187a8b6e0c42ec..e14caf4ac323b4 100644 --- a/base/io.jl +++ b/base/io.jl @@ -176,6 +176,19 @@ julia> write(io, "Sometimes those members") + write(io, " write documentation.") julia> String(take!(io)) "Sometimes those members write documentation." ``` +User-defined plain-data types without `write` methods can be written when wrapped in a `Ref`: +```jldoctest +julia> struct MyStruct; x::Float64; end + +julia> io = IOBuffer() +IOBuffer(data=UInt8[...], readable=true, writable=true, seekable=true, append=false, size=0, maxsize=Inf, ptr=1, mark=-1) + +julia> write(io, Ref(MyStruct(42.0))) +8 + +julia> seekstart(io); read!(io, Ref(MyStruct(NaN))) +Base.RefValue{MyStruct}(MyStruct(42.0)) +``` """ function write end @@ -366,7 +379,7 @@ function readline(filename::AbstractString; keep::Bool=false) end end -function readline(s::IO=stdin; keep::Bool=false) +function readline(s::IO=stdin; keep::Bool=false)::String line = readuntil(s, 0x0a, keep=true) i = length(line) if keep || i == 0 || line[i] != 0x0a @@ -798,7 +811,7 @@ The size of `b` will be increased if needed (i.e. if `nb` is greater than `lengt and enough bytes could be read), but it will never be decreased. """ function readbytes!(s::IO, b::AbstractArray{UInt8}, nb=length(b)) - @assert !has_offset_axes(b) + require_one_based_indexing(b) olb = lb = length(b) nr = 0 while nr < nb && !eof(s) @@ -923,7 +936,7 @@ previously marked position. Throw an error if the stream is not marked. See also [`mark`](@ref), [`unmark`](@ref), [`ismarked`](@ref). """ function reset(io::T) where T<:IO - ismarked(io) || throw(ArgumentError("$(T) not marked")) + ismarked(io) || throw(ArgumentError("$T not marked")) m = io.mark seek(io, m) io.mark = -1 # must be after seek, or seek may fail diff --git a/base/iobuffer.jl b/base/iobuffer.jl index 253f42e1d3a402..5a09c36d8d873b 100644 --- a/base/iobuffer.jl +++ b/base/iobuffer.jl @@ -16,7 +16,7 @@ mutable struct GenericIOBuffer{T<:AbstractVector{UInt8}} <: IO function GenericIOBuffer{T}(data::T, readable::Bool, writable::Bool, seekable::Bool, append::Bool, maxsize::Integer) where T<:AbstractVector{UInt8} - @assert !has_offset_axes(data) + require_one_based_indexing(data) new(data,readable,writable,seekable,append,length(data),maxsize,1,-1) end end @@ -183,7 +183,7 @@ function read(from::GenericIOBuffer, T::Union{Type{Int16},Type{UInt16},Type{Int3 end function read_sub(from::GenericIOBuffer, a::AbstractArray{T}, offs, nel) where T - @assert !has_offset_axes(a) + require_one_based_indexing(a) from.readable || throw(ArgumentError("read failed, IOBuffer is not readable")) if offs+nel-1 > length(a) || offs < 1 || nel < 0 throw(BoundsError()) @@ -425,7 +425,7 @@ function unsafe_write(to::GenericIOBuffer, p::Ptr{UInt8}, nb::UInt) end function write_sub(to::GenericIOBuffer, a::AbstractArray{UInt8}, offs, nel) - @assert !has_offset_axes(a) + require_one_based_indexing(a) if offs+nel-1 > length(a) || offs < 1 || nel < 0 throw(BoundsError()) end @@ -505,7 +505,7 @@ end # copy-free crc32c of IOBuffer: function _crc32c(io::IOBuffer, nb::Integer, crc::UInt32=0x00000000) - nb < 0 && throw(ArgumentError("number of bytes to checksum must be ≥ 0")) + nb < 0 && throw(ArgumentError("number of bytes to checksum must be ≥ 0, got $nb")) io.readable || throw(ArgumentError("read failed, IOBuffer is not readable")) n = min(nb, bytesavailable(io)) n == 0 && return crc diff --git a/base/iostream.jl b/base/iostream.jl index dbacbd888bc144..f518f1e9fac658 100644 --- a/base/iostream.jl +++ b/base/iostream.jl @@ -4,6 +4,12 @@ const sizeof_ios_t = Int(ccall(:jl_sizeof_ios_t, Cint, ())) +""" + IOStream + +A buffered IO stream wrapping an OS file descriptor. +Mostly used to represent files returned by [`open`](@ref). +""" mutable struct IOStream <: IO handle::Ptr{Cvoid} ios::Array{UInt8,1} @@ -199,7 +205,7 @@ eof(s::IOStream) = ccall(:ios_eof_blocking, Cint, (Ptr{Cvoid},), s.ios)!=0 """ fdio([name::AbstractString, ]fd::Integer[, own::Bool=false]) -> IOStream -Create an `IOStream` object from an integer file descriptor. If `own` is `true`, closing +Create an [`IOStream`](@ref) object from an integer file descriptor. If `own` is `true`, closing this object will close the underlying descriptor. By default, an `IOStream` is closed when it is garbage collected. `name` allows you to associate the descriptor with a named file. """ @@ -446,20 +452,21 @@ function readbytes_all!(s::IOStream, b::Array{UInt8}, nb) eof(s) && break end if lb > olb && lb > nr - resize!(b, nr) # shrink to just contain input data if was resized + resize!(b, max(olb, nr)) # shrink to just contain input data if was resized end return nr end function readbytes_some!(s::IOStream, b::Array{UInt8}, nb) - olb = lb = length(b) - if nb > lb + olb = length(b) + if nb > olb resize!(b, nb) end nr = GC.@preserve b Int(ccall(:ios_read, Csize_t, (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t), s.ios, pointer(b), nb)) + lb = length(b) if lb > olb && lb > nr - resize!(b, nr) + resize!(b, max(olb, nr)) # shrink to just contain input data if was resized end return nr end @@ -471,7 +478,10 @@ Read at most `nb` bytes from `stream` into `b`, returning the number of bytes re The size of `b` will be increased if needed (i.e. if `nb` is greater than `length(b)` and enough bytes could be read), but it will never be decreased. -See [`read`](@ref) for a description of the `all` option. +If `all` is `true` (the default), this function will block repeatedly trying to read all +requested bytes, until an error or end-of-file occurs. If `all` is `false`, at most one +`read` call is performed, and the amount of data returned is device-dependent. Note that not +all stream types support the `all` option. """ function readbytes!(s::IOStream, b::Array{UInt8}, nb=length(b); all::Bool=true) return all ? readbytes_all!(s, b, nb) : readbytes_some!(s, b, nb) @@ -503,7 +513,9 @@ requested bytes, until an error or end-of-file occurs. If `all` is `false`, at m all stream types support the `all` option. """ function read(s::IOStream, nb::Integer; all::Bool=true) - b = Vector{UInt8}(undef, nb) + # When all=false we have to allocate a buffer of the requested size upfront + # since a single call will be made + b = Vector{UInt8}(undef, all && nb == typemax(Int) ? 1024 : nb) nr = readbytes!(s, b, nb, all=all) resize!(b, nr) end diff --git a/base/irrationals.jl b/base/irrationals.jl index efce4669694c56..b147959667abfa 100644 --- a/base/irrationals.jl +++ b/base/irrationals.jl @@ -17,7 +17,11 @@ symbol `sym`. """ struct Irrational{sym} <: AbstractIrrational end -show(io::IO, x::Irrational{sym}) where {sym} = print(io, "$sym = $(string(float(x))[1:15])...") +show(io::IO, x::Irrational{sym}) where {sym} = print(io, sym) + +function show(io::IO, ::MIME"text/plain", x::Irrational{sym}) where {sym} + print(io, sym, " = ", string(float(x))[1:15], "...") +end promote_rule(::Type{<:AbstractIrrational}, ::Type{Float16}) = Float16 promote_rule(::Type{<:AbstractIrrational}, ::Type{Float32}) = Float32 @@ -176,8 +180,10 @@ big(::Type{<:AbstractIrrational}) = BigFloat # align along = for nice Array printing function alignment(io::IO, x::AbstractIrrational) - ctx = IOContext(io, :compact=>true) - m = match(r"^(.*?)(=.*)$", sprint(show, x, context=ctx, sizehint=0)) - m === nothing ? (length(sprint(show, x, context=ctx, sizehint=0)), 0) : + m = match(r"^(.*?)(=.*)$", sprint(show, x, context=io, sizehint=0)) + m === nothing ? (length(sprint(show, x, context=io, sizehint=0)), 0) : (length(m.captures[1]), length(m.captures[2])) end + +# inv +inv(x::AbstractIrrational) = 1/x diff --git a/base/iterators.jl b/base/iterators.jl index b329a6c6f9effa..bebe8ac1701f15 100644 --- a/base/iterators.jl +++ b/base/iterators.jl @@ -115,7 +115,7 @@ and `x` is the `i`th value from the given iterator. It's useful when you need not only the values `x` over which you are iterating, but also the number of iterations so far. Note that `i` may not be valid for indexing `iter`; it's also possible that `x != iter[i]`, if `iter` -has indices that do not start at 1. See the `enumerate(IndexLinear(), +has indices that do not start at 1. See the `pairs(IndexLinear(), iter)` method if you want to ensure that `i` is an index. # Examples @@ -462,6 +462,7 @@ julia> collect(Iterators.rest([1,2,3,4], 2)) ``` """ rest(itr,state) = Rest(itr,state) +rest(itr::Rest,state) = Rest(itr.itr,state) rest(itr) = itr """ @@ -1089,10 +1090,9 @@ end @inline peek(s::Stateful, sentinel=nothing) = s.nextvalstate !== nothing ? s.nextvalstate[1] : sentinel @inline iterate(s::Stateful, state=nothing) = s.nextvalstate === nothing ? nothing : (popfirst!(s), nothing) -IteratorSize(::Type{Stateful{VS,T}} where VS) where {T} = - isa(IteratorSize(T), SizeUnknown) ? SizeUnknown() : HasLength() +IteratorSize(::Type{Stateful{T,VS}}) where {T,VS} = IteratorSize(T) isa HasShape ? HasLength() : IteratorSize(T) eltype(::Type{Stateful{T, VS}} where VS) where {T} = eltype(T) -IteratorEltype(::Type{Stateful{VS,T}} where VS) where {T} = IteratorEltype(T) +IteratorEltype(::Type{Stateful{T,VS}}) where {T,VS} = IteratorEltype(T) length(s::Stateful) = length(s.itr) - s.taken end diff --git a/base/libc.jl b/base/libc.jl index 18486d43d4eb82..1ffe8a92aabfbf 100644 --- a/base/libc.jl +++ b/base/libc.jl @@ -5,7 +5,7 @@ module Libc Interface to libc, the C standard library. """ Libc -import Base: transcode +import Base: transcode, windowserror import Core.Intrinsics: bitcast export FILE, TmStruct, strftime, strptime, getpid, gethostname, free, malloc, calloc, realloc, @@ -54,7 +54,7 @@ if Sys.iswindows() status = ccall(:DuplicateHandle, stdcall, Int32, (Ptr{Cvoid}, WindowsRawSocket, Ptr{Cvoid}, Ptr{WindowsRawSocket}, UInt32, Int32, UInt32), my_process, src, my_process, new_handle, 0, false, DUPLICATE_SAME_ACCESS) - status == 0 && error("dup failed: $(FormatMessage())") + windowserror("dup failed", status == 0) return new_handle[] end function dup(src::WindowsRawSocket, target::RawFD) @@ -122,6 +122,16 @@ elseif Sys.iswindows() else error("systemsleep undefined for this OS") end +""" + systemsleep(s::Real) + +Suspends execution for `s` seconds. +This function does not yield to Julia's scheduler and therefore blocks +the Julia thread that it is running on for the duration of the sleep time. + +See also: [`sleep`](@ref) +""" +systemsleep struct TimeVal sec::Int64 diff --git a/base/libuv.jl b/base/libuv.jl index 31ea116fba0a6e..a24434f8078fce 100644 --- a/base/libuv.jl +++ b/base/libuv.jl @@ -89,16 +89,8 @@ uv_error(prefix::AbstractString, c::Integer) = c < 0 ? throw(_UVError(prefix,c)) eventloop() = uv_eventloop::Ptr{Cvoid} #mkNewEventLoop() = ccall(:jl_new_event_loop,Ptr{Cvoid},()) # this would probably be fine, but is nowhere supported -function run_event_loop() - ccall(:jl_run_event_loop,Cvoid,(Ptr{Cvoid},),eventloop()) -end -function process_events(block::Bool) - loop = eventloop() - if block - return ccall(:jl_run_once,Int32,(Ptr{Cvoid},),loop) - else - return ccall(:jl_process_events,Int32,(Ptr{Cvoid},),loop) - end +function process_events() + return ccall(:jl_process_events, Int32, (Ptr{Cvoid},), eventloop()) end function uv_alloc_buf end diff --git a/base/linked_list.jl b/base/linked_list.jl new file mode 100644 index 00000000000000..195d2d02d61f1f --- /dev/null +++ b/base/linked_list.jl @@ -0,0 +1,151 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +mutable struct InvasiveLinkedList{T} + # Invasive list requires that T have a field `.next >: U{T, Nothing}` and `.queue >: U{ILL{T}, Nothing}` + head::Union{T, Nothing} + tail::Union{T, Nothing} + InvasiveLinkedList{T}() where {T} = new{T}(nothing, nothing) +end + +#const list_append!! = append! +#const list_deletefirst! = delete! + +eltype(::Type{<:InvasiveLinkedList{T}}) where {T} = @isdefined(T) ? T : Any + +iterate(q::InvasiveLinkedList) = (h = q.head; h === nothing ? nothing : (h, h)) +iterate(q::InvasiveLinkedList{T}, v::T) where {T} = (h = v.next; h === nothing ? nothing : (h, h)) + +isempty(q::InvasiveLinkedList) = (q.head === nothing) + +function length(q::InvasiveLinkedList) + i = 0 + head = q.head + while head !== nothing + i += 1 + head = head.next + end + return i +end + +function list_append!!(q::InvasiveLinkedList{T}, q2::InvasiveLinkedList{T}) where T + q === q2 && error("can't append list to itself") + head2 = q2.head + if head2 !== nothing + tail2 = q2.tail::T + q2.head = nothing + q2.tail = nothing + tail = q.tail + q.tail = tail2 + if tail === nothing + q.head = head2 + else + tail.next = head2 + end + while head2 !== nothing + head2.queue = q + head2 = head2.next + end + end + return q +end + +function push!(q::InvasiveLinkedList{T}, val::T) where T + val.queue === nothing || error("val already in a list") + val.queue = q + tail = q.tail + if tail === nothing + q.head = q.tail = val + else + tail.next = val + q.tail = val + end + return q +end + +function pushfirst!(q::InvasiveLinkedList{T}, val::T) where T + val.queue === nothing || error("val already in a list") + val.queue = q + head = q.head + if head === nothing + q.head = q.tail = val + else + val.next = head + q.head = val + end + return q +end + +function pop!(q::InvasiveLinkedList{T}) where {T} + val = q.tail::T + list_deletefirst!(q, val) # expensive! + return val +end + +function popfirst!(q::InvasiveLinkedList{T}) where {T} + val = q.head::T + list_deletefirst!(q, val) # cheap + return val +end + +function list_deletefirst!(q::InvasiveLinkedList{T}, val::T) where T + val.queue === q || return + head = q.head::T + if head === val + if q.tail::T === val + q.head = q.tail = nothing + else + q.head = val.next::T + end + else + head_next = head.next + while head_next !== val + head = head_next + head_next = head.next + end + if q.tail::T === val + head.next = nothing + q.tail = head + else + head.next = val.next::T + end + end + val.next = nothing + val.queue = nothing + return q +end + +#function list_deletefirst!(q::Array{T}, val::T) where T +# i = findfirst(isequal(val), q) +# i === nothing || deleteat!(q, i) +# return q +#end + + +mutable struct LinkedListItem{T} + # Adapter class to use any `T` in a LinkedList + next::Union{LinkedListItem{T}, Nothing} + queue::Union{InvasiveLinkedList{LinkedListItem{T}}, Nothing} + value::T + LinkedListItem{T}(value::T) where {T} = new{T}(nothing, nothing, value) +end +const LinkedList{T} = InvasiveLinkedList{LinkedListItem{T}} + +# delegate methods, as needed +eltype(::Type{<:LinkedList{T}}) where {T} = @isdefined(T) ? T : Any +iterate(q::LinkedList) = (h = q.head; h === nothing ? nothing : (h.value, h)) +iterate(q::InvasiveLinkedList{LLT}, v::LLT) where {LLT<:LinkedListItem} = (h = v.next; h === nothing ? nothing : (h.value, h)) +push!(q::LinkedList{T}, val::T) where {T} = push!(q, LinkedListItem{T}(val)) +pushfirst!(q::LinkedList{T}, val::T) where {T} = pushfirst!(q, LinkedListItem{T}(val)) +pop!(q::LinkedList) = invoke(pop!, Tuple{InvasiveLinkedList,}, q).value +popfirst!(q::LinkedList) = invoke(popfirst!, Tuple{InvasiveLinkedList,}, q).value +function list_deletefirst!(q::LinkedList{T}, val::T) where T + h = q.head + while h !== nothing + if isequal(h.value, val) + list_deletefirst!(q, h) + break + end + h = h.next + end + return q +end diff --git a/base/loading.jl b/base/loading.jl index 51201b98b61813..41de31ef4bd0cd 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -756,7 +756,7 @@ In a module, declare that the file specified by `path` (relative or absolute) is dependency for precompilation; that is, the module will need to be recompiled if this file changes. -This is only needed if your module depends on a file that is not used via `include`. It has +This is only needed if your module depends on a file that is not used via [`include`](@ref). It has no effect outside of compilation. """ function include_dependency(path::AbstractString) @@ -797,22 +797,23 @@ const modules_warned_for = Set{PkgId}() """ require(into::Module, module::Symbol) -This function is part of the implementation of `using` / `import`, if a module is not +This function is part of the implementation of [`using`](@ref) / [`import`](@ref), if a module is not already defined in `Main`. It can also be called directly to force reloading a module, regardless of whether it has been loaded before (for example, when interactively developing libraries). Loads a source file, in the context of the `Main` module, on every active node, searching standard locations for files. `require` is considered a top-level operation, so it sets the -current `include` path but does not use it to search for files (see help for `include`). +current `include` path but does not use it to search for files (see help for [`include`](@ref)). This function is typically used to load library code, and is implicitly called by `using` to load packages. When searching for files, `require` first looks for package code in the global array -`LOAD_PATH`. `require` is case-sensitive on all platforms, including those with +[`LOAD_PATH`](@ref). `require` is case-sensitive on all platforms, including those with case-insensitive filesystems like macOS and Windows. -For more details regarding code loading, see the manual. +For more details regarding code loading, see the manual sections on [modules](@ref modules) and +[parallel computing](@ref code-availability). """ function require(into::Module, mod::Symbol) uuidkey = identify_package(into, String(mod)) @@ -959,7 +960,10 @@ function _require(pkg::PkgId) # or if the require search declared it was pre-compiled before (and therefore is expected to still be pre-compilable) cachefile = compilecache(pkg, path) if isa(cachefile, Exception) - if !precompilableerror(cachefile) + if precompilableerror(cachefile) + verbosity = isinteractive() ? CoreLogging.Info : CoreLogging.Debug + @logmsg verbosity "Skipping precompilation since __precompile__(false). Importing $pkg." + else @warn "The call to compilecache failed to create a usable precompiled cache file for $pkg" exception=m end # fall-through to loading the file locally @@ -1003,7 +1007,7 @@ end """ include_string(m::Module, code::AbstractString, filename::AbstractString="string") -Like `include`, except reads code from the given string rather than from a file. +Like [`include`](@ref), except reads code from the given string rather than from a file. """ include_string(m::Module, txt::String, fname::String) = ccall(:jl_load_file_string, Any, (Ptr{UInt8}, Csize_t, Cstring, Any), @@ -1050,7 +1054,7 @@ end Base.include([m::Module,] path::AbstractString) Evaluate the contents of the input source file in the global scope of module `m`. -Every module (except those defined with `baremodule`) has its own 1-argument +Every module (except those defined with [`baremodule`](@ref)) has its own 1-argument definition of `include`, which evaluates the file in that module. Returns the result of the last evaluated expression of the input file. During including, a task-local include path is set to the directory containing the file. Nested calls to @@ -1062,7 +1066,7 @@ Base.include # defined in sysimg.jl """ evalfile(path::AbstractString, args::Vector{String}=String[]) -Load the file using [`Base.include`](@ref), evaluate all expressions, +Load the file using [`include`](@ref), evaluate all expressions, and return the value of the last one. """ function evalfile(path::AbstractString, args::Vector{String}=String[]) @@ -1424,5 +1428,6 @@ Return the current working directory if run from a REPL or if evaluated by `juli """ macro __DIR__() __source__.file === nothing && return nothing - return abspath(dirname(String(__source__.file))) + _dirname = dirname(String(__source__.file)) + return isempty(_dirname) ? pwd() : abspath(_dirname) end diff --git a/base/lock.jl b/base/lock.jl index aef65052cdf8e4..76dca392b7775f 100644 --- a/base/lock.jl +++ b/base/lock.jl @@ -177,6 +177,8 @@ end wait(c::Condition) end +const ThreadSynchronizer = GenericCondition{Threads.SpinLock} + """ Semaphore(sem_size) diff --git a/base/locks-mt.jl b/base/locks-mt.jl index 397d41543da9aa..f7d1a09771d4ef 100644 --- a/base/locks-mt.jl +++ b/base/locks-mt.jl @@ -1,8 +1,6 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -import .Base: _uv_hook_close, unsafe_convert, - lock, trylock, unlock, islocked, wait, notify, - AbstractLock +import .Base: unsafe_convert, lock, trylock, unlock, islocked, wait, notify, AbstractLock # Important Note: these low-level primitives defined here # are typically not for general usage @@ -88,14 +86,14 @@ mutable struct Mutex <: AbstractLock function Mutex() m = new(zero(Int16), Libc.malloc(UV_MUTEX_SIZE)) ccall(:uv_mutex_init, Cvoid, (Ptr{Cvoid},), m.handle) - finalizer(_uv_hook_close, m) + finalizer(mutex_destroy, m) return m end end unsafe_convert(::Type{Ptr{Cvoid}}, m::Mutex) = m.handle -function _uv_hook_close(x::Mutex) +function mutex_destroy(x::Mutex) h = x.handle if h != C_NULL x.handle = C_NULL diff --git a/base/logging.jl b/base/logging.jl index ef6ca0380d12fe..2aaa0b2d0b5c32 100644 --- a/base/logging.jl +++ b/base/logging.jl @@ -33,7 +33,7 @@ abstract type AbstractLogger ; end Log a message to `logger` at `level`. The logical location at which the message was generated is given by module `_module` and `group`; the source -location by `file` and `line`. `id` is an arbitrary unique `Symbol` to be used +location by `file` and `line`. `id` is an arbitrary unique [`Symbol`](@ref) to be used as a key to identify the log statement when filtering. """ function handle_message end @@ -150,7 +150,7 @@ formatted as markdown when presented. The optional list of `key=value` pairs supports arbitrary user defined metadata which will be passed through to the logging backend as part of the log record. If only a `value` expression is supplied, a key representing the -expression will be generated using `Symbol`. For example, `x` becomes `x=x`, +expression will be generated using [`Symbol`](@ref). For example, `x` becomes `x=x`, and `foo(10)` becomes `Symbol("foo(10)")=foo(10)`. For splatting a list of key value pairs, use the normal splatting syntax, `@info "blah" kws...`. diff --git a/base/math.jl b/base/math.jl index 4f9be26562fb69..0a9fac443c6026 100644 --- a/base/math.jl +++ b/base/math.jl @@ -98,7 +98,8 @@ macro horner(x, p...) for i = length(p)-1:-1:1 ex = :(muladd(t, $ex, $(esc(p[i])))) end - Expr(:block, :(t = $(esc(x))), ex) + ex = quote local r = $ex end # structure this to add exactly one line number node for the macro + return Expr(:block, :(local t = $(esc(x))), ex, :r) end # Evaluate p[1] + z*p[2] + z^2*p[3] + ... + z^(n-1)*p[n]. This uses diff --git a/base/meta.jl b/base/meta.jl index 53abd67e270bfd..0d572ab1f6a99f 100644 --- a/base/meta.jl +++ b/base/meta.jl @@ -128,10 +128,6 @@ function parse(str::AbstractString, pos::Integer; greedy::Bool=true, raise::Bool if raise && isa(ex,Expr) && ex.head === :error throw(ParseError(ex.args[1])) end - if ex === () - raise && throw(ParseError("end of input")) - ex = Expr(:error, "end of input") - end return ex, pos+1 # C is zero-based, Julia is 1-based end @@ -297,6 +293,6 @@ end _instantiate_type_in_env(x, spsig, spvals) = ccall(:jl_instantiate_type_in_env, Any, (Any, Any, Ptr{Any}), x, spsig, spvals) -is_meta_expr_head(head::Symbol) = (head === :inbounds || head === :boundscheck || head === :meta || head === :simdloop) +is_meta_expr_head(head::Symbol) = (head === :inbounds || head === :boundscheck || head === :meta || head === :loopinfo) end # module diff --git a/base/methodshow.jl b/base/methodshow.jl index 236b3fd1665ff3..1dd02c9df5ac2e 100644 --- a/base/methodshow.jl +++ b/base/methodshow.jl @@ -43,12 +43,9 @@ function argtype_decl(env, n, sig::DataType, i::Int, nargs, isva::Bool) # -> (ar end function method_argnames(m::Method) - if !isdefined(m, :source) && isdefined(m, :generator) - return m.generator.argnames - end - argnames = Vector{Any}(undef, m.nargs) - ccall(:jl_fill_argnames, Cvoid, (Any, Any), m.source, argnames) - return argnames + argnames = ccall(:jl_uncompress_argnames, Vector{Any}, (Any,), m.slot_syms) + isempty(argnames) && return argnames + return argnames[1:m.nargs] end function arg_decl_parts(m::Method) @@ -60,8 +57,8 @@ function arg_decl_parts(m::Method) end file = m.file line = m.line - if isdefined(m, :source) || isdefined(m, :generator) - argnames = method_argnames(m) + argnames = method_argnames(m) + if length(argnames) >= m.nargs show_env = ImmutableDict{Symbol, Any}() for t in tv show_env = ImmutableDict(show_env, :unionall_env => t) @@ -74,26 +71,23 @@ function arg_decl_parts(m::Method) return tv, decls, file, line end +const empty_sym = Symbol("") + function kwarg_decl(m::Method, kwtype::DataType) sig = rewrap_unionall(Tuple{kwtype, Any, unwrap_unionall(m.sig).parameters...}, m.sig) - kwli = ccall(:jl_methtable_lookup, Any, (Any, Any, UInt), kwtype.name.mt, sig, max_world(m)) + kwli = ccall(:jl_methtable_lookup, Any, (Any, Any, UInt), kwtype.name.mt, sig, get_world_counter()) if kwli !== nothing kwli = kwli::Method - if isdefined(kwli, :source) - src = kwli.source - nslots = ccall(:jl_ast_nslots, Int, (Any,), src) - slotnames = Vector{Any}(undef, nslots) - ccall(:jl_fill_argnames, Cvoid, (Any, Any), src, slotnames) - kws = filter(x -> !('#' in string(x)), slotnames[(kwli.nargs + 1):end]) - # ensure the kwarg... is always printed last. The order of the arguments are not - # necessarily the same as defined in the function - i = findfirst(x -> endswith(string(x), "..."), kws) - if i !== nothing - push!(kws, kws[i]) - deleteat!(kws, i) - end - return kws + slotnames = ccall(:jl_uncompress_argnames, Vector{Any}, (Any,), kwli.slot_syms) + kws = filter(x -> !(x === empty_sym || '#' in string(x)), slotnames[(kwli.nargs + 1):end]) + # ensure the kwarg... is always printed last. The order of the arguments are not + # necessarily the same as defined in the function + i = findfirst(x -> endswith(string(x), "..."), kws) + if i !== nothing + push!(kws, kws[i]) + deleteat!(kws, i) end + return kws end return () end @@ -118,6 +112,12 @@ function show_method_params(io::IO, tv) end end +# In case the line numbers in the source code have changed since the code was compiled, +# allow packages to set a callback function that corrects them. +# (Used by Revise and perhaps other packages.) +default_methodloc(method::Method) = method.file, method.line +const methodloc_callback = Ref{Function}(default_methodloc) + function show(io::IO, m::Method; kwtype::Union{DataType, Nothing}=nothing) tv, decls, file, line = arg_decl_parts(m) sig = unwrap_unionall(m.sig) @@ -156,6 +156,10 @@ function show(io::IO, m::Method; kwtype::Union{DataType, Nothing}=nothing) show_method_params(io, tv) print(io, " in ", m.module) if line > 0 + try + file, line = invokelatest(methodloc_callback[], m) + catch + end print(io, " at ", file, ":", line) end end @@ -179,12 +183,17 @@ function show_method_table(io::IO, ms::MethodList, max::Int=-1, header::Bool=tru resize!(LAST_SHOWN_LINE_INFOS, 0) for meth in ms - if max==-1 || n 0 + try + file, line = invokelatest(methodloc_callback[], m) + catch + end u = url(m) if isempty(u) print(io, " at ", file, ":", line) @@ -330,7 +343,12 @@ function show(io::IO, mime::MIME"text/plain", mt::AbstractVector{Method}) first = false print(io, "[$(i)] ") show(io, m) - push!(LAST_SHOWN_LINE_INFOS, (string(m.file), m.line)) + file, line = m.file, m.line + try + file, line = invokelatest(methodloc_callback[], m) + catch + end + push!(LAST_SHOWN_LINE_INFOS, (string(file), line)) end end diff --git a/base/missing.jl b/base/missing.jl index 384771ca6ed404..e8db30d7c2f43e 100644 --- a/base/missing.jl +++ b/base/missing.jl @@ -74,15 +74,16 @@ isapprox(::Missing, ::Any; kwargs...) = missing isapprox(::Any, ::Missing; kwargs...) = missing # Unary operators/functions -for f in (:(!), :(~), :(+), :(-), :(identity), :(zero), :(one), :(oneunit), +for f in (:(!), :(~), :(+), :(-), :(zero), :(one), :(oneunit), :(isfinite), :(isinf), :(isodd), :(isinteger), :(isreal), :(isnan), :(iszero), :(transpose), :(adjoint), :(float), :(conj), :(abs), :(abs2), :(iseven), :(ispow2), - :(real), :(imag), :(sign)) + :(real), :(imag), :(sign), :(inv)) @eval ($f)(::Missing) = missing end for f in (:(Base.zero), :(Base.one), :(Base.oneunit)) + @eval ($f)(::Type{Missing}) = missing @eval function $(f)(::Type{Union{T, Missing}}) where T T === Any && throw(MethodError($f, (Any,))) # To prevent StackOverflowError $f(T) @@ -107,16 +108,25 @@ max(::Missing, ::Any) = missing max(::Any, ::Missing) = missing # Rounding and related functions -for f in (:(ceil), :(floor), :(round), :(trunc)) +round(::Missing, ::RoundingMode=RoundNearest; sigdigits::Integer=0, digits::Integer=0, base::Integer=0) = missing +round(::Type{>:Missing}, ::Missing, ::RoundingMode=RoundNearest) = missing +round(::Type{T}, ::Missing, ::RoundingMode=RoundNearest) where {T} = + throw(MissingException("cannot convert a missing value to type $T: use Union{$T, Missing} instead")) +round(::Type{T}, x::Any, r::RoundingMode=RoundNearest) where {T>:Missing} = round(nonmissingtype(T), x, r) +# to fix ambiguities +round(::Type{T}, x::Rational, r::RoundingMode=RoundNearest) where {T>:Missing} = round(nonmissingtype(T), x, r) +round(::Type{T}, x::Rational{Bool}, r::RoundingMode=RoundNearest) where {T>:Missing} = round(nonmissingtype(T), x, r) + +# Handle ceil, floor, and trunc separately as they have no RoundingMode argument +for f in (:(ceil), :(floor), :(trunc)) @eval begin - ($f)(::Missing, digits::Integer=0, base::Integer=0) = missing + ($f)(::Missing; sigdigits::Integer=0, digits::Integer=0, base::Integer=0) = missing ($f)(::Type{>:Missing}, ::Missing) = missing ($f)(::Type{T}, ::Missing) where {T} = throw(MissingException("cannot convert a missing value to type $T: use Union{$T, Missing} instead")) ($f)(::Type{T}, x::Any) where {T>:Missing} = $f(nonmissingtype(T), x) # to fix ambiguities ($f)(::Type{T}, x::Rational) where {T>:Missing} = $f(nonmissingtype(T), x) - ($f)(::Type{T}, x::Rational{Bool}) where {T>:Missing} = $f(nonmissingtype(T), x) end end @@ -153,6 +163,9 @@ float(A::AbstractArray{Missing}) = A skipmissing(itr) Return an iterator over the elements in `itr` skipping [`missing`](@ref) values. +The returned object can be indexed using indices of `itr` if the latter is indexable. +Indices corresponding to missing values are not valid: they are skipped by [`keys`](@ref) +and [`eachindex`](@ref), and a `MissingException` is thrown when trying to use them. Use [`collect`](@ref) to obtain an `Array` containing the non-`missing` values in `itr`. Note that even if `itr` is a multidimensional array, the result will always @@ -161,9 +174,27 @@ of the input. # Examples ```jldoctest -julia> sum(skipmissing([1, missing, 2])) +julia> x = skipmissing([1, missing, 2]) +Base.SkipMissing{Array{Union{Missing, Int64},1}}(Union{Missing, Int64}[1, missing, 2]) + +julia> sum(x) 3 +julia> x[1] +1 + +julia> x[2] +ERROR: MissingException: the value at index (2,) is missing +[...] + +julia> argmax(x) +3 + +julia> collect(keys(x)) +2-element Array{Int64,1}: + 1 + 3 + julia> collect(skipmissing([1, missing, 2])) 2-element Array{Int64,1}: 1 @@ -196,6 +227,17 @@ function iterate(itr::SkipMissing, state...) item, state end +IndexStyle(::Type{<:SkipMissing{T}}) where {T} = IndexStyle(T) +eachindex(itr::SkipMissing) = + Iterators.filter(i -> @inbounds(itr.x[i]) !== missing, eachindex(itr.x)) +keys(itr::SkipMissing) = + Iterators.filter(i -> @inbounds(itr.x[i]) !== missing, keys(itr.x)) +@propagate_inbounds function getindex(itr::SkipMissing, I...) + v = itr.x[I...] + v === missing && throw(MissingException("the value at index $I is missing")) + v +end + # Optimized mapreduce implementation # The generic method is faster when !(eltype(A) >: Missing) since it does not need # additional loops to identify the two first non-missing values of each block @@ -287,6 +329,37 @@ mapreduce_impl(f, op, A::SkipMissing, ifirst::Integer, ilast::Integer) = end end +""" + filter(f, itr::SkipMissing{<:AbstractArray}) + +Return a vector similar to the array wrapped by the given `SkipMissing` iterator +but with all missing elements and those for which `f` returns `false` removed. + +!!! compat "Julia 1.2" + This method requires Julia 1.2 or later. + +# Examples +```jldoctest +julia> x = [1 2; missing 4] +2×2 Array{Union{Missing, Int64},2}: + 1 2 + missing 4 + +julia> filter(isodd, skipmissing(x)) +1-element Array{Int64,1}: + 1 +``` +""" +function filter(f, itr::SkipMissing{<:AbstractArray}) + y = similar(itr.x, eltype(itr), 0) + for xi in itr.x + if xi !== missing && f(xi) + push!(y, xi) + end + end + y +end + """ coalesce(x, y...) diff --git a/base/mpfr.jl b/base/mpfr.jl index aaf3e9783bf62c..c08366b8b75a75 100644 --- a/base/mpfr.jl +++ b/base/mpfr.jl @@ -74,7 +74,7 @@ function convert(::Type{RoundingMode}, r::MPFRRoundingMode) elseif r == MPFRRoundFromZero return RoundFromZero else - throw(ArgumentError("invalid MPFR rounding mode code: $c")) + throw(ArgumentError("invalid MPFR rounding mode code: $r")) end end @@ -331,7 +331,7 @@ function BigInt(x::BigFloat) end function (::Type{T})(x::BigFloat) where T<:Integer - isinteger(x) || throw(InexactError(Symbol(string(T)), T, x)) + isinteger(x) || throw(InexactError(nameof(T), T, x)) trunc(T,x) end @@ -355,6 +355,8 @@ promote_rule(::Type{BigFloat}, ::Type{<:AbstractFloat}) = BigFloat big(::Type{<:AbstractFloat}) = BigFloat +big(x::AbstractFloat) = convert(BigFloat, x) + function (::Type{Rational{BigInt}})(x::AbstractFloat) isnan(x) && return zero(BigInt) // zero(BigInt) isinf(x) && return copysign(one(BigInt),x) // zero(BigInt) diff --git a/base/multidimensional.jl b/base/multidimensional.jl index f4589ca2c6d967..4b9a3dd747bf1b 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -95,6 +95,9 @@ module IteratorsMD # access to index tuple Tuple(index::CartesianIndex) = index.I + # equality + Base.:(==)(a::CartesianIndex{N}, b::CartesianIndex{N}) where N = a.I == b.I + # zeros and ones zero(::CartesianIndex{N}) where {N} = zero(CartesianIndex{N}) zero(::Type{CartesianIndex{N}}) where {N} = CartesianIndex(ntuple(x -> 0, Val(N))) @@ -142,11 +145,15 @@ module IteratorsMD # nextind and prevind with CartesianIndex function Base.nextind(a::AbstractArray{<:Any,N}, i::CartesianIndex{N}) where {N} iter = CartesianIndices(axes(a)) - return CartesianIndex(inc(i.I, first(iter).I, last(iter).I)) + # might overflow + I = inc(i.I, first(iter).I, last(iter).I) + return I end function Base.prevind(a::AbstractArray{<:Any,N}, i::CartesianIndex{N}) where {N} iter = CartesianIndices(axes(a)) - return CartesianIndex(dec(i.I, last(iter).I, first(iter).I)) + # might underflow + I = dec(i.I, last(iter).I, first(iter).I) + return I end # Iteration over the elements of CartesianIndex cannot be supported until its length can be inferred, @@ -334,20 +341,30 @@ module IteratorsMD iterfirst, iterfirst end @inline function iterate(iter::CartesianIndices, state) - nextstate = CartesianIndex(inc(state.I, first(iter).I, last(iter).I)) - nextstate.I[end] > last(iter.indices[end]) && return nothing - nextstate, nextstate + valid, I = __inc(state.I, first(iter).I, last(iter).I) + valid || return nothing + return CartesianIndex(I...), CartesianIndex(I...) end # increment & carry - @inline inc(::Tuple{}, ::Tuple{}, ::Tuple{}) = () - @inline inc(state::Tuple{Int}, start::Tuple{Int}, stop::Tuple{Int}) = (state[1]+1,) @inline function inc(state, start, stop) + _, I = __inc(state, start, stop) + return CartesianIndex(I...) + end + + # increment post check to avoid integer overflow + @inline __inc(::Tuple{}, ::Tuple{}, ::Tuple{}) = false, () + @inline function __inc(state::Tuple{Int}, start::Tuple{Int}, stop::Tuple{Int}) + valid = state[1] < stop[1] + return valid, (state[1]+1,) + end + + @inline function __inc(state, start, stop) if state[1] < stop[1] - return (state[1]+1,tail(state)...) + return true, (state[1]+1, tail(state)...) end - newtail = inc(tail(state), tail(start), tail(stop)) - (start[1], newtail...) + valid, I = __inc(tail(state), tail(start), tail(stop)) + return valid, (start[1], I...) end # 0-d cartesian ranges are special-cased to iterate once and only once @@ -414,21 +431,32 @@ module IteratorsMD iterfirst, iterfirst end @inline function iterate(r::Reverse{<:CartesianIndices}, state) - nextstate = CartesianIndex(dec(state.I, last(r.itr).I, first(r.itr).I)) - nextstate.I[end] < first(r.itr.indices[end]) && return nothing - nextstate, nextstate + valid, I = __dec(state.I, last(r.itr).I, first(r.itr).I) + valid || return nothing + return CartesianIndex(I...), CartesianIndex(I...) end # decrement & carry - @inline dec(::Tuple{}, ::Tuple{}, ::Tuple{}) = () - @inline dec(state::Tuple{Int}, start::Tuple{Int}, stop::Tuple{Int}) = (state[1]-1,) @inline function dec(state, start, stop) + _, I = __dec(state, start, stop) + return CartesianIndex(I...) + end + + # decrement post check to avoid integer overflow + @inline __dec(::Tuple{}, ::Tuple{}, ::Tuple{}) = false, () + @inline function __dec(state::Tuple{Int}, start::Tuple{Int}, stop::Tuple{Int}) + valid = state[1] > stop[1] + return valid, (state[1]-1,) + end + + @inline function __dec(state, start, stop) if state[1] > stop[1] - return (state[1]-1,tail(state)...) + return true, (state[1]-1, tail(state)...) end - newtail = dec(tail(state), tail(start), tail(stop)) - (start[1], newtail...) + valid, I = __dec(tail(state), tail(start), tail(stop)) + return valid, (start[1], I...) end + # 0-d cartesian ranges are special-cased to iterate once and only once iterate(iter::Reverse{<:CartesianIndices{0}}, state=false) = state ? nothing : (CartesianIndex(), true) @@ -738,7 +766,7 @@ julia> diff(vec(a)) ``` """ function diff(a::AbstractArray{T,N}; dims::Integer) where {T,N} - has_offset_axes(a) && throw(ArgumentError("offset axes unsupported")) + require_one_based_indexing(a) 1 <= dims <= N || throw(ArgumentError("dimension $dims out of range (1:$N)")) r = axes(a) @@ -1371,33 +1399,33 @@ Return unique regions of `A` along dimension `dims`. julia> A = map(isodd, reshape(Vector(1:8), (2,2,2))) 2×2×2 Array{Bool,3}: [:, :, 1] = - true true - false false + 1 1 + 0 0 [:, :, 2] = - true true - false false + 1 1 + 0 0 julia> unique(A) 2-element Array{Bool,1}: - true - false + 1 + 0 julia> unique(A, dims=2) 2×1×2 Array{Bool,3}: [:, :, 1] = - true - false + 1 + 0 [:, :, 2] = - true - false + 1 + 0 julia> unique(A, dims=3) 2×2×1 Array{Bool,3}: [:, :, 1] = - true true - false false + 1 1 + 0 0 ``` """ unique(A::AbstractArray; dims::Union{Colon,Integer} = :) = _unique_dims(A, dims) @@ -1526,7 +1554,7 @@ function _extrema_dims(f, A::AbstractArray, dims) end @noinline function extrema!(f, B, A) - @assert !has_offset_axes(B, A) + require_one_based_indexing(B, A) sA = size(A) sB = size(B) for I in CartesianIndices(sB) diff --git a/base/multimedia.jl b/base/multimedia.jl index 9e17f05d7312b1..d34aa1823d713c 100644 --- a/base/multimedia.jl +++ b/base/multimedia.jl @@ -2,6 +2,8 @@ module Multimedia +import .Base: show, print, convert, repr + export AbstractDisplay, display, pushdisplay, popdisplay, displayable, redisplay, MIME, @MIME_str, istextmime, showable, TextDisplay @@ -11,11 +13,39 @@ export AbstractDisplay, display, pushdisplay, popdisplay, displayable, redisplay # that Julia's dispatch and overloading mechanisms can be used to # dispatch show and to add conversions for new types. -# defined in sysimg.jl for bootstrapping: -# struct MIME{mime} end -# macro MIME_str(s) -import .Base: MIME, @MIME_str -import .Base: show, print, string, convert, repr +""" + MIME + +A type representing a standard internet data format. "MIME" stands for +"Multipurpose Internet Mail Extensions", since the standard was originally +used to describe multimedia attachments to email messages. + +A `MIME` object can be passed as the second argument to [`show`](@ref) to +request output in that format. + +# Examples +```jldoctest +julia> show(stdout, MIME("text/plain"), "hi") +"hi" +``` +""" +struct MIME{mime} end + +""" + @MIME_str + +A convenience macro for writing [`MIME`](@ref) types, typically used when +adding methods to [`show`](@ref). +For example the syntax `show(io::IO, ::MIME"text/html", x::MyType) = ...` +could be used to define how to write an HTML representation of `MyType`. +""" +macro MIME_str(s) + :(MIME{$(Expr(:quote, Symbol(s)))}) +end + +# fallback text/plain representation of any type: +show(io::IO, ::MIME"text/plain", x) = show(io, x) + MIME(s) = MIME{Symbol(s)}() show(io::IO, ::MIME{mime}) where {mime} = print(io, "MIME type ", string(mime)) print(io::IO, ::MIME{mime}) where {mime} = print(io, mime) @@ -172,6 +202,12 @@ end # cannot be displayed. The return value of display(...) is up to the # AbstractDisplay type. +""" + AbstractDisplay + +Abstract supertype for rich display output devices. [`TextDisplay`](@ref) is a subtype +of this. +""" abstract type AbstractDisplay end # it is convenient to accept strings instead of ::MIME diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 66f5a18774b278..eb920f9cbdf73b 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -5,7 +5,7 @@ `NamedTuple`s are, as their name suggests, named [`Tuple`](@ref)s. That is, they're a tuple-like collection of values, where each entry has a unique name, represented as a -`Symbol`. Like `Tuple`s, `NamedTuple`s are immutable; neither the names nor the values +[`Symbol`](@ref). Like `Tuple`s, `NamedTuple`s are immutable; neither the names nor the values can be modified in place after construction. Accessing the value associated with a name in a named tuple can be done using field @@ -56,21 +56,10 @@ Construct a named tuple with the given `names` (a tuple of Symbols) and field ty (a `Tuple` type) from a tuple of values. """ function NamedTuple{names,T}(args::Tuple) where {names, T <: Tuple} - if length(args) == length(names) - if @generated - N = length(names) - types = T.parameters - Expr(:new, :(NamedTuple{names,T}), Any[ :(convert($(types[i]), args[$i])) for i in 1:N ]...) - else - N = length(names) - NT = NamedTuple{names,T} - types = T.parameters - fields = Any[ convert(types[i], args[i]) for i = 1:N ] - ccall(:jl_new_structv, Any, (Any, Ptr{Cvoid}, UInt32), NT, fields, N)::NT - end - else + if length(args) != length(names) throw(ArgumentError("Wrong number of arguments to named tuple constructor.")) end + NamedTuple{names,T}(T(args)) end """ @@ -112,13 +101,7 @@ function convert(::Type{NamedTuple{names,T}}, nt::NamedTuple{names}) where {name end if nameof(@__MODULE__) === :Base - function Tuple(nt::NamedTuple{names}) where {names} - if @generated - return Expr(:tuple, Any[:(getfield(nt, $(QuoteNode(n)))) for n in names]...) - else - return tuple(nt...) - end - end + Tuple(nt::NamedTuple) = (nt...,) (::Type{T})(nt::NamedTuple) where {T <: Tuple} = convert(T, Tuple(nt)) end @@ -173,20 +156,12 @@ isless(a::NamedTuple{n}, b::NamedTuple{n}) where {n} = isless(Tuple(a), Tuple(b) same_names(::NamedTuple{names}...) where {names} = true same_names(::NamedTuple...) = false +# NOTE: this method signature makes sure we don't define map(f) function map(f, nt::NamedTuple{names}, nts::NamedTuple...) where names if !same_names(nt, nts...) throw(ArgumentError("Named tuple names do not match.")) end - # this method makes sure we don't define a map(f) method - NT = NamedTuple{names} - if @generated - N = length(names) - M = length(nts) - args = Expr[:(f($(Expr[:(getfield(nt, $j)), (:(getfield(nts[$i], $j)) for i = 1:M)...]...))) for j = 1:N] - :( NT(($(args...),)) ) - else - NT(map(f, map(Tuple, (nt, nts...))...)) - end + NamedTuple{names}(map(f, map(Tuple, (nt, nts...))...)) end # a version of `in` for the older world these generated functions run in diff --git a/base/ntuple.jl b/base/ntuple.jl new file mode 100644 index 00000000000000..0967066f3447ec --- /dev/null +++ b/base/ntuple.jl @@ -0,0 +1,68 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +# `ntuple`, for constructing tuples of a given length + +""" + ntuple(f::Function, n::Integer) + +Create a tuple of length `n`, computing each element as `f(i)`, +where `i` is the index of the element. + +# Examples +```jldoctest +julia> ntuple(i -> 2*i, 4) +(2, 4, 6, 8) +``` +""" +function ntuple(f::F, n::Integer) where F + t = n == 0 ? () : + n == 1 ? (f(1),) : + n == 2 ? (f(1), f(2)) : + n == 3 ? (f(1), f(2), f(3)) : + n == 4 ? (f(1), f(2), f(3), f(4)) : + n == 5 ? (f(1), f(2), f(3), f(4), f(5)) : + n == 6 ? (f(1), f(2), f(3), f(4), f(5), f(6)) : + n == 7 ? (f(1), f(2), f(3), f(4), f(5), f(6), f(7)) : + n == 8 ? (f(1), f(2), f(3), f(4), f(5), f(6), f(7), f(8)) : + n == 9 ? (f(1), f(2), f(3), f(4), f(5), f(6), f(7), f(8), f(9)) : + n == 10 ? (f(1), f(2), f(3), f(4), f(5), f(6), f(7), f(8), f(9), f(10)) : + _ntuple(f, n) + return t +end + +function _ntuple(f, n) + @_noinline_meta + (n >= 0) || throw(ArgumentError(string("tuple length should be ≥ 0, got ", n))) + ([f(i) for i = 1:n]...,) +end + +# inferrable ntuple (enough for bootstrapping) +ntuple(f, ::Val{0}) = () +ntuple(f, ::Val{1}) = (@_inline_meta; (f(1),)) +ntuple(f, ::Val{2}) = (@_inline_meta; (f(1), f(2))) +ntuple(f, ::Val{3}) = (@_inline_meta; (f(1), f(2), f(3))) + +@inline function ntuple(f::F, ::Val{N}) where {F,N} + N::Int + (N >= 0) || throw(ArgumentError(string("tuple length should be ≥ 0, got ", N))) + if @generated + quote + @nexprs $N i -> t_i = f(i) + @ncall $N tuple t + end + else + Tuple(f(i) for i = 1:N) + end +end + +@inline function fill_to_length(t::Tuple, val, ::Val{N}) where {N} + M = length(t) + M > N && throw(ArgumentError("input tuple of length $M, requested $N")) + if @generated + quote + (t..., $(fill(:val, N-length(t.parameters))...)) + end + else + (t..., fill(val, N-M)...) + end +end diff --git a/base/number.jl b/base/number.jl index f2787a01acfad9..7ec3f7425daf0b 100644 --- a/base/number.jl +++ b/base/number.jl @@ -60,9 +60,9 @@ true isone(x) = x == one(x) # fallback method size(x::Number) = () -size(x::Number,d) = convert(Int,d)<1 ? throw(BoundsError()) : 1 +size(x::Number, d::Integer) = d < 1 ? throw(BoundsError()) : 1 axes(x::Number) = () -axes(x::Number,d) = convert(Int,d)<1 ? throw(BoundsError()) : OneTo(1) +axes(x::Number, d::Integer) = d < 1 ? throw(BoundsError()) : OneTo(1) eltype(::Type{T}) where {T<:Number} = T ndims(x::Number) = 0 ndims(::Type{<:Number}) = 0 @@ -216,6 +216,9 @@ julia> inv(1 + 2im) * (1 + 2im) julia> inv(2//3) 3//2 ``` + +!!! compat "Julia 1.2" + `inv(::Missing)` requires at least Julia 1.2. """ inv(x::Number) = one(x)/x diff --git a/base/operators.jl b/base/operators.jl index becbe2cce0a532..e151f203b7f41d 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -132,16 +132,24 @@ isequal(x::AbstractFloat, y::Real ) = (isnan(x) & isnan(y)) | signequal( """ isless(x, y) -Test whether `x` is less than `y`, according to a canonical total order. Values that are -normally unordered, such as `NaN`, are ordered in an arbitrary but consistent fashion. +Test whether `x` is less than `y`, according to a fixed total order. +`isless` is not defined on all pairs of values `(x, y)`. However, if it +is defined, it is expected to satisfy the following: +- If `isless(x, y)` is defined, then so is `isless(y, x)` and `isequal(x, y)`, + and exactly one of those three yields `true`. +- The relation defined by `isless` is transitive, i.e., + `isless(x, y) && isless(y, z)` implies `isless(x, z)`. + +Values that are normally unordered, such as `NaN`, +are ordered in an arbitrary but consistent fashion. [`missing`](@ref) values are ordered last. This is the default comparison used by [`sort`](@ref). # Implementation -Non-numeric types with a canonical total order should implement this function. +Non-numeric types with a total order should implement this function. Numeric types only need to implement it if they have special values such as `NaN`. -Types with a canonical partial order should implement [`<`](@ref). +Types with a partial order should implement [`<`](@ref). """ function isless end @@ -581,10 +589,16 @@ See also [`>>`](@ref), [`>>>`](@ref). function <<(x::Integer, c::Integer) @_inline_meta typemin(Int) <= c <= typemax(Int) && return x << (c % Int) - (x >= 0 || c >= 0) && return zero(x) + (x >= 0 || c >= 0) && return zero(x) << 0 # for type stability oftype(x, -1) end -<<(x::Integer, c::Unsigned) = c <= typemax(UInt) ? x << (c % UInt) : zero(x) +function <<(x::Integer, c::Unsigned) + @_inline_meta + if c isa UInt + throw(MethodError(<<, (x, c))) + end + c <= typemax(UInt) ? x << (c % UInt) : zero(x) << UInt(0) +end <<(x::Integer, c::Int) = c >= 0 ? x << unsigned(c) : x >> unsigned(-c) """ @@ -619,11 +633,13 @@ See also [`>>>`](@ref), [`<<`](@ref). """ function >>(x::Integer, c::Integer) @_inline_meta + if c isa UInt + throw(MethodError(>>, (x, c))) + end typemin(Int) <= c <= typemax(Int) && return x >> (c % Int) - (x >= 0 || c < 0) && return zero(x) + (x >= 0 || c < 0) && return zero(x) >> 0 oftype(x, -1) end ->>(x::Integer, c::Unsigned) = c <= typemax(UInt) ? x >> (c % UInt) : zero(x) >>(x::Integer, c::Int) = c >= 0 ? x >> unsigned(c) : x << unsigned(-c) """ @@ -655,9 +671,15 @@ See also [`>>`](@ref), [`<<`](@ref). """ function >>>(x::Integer, c::Integer) @_inline_meta - typemin(Int) <= c <= typemax(Int) ? x >>> (c % Int) : zero(x) + typemin(Int) <= c <= typemax(Int) ? x >>> (c % Int) : zero(x) >>> 0 +end +function >>>(x::Integer, c::Unsigned) + @_inline_meta + if c isa UInt + throw(MethodError(>>>, (x, c))) + end + c <= typemax(UInt) ? x >>> (c % UInt) : zero(x) >>> 0 end ->>>(x::Integer, c::Unsigned) = c <= typemax(UInt) ? x >>> (c % UInt) : zero(x) >>>(x::Integer, c::Int) = c >= 0 ? x >>> unsigned(c) : x << unsigned(-c) # fallback div, fld, and cld implementations @@ -927,6 +949,71 @@ used to implement specialized methods. """ ==(x) = Fix2(==, x) +""" + !=(x) + +Create a function that compares its argument to `x` using [`!=`](@ref), i.e. +a function equivalent to `y -> y != x`. +The returned function is of type `Base.Fix2{typeof(!=)}`, which can be +used to implement specialized methods. + +!!! compat "Julia 1.2" + This functionality requires at least Julia 1.2. +""" +!=(x) = Fix2(!=, x) + +""" + >=(x) + +Create a function that compares its argument to `x` using [`>=`](@ref), i.e. +a function equivalent to `y -> y >= x`. +The returned function is of type `Base.Fix2{typeof(>=)}`, which can be +used to implement specialized methods. + +!!! compat "Julia 1.2" + This functionality requires at least Julia 1.2. +""" +>=(x) = Fix2(>=, x) + +""" + <=(x) + +Create a function that compares its argument to `x` using [`<=`](@ref), i.e. +a function equivalent to `y -> y <= x`. +The returned function is of type `Base.Fix2{typeof(<=)}`, which can be +used to implement specialized methods. + +!!! compat "Julia 1.2" + This functionality requires at least Julia 1.2. +""" +<=(x) = Fix2(<=, x) + +""" + >(x) + +Create a function that compares its argument to `x` using [`>`](@ref), i.e. +a function equivalent to `y -> y > x`. +The returned function is of type `Base.Fix2{typeof(>)}`, which can be +used to implement specialized methods. + +!!! compat "Julia 1.2" + This functionality requires at least Julia 1.2. +""" +>(x) = Fix2(>, x) + +""" + <(x) + +Create a function that compares its argument to `x` using [`<`](@ref), i.e. +a function equivalent to `y -> y < x`. +The returned function is of type `Base.Fix2{typeof(<)}`, which can be +used to implement specialized methods. + +!!! compat "Julia 1.2" + This functionality requires at least Julia 1.2. +""" +<(x) = Fix2(<, x) + """ splat(f) diff --git a/base/path.jl b/base/path.jl index a0c6cf27163444..d0e08553fa4208 100644 --- a/base/path.jl +++ b/base/path.jl @@ -145,12 +145,16 @@ end """ dirname(path::AbstractString) -> AbstractString -Get the directory part of a path. +Get the directory part of a path. Trailing characters ('/' or '\\') in the path are +counted as part of the path. # Examples ```jldoctest julia> dirname("/home/myuser") "/home" + +julia> dirname("/home/myuser/") +"/home/myuser" ``` See also: [`basename`](@ref) @@ -333,17 +337,26 @@ current directory if necessary. Equivalent to `abspath(joinpath(path, paths...)) abspath(a::AbstractString, b::AbstractString...) = abspath(joinpath(a,b...)) if Sys.iswindows() + function realpath(path::AbstractString) - p = cwstring(path) - buf = zeros(UInt16, length(p)) - while true - n = ccall((:GetFullPathNameW, "kernel32"), stdcall, - UInt32, (Ptr{UInt16}, UInt32, Ptr{UInt16}, Ptr{Cvoid}), - p, length(buf), buf, C_NULL) - systemerror(:realpath, n == 0) - x = n < length(buf) # is the buffer big enough? - resize!(buf, n) # shrink if x, grow if !x - x && return transcode(String, buf) + h = ccall(:CreateFileW, stdcall, Int, (Cwstring, UInt32, UInt32, Ptr{Cvoid}, UInt32, UInt32, Int), + path, 0, 0x03, C_NULL, 3, 0x02000000, 0) + windowserror(:realpath, h == -1) + try + buf = Vector{UInt16}(undef, 256) + oldlen = len = length(buf) + while len >= oldlen + len = ccall(:GetFinalPathNameByHandleW, stdcall, UInt32, (Int, Ptr{UInt16}, UInt32, UInt32), + h, buf, (oldlen=len)-1, 0x0) + windowserror(:realpath, iszero(len)) + resize!(buf, len) # strips NUL terminator on last call + end + if 4 < len < 264 && 0x005c == buf[1] == buf[2] == buf[4] && 0x003f == buf[3] + Base._deletebeg!(buf, 4) # omit \\?\ prefix for paths < MAXPATH in length + end + return transcode(String, buf) + finally + windowserror(:realpath, iszero(ccall(:CloseHandle, stdcall, Cint, (Int,), h))) end end @@ -354,7 +367,7 @@ function longpath(path::AbstractString) n = ccall((:GetLongPathNameW, "kernel32"), stdcall, UInt32, (Ptr{UInt16}, Ptr{UInt16}, UInt32), p, buf, length(buf)) - systemerror(:longpath, n == 0) + windowserror(:longpath, n == 0) x = n < length(buf) # is the buffer big enough? resize!(buf, n) # shrink if x, grow if !x x && return transcode(String, buf) @@ -376,6 +389,10 @@ end # os-test realpath(path::AbstractString) -> AbstractString Canonicalize a path by expanding symbolic links and removing "." and ".." entries. +On case-insensitive case-preserving filesystems (typically Mac and Windows), the +filesystem's stored case for the path is returned. + +(This function throws an exception if `path` does not exist in the filesystem.) """ realpath(path::AbstractString) diff --git a/base/pcre.jl b/base/pcre.jl index 5955c3880f6d59..7aa91076b84ad7 100644 --- a/base/pcre.jl +++ b/base/pcre.jl @@ -37,6 +37,7 @@ const COMPILE_MASK = CASELESS | DOLLAR_ENDONLY | DOTALL | + ENDANCHORED | EXTENDED | FIRSTLINE | MULTILINE | @@ -76,7 +77,7 @@ function info(regex::Ptr{Cvoid}, what::Integer, ::Type{T}) where T buf = RefValue{T}() ret = ccall((:pcre2_pattern_info_8, PCRE_LIB), Int32, (Ptr{Cvoid}, Int32, Ptr{Cvoid}), - regex, what, buf) + regex, what, buf) % UInt32 if ret != 0 error(ret == ERROR_NULL ? "NULL regex object" : ret == ERROR_BADMAGIC ? "invalid regex object" : @@ -106,8 +107,10 @@ end function jit_compile(regex::Ptr{Cvoid}) errno = ccall((:pcre2_jit_compile_8, PCRE_LIB), Cint, - (Ptr{Cvoid}, UInt32), regex, JIT_COMPLETE) - errno == 0 || error("PCRE JIT error: $(err_message(errno))") + (Ptr{Cvoid}, UInt32), regex, JIT_COMPLETE) % UInt32 + errno == 0 && return true + errno == ERROR_JIT_BADOPTION && return false + error("PCRE JIT error: $(err_message(errno))") end free_match_data(match_data) = diff --git a/base/pointer.jl b/base/pointer.jl index 8ef999d7ce2bb3..965139e8186f25 100644 --- a/base/pointer.jl +++ b/base/pointer.jl @@ -124,6 +124,8 @@ unsafe_store!(p::Ptr{T}, x, i::Integer=1) where {T} = pointerset(p, convert(T,x) Convert a `Ptr` to an object reference. Assumes the pointer refers to a valid heap-allocated Julia object. If this is not the case, undefined behavior results, hence this function is considered "unsafe" and should be used with care. + +See also: [`pointer_from_objref`](@ref). """ unsafe_pointer_to_objref(x::Ptr) = ccall(:jl_value_ptr, Any, (Ptr{Cvoid},), x) @@ -136,6 +138,8 @@ remains referenced for the whole time that the `Ptr` will be used. This function may not be called on immutable objects, since they do not have stable memory addresses. + +See also: [`unsafe_pointer_to_objref`](@ref). """ function pointer_from_objref(@nospecialize(x)) @_inline_meta diff --git a/base/process.jl b/base/process.jl index 1fb2c7304c151c..a1c3063643fa62 100644 --- a/base/process.jl +++ b/base/process.jl @@ -113,6 +113,7 @@ function show(io::IO, cmd::Cmd) print_env && (print(io, ","); show(io, cmd.env)) print_dir && (print(io, "; dir="); show(io, cmd.dir)) (print_dir || print_env) && print(io, ")") + nothing end function show(io::IO, cmds::Union{OrCmds,ErrOrCmds}) @@ -710,8 +711,9 @@ read(cmd::AbstractCmd, ::Type{String}) = String(read(cmd)) """ run(command, args...; wait::Bool = true) -Run a command object, constructed with backticks. Throws an error if anything goes wrong, -including the process exiting with a non-zero status (when `wait` is true). +Run a command object, constructed with backticks (see the [Running External Programs](@ref) +section in the manual). Throws an error if anything goes wrong, including the process +exiting with a non-zero status (when `wait` is true). If `wait` is false, the process runs asynchronously. You can later wait for it and check its exit status by calling `success` on the returned process object. @@ -775,8 +777,9 @@ success(procs::ProcessChain) = success(procs.processes) """ success(command) -Run a command object, constructed with backticks, and tell whether it was successful (exited -with a code of 0). An exception is raised if the process cannot be started. +Run a command object, constructed with backticks (see the [Running External Programs](@ref) +section in the manual), and tell whether it was successful (exited with a code of 0). +An exception is raised if the process cannot be started. """ success(cmd::AbstractCmd) = success(_spawn(cmd)) diff --git a/base/range.jl b/base/range.jl index e7607839a47850..9fbc198d1c3722 100644 --- a/base/range.jl +++ b/base/range.jl @@ -523,7 +523,7 @@ firstindex(::UnitRange) = 1 firstindex(::StepRange) = 1 firstindex(::LinRange) = 1 -function length(r::StepRange{T}) where T<:Union{Int,UInt,Int64,UInt64} +function length(r::StepRange{T}) where T<:Union{Int,UInt,Int64,UInt64,Int128,UInt128} isempty(r) && return zero(T) if r.step > 1 return checked_add(convert(T, div(unsigned(r.stop - r.start), r.step)), one(T)) @@ -536,13 +536,13 @@ function length(r::StepRange{T}) where T<:Union{Int,UInt,Int64,UInt64} end end -function length(r::AbstractUnitRange{T}) where T<:Union{Int,Int64} +function length(r::AbstractUnitRange{T}) where T<:Union{Int,Int64,Int128} @_inline_meta checked_add(checked_sub(last(r), first(r)), one(T)) end length(r::OneTo{T}) where {T<:Union{Int,Int64}} = T(r.stop) -length(r::AbstractUnitRange{T}) where {T<:Union{UInt,UInt64}} = +length(r::AbstractUnitRange{T}) where {T<:Union{UInt,UInt64,UInt128}} = r.stop < r.start ? zero(T) : checked_add(last(r) - first(r), one(T)) # some special cases to favor default Int type diff --git a/base/rational.jl b/base/rational.jl index 73d9aa0d5b00fe..5eeefb27be236b 100644 --- a/base/rational.jl +++ b/base/rational.jl @@ -85,7 +85,7 @@ Rational(x::Rational) = x Bool(x::Rational) = x==0 ? false : x==1 ? true : throw(InexactError(:Bool, Bool, x)) # to resolve ambiguity (::Type{T})(x::Rational) where {T<:Integer} = (isinteger(x) ? convert(T, x.num) : - throw(InexactError(Symbol(string(T)), T, x))) + throw(InexactError(nameof(T), T, x))) AbstractFloat(x::Rational) = float(x.num)/float(x.den) function (::Type{T})(x::Rational{S}) where T<:AbstractFloat where S @@ -101,6 +101,8 @@ end Rational(x::Float64) = Rational{Int64}(x) Rational(x::Float32) = Rational{Int}(x) +big(q::Rational) = big(numerator(q))//big(denominator(q)) + big(z::Complex{<:Rational{<:Integer}}) = Complex{Rational{BigInt}}(z) promote_rule(::Type{Rational{T}}, ::Type{S}) where {T<:Integer,S<:Integer} = Rational{promote_type(T,S)} @@ -366,9 +368,10 @@ end trunc(::Type{T}, x::Rational) where {T} = convert(T,div(x.num,x.den)) floor(::Type{T}, x::Rational) where {T} = convert(T,fld(x.num,x.den)) ceil(::Type{T}, x::Rational) where {T} = convert(T,cld(x.num,x.den)) +round(::Type{T}, x::Rational, r::RoundingMode=RoundNearest) where {T} = _round_rational(T, x, r) +round(x::Rational, r::RoundingMode) = round(Rational, x, r) - -function round(::Type{T}, x::Rational{Tr}, ::RoundingMode{:Nearest}) where {T,Tr} +function _round_rational(::Type{T}, x::Rational{Tr}, ::RoundingMode{:Nearest}) where {T,Tr} if denominator(x) == zero(Tr) && T <: Integer throw(DivideError()) elseif denominator(x) == zero(Tr) @@ -382,9 +385,7 @@ function round(::Type{T}, x::Rational{Tr}, ::RoundingMode{:Nearest}) where {T,Tr convert(T, s) end -round(::Type{T}, x::Rational) where {T} = round(T, x, RoundNearest) - -function round(::Type{T}, x::Rational{Tr}, ::RoundingMode{:NearestTiesAway}) where {T,Tr} +function _round_rational(::Type{T}, x::Rational{Tr}, ::RoundingMode{:NearestTiesAway}) where {T,Tr} if denominator(x) == zero(Tr) && T <: Integer throw(DivideError()) elseif denominator(x) == zero(Tr) @@ -398,7 +399,7 @@ function round(::Type{T}, x::Rational{Tr}, ::RoundingMode{:NearestTiesAway}) whe convert(T, s) end -function round(::Type{T}, x::Rational{Tr}, ::RoundingMode{:NearestTiesUp}) where {T,Tr} +function _round_rational(::Type{T}, x::Rational{Tr}, ::RoundingMode{:NearestTiesUp}) where {T,Tr} if denominator(x) == zero(Tr) && T <: Integer throw(DivideError()) elseif denominator(x) == zero(Tr) @@ -412,18 +413,13 @@ function round(::Type{T}, x::Rational{Tr}, ::RoundingMode{:NearestTiesUp}) where convert(T, s) end -function round(::Type{T}, x::Rational{Bool}) where T +function round(::Type{T}, x::Rational{Bool}, ::RoundingMode=RoundNearest) where T if denominator(x) == false && (T <: Union{Integer, Bool}) throw(DivideError()) end convert(T, x) end -round(::Type{T}, x::Rational{Bool}, ::RoundingMode{:Nearest}) where {T} = round(T, x) -round(::Type{T}, x::Rational{Bool}, ::RoundingMode{:NearestTiesAway}) where {T} = round(T, x) -round(::Type{T}, x::Rational{Bool}, ::RoundingMode{:NearestTiesUp}) where {T} = round(T, x) -round(::Type{T}, x::Rational{Bool}, ::RoundingMode) where {T} = round(T, x) - trunc(x::Rational{T}) where {T} = Rational(trunc(T,x)) floor(x::Rational{T}) where {T} = Rational(floor(T,x)) ceil(x::Rational{T}) where {T} = Rational(ceil(T,x)) diff --git a/base/reduce.jl b/base/reduce.jl index 6cbc6b1b5b83e0..4149b4c69e4e0b 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -81,10 +81,10 @@ argument `init` will be used exactly once. In general, it will be necessary to p # Examples ```jldoctest julia> foldl(=>, 1:4) -((1=>2)=>3) => 4 +((1 => 2) => 3) => 4 julia> foldl(=>, 1:4; init=0) -(((0=>1)=>2)=>3) => 4 +(((0 => 1) => 2) => 3) => 4 ``` """ foldl(op, itr; kw...) = mapfoldl(identity, op, itr; kw...) @@ -135,10 +135,10 @@ argument `init` will be used exactly once. In general, it will be necessary to p # Examples ```jldoctest julia> foldr(=>, 1:4) -1 => (2=>(3=>4)) +1 => (2 => (3 => 4)) julia> foldr(=>, 1:4; init=0) -1 => (2=>(3=>(4=>0))) +1 => (2 => (3 => (4 => 0))) ``` """ foldr(op, itr; kw...) = mapfoldr(identity, op, itr; kw...) @@ -565,10 +565,10 @@ values are `false` (or equivalently, if the input contains no `true` value), fol ```jldoctest julia> a = [true,false,false,true] 4-element Array{Bool,1}: - true - false - false - true + 1 + 0 + 0 + 1 julia> any(a) true @@ -600,10 +600,10 @@ values are `true` (or equivalently, if the input contains no `false` value), fol ```jldoctest julia> a = [true,false,false,true] 4-element Array{Bool,1}: - true - false - false - true + 1 + 0 + 0 + 1 julia> all(a) false diff --git a/base/reducedim.jl b/base/reducedim.jl index 3a3f9f14469c8b..7b005aeff4f102 100644 --- a/base/reducedim.jl +++ b/base/reducedim.jl @@ -294,11 +294,11 @@ julia> a = reshape(Vector(1:16), (4,4)) julia> mapreduce(isodd, *, a, dims=1) 1×4 Array{Bool,2}: - false false false false + 0 0 0 0 julia> mapreduce(isodd, |, a, dims=1) 1×4 Array{Bool,2}: - true true true true + 1 1 1 1 ``` """ mapreduce(f, op, A::AbstractArray; dims=:, kw...) = _mapreduce_dim(f, op, kw.data, A, dims) @@ -553,17 +553,17 @@ Test whether all values along the given dimensions of an array are `true`. ```jldoctest julia> A = [true false; true true] 2×2 Array{Bool,2}: - true false - true true + 1 0 + 1 1 julia> all(A, dims=1) 1×2 Array{Bool,2}: - true false + 1 0 julia> all(A, dims=2) 2×1 Array{Bool,2}: - false - true + 0 + 1 ``` """ all(A::AbstractArray; dims) @@ -577,8 +577,8 @@ Test whether all values in `A` along the singleton dimensions of `r` are `true`, ```jldoctest julia> A = [true false; true false] 2×2 Array{Bool,2}: - true false - true false + 1 0 + 1 0 julia> all!([1; 1], A) 2-element Array{Int64,1}: @@ -601,17 +601,17 @@ Test whether any values along the given dimensions of an array are `true`. ```jldoctest julia> A = [true false; true false] 2×2 Array{Bool,2}: - true false - true false + 1 0 + 1 0 julia> any(A, dims=1) 1×2 Array{Bool,2}: - true false + 1 0 julia> any(A, dims=2) 2×1 Array{Bool,2}: - true - true + 1 + 1 ``` """ any(::AbstractArray; dims) @@ -626,8 +626,8 @@ results to `r`. ```jldoctest julia> A = [true false; true false] 2×2 Array{Bool,2}: - true false - true false + 1 0 + 1 0 julia> any!([1; 1], A) 2-element Array{Int64,1}: diff --git a/base/reflection.jl b/base/reflection.jl index d201ecdff72c94..46f268feb9e0c7 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -5,7 +5,7 @@ """ nameof(m::Module) -> Symbol -Get the name of a `Module` as a `Symbol`. +Get the name of a `Module` as a [`Symbol`](@ref). # Examples ```jldoctest @@ -176,6 +176,19 @@ fieldnames(::Core.TypeofBottom) = throw(ArgumentError("The empty type does not have field names since it does not have instances.")) fieldnames(t::Type{<:Tuple}) = ntuple(identity, fieldcount(t)) +""" + hasfield(T::Type, name::Symbol) + +Return a boolean indicating whether `T` has `name` as one of its own fields. + +!!! compat "Julia 1.2" + This function requires at least Julia 1.2. +""" +function hasfield(::Type{T}, name::Symbol) where T + @_pure_meta + return fieldindex(T, name, false) > 0 +end + """ nameof(t::DataType) -> Symbol @@ -195,7 +208,7 @@ julia> nameof(Foo.S{T} where T) ``` """ nameof(t::DataType) = t.name.name -nameof(t::UnionAll) = nameof(unwrap_unionall(t)) +nameof(t::UnionAll) = nameof(unwrap_unionall(t))::Symbol """ parentmodule(t::DataType) -> Module @@ -232,6 +245,8 @@ isconst(m::Module, s::Symbol) = Tests whether variable `s` is defined in the current scope. +See also [`isdefined`](@ref). + # Examples ```jldoctest julia> function f() @@ -696,7 +711,7 @@ enumerated types (see `@enum`). julia> @enum Color red blue green julia> instances(Color) -(red::Color = 0, blue::Color = 1, green::Color = 2) +(red, blue, green) ``` """ function instances end @@ -745,22 +760,25 @@ Note that an error will be thrown if `types` are not leaf types when `generated` `true` and any of the corresponding methods are an `@generated` method. """ function code_lowered(@nospecialize(f), @nospecialize(t=Tuple); generated::Bool=true, debuginfo::Symbol=:default) - if debuginfo == :default + if @isdefined(IRShow) + debuginfo = IRShow.debuginfo(debuginfo) + elseif debuginfo == :default debuginfo = :source - elseif debuginfo != :source && debuginfo != :none + end + if debuginfo != :source && debuginfo != :none throw(ArgumentError("'debuginfo' must be either :source or :none")) end return map(method_instances(f, t)) do m if generated && isgenerated(m) - if isa(m, Core.MethodInstance) - return Core.Compiler.get_staged(m) - else # isa(m, Method) + if may_invoke_generator(m) + return ccall(:jl_code_for_staged, Any, (Any,), m)::CodeInfo + else error("Could not expand generator for `@generated` method ", m, ". ", "This can happen if the provided argument types (", t, ") are ", "not leaf types, but the `generated` argument is `true`.") end end - code = uncompressed_ast(m) + code = uncompressed_ast(m.def::Method) debuginfo == :none && remove_linenums!(code) return code end @@ -880,20 +898,20 @@ function length(mt::Core.MethodTable) end isempty(mt::Core.MethodTable) = (mt.defs === nothing) -uncompressed_ast(m::Method) = isdefined(m, :source) ? uncompressed_ast(m, m.source) : +uncompressed_ast(m::Method) = isdefined(m, :source) ? _uncompressed_ast(m, m.source) : isdefined(m, :generator) ? error("Method is @generated; try `code_lowered` instead.") : error("Code for this Method is not available.") -uncompressed_ast(m::Method, s::CodeInfo) = copy(s) -uncompressed_ast(m::Method, s::Array{UInt8,1}) = ccall(:jl_uncompress_ast, Any, (Any, Any), m, s)::CodeInfo -uncompressed_ast(m::Core.MethodInstance) = uncompressed_ast(m.def) +_uncompressed_ast(m::Method, s::CodeInfo) = copy(s) +_uncompressed_ast(m::Method, s::Array{UInt8,1}) = ccall(:jl_uncompress_ast, Any, (Any, Ptr{Cvoid}, Any), m, C_NULL, s)::CodeInfo +_uncompressed_ast(m::Core.CodeInstance, s::Array{UInt8,1}) = ccall(:jl_uncompress_ast, Any, (Any, Ptr{Cvoid}, Any), li.def.def::Method, li, s)::CodeInfo function method_instances(@nospecialize(f), @nospecialize(t), world::UInt = typemax(UInt)) tt = signature_type(f, t) - results = Vector{Union{Method,Core.MethodInstance}}() + results = Core.MethodInstance[] for method_data in _methods_by_ftype(tt, -1, world) mtypes, msp, m = method_data - instance = Core.Compiler.code_for_method(m, mtypes, msp, world, false) - push!(results, ifelse(isa(instance, Core.MethodInstance), instance, m)) + instance = ccall(:jl_specializations_get_linfo, Ref{MethodInstance}, (Any, Any, Any), m, mtypes, msp) + push!(results, instance) end return results end @@ -925,8 +943,78 @@ struct CodegenParams emit_function, emitted_function) end +const SLOT_USED = 0x8 +ast_slotflag(@nospecialize(code), i) = ccall(:jl_ast_slotflag, UInt8, (Any, Csize_t), code, i - 1) + +""" + may_invoke_generator(method, atypes, sparams) + +Computes whether or not we may invoke the generator for the given `method` on +the given atypes and sparams. For correctness, all generated function are +required to return monotonic answers. However, since we don't expect users to +be able to successfully implement this criterion, we only call generated +functions on concrete types. The one exception to this is that we allow calling +generators with abstract types if the generator does not use said abstract type +(and thus cannot incorrectly use it to break monotonicity). This function +computes whether we are in either of these cases. + +Unlike normal functions, the compilation heuristics still can't generate good dispatch +in some cases, but this may still allow inference not to fall over in some limited cases. +""" +function may_invoke_generator(method::MethodInstance) + return may_invoke_generator(method.def::Method, method.specTypes, method.sparam_vals) +end +function may_invoke_generator(method::Method, @nospecialize(atypes), sparams::SimpleVector) + # If we have complete information, we may always call the generator + isdispatchtuple(atypes) && return true + + # We don't have complete information, but it is possible that the generator + # syntactically doesn't make use of the information we don't have. Check + # for that. + + # For now, only handle the (common, generated by the frontend case) that the + # generator only has one method + isa(method.generator, Core.GeneratedFunctionStub) || return false + gen_mthds = methods(method.generator.gen) + length(gen_mthds) == 1 || return false + + generator_method = first(gen_mthds) + nsparams = length(sparams) + isdefined(generator_method, :source) || return false + code = generator_method.source + nslots = ccall(:jl_ast_nslots, Int, (Any,), code) + at = unwrap_unionall(atypes) + (nslots >= 1 + length(sparams) + length(at.parameters)) || return false + + for i = 1:nsparams + if isa(sparams[i], TypeVar) + if (ast_slotflag(code, 1 + i) & SLOT_USED) != 0 + return false + end + end + end + for i = 1:length(at.parameters) + if !isdispatchelem(at.parameters[i]) + if (ast_slotflag(code, 1 + i + nsparams) & SLOT_USED) != 0 + return false + end + end + end + return true +end + # give a decent error message if we try to instantiate a staged function on non-leaf types -function func_for_method_checked(m::Method, @nospecialize types) +function func_for_method_checked(m::Method, @nospecialize(types), sparams::SimpleVector) + if isdefined(m, :generator) && !may_invoke_generator(m, types, sparams) + error("cannot call @generated function `", m, "` ", + "with abstract argument types: ", types) + end + return m +end + +function func_for_method_checked(m::Method, @nospecialize(types)) + depwarn("The two argument form of `func_for_method_checked` is deprecated. Pass sparams in addition.", + :func_for_method_checked) if isdefined(m, :generator) && !isdispatchtuple(types) error("cannot call @generated function `", m, "` ", "with abstract argument types: ", types) @@ -934,31 +1022,37 @@ function func_for_method_checked(m::Method, @nospecialize types) return m end + """ code_typed(f, types; optimize=true, debuginfo=:default) Returns an array of type-inferred lowered form (IR) for the methods matching the given generic function and type signature. The keyword argument `optimize` controls whether additional optimizations, such as inlining, are also applied. -The keyword debuginfo controls the amount of code metadata present in the output. +The keyword `debuginfo` controls the amount of code metadata present in the output, +possible options are `:source` or `:none`. """ function code_typed(@nospecialize(f), @nospecialize(types=Tuple); - optimize=true, debuginfo::Symbol=:default, - world = ccall(:jl_get_world_counter, UInt, ()), + optimize=true, + debuginfo::Symbol=:default, + world = get_world_counter(), params = Core.Compiler.Params(world)) ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions") if isa(f, Core.Builtin) throw(ArgumentError("argument is not a generic function")) end - if debuginfo == :default + if @isdefined(IRShow) + debuginfo = IRShow.debuginfo(debuginfo) + elseif debuginfo == :default debuginfo = :source - elseif debuginfo != :source && debuginfo != :none + end + if debuginfo != :source && debuginfo != :none throw(ArgumentError("'debuginfo' must be either :source or :none")) end types = to_tuple_type(types) asts = [] for x in _methods(f, types, -1, world) - meth = func_for_method_checked(x[3], types) + meth = func_for_method_checked(x[3], types, x[2]) (code, ty) = Core.Compiler.typeinf_code(meth, x[1], x[2], optimize, params) code === nothing && error("inference not successful") # inference disabled? debuginfo == :none && remove_linenums!(code) @@ -974,10 +1068,10 @@ function return_types(@nospecialize(f), @nospecialize(types=Tuple)) end types = to_tuple_type(types) rt = [] - world = ccall(:jl_get_world_counter, UInt, ()) + world = get_world_counter() params = Core.Compiler.Params(world) for x in _methods(f, types, -1, world) - meth = func_for_method_checked(x[3], types) + meth = func_for_method_checked(x[3], types, x[2]) ty = Core.Compiler.typeinf_type(meth, x[1], x[2], params) ty === nothing && error("inference not successful") # inference disabled? push!(rt, ty) @@ -1023,7 +1117,7 @@ end Get the name of a generic `Function` as a symbol, or `:anonymous`. """ -nameof(f::Function) = typeof(f).name.mt.name +nameof(f::Function) = (typeof(f).name.mt::Core.MethodTable).name functionloc(m::Core.MethodInstance) = functionloc(m.def) @@ -1084,23 +1178,55 @@ function parentmodule(@nospecialize(f), @nospecialize(types)) end """ - hasmethod(f, Tuple type; world = typemax(UInt)) -> Bool + hasmethod(f, t::Type{<:Tuple}[, kwnames]; world=typemax(UInt)) -> Bool Determine whether the given generic function has a method matching the given `Tuple` of argument types with the upper bound of world age given by `world`. +If a tuple of keyword argument names `kwnames` is provided, this also checks +whether the method of `f` matching `t` has the given keyword argument names. +If the matching method accepts a variable number of keyword arguments, e.g. +with `kwargs...`, any names given in `kwnames` are considered valid. Otherwise +the provided names must be a subset of the method's keyword arguments. + See also [`applicable`](@ref). +!!! compat "Julia 1.2" + Providing keyword argument names requires Julia 1.2 or later. + # Examples ```jldoctest julia> hasmethod(length, Tuple{Array}) true + +julia> hasmethod(sum, Tuple{Function, Array}, (:dims,)) +true + +julia> hasmethod(sum, Tuple{Function, Array}, (:apples, :bananas)) +false + +julia> g(; xs...) = 4; + +julia> hasmethod(g, Tuple{}, (:a, :b, :c, :d)) # g accepts arbitrary kwargs +true ``` """ -function hasmethod(@nospecialize(f), @nospecialize(t); world = typemax(UInt)) +function hasmethod(@nospecialize(f), @nospecialize(t); world=typemax(UInt)) t = to_tuple_type(t) t = signature_type(f, t) - return ccall(:jl_method_exists, Cint, (Any, Any, UInt), typeof(f).name.mt, t, world) != 0 + return ccall(:jl_gf_invoke_lookup, Any, (Any, UInt), t, world) !== nothing +end + +function hasmethod(@nospecialize(f), @nospecialize(t), kwnames::Tuple{Vararg{Symbol}}; world=typemax(UInt)) + # TODO: this appears to be doing the wrong queries + hasmethod(f, t, world=world) || return false + isempty(kwnames) && return true + m = which(f, t) + kws = kwarg_decl(m, Core.kwftype(typeof(f))) + for kw in kws + endswith(String(kw), "...") && return true + end + return issubset(kwnames, kws) end """ @@ -1183,10 +1309,12 @@ has_bottom_parameter(t::Union) = has_bottom_parameter(t.a) & has_bottom_paramete has_bottom_parameter(t::TypeVar) = t.ub == Bottom || has_bottom_parameter(t.ub) has_bottom_parameter(::Any) = false -min_world(m::Method) = reinterpret(UInt, m.min_world) -max_world(m::Method) = reinterpret(UInt, m.max_world) -min_world(m::Core.MethodInstance) = reinterpret(UInt, m.min_world) -max_world(m::Core.MethodInstance) = reinterpret(UInt, m.max_world) +min_world(m::Core.CodeInstance) = reinterpret(UInt, m.min_world) +max_world(m::Core.CodeInstance) = reinterpret(UInt, m.max_world) +min_world(m::Core.CodeInfo) = reinterpret(UInt, m.min_world) +max_world(m::Core.CodeInfo) = reinterpret(UInt, m.max_world) +get_world_counter() = ccall(:jl_get_world_counter, UInt, ()) + """ propertynames(x, private=false) @@ -1204,3 +1332,13 @@ REPL tab completion on `x.` shows only the `private=false` properties. propertynames(x) = fieldnames(typeof(x)) propertynames(m::Module) = names(m) propertynames(x, private) = propertynames(x) # ignore private flag by default + +""" + hasproperty(x, s::Symbol) + +Return a boolean indicating whether the object `x` has `s` as one of its own properties. + +!!! compat "Julia 1.2" + This function requires at least Julia 1.2. +""" +hasproperty(x, s::Symbol) = s in propertynames(x) diff --git a/base/regex.jl b/base/regex.jl index 384d6ca3dfa21d..3bee6dbd649478 100644 --- a/base/regex.jl +++ b/base/regex.jl @@ -174,6 +174,74 @@ function occursin(r::Regex, s::SubString; offset::Integer=0) r.match_data) end +""" + startswith(s::AbstractString, prefix::Regex) + +Return `true` if `s` starts with the regex pattern, `prefix`. + +!!! note + `startswith` does not compile the anchoring into the regular + expression, but instead passes the anchoring as + `match_option` to PCRE. If compile time is amortized, + `occursin(r"^...", s)` is faster than `startswith(s, r"...")`. + +See also [`occursin`](@ref) and [`endswith`](@ref). + +!!! compat "Julia 1.2" + This method requires at least Julia 1.2. + +# Examples +```jldoctest +julia> startswith("JuliaLang", r"Julia|Romeo") +true +``` +""" +function startswith(s::AbstractString, r::Regex) + compile(r) + return PCRE.exec(r.regex, String(s), 0, r.match_options | PCRE.ANCHORED, + r.match_data) +end + +function startswith(s::SubString, r::Regex) + compile(r) + return PCRE.exec(r.regex, s, 0, r.match_options | PCRE.ANCHORED, + r.match_data) +end + +""" + endswith(s::AbstractString, suffix::Regex) + +Return `true` if `s` ends with the regex pattern, `suffix`. + +!!! note + `endswith` does not compile the anchoring into the regular + expression, but instead passes the anchoring as + `match_option` to PCRE. If compile time is amortized, + `occursin(r"...\$", s)` is faster than `endswith(s, r"...")`. + +See also [`occursin`](@ref) and [`startswith`](@ref). + +!!! compat "Julia 1.2" + This method requires at least Julia 1.2. + +# Examples +```jldoctest +julia> endswith("JuliaLang", r"Lang|Roberts") +true +``` +""" +function endswith(s::AbstractString, r::Regex) + compile(r) + return PCRE.exec(r.regex, String(s), 0, r.match_options | PCRE.ENDANCHORED, + r.match_data) +end + +function endswith(s::SubString, r::Regex) + compile(r) + return PCRE.exec(r.regex, s, 0, r.match_options | PCRE.ENDANCHORED, + r.match_data) +end + """ match(r::Regex, s::AbstractString[, idx::Integer[, addopts]]) @@ -406,7 +474,7 @@ function iterate(itr::RegexMatchIterator, (offset,prevempty)=(1,false)) end """ - eachmatch(r::Regex, s::AbstractString; overlap::Bool=false]) + eachmatch(r::Regex, s::AbstractString; overlap::Bool=false) Search for all matches of a the regular expression `r` in `s` and return a iterator over the matches. If overlap is `true`, the matching sequences are allowed to overlap indices in the diff --git a/base/reinterpretarray.jl b/base/reinterpretarray.jl index 8d7a175d8252d1..fa2af7a20af101 100644 --- a/base/reinterpretarray.jl +++ b/base/reinterpretarray.jl @@ -45,6 +45,34 @@ struct ReinterpretArray{T,N,S,A<:AbstractArray{S, N}} <: AbstractArray{T, N} end end +# Definition of StridedArray +StridedFastContiguousSubArray{T,N,A<:DenseArray} = FastContiguousSubArray{T,N,A} +StridedReinterpretArray{T,N,A<:Union{DenseArray,StridedFastContiguousSubArray}} = ReinterpretArray{T,N,S,A} where S +StridedReshapedArray{T,N,A<:Union{DenseArray,StridedFastContiguousSubArray,StridedReinterpretArray}} = ReshapedArray{T,N,A} +StridedSubArray{T,N,A<:Union{DenseArray,StridedReshapedArray,StridedReinterpretArray}, + I<:Tuple{Vararg{Union{RangeIndex, AbstractCartesianIndex}}}} = SubArray{T,N,A,I} +StridedArray{T,N} = Union{DenseArray{T,N}, StridedSubArray{T,N}, StridedReshapedArray{T,N}, StridedReinterpretArray{T,N}} +StridedVector{T} = Union{DenseArray{T,1}, StridedSubArray{T,1}, StridedReshapedArray{T,1}, StridedReinterpretArray{T,1}} +StridedMatrix{T} = Union{DenseArray{T,2}, StridedSubArray{T,2}, StridedReshapedArray{T,2}, StridedReinterpretArray{T,2}} +StridedVecOrMat{T} = Union{StridedVector{T}, StridedMatrix{T}} + +# the definition of strides for Array{T,N} is tuple() if N = 0, otherwise it is +# a tuple containing 1 and a cumulative product of the first N-1 sizes +# this definition is also used for StridedReshapedArray and StridedReinterpretedArray +# which have the same memory storage as Array +function stride(a::Union{DenseArray,StridedReshapedArray,StridedReinterpretArray}, i::Int) + if i > ndims(a) + return length(a) + end + s = 1 + for n = 1:(i-1) + s *= size(a, n) + end + return s +end + +strides(a::Union{DenseArray,StridedReshapedArray,StridedReinterpretArray}) = size_to_strides(1, size(a)...) + function check_readable(a::ReinterpretArray{T, N, S} where N) where {T,S} # See comment in check_writable if !a.readable && !array_subpadding(T, S) @@ -68,6 +96,7 @@ IndexStyle(a::ReinterpretArray) = IndexStyle(a.parent) parent(a::ReinterpretArray) = a.parent dataids(a::ReinterpretArray) = dataids(a.parent) +unaliascopy(a::ReinterpretArray{T}) where {T} = reinterpret(T, unaliascopy(a.parent)) function size(a::ReinterpretArray{T,N,S} where {N}) where {T,S} psize = size(a.parent) diff --git a/base/reshapedarray.jl b/base/reshapedarray.jl index c57a84f526a9c6..f8a577e3dea39e 100644 --- a/base/reshapedarray.jl +++ b/base/reshapedarray.jl @@ -164,7 +164,7 @@ _reshape(parent::Array, dims::Dims) = reshape(parent, dims) # When reshaping Vector->Vector, don't wrap with a ReshapedArray function _reshape(v::AbstractVector, dims::Dims{1}) - @assert !has_offset_axes(v) + require_one_based_indexing(v) len = dims[1] len == length(v) || _throw_dmrs(length(v), "length", len) v diff --git a/base/shell.jl b/base/shell.jl index eec954f9cb2c77..8e307bde5da4f9 100644 --- a/base/shell.jl +++ b/base/shell.jl @@ -128,9 +128,6 @@ function shell_split(s::AbstractString) end function print_shell_word(io::IO, word::AbstractString, special::AbstractString = "") - if isempty(word) - print(io, "''") - end has_single = false has_special = false for c in word @@ -141,7 +138,9 @@ function print_shell_word(io::IO, word::AbstractString, special::AbstractString end end end - if !has_special + if isempty(word) + print(io, "''") + elseif !has_special print(io, word) elseif !has_single print(io, '\'', word, '\'') @@ -155,6 +154,7 @@ function print_shell_word(io::IO, word::AbstractString, special::AbstractString end print(io, '"') end + nothing end function print_shell_escaped(io::IO, cmd::AbstractString, args::AbstractString...; @@ -186,7 +186,7 @@ julia> Base.shell_escape("echo", "this", "&&", "that") ``` """ shell_escape(args::AbstractString...; special::AbstractString="") = - sprint(io->print_shell_escaped(io, args..., special=special)) + sprint((io, args...) -> print_shell_escaped(io, args..., special=special), args...) function print_shell_escaped_posixly(io::IO, args::AbstractString...) @@ -215,7 +215,9 @@ function print_shell_escaped_posixly(io::IO, args::AbstractString...) end return true end - if all(isword, arg) + if isempty(arg) + print(io, "''") + elseif all(isword, arg) have_single && (arg = replace(arg, '\'' => "\\'")) have_double && (arg = replace(arg, '"' => "\\\"")) print(io, arg) @@ -243,4 +245,4 @@ julia> Base.shell_escape_posixly("echo", "this", "&&", "that") ``` """ shell_escape_posixly(args::AbstractString...) = - sprint(io->print_shell_escaped_posixly(io, args...)) + sprint(print_shell_escaped_posixly, args...) diff --git a/base/show.jl b/base/show.jl index 6dbc48423a6097..378ded97aa1d00 100644 --- a/base/show.jl +++ b/base/show.jl @@ -91,8 +91,8 @@ function show(io::IO, ::MIME"text/plain", t::AbstractDict{K,V}) where {K,V} rows -= 1 # Subtract the summary # determine max key width to align the output, caching the strings - ks = Vector{AbstractString}(undef, min(rows, length(t))) - vs = Vector{AbstractString}(undef, min(rows, length(t))) + ks = Vector{String}(undef, min(rows, length(t))) + vs = Vector{String}(undef, min(rows, length(t))) keylen = 0 vallen = 0 for (i, (k, v)) in enumerate(t) @@ -307,8 +307,20 @@ end show(x) Write an informative text representation of a value to the current output stream. New types -should overload `show(io, x)` where the first argument is a stream. The representation used +should overload `show(io::IO, x)` where the first argument is a stream. The representation used by `show` generally includes Julia-specific formatting and type information. + +[`repr`](@ref) returns the output of `show` as a string. + +See also [`print`](@ref), which writes un-decorated representations. + +# Examples +```jldoctest +julia> show("Hello World!") +"Hello World!" +julia> print("Hello World!") +Hello World! +``` """ show(x) = show(stdout::IO, x) @@ -378,17 +390,15 @@ function show(io::IO, f::Function) end end +print(io::IO, f::Function) = print(io, nameof(f)) + function show(io::IO, x::Core.IntrinsicFunction) name = ccall(:jl_intrinsic_name, Cstring, (Core.IntrinsicFunction,), x) print(io, unsafe_string(name)) end show(io::IO, ::Core.TypeofBottom) = print(io, "Union{}") - -function show(io::IO, x::Union) - print(io, "Union") - show_delim_array(io, uniontypes(x), '{', ',', '}', false) -end +show(io::IO, ::MIME"text/plain", ::Core.TypeofBottom) = print(io, "Union{}") function print_without_params(@nospecialize(x)) if isa(x,UnionAll) @@ -410,7 +420,17 @@ function io_has_tvar_name(io::IOContext, name::Symbol, @nospecialize(x)) end io_has_tvar_name(io::IO, name::Symbol, @nospecialize(x)) = false -function show(io::IO, x::UnionAll) +function show(io::IO, @nospecialize(x::Type)) + if x isa DataType + show_datatype(io, x) + return + elseif x isa Union + print(io, "Union") + show_delim_array(io, uniontypes(x), '{', ',', '}', false) + return + end + x::UnionAll + if print_without_params(x) return show(io, unwrap_unionall(x).name) end @@ -433,8 +453,6 @@ function show(io::IO, x::UnionAll) show(io, x.var) end -show(io::IO, x::DataType) = show_datatype(io, x) - # Check whether 'sym' (defined in module 'parent') is visible from module 'from' # 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. @@ -455,14 +473,14 @@ function show_type_name(io::IO, tn::Core.TypeName) globname = isdefined(tn, :mt) ? tn.mt.name : nothing globfunc = false if globname !== nothing - globname_str = string(globname) + globname_str = string(globname::Symbol) if ('#' ∉ globname_str && '@' ∉ globname_str && isdefined(tn, :module) && isbindingresolved(tn.module, globname) && isdefined(tn.module, globname) && isconcretetype(tn.wrapper) && isa(getfield(tn.module, globname), tn.wrapper)) globfunc = true end end - sym = globfunc ? globname : tn.name + sym = (globfunc ? globname : tn.name)::Symbol if get(io, :compact, false) if globfunc return print(io, "typeof(", sym, ")") @@ -546,7 +564,7 @@ show_supertypes(typ::DataType) = show_supertypes(stdout, typ) """ @show -Show an expression and result, returning the result. +Show an expression and result, returning the result. See also [`show`](@ref). """ macro show(exs...) blk = Expr(:block) @@ -564,7 +582,7 @@ end show(io::IO, ::Nothing) = print(io, "nothing") print(io::IO, ::Nothing) = throw(ArgumentError("`nothing` should not be printed; use `show`, `repr`, or custom output instead.")) -show(io::IO, b::Bool) = print(io, b ? "true" : "false") +show(io::IO, b::Bool) = print(io, get(io, :typeinfo, Any) === Bool ? (b ? "1" : "0") : (b ? "true" : "false")) show(io::IO, n::Signed) = (write(io, string(n)); nothing) show(io::IO, n::Unsigned) = print(io, "0x", string(n, pad = sizeof(n)<<1, base = 16)) print(io::IO, n::Unsigned) = print(io, string(n)) @@ -576,6 +594,7 @@ has_tight_type(p::Pair) = typeof(p.second) == typeof(p).parameters[2] isdelimited(io::IO, x) = true +isdelimited(io::IO, x::Function) = !isoperator(Symbol(x)) # !isdelimited means that the Pair is printed with "=>" (like in "1 => 2"), # without its explicit type (like in "Pair{Integer,Integer}(1, 2)") @@ -589,11 +608,10 @@ function gettypeinfos(io::IO, p::Pair) end function show(io::IO, p::Pair) - iocompact = IOContext(io, :compact => get(io, :compact, true)) - isdelimited(io, p) && return show_default(iocompact, p) + isdelimited(io, p) && return show_default(io, p) typeinfos = gettypeinfos(io, p) for i = (1, 2) - io_i = IOContext(iocompact, :typeinfo => typeinfos[i]) + io_i = IOContext(io, :typeinfo => typeinfos[i]) isdelimited(io_i, p[i]) || print(io, "(") show(io_i, p[i]) isdelimited(io_i, p[i]) || print(io, ")") @@ -611,13 +629,12 @@ end function sourceinfo_slotnames(src::CodeInfo) slotnames = src.slotnames - isa(slotnames, Array) || return String[] names = Dict{String,Int}() printnames = Vector{String}(undef, length(slotnames)) for i in eachindex(slotnames) name = string(slotnames[i]) idx = get!(names, name, i) - if idx != i + if idx != i || isempty(name) printname = "$name@_$i" idx > 0 && (printnames[idx] = "$name@_$idx") names[name] = 0 @@ -956,7 +973,8 @@ end function show_call(io::IO, head, func, func_args, indent) op, cl = expr_calls[head] if (isa(func, Symbol) && func !== :(:) && !(head === :. && isoperator(func))) || - (isa(func, Expr) && (func.head == :. || func.head == :curly)) + (isa(func, Expr) && (func.head == :. || func.head == :curly)) || + isa(func, GlobalRef) show_unquoted(io, func, indent) else print(io, '(') @@ -986,7 +1004,7 @@ show_unquoted(io::IO, ex::GotoNode, ::Int, ::Int) = print(io, "goto %", ex function show_unquoted(io::IO, ex::GlobalRef, ::Int, ::Int) print(io, ex.mod) print(io, '.') - quoted = !isidentifier(ex.name) + quoted = !isidentifier(ex.name) && !startswith(string(ex.name), "@") parens = quoted && (!isoperator(ex.name) || (ex.name in quoted_syms)) quoted && print(io, ':') parens && print(io, '(') @@ -1062,6 +1080,10 @@ function show_generator(io, ex, indent) end end +function valid_import_path(@nospecialize ex) + return Meta.isexpr(ex, :(.)) && length(ex.args) > 0 && all(a->isa(a,Symbol), ex.args) +end + function show_import_path(io::IO, ex) if !isa(ex, Expr) print(io, ex) @@ -1092,12 +1114,12 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int) head, args, nargs = ex.head, ex.args, length(ex.args) unhandled = false # dot (i.e. "x.y"), but not compact broadcast exps - if head === :(.) && (length(args) != 2 || !is_expr(args[2], :tuple)) - if length(args) == 2 && is_quoted(args[2]) + if head === :(.) && (nargs != 2 || !is_expr(args[2], :tuple)) + if nargs == 2 && is_quoted(args[2]) item = args[1] # field field = unquoted(args[2]) - parens = !is_quoted(item) && !(item isa Symbol && isidentifier(item)) + parens = !is_quoted(item) && !(item isa Symbol && isidentifier(item)) && !Meta.isexpr(item, :(.)) parens && print(io, '(') show_unquoted(io, item, indent) parens && print(io, ')') @@ -1203,8 +1225,8 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int) end # new expr - elseif head === :new - show_enclosed_list(io, "%new(", args, ", ", ")", indent) + elseif head === :new || head === :splatnew + show_enclosed_list(io, "%$head(", args, ", ", ")", indent) # other call-like expressions ("A[1,2]", "T{X,Y}", "f.(X,Y)") elseif haskey(expr_calls, head) && nargs >= 1 # :ref/:curly/:calldecl/:(.) @@ -1212,23 +1234,23 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int) show_call(io, head, args[1], funcargslike, indent) # comprehensions - elseif head === :typed_comprehension && length(args) == 2 + elseif head === :typed_comprehension && nargs == 2 show_unquoted(io, args[1], indent) print(io, '[') show_generator(io, args[2], indent) print(io, ']') - elseif head === :comprehension && length(args) == 1 + elseif head === :comprehension && nargs == 1 print(io, '[') show_generator(io, args[1], indent) print(io, ']') - elseif (head === :generator && length(args) >= 2) || (head === :flatten && length(args) == 1) + elseif (head === :generator && nargs >= 2) || (head === :flatten && nargs == 1) print(io, '(') show_generator(io, ex, indent) print(io, ')') - elseif head === :filter && length(args) == 2 + elseif head === :filter && nargs == 2 show_unquoted(io, args[2], indent) print(io, " if ") show_unquoted(io, args[1], indent) @@ -1328,7 +1350,7 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int) if prec >= 0 show_call(io, :call, args[1], args[3:end], indent) else - show_args = Vector{Any}(undef, length(args) - 1) + show_args = Vector{Any}(undef, nargs - 1) show_args[1] = args[1] show_args[2:end] = args[3:end] show_list(io, show_args, ' ', indent) @@ -1388,7 +1410,7 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int) end print(io, '"') - elseif (head === :&#= || head === :$=#) && length(args) == 1 + elseif (head === :&#= || head === :$=#) && nargs == 1 print(io, head) a1 = args[1] parens = (isa(a1,Expr) && a1.head !== :tuple) || (isa(a1,Symbol) && isoperator(a1)) @@ -1397,7 +1419,7 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int) parens && print(io, ")") # transpose - elseif head === Symbol('\'') && length(args) == 1 + elseif head === Symbol('\'') && nargs == 1 if isa(args[1], Symbol) show_unquoted(io, args[1]) else @@ -1408,7 +1430,7 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int) print(io, head) # `where` syntax - elseif head === :where && length(args) > 1 + elseif head === :where && nargs > 1 parens = 1 <= prec parens && print(io, "(") show_unquoted(io, args[1], indent, operator_precedence(:(::))) @@ -1422,7 +1444,9 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int) end parens && print(io, ")") - elseif head === :import || head === :using + elseif (head === :import || head === :using) && nargs == 1 && + (valid_import_path(args[1]) || + (Meta.isexpr(args[1], :(:)) && length(args[1].args) > 1 && all(valid_import_path, args[1].args))) print(io, head) print(io, ' ') first = true @@ -1433,11 +1457,11 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int) first = false show_import_path(io, a) end - elseif head === :meta && length(args) >= 2 && args[1] === :push_loc + elseif head === :meta && nargs >= 2 && args[1] === :push_loc print(io, "# meta: location ", join(args[2:end], " ")) - elseif head === :meta && length(args) == 1 && args[1] === :pop_loc + elseif head === :meta && nargs == 1 && args[1] === :pop_loc print(io, "# meta: pop location") - elseif head === :meta && length(args) == 2 && args[1] === :pop_loc + elseif head === :meta && nargs == 2 && args[1] === :pop_loc print(io, "# meta: pop locations ($(args[2]))") # print anything else as "Expr(head, args...)" else @@ -1559,16 +1583,17 @@ module IRShow Base.last(r::Compiler.StmtRange) = Compiler.last(r) include("compiler/ssair/show.jl") - const debuginfo = Dict{Symbol, Any}( + const __debuginfo = Dict{Symbol, Any}( # :full => src -> Base.IRShow.DILineInfoPrinter(src.linetable), # and add variable slot information :source => src -> Base.IRShow.DILineInfoPrinter(src.linetable), # :oneliner => src -> Base.IRShow.PartialLineInfoPrinter(src.linetable), :none => src -> Base.IRShow.lineinfo_disabled, ) - debuginfo[:default] = debuginfo[:none] + const default_debuginfo = Ref{Symbol}(:none) + debuginfo(sym) = sym == :default ? default_debuginfo[] : sym end -function show(io::IO, src::CodeInfo; debuginfo::Symbol=:default) +function show(io::IO, src::CodeInfo; debuginfo::Symbol=:source) # Fix slot names and types in function body print(io, "CodeInfo(") lambda_io::IOContext = io @@ -1579,7 +1604,9 @@ function show(io::IO, src::CodeInfo; debuginfo::Symbol=:default) if isempty(src.linetable) || src.linetable[1] isa LineInfoNode println(io) # TODO: static parameter values? - IRShow.show_ir(lambda_io, src, IRShow.debuginfo[debuginfo](src)) + # only accepts :source or :none, we can't have a fallback for default since + # that would break code_typed(, debuginfo=:source) iff IRShow.default_debuginfo[] = :none + IRShow.show_ir(lambda_io, src, IRShow.__debuginfo[debuginfo](src)) else # this is a CodeInfo that has not been used as a method yet, so its locations are still LineNumberNodes body = Expr(:block) @@ -1779,10 +1806,9 @@ end function alignment(io::IO, x::Pair) s = sprint(show, x, context=io, sizehint=0) if !isdelimited(io, x) # i.e. use "=>" for display - iocompact = IOContext(io, :compact => get(io, :compact, true), - :typeinfo => gettypeinfos(io, x)[1]) - left = length(sprint(show, x.first, context=iocompact, sizehint=0)) - left += 2 * !isdelimited(iocompact, x.first) # for parens around p.first + ctx = IOContext(io, :typeinfo => gettypeinfos(io, x)[1]) + left = length(sprint(show, x.first, context=ctx, sizehint=0)) + left += 2 * !isdelimited(ctx, x.first) # for parens around p.first left += !get(io, :compact, false) # spaces are added around "=>" (left+1, length(s)-left-1) # +1 for the "=" part of "=>" else @@ -1830,12 +1856,13 @@ _indsstring(i) = string(i) _indsstring(i::Union{IdentityUnitRange, Slice}) = string(i.indices) # anything array-like gets summarized e.g. 10-element Array{Int64,1} -summary(io::IO, a::AbstractArray) = summary(io, a, axes(a)) -function summary(io::IO, a, inds::Tuple{Vararg{OneTo}}) +summary(io::IO, a::AbstractArray) = array_summary(io, a, axes(a)) +function array_summary(io::IO, a, inds::Tuple{Vararg{OneTo}}) print(io, dims2string(length.(inds)), " ") showarg(io, a, true) end -function summary(io::IO, a, inds) +function array_summary(io::IO, a, inds) + print(io, dims2string(length.(inds)), " ") showarg(io, a, true) print(io, " with indices ", inds2string(inds)) end diff --git a/base/simdloop.jl b/base/simdloop.jl index 05b4e861dfb372..0fdedba06e1cd8 100644 --- a/base/simdloop.jl +++ b/base/simdloop.jl @@ -37,17 +37,21 @@ check_body!(x::QuoteNode) = check_body!(x.value) check_body!(x) = true # @simd splits a for loop into two loops: an outer scalar loop and -# an inner loop marked with :simdloop. The simd_... functions define +# an inner loop marked with :loopinfo. The simd_... functions define # the splitting. +# Custom iterators that do not support random access cannot support +# vectorization. In order to be compatible with `@simd` annotated loops, +#they should override `simd_inner_length(v::MyIter, j) = 1`, +#`simd_outer_range(v::MyIter) = v`, and `simd_index(v::MyIter, j, i) = j`. # Get range for outer loop. simd_outer_range(r) = 0:0 # Get trip count for inner loop. -@inline simd_inner_length(r,j::Int) = Base.length(r) +@inline simd_inner_length(r, j) = Base.length(r) # Construct user-level element from original range, outer loop index j, and inner loop index i. -@inline simd_index(r,j::Int,i) = (@inbounds ret = r[i+firstindex(r)]; ret) +@inline simd_index(r, j, i) = (@inbounds ret = r[i+firstindex(r)]; ret) # Compile Expr x in context of @simd. function compile(x, ivdep) @@ -72,7 +76,7 @@ function compile(x, ivdep) local $var = Base.simd_index($r,$j,$i) $(x.args[2]) # Body of loop $i += 1 - $(Expr(:simdloop, ivdep)) # Mark loop as SIMD loop + $(Expr(:loopinfo, Symbol("julia.simdloop"), ivdep)) # Mark loop as SIMD loop end end end @@ -121,12 +125,12 @@ either case, your inner loop should have the following properties to allow vecto * No iteration ever waits on a previous iteration to make forward progress. """ macro simd(forloop) - esc(compile(forloop, false)) + esc(compile(forloop, nothing)) end macro simd(ivdep, forloop) if ivdep == :ivdep - esc(compile(forloop, true)) + esc(compile(forloop, Symbol("julia.ivdep"))) else throw(SimdError("Only ivdep is valid as the first argument to @simd")) end diff --git a/base/some.jl b/base/some.jl index b63f1ab3016ed5..1d42f4c2ce77a6 100644 --- a/base/some.jl +++ b/base/some.jl @@ -16,6 +16,7 @@ promote_rule(::Type{Some{T}}, ::Type{Some{S}}) where {T, S<:T} = Some{T} promote_rule(::Type{Some{T}}, ::Type{Nothing}) where {T} = Union{Some{T}, Nothing} convert(::Type{Some{T}}, x::Some) where {T} = Some{T}(convert(T, x.value)) +convert(::Type{Some{T}}, x::Some{T}) where {T} = x convert(::Type{Union{Some{T}, Nothing}}, x::Some) where {T} = convert(Some{T}, x) convert(::Type{Union{T, Nothing}}, x::Union{T, Nothing}) where {T} = x diff --git a/base/sort.jl b/base/sort.jl index 9391db27fa4b10..39197f28117aa6 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -10,7 +10,7 @@ using .Base: copymutable, LinearIndices, length, (:), AbstractVector, @inbounds, AbstractRange, @eval, @inline, Vector, @noinline, AbstractMatrix, AbstractUnitRange, isless, identity, eltype, >, <, <=, >=, |, +, -, *, !, extrema, sub_with_overflow, add_with_overflow, oneunit, div, getindex, setindex!, - length, resize!, fill, Missing, has_offset_axes + length, resize!, fill, Missing, require_one_based_indexing using .Base: >>>, !== @@ -222,7 +222,7 @@ function searchsorted(v::AbstractVector, x, ilo::Int, ihi::Int, o::Ordering) end function searchsortedlast(a::AbstractRange{<:Real}, x::Real, o::DirectOrdering) - has_offset_axes(a) && throw(ArgumentError("range must be indexed starting with 1")) + require_one_based_indexing(a) if step(a) == 0 lt(o, x, first(a)) ? 0 : length(a) else @@ -232,7 +232,7 @@ function searchsortedlast(a::AbstractRange{<:Real}, x::Real, o::DirectOrdering) end function searchsortedfirst(a::AbstractRange{<:Real}, x::Real, o::DirectOrdering) - has_offset_axes(a) && throw(ArgumentError("range must be indexed starting with 1")) + require_one_based_indexing(a) if step(a) == 0 lt(o, first(a), x) ? length(a) + 1 : 1 else @@ -242,7 +242,7 @@ function searchsortedfirst(a::AbstractRange{<:Real}, x::Real, o::DirectOrdering) end function searchsortedlast(a::AbstractRange{<:Integer}, x::Real, o::DirectOrdering) - has_offset_axes(a) && throw(ArgumentError("range must be indexed starting with 1")) + require_one_based_indexing(a) if step(a) == 0 lt(o, x, first(a)) ? 0 : length(a) else @@ -251,7 +251,7 @@ function searchsortedlast(a::AbstractRange{<:Integer}, x::Real, o::DirectOrderin end function searchsortedfirst(a::AbstractRange{<:Integer}, x::Real, o::DirectOrdering) - has_offset_axes(a) && throw(ArgumentError("range must be indexed starting with 1")) + require_one_based_indexing(a) if step(a) == 0 lt(o, first(a), x) ? length(a)+1 : 1 else @@ -260,7 +260,7 @@ function searchsortedfirst(a::AbstractRange{<:Integer}, x::Real, o::DirectOrderi end function searchsortedfirst(a::AbstractRange{<:Integer}, x::Unsigned, o::DirectOrdering) - has_offset_axes(a) && throw(ArgumentError("range must be indexed starting with 1")) + require_one_based_indexing(a) if lt(o, first(a), x) if step(a) == 0 length(a) + 1 @@ -273,7 +273,7 @@ function searchsortedfirst(a::AbstractRange{<:Integer}, x::Unsigned, o::DirectOr end function searchsortedlast(a::AbstractRange{<:Integer}, x::Unsigned, o::DirectOrdering) - has_offset_axes(a) && throw(ArgumentError("range must be indexed starting with 1")) + require_one_based_indexing(a) if lt(o, x, first(a)) 0 elseif step(a) == 0 @@ -305,18 +305,20 @@ if `a` does not contain values equal to `x`. # Examples ```jldoctest -julia> a = [4, 3, 2, 1] -4-element Array{Int64,1}: - 4 - 3 - 2 - 1 +julia> searchsorted([1, 2, 4, 5, 5, 7], 4) # single match +3:3 + +julia> searchsorted([1, 2, 4, 5, 5, 7], 5) # multiple matches +4:5 -julia> searchsorted(a, 4) -5:4 +julia> searchsorted([1, 2, 4, 5, 5, 7], 3) # no match, insert in the middle +3:2 -julia> searchsorted(a, 4, rev=true) -1:1 +julia> searchsorted([1, 2, 4, 5, 5, 7], 9) # no match, insert at end +7:6 + +julia> searchsorted([1, 2, 4, 5, 5, 7], 0) # no match, insert at start +1:0 ``` """ searchsorted @@ -329,14 +331,20 @@ specified order. Return `length(a) + 1` if `x` is greater than all values in `a` # Examples ```jldoctest -julia> searchsortedfirst([1, 2, 4, 5, 14], 4) +julia> searchsortedfirst([1, 2, 4, 5, 5, 7], 4) # single match 3 -julia> searchsortedfirst([1, 2, 4, 5, 14], 4, rev=true) -1 +julia> searchsortedfirst([1, 2, 4, 5, 5, 7], 5) # multiple matches +4 -julia> searchsortedfirst([1, 2, 4, 5, 14], 15) -6 +julia> searchsortedfirst([1, 2, 4, 5, 5, 7], 3) # no match, insert in the middle +3 + +julia> searchsortedfirst([1, 2, 4, 5, 5, 7], 9) # no match, insert at end +7 + +julia> searchsortedfirst([1, 2, 4, 5, 5, 7], 0) # no match, insert at start +1 ``` """ searchsortedfirst @@ -349,13 +357,19 @@ be sorted. # Examples ```jldoctest -julia> searchsortedlast([1, 2, 4, 5, 14], 4) +julia> searchsortedlast([1, 2, 4, 5, 5, 7], 4) # single match 3 -julia> searchsortedlast([1, 2, 4, 5, 14], 4, rev=true) +julia> searchsortedlast([1, 2, 4, 5, 5, 7], 5) # multiple matches 5 -julia> searchsortedlast([1, 2, 4, 5, 14], -1) +julia> searchsortedlast([1, 2, 4, 5, 5, 7], 3) # no match, insert in the middle +2 + +julia> searchsortedlast([1, 2, 4, 5, 5, 7], 9) # no match, insert at end +6 + +julia> searchsortedlast([1, 2, 4, 5, 5, 7], 0) # no match, insert at start 0 ``` """ searchsortedlast @@ -808,9 +822,9 @@ end sortperm(v; alg::Algorithm=DEFAULT_UNSTABLE, lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward) Return a permutation vector `I` that puts `v[I]` in sorted order. The order is specified -using the same keywords as `sort!`. The permutation is guaranteed to be stable even if the -sorting algorithm is unstable, meaning that indices of equal elements appear in ascending -order. +using the same keywords as [`sort!`](@ref). The permutation is guaranteed to be stable even +if the sorting algorithm is unstable, meaning that indices of equal elements appear in +ascending order. See also [`sortperm!`](@ref). diff --git a/base/special/log.jl b/base/special/log.jl index 4b6429adf7c2db..c9aafd3d08224a 100644 --- a/base/special/log.jl +++ b/base/special/log.jl @@ -26,7 +26,7 @@ import .Base.Math.@horner # print("(",l_hi,",",l_lo,"),") # end -const t_log_Float64 = [(0.0,0.0),(0.007782140442941454,-8.865052917267247e-13), +const t_log_Float64 = ((0.0,0.0),(0.007782140442941454,-8.865052917267247e-13), (0.015504186536418274,-4.530198941364935e-13),(0.0231670592820592,-5.248209479295644e-13), (0.03077165866670839,4.529814257790929e-14),(0.0383188643027097,-5.730994833076631e-13), (0.04580953603181115,-5.16945692881222e-13),(0.053244514518155484,6.567993368985218e-13), @@ -90,7 +90,7 @@ const t_log_Float64 = [(0.0,0.0),(0.007782140442941454,-8.865052917267247e-13), (0.6694306539429817,-3.5246757297904794e-13),(0.6734226752123504,-1.8372084495629058e-13), (0.6773988235909201,8.860668981349492e-13),(0.6813592248072382,6.64862680714687e-13), (0.6853040030982811,6.383161517064652e-13),(0.6892332812385575,2.5144230728376075e-13), - (0.6931471805601177,-1.7239444525614835e-13)] + (0.6931471805601177,-1.7239444525614835e-13)) # Float32 lookup table @@ -105,7 +105,7 @@ const t_log_Float64 = [(0.0,0.0),(0.007782140442941454,-8.865052917267247e-13), # print(float64(Base.log(big(1.0+j*is7))),",") # end -const t_log_Float32 = [0.0,0.007782140442054949,0.015504186535965254,0.02316705928153438, +const t_log_Float32 = (0.0,0.007782140442054949,0.015504186535965254,0.02316705928153438, 0.030771658666753687,0.0383188643021366,0.0458095360312942,0.053244514518812285, 0.06062462181643484,0.06795066190850775,0.07522342123758753,0.08244366921107459, 0.08961215868968714,0.09672962645855111,0.10379679368164356,0.11081436634029011, @@ -137,7 +137,7 @@ const t_log_Float32 = [0.0,0.007782140442054949,0.015504186535965254,0.023167059 0.6451379613735847,0.6492279466251099,0.6533012720127457,0.65735807270836, 0.661398482245365,0.6654226325450905,0.6694306539426292,0.6734226752121667, 0.6773988235918061,0.6813592248079031,0.6853040030989194,0.689233281238809, - 0.6931471805599453] + 0.6931471805599453) # determine if hardware FMA is available # should probably check with LLVM, see #9855. diff --git a/base/stat.jl b/base/stat.jl index 540c1066898617..2577ffa3472dee 100644 --- a/base/stat.jl +++ b/base/stat.jl @@ -151,7 +151,9 @@ ctime(st::StatStruct) = st.ctime """ ispath(path) -> Bool -Return `true` if `path` is a valid filesystem path, `false` otherwise. +Return `true` if a valid filesystem entity exists at `path`, +otherwise returns `false`. +This is the generalization of [`isfile`](@ref), [`isdir`](@ref) etc. """ ispath(st::StatStruct) = filemode(st) & 0xf000 != 0x0000 @@ -182,6 +184,8 @@ true julia> isdir("not/a/directory") false ``` + +See also: [`isfile`](@ref) and [`ispath`](@ref). """ isdir(st::StatStruct) = filemode(st) & 0xf000 == 0x4000 @@ -209,6 +213,8 @@ true julia> close(f); rm("test_file.txt") ``` + +See also: [`isdir`](@ref) and [`ispath`](@ref). """ isfile(st::StatStruct) = filemode(st) & 0xf000 == 0x8000 diff --git a/base/stream.jl b/base/stream.jl index 6bf914fc05d774..8d90bd8be9eadd 100644 --- a/base/stream.jl +++ b/base/stream.jl @@ -573,7 +573,7 @@ function uv_readcb(handle::Ptr{Cvoid}, nread::Cssize_t, buf::Ptr{Cvoid}) ((bytesavailable(stream.buffer) >= stream.throttle) || (bytesavailable(stream.buffer) >= stream.buffer.maxsize))) # save cycles by stopping kernel notifications from arriving - ccall(:uv_read_stop, Cint, (Ptr{Cvoid},), stream) + ccall(:jl_uv_read_stop, Cint, (Ptr{Cvoid},), stream) stream.status = StatusOpen end nothing @@ -712,7 +712,7 @@ function start_reading(stream::LibuvStream) # libuv may call the alloc callback immediately # for a TTY on Windows, so ensure the status is set first stream.status = StatusActive - ret = ccall(:uv_read_start, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}), + ret = ccall(:jl_uv_read_start, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}), stream, uv_jl_alloc_buf::Ptr{Cvoid}, uv_jl_readcb::Ptr{Cvoid}) return ret elseif stream.status == StatusPaused @@ -734,7 +734,7 @@ if Sys.iswindows() function stop_reading(stream::LibuvStream) if stream.status == StatusActive stream.status = StatusOpen - ccall(:uv_read_stop, Cint, (Ptr{Cvoid},), stream) + ccall(:jl_uv_read_stop, Cint, (Ptr{Cvoid},), stream) end nothing end diff --git a/base/strings/basic.jl b/base/strings/basic.jl index 3e3b76950b8804..d7c9cb279f7e4b 100644 --- a/base/strings/basic.jl +++ b/base/strings/basic.jl @@ -203,6 +203,7 @@ string(s::AbstractString) = s (::Type{Vector{T}})(s::AbstractString) where {T<:AbstractChar} = collect(T, s) Symbol(s::AbstractString) = Symbol(String(s)) +Symbol(x...) = Symbol(string(x...)) convert(::Type{T}, s::T) where {T<:AbstractString} = s convert(::Type{T}, s::AbstractString) where {T<:AbstractString} = T(s) @@ -570,8 +571,7 @@ function map(f, s::AbstractString) for c in s c′ = f(c) isa(c′, AbstractChar) || throw(ArgumentError( - "map(f, s::AbstractString) requires f to return AbstractChar; " * - "try map(f, collect(s)) or a comprehension instead")) + "map(f, s::AbstractString) requires f to return AbstractChar; try map(f, collect(s)) or a comprehension instead")) write(out, c′::AbstractChar) end String(take!(out)) @@ -693,7 +693,6 @@ end length(s::CodeUnits) = ncodeunits(s.s) sizeof(s::CodeUnits{T}) where {T} = ncodeunits(s.s) * sizeof(T) size(s::CodeUnits) = (length(s),) -strides(s::CodeUnits) = (1,) elsize(s::CodeUnits{T}) where {T} = sizeof(T) @propagate_inbounds getindex(s::CodeUnits, i::Int) = codeunit(s.s, i) IndexStyle(::Type{<:CodeUnits}) = IndexLinear() diff --git a/base/strings/io.jl b/base/strings/io.jl index 71767cefb52af6..d9bfc3cf4f4625 100644 --- a/base/strings/io.jl +++ b/base/strings/io.jl @@ -6,13 +6,19 @@ print([io::IO], xs...) Write to `io` (or to the default output stream [`stdout`](@ref) -if `io` is not given) a canonical (un-decorated) text representation -of values `xs` if there is one, otherwise call [`show`](@ref). +if `io` is not given) a canonical (un-decorated) text representation. The representation used by `print` includes minimal formatting and tries to avoid Julia-specific details. Printing `nothing` is not allowed and throws an error. +`print` falls back to calling `show`, so most types should just define +`show`. Define `print` if your type has a separate "plain" representation. +For example, `show` displays strings with quotes, and `print` displays strings +without quotes. + +[`string`](@ref) returns the output of `print` as a string. + # Examples ```jldoctest julia> print("Hello World!") @@ -105,6 +111,7 @@ end tostr_sizehint(x) = 8 tostr_sizehint(x::AbstractString) = lastindex(x) +tostr_sizehint(x::Union{String,SubString{String}}) = sizeof(x) tostr_sizehint(x::Float64) = 20 tostr_sizehint(x::Float32) = 12 @@ -112,7 +119,7 @@ function print_to_string(xs...) if isempty(xs) return "" end - siz = 0 + siz::Int = 0 for x in xs siz += tostr_sizehint(x) end @@ -128,7 +135,7 @@ function string_with_env(env, xs...) if isempty(xs) return "" end - siz = 0 + siz::Int = 0 for x in xs siz += tostr_sizehint(x) end @@ -146,6 +153,12 @@ end Create a string from any values, except `nothing`, using the [`print`](@ref) function. +`string` should usually not be defined directly. Instead, define a method +`print(io::IO, x::MyType)`. If `string(x)` for a certain type needs to be +highly efficient, then it may make sense to add a method to `string` and +define `print(io::IO, x::MyType) = print(io, string(x))` to ensure the +functions are consistent. + # Examples ```jldoctest julia> string("a", 1, true) @@ -179,6 +192,7 @@ end repr(x; context=nothing) Create a string from any value using the [`show`](@ref) function. +You should not add methods to `repr`; define a `show` method instead. The optional keyword argument `context` can be set to an `IO` or [`IOContext`](@ref) object whose attributes are used for the I/O stream passed to `show`. @@ -206,6 +220,8 @@ julia> repr(big(1/3), context=:compact => true) """ repr(x; context=nothing) = sprint(show, x; context=context) +limitrepr(x) = repr(x, context = :limit=>true) + # IOBuffer views of a (byte)string: """ @@ -439,7 +455,24 @@ function unescape_string(io, s::AbstractString) end unescape_string(s::AbstractString) = sprint(unescape_string, s, sizehint=lastindex(s)) +""" + @b_str + +Create an immutable byte (`UInt8`) vector using string syntax. +# Examples +```jldoctest +julia> v = b"12\\x01\\x02" +4-element Base.CodeUnits{UInt8,String}: + 0x31 + 0x32 + 0x01 + 0x02 + +julia> v[2] +0x32 +``` +""" macro b_str(s) v = codeunits(unescape_string(s)) QuoteNode(v) diff --git a/base/strings/search.jl b/base/strings/search.jl index cb819b30071770..8b62b34bc2d07e 100644 --- a/base/strings/search.jl +++ b/base/strings/search.jl @@ -259,7 +259,7 @@ findnext(t::AbstractString, s::AbstractString, i::Integer) = _search(s, t, i) findlast(pattern::AbstractString, string::AbstractString) Find the last occurrence of `pattern` in `string`. Equivalent to -[`findlast(pattern, string, lastindex(s))`](@ref). +[`findprev(pattern, string, lastindex(string))`](@ref). # Examples ```jldoctest diff --git a/base/strings/string.jl b/base/strings/string.jl index 50b95f8cbf9f3f..5cbea2dfcc61b8 100644 --- a/base/strings/string.jl +++ b/base/strings/string.jl @@ -100,8 +100,9 @@ function cmp(a::String, b::String) end function ==(a::String, b::String) + pointer_from_objref(a) == pointer_from_objref(b) && return true al = sizeof(a) - al == sizeof(b) && 0 == ccall(:memcmp, Int32, (Ptr{UInt8}, Ptr{UInt8}, UInt), a, b, al) + return al == sizeof(b) && 0 == ccall(:memcmp, Int32, (Ptr{UInt8}, Ptr{UInt8}, UInt), a, b, al) end typemin(::Type{String}) = "" @@ -177,10 +178,10 @@ is_valid_continuation(c) = c & 0xc0 == 0x80 b = codeunit(s, i) u = UInt32(b) << 24 between(b, 0x80, 0xf7) || return reinterpret(Char, u), i+1 - return next_continued(s, i, u) + return iterate_continued(s, i, u) end -function next_continued(s::String, i::Int, u::UInt32) +function iterate_continued(s::String, i::Int, u::UInt32) u < 0xc0000000 && (i += 1; @goto ret) n = ncodeunits(s) # first continuation byte @@ -252,6 +253,8 @@ getindex(s::String, r::UnitRange{<:Integer}) = s[Int(first(r)):Int(last(r))] return ss end +length(s::String) = length_continued(s, 1, ncodeunits(s), ncodeunits(s)) + @inline function length(s::String, i::Int, j::Int) @boundscheck begin 0 < i ≤ ncodeunits(s)+1 || throw(BoundsError(s, i)) @@ -260,12 +263,10 @@ end j < i && return 0 @inbounds i, k = thisind(s, i), i c = j - i + (i == k) - length(s, i, j, c) + length_continued(s, i, j, c) end -length(s::String) = length(s, 1, ncodeunits(s), ncodeunits(s)) - -@inline function length(s::String, i::Int, n::Int, c::Int) +@inline function length_continued(s::String, i::Int, n::Int, c::Int) i < n || return c @inbounds b = codeunit(s, i) @inbounds while true @@ -294,6 +295,13 @@ end isvalid(s::String, i::Int) = checkbounds(Bool, s, i) && thisind(s, i) == i +function isascii(s::String) + @inbounds for i = 1:ncodeunits(s) + codeunit(s, i) >= 0x80 && return false + end + return true +end + """ repeat(c::AbstractChar, r::Integer) -> String diff --git a/base/strings/util.jl b/base/strings/util.jl index 270e23dfeca611..d6d65ebd7dd991 100644 --- a/base/strings/util.jl +++ b/base/strings/util.jl @@ -96,6 +96,9 @@ julia> chop(a, head = 5, tail = 5) ``` """ function chop(s::AbstractString; head::Integer = 0, tail::Integer = 1) + if isempty(s) + return SubString(s) + end SubString(s, nextind(s, firstindex(s), head), prevind(s, lastindex(s), tail)) end @@ -170,7 +173,7 @@ lstrip(s::AbstractString, chars::Chars) = lstrip(in(chars), s) Remove trailing characters from `str`, either those specified by `chars` or those for which the function `pred` returns `true`. -The default behaviour is to remove leading whitespace and delimiters: see +The default behaviour is to remove trailing whitespace and delimiters: see [`isspace`](@ref) for precise details. The optional `chars` argument specifies which characters to remove: it can be a single @@ -195,15 +198,20 @@ rstrip(s::AbstractString) = rstrip(isspace, s) rstrip(s::AbstractString, chars::Chars) = rstrip(in(chars), s) """ - strip(str::AbstractString, [chars]) + strip([pred=isspace,] str::AbstractString) + strip(str::AbstractString, chars) -Remove leading and trailing characters from `str`. +Remove leading and trailing characters from `str`, either those specified by `chars` or +those for which the function `pred` returns `true`. The default behaviour is to remove leading whitespace and delimiters: see [`isspace`](@ref) for precise details. -The optional `chars` argument specifies which characters to remove: it can be a single character, -vector or set of characters, or a predicate function. +The optional `chars` argument specifies which characters to remove: it can be a single +character, vector or set of characters. + +!!! compat "Julia 1.2" + The method which accepts a predicate function requires Julia 1.2 or later. # Examples ```jldoctest @@ -212,7 +220,8 @@ julia> strip("{3, 5}\\n", ['{', '}', '\\n']) ``` """ strip(s::AbstractString) = lstrip(rstrip(s)) -strip(s::AbstractString, chars) = lstrip(rstrip(s, chars), chars) +strip(s::AbstractString, chars::Chars) = lstrip(rstrip(s, chars), chars) +strip(f, s::AbstractString) = lstrip(f, rstrip(f, s)) ## string padding functions ## @@ -602,7 +611,7 @@ function ascii(s::String) end return s end -@noinline __throw_invalid_ascii(s, i) = throw(ArgumentError("invalid ASCII at index $i in $(repr(s))")) +@noinline __throw_invalid_ascii(s::String, i::Int) = throw(ArgumentError("invalid ASCII at index $i in $(repr(s))")) """ ascii(s::AbstractString) diff --git a/base/subarray.jl b/base/subarray.jl index 2d6f8a709a642a..59cc8cbd416997 100644 --- a/base/subarray.jl +++ b/base/subarray.jl @@ -42,7 +42,7 @@ check_parent_index_match(parent, ::NTuple{N, Bool}) where {N} = # are inlined @inline Base.throw_boundserror(A::SubArray, I) = __subarray_throw_boundserror(typeof(A), A.parent, A.indices, A.offset1, A.stride1, I) -@noinline __subarray_throw_boundserror(T, parent, indices, offset1, stride1, I) = +@noinline __subarray_throw_boundserror(::Type{T}, parent, indices, offset1, stride1, I) where {T} = throw(BoundsError(T(parent, indices, offset1, stride1), I)) # This computes the linear indexing compatibility for a given tuple of indices @@ -68,6 +68,8 @@ similar(V::SubArray, T::Type, dims::Dims) = similar(V.parent, T, dims) sizeof(V::SubArray) = length(V) * sizeof(eltype(V)) +copy(V::SubArray) = V.parent[V.indices...] + parent(V::SubArray) = V.parent parentindices(V::SubArray) = V.indices @@ -177,7 +179,7 @@ _maybe_reindex(V, I, A::Tuple{AbstractArray{<:AbstractCartesianIndex{1}}, Vararg _maybe_reindex(V, I, A::Tuple{Any, Vararg{Any}}) = (@_inline_meta; _maybe_reindex(V, I, tail(A))) function _maybe_reindex(V, I, ::Tuple{}) @_inline_meta - @inbounds idxs = to_indices(V.parent, reindex(V, V.indices, I)) + @inbounds idxs = to_indices(V.parent, reindex(V.indices, I)) SubArray(V.parent, idxs) end @@ -191,32 +193,32 @@ end AbstractZeroDimArray{T} = AbstractArray{T, 0} -reindex(V, ::Tuple{}, ::Tuple{}) = () +reindex(::Tuple{}, ::Tuple{}) = () # Skip dropped scalars, so simply peel them off the parent indices and continue -reindex(V, idxs::Tuple{ScalarIndex, Vararg{Any}}, subidxs::Tuple{Vararg{Any}}) = - (@_propagate_inbounds_meta; (idxs[1], reindex(V, tail(idxs), subidxs)...)) +reindex(idxs::Tuple{ScalarIndex, Vararg{Any}}, subidxs::Tuple{Vararg{Any}}) = + (@_propagate_inbounds_meta; (idxs[1], reindex(tail(idxs), subidxs)...)) # Slices simply pass their subindices straight through -reindex(V, idxs::Tuple{Slice, Vararg{Any}}, subidxs::Tuple{Any, Vararg{Any}}) = - (@_propagate_inbounds_meta; (subidxs[1], reindex(V, tail(idxs), tail(subidxs))...)) +reindex(idxs::Tuple{Slice, Vararg{Any}}, subidxs::Tuple{Any, Vararg{Any}}) = + (@_propagate_inbounds_meta; (subidxs[1], reindex(tail(idxs), tail(subidxs))...)) # Re-index into parent vectors with one subindex -reindex(V, idxs::Tuple{AbstractVector, Vararg{Any}}, subidxs::Tuple{Any, Vararg{Any}}) = - (@_propagate_inbounds_meta; (idxs[1][subidxs[1]], reindex(V, tail(idxs), tail(subidxs))...)) +reindex(idxs::Tuple{AbstractVector, Vararg{Any}}, subidxs::Tuple{Any, Vararg{Any}}) = + (@_propagate_inbounds_meta; (idxs[1][subidxs[1]], reindex(tail(idxs), tail(subidxs))...)) # Parent matrices are re-indexed with two sub-indices -reindex(V, idxs::Tuple{AbstractMatrix, Vararg{Any}}, subidxs::Tuple{Any, Any, Vararg{Any}}) = - (@_propagate_inbounds_meta; (idxs[1][subidxs[1], subidxs[2]], reindex(V, tail(idxs), tail(tail(subidxs)))...)) +reindex(idxs::Tuple{AbstractMatrix, Vararg{Any}}, subidxs::Tuple{Any, Any, Vararg{Any}}) = + (@_propagate_inbounds_meta; (idxs[1][subidxs[1], subidxs[2]], reindex(tail(idxs), tail(tail(subidxs)))...)) # In general, we index N-dimensional parent arrays with N indices -@generated function reindex(V, idxs::Tuple{AbstractArray{T,N}, Vararg{Any}}, subidxs::Tuple{Vararg{Any}}) where {T,N} +@generated function reindex(idxs::Tuple{AbstractArray{T,N}, Vararg{Any}}, subidxs::Tuple{Vararg{Any}}) where {T,N} if length(subidxs.parameters) >= N subs = [:(subidxs[$d]) for d in 1:N] tail = [:(subidxs[$d]) for d in N+1:length(subidxs.parameters)] - :(@_propagate_inbounds_meta; (idxs[1][$(subs...)], reindex(V, tail(idxs), ($(tail...),))...)) + :(@_propagate_inbounds_meta; (idxs[1][$(subs...)], reindex(tail(idxs), ($(tail...),))...)) else - :(throw(ArgumentError("cannot re-index $(ndims(V)) dimensional SubArray with fewer than $(ndims(V)) indices\nThis should not occur; please submit a bug report."))) + :(throw(ArgumentError("cannot re-index SubArray with fewer indices than dimensions\nThis should not occur; please submit a bug report."))) end end @@ -225,7 +227,7 @@ SlowSubArray{T,N,P,I} = SubArray{T,N,P,I,false} function getindex(V::SubArray{T,N}, I::Vararg{Int,N}) where {T,N} @_inline_meta @boundscheck checkbounds(V, I...) - @inbounds r = V.parent[reindex(V, V.indices, I)...] + @inbounds r = V.parent[reindex(V.indices, I)...] r end @@ -266,7 +268,7 @@ end function setindex!(V::SubArray{T,N}, x, I::Vararg{Int,N}) where {T,N} @_inline_meta @boundscheck checkbounds(V, I...) - @inbounds V.parent[reindex(V, V.indices, I)...] = x + @inbounds V.parent[reindex(V.indices, I)...] = x V end function setindex!(V::FastSubArray, x, i::Int) @@ -299,14 +301,13 @@ IndexStyle(::Type{<:SubArray}) = IndexCartesian() # Strides are the distance in memory between adjacent elements in a given dimension # which we determine from the strides of the parent -strides(V::SubArray) = substrides(V.parent, V.indices) +strides(V::SubArray) = substrides(strides(V.parent), V.indices) -substrides(parent, I::Tuple) = substrides(parent, strides(parent), I) -substrides(parent, strds::Tuple{}, ::Tuple{}) = () -substrides(parent, strds::NTuple{N,Int}, I::Tuple{ScalarIndex, Vararg{Any}}) where N = (substrides(parent, tail(strds), tail(I))...,) -substrides(parent, strds::NTuple{N,Int}, I::Tuple{Slice, Vararg{Any}}) where N = (first(strds), substrides(parent, tail(strds), tail(I))...) -substrides(parent, strds::NTuple{N,Int}, I::Tuple{AbstractRange, Vararg{Any}}) where N = (first(strds)*step(I[1]), substrides(parent, tail(strds), tail(I))...) -substrides(parent, strds, I::Tuple{Any, Vararg{Any}}) = throw(ArgumentError("strides is invalid for SubArrays with indices of type $(typeof(I[1]))")) +substrides(strds::Tuple{}, ::Tuple{}) = () +substrides(strds::NTuple{N,Int}, I::Tuple{ScalarIndex, Vararg{Any}}) where N = (substrides(tail(strds), tail(I))...,) +substrides(strds::NTuple{N,Int}, I::Tuple{Slice, Vararg{Any}}) where N = (first(strds), substrides(tail(strds), tail(I))...) +substrides(strds::NTuple{N,Int}, I::Tuple{AbstractRange, Vararg{Any}}) where N = (first(strds)*step(I[1]), substrides(tail(strds), tail(I))...) +substrides(strds, I::Tuple{Any, Vararg{Any}}) = throw(ArgumentError("strides is invalid for SubArrays with indices of type $(typeof(I[1]))")) stride(V::SubArray, d::Integer) = d <= ndims(V) ? strides(V)[d] : strides(V)[end] * size(V)[end] @@ -394,12 +395,12 @@ end # indices are taken from the range/vector # Since bounds-checking is performance-critical and uses # indices, it's worth optimizing these implementations thoroughly -axes(S::SubArray) = (@_inline_meta; _indices_sub(S, S.indices...)) -_indices_sub(S::SubArray) = () -_indices_sub(S::SubArray, ::Real, I...) = (@_inline_meta; _indices_sub(S, I...)) -function _indices_sub(S::SubArray, i1::AbstractArray, I...) +axes(S::SubArray) = (@_inline_meta; _indices_sub(S.indices...)) +_indices_sub(::Real, I...) = (@_inline_meta; _indices_sub(I...)) +_indices_sub() = () +function _indices_sub(i1::AbstractArray, I...) @_inline_meta - (unsafe_indices(i1)..., _indices_sub(S, I...)...) + (unsafe_indices(i1)..., _indices_sub(I...)...) end ## Compatibility diff --git a/base/summarysize.jl b/base/summarysize.jl index a2974b967ce3e5..c6cac492b88c4c 100644 --- a/base/summarysize.jl +++ b/base/summarysize.jl @@ -11,7 +11,7 @@ end """ Base.summarysize(obj; exclude=Union{...}, chargeall=Union{...}) -> Int -Compute the amount of memory used by all unique objects reachable from the argument. +Compute the amount of memory, in bytes, used by all unique objects reachable from the argument. # Keyword Arguments - `exclude`: specifies the types of objects to exclude from the traversal. diff --git a/base/sysimg.jl b/base/sysimg.jl index 48920935e95d44..bfd57c28f28583 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -1,486 +1,11 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -baremodule Base - -using Core.Intrinsics, Core.IR - -const is_primary_base_module = ccall(:jl_module_parent, Ref{Module}, (Any,), Base) === Core.Main -ccall(:jl_set_istopmod, Cvoid, (Any, Bool), Base, is_primary_base_module) - -# Try to help prevent users from shooting them-selves in the foot -# with ambiguities by defining a few common and critical operations -# (and these don't need the extra convert code) -getproperty(x::Module, f::Symbol) = getfield(x, f) -setproperty!(x::Module, f::Symbol, v) = setfield!(x, f, v) -getproperty(x::Type, f::Symbol) = getfield(x, f) -setproperty!(x::Type, f::Symbol, v) = setfield!(x, f, v) - -getproperty(Core.@nospecialize(x), f::Symbol) = getfield(x, f) -setproperty!(x, f::Symbol, v) = setfield!(x, f, convert(fieldtype(typeof(x), f), v)) - -function include_relative end -function include(mod::Module, path::AbstractString) - local result - if INCLUDE_STATE === 1 - result = _include1(mod, path) - elseif INCLUDE_STATE === 2 - result = _include(mod, path) - elseif INCLUDE_STATE === 3 - result = include_relative(mod, path) - end - result -end -function include(path::AbstractString) - local result - if INCLUDE_STATE === 1 - result = _include1(Base, path) - elseif INCLUDE_STATE === 2 - result = _include(Base, path) - else - # to help users avoid error (accidentally evaluating into Base), this is not allowed - error("Base.include(string) is discontinued, use `include(fname)` or `Base.include(@__MODULE__, fname)` instead.") - end - result -end -const _included_files = Array{Tuple{Module,String},1}() -function _include1(mod::Module, path) - Core.Compiler.push!(_included_files, (mod, ccall(:jl_prepend_cwd, Any, (Any,), path))) - Core.include(mod, path) -end -let SOURCE_PATH = "" - # simple, race-y TLS, relative include - global _include - function _include(mod::Module, path) - prev = SOURCE_PATH - path = normpath(joinpath(dirname(prev), path)) - push!(_included_files, (mod, abspath(path))) - SOURCE_PATH = path - result = Core.include(mod, path) - SOURCE_PATH = prev - result - end -end -INCLUDE_STATE = 1 # include = Core.include - -include("coreio.jl") - -eval(x) = Core.eval(Base, x) -eval(m::Module, x) = Core.eval(m, x) - -VecElement{T}(arg) where {T} = VecElement{T}(convert(T, arg)) -convert(::Type{T}, arg) where {T<:VecElement} = T(arg) -convert(::Type{T}, arg::T) where {T<:VecElement} = arg - -# init core docsystem -import Core: @doc, @__doc__, WrappedException, @int128_str, @uint128_str, @big_str, @cmd -if isdefined(Core, :Compiler) - import Core.Compiler.CoreDocs - Core.atdoc!(CoreDocs.docm) -end - -include("exports.jl") - -if false - # simple print definitions for debugging. enable these if something - # goes wrong during bootstrap before printing code is available. - # otherwise, they just just eventually get (noisily) overwritten later - global show, print, println - show(io::IO, x) = Core.show(io, x) - print(io::IO, a...) = Core.print(io, a...) - println(io::IO, x...) = Core.println(io, x...) -end - -""" - time_ns() - -Get the time in nanoseconds. The time corresponding to 0 is undefined, and wraps every 5.8 years. -""" -time_ns() = ccall(:jl_hrtime, UInt64, ()) - -start_base_include = time_ns() - -## Load essential files and libraries -include("essentials.jl") -include("ctypes.jl") -include("gcutils.jl") -include("generator.jl") -include("reflection.jl") -include("options.jl") - -# core operations & types -include("promotion.jl") -include("tuple.jl") -include("pair.jl") -include("traits.jl") -include("range.jl") -include("expr.jl") -include("error.jl") - -# core numeric operations & types -include("bool.jl") -include("number.jl") -include("int.jl") -include("operators.jl") -include("pointer.jl") -include("refvalue.jl") -include("refpointer.jl") -include("checked.jl") -using .Checked - -# vararg Symbol constructor -Symbol(x...) = Symbol(string(x...)) - -# array structures -include("indices.jl") -include("array.jl") -include("abstractarray.jl") -include("subarray.jl") -include("views.jl") - -# ## dims-type-converting Array constructors for convenience -# type and dimensionality specified, accepting dims as series of Integers -Vector{T}(::UndefInitializer, m::Integer) where {T} = Vector{T}(undef, Int(m)) -Matrix{T}(::UndefInitializer, m::Integer, n::Integer) where {T} = Matrix{T}(undef, Int(m), Int(n)) -Array{T,N}(::UndefInitializer, d::Vararg{Integer,N}) where {T,N} = Array{T,N}(undef, convert(Tuple{Vararg{Int}}, d)) -# type but not dimensionality specified, accepting dims as series of Integers -Array{T}(::UndefInitializer, m::Integer) where {T} = Array{T,1}(undef, Int(m)) -Array{T}(::UndefInitializer, m::Integer, n::Integer) where {T} = Array{T,2}(undef, Int(m), Int(n)) -Array{T}(::UndefInitializer, m::Integer, n::Integer, o::Integer) where {T} = Array{T,3}(undef, Int(m), Int(n), Int(o)) -Array{T}(::UndefInitializer, d::Integer...) where {T} = Array{T}(undef, convert(Tuple{Vararg{Int}}, d)) -# dimensionality but not type specified, accepting dims as series of Integers -Vector(::UndefInitializer, m::Integer) = Vector{Any}(undef, Int(m)) -Matrix(::UndefInitializer, m::Integer, n::Integer) = Matrix{Any}(undef, Int(m), Int(n)) -# Dimensions as a single tuple -Array{T}(::UndefInitializer, d::NTuple{N,Integer}) where {T,N} = Array{T,N}(undef, convert(Tuple{Vararg{Int}}, d)) -Array{T,N}(::UndefInitializer, d::NTuple{N,Integer}) where {T,N} = Array{T,N}(undef, convert(Tuple{Vararg{Int}}, d)) -# empty vector constructor -Vector() = Vector{Any}(undef, 0) - -# Array constructors for nothing and missing -# type and dimensionality specified -Array{T,N}(::Nothing, d...) where {T,N} = fill!(Array{T,N}(undef, d...), nothing) -Array{T,N}(::Missing, d...) where {T,N} = fill!(Array{T,N}(undef, d...), missing) -# type but not dimensionality specified -Array{T}(::Nothing, d...) where {T} = fill!(Array{T}(undef, d...), nothing) -Array{T}(::Missing, d...) where {T} = fill!(Array{T}(undef, d...), missing) - -include("abstractdict.jl") - -include("iterators.jl") -using .Iterators: zip, enumerate -using .Iterators: Flatten, Filter, product # for generators - -include("namedtuple.jl") - -# numeric operations -include("hashing.jl") -include("rounding.jl") -using .Rounding -include("float.jl") -include("twiceprecision.jl") -include("complex.jl") -include("rational.jl") -include("multinverses.jl") -using .MultiplicativeInverses -include("abstractarraymath.jl") -include("arraymath.jl") - -# define MIME"foo/bar" early so that we can overload 3-arg show -struct MIME{mime} end -macro MIME_str(s) - :(MIME{$(Expr(:quote, Symbol(s)))}) -end -# fallback text/plain representation of any type: -show(io::IO, ::MIME"text/plain", x) = show(io, x) - -# SIMD loops -include("simdloop.jl") -using .SimdLoop - -# map-reduce operators -include("reduce.jl") - -## core structures -include("reshapedarray.jl") -include("reinterpretarray.jl") -include("bitarray.jl") -include("bitset.jl") - -if !isdefined(Core, :Compiler) - include("docs/core.jl") - Core.atdoc!(CoreDocs.docm) -end - -# Some type -include("some.jl") - -include("dict.jl") -include("abstractset.jl") -include("set.jl") - -include("char.jl") -include("strings/basic.jl") -include("strings/string.jl") -include("strings/substring.jl") - -# Definition of StridedArray -StridedFastContiguousSubArray{T,N,A<:DenseArray} = FastContiguousSubArray{T,N,A} -StridedReinterpretArray{T,N,A<:Union{DenseArray,StridedFastContiguousSubArray}} = ReinterpretArray{T,N,S,A} where S -StridedReshapedArray{T,N,A<:Union{DenseArray,StridedFastContiguousSubArray,StridedReinterpretArray}} = ReshapedArray{T,N,A} -StridedSubArray{T,N,A<:Union{DenseArray,StridedReshapedArray,StridedReinterpretArray}, - I<:Tuple{Vararg{Union{RangeIndex, AbstractCartesianIndex}}}} = SubArray{T,N,A,I} -StridedArray{T,N} = Union{DenseArray{T,N}, StridedSubArray{T,N}, StridedReshapedArray{T,N}, StridedReinterpretArray{T,N}} -StridedVector{T} = Union{DenseArray{T,1}, StridedSubArray{T,1}, StridedReshapedArray{T,1}, StridedReinterpretArray{T,1}} -StridedMatrix{T} = Union{DenseArray{T,2}, StridedSubArray{T,2}, StridedReshapedArray{T,2}, StridedReinterpretArray{T,2}} -StridedVecOrMat{T} = Union{StridedVector{T}, StridedMatrix{T}} - -# For OS specific stuff -include(string((length(Core.ARGS)>=2 ? Core.ARGS[2] : ""), "build_h.jl")) # include($BUILDROOT/base/build_h.jl) -include(string((length(Core.ARGS)>=2 ? Core.ARGS[2] : ""), "version_git.jl")) # include($BUILDROOT/base/version_git.jl) - -include("osutils.jl") -include("c.jl") - -# Core I/O -include("io.jl") -include("iostream.jl") -include("iobuffer.jl") - -# strings & printing -include("intfuncs.jl") -include("strings/strings.jl") -include("parse.jl") -include("shell.jl") -include("regex.jl") -include("show.jl") -include("arrayshow.jl") - -# multidimensional arrays -include("cartesian.jl") -using .Cartesian -include("multidimensional.jl") -include("permuteddimsarray.jl") -using .PermutedDimsArrays - -include("broadcast.jl") -using .Broadcast -using .Broadcast: broadcasted, broadcasted_kwsyntax, materialize, materialize! - -# define the real ntuple functions -@inline function ntuple(f::F, ::Val{N}) where {F,N} - N::Int - (N >= 0) || throw(ArgumentError(string("tuple length should be ≥0, got ", N))) - if @generated - quote - @nexprs $N i -> t_i = f(i) - @ncall $N tuple t - end - else - Tuple(f(i) for i = 1:N) - end -end -@inline function fill_to_length(t::Tuple, val, ::Val{N}) where {N} - M = length(t) - M > N && throw(ArgumentError("input tuple of length $M, requested $N")) - if @generated - quote - (t..., $(fill(:val, N-length(t.parameters))...)) - end - else - (t..., fill(val, N-M)...) - end -end - -# missing values -include("missing.jl") - -# version -include("version.jl") - -# system & environment -include("sysinfo.jl") -include("libc.jl") -using .Libc: getpid, gethostname, time - -const DL_LOAD_PATH = String[] -if Sys.isapple() - push!(DL_LOAD_PATH, "@loader_path/julia") - push!(DL_LOAD_PATH, "@loader_path") -end - -include("env.jl") - -# Scheduling -include("libuv.jl") -include("event.jl") -include("task.jl") -include("threads.jl") -include("lock.jl") -include("weakkeydict.jl") - -# Logging -include("logging.jl") -using .CoreLogging - -# functions defined in Random -function rand end -function randn end - -# I/O -include("stream.jl") -include("filesystem.jl") -using .Filesystem -include("process.jl") -include("grisu/grisu.jl") -include("methodshow.jl") -include("secretbuffer.jl") - -# core math functions -include("floatfuncs.jl") -include("math.jl") -using .Math -const (√)=sqrt -const (∛)=cbrt - -INCLUDE_STATE = 2 # include = _include (from lines above) - -# reduction along dims -include("reducedim.jl") # macros in this file relies on string.jl -include("accumulate.jl") - -# basic data structures -include("ordering.jl") -using .Order - -# Combinatorics -include("sort.jl") -using .Sort - -# Fast math -include("fastmath.jl") -using .FastMath - -function deepcopy_internal end - -# enums -include("Enums.jl") -using .Enums - -# BigInts and BigFloats -include("gmp.jl") -using .GMP - -for T in [Signed, Integer, BigInt, Float32, Float64, Real, Complex, Rational] - @eval flipsign(x::$T, ::Unsigned) = +x - @eval copysign(x::$T, ::Unsigned) = +x -end - -include("mpfr.jl") -using .MPFR -big(n::Integer) = convert(BigInt,n) -big(x::AbstractFloat) = convert(BigFloat,x) -big(q::Rational) = big(numerator(q))//big(denominator(q)) - -include("combinatorics.jl") - -# more hashing definitions -include("hashing2.jl") - -# irrational mathematical constants -include("irrationals.jl") -include("mathconstants.jl") -using .MathConstants: ℯ, π, pi - -# (s)printf macros -include("printf.jl") -# import .Printf - -# metaprogramming -include("meta.jl") - -# concurrency and parallelism -include("channels.jl") - -# utilities -include("deepcopy.jl") -include("download.jl") -include("summarysize.jl") -include("errorshow.jl") - -# Stack frames and traces -include("stacktraces.jl") -using .StackTraces - -include("initdefs.jl") - -# worker threads -include("threadcall.jl") - -# code loading -include("uuid.jl") -include("loading.jl") - -# misc useful functions & macros -include("util.jl") - -include("asyncmap.jl") - -include("multimedia.jl") -using .Multimedia - -# deprecated functions -include("deprecated.jl") - -# Some basic documentation -include("docs/basedocs.jl") - -include("client.jl") - -# Documentation -- should always be included last in sysimg. -include("docs/Docs.jl") -using .Docs -if isdefined(Core, :Compiler) && is_primary_base_module - Docs.loaddocs(Core.Compiler.CoreDocs.DOCS) -end - -end_base_include = time_ns() - -if is_primary_base_module -function __init__() - # try to ensuremake sure OpenBLAS does not set CPU affinity (#1070, #9639) - if !haskey(ENV, "OPENBLAS_MAIN_FREE") && !haskey(ENV, "GOTOBLAS_MAIN_FREE") - ENV["OPENBLAS_MAIN_FREE"] = "1" - end - # And try to prevent openblas from starting too many threads, unless/until specifically requested - if !haskey(ENV, "OPENBLAS_NUM_THREADS") && !haskey(ENV, "OMP_NUM_THREADS") - cpu_threads = Sys.CPU_THREADS::Int - if cpu_threads > 8 # always at most 8 - ENV["OPENBLAS_NUM_THREADS"] = "8" - elseif haskey(ENV, "JULIA_CPU_THREADS") # or exactly as specified - ENV["OPENBLAS_NUM_THREADS"] = cpu_threads - end # otherwise, trust that openblas will pick CPU_THREADS anyways, without any intervention - end - # for the few uses of Libc.rand in Base: - Libc.srand() - # Base library init - reinit_stdio() - Multimedia.reinit_displays() # since Multimedia.displays uses stdout as fallback - # initialize loading - init_depot_path() - init_load_path() - nothing -end - -INCLUDE_STATE = 3 # include = include_relative -end - -const tot_time_stdlib = RefValue(0.0) - -end # baremodule Base +Core.include(Main, "Base.jl") using .Base # Ensure this file is also tracked +pushfirst!(Base._included_files, (@__MODULE__, joinpath(@__DIR__, "Base.jl"))) pushfirst!(Base._included_files, (@__MODULE__, joinpath(@__DIR__, "sysimg.jl"))) # set up depot & load paths to be able to find stdlib packages diff --git a/base/sysinfo.jl b/base/sysinfo.jl index c6ed553cf337ae..e9c71b0401277c 100644 --- a/base/sysinfo.jl +++ b/base/sysinfo.jl @@ -234,7 +234,18 @@ function loadavg() return loadavg_ end +""" + Sys.free_memory() + +Get the total free memory in RAM in kilobytes. +""" free_memory() = ccall(:uv_get_free_memory, UInt64, ()) + +""" + Sys.total_memory() + +Get the total memory in RAM (including that which is currently used) in kilobytes. +""" total_memory() = ccall(:uv_get_total_memory, UInt64, ()) """ diff --git a/base/task.jl b/base/task.jl index 4045cde09ffa15..fe64a0b0d64587 100644 --- a/base/task.jl +++ b/base/task.jl @@ -2,6 +2,8 @@ ## basic task functions and TLS +Core.Task(@nospecialize(f), reserved_stack::Int=0) = Core._Task(f, reserved_stack, ThreadSynchronizer()) + # Container for a captured exception and its backtrace. Can be serialized. struct CapturedException <: Exception ex::Any @@ -135,6 +137,8 @@ istaskstarted(t::Task) = ccall(:jl_is_task_started, Cint, (Any,), t) != 0 istaskfailed(t::Task) = (t.state == :failed) +Threads.threadid(t::Task) = Int(ccall(:jl_get_task_tid, Int16, (Any,), t)+1) + task_result(t::Task) = t.result task_local_storage() = get_task_tls(current_task()) @@ -142,7 +146,7 @@ function get_task_tls(t::Task) if t.storage === nothing t.storage = IdDict() end - (t.storage)::IdDict{Any,Any} + return (t.storage)::IdDict{Any,Any} end """ @@ -168,30 +172,35 @@ for emulating dynamic scoping. """ function task_local_storage(body::Function, key, val) tls = task_local_storage() - hadkey = haskey(tls,key) - old = get(tls,key,nothing) + hadkey = haskey(tls, key) + old = get(tls, key, nothing) tls[key] = val - try body() + try + return body() finally - hadkey ? (tls[key] = old) : delete!(tls,key) + hadkey ? (tls[key] = old) : delete!(tls, key) end end # NOTE: you can only wait for scheduled tasks function wait(t::Task) if !istaskdone(t) - if t.donenotify === nothing - t.donenotify = Condition() + lock(t.donenotify) + try + while !istaskdone(t) + wait(t.donenotify) + end + finally + unlock(t.donenotify) end end - while !istaskdone(t) - wait(t.donenotify) - end if istaskfailed(t) throw(t.exception) end end +fetch(@nospecialize x) = x + """ fetch(t::Task) @@ -200,7 +209,7 @@ exception, the exception is propagated (re-thrown in the task that called fetch) """ function fetch(t::Task) wait(t) - task_result(t) + return task_result(t) end @@ -264,6 +273,7 @@ macro async(expr) push!($var, task) end schedule(task) + task end end @@ -271,7 +281,7 @@ end function register_taskdone_hook(t::Task, hook) tls = get_task_tls(t) push!(get!(tls, :TASKDONE_HOOKS, []), hook) - t + return t end # runtime system hook called when a task finishes @@ -284,9 +294,17 @@ function task_done_hook(t::Task) t.backtrace = catch_backtrace() end - if isa(t.donenotify, Condition) && !isempty(t.donenotify.waitq) - handled = true - notify(t.donenotify, result, true, err) + donenotify = t.donenotify + if isa(donenotify, ThreadSynchronizer) + lock(donenotify) + try + if !isempty(donenotify.waitq) + handled = true + notify(donenotify, result, true, err) + end + finally + unlock(donenotify) + end end # Execute any other hooks registered in the TLS @@ -296,8 +314,8 @@ function task_done_hook(t::Task) handled = true end - if err && !handled - if isa(result,InterruptException) && isdefined(Base,:active_repl_backend) && + if err && !handled && Threads.threadid() == 1 + if isa(result, InterruptException) && isdefined(Base, :active_repl_backend) && active_repl_backend.backend_task.state == :runnable && isempty(Workqueue) && active_repl_backend.in_eval throwto(active_repl_backend.backend_task, result) # this terminates the task @@ -311,7 +329,8 @@ function task_done_hook(t::Task) # If an InterruptException happens while blocked in the event loop, try handing # the exception to the REPL task since the current task is done. # issue #19467 - if isa(e,InterruptException) && isdefined(Base,:active_repl_backend) && + if Threads.threadid() == 1 && + isa(e, InterruptException) && isdefined(Base, :active_repl_backend) && active_repl_backend.backend_task.state == :runnable && isempty(Workqueue) && active_repl_backend.in_eval throwto(active_repl_backend.backend_task, e) @@ -321,36 +340,263 @@ function task_done_hook(t::Task) end end -""" - timedwait(testcb::Function, secs::Float64; pollint::Float64=0.1) -Waits until `testcb` returns `true` or for `secs` seconds, whichever is earlier. -`testcb` is polled every `pollint` seconds. -""" -function timedwait(testcb::Function, secs::Float64; pollint::Float64=0.1) - pollint > 0 || throw(ArgumentError("cannot set pollint to $pollint seconds")) - start = time() - done = Channel(1) - timercb(aw) = begin - try - if testcb() - put!(done, :ok) - elseif (time() - start) > secs - put!(done, :timed_out) - end - catch e - put!(done, :error) - finally - isready(done) && close(aw) +## scheduler and work queue + +struct InvasiveLinkedListSynchronized{T} + queue::InvasiveLinkedList{T} + lock::Threads.SpinLock + InvasiveLinkedListSynchronized{T}() where {T} = new(InvasiveLinkedList{T}(), Threads.SpinLock()) +end +isempty(W::InvasiveLinkedListSynchronized) = isempty(W.queue) +length(W::InvasiveLinkedListSynchronized) = length(W.queue) +function push!(W::InvasiveLinkedListSynchronized{T}, t::T) where T + lock(W.lock) + try + push!(W.queue, t) + finally + unlock(W.lock) + end + return W +end +function pushfirst!(W::InvasiveLinkedListSynchronized{T}, t::T) where T + lock(W.lock) + try + pushfirst!(W.queue, t) + finally + unlock(W.lock) + end + return W +end +function pop!(W::InvasiveLinkedListSynchronized) + lock(W.lock) + try + return pop!(W.queue) + finally + unlock(W.lock) + end +end +function popfirst!(W::InvasiveLinkedListSynchronized) + lock(W.lock) + try + return popfirst!(W.queue) + finally + unlock(W.lock) + end +end +function list_deletefirst!(W::InvasiveLinkedListSynchronized{T}, t::T) where T + lock(W.lock) + try + list_deletefirst!(W.queue, t) + finally + unlock(W.lock) + end + return W +end + +const StickyWorkqueue = InvasiveLinkedListSynchronized{Task} +global const Workqueues = [StickyWorkqueue()] +global const Workqueue = Workqueues[1] # default work queue is thread 1 +function __preinit_threads__() + if length(Workqueues) < Threads.nthreads() + resize!(Workqueues, Threads.nthreads()) + for i = 2:length(Workqueues) + Workqueues[i] = StickyWorkqueue() end end + nothing +end - if !testcb() - t = Timer(timercb, pollint, interval = pollint) - ret = fetch(done) - close(t) +function enq_work(t::Task) + (t.state == :runnable && t.queue === nothing) || error("schedule: Task not runnable") + if t.sticky + tid = Threads.threadid(t) + if tid == 0 + tid = Threads.threadid() + end + push!(Workqueues[tid], t) else - ret = :ok + tid = 0 + ccall(:jl_enqueue_task, Cvoid, (Any,), t) end - ret + ccall(:jl_wakeup_thread, Cvoid, (Int16,), (tid - 1) % Int16) + return t +end + +schedule(t::Task) = enq_work(t) + +""" + schedule(t::Task, [val]; error=false) + +Add a [`Task`](@ref) to the scheduler's queue. This causes the task to run constantly when the system +is otherwise idle, unless the task performs a blocking operation such as [`wait`](@ref). + +If a second argument `val` is provided, it will be passed to the task (via the return value of +[`yieldto`](@ref)) when it runs again. If `error` is `true`, the value is raised as an exception in +the woken task. + +# Examples +```jldoctest +julia> a5() = sum(i for i in 1:1000); + +julia> b = Task(a5); + +julia> istaskstarted(b) +false + +julia> schedule(b); + +julia> yield(); + +julia> istaskstarted(b) +true + +julia> istaskdone(b) +true +``` +""" +function schedule(t::Task, @nospecialize(arg); error=false) + # schedule a task to be (re)started with the given value or exception + t.state == :runnable || error("schedule: Task not runnable") + if error + t.queue === nothing || Base.list_deletefirst!(t.queue, t) + t.exception = arg + else + t.queue === nothing || error("schedule: Task not runnable") + t.result = arg + end + enq_work(t) + return t +end + +""" + yield() + +Switch to the scheduler to allow another scheduled task to run. A task that calls this +function is still runnable, and will be restarted immediately if there are no other runnable +tasks. +""" +yield() = (enq_work(current_task()); wait()) + +""" + yield(t::Task, arg = nothing) + +A fast, unfair-scheduling version of `schedule(t, arg); yield()` which +immediately yields to `t` before calling the scheduler. +""" +function yield(t::Task, @nospecialize(x=nothing)) + t.result = x + enq_work(current_task()) + return try_yieldto(ensure_rescheduled, Ref(t)) +end + +""" + yieldto(t::Task, arg = nothing) + +Switch to the given task. The first time a task is switched to, the task's function is +called with no arguments. On subsequent switches, `arg` is returned from the task's last +call to `yieldto`. This is a low-level call that only switches tasks, not considering states +or scheduling in any way. Its use is discouraged. +""" +function yieldto(t::Task, @nospecialize(x=nothing)) + t.result = x + return try_yieldto(identity, Ref(t)) +end + +function try_yieldto(undo, reftask::Ref{Task}) + try + ccall(:jl_switchto, Cvoid, (Any,), reftask) + catch + undo(reftask[]) + rethrow() + end + ct = current_task() + exc = ct.exception + if exc !== nothing + ct.exception = nothing + throw(exc) + end + result = ct.result + ct.result = nothing + return result +end + +# yield to a task, throwing an exception in it +function throwto(t::Task, @nospecialize exc) + t.exception = exc + return yieldto(t) +end + +function ensure_rescheduled(othertask::Task) + ct = current_task() + W = Workqueues[Threads.threadid()] + if ct !== othertask && othertask.state == :runnable + # we failed to yield to othertask + # return it to the head of a queue to be retried later + tid = Threads.threadid(othertask) + Wother = tid == 0 ? W : Workqueues[tid] + pushfirst!(Wother, othertask) + end + # if the current task was queued, + # also need to return it to the runnable state + # before throwing an error + list_deletefirst!(W, ct) + nothing +end + +function trypoptask(W::StickyWorkqueue) + isempty(W) && return + t = popfirst!(W) + if t.state != :runnable + # assume this somehow got queued twice, + # probably broken now, but try discarding this switch and keep going + # can't throw here, because it's probably not the fault of the caller to wait + # and don't want to use print() here, because that may try to incur a task switch + ccall(:jl_safe_printf, Cvoid, (Ptr{UInt8}, Int32...), + "\nWARNING: Workqueue inconsistency detected: popfirst!(Workqueue).state != :runnable\n") + return + end + return t +end + +@noinline function poptaskref(W::StickyWorkqueue) + gettask = () -> trypoptask(W) + task = ccall(:jl_task_get_next, Any, (Any,), gettask) + ## Below is a reference implementation for `jl_task_get_next`, which currently lives in C + #local task + #while true + # task = trypoptask(W) + # task === nothing || break + # if !Threads.in_threaded_loop[] && Threads.threadid() == 1 + # if process_events(true) == 0 + # task = trypoptask(W) + # task === nothing || break + # # if there are no active handles and no runnable tasks, just + # # wait for signals. + # pause() + # end + # else + # if Threads.threadid() == 1 + # process_events(false) + # end + # ccall(:jl_gc_safepoint, Cvoid, ()) + # ccall(:jl_cpu_pause, Cvoid, ()) + # end + #end + return Ref(task) +end + +function wait() + W = Workqueues[Threads.threadid()] + reftask = poptaskref(W) + result = try_yieldto(ensure_rescheduled, reftask) + process_events() + # return when we come out of the queue + return result +end + +if Sys.iswindows() + pause() = ccall(:Sleep, stdcall, Cvoid, (UInt32,), 0xffffffff) +else + pause() = ccall(:pause, Cvoid, ()) end diff --git a/base/threadingconstructs.jl b/base/threadingconstructs.jl index 61a1f598546a60..834fc672a2e1cf 100644 --- a/base/threadingconstructs.jl +++ b/base/threadingconstructs.jl @@ -68,16 +68,17 @@ function _threadsfor(iter,lbody) # Hack to make nested threaded loops kinda work if threadid() != 1 || in_threaded_loop[] # We are in a nested threaded loop - threadsfor_fun(true) + Base.invokelatest(threadsfor_fun, true) else in_threaded_loop[] = true # the ccall is not expected to throw - ccall(:jl_threading_run, Ref{Cvoid}, (Any,), threadsfor_fun) + ccall(:jl_threading_run, Cvoid, (Any,), threadsfor_fun) in_threaded_loop[] = false end nothing end end + """ Threads.@threads @@ -96,7 +97,7 @@ macro threads(args...) throw(ArgumentError("need an expression argument to @threads")) end if ex.head === :for - return _threadsfor(ex.args[1],ex.args[2]) + return _threadsfor(ex.args[1], ex.args[2]) else throw(ArgumentError("unrecognized argument to @threads")) end diff --git a/base/tuple.jl b/base/tuple.jl index 3852ae14368c13..7e8c6804bc496b 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -19,7 +19,7 @@ NTuple length(@nospecialize t::Tuple) = nfields(t) firstindex(@nospecialize t::Tuple) = 1 lastindex(@nospecialize t::Tuple) = length(t) -size(@nospecialize(t::Tuple), d) = (d == 1) ? length(t) : throw(ArgumentError("invalid tuple dimension $d")) +size(@nospecialize(t::Tuple), d::Integer) = (d == 1) ? length(t) : throw(ArgumentError("invalid tuple dimension $d")) axes(@nospecialize t::Tuple) = (OneTo(length(t)),) @eval getindex(@nospecialize(t::Tuple), i::Int) = getfield(t, i, $(Expr(:boundscheck))) @eval getindex(@nospecialize(t::Tuple), i::Real) = getfield(t, convert(Int, i), $(Expr(:boundscheck))) @@ -107,11 +107,25 @@ safe_tail(t::Tuple{}) = () # front (the converse of tail: it skips the last entry) +""" + front(x::Tuple)::Tuple + +Return a `Tuple` consisting of all but the last component of `x`. + +# Examples +```jldoctest +julia> Base.front((1,2,3)) +(1, 2) + +julia> Base.front(()) +ERROR: ArgumentError: Cannot call front on an empty tuple. +``` +""" function front(t::Tuple) @_inline_meta _front(t...) end -_front() = throw(ArgumentError("Cannot call front on an empty tuple")) +_front() = throw(ArgumentError("Cannot call front on an empty tuple.")) _front(v) = () function _front(v, t...) @_inline_meta @@ -120,46 +134,6 @@ end ## mapping ## -""" - ntuple(f::Function, n::Integer) - -Create a tuple of length `n`, computing each element as `f(i)`, -where `i` is the index of the element. - -# Examples -```jldoctest -julia> ntuple(i -> 2*i, 4) -(2, 4, 6, 8) -``` -""" -function ntuple(f::F, n::Integer) where F - t = n == 0 ? () : - n == 1 ? (f(1),) : - n == 2 ? (f(1), f(2)) : - n == 3 ? (f(1), f(2), f(3)) : - n == 4 ? (f(1), f(2), f(3), f(4)) : - n == 5 ? (f(1), f(2), f(3), f(4), f(5)) : - n == 6 ? (f(1), f(2), f(3), f(4), f(5), f(6)) : - n == 7 ? (f(1), f(2), f(3), f(4), f(5), f(6), f(7)) : - n == 8 ? (f(1), f(2), f(3), f(4), f(5), f(6), f(7), f(8)) : - n == 9 ? (f(1), f(2), f(3), f(4), f(5), f(6), f(7), f(8), f(9)) : - n == 10 ? (f(1), f(2), f(3), f(4), f(5), f(6), f(7), f(8), f(9), f(10)) : - _ntuple(f, n) - return t -end - -function _ntuple(f, n) - @_noinline_meta - (n >= 0) || throw(ArgumentError(string("tuple length should be ≥0, got ", n))) - ([f(i) for i = 1:n]...,) -end - -# inferrable ntuple (enough for bootstrapping) -ntuple(f, ::Val{0}) = () -ntuple(f, ::Val{1}) = (@_inline_meta; (f(1),)) -ntuple(f, ::Val{2}) = (@_inline_meta; (f(1), f(2))) -ntuple(f, ::Val{3}) = (@_inline_meta; (f(1), f(2), f(3))) - # 1 argument function map(f, t::Tuple{}) = () map(f, t::Tuple{Any,}) = (f(t[1]),) @@ -243,18 +217,6 @@ if nameof(@__MODULE__) === :Base (::Type{T})(x::Tuple) where {T<:Tuple} = convert(T, x) # still use `convert` for tuples -# resolve ambiguity between preceding and following methods -All16{E,N}(x::Tuple) where {E,N} = convert(All16{E,N}, x) - -function (T::All16{E,N})(itr) where {E,N} - len = N+16 - elts = collect(E, Iterators.take(itr,len)) - if length(elts) != len - _totuple_err(T) - end - (elts...,) -end - (::Type{T})(itr) where {T<:Tuple} = _totuple(T, itr) _totuple(::Type{Tuple{}}, itr, s...) = () @@ -271,6 +233,16 @@ function _totuple(T, itr, s...) (convert(tuple_type_head(T), y[1]), _totuple(tuple_type_tail(T), itr, y[2])...) end +# use iterative algorithm for long tuples +function _totuple(T::Type{All16{E,N}}, itr) where {E,N} + len = N+16 + elts = collect(E, Iterators.take(itr,len)) + if length(elts) != len + _totuple_err(T) + end + (elts...,) +end + _totuple(::Type{Tuple{Vararg{E}}}, itr, s...) where {E} = (collect(E, Iterators.rest(itr,s...))...,) _totuple(::Type{Tuple}, itr, s...) = (collect(Iterators.rest(itr,s...))...,) diff --git a/base/util.jl b/base/util.jl index 77ff86070ce822..e2f333d00154a8 100644 --- a/base/util.jl +++ b/base/util.jl @@ -614,11 +614,8 @@ if Sys.iswindows() # 2.3: If that failed for any reason other than the user canceling, error out. # If the user canceled, just return nothing - if code == ERROR_CANCELLED - return nothing - elseif code != ERROR_SUCCESS - error(Base.Libc.FormatMessage(code)) - end + code == ERROR_CANCELLED && return nothing + windowserror(:winprompt, code != ERROR_SUCCESS) # Step 3: Convert encrypted credentials back to plain text passbuf = Vector{UInt16}(undef, 1024) @@ -630,9 +627,7 @@ if Sys.iswindows() succeeded = ccall((:CredUnPackAuthenticationBufferW, "credui.dll"), Bool, (UInt32, Ptr{Cvoid}, UInt32, Ptr{UInt16}, Ptr{UInt32}, Ptr{UInt16}, Ptr{UInt32}, Ptr{UInt16}, Ptr{UInt32}), 0, outbuf_data[], outbuf_size[], usernamebuf, usernamelen, dummybuf, Ref{UInt32}(1024), passbuf, passlen) - if !succeeded - error(Base.Libc.FormatMessage()) - end + windowserror(:winprompt, !succeeded) # Step 4: Free the encrypted buffer # ccall(:SecureZeroMemory, Ptr{Cvoid}, (Ptr{Cvoid}, Csize_t), outbuf_data[], outbuf_size[]) - not an actual function @@ -659,7 +654,7 @@ _crc32c(a::Union{Array{UInt8},FastContiguousSubArray{UInt8,N,<:Array{UInt8}} whe _crc32c(s::String, crc::UInt32=0x00000000) = unsafe_crc32c(s, sizeof(s) % Csize_t, crc) function _crc32c(io::IO, nb::Integer, crc::UInt32=0x00000000) - nb < 0 && throw(ArgumentError("number of bytes to checksum must be ≥ 0")) + nb < 0 && throw(ArgumentError("number of bytes to checksum must be ≥ 0, got $nb")) # use block size 24576=8192*3, since that is the threshold for # 3-way parallel SIMD code in the underlying jl_crc32c C function. buf = Vector{UInt8}(undef, min(nb, 24576)) diff --git a/base/uuid.jl b/base/uuid.jl index 75bf21d4697cba..2fc75b95684f19 100644 --- a/base/uuid.jl +++ b/base/uuid.jl @@ -65,3 +65,5 @@ end print(io::IO, u::UUID) = print(io, string(u)) show(io::IO, u::UUID) = print(io, "UUID(\"", u, "\")") + +isless(a::UUID, b::UUID) = isless(a.value, b.value) diff --git a/base/weakkeydict.jl b/base/weakkeydict.jl index 4b6dab33aca54a..db1b60e27d7716 100644 --- a/base/weakkeydict.jl +++ b/base/weakkeydict.jl @@ -76,7 +76,7 @@ lock(f, wkh::WeakKeyDict) = lock(f, wkh.lock) trylock(f, wkh::WeakKeyDict) = trylock(f, wkh.lock) function setindex!(wkh::WeakKeyDict{K}, v, key) where K - !isa(key, K) && throw(ArgumentError("$key is not a valid key for type $K")) + !isa(key, K) && throw(ArgumentError("$(limitrepr(key)) is not a valid key for type $K")) finalizer(wkh.finalizer, key) lock(wkh) do wkh.ht[WeakRef(key)] = v @@ -92,14 +92,15 @@ function getkey(wkh::WeakKeyDict{K}, kk, default) where K end end +map!(f,iter::ValueIterator{<:WeakKeyDict})= map!(f, values(iter.dict.ht)) get(wkh::WeakKeyDict{K}, key, default) where {K} = lock(() -> get(wkh.ht, key, default), wkh) get(default::Callable, wkh::WeakKeyDict{K}, key) where {K} = lock(() -> get(default, wkh.ht, key), wkh) function get!(wkh::WeakKeyDict{K}, key, default) where {K} - !isa(key, K) && throw(ArgumentError("$key is not a valid key for type $K")) + !isa(key, K) && throw(ArgumentError("$(limitrepr(key)) is not a valid key for type $K")) lock(() -> get!(wkh.ht, WeakRef(key), default), wkh) end function get!(default::Callable, wkh::WeakKeyDict{K}, key) where {K} - !isa(key, K) && throw(ArgumentError("$key is not a valid key for type $K")) + !isa(key, K) && throw(ArgumentError("$(limitrepr(key)) is not a valid key for type $K")) lock(() -> get!(default, wkh.ht, WeakRef(key)), wkh) end pop!(wkh::WeakKeyDict{K}, key) where {K} = lock(() -> pop!(wkh.ht, key), wkh) diff --git a/contrib/README.md b/contrib/README.md index 1f0594c63e8308..734b24cfed507b 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -4,7 +4,6 @@ Installation | Name | Description | | ---------------------------- | --------------------------------------------------------- | |[ add_license_to_files.jl ](https://github.com/JuliaLang/julia/blob/master/contrib/add_license_to_files.jl ) | Add the Julia license to files in the Julia Project | -|[ build_sysimg.jl ](https://github.com/JuliaLang/julia/blob/master/contrib/build_sysimg.jl) | Build a system image binary | |[ check-whitespace.sh ](https://github.com/JuliaLang/julia/blob/master/contrib/check-whitespace.sh) | Check for trailing white space | |[ commit-name.sh ](https://github.com/JuliaLang/julia/blob/master/contrib/commit-name.sh) | Computes a version name for a commit | |[ filterArgs.sh ](https://github.com/JuliaLang/julia/blob/master/contrib/filterArgs.sh) | Update library search code to use only tokens that start with -L | diff --git a/contrib/fixup-libgfortran.sh b/contrib/fixup-libgfortran.sh index 897c067de6a830..fbe744bff65c70 100755 --- a/contrib/fixup-libgfortran.sh +++ b/contrib/fixup-libgfortran.sh @@ -35,7 +35,7 @@ find_shlib() lib_path="$1" if [ -f "$lib_path" ]; then if [ "$UNAME" = "Linux" ]; then - ldd "$lib_path" | grep $2 | cut -d' ' -f3 | xargs + ldd "$lib_path" | grep $2 | grep -v "not found" | cut -d' ' -f3 | xargs else # $UNAME is "Darwin", we only have two options, see above otool -L "$lib_path" | grep $2 | cut -d' ' -f1 | xargs fi diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index 0b9cc449a85a0f..e3d93455637254 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -90,7 +90,7 @@ function generate_precompile_statements() done = false blackhole = Sys.isunix() ? "/dev/null" : "nul" withenv("JULIA_HISTORY" => blackhole, "JULIA_PROJECT" => nothing, - "TERM" => "") do + "TERM" => "", "JULIA_LOAD_PATH" => Sys.iswindows() ? "@;@stdlib" : "@:@stdlib") do if have_repl p = run(`$(julia_cmd()) -O0 --trace-compile=$precompile_file --sysimage $sysimg --compile=all --startup-file=no --color=yes @@ -99,10 +99,12 @@ function generate_precompile_statements() pty_slave, pty_slave, pty_slave; wait=false) readuntil(pty_master, "julia>", keep=true) t = @async begin + s = "" while true sleep(0.5) - s = String(readavailable(pty_master)) - write(repl_output_buffer, s) + news = String(readavailable(pty_master)) + write(repl_output_buffer, news) + s *= news if occursin("__PRECOMPILE_END__", s) break end diff --git a/contrib/julia-config.jl b/contrib/julia-config.jl index 8ac742fade6c11..c5fe6ad8a7ef3c 100755 --- a/contrib/julia-config.jl +++ b/contrib/julia-config.jl @@ -35,7 +35,7 @@ function ldflags() fl = "-L$(shell_escape(libDir()))" if Sys.iswindows() fl = fl * " -Wl,--stack,8388608" - elseif Sys.islinux() + elseif !Sys.isapple() fl = fl * " -Wl,--export-dynamic" end return fl diff --git a/contrib/mac/app/Makefile b/contrib/mac/app/Makefile index 3a03d129d4a22a..3b360f59329bdc 100644 --- a/contrib/mac/app/Makefile +++ b/contrib/mac/app/Makefile @@ -23,7 +23,7 @@ APP_COPYRIGHT:=© 2016 The Julia Project all: clean $(DMG_NAME) $(DMG_NAME): dmg/$(APP_NAME) dmg/.VolumeIcon.icns dmg/Applications - hdiutil create $@ -size 1t -ov -volname "$(VOL_NAME)" -imagekey zlib-level=9 -srcfolder dmg + hdiutil create $@ -size 1t -fs HFS+ -ov -volname "$(VOL_NAME)" -imagekey zlib-level=9 -srcfolder dmg dmg/.VolumeIcon.icns: julia.icns -mkdir -p dmg diff --git a/contrib/mac/app/julia.icns b/contrib/mac/app/julia.icns index 691f1be5d1cfa2..16358e19db453d 100644 Binary files a/contrib/mac/app/julia.icns and b/contrib/mac/app/julia.icns differ diff --git a/contrib/mac/macports.make b/contrib/mac/macports.make deleted file mode 100644 index c4b32b2157d1b0..00000000000000 --- a/contrib/mac/macports.make +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/make -f - -# Any Makefile variable can be set, but the following are the typical defaults -# required to build with a MacPorts installation - -PREFIX=/opt/local -LLVM_CONFIG=${PREFIX}/bin/llvm-config-mp-3.2 -PCRE_CONFIG=${PREFIX}/bin/pcre-config -CFLAGS='-02 -I${PREFIX}/include -I${PREFIX}/include/ufsparse' -CXXFLAGS='-02' -LDFLAGS='-L${PREFIX}/lib' -CC=/usr/bin/llvm-gcc-4.2 -CXX=/usr/bin/llvm-g++-4.2 -CPP=/usr/bin/llvm-cpp-4.2 -FC=${PREFIX}/bin/gfortran-mp-4.5 -USEGCC=1 -USECLANG=0 -SUITESPARSE_VER_MAJOR=4 -JMAKEFLAGS = USE_SYSTEM_LLVM=1 LLVM_CONFIG=${LLVM_CONFIG} \ - USE_SYSTEM_PCRE=1 PCRE_CONFIG=${PCRE_CONFIG} \ - USE_SYSTEM_LIBM=1 \ - USE_SYSTEM_OPENLIBM=0 \ - USE_SYSTEM_BLAS=1 USE_BLAS64=0\ - USE_SYSTEM_LAPACK=1 \ - USE_SYSTEM_GMP=1 \ - USE_SYSTEM_MPFR=1 \ - USE_SYSTEM_ARPACK=1 \ - USE_SYSTEM_SUITESPARSE=1 \ - USE_SYSTEM_ZLIB=1 \ - USE_SYSTEM_GRISU=0 \ - USE_SYSTEM_LIBUV=0 \ - PREFIX=${PREFIX} CFLAGS=${CFLAGS} CXXFLAGS=${CXXFLAGS} LDFLAGS=${LDFLAGS} \ - CC=${CC} CXX=${CXX} CPP=${CPP} FC=${FC} USEGCC=${USEGCC} USECLANG=${USECLANG} - -all: default -../../usr/lib/libspqr.dylib: - $(MAKE) -C .. -f repackage_system_suitesparse${SUITESPARSE_VER_MAJOR}.make \ - USE_SYSTEM_BLAS=1 USE_SYSTEM_LAPACK=1 USE_BLAS64=0 \ - CFLAGS=${CFLAGS} CXXFLAGS=${CXXFLAGS} LDFLAGS=${LDFLAGS} - -default test release debug: ../../usr/lib/libspqr.dylib - $(MAKE) -C ../.. $(JMAKEFLAGS) $@ - -.PHONY: all default test release debug diff --git a/contrib/normalize_triplet.py b/contrib/normalize_triplet.py index 8ac7e2553c2914..d3b168e71ebb5c 100755 --- a/contrib/normalize_triplet.py +++ b/contrib/normalize_triplet.py @@ -6,7 +6,7 @@ # a method `platform_key_abi()` to parse uname-like output into something standarized. if len(sys.argv) < 2: - print("Usage: %s []") + print("Usage: %s [] []") sys.exit(1) arch_mapping = { @@ -108,15 +108,22 @@ def p(x): # "-gcc8" tag at the end of the triplet, but only if it has otherwise # not been specified if gcc_version == "blank_gcc": - if len(sys.argv) == 3: + if len(sys.argv) >= 3: gcc_version = { "4": "gcc4", "5": "gcc4", "6": "gcc4", "7": "gcc7", "8": "gcc8", - }[sys.argv[2][0]] + }[list(filter(lambda x: re.match("\d+\.\d+\.\d+", x), sys.argv[2].split()))[-1][0]] +if cxx_abi == "blank_cxx_abi": + if len(sys.argv) == 4: + cxx_abi = { + "0": "cxx03", + "1": "cxx11", + "": "", + }[sys.argv[3]] print(arch+p(platform)+p(libc)+r(call_abi)+p(gcc_version)+p(cxx_abi)) diff --git a/contrib/refresh_bb_tarballs.sh b/contrib/refresh_bb_tarballs.sh index 9aac101b68e7d3..5258ae34bbe298 100755 --- a/contrib/refresh_bb_tarballs.sh +++ b/contrib/refresh_bb_tarballs.sh @@ -1,18 +1,34 @@ #!/bin/sh +# Invoke this with no arguments to refresh all tarballs, or with a project name to refresh only that project. +# +# Example: +# ./refresh_bb_tarballs.sh gmp + # Get this list via: # using BinaryBuilder # print("TRIPLETS=\"$(join(triplet.(BinaryBuilder.supported_platforms()), " "))\"") TRIPLETS="i686-linux-gnu x86_64-linux-gnu aarch64-linux-gnu arm-linux-gnueabihf powerpc64le-linux-gnu i686-linux-musl x86_64-linux-musl aarch64-linux-musl arm-linux-musleabihf x86_64-apple-darwin14 x86_64-unknown-freebsd11.1 i686-w64-mingw32 x86_64-w64-mingw32" # These are the projects currently using BinaryBuilder; both GCC-expanded and non-GCC-expanded: -BB_PROJECTS="llvm" -BB_GCC_EXPANDED_PROJECTS="openblas" +BB_PROJECTS="gmp mbedtls libssh2 mpfr curl libgit2 pcre libuv unwind osxunwind" +BB_GCC_EXPANDED_PROJECTS="llvm openblas suitesparse openlibm" + +# If we've been given a project name, filter down to that one: +if [ -n ${1} ]; then + case "${BB_PROJECTS}" in + *${1}*) BB_PROJECTS="${1}" ;; + *) BB_PROJECTS="" ;; + esac + case "${BB_GCC_EXPANDED_PROJECTS}" in + *${1}*) BB_GCC_EXPANDED_PROJECTS="${1}" ;; + *) BB_GCC_EXPANDED_PROJECTS="" ;; + esac +fi # Get "contrib/" directory path CONTRIB_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) -set -x # For each triplet and each project, download the BB tarball and save its hash: for triplet in ${TRIPLETS}; do for proj in ${BB_PROJECTS}; do @@ -24,8 +40,8 @@ for triplet in ${TRIPLETS}; do for proj in ${BB_GCC_EXPANDED_PROJECTS}; do PROJ="$(echo ${proj} | tr [a-z] [A-Z])" for gcc in gcc4 gcc7 gcc8; do - make -C "${CONTRIB_DIR}/../deps" USE_BINARYBUILDER_${PROJ}=1 ${PROJ}_BB_TRIPLET=${triplet}-${gcc} distclean-${proj} - make -C "${CONTRIB_DIR}/../deps" USE_BINARYBUILDER_${PROJ}=1 ${PROJ}_BB_TRIPLET=${triplet}-${gcc} compile-${proj} + make -C "${CONTRIB_DIR}/../deps" USE_BINARYBUILDER_${PROJ}=1 ${PROJ}_BB_TRIPLET=${triplet}-${gcc} BB_TRIPLET_CXXABI=${triplet} distclean-${proj} + make -C "${CONTRIB_DIR}/../deps" USE_BINARYBUILDER_${PROJ}=1 ${PROJ}_BB_TRIPLET=${triplet}-${gcc} BB_TRIPLET_CXXABI=${triplet} compile-${proj} done done done diff --git a/contrib/repackage_system_suitesparse4.make b/contrib/repackage_system_suitesparse4.make deleted file mode 100755 index a3ed9889af2925..00000000000000 --- a/contrib/repackage_system_suitesparse4.make +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/make -f - -JULIAHOME := $(abspath ..) -include $(JULIAHOME)/Make.inc - -all: default - -SS_LIB = $(shell dirname $(shell find $(shell eval $(JULIAHOME)/contrib/filterArgs.sh $(LDFLAGS)) /lib /usr/lib /usr/local/lib -name libsuitesparseconfig.a 2>/dev/null | head -n 1) 2>/dev/null) - -ifeq ($(OS),Darwin) -ifeq ($(USE_SYSTEM_BLAS),1) -ifeq ($(USE_SYSTEM_LAPACK),0) - -$(build_private_libdir)/libgfortblas.dylib: - mkdir -p $(build_private_libdir) - make -C ../deps/ $@ - -default: $(build_private_libdir)/libgfortblas.dylib -endif -endif -endif - -default: - mkdir -p $(build_private_libdir) - mkdir -p $(JULIAHOME)/deps/build/SuiteSparse-SYSTEM/lib - cd $(JULIAHOME)/deps/build/SuiteSparse-SYSTEM/lib && \ - rm -f $(build_private_libdir)/lib{amd,camd,cholmod,ccolamd,colamd,spqr,umfpack}.$(SHLIB_EXT) - $(CC) -shared $(WHOLE_ARCHIVE) $(SS_LIB)/libsuitesparseconfig.a $(NO_WHOLE_ARCHIVE) -o $(build_private_libdir)/libsuitesparseconfig.$(SHLIB_EXT) - $(INSTALL_NAME_CMD)libsuitesparseconfig.$(SHLIB_EXT) $(build_private_libdir)/libsuitesparseconfig.$(SHLIB_EXT) - $(CC) -shared $(WHOLE_ARCHIVE) $(SS_LIB)/libamd.a $(NO_WHOLE_ARCHIVE) -o $(build_private_libdir)/libamd.$(SHLIB_EXT) $(LDFLAGS) -L$(build_private_libdir) -lsuitesparseconfig $(RPATH_ORIGIN) - $(INSTALL_NAME_CMD)libamd.$(SHLIB_EXT) $(build_private_libdir)/libamd.$(SHLIB_EXT) - $(CC) -shared $(WHOLE_ARCHIVE) $(SS_LIB)/libcamd.a $(NO_WHOLE_ARCHIVE) -o $(build_private_libdir)/libcamd.$(SHLIB_EXT) $(LDFLAGS) -L$(build_private_libdir) -lsuitesparseconfig $(RPATH_ORIGIN) - $(INSTALL_NAME_CMD)libcamd.$(SHLIB_EXT) $(build_private_libdir)/libcamd.$(SHLIB_EXT) - $(CC) -shared $(WHOLE_ARCHIVE) $(SS_LIB)/libccolamd.a $(NO_WHOLE_ARCHIVE) -o $(build_private_libdir)/libccolamd.$(SHLIB_EXT) $(LDFLAGS) $(LIBBLAS) -L$(build_private_libdir) -lsuitesparseconfig $(RPATH_ORIGIN) - $(INSTALL_NAME_CMD)libccolamd.$(SHLIB_EXT) $(build_private_libdir)/libccolamd.$(SHLIB_EXT) - $(CC) -shared $(WHOLE_ARCHIVE) $(SS_LIB)/libcolamd.a $(NO_WHOLE_ARCHIVE) -o $(build_private_libdir)/libcolamd.$(SHLIB_EXT) $(LDFLAGS) $(LIBBLAS) -L$(build_private_libdir) -lsuitesparseconfig $(RPATH_ORIGIN) - $(INSTALL_NAME_CMD)libcolamd.$(SHLIB_EXT) $(build_private_libdir)/libcolamd.$(SHLIB_EXT) - $(CXX) -shared $(WHOLE_ARCHIVE) $(SS_LIB)/libcholmod.a $(NO_WHOLE_ARCHIVE) -o $(build_private_libdir)/libcholmod.$(SHLIB_EXT) $(LDFLAGS) $(LIBBLAS) -L$(build_private_libdir) -lsuitesparseconfig -lcolamd -lccolamd -lcamd -lamd $(RPATH_ORIGIN) - $(INSTALL_NAME_CMD)libcholmod.$(SHLIB_EXT) $(build_private_libdir)/libcholmod.$(SHLIB_EXT) - $(CXX) -shared $(WHOLE_ARCHIVE) $(SS_LIB)/libumfpack.a $(NO_WHOLE_ARCHIVE) -o $(build_private_libdir)/libumfpack.$(SHLIB_EXT) $(LDFLAGS) $(LIBBLAS) -L$(build_private_libdir) -lsuitesparseconfig -lcolamd -lccolamd -lcamd -lamd -lcholmod $(RPATH_ORIGIN) - $(INSTALL_NAME_CMD)libumfpack.$(SHLIB_EXT) $(build_private_libdir)/libumfpack.$(SHLIB_EXT) - $(CXX) -shared $(WHOLE_ARCHIVE) $(SS_LIB)/libspqr.a $(NO_WHOLE_ARCHIVE) -o $(build_private_libdir)/libspqr.$(SHLIB_EXT) $(LDFLAGS) $(LIBBLAS) -L$(build_private_libdir) -lsuitesparseconfig -lcolamd -lccolamd -lcamd -lamd -lcholmod $(RPATH_ORIGIN) - $(INSTALL_NAME_CMD)libspqr.$(SHLIB_EXT) $(build_private_libdir)/libspqr.$(SHLIB_EXT) - diff --git a/contrib/windows/Vagrantfile b/contrib/windows/Vagrantfile index c3da1be4df6a72..dbd8aa0e3fb979 100644 --- a/contrib/windows/Vagrantfile +++ b/contrib/windows/Vagrantfile @@ -1,7 +1,7 @@ # Vagrantfile for building Windows Julia via MSYS2 or Cygwin $script_cygwin = <