JuliaLang / julia

The Julia Programming Language
https://julialang.org/
MIT License
45.58k stars 5.48k forks source link

Sometimes @fastmath @. macros cannot work together. #49945

Open Tree-Yang opened 1 year ago

Tree-Yang commented 1 year ago

I am a newcomer to Julia language. However, I found something strange about @fastmath and @. .

This problem happens when slices of matrices or vectors are involved. Please consider the following case:

julia> a = rand(Float64, 2, 10)
2×10 Matrix{Float64}:
 0.670906  0.160401  0.626289  0.391141  0.359202  0.546171  0.382683  0.276164     0.0326305  0.288932
 0.102545  0.316434  0.306069  0.919442  0.943035  0.516146  0.710773  0.000468101  0.428014   0.305994

julia> @fastmath @. a[1, 1:2:10] += 1.0
ERROR: UndefVarError: ##312 not defined
Stacktrace:
 [1] top-level scope
   @ fastmath.jl:116

However, the following code works:

 @fastmath a[1, 1:2:10] .+= 1.0
5-element view(::Matrix{Float64}, 1, 1:2:9) with eltype Float64:
 1.6709058653769435
 1.6262886999132795
 1.3592019776966817
 1.3826830585942695
 1.0326304999494327

julia> a
2×10 Matrix{Float64}:
 1.67091   0.160401  1.62629   0.391141  1.3592    0.546171  1.38268   0.276164     1.03263   0.288932
 0.102545  0.316434  0.306069  0.919442  0.943035  0.516146  0.710773  0.000468101  0.428014  0.305994

When no slices are involved, this problem will disapper:

@fastmath @. a += 1.0
2×10 Matrix{Float64}:
 2.67091  1.1604   2.62629  1.39114  2.3592   1.54617  2.38268  1.27616  2.03263  1.28893
 1.10255  1.31643  1.30607  1.91944  1.94303  1.51615  1.71077  1.00047  1.42801  1.30599

My platform is Julia 1.8.1 on Window 11 version 22H2 (OS Build 22621.1.1702). The same problem appears for Julia 1.8.5 on WSL 2 Ubuntu 20.04 LTS.

Is this a feature of the language? or is it a bug?

giordano commented 1 year ago

Is this a feature of the language? or is it a bug?

Can't say it's a feature, but as a matter of fact macros aren't composable, in general you can't stick two macros in front of each other and expect them to magically work, because macros can do arbitrary syntax manipulation and one macro may be changing under the feet of the other macro the expression that it sees.

Side note, @fastmath doesn't do anything with .+= anyway because macro expansion happens before lowering, see

julia> @macroexpand @fastmath a[1, 1:2:10] .+= 1.0
:(a[1, 1:2:10] .+= 1.0)
Tree-Yang commented 1 year ago

in general you can't stick two macros in front of each other and expect them to magically work.

Thank you for the reply. Then I will make sure macros are not used together in my codes.

As for why '@fastmath' appears in front of '@.', the reason is that I put '@fastmath' before the definition of the function, that is,

'@fastmath function myfunc()', and the '@.' macro is used inside the function.

Is this a feature of the language? or is it a bug?

Can't say it's a feature, but as a matter of fact macros aren't composable, in general you can't stick two macros in front of each other and expect them to magically work, because macros can do arbitrary syntax manipulation and one macro may be changing under the feet of the other macro the expression that it sees.

Side note, @fastmath doesn't do anything with broadcasting anyway, see

julia> @macroexpand @fastmath a[1, 1:2:10] .+= 1.0
:(a[1, 1:2:10] .+= 1.0)
antoine-levitt commented 1 year ago

Can't say it's a feature, but as a matter of fact macros aren't composable, in general you can't stick two macros in front of each other and expect them to magically work, because macros can do arbitrary syntax manipulation and one macro may be changing under the feet of the other macro the expression that it sees.

I think this is a bug:

julia> @macroexpand @fastmath @. a[1, 1:2:10] += 1.0
quote
    #= fastmath.jl:116 =#
    var"##298" .= a
    #= fastmath.jl:117 =#
    (var"##299", var"##300") .= (1, 1:2:10)
    #= fastmath.jl:118 =#
    var"##298"[var"##299", var"##300"] .= Base.FastMath.add_fast.(var"##298"[var"##299", var"##300"], 1.0)
end

If I understand correctly, @a @b x passes @b x to the macro a, which then tweaks it. In this particular case, it looks like fastmath just does Base.exprarray(make_fastmath(expr.head), Base.mapany(make_fastmath, expr.args)), ie applies fastmath recursively to the expression. Shouldn't it expand any macros it encounters before going through? (and shouldn't all macros do this as good practice?)