diff --git a/base/broadcast.jl b/base/broadcast.jl index 2ec5d451512bd..a0f2c969e7c7a 100644 --- a/base/broadcast.jl +++ b/base/broadcast.jl @@ -609,4 +609,229 @@ macro __dot__(x) esc(__dot__(x)) end +############################################################ +## The parser turns dotted calls into the equivalent Fusion expression. +## Effectively, this turns the Expr tree into a runtime AST, +## for a limited subset of expression types. +# +## For example, in the expression: +# d = sin.((a .+ (b .* c))...) +## The kernel becomes +# d' = Fusion{3}( +# FusionApply( +# sin, +# ( FusionCall( +# +, +# ( FusionArg{1}(), +# FusionCall( +# *, +# ( FusionArg{2}(), +# FusionArg{3}() )), )), )), +# (:a, :b, :c)) +## and then the final expansion becomes: +# d = broadcast(d', a, b, c) + +struct Fusion{N, vararg#=::Bool=#, T} + f::T + # Debugging Metadata: + # names::NTuple{N, Symbol} + # source::LineNumberNode + function Fusion{N, vararg}(f) where {N, vararg} + return new{N, vararg::Bool, typeof(f)}(f) + end +end + +struct FusionArg{N} +end + +struct FusionConstant{T} + c::T + function FusionConstant(c) where {} + return new{typeof(c)}(c) + end +end + +struct FusionCall{F, Args<:Tuple} + f::F + args::Args + function FusionCall(f, args::Tuple) where {} + return new{typeof(f), typeof(args)}(f, args) + end +end + +struct FusionApply{N, F, Args<:NTuple{N, Any}} + f::F + args::Args + function FusionApply(f, args::NTuple{N, Any}) where {N} + return new{N, typeof(f), typeof(args)}(f, args) + end +end + +function kw_to_vec(kws::Vector{Any}) + kwargs = Vector{Any}(2 * length(kws)) + for i in 1:2:length(kws) + kw = kws[i]::Tuple{Any, Any} + kwargs[i] = getfield(kw, 1) + kwargs[i + 1] = getfield(kw, 2) + end + return kwargs +end + +struct FusionKWCall{F, Args<:Tuple} + f::F + args::Args + kwargs::Vector{Any} + function FusionKWCall(f, args::Tuple; kwargs...) where {} + return new{typeof(f), typeof(args)}(f, args, kw_to_vec(kwargs)) + end +end + +struct FusionKWApply{F, Args<:Tuple} + f::F + args::Args + kwargs::Vector{Any} + function FusionKWApply(f, args::Tuple; kwargs...) where {} + return new{typeof(f), typeof(args)}(f, args, kw_to_vec(kwargs)) + end +end + +function tuplehead(t::Tuple, N::Val) + return ntuple(i -> t[i], N) +end +@generated function tupletail(t::NTuple{M, Any}, ::Val{N}) where {N, M} + # alternative, non-generated versions, + # enable when inference is improved: + #tupletail(t, Nreq) = ntuple(i -> t[i + Nreq], length(t) - Nreq) + #tupletail(t, Nreq) = t[(Nreq + 1):end] + args = Any[ :(getfield(t, $i)) for i in (N + 1):M ] + tpl = Expr(:tuple) + tpl.args = args + return tpl +end + +@inline (f::Fusion{N, false})(args::Vararg{Any, N}) where {N} = f.f(args...) +function (f::Fusion{Nreq, true})(args::Vararg{Any, M}) where {Nreq, M} + M >= Nreq || throw(MethodError(f, args)) + fargs = tuplehead(args, Val(Nreq)) + vararg = tupletail(args, Val(Nreq)) + return f.f((fargs..., vararg)...) +end +@inline (f::FusionArg{N})(args...) where {N} = args[N] +@inline (f::FusionConstant)(args...) = f.c +@inline (f::FusionCall)(args...) = f.f(map(a -> a(args...), f.args)...) +# TODO: calling _apply on map _apply is not handled by inference +# for now, we unroll some cases and generate others, to help it out +#@inline (f::FusionApply)(args...) = Core._apply(f.f, map(a -> a(args...), f.args)...) +@inline (f::FusionApply{0})(args...) = f.f() +@inline (f::FusionApply{1})(args...) = f.f(f.args[1](args...)...) +@inline (f::FusionApply{2})(args...) = f.f(f.args[1](args...)..., f.args[2](args...)...) +@inline (f::FusionApply{3})(args...) = f.f(f.args[1](args...)..., f.args[2](args...)..., f.args[3](args...)...) +@generated function (f::FusionApply{N})(args...) where {N} + fargs = Any[ :(getfield(f.args, $i)(args...)) for i in 1:N ] + return Expr(:call, GlobalRef(Core, :_apply), :(f.f), fargs...) +end +@inline function (f::FusionKWCall)(args...) + fargs = map(a -> a(args...), f.args) + # return f.f(args...; kwargs...) + if isempty(f.kwargs) + return f.f(fargs...) + else + return Core.kwfunc(f.f)(f.kwargs, f.f, fargs...) + end +end +@inline function (f::FusionKWApply)(args...) + fargs = map(a -> a(args...), f.args) + # return Core._apply(f.f, args...; kwargs...) + if isempty(f.kwargs) + return Core._apply(f.f, fargs...) + else + return Core._apply(Core.kwfunc(f.f), (f.kwargs,), (f.f,), fargs...) + end +end + +function Base.show(io::IO, f::Fusion{N, vararg}) where {N, vararg} + nargs = (vararg ? N + 1 : N) + names = String[ "a_$i" for i in 1:nargs ] # f.names + print(io, "(") + join(io, names, ", ") + vararg && print(io, "...") + print(io, ") -> ") + show_fusion(io, f.f, names) +end + +function show_fusion(io::IO, f::FusionArg{N}, names) where N + print(io, names[N]) + nothing +end + +function show_fusion(io::IO, f::FusionConstant{N}, names) where N + print(io, f.c) + nothing +end + +function show_fusion(io::IO, f::FusionCall, names) + Base.show(io, f.f) + print(io, '(') + first = true + for i in f.args + first || print(io, ", ") + first = false + show_fusion(io, i, names) + end + print(io, ')') + nothing +end + +function show_fusion(io::IO, f::FusionApply, names) + print(io, "Core._apply(") + Base.show(io, f.f) + for i in f.args + print(io, ", ") + show_fusion(io, i, names) + end + print(io, ')') + nothing +end + +function show_fusion(io::IO, f::FusionKWCall, names) + Base.show(io, f.f) + print(io, '(') + first = true + for i in f.args + first || print(io, ", ") + first = false + show_fusion(io, i, names) + end + print(io, "; ") + first = true + for i in 1:2:length(f.kwargs) + first || print(io, ", ") + first = false + print(io, f.kwargs[i]) + print(io, "=") + end + print(io, ')') + nothing +end + + +function show_fusion(io::IO, f::FusionKWApply, names) + print(io, "Core._apply(") + Base.show(io, f.f) + for i in f.args + print(io, ", ") + show_fusion(io, i, names) + end + print(io, "; #=kwargs=#...)") + nothing +end + + +function show_fusion(io::IO, @nospecialize(f), names) + print(io, "#= unexpected expression ") + show(io, f) + print(io, " =#") + nothing +end + end # module diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index b78e091082434..ce5730a4f0393 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -1077,9 +1077,9 @@ (string "parametric method syntax " (deparse (cadr e)) (linenode-string (function-body-lineno body))) (deparse `(where (call ,name ,@(if (has-parameters? argl) - (cons (car argl) (cddr argl)) - (cdr argl))) - ,@raw-typevars)))) + (cons (car argl) (cddr argl)) + (cdr argl))) + ,@raw-typevars)))) (expand-forms (method-def-expr name sparams argl body isstaged rett)))) (else @@ -1776,129 +1776,100 @@ ; fuse nested calls to expr == f.(args...) into a single broadcast call, ; or a broadcast! call if lhs is non-null. (define (expand-fuse-broadcast lhs rhs) - (define (fuse? e) (and (pair? e) (eq? (car e) 'fuse))) - (define (anyfuse? exprs) - (if (null? exprs) #f (if (fuse? (car exprs)) #t (anyfuse? (cdr exprs))))) - (define (to-lambda f args kwargs) ; convert f to anonymous function with hygienic tuple args - (define (genarg arg) (if (vararg? arg) (list '... (gensy)) (gensy))) - ; (To do: optimize the case where f is already an anonymous function, in which - ; case we only need to hygienicize the arguments? But it is quite tricky - ; to fully handle splatted args, typed args, keywords, etcetera. And probably - ; the extra function call is harmless because it will get inlined anyway.) - (let ((genargs (map genarg args))) ; hygienic formal parameters - (if (null? kwargs) - `(-> ,(cons 'tuple genargs) (call ,f ,@genargs)) ; no keyword args - `(-> ,(cons 'tuple genargs) (call ,f (parameters ,@kwargs) ,@genargs))))) - (define (from-lambda f) ; convert (-> (tuple args...) (call func args...)) back to func - (if (and (pair? f) (eq? (car f) '->) (pair? (cadr f)) (eq? (caadr f) 'tuple) - (pair? (caddr f)) (eq? (caaddr f) 'call) (equal? (cdadr f) (cdr (cdaddr f)))) - (car (cdaddr f)) - f)) - (define (fuse-args oldargs) ; replace (fuse f args) with args in oldargs list - (define (fargs newargs oldargs) - (if (null? oldargs) - newargs - (fargs (if (fuse? (car oldargs)) - (append (reverse (caddar oldargs)) newargs) - (cons (car oldargs) newargs)) - (cdr oldargs)))) - (reverse (fargs '() oldargs))) - (define (fuse-funcs f args) ; for (fuse g a) in args, merge/inline g into f - ; any argument A of f that is (fuse g a) gets replaced by let A=(body of g): - (define (fuse-lets fargs args lets) - (if (null? args) - lets - (if (fuse? (car args)) - (fuse-lets (cdr fargs) (cdr args) (cons (list '= (car fargs) (caddr (cadar args))) lets)) - (fuse-lets (cdr fargs) (cdr args) lets)))) - (let ((fargs (cdadr f)) - (fbody (caddr f))) - `(-> - (tuple ,@(fuse-args (map (lambda (oldarg arg) (if (fuse? arg) - `(fuse _ ,(cdadr (cadr arg))) - oldarg)) - fargs args))) - (let (block ,@(reverse (fuse-lets fargs args '()))) ,fbody)))) - (define (dot-to-fuse e) ; convert e == (. f (tuple args)) to (fuse f args) - (define (make-fuse f args) ; check for nested (fuse f args) exprs and combine - (define (split-kwargs args) ; return (cons keyword-args positional-args) extracted from args - (define (sk args kwargs pargs) - (if (null? args) - (cons kwargs pargs) - (if (kwarg? (car args)) - (sk (cdr args) (cons (car args) kwargs) pargs) - (sk (cdr args) kwargs (cons (car args) pargs))))) - (if (has-parameters? args) - (sk (reverse (cdr args)) (cdar args) '()) - (sk (reverse args) '() '()))) - (let* ((kws.args (split-kwargs args)) - (kws (car kws.args)) - (args (cdr kws.args)) ; fusing occurs on positional args only - (args_ (map dot-to-fuse args))) - (if (anyfuse? args_) - `(fuse ,(fuse-funcs (to-lambda f args kws) args_) ,(fuse-args args_)) - `(fuse ,(to-lambda f args kws) ,args_)))) + (define nparams 0) + (define arglist '()) + (define vararg #f) + (define (dot-to-fuse e) ; convert e == (. f (tuple args)) to Fusion (if (and (pair? e) (eq? (car e) '|.|)) - (let ((f (cadr e)) (x (caddr e))) + (let ((f (cadr e)) + (x (caddr e))) (cond ((or (eq? (car x) 'quote) (eq? (car x) 'inert) (eq? (car x) '$)) `(call (core getfield) ,f ,x)) ((eq? (car x) 'tuple) - (make-fuse f (cdr x))) + (make-fusion f (cdr x))) (else (error (string "invalid syntax " (deparse e)))))) (if (and (pair? e) (eq? (car e) 'call) (dotop? (cadr e))) - (make-fuse (undotop (cadr e)) (cddr e)) + (make-fusion (undotop (cadr e)) (cddr e)) e))) - ; given e == (fuse lambda args), compress the argument list by removing (pure) - ; duplicates in args, inlining literals, and moving any varargs to the end: - (define (compress-fuse e) - (define (findfarg arg args fargs) ; for arg in args, return corresponding farg - (if (eq? arg (car args)) - (car fargs) - (findfarg arg (cdr args) (cdr fargs)))) - (if (fuse? e) + (define (make-fusion f args) + (define fuse (make-fuse-call f args)) + (if vararg (begin + (define argn (+ nparams 1)) + (set! arglist (cons (cons `(... ,vararg) argn) arglist)) + (set! fuse (insert-vararg fuse argn)))) + (define fusion_type `(curly (|.| (top Broadcast) 'Fusion) ,nparams ,(if vararg 'true 'false))) + (define fusion `(call ,fusion_type ,fuse)) + `(fuse ,fusion ,(reverse! (map car arglist)))) + (define (insert-vararg form argn) + (cond ((vararg? form) + `(call (curly (|.| (top Broadcast) 'FusionArg) ,argn))) + ((and (pair? form) (contains vararg? (cdr form))) + (cons (car form) (map (lambda (x) (insert-vararg x argn)) (cdr form)))) + (else + form))) + (define (make-fuse-arg e) + (cond ((vararg? e) ; TODO: this is sloppy at order-of-execution handling + (if (and vararg (not (eq? vararg (cadr e)))) (error "multiple splatted args cannot be fused into a single broadcast")) + (set! vararg (cadr e)) + `(...)) ; return a placeholder token + ((julia-scalar? e) + `(call (|.| (top Broadcast) 'FusionConstant) ,e)) + (else + (let ((argn (memq e arglist))) + (if (not argn) (begin + (set! nparams (+ nparams 1)) + (set! argn nparams) + (set! arglist (cons (cons e argn) arglist)))) + `(call (curly (|.| (top Broadcast) 'FusionArg) ,argn)))))) + (define (make-fuse-call f args) + (define (split-kwargs args) ; return (cons keyword-args positional-args) extracted from args + (define (sk args kwargs pargs) + (if (null? args) + (cons kwargs pargs) + (if (kwarg? (car args)) + (sk (cdr args) (cons (car args) kwargs) pargs) + (sk (cdr args) kwargs (cons (car args) pargs))))) + (if (has-parameters? args) + (sk (reverse (cdr args)) (cdar args) '()) + (sk (reverse args) '() '()))) + (let* ((kws+args (split-kwargs args)) + (kws (car kws+args)) + (args (cdr kws+args)) + (args (map fuse-arg args)) ; fusing occurs on positional args only + (apply-call (any vararg? args)) + (call-head (if apply-call + (if (null? kws) 'FusionApply 'FusionKWApply) + (if (null? kws) 'FusionCall 'FusionKWCall))) + (args (if apply-call ; if calling apply, need to wrap each non-vararg args in a 1-tuple + (map (lambda (x) (if (vararg? x) + x + `(call (|.| (top Broadcast) 'FusionCall) (core tuple) (call (core tuple) ,x)))) + args) + args)) + (kws (if (null? kws) kws (list (cons 'parameters kws))))) + `(call (|.| (top Broadcast) ',call-head) ,@kws ,f (call (core tuple) ,@args)))) + (define (fuse-arg e) ; convert e into an argument for Fusion + ; examine syntax for `(. (tuple f) ...)` or `(call .op ...)` + ; or just `(. m 'field) and call appropriate lowering + (if (and (pair? e) (eq? (car e) '|.|)) (let ((f (cadr e)) - (args (caddr e))) - (define (cf old-fargs old-args new-fargs new-args renames varfarg vararg) - (if (null? old-args) - (let ((nfargs (if (null? varfarg) new-fargs (cons varfarg new-fargs))) - (nargs (if (null? vararg) new-args (cons vararg new-args)))) - `(fuse (-> (tuple ,@(reverse nfargs)) ,(replace-vars (caddr f) renames)) - ,(reverse nargs))) - (let ((farg (car old-fargs)) (arg (car old-args))) - (cond - ((and (vararg? farg) (vararg? arg)) ; arg... must be the last argument - (if (null? varfarg) - (cf (cdr old-fargs) (cdr old-args) - new-fargs new-args renames farg arg) - (if (eq? (cadr vararg) (cadr arg)) - (cf (cdr old-fargs) (cdr old-args) - new-fargs new-args (cons (cons (cadr farg) (cadr varfarg)) renames) - varfarg vararg) - (error "multiple splatted args cannot be fused into a single broadcast")))) - ((julia-scalar? arg) ; inline numeric literals etc. - (cf (cdr old-fargs) (cdr old-args) - new-fargs new-args - (cons (cons farg arg) renames) - varfarg vararg)) - ((and (symbol? arg) (memq arg new-args)) ; combine duplicate args - ; (note: calling memq for every arg is O(length(args)^2) ... - ; ... would be better to replace with a hash table if args is long) - (cf (cdr old-fargs) (cdr old-args) - new-fargs new-args - (cons (cons farg (findfarg arg new-args new-fargs)) renames) - varfarg vararg)) - (else - (cf (cdr old-fargs) (cdr old-args) - (cons farg new-fargs) (cons arg new-args) renames varfarg vararg)))))) - (cf (cdadr f) args '() '() '() '() '())) - e)) ; (not (fuse? e)) - (let ((e (compress-fuse (dot-to-fuse rhs))) ; an expression '(fuse func args) if expr is a dot call - (lhs-view (ref-to-view lhs))) ; x[...] expressions on lhs turn in to view(x, ...) to update x in-place - (if (fuse? e) + (x (caddr e))) + (cond ((or (eq? (car x) 'quote) (eq? (car x) 'inert) (eq? (car x) '$)) + (make-fuse-arg `(call (core getfield) ,f ,x))) + ((eq? (car x) 'tuple) + (make-fuse-call f (cdr x))) + (else + (error (string "invalid syntax " (deparse e)))))) + (if (and (pair? e) (eq? (car e) 'call) (dotop? (cadr e))) + (make-fuse-call (undotop (cadr e)) (cddr e)) + (make-fuse-arg e)))) + (let* ((lhs-view (ref-to-view lhs)) ; x[...] expressions on lhs turn in to view(x, ...) to update x in-place + (e (dot-to-fuse rhs))) + (if (and (pair? e) (eq? (car e) 'fuse)) (if (null? lhs) - (expand-forms `(call (top broadcast) ,(from-lambda (cadr e)) ,@(caddr e))) - (expand-forms `(call (top broadcast!) ,(from-lambda (cadr e)) ,lhs-view ,@(caddr e)))) + (expand-forms `(call (top broadcast) ,(cadr e) ,@(caddr e))) + (expand-forms `(call (top broadcast!) ,(cadr e) ,lhs-view ,@(caddr e)))) (if (null? lhs) (expand-forms e) (expand-forms `(call (top broadcast!) (top identity) ,lhs-view ,e)))))) diff --git a/test/broadcast.jl b/test/broadcast.jl index dc69b534234cf..69cd169f19deb 100644 --- a/test/broadcast.jl +++ b/test/broadcast.jl @@ -315,14 +315,23 @@ let identity = error, x = [1,2,3] @test x == [1,1,1] end -# make sure scalars are inlined, which causes f.(x,scalar) to lower to a "thunk" -import Base.Meta: isexpr -@test isexpr(expand(Main, :(f.(x,y))), :call) -@test isexpr(expand(Main, :(f.(x,1))), :thunk) -@test isexpr(expand(Main, :(f.(x,1.0))), :thunk) -@test isexpr(expand(Main, :(f.(x,$π))), :thunk) -@test isexpr(expand(Main, :(f.(x,"hello"))), :thunk) -@test isexpr(expand(Main, :(f.(x,$("hello")))), :thunk) +# make sure scalars are inlined as FusionConstants (generated by using Core.println) +@test expand(Main, :(f.(x, y))) == Expr(:call, GlobalRef(Base, :broadcast), Expr(:call, Expr(:call, GlobalRef(Core, :apply_type), Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:Fusion)), 2, false), Expr(:call, Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:FusionCall)), :f, Expr(:call, GlobalRef(Core, :tuple), Expr(:call, Expr(:call, GlobalRef(Core, :apply_type), Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:FusionArg)), 1)), Expr(:call, Expr(:call, GlobalRef(Core, :apply_type), Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:FusionArg)), 2))))), :x, :y) + +@test expand(Main, :(f.(x, 1))) == Expr(:call, GlobalRef(Base, :broadcast), Expr(:call, Expr(:call, GlobalRef(Core, :apply_type), Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:Fusion)), 1, false), Expr(:call, Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:FusionCall)), :f, Expr(:call, GlobalRef(Core, :tuple), Expr(:call, Expr(:call, GlobalRef(Core, :apply_type), Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:FusionArg)), 1)), Expr(:call, Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:FusionConstant)), 1)))), :x) + +@test expand(Main, :(f.(x, 1.0))) == Expr(:call, GlobalRef(Base, :broadcast), Expr(:call, Expr(:call, GlobalRef(Core, :apply_type), Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:Fusion)), 1, false), Expr(:call, Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:FusionCall)), :f, Expr(:call, GlobalRef(Core, :tuple), Expr(:call, Expr(:call, GlobalRef(Core, :apply_type), Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:FusionArg)), 1)), Expr(:call, Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:FusionConstant)), 1.0)))), :x) + +@test expand(Main, :(f.(x, $π))) == Expr(:call, GlobalRef(Base, :broadcast), Expr(:call, Expr(:call, GlobalRef(Core, :apply_type), Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:Fusion)), 1, false), Expr(:call, Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:FusionCall)), :f, Expr(:call, GlobalRef(Core, :tuple), Expr(:call, Expr(:call, GlobalRef(Core, :apply_type), Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:FusionArg)), 1)), Expr(:call, Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:FusionConstant)), π)))), :x) + +@test expand(Main, :(f.(x, "hello"))) == Expr(:call, GlobalRef(Base, :broadcast), Expr(:call, Expr(:call, GlobalRef(Core, :apply_type), Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:Fusion)), 1, false), Expr(:call, Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:FusionCall)), :f, Expr(:call, GlobalRef(Core, :tuple), Expr(:call, Expr(:call, GlobalRef(Core, :apply_type), Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:FusionArg)), 1)), Expr(:call, Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:FusionConstant)), "hello")))), :x) + +@test expand(Main, :(f.(x, $"hello"))) == Expr(:call, GlobalRef(Base, :broadcast), Expr(:call, Expr(:call, GlobalRef(Core, :apply_type), Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:Fusion)), 1, false), Expr(:call, Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:FusionCall)), :f, Expr(:call, GlobalRef(Core, :tuple), Expr(:call, Expr(:call, GlobalRef(Core, :apply_type), Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:FusionArg)), 1)), Expr(:call, Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:FusionConstant)), "hello")))), :x) + +let val = QuoteNode(Any["some", "random", "splice", "value"]) + @test expand(Main, :(f.(x, $val))) == Expr(:call, GlobalRef(Base, :broadcast), Expr(:call, Expr(:call, GlobalRef(Core, :apply_type), Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:Fusion)), 2, false), Expr(:call, Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:FusionCall)), :f, Expr(:call, GlobalRef(Core, :tuple), Expr(:call, Expr(:call, GlobalRef(Core, :apply_type), Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:FusionArg)), 1)), Expr(:call, Expr(:call, GlobalRef(Core, :apply_type), Expr(:call, GlobalRef(Core, :getfield), GlobalRef(Base, :Broadcast), :(:FusionArg)), 2))))), :x, val) +end + # PR #17623: Fused binary operators @test [true] .* [true] == [true] @@ -451,8 +460,10 @@ end @testset "broadcasting for custom AbstractArray" begin a = randn(10) aa = Array19745(a) - @test a .+ 1 == @inferred(aa .+ 1) - @test a .* a' == @inferred(aa .* aa') + fadd(aa) = aa .+ 1 + fprod(aa) = aa .* aa' + @test a .+ 1 == @inferred(fadd(aa)) + @test a .* a' == @inferred(fprod(aa)) @test isa(aa .+ 1, Array19745) @test isa(aa .* aa', Array19745) end