From b5f6fdc8513414f941dfec7d840979ff2e9fc48c Mon Sep 17 00:00:00 2001 From: Artem Solod Date: Mon, 7 Oct 2019 00:43:51 +0300 Subject: [PATCH 01/18] modulo when constructing Time values --- stdlib/Dates/src/types.jl | 7 +------ stdlib/Dates/test/arithmetic.jl | 9 +++++++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/stdlib/Dates/src/types.jl b/stdlib/Dates/src/types.jl index 9f7f99a216d462..9116b51a87eb79 100644 --- a/stdlib/Dates/src/types.jl +++ b/stdlib/Dates/src/types.jl @@ -124,7 +124,7 @@ end """ struct Time <: TimeType instant::Nanosecond - Time(instant::Nanosecond) = new(instant) + Time(instant::Nanosecond) = new(mod(instant, 86400000000000)) end # Convert y,m,d to # of Rata Die days @@ -397,11 +397,6 @@ Base.promote_rule(::Type{Date}, x::Type{DateTime}) = DateTime Base.isless(x::T, y::T) where {T<:TimeType} = isless(value(x), value(y)) Base.isless(x::TimeType, y::TimeType) = isless(promote(x, y)...) (==)(x::T, y::T) where {T<:TimeType} = (==)(value(x), value(y)) -function ==(a::Time, b::Time) - return hour(a) == hour(b) && minute(a) == minute(b) && - second(a) == second(b) && millisecond(a) == millisecond(b) && - microsecond(a) == microsecond(b) && nanosecond(a) == nanosecond(b) -end (==)(x::TimeType, y::TimeType) = (===)(promote(x, y)...) Base.min(x::AbstractTime) = x Base.max(x::AbstractTime) = x diff --git a/stdlib/Dates/test/arithmetic.jl b/stdlib/Dates/test/arithmetic.jl index a4861cb7ed17e2..77979edb05dd53 100644 --- a/stdlib/Dates/test/arithmetic.jl +++ b/stdlib/Dates/test/arithmetic.jl @@ -325,6 +325,15 @@ end @test t + Dates.Millisecond(1) == Dates.Time(0, 0, 0, 1) @test t + Dates.Microsecond(1) == Dates.Time(0, 0, 0, 0, 1) @test_throws MethodError t + Dates.Day(1) + @testset "Time-TimePeriod arithmetic inequalities" begin + t = Dates.Time(4, 20) + @test t - Dates.Nanosecond(1) < t + @test t + Dates.Nanosecond(1) > t + @test t + Dates.Hour(24) < typemax(Dates.Time) + @test t - Dates.Hour(24) > typemin(Dates.Time) + @test t + Dates.Hour(23) < t + @test t + Dates.Hour(25) > t + end end @testset "Month arithmetic and non-associativity" begin # Month arithmetic minimizes "edit distance", or number of changes From 5133aec4d82d61352fe41407104e875f5accb8af Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 9 Oct 2019 10:48:51 -0400 Subject: [PATCH 02/18] deps: fix llvm download url Apparently LLVM will still release binaries on releases.llvm.org (and not GitHub), the 8.0.1 release was just an exception. --- deps/llvm.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/llvm.mk b/deps/llvm.mk index 8eddb4bd179d8d..6d67d32ff40a0f 100644 --- a/deps/llvm.mk +++ b/deps/llvm.mk @@ -173,7 +173,7 @@ endif ifneq ($(LLVM_VER),svn) ifeq (,$(findstring rc,$(LLVM_VER))) -ifeq ($(shell [ $(LLVM_VER_MAJ) -ge 8 -a $(LLVM_VER) != 8.0.0 ]; echo $$?),0) +ifeq ($(shell [ x"$(LLVM_VER)" = x"8.0.1" ]; echo $$?),0) LLVM_SRC_URL := https://github.com/llvm/llvm-project/releases/download/llvmorg-$(LLVM_VER) else LLVM_SRC_URL := http://releases.llvm.org/$(LLVM_VER) From a5f5235aa026f27025aaee9c19235b2a477f1afb Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 25 Sep 2019 16:45:56 -0400 Subject: [PATCH 03/18] src: fix and sort some more Makefile dependency links --- src/Makefile | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/Makefile b/src/Makefile index 34946b0ab43f63..d8f185257dcc1c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -206,29 +206,34 @@ $(BUILDDIR)/julia_flisp.boot: $(addprefix $(SRCDIR)/,jlfrontend.scm flisp/aliase $(call cygpath_w,$(SRCDIR)/mk_julia_flisp_boot.scm) $(call cygpath_w,$(dir $<)) $(notdir $<) $(call cygpath_w,$@)) # additional dependency links +$(BUILDDIR)/anticodegen.o $(BUILDDIR)/anticodegen.dbg.obj: $(SRCDIR)/intrinsics.h $(BUILDDIR)/ast.o $(BUILDDIR)/ast.dbg.obj: $(BUILDDIR)/julia_flisp.boot.inc $(SRCDIR)/flisp/*.h +$(BUILDDIR)/builtins.o $(BUILDDIR)/builtins.dbg.obj: $(SRCDIR)/table.c $(SRCDIR)/builtin_proto.h $(BUILDDIR)/codegen.o $(BUILDDIR)/codegen.dbg.obj: $(addprefix $(SRCDIR)/,\ intrinsics.cpp jitlayers.h intrinsics.h codegen_shared.h cgutils.cpp ccall.cpp abi_*.cpp processor.h builtin_proto.h) -$(BUILDDIR)/processor.o $(BUILDDIR)/processor.dbg.obj: $(addprefix $(SRCDIR)/,processor_*.cpp processor.h features_*.h) -$(BUILDDIR)/interpreter.o $(BUILDDIR)/interpreter.dbg.obj: $(SRCDIR)/interpreter-stacktrace.c $(SRCDIR)/builtin_proto.h -$(BUILDDIR)/anticodegen.o $(BUILDDIR)/anticodegen.dbg.obj: $(SRCDIR)/intrinsics.h -$(BUILDDIR)/debuginfo.o $(BUILDDIR)/debuginfo.dbg.obj: \ - $(addprefix $(SRCDIR)/,debuginfo.h processor.h) +$(BUILDDIR)/debuginfo.o $(BUILDDIR)/debuginfo.dbg.obj: $(addprefix $(SRCDIR)/,debuginfo.h processor.h) $(BUILDDIR)/disasm.o $(BUILDDIR)/disasm.dbg.obj: $(SRCDIR)/debuginfo.h $(SRCDIR)/processor.h -$(BUILDDIR)/jitlayers.o $(BUILDDIR)/jitlayers.dbg.obj: $(SRCDIR)/jitlayers.h -$(BUILDDIR)/builtins.o $(BUILDDIR)/builtins.dbg.obj: $(SRCDIR)/table.c $(SRCDIR)/builtin_proto.h +$(BUILDDIR)/dump.o $(BUILDDIR)/dump.dbg.obj: $(addprefix $(SRCDIR)/,common_symbols1.inc common_symbols2.inc builtin_proto.h) +$(BUILDDIR)/gc-debug.o $(BUILDDIR)/gc-debug.dbg.obj: $(SRCDIR)/gc.h +$(BUILDDIR)/gc-pages.o $(BUILDDIR)/gc-pages.dbg.obj: $(SRCDIR)/gc.h +$(BUILDDIR)/gc.o $(BUILDDIR)/gc.dbg.obj: $(SRCDIR)/gc.h $(BUILDDIR)/init.o $(BUILDDIR)/init.dbg.obj: $(SRCDIR)/builtin_proto.h -$(BUILDDIR)/toplevel.o $(BUILDDIR)/toplevel.dbg.obj: $(SRCDIR)/builtin_proto.h +$(BUILDDIR)/interpreter.o $(BUILDDIR)/interpreter.dbg.obj: $(SRCDIR)/interpreter-stacktrace.c $(SRCDIR)/builtin_proto.h +$(BUILDDIR)/jitlayers.o $(BUILDDIR)/jitlayers.dbg.obj: $(SRCDIR)/jitlayers.h $(SRCDIR)/codegen_shared.h $(BUILDDIR)/jltypes.o $(BUILDDIR)/jltypes.dbg.obj: $(SRCDIR)/builtin_proto.h -$(BUILDDIR)/staticdata.o $(BUILDDIR)/staticdata.dbg.obj: $(SRCDIR)/processor.h $(SRCDIR)/builtin_proto.h +$(BUILDDIR)/libllvmcalltest.$(SHLIB_EXT): $(SRCDIR)/codegen_shared.h +$(BUILDDIR)/llvm-alloc-opt.o $(BUILDDIR)/llvm-alloc-opt.dbg.obj: $(SRCDIR)/codegen_shared.h $(BUILDDIR)/llvm-final-gc-lowering.o $(BUILDDIR)/llvm-final-gc-lowering.dbg.obj: $(SRCDIR)/llvm-pass-helpers.h +$(BUILDDIR)/llvm-gc-invariant-verifier.o $(BUILDDIR)/llvm-gc-invariant-verifier.dbg.obj: $(SRCDIR)/codegen_shared.h $(BUILDDIR)/llvm-late-gc-lowering.o $(BUILDDIR)/llvm-late-gc-lowering.dbg.obj: $(SRCDIR)/llvm-pass-helpers.h -$(BUILDDIR)/llvm-pass-helpers.o $(BUILDDIR)/llvm-pass-helpers.dbg.obj: $(SRCDIR)/llvm-pass-helpers.h -$(BUILDDIR)/gc.o $(BUILDDIR)/gc.dbg.obj: $(SRCDIR)/gc.h -$(BUILDDIR)/gc-debug.o $(BUILDDIR)/gc-debug.dbg.obj: $(SRCDIR)/gc.h -$(BUILDDIR)/gc-pages.o $(BUILDDIR)/gc-pages.dbg.obj: $(SRCDIR)/gc.h +$(BUILDDIR)/llvm-multiversioning.o $(BUILDDIR)/llvm-multiversioning.dbg.obj: $(SRCDIR)/codegen_shared.h +$(BUILDDIR)/llvm-pass-helpers.o $(BUILDDIR)/llvm-pass-helpers.dbg.obj: $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/codegen_shared.h +$(BUILDDIR)/llvm-ptls.o $(BUILDDIR)/llvm-ptls.dbg.obj: $(SRCDIR)/codegen_shared.h +$(BUILDDIR)/processor.o $(BUILDDIR)/processor.dbg.obj: $(addprefix $(SRCDIR)/,processor_*.cpp processor.h features_*.h) $(BUILDDIR)/signal-handling.o $(BUILDDIR)/signal-handling.dbg.obj: $(addprefix $(SRCDIR)/,signals-*.c) -$(BUILDDIR)/dump.o $(BUILDDIR)/dump.dbg.obj: $(addprefix $(SRCDIR)/,common_symbols1.inc common_symbols2.inc builtin_proto.h) +$(BUILDDIR)/staticdata.o $(BUILDDIR)/staticdata.dbg.obj: $(SRCDIR)/processor.h $(SRCDIR)/builtin_proto.h +$(BUILDDIR)/toplevel.o $(BUILDDIR)/toplevel.dbg.obj: $(SRCDIR)/builtin_proto.h + $(addprefix $(BUILDDIR)/,threading.o threading.dbg.obj gc.o gc.dbg.obj init.c init.dbg.obj task.o task.dbg.obj): $(addprefix $(SRCDIR)/,threading.h) $(addprefix $(BUILDDIR)/,APInt-C.o APInt-C.dbg.obj runtime_intrinsics.o runtime_intrinsics.dbg.obj): $(SRCDIR)/APInt-C.h From ac9582a60776e911773fa463ed3a70abd39e90c8 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 9 Oct 2019 11:10:05 -0400 Subject: [PATCH 04/18] test: allow selecting running one llvmpasses test --- test/llvmpasses/Makefile | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/llvmpasses/Makefile b/test/llvmpasses/Makefile index eac3373a5d6ccb..ef583fd451f076 100644 --- a/test/llvmpasses/Makefile +++ b/test/llvmpasses/Makefile @@ -1,8 +1,14 @@ SRCDIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) JULIAHOME := $(abspath $(SRCDIR)/../..) include $(JULIAHOME)/Make.inc -test: + +check: $(SRCDIR) + +TESTS = $(patsubst $(SRCDIR)/%,%,$(wildcard $(SRCDIR)/*.jl $(SRCDIR)/*.ll)) + +$(SRCDIR) $(TESTS): PATH=$(build_bindir):$(build_depsbindir):$$PATH \ LD_LIBRARY_PATH=${build_libdir}:$$LD_LIBRARY_PATH \ - $(build_depsbindir)/lit/lit.py -v $(SRCDIR) -.PHONY: test + $(build_depsbindir)/lit/lit.py -v $@ + +.PHONY: $(TESTS) $(SRCDIR) check all From 8e155c70045231ae9c92f595316180e78ce91746 Mon Sep 17 00:00:00 2001 From: Morten Piibeleht Date: Thu, 10 Oct 2019 10:54:27 +1300 Subject: [PATCH 05/18] Fix attaching docstrings to where call syntax (#33446) * Allow documenting where-expressions I.e. docstrings can now also be attached to expressions that look as follows: """ """ foo(::T) where T Note that it was already fine to attach docstrings to such methods when the docstring was attached to the function body: """ """ function foo(::T) where T ... end * Better fix * Add tests --- base/docs/Docs.jl | 19 ++++++++++++++++--- test/docs.jl | 12 ++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/base/docs/Docs.jl b/base/docs/Docs.jl index d409eea9a6ca8f..4fbd0936ca4af7 100644 --- a/base/docs/Docs.jl +++ b/base/docs/Docs.jl @@ -366,13 +366,15 @@ end function calldoc(__source__, __module__, str, def::Expr) @nospecialize str - args = def.args[2:end] + args = callargs(def) if isempty(args) || all(validcall, args) objectdoc(__source__, __module__, str, nothing, def, signature(def)) else docerror(def) end end +callargs(ex::Expr) = isexpr(ex, :where) ? callargs(ex.args[1]) : + isexpr(ex, :call) ? ex.args[2:end] : error("Invalid expression to callargs: $ex") validcall(x) = isa(x, Symbol) || isexpr(x, (:(::), :..., :kw, :parameters)) function moduledoc(__source__, __module__, meta, def, def′::Expr) @@ -502,6 +504,12 @@ function docm(source::LineNumberNode, mod::Module, ex) end end +# iscallexpr checks if an expression is a :call expression. The call expression may be +# also part of a :where expression, so it unwraps the :where layers until it reaches the +# "actual" expression +iscallexpr(ex::Expr) = isexpr(ex, :where) ? iscallexpr(ex.args[1]) : isexpr(ex, :call) +iscallexpr(ex) = false + function docm(source::LineNumberNode, mod::Module, meta, ex, define::Bool = true) @nospecialize meta ex # Some documented expressions may be decorated with macro calls which obscure the actual @@ -530,9 +538,14 @@ function docm(source::LineNumberNode, mod::Module, meta, ex, define::Bool = true # function f end # f(...) # - isexpr(x, FUNC_HEADS) && is_signature(x.args[1]) ? objectdoc(source, mod, meta, def, x, signature(x)) : + # Including if the "call" expression is wrapped in "where" expression(s) (#32960), i.e. + # + # f(::T) where T + # f(::T, ::U) where T where U + # + isexpr(x, FUNC_HEADS) && is_signature(x.args[1]) ? objectdoc(source, mod, meta, def, x, signature(x)) : isexpr(x, [:function, :macro]) && !isexpr(x.args[1], :call) ? objectdoc(source, mod, meta, def, x) : - isexpr(x, :call) ? calldoc(source, mod, meta, x) : + iscallexpr(x) ? calldoc(source, mod, meta, x) : # Type definitions. # diff --git a/test/docs.jl b/test/docs.jl index 9b92377c964bff..152ef3cd00dfb4 100644 --- a/test/docs.jl +++ b/test/docs.jl @@ -159,6 +159,18 @@ t(::Int, ::Any) "t-3" t{S <: Integer}(::S) +# Docstrings to parametric methods after definition using where syntax (#32960): +tw(x::T) where T = nothing +tw(x::T, y::U) where {T, U <: Integer} = nothing +tw(x::T, y::U, z::V) where T where U <: Integer where V <: AbstractFloat = nothing + +"tw-1" +tw(x::T) where T +"tw-2" +tw(x::T, y::U) where {T, U <: Integer} +"tw-3" +tw(x::T, y::U, z::V) where T where U <: Integer where V <: AbstractFloat + "FieldDocs" mutable struct FieldDocs "one" From 581f81c9d7b4c1b8d40f5c165ead05858c58c1f1 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Thu, 10 Oct 2019 11:45:31 +0200 Subject: [PATCH 06/18] use simpler copy algorithm for small BitArrays in broadcasting (#33517) --- base/broadcast.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/base/broadcast.jl b/base/broadcast.jl index 913111e6481c2e..16c0653be515d5 100644 --- a/base/broadcast.jl +++ b/base/broadcast.jl @@ -916,6 +916,7 @@ end @inline function copyto!(dest::BitArray, bc::Broadcasted{Nothing}) axes(dest) == axes(bc) || throwdm(axes(dest), axes(bc)) ischunkedbroadcast(dest, bc) && return chunkedcopyto!(dest, bc) + length(dest) < 256 && return invoke(copyto!, Tuple{AbstractArray, Broadcasted{Nothing}}, dest, bc) tmp = Vector{Bool}(undef, bitcache_size) destc = dest.chunks ind = cind = 1 From 2f7f0bf6dd688e1be63505771c1ecf9cda2cfd23 Mon Sep 17 00:00:00 2001 From: Alexander Seiler Date: Thu, 10 Oct 2019 16:56:45 +0200 Subject: [PATCH 07/18] Fix multiplication of Triangular and Diagonal matrix (#33334) * Fix multiplication of Triangular and Diagonal matrix * Adopt more multiplication methods * Add `similar` methods for `Adjoint` and `Transpose` * Enable type tests --- stdlib/LinearAlgebra/src/diagonal.jl | 18 ++++++--- stdlib/LinearAlgebra/src/triangular.jl | 10 +++++ stdlib/LinearAlgebra/test/diagonal.jl | 56 ++++++++++++++++++++++---- 3 files changed, 70 insertions(+), 14 deletions(-) diff --git a/stdlib/LinearAlgebra/src/diagonal.jl b/stdlib/LinearAlgebra/src/diagonal.jl index bc531d9ac48015..b9792a2ae5a31f 100644 --- a/stdlib/LinearAlgebra/src/diagonal.jl +++ b/stdlib/LinearAlgebra/src/diagonal.jl @@ -162,8 +162,10 @@ end (*)(Da::Diagonal, Db::Diagonal) = Diagonal(Da.diag .* Db.diag) (*)(D::Diagonal, V::AbstractVector) = D.diag .* V -(*)(A::AbstractTriangular, D::Diagonal) = rmul!(copy(A), D) -(*)(D::Diagonal, B::AbstractTriangular) = lmul!(D, copy(B)) +(*)(A::AbstractTriangular, D::Diagonal) = + rmul!(copyto!(similar(A, promote_op(*, eltype(A), eltype(D.diag))), A), D) +(*)(D::Diagonal, B::AbstractTriangular) = + lmul!(D, copyto!(similar(B, promote_op(*, eltype(B), eltype(D.diag))), B)) (*)(A::AbstractMatrix, D::Diagonal) = rmul!(copyto!(similar(A, promote_op(*, eltype(A), eltype(D.diag)), size(A)), A), D) @@ -214,7 +216,8 @@ function lmul!(D::Diagonal, B::UnitUpperTriangular) end *(D::Adjoint{<:Any,<:Diagonal}, B::Diagonal) = Diagonal(adjoint.(D.parent.diag) .* B.diag) -*(A::Adjoint{<:Any,<:AbstractTriangular}, D::Diagonal) = rmul!(copy(A), D) +*(A::Adjoint{<:Any,<:AbstractTriangular}, D::Diagonal) = + rmul!(copyto!(similar(A, promote_op(*, eltype(A), eltype(D.diag))), A), D) function *(adjA::Adjoint{<:Any,<:AbstractMatrix}, D::Diagonal) A = adjA.parent Ac = similar(A, promote_op(*, eltype(A), eltype(D.diag)), (size(A, 2), size(A, 1))) @@ -223,7 +226,8 @@ function *(adjA::Adjoint{<:Any,<:AbstractMatrix}, D::Diagonal) end *(D::Transpose{<:Any,<:Diagonal}, B::Diagonal) = Diagonal(transpose.(D.parent.diag) .* B.diag) -*(A::Transpose{<:Any,<:AbstractTriangular}, D::Diagonal) = rmul!(copy(A), D) +*(A::Transpose{<:Any,<:AbstractTriangular}, D::Diagonal) = + rmul!(copyto!(similar(A, promote_op(*, eltype(A), eltype(D.diag))), A), D) function *(transA::Transpose{<:Any,<:AbstractMatrix}, D::Diagonal) A = transA.parent At = similar(A, promote_op(*, eltype(A), eltype(D.diag)), (size(A, 2), size(A, 1))) @@ -232,7 +236,8 @@ function *(transA::Transpose{<:Any,<:AbstractMatrix}, D::Diagonal) end *(D::Diagonal, B::Adjoint{<:Any,<:Diagonal}) = Diagonal(D.diag .* adjoint.(B.parent.diag)) -*(D::Diagonal, B::Adjoint{<:Any,<:AbstractTriangular}) = lmul!(D, collect(B)) +*(D::Diagonal, B::Adjoint{<:Any,<:AbstractTriangular}) = + lmul!(D, copyto!(similar(B, promote_op(*, eltype(B), eltype(D.diag))), B)) *(D::Diagonal, adjQ::Adjoint{<:Any,<:Union{QRCompactWYQ,QRPackedQ}}) = (Q = adjQ.parent; rmul!(Array(D), adjoint(Q))) function *(D::Diagonal, adjA::Adjoint{<:Any,<:AbstractMatrix}) A = adjA.parent @@ -242,7 +247,8 @@ function *(D::Diagonal, adjA::Adjoint{<:Any,<:AbstractMatrix}) end *(D::Diagonal, B::Transpose{<:Any,<:Diagonal}) = Diagonal(D.diag .* transpose.(B.parent.diag)) -*(D::Diagonal, B::Transpose{<:Any,<:AbstractTriangular}) = lmul!(D, copy(B)) +*(D::Diagonal, B::Transpose{<:Any,<:AbstractTriangular}) = + lmul!(D, copyto!(similar(B, promote_op(*, eltype(B), eltype(D.diag))), B)) function *(D::Diagonal, transA::Transpose{<:Any,<:AbstractMatrix}) A = transA.parent At = similar(A, promote_op(*, eltype(A), eltype(D.diag)), (size(A, 2), size(A, 1))) diff --git a/stdlib/LinearAlgebra/src/triangular.jl b/stdlib/LinearAlgebra/src/triangular.jl index 8728e410ddcbb2..2b6698ecf3840c 100644 --- a/stdlib/LinearAlgebra/src/triangular.jl +++ b/stdlib/LinearAlgebra/src/triangular.jl @@ -50,6 +50,16 @@ for t in (:LowerTriangular, :UnitLowerTriangular, :UpperTriangular, end end +similar(A::Union{Adjoint{Ti,Tv}, Transpose{Ti,Tv}}, ::Type{T}) where {T,Ti,Tv<:LowerTriangular} = + UpperTriangular(similar(parent(parent(A)), T)) +similar(A::Union{Adjoint{Ti,Tv}, Transpose{Ti,Tv}}, ::Type{T}) where {T,Ti,Tv<:UnitLowerTriangular} = + UnitUpperTriangular(similar(parent(parent(A)), T)) +similar(A::Union{Adjoint{Ti,Tv}, Transpose{Ti,Tv}}, ::Type{T}) where {T,Ti,Tv<:UpperTriangular} = + LowerTriangular(similar(parent(parent(A)), T)) +similar(A::Union{Adjoint{Ti,Tv}, Transpose{Ti,Tv}}, ::Type{T}) where {T,Ti,Tv<:UnitUpperTriangular} = + UnitLowerTriangular(similar(parent(parent(A)), T)) + + LowerTriangular(U::UpperTriangular) = throw(ArgumentError( "cannot create a LowerTriangular matrix from an UpperTriangular input")) UpperTriangular(U::LowerTriangular) = throw(ArgumentError( diff --git a/stdlib/LinearAlgebra/test/diagonal.jl b/stdlib/LinearAlgebra/test/diagonal.jl index 698aa80522606f..bf3ca635fbc264 100644 --- a/stdlib/LinearAlgebra/test/diagonal.jl +++ b/stdlib/LinearAlgebra/test/diagonal.jl @@ -444,14 +444,54 @@ end @test Matrix(1.0I, 5, 5) \ Diagonal(fill(1.,5)) == Matrix(I, 5, 5) @testset "Triangular and Diagonal" begin - for T in (LowerTriangular(randn(5,5)), LinearAlgebra.UnitLowerTriangular(randn(5,5))) - D = Diagonal(randn(5)) - @test T*D == Array(T)*Array(D) - @test T'D == Array(T)'*Array(D) - @test transpose(T)*D == transpose(Array(T))*Array(D) - @test D*T' == Array(D)*Array(T)' - @test D*transpose(T) == Array(D)*transpose(Array(T)) - @test D*T == Array(D)*Array(T) + function _test_matrix(type) + if type == Int + return rand(1:9, 5, 5) + else + return randn(type, 5, 5) + end + end + types = (Float64, Int, ComplexF64) + for ta in types + D = Diagonal(_test_matrix(ta)) + for tb in types + B = _test_matrix(tb) + Tmats = (LowerTriangular(B), UnitLowerTriangular(B), UpperTriangular(B), UnitUpperTriangular(B)) + restypes = (LowerTriangular, LowerTriangular, UpperTriangular, UpperTriangular) + for (T, rtype) in zip(Tmats, restypes) + adjtype = (rtype == LowerTriangular) ? UpperTriangular : LowerTriangular + + # Triangular * Diagonal + R = T * D + @test R == Array(T) * Array(D) + @test isa(R, rtype) + + # Diagonal * Triangular + R = D * T + @test R == Array(D) * Array(T) + @test isa(R, rtype) + + # Adjoint of Triangular * Diagonal + R = T' * D + @test R == Array(T)' * Array(D) + @test isa(R, adjtype) + + # Diagonal * Adjoint of Triangular + R = D * T' + @test R == Array(D) * Array(T)' + @test isa(R, adjtype) + + # Transpose of Triangular * Diagonal + R = transpose(T) * D + @test R == transpose(Array(T)) * Array(D) + @test isa(R, adjtype) + + # Diagonal * Transpose of Triangular + R = D * transpose(T) + @test R == Array(D) * transpose(Array(T)) + @test isa(R, adjtype) + end + end end end From 5c1dca4fb9249a409ef53ae540f98fdf1b8e55b7 Mon Sep 17 00:00:00 2001 From: Mustafa M Date: Thu, 10 Oct 2019 11:41:39 -0400 Subject: [PATCH 08/18] Don't eagerly convert isabspath to String (#33491) --- base/path.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/base/path.jl b/base/path.jl index 6b771b6ee0b6e5..2a9ac3cdaab7f3 100644 --- a/base/path.jl +++ b/base/path.jl @@ -79,9 +79,9 @@ end if Sys.iswindows() - isabspath(path::String) = occursin(path_absolute_re, path) + isabspath(path::AbstractString) = occursin(path_absolute_re, path) else - isabspath(path::String) = startswith(path, '/') + isabspath(path::AbstractString) = startswith(path, '/') end """ @@ -463,6 +463,6 @@ end relpath(path::AbstractString, startpath::AbstractString) = relpath(String(path), String(startpath)) -for f in (:isabspath, :isdirpath, :splitdir, :splitdrive, :splitext, :normpath, :abspath) +for f in (:isdirpath, :splitdir, :splitdrive, :splitext, :normpath, :abspath) @eval $f(path::AbstractString) = $f(String(path)) end From b30047137e1916774544510b42cc03a062f04442 Mon Sep 17 00:00:00 2001 From: Fredrik Ekre Date: Thu, 10 Oct 2019 19:40:19 +0200 Subject: [PATCH 09/18] Use default community health files from JuliaLang/.github, see (#33522) https://help.github.com/en/articles/creating-a-default-community-health-file-for-your-organization --- .github/CODE_OF_CONDUCT.md | 23 ----------------------- .github/FUNDING.yml | 1 - .github/ISSUE_TEMPLATE.md | 9 --------- .github/SECURITY.md | 15 --------------- .github/SUPPORT.md | 12 ------------ 5 files changed, 60 deletions(-) delete mode 100644 .github/CODE_OF_CONDUCT.md delete mode 100644 .github/FUNDING.yml delete mode 100644 .github/ISSUE_TEMPLATE.md delete mode 100644 .github/SECURITY.md delete mode 100644 .github/SUPPORT.md diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md deleted file mode 100644 index a4c3b6bd15ba84..00000000000000 --- a/.github/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,23 +0,0 @@ -Julia Community Standards -========================= - -The Julia community is committed to maintaining a welcoming, civil and constructive environment. We expect the following standards to be observed and upheld by all participants in any community forum (mailing lists, GitHub, IRC, etc.). - -**Be respectful and inclusive.** -Please do not use overtly sexual language or imagery, and do not attack anyone based on any aspect of personal identity, including gender, sexuality, religion, ethnicity, race, age or ability. Keep in mind that what you write in public forums is read by many people who don't know you personally, so please refrain from making prejudiced or sexual jokes and comments – even ones that you might consider acceptable in private. Ask yourself if a comment or statement might make someone feel unwelcomed or like an outsider. - -In particular, do not sexualize the term "Julia" or any other aspects of the project. While "Julia" is a female name in many parts of the world, the programming language is not a person and does not have a gender. - -**Give credit.** -All participants in the Julia community are expected to respect copyright laws and ethical attribution standards. This applies to both code and written materials, such as documentation or blog posts. Materials that violate the law, are plagiaristic, or ethically dubious in some way will be removed from officially-maintained lists of resources. - -If you believe one of these standards has been violated, you can either file an issue on an appropriate repository or confidentially contact the [Julia Stewards](https://julialang.org/community/stewards/) at [stewards@julialang.org](mailto:stewards@julialang.org). Keep in mind that most mistakes are due to ignorance rather than malice. - -**Be concise.** -Constructive criticism and suggestions are welcome, but high-traffic forums do not generally have the bandwidth for extensive discourse. Consider writing a blog post if you feel that you have enough to say on a particular subject. - -**Get involved.** -The Julia community is built on a foundation of reciprocity and collaboration. Be aware that most community members contribute on a voluntary basis, so ideas and bug reports are ok, but demands are not. Pull requests are always welcomed – see the [guidelines for contributing](https://github.com/JuliaLang/julia/blob/master/CONTRIBUTING.md) to read about how to get started. - -**Any concerns?** -If you have a conflict or concern that requires resolution, please contact the [Julia Community Stewards](https://julialang.org/community/stewards/). diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 5201c4d1c2f4ff..00000000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -custom: https://numfocus.org/donate-to-julia diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 60707142355fe7..00000000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,9 +0,0 @@ -If you have a question please search or post to our Discourse site: https://discourse.julialang.org. -We use the GitHub issue tracker for bug reports and feature requests only. - -If you're submitting a bug report, be sure to include as much relevant information as -possible, including a minimal reproducible example and the output of `versioninfo()`. -If you're experiencing a problem with a particular package, open an issue on that -package's repository instead. - -Thanks for contributing to the Julia project! diff --git a/.github/SECURITY.md b/.github/SECURITY.md deleted file mode 100644 index bdf8e8d1ccd398..00000000000000 --- a/.github/SECURITY.md +++ /dev/null @@ -1,15 +0,0 @@ -# Security Policy - -## Supported Versions - -The LTS and current stable releases of Julia are the ones supported for security updates. - -| Version | Supported | -| ------- | ------------------ | -| LTS | :white_check_mark: | -| Stable | :white_check_mark: | -| < 1.0 | :x: | - -## Reporting a Vulnerability - -Please report security vulnerabilities by emailing security@julialang.org. diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md deleted file mode 100644 index ca8b032ed8c4c0..00000000000000 --- a/.github/SUPPORT.md +++ /dev/null @@ -1,12 +0,0 @@ -# Getting support for Julia - -We use the GitHub issue tracker for bug reports and feature requests only. If -what you'd like to do is best described as a bug report or a code contribution -then you should submit a GitHub issue or pull request as usual. Please see our -[Notes for Julia -Contributors](https://github.com/JuliaLang/julia/blob/master/CONTRIBUTING.md) -for how to file a bug report, our contributor checklist and other helpful -information. But if you have come here for help, or if you are unsure whether -the behavior you're experiencing is a bug, then you should see our [Community -page](https://julialang.org/community/) for a list of other places where you can -get support first. From 99483b5a9a0e1387f50941f80047241500cd8cff Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Thu, 10 Oct 2019 17:31:16 -0400 Subject: [PATCH 10/18] improve docs for `SpinLock` to steer people away from it (#33514) Inspired by e.g. #33507 --- base/locks-mt.jl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/base/locks-mt.jl b/base/locks-mt.jl index a828adef1f9132..3317bb4d6439dd 100644 --- a/base/locks-mt.jl +++ b/base/locks-mt.jl @@ -11,19 +11,20 @@ export SpinLock # Atomic Locks ########################################## -# Test-and-test-and-set spin locks are quickest up to about 30ish -# contending threads. If you have more contention than that, perhaps -# a lock is the wrong way to synchronize. """ SpinLock() -Create a non-reentrant lock. +Create a non-reentrant, test-and-test-and-set spin lock. Recursive use will result in a deadlock. +This kind of lock should only be used around code that takes little time +to execute and does not block (e.g. perform I/O). +In general, [`ReentrantLock`](@ref) should be used instead. + Each [`lock`](@ref) must be matched with an [`unlock`](@ref). Test-and-test-and-set spin locks are quickest up to about 30ish -contending threads. If you have more contention than that, perhaps -a lock is the wrong way to synchronize. +contending threads. If you have more contention than that, different +synchronization approaches should be considered. """ struct SpinLock <: AbstractLock handle::Atomic{Int} From 1ed2e1d8338babdd8fa1173766e9be9bec8d59ee Mon Sep 17 00:00:00 2001 From: "Huang, Zhaoquan" Date: Fri, 11 Oct 2019 05:31:46 +0800 Subject: [PATCH 11/18] Update dtypes.h to fix C4146 in Visual C++ (#33511) --- src/support/dtypes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/support/dtypes.h b/src/support/dtypes.h index 6e3c13129a0106..916a90928cf7d6 100644 --- a/src/support/dtypes.h +++ b/src/support/dtypes.h @@ -227,7 +227,7 @@ STATIC_INLINE unsigned int next_power_of_two(unsigned int val) JL_NOTSAFEPOINT return val; } -#define LLT_ALIGN(x, sz) (((x) + (sz)-1) & -(sz)) +#define LLT_ALIGN(x, sz) (((x) + (sz)-1) & ~((sz)-1)) // branch prediction annotations #ifdef __GNUC__ From 5f013d82f92026f7dfbe4234f283658beb1f8a2a Mon Sep 17 00:00:00 2001 From: Olivier MATHIEU Date: Fri, 11 Oct 2019 00:28:33 +0200 Subject: [PATCH 12/18] Add takewhile, dropwhile to iterators (#33437) --- NEWS.md | 1 + base/iterators.jl | 101 +++++++++++++++++++++++++++++++++++++++++++++- test/iterators.jl | 23 +++++++++++ 3 files changed, 124 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 7ac3d162a924a9..1df8753ba0a7b6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -31,6 +31,7 @@ New library functions * The `tempname` function now takes a `cleanup::Bool` keyword argument defaulting to `true`, which causes the process to try to ensure that any file or directory at the path returned by `tempname` is deleted upon process exit ([#33090]). * The `readdir` function now takes a `join::Bool` keyword argument defaulting to `false`, which when set causes `readdir` to join its directory argument with each listed name ([#33113]). * The new `only(x)` function returns the one-and-only element of a collection `x`, and throws an `ArgumentError` if `x` contains zero or multiple elements. ([#33129]) +* `takewhile` and `dropwhile` have been added to the Iterators submodule ([#33437]). Standard library changes diff --git a/base/iterators.jl b/base/iterators.jl index 9c813d7e21632a..77849b4c096e08 100644 --- a/base/iterators.jl +++ b/base/iterators.jl @@ -22,7 +22,7 @@ import .Base: getindex, setindex!, get, iterate, popfirst!, isdone, peek -export enumerate, zip, rest, countfrom, take, drop, cycle, repeated, product, flatten, partition +export enumerate, zip, rest, countfrom, take, drop, takewhile, dropwhile, cycle, repeated, product, flatten, partition tail_if_any(::Tuple{}) = () tail_if_any(x::Tuple) = tail(x) @@ -650,6 +650,105 @@ end iterate(it::Drop, state) = iterate(it.xs, state) isdone(it::Drop, state) = isdone(it.xs, state) + +# takewhile + +struct TakeWhile{I,P<:Function} + pred::P + xs::I +end + +""" + takewhile(pred, iter) + +An iterator that generates element from `iter` as long as predicate `pred` is true, +afterwards, drops every element. + +!!! compat "Julia 1.4" + This function requires at least Julia 1.4. + +# Examples + +```jldoctest +julia> s = collect(1:5) +5-element Array{Int64,1}: + 1 + 2 + 3 + 4 + 5 + +julia> collect(Iterators.takewhile(<(3),s)) +2-element Array{Int64,1}: + 1 + 2 +``` +""" +takewhile(pred,xs) = TakeWhile(pred,xs) + +function iterate(ibl::TakeWhile, itr...) + y = iterate(ibl.xs,itr...) + y === nothing && return nothing + ibl.pred(y[1]) || return nothing + y +end + +IteratorSize(::Type{<:TakeWhile}) = SizeUnknown() +eltype(::Type{TakeWhile{I,P}}) where {I,P} = eltype(I) +IteratorEltype(::Type{TakeWhile{I,P}}) where {I,P} = IteratorEltype(I) + + +# dropwhile + +struct DropWhile{I,P<:Function} + pred::P + xs::I +end + +""" + dropwhile(pred, iter) + +An iterator that drops element from `iter` as long as predicate `pred` is true, +afterwards, returns every element. + +!!! compat "Julia 1.4" + This function requires at least Julia 1.4. + +# Examples + +```jldoctest +julia> s = collect(1:5) +5-element Array{Int64,1}: + 1 + 2 + 3 + 4 + 5 + +julia> collect(Iterators.dropwhile(<(3),s)) +3-element Array{Int64,1}: + 3 + 4 + 5 +``` +""" +dropwhile(pred,itr) = DropWhile(pred,itr) + +iterate(ibl::DropWhile,itr) = iterate(ibl.xs, itr) +function iterate(ibl::DropWhile) + y = iterate(ibl.xs) + while y !== nothing + ibl.pred(y[1]) || break + y = iterate(ibl.xs,y[2]) + end + y +end + +IteratorSize(::Type{<:DropWhile}) = SizeUnknown() +eltype(::Type{DropWhile{I,P}}) where {I,P} = eltype(I) +IteratorEltype(::Type{DropWhile{I,P}}) where {I,P} = IteratorEltype(I) + + # Cycle an iterator forever struct Cycle{I} diff --git a/test/iterators.jl b/test/iterators.jl index eabe5e0905b986..1a7b022c2dbeb4 100644 --- a/test/iterators.jl +++ b/test/iterators.jl @@ -149,6 +149,29 @@ for xs in Any["abc", [1, 2, 3]] @test take(drop(take(xs, 3), 1), 1) === take(drop(xs, 1), 1) end +# takewhile +# -------- +@testset begin + @test collect(takewhile(<(4),1:10)) == [1,2,3] + @test collect(takewhile(<(4),Iterators.countfrom(1))) == [1,2,3] + @test collect(takewhile(<(4),5:10)) == [] + @test collect(takewhile(_->true,5:10)) == 5:10 + @test collect(takewhile(isodd,[1,1,2,3])) == [1,1] + @test collect(takewhile(<(2), takewhile(<(3), [1,1,2,3]))) == [1,1] +end + +# dropwhile +# -------- +@testset begin + @test collect(dropwhile(<(4), 1:10)) == 4:10 + @test collect(dropwhile(<(4), 1:10)) isa Vector{Int} + @test isempty(dropwhile(<(4), [])) + @test collect(dropwhile(_->false,1:3)) == 1:3 + @test isempty(dropwhile(_->true, 1:3)) + @test collect(dropwhile(isodd,[1,1,2,3])) == [2,3] + @test collect(dropwhile(iseven,dropwhile(isodd,[1,1,2,3]))) == [3] +end + # cycle # ----- let i = 0 From 49e2f41d8b684e6a1a5883701ec143957d48e090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20Besan=C3=A7on?= Date: Fri, 11 Oct 2019 00:34:48 +0200 Subject: [PATCH 13/18] add setindex for named tuples (#33468) --- base/namedtuple.jl | 24 ++++++++++++++++++++++++ test/namedtuple.jl | 8 ++++++++ 2 files changed, 32 insertions(+) diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 17f1d9c38684ab..965466c71489ee 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -300,3 +300,27 @@ function structdiff(a::NamedTuple{an}, b::Union{NamedTuple{bn}, Type{NamedTuple{ NamedTuple{names,types}(map(n->getfield(a, n), names)) end end + +""" + setindex(nt::NamedTuple, val, key::Symbol) + +Constructs a new `NamedTuple` with the key `key` set to `val`. +If `key` is already in the keys of `nt`, `val` replaces the old value. + +```jldoctest +julia> nt = (a = 3,) +(a = 3,) + +julia> Base.setindex(nt, 33, :b) +(a = 3, b = 33) + +julia> Base.setindex(nt, 4, :a) +(a = 4,) + +julia> Base.setindex(nt, "a", :a) +(a = "a",) +``` +""" +function setindex(nt::NamedTuple, v, idx::Symbol) + merge(nt, (; idx => v)) +end diff --git a/test/namedtuple.jl b/test/namedtuple.jl index 17438a46c7b7fb..af4c20915acda1 100644 --- a/test/namedtuple.jl +++ b/test/namedtuple.jl @@ -267,3 +267,11 @@ let n = NamedTuple{(:T,), Tuple{Type{Float64}}}((Float64,)) @test n isa NamedTuple{(:T,), Tuple{Type{Float64}}} @test n.T === Float64 end + +# setindex +let nt0 = NamedTuple(), nt1 = (a=33,), nt2 = (a=0, b=:v) + @test Base.setindex(nt0, 33, :a) == nt1 + @test Base.setindex(Base.setindex(nt1, 0, :a), :v, :b) == nt2 + @test Base.setindex(nt1, "value", :a) == (a="value",) + @test Base.setindex(nt1, "value", :a) isa NamedTuple{(:a,),<:Tuple{AbstractString}} +end From 181065685969a4d863ac8ed6642d11250496dd7f Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 10 Oct 2019 18:37:18 -0400 Subject: [PATCH 14/18] [GCLowering] fix lost use (#33509) * [GCLowering] bugfix: remove UpExposedUsesUnrooted It was not considering union-representation values in NoteOperandUses. * [GCLowering] test: use CHECK-DAG instead of CHECK-next Seems to be platform-specific (and the order is irrelevant anyways). --- src/llvm-late-gc-lowering.cpp | 47 +++++++++++-------------------- test/llvmpasses/final-lower-gc.ll | 18 ++++++------ test/llvmpasses/gcroots.ll | 15 ++++++++++ 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/llvm-late-gc-lowering.cpp b/src/llvm-late-gc-lowering.cpp index 03ed17887acab9..09862e12d52e19 100644 --- a/src/llvm-late-gc-lowering.cpp +++ b/src/llvm-late-gc-lowering.cpp @@ -224,12 +224,10 @@ using namespace llvm; */ struct BBState { + // Uses in this BB // These do not get updated after local analysis BitVector Defs; BitVector PhiOuts; - //// Upward exposed uses that do not have a preceding safepoint - BitVector UpExposedUsesUnrooted; - //// All other uses BitVector UpExposedUses; // These get updated during dataflow BitVector LiveIn; @@ -335,10 +333,7 @@ struct LateLowerGCFrame: public FunctionPass, private JuliaPassContext { std::vector NumberVector(State &S, Value *Vec); int NumberBase(State &S, Value *V, Value *Base); std::vector NumberVectorBase(State &S, Value *Base); - void NoteOperandUses(State &S, BBState &BBS, User &UI, BitVector &Uses); - void NoteOperandUses(State &S, BBState &BBS, User &UI){ - NoteOperandUses(S, BBS, UI, BBS.UpExposedUses); - } + void NoteOperandUses(State &S, BBState &BBS, User &UI); State LocalScan(Function &F); void ComputeLiveness(State &S); void ComputeLiveSets(State &S); @@ -401,6 +396,8 @@ static std::pair FindBaseValue(const State &S, Value *V, bool UseCac return std::make_pair(CurrentV, fld_idx); } } + // Note that this is true: + // assert(fld_idx == -1 ? CurrentV->getType()->isPointerTy() : CurrentV->getType()->isVectorPointerTy()); if (isa(CurrentV)) CurrentV = cast(CurrentV)->getOperand(0); else if (isa(CurrentV)) { @@ -721,7 +718,6 @@ static void MaybeResize(BBState &BBS, unsigned Idx) { if (BBS.Defs.size() <= Idx) { BBS.Defs.resize(Idx + 1); BBS.UpExposedUses.resize(Idx + 1); - BBS.UpExposedUsesUnrooted.resize(Idx + 1); BBS.PhiOuts.resize(Idx + 1); } } @@ -736,7 +732,6 @@ static void NoteDef(State &S, BBState &BBS, int Num, const std::vector &Saf assert(BBS.Defs[Num] == 0 && "SSA Violation or misnumbering?"); BBS.Defs[Num] = 1; BBS.UpExposedUses[Num] = 0; - BBS.UpExposedUsesUnrooted[Num] = 0; // This value could potentially be live at any following safe point // if it ends up live out, so add it to the LiveIfLiveOut lists for all // following safepoints. @@ -782,8 +777,6 @@ static int NoteSafepoint(State &S, BBState &BBS, CallInst *CI) { // considered live at this safepoint even when they have a def earlier // in this BB (i.e. even when they don't participate in the dataflow // computation) - BBS.UpExposedUses |= BBS.UpExposedUsesUnrooted; - BBS.UpExposedUsesUnrooted.reset(); S.LiveSets.push_back(BBS.UpExposedUses); S.LiveIfLiveOut.push_back(std::vector{}); return Number; @@ -811,12 +804,13 @@ void LateLowerGCFrame::NoteUse(State &S, BBState &BBS, Value *V, BitVector &Uses } } -void LateLowerGCFrame::NoteOperandUses(State &S, BBState &BBS, User &UI, BitVector &Uses) { +void LateLowerGCFrame::NoteOperandUses(State &S, BBState &BBS, User &UI) { for (Use &U : UI.operands()) { Value *V = U; - if (!isSpecialPtr(V->getType()) && !isSpecialPtrVec(V->getType())) - continue; - NoteUse(S, BBS, V, Uses); + if (isSpecialPtr(V->getType()) || isSpecialPtrVec(V->getType()) || + isUnionRep(V->getType())) { + NoteUse(S, BBS, V); + } } } @@ -858,8 +852,6 @@ JL_USED_FUNC static void dumpLivenessState(Function &F, State &S) { dumpBitVectorValues(S, S.BBStates[&BB].Defs); dbgs() << "\n\tPhiOuts: "; dumpBitVectorValues(S, S.BBStates[&BB].PhiOuts); - dbgs() << "\n\tUpExposedUsesUnrooted: "; - dumpBitVectorValues(S, S.BBStates[&BB].UpExposedUsesUnrooted); dbgs() << "\n\tUpExposedUses: "; dumpBitVectorValues(S, S.BBStates[&BB].UpExposedUses); dbgs() << "\n\tLiveIn: "; @@ -1131,14 +1123,7 @@ State LateLowerGCFrame::LocalScan(Function &F) { else { MaybeNoteDef(S, BBS, CI, BBS.Safepoints); } - NoteOperandUses(S, BBS, I, BBS.UpExposedUses); - for (Use &U : CI->operands()) { - Value *V = U; - if (isUnionRep(V->getType())) { - NoteUse(S, BBS, V); - continue; - } - } + NoteOperandUses(S, BBS, I); if (CI->canReturnTwice()) { S.ReturnsTwice.push_back(CI); } @@ -1202,7 +1187,7 @@ State LateLowerGCFrame::LocalScan(Function &F) { cast(LI->getType())->getAddressSpace() != AddressSpace::Loaded) { MaybeNoteDef(S, BBS, LI, BBS.Safepoints, std::move(RefinedPtr)); } - NoteOperandUses(S, BBS, I, BBS.UpExposedUsesUnrooted); + NoteOperandUses(S, BBS, I); } else if (SelectInst *SI = dyn_cast(&I)) { // We need to insert an extra select for the GC root if (!isSpecialPtr(SI->getType()) && !isSpecialPtrVec(SI->getType()) && @@ -1234,7 +1219,7 @@ State LateLowerGCFrame::LocalScan(Function &F) { }; } MaybeNoteDef(S, BBS, SI, BBS.Safepoints, std::move(RefinedPtr)); - NoteOperandUses(S, BBS, I, BBS.UpExposedUsesUnrooted); + NoteOperandUses(S, BBS, I); } } else if (PHINode *Phi = dyn_cast(&I)) { if (!isSpecialPtr(Phi->getType()) && !isSpecialPtrVec(Phi->getType()) && @@ -1267,8 +1252,10 @@ State LateLowerGCFrame::LocalScan(Function &F) { NoteUse(S, IncomingBBS, Phi->getIncomingValue(i), IncomingBBS.PhiOuts); } } - } else if (isa(&I) || isa(&I)) { - NoteOperandUses(S, BBS, I, BBS.UpExposedUsesUnrooted); + } else if (isa(&I)) { + NoteOperandUses(S, BBS, I); + } else if (isa(&I)) { + NoteOperandUses(S, BBS, I); } else if (auto *ASCI = dyn_cast(&I)) { if (getValueAddrSpace(ASCI) == AddressSpace::Tracked) { SmallVector RefinedPtr{}; @@ -1290,7 +1277,6 @@ State LateLowerGCFrame::LocalScan(Function &F) { } // Pre-seed the dataflow variables; BBS.LiveIn = BBS.UpExposedUses; - BBS.LiveIn |= BBS.UpExposedUsesUnrooted; BBS.Done = true; } FixUpRefinements(PHINumbers, S); @@ -1331,7 +1317,6 @@ void LateLowerGCFrame::ComputeLiveness(State &S) { FlippedDefs.flip(); NewLiveIn &= FlippedDefs; NewLiveIn |= BBS.UpExposedUses; - NewLiveIn |= BBS.UpExposedUsesUnrooted; if (NewLiveIn != BBS.LiveIn) { AnyChanged = true; BBS.LiveIn = NewLiveIn; diff --git a/test/llvmpasses/final-lower-gc.ll b/test/llvmpasses/final-lower-gc.ll index b1776aea8d7016..f4d126471ebe04 100644 --- a/test/llvmpasses/final-lower-gc.ll +++ b/test/llvmpasses/final-lower-gc.ll @@ -24,15 +24,15 @@ top: %gcframe = call %jl_value_t addrspace(10)** @julia.new_gc_frame(i32 2) ; CHECK: %ptls = call %jl_value_t*** @julia.ptls_states() %ptls = call %jl_value_t*** @julia.ptls_states() -; CHECK-NEXT: [[GCFRAME_SIZE_PTR:%.*]] = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 0 -; CHECK-NEXT: [[GCFRAME_SIZE_PTR2:%.*]] = bitcast %jl_value_t addrspace(10)** [[GCFRAME_SIZE_PTR]] to i64* -; CHECK-NEXT: store i64 4, i64* [[GCFRAME_SIZE_PTR2]], !tbaa !0 -; CHECK-NEXT: [[GCFRAME_SLOT:%.*]] = getelementptr %jl_value_t**, %jl_value_t*** %ptls, i32 0 -; CHECK-NEXT: [[PREV_GCFRAME_PTR:%.*]] = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 1 -; CHECK-NEXT: [[PREV_GCFRAME_PTR2:%.*]] = bitcast %jl_value_t addrspace(10)** [[PREV_GCFRAME_PTR]] to %jl_value_t*** -; CHECK-NEXT: [[PREV_GCFRAME:%.*]] = load %jl_value_t**, %jl_value_t*** [[GCFRAME_SLOT]] -; CHECK-NEXT: store %jl_value_t** [[PREV_GCFRAME]], %jl_value_t*** [[PREV_GCFRAME_PTR2]], !tbaa !0 -; CHECK-NEXT: [[GCFRAME_SLOT2:%.*]] = bitcast %jl_value_t*** [[GCFRAME_SLOT]] to %jl_value_t addrspace(10)*** +; CHECK-DAG: [[GCFRAME_SIZE_PTR:%.*]] = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 0 +; CHECK-DAG: [[GCFRAME_SIZE_PTR2:%.*]] = bitcast %jl_value_t addrspace(10)** [[GCFRAME_SIZE_PTR]] to i64* +; CHECK-DAG: store i64 4, i64* [[GCFRAME_SIZE_PTR2]], !tbaa !0 +; CHECK-DAG: [[GCFRAME_SLOT:%.*]] = getelementptr %jl_value_t**, %jl_value_t*** %ptls, i32 0 +; CHECK-DAG: [[PREV_GCFRAME_PTR:%.*]] = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 1 +; CHECK-DAG: [[PREV_GCFRAME_PTR2:%.*]] = bitcast %jl_value_t addrspace(10)** [[PREV_GCFRAME_PTR]] to %jl_value_t*** +; CHECK-DAG: [[PREV_GCFRAME:%.*]] = load %jl_value_t**, %jl_value_t*** [[GCFRAME_SLOT]] +; CHECK-DAG: store %jl_value_t** [[PREV_GCFRAME]], %jl_value_t*** [[PREV_GCFRAME_PTR2]], !tbaa !0 +; CHECK-DAG: [[GCFRAME_SLOT2:%.*]] = bitcast %jl_value_t*** [[GCFRAME_SLOT]] to %jl_value_t addrspace(10)*** ; CHECK-NEXT: store %jl_value_t addrspace(10)** %gcframe, %jl_value_t addrspace(10)*** [[GCFRAME_SLOT2]] call void @julia.push_gc_frame(%jl_value_t addrspace(10)** %gcframe, i32 2) %aboxed = call %jl_value_t addrspace(10)* @jl_box_int64(i64 signext %a) diff --git a/test/llvmpasses/gcroots.ll b/test/llvmpasses/gcroots.ll index 9b048d51433d6d..61062936165b0b 100644 --- a/test/llvmpasses/gcroots.ll +++ b/test/llvmpasses/gcroots.ll @@ -182,6 +182,21 @@ define %jl_value_t addrspace(10)* @ret_use(i64 %a, i64 %b) { ret %jl_value_t addrspace(10)* %aboxed } +define {%jl_value_t addrspace(10)*, i8} @ret_use_struct() { +; CHECK-LABEL: @ret_use_struct +; CHECK: %gcframe = alloca %jl_value_t addrspace(10)*, i32 3 + %ptls = call %jl_value_t*** @julia.ptls_states() +; CHECK: %aunion = call { %jl_value_t addrspace(10)*, i8 } @union_ret() + %aunion = call { %jl_value_t addrspace(10)*, i8 } @union_ret() +; CHECK-DAG: [[GEP0:%.*]] = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 [[GEPSLOT0:[0-9]+]] +; CHECK-DAG: [[EXTRACT:%.*]] = extractvalue { %jl_value_t addrspace(10)*, i8 } %aunion, 0 +; CHECK-NEXT: store %jl_value_t addrspace(10)* [[EXTRACT]], %jl_value_t addrspace(10)** [[GEP0]] +; CHECK-NEXT: call void @jl_safepoint() + call void @jl_safepoint() + ret {%jl_value_t addrspace(10)*, i8} %aunion +} + + define i8 @nosafepoint(%jl_value_t addrspace(10)* dereferenceable(16)) { ; CHECK-LABEL: @nosafepoint ; CHECK-NOT: %gcframe From e0f904504f4fcd9388ec5da50380d9a28d9086a3 Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Thu, 10 Oct 2019 23:12:56 -0700 Subject: [PATCH 15/18] Move `7z` installation out of non-Windows if statement (#33505) --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index dea4934127b6da..8e234ebdf03b69 100644 --- a/Makefile +++ b/Makefile @@ -298,9 +298,6 @@ endif -$(INSTALL_M) $(build_bindir)/libopenlibm.dll.a $(DESTDIR)$(libdir)/ else - # Install `7z` into libexec/ - $(INSTALL_M) $(build_bindir)/7z$(EXE) $(DESTDIR)$(libexecdir)/ - # Copy over .dSYM directories directly for Darwin ifneq ($(DARWIN_FRAMEWORK),1) ifeq ($(OS),Darwin) @@ -330,6 +327,7 @@ ifeq ($(BUNDLE_DEBUG_LIBS),1) @$(DSYMUTIL) -o $(DESTDIR)$(prefix)/$(framework_resources)/sys-debug.dylib.dSYM $(build_private_libdir)/sys-debug.dylib endif endif + for suffix in $(JL_PRIVATE_LIBS-0) ; do \ for lib in $(build_libdir)/$${suffix}.*$(SHLIB_EXT)*; do \ if [ "$${lib##*.}" != "dSYM" ]; then \ @@ -342,6 +340,8 @@ endif $(INSTALL_M) $$lib $(DESTDIR)$(private_libdir) ; \ done endif + # Install `7z` into libexec/ + $(INSTALL_M) $(build_bindir)/7z$(EXE) $(DESTDIR)$(libexecdir)/ # Copy public headers cp -R -L $(build_includedir)/julia/* $(DESTDIR)$(includedir)/julia From 2b1fc4c71e248524e7a129de9392c1bf5decc93d Mon Sep 17 00:00:00 2001 From: Nathan Daly Date: Fri, 11 Oct 2019 11:45:40 -0400 Subject: [PATCH 16/18] Have `copy(ci::CodeInfo)` also copy `ci.edges`. (#33523) Before this commit, it was a shallow-copy, so the array was shared between the orginal ci and the copy. --- base/expr.jl | 5 +++-- test/copy.jl | 10 ++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/base/expr.jl b/base/expr.jl index 7e57c2282b5496..8fe1753e1b34b6 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -67,8 +67,9 @@ function copy(c::CodeInfo) cnew.slotflags = copy(cnew.slotflags) cnew.codelocs = copy(cnew.codelocs) cnew.linetable = copy(cnew.linetable) - cnew.ssaflags = copy(cnew.ssaflags) - ssavaluetypes = cnew.ssavaluetypes + cnew.ssaflags = copy(cnew.ssaflags) + cnew.edges = cnew.edges === nothing ? nothing : copy(cnew.edges) + ssavaluetypes = cnew.ssavaluetypes ssavaluetypes isa Vector{Any} && (cnew.ssavaluetypes = copy(ssavaluetypes)) return cnew end diff --git a/test/copy.jl b/test/copy.jl index 2d70726a3287c2..532708e8b303de 100644 --- a/test/copy.jl +++ b/test/copy.jl @@ -193,3 +193,13 @@ end let x = Bar17149() @test deepcopy(x) !== x end + +@testset "copying CodeInfo" begin + _testfunc() = nothing + ci,_ = code_typed(_testfunc, ())[1] + ci.edges = [_testfunc] + + ci2 = copy(ci) + # Test that edges are not shared + @test ci2.edges !== ci.edges +end From cf5957eba7199a4eb5633a76828e6bae1f240b34 Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Fri, 11 Oct 2019 13:38:36 -0400 Subject: [PATCH 17/18] fix #32678, error due to Ptr constant in `_reformat_bt` (#33524) Also change `jl_get_backtrace` to return a pair of values instead of mutating. --- base/error.jl | 12 +++++------- src/julia_internal.h | 2 +- src/stackwalk.c | 21 +++++++++++---------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/base/error.jl b/base/error.jl index c0a37ed66f8996..0dc356723ab5ee 100644 --- a/base/error.jl +++ b/base/error.jl @@ -70,7 +70,7 @@ function _reformat_bt(bt, bt2) i, j = 1, 1 while i <= length(bt) ip = bt[i]::Ptr{Cvoid} - if ip == Ptr{Cvoid}(-1%UInt) + if UInt(ip) == (-1 % UInt) # The next one is really a CodeInfo push!(ret, InterpreterIP( bt2[j], @@ -95,8 +95,8 @@ function backtrace() # skip frame for backtrace(). Note that for this to work properly, # backtrace() itself must not be interpreted nor inlined. skip = 1 - bt1, bt2 = ccall(:jl_backtrace_from_here, Any, (Cint,Cint), false, skip) - _reformat_bt(bt1, bt2) + bt1, bt2 = ccall(:jl_backtrace_from_here, Ref{SimpleVector}, (Cint, Cint), false, skip) + return _reformat_bt(bt1::Vector{Ptr{Cvoid}}, bt2::Vector{Any}) end """ @@ -105,10 +105,8 @@ end Get the backtrace of the current exception, for use within `catch` blocks. """ function catch_backtrace() - bt = Ref{Any}(nothing) - bt2 = Ref{Any}(nothing) - ccall(:jl_get_backtrace, Cvoid, (Ref{Any}, Ref{Any}), bt, bt2) - return _reformat_bt(bt[], bt2[]) + bt, bt2 = ccall(:jl_get_backtrace, Ref{SimpleVector}, ()) + return _reformat_bt(bt::Vector{Ptr{Cvoid}}, bt2::Vector{Any}) end """ diff --git a/src/julia_internal.h b/src/julia_internal.h index d408257150e00e..de2b060419d98b 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -646,7 +646,7 @@ size_t rec_backtrace_ctx(uintptr_t *bt_data, size_t maxsize, bt_context_t *ctx, #ifdef LIBOSXUNWIND size_t rec_backtrace_ctx_dwarf(uintptr_t *bt_data, size_t maxsize, bt_context_t *ctx, int add_interp_frames) JL_NOTSAFEPOINT; #endif -JL_DLLEXPORT void jl_get_backtrace(jl_array_t **bt, jl_array_t **bt2); +JL_DLLEXPORT jl_value_t *jl_get_backtrace(void); void jl_critical_error(int sig, bt_context_t *context, uintptr_t *bt_data, size_t *bt_size); JL_DLLEXPORT void jl_raise_debugger(void); int jl_getFunctionInfo(jl_frame_t **frames, uintptr_t pointer, int skipC, int noInline) JL_NOTSAFEPOINT; diff --git a/src/stackwalk.c b/src/stackwalk.c index b403a5e46e1a82..d4615a19b2c543 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -240,18 +240,17 @@ JL_DLLEXPORT jl_value_t *jl_backtrace_from_here(int returnsp, int skip) return bt; } +// note: btout and bt2out must be GC roots void decode_backtrace(uintptr_t *bt_data, size_t bt_size, jl_array_t **btout, jl_array_t **bt2out) { - jl_array_t *bt = NULL; - jl_array_t *bt2 = NULL; - JL_GC_PUSH2(&bt, &bt2); + jl_array_t *bt, *bt2; if (array_ptr_void_type == NULL) { array_ptr_void_type = jl_apply_type2((jl_value_t*)jl_array_type, (jl_value_t*)jl_voidpointer_type, jl_box_long(1)); } - bt = jl_alloc_array_1d(array_ptr_void_type, bt_size); + bt = *btout = jl_alloc_array_1d(array_ptr_void_type, bt_size); memcpy(bt->data, bt_data, bt_size * sizeof(void*)); - bt2 = jl_alloc_array_1d(jl_array_any_type, 0); + bt2 = *bt2out = jl_alloc_array_1d(jl_array_any_type, 0); // Scan the stack for any interpreter frames size_t n = 0; while (n < bt_size) { @@ -261,12 +260,9 @@ void decode_backtrace(uintptr_t *bt_data, size_t bt_size, } n++; } - *btout = bt; - *bt2out = bt2; - JL_GC_POP(); } -JL_DLLEXPORT void jl_get_backtrace(jl_array_t **btout, jl_array_t **bt2out) +JL_DLLEXPORT jl_value_t *jl_get_backtrace(void) { jl_excstack_t *s = jl_get_ptls_states()->current_task->excstack; uintptr_t *bt_data = NULL; @@ -275,7 +271,12 @@ JL_DLLEXPORT void jl_get_backtrace(jl_array_t **btout, jl_array_t **bt2out) bt_data = jl_excstack_bt_data(s, s->top); bt_size = jl_excstack_bt_size(s, s->top); } - decode_backtrace(bt_data, bt_size, btout, bt2out); + jl_array_t *bt = NULL, *bt2 = NULL; + JL_GC_PUSH2(&bt, &bt2); + decode_backtrace(bt_data, bt_size, &bt, &bt2); + jl_svec_t *pair = jl_svec2(bt, bt2); + JL_GC_POP(); + return (jl_value_t*)pair; } // Return data from the exception stack for `task` as an array of Any, starting From f0ab5bbd9a2ac1144e7c2f077870c40dded51962 Mon Sep 17 00:00:00 2001 From: Mustafa M Date: Fri, 11 Oct 2019 17:30:58 -0400 Subject: [PATCH 18/18] Add winsomely that converts args to a command line (#33465) Converts the collection of strings 'args' into a Windows command line. Co-authored-by: Mustafa M. Co-authored-by: Markus Kuhn Co-authored-by: Jameson Nash --- base/cmd.jl | 2 ++ base/shell.jl | 59 +++++++++++++++++++++++++++++++++++ test/spawn.jl | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+) diff --git a/base/cmd.jl b/base/cmd.jl index 3da3313d17776a..4890af1c4c7cdb 100644 --- a/base/cmd.jl +++ b/base/cmd.jl @@ -102,6 +102,8 @@ shell_escape(cmd::Cmd; special::AbstractString="") = shell_escape(cmd.exec..., special=special) shell_escape_posixly(cmd::Cmd) = shell_escape_posixly(cmd.exec...) +shell_escape_winsomely(cmd::Cmd) = + shell_escape_winsomely(cmd.exec...) function show(io::IO, cmd::Cmd) print_env = cmd.env !== nothing diff --git a/base/shell.jl b/base/shell.jl index 099aefbb26ee05..85243a3e405426 100644 --- a/base/shell.jl +++ b/base/shell.jl @@ -253,3 +253,62 @@ julia> Base.shell_escape_posixly("echo", "this", "&&", "that") """ shell_escape_posixly(args::AbstractString...) = sprint(print_shell_escaped_posixly, args...) + + +function print_shell_escaped_winsomely(io::IO, args::AbstractString...) + first = true + for arg in args + first || write(io, ' ') + first = false + # Quote any arg that contains a whitespace (' ' or '\t') or a double quote mark '"'. + # It's also valid to quote an arg with just a whitespace, + # but the following may be 'safer', and both implementations are valid anyways. + quotes = any(c -> c in (' ', '\t', '"'), arg) || isempty(arg) + quotes && write(io, '"') + backslashes = 0 + for c in arg + if c == '\\' + backslashes += 1 + else + # escape all backslashes and the following double quote + c == '"' && (backslashes = backslashes * 2 + 1) + for j = 1:backslashes + # backslashes aren't special here + write(io, '\\') + end + backslashes = 0 + write(io, c) + end + end + # escape all backslashes, letting the terminating double quote we add below to then be interpreted as a special char + quotes && (backslashes *= 2) + for j = 1:backslashes + write(io, '\\') + end + quotes && write(io, '"') + end + return nothing +end + + +""" + shell_escaped_winsomely(args::Union{Cmd,AbstractString...})::String + +Convert the collection of strings `args` into single string suitable for passing as the argument +string for a Windows command line. Windows passes the entire command line as a single string to +the application (unlike POSIX systems, where the list of arguments are passed separately). +Many Windows API applications (including julia.exe), use the conventions of the [Microsoft C +runtime](https://docs.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments) to +split that command line into a list of strings. This function implements the inverse of such a +C runtime command-line parser. It joins command-line arguments to be passed to a Windows console +application into a command line, escaping or quoting meta characters such as space, +double quotes and backslash where needed. This may be useful in concert with the `windows_verbatim` +flag to [`Cmd`](@ref) when constructing process pipelines. + +# Example +```jldoctest +julia> println(shell_escaped_winsomely("A B\\", "C")) +"A B\\" C +""" +shell_escape_winsomely(args::AbstractString...) = + sprint(print_shell_escaped_winsomely, args..., sizehint=(sum(length, args)) + 3*length(args)) diff --git a/test/spawn.jl b/test/spawn.jl index d62179724097f7..6a6c63e071f32b 100644 --- a/test/spawn.jl +++ b/test/spawn.jl @@ -675,3 +675,88 @@ end if Sys.iswindows() rm(busybox, force=true) end + + +# shell escaping on Windows +@testset "shell_escape_winsomely" begin + # Note argument A can be parsed both as A or "A". + # We do not test that the parsing satisfies either of these conditions. + # In other words, tests may fail even for valid parsing. + # This is done to avoid overly verbose tests. + + # input : + # output: "" + @test Base.shell_escape_winsomely("") == "\"\"" + + @test Base.shell_escape_winsomely("A") == "A" + + @test Base.shell_escape_winsomely(`A`) == "A" + + # input : hello world + # output: "hello world" + @test Base.shell_escape_winsomely("hello world") == "\"hello world\"" + + # input : hello world + # output: "hello world" + @test Base.shell_escape_winsomely("hello\tworld") == "\"hello\tworld\"" + + # input : hello"world + # output: "hello\"world" (also valid) hello\"world + @test Base.shell_escape_winsomely("hello\"world") == "\"hello\\\"world\"" + + # input : hello""world + # output: "hello\"\"world" (also valid) hello\"\"world + @test Base.shell_escape_winsomely("hello\"\"world") == "\"hello\\\"\\\"world\"" + + # input : hello\world + # output: hello\world + @test Base.shell_escape_winsomely("hello\\world") == "hello\\world" + + # input : hello\\world + # output: hello\\world + @test Base.shell_escape_winsomely("hello\\\\world") == "hello\\\\world" + + # input : hello\"world + # output: "hello\"world" (also valid) hello\"world + @test Base.shell_escape_winsomely("hello\\\"world") == "\"hello\\\\\\\"world\"" + + # input : hello\\"world + # output: "hello\\\\\"world" (also valid) hello\\\\\"world + @test Base.shell_escape_winsomely("hello\\\\\"world") == "\"hello\\\\\\\\\\\"world\"" + + # input : hello world\ + # output: "hello world\\" + @test Base.shell_escape_winsomely("hello world\\") == "\"hello world\\\\\"" + + # input : A\B + # output: A\B" + @test Base.shell_escape_winsomely("A\\B") == "A\\B" + + # input : [A\, B] + # output: "A\ B" + @test Base.shell_escape_winsomely("A\\", "B") == "A\\ B" + + # input : A"B + # output: "A\"B" + @test Base.shell_escape_winsomely("A\"B") == "\"A\\\"B\"" + + # input : [A B\, C] + # output: "A B\\" C + @test Base.shell_escape_winsomely("A B\\", "C") == "\"A B\\\\\" C" + + # input : [A "B, C] + # output: "A \"B" C + @test Base.shell_escape_winsomely("A \"B", "C") == "\"A \\\"B\" C" + + # input : [A B\, C] + # output: "A B\\" C + @test Base.shell_escape_winsomely("A B\\", "C") == "\"A B\\\\\" C" + + # input :[A\ B\, C] + # output: "A\ B\\" C + @test Base.shell_escape_winsomely("A\\ B\\", "C") == "\"A\\ B\\\\\" C" + + # input : [A\ B\, C, D K] + # output: "A\ B\\" C "D K" + @test Base.shell_escape_winsomely("A\\ B\\", "C", "D K") == "\"A\\ B\\\\\" C \"D K\"" +end