• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #37632

26 Sep 2023 06:44AM UTC coverage: 86.999% (-0.9%) from 87.914%
#37632

push

local

web-flow
inference: make `throw` block deoptimization concrete-eval friendly (#49235)

The deoptimization can sometimes destroy the effects analysis and
disable [semi-]concrete evaluation that is otherwise possible. This is
because the deoptimization was designed with the type domain
profitability in mind (#35982), and hasn't been adequately considering
the effects domain.

This commit makes the deoptimization aware of the effects domain more
and enables the `throw` block deoptimization only when the effects
already known to be ineligible for concrete-evaluation.

In our current effect system, `ALWAYS_FALSE`/`false` means that the
effect can not be refined to `ALWAYS_TRUE`/`true` anymore (unless given
user annotation later). Therefore we can enable the `throw` block
deoptimization without hindering the chance of concrete-evaluation when
any of the following conditions are met:
- `effects.consistent === ALWAYS_FALSE`
- `effects.effect_free === ALWAYS_FALSE`
- `effects.terminates === false`
- `effects.nonoverlayed === false`

Here are some numbers:

| Metric | master | this commit | #35982 reverted (set
`unoptimize_throw_blocks=false`) |

|-------------------------|-----------|-------------|--------------------------------------------|
| Base (seconds) | 15.579300 | 15.206645 | 15.296319 |
| Stdlibs (seconds) | 17.919013 | 17.667094 | 17.738128 |
| Total (seconds) | 33.499279 | 32.874737 | 33.035448 |
| Precompilation (seconds) | 49.967516 | 49.421121 | 49.999998 |
| First time `plot(rand(10,3))` [^1] | `2.476678 seconds (11.74 M
allocations)` | `2.430355 seconds (11.77 M allocations)` | `2.514874
seconds (11.64 M allocations)` |
| First time `solve(prob, QNDF())(5.0)` [^2] | `4.469492 seconds (15.32
M allocations)` | `4.499217 seconds (15.41 M allocations)` | `4.470772
seconds (15.38 M allocations)` |

[^1]: With disabling precompilation of Plots.jl.
[^2]: With disabling precompilation of OrdinaryDiffEq.

These numbers ma... (continued)

7 of 7 new or added lines in 1 file covered. (100.0%)

73407 of 84377 relevant lines covered (87.0%)

11275130.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.05
/base/expr.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
isexpr(@nospecialize(ex), heads) = isa(ex, Expr) && in(ex.head, heads)
1,935✔
4
isexpr(@nospecialize(ex), heads, n::Int) = isa(ex, Expr) && in(ex.head, heads) && length(ex.args) == n
×
5
const is_expr = isexpr
6

7
## symbols ##
8

9
"""
10
    gensym([tag])
11

12
Generates a symbol which will not conflict with other variable names (in the same module).
13
"""
14
gensym() = ccall(:jl_gensym, Ref{Symbol}, ())
484✔
15

16
gensym(s::String) = ccall(:jl_tagged_gensym, Ref{Symbol}, (Ptr{UInt8}, Csize_t), s, sizeof(s))
213✔
17

18
gensym(ss::String...) = map(gensym, ss)
×
19
gensym(s::Symbol) = ccall(:jl_tagged_gensym, Ref{Symbol}, (Ptr{UInt8}, Csize_t), s, -1 % Csize_t)
9✔
20

21
"""
22
    @gensym
23

24
Generates a gensym symbol for a variable. For example, `@gensym x y` is transformed into
25
`x = gensym("x"); y = gensym("y")`.
26
"""
27
macro gensym(names...)
1✔
28
    blk = Expr(:block)
1✔
29
    for name in names
1✔
30
        push!(blk.args, :($(esc(name)) = gensym($(string(name)))))
1✔
31
    end
1✔
32
    push!(blk.args, :nothing)
1✔
33
    return blk
1✔
34
end
35

36
## line numbers ##
37
convert(::Type{LineNumberNode}, lin::Core.LineInfoNode) = LineNumberNode(Int(lin.line), lin.file)
1✔
38

39
## expressions ##
40

41
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === head
2,147,483,647✔
42
isexpr(@nospecialize(ex), head::Symbol, n::Int) = isa(ex, Expr) && ex.head === head && length(ex.args) == n
63,597✔
43

44
copy(e::Expr) = exprarray(e.head, copy_exprargs(e.args))
119,833,496✔
45

46
# copy parts of an AST that the compiler mutates
47
function copy_exprs(@nospecialize(x))
384,060,838✔
48
    if isa(x, Expr)
384,060,838✔
49
        return copy(x)
119,817,463✔
50
    elseif isa(x, PhiNode)
264,243,375✔
51
        values = x.values
2,742,206✔
52
        nvalues = length(values)
2,742,206✔
53
        new_values = Vector{Any}(undef, nvalues)
2,742,206✔
54
        @inbounds for i = 1:nvalues
5,484,402✔
55
            isassigned(values, i) || continue
5,261,759✔
56
            new_values[i] = copy_exprs(values[i])
5,261,751✔
57
        end
7,781,322✔
58
        return PhiNode(copy(x.edges), new_values)
2,742,206✔
59
    elseif isa(x, PhiCNode)
261,501,169✔
60
        values = x.values
11✔
61
        nvalues = length(values)
11✔
62
        new_values = Vector{Any}(undef, nvalues)
11✔
63
        @inbounds for i = 1:nvalues
22✔
64
            isassigned(values, i) || continue
17✔
65
            new_values[i] = copy_exprs(values[i])
17✔
66
        end
23✔
67
        return PhiCNode(new_values)
11✔
68
    end
69
    return x
261,501,158✔
70
end
71
copy_exprargs(x::Array{Any,1}) = Any[copy_exprs(@inbounds x[i]) for i in 1:length(x)]
293,506,772✔
72

73
@eval exprarray(head::Symbol, arg::Array{Any,1}) = $(Expr(:new, :Expr, :head, :arg))
119,835,293✔
74

75
# create copies of the CodeInfo definition, and any mutable fields
76
function copy(c::CodeInfo)
6,499,962✔
77
    cnew = ccall(:jl_copy_code_info, Ref{CodeInfo}, (Any,), c)
6,499,962✔
78
    cnew.code = copy_exprargs(cnew.code)
101,579,309✔
79
    cnew.slotnames = copy(cnew.slotnames)
6,499,962✔
80
    cnew.slotflags = copy(cnew.slotflags)
6,499,962✔
81
    if cnew.slottypes !== nothing
6,499,962✔
82
        cnew.slottypes = copy(cnew.slottypes)
4,415,264✔
83
    end
84
    cnew.codelocs  = copy(cnew.codelocs)
6,499,962✔
85
    cnew.linetable = copy(cnew.linetable::Union{Vector{Any},Vector{Core.LineInfoNode}})
12,998,683✔
86
    cnew.ssaflags  = copy(cnew.ssaflags)
6,499,962✔
87
    cnew.edges     = cnew.edges === nothing ? nothing : copy(cnew.edges::Vector)
6,499,963✔
88
    ssavaluetypes  = cnew.ssavaluetypes
6,499,962✔
89
    ssavaluetypes isa Vector{Any} && (cnew.ssavaluetypes = copy(ssavaluetypes))
6,499,962✔
90
    return cnew
6,499,962✔
91
end
92

93

94
==(x::Expr, y::Expr) = x.head === y.head && isequal(x.args, y.args)
13,465✔
95
==(x::QuoteNode, y::QuoteNode) = isequal(x.value, y.value)
1,513✔
96
==(stmt1::Core.PhiNode, stmt2::Core.PhiNode) = stmt1.edges == stmt2.edges && stmt1.values == stmt2.values
×
97

98
"""
99
    macroexpand(m::Module, x; recursive=true)
100

101
Take the expression `x` and return an equivalent expression with all macros removed (expanded)
102
for executing in module `m`.
103
The `recursive` keyword controls whether deeper levels of nested macros are also expanded.
104
This is demonstrated in the example below:
105
```julia-repl
106
julia> module M
107
           macro m1()
108
               42
109
           end
110
           macro m2()
111
               :(@m1())
112
           end
113
       end
114
M
115

116
julia> macroexpand(M, :(@m2()), recursive=true)
117
42
118

119
julia> macroexpand(M, :(@m2()), recursive=false)
120
:(#= REPL[16]:6 =# M.@m1)
121
```
122
"""
123
function macroexpand(m::Module, @nospecialize(x); recursive=true)
387✔
124
    if recursive
32✔
125
        ccall(:jl_macroexpand, Any, (Any, Any), x, m)
355✔
126
    else
127
        ccall(:jl_macroexpand1, Any, (Any, Any), x, m)
×
128
    end
129
end
130

131
"""
132
    @macroexpand
133

134
Return equivalent expression with all macros removed (expanded).
135

136
There are differences between `@macroexpand` and [`macroexpand`](@ref).
137

138
* While [`macroexpand`](@ref) takes a keyword argument `recursive`, `@macroexpand`
139
  is always recursive. For a non recursive macro version, see [`@macroexpand1`](@ref).
140

141
* While [`macroexpand`](@ref) has an explicit `module` argument, `@macroexpand` always
142
  expands with respect to the module in which it is called.
143

144
This is best seen in the following example:
145
```julia-repl
146
julia> module M
147
           macro m()
148
               1
149
           end
150
           function f()
151
               (@macroexpand(@m),
152
                macroexpand(M, :(@m)),
153
                macroexpand(Main, :(@m))
154
               )
155
           end
156
       end
157
M
158

159
julia> macro m()
160
           2
161
       end
162
@m (macro with 1 method)
163

164
julia> M.f()
165
(1, 1, 2)
166
```
167
With `@macroexpand` the expression expands where `@macroexpand` appears in the code (module `M` in the example).
168
With `macroexpand` the expression expands in the module given as the first argument.
169
"""
170
macro macroexpand(code)
34✔
171
    return :(macroexpand($__module__, $(QuoteNode(code)), recursive=true))
34✔
172
end
173

174

175
"""
176
    @macroexpand1
177

178
Non recursive version of [`@macroexpand`](@ref).
179
"""
180
macro macroexpand1(code)
181
    return :(macroexpand($__module__, $(QuoteNode(code)), recursive=false))
182
end
183

184
## misc syntax ##
185

186
"""
187
    Core.eval(m::Module, expr)
188

189
Evaluate an expression in the given module and return the result.
190
"""
191
Core.eval
192

193
"""
194
    @inline
195

196
Give a hint to the compiler that this function is worth inlining.
197

198
Small functions typically do not need the `@inline` annotation,
199
as the compiler does it automatically. By using `@inline` on bigger functions,
200
an extra nudge can be given to the compiler to inline it.
201

202
`@inline` can be applied immediately before a function definition or within a function body.
203

204
```julia
205
# annotate long-form definition
206
@inline function longdef(x)
207
    ...
208
end
209

210
# annotate short-form definition
211
@inline shortdef(x) = ...
212

213
# annotate anonymous function that a `do` block creates
214
f() do
215
    @inline
216
    ...
217
end
218
```
219

220
!!! compat "Julia 1.8"
221
    The usage within a function body requires at least Julia 1.8.
222

223
---
224
    @inline block
225

226
Give a hint to the compiler that calls within `block` are worth inlining.
227

228
```julia
229
# The compiler will try to inline `f`
230
@inline f(...)
231

232
# The compiler will try to inline `f`, `g` and `+`
233
@inline f(...) + g(...)
234
```
235

236
!!! note
237
    A callsite annotation always has the precedence over the annotation applied to the
238
    definition of the called function:
239
    ```julia
240
    @noinline function explicit_noinline(args...)
241
        # body
242
    end
243

244
    let
245
        @inline explicit_noinline(args...) # will be inlined
246
    end
247
    ```
248

249
!!! note
250
    When there are nested callsite annotations, the innermost annotation has the precedence:
251
    ```julia
252
    @noinline let a0, b0 = ...
253
        a = @inline f(a0)  # the compiler will try to inline this call
254
        b = f(b0)          # the compiler will NOT try to inline this call
255
        return a, b
256
    end
257
    ```
258

259
!!! warning
260
    Although a callsite annotation will try to force inlining in regardless of the cost model,
261
    there are still chances it can't succeed in it. Especially, recursive calls can not be
262
    inlined even if they are annotated as `@inline`d.
263

264
!!! compat "Julia 1.8"
265
    The callsite annotation requires at least Julia 1.8.
266
"""
267
macro inline(x)
1,083✔
268
    return annotate_meta_def_or_block(x, :inline)
1,083✔
269
end
270

271
"""
272
    @noinline
273

274
Give a hint to the compiler that it should not inline a function.
275

276
Small functions are typically inlined automatically.
277
By using `@noinline` on small functions, auto-inlining can be
278
prevented.
279

280
`@noinline` can be applied immediately before a function definition or within a function body.
281

282
```julia
283
# annotate long-form definition
284
@noinline function longdef(x)
285
    ...
286
end
287

288
# annotate short-form definition
289
@noinline shortdef(x) = ...
290

291
# annotate anonymous function that a `do` block creates
292
f() do
293
    @noinline
294
    ...
295
end
296
```
297

298
!!! compat "Julia 1.8"
299
    The usage within a function body requires at least Julia 1.8.
300

301
---
302
    @noinline block
303

304
Give a hint to the compiler that it should not inline the calls within `block`.
305

306
```julia
307
# The compiler will try to not inline `f`
308
@noinline f(...)
309

310
# The compiler will try to not inline `f`, `g` and `+`
311
@noinline f(...) + g(...)
312
```
313

314
!!! note
315
    A callsite annotation always has the precedence over the annotation applied to the
316
    definition of the called function:
317
    ```julia
318
    @inline function explicit_inline(args...)
319
        # body
320
    end
321

322
    let
323
        @noinline explicit_inline(args...) # will not be inlined
324
    end
325
    ```
326

327
!!! note
328
    When there are nested callsite annotations, the innermost annotation has the precedence:
329
    ```julia
330
    @inline let a0, b0 = ...
331
        a = @noinline f(a0)  # the compiler will NOT try to inline this call
332
        b = f(b0)            # the compiler will try to inline this call
333
        return a, b
334
    end
335
    ```
336

337
!!! compat "Julia 1.8"
338
    The callsite annotation requires at least Julia 1.8.
339

340
---
341
!!! note
342
    If the function is trivial (for example returning a constant) it might get inlined anyway.
343
"""
344
macro noinline(x)
250✔
345
    return annotate_meta_def_or_block(x, :noinline)
250✔
346
end
347

348
"""
349
    Base.@constprop setting [ex]
350

351
Control the mode of interprocedural constant propagation for the annotated function.
352

353
Two `setting`s are supported:
354

355
- `Base.@constprop :aggressive [ex]`: apply constant propagation aggressively.
356
  For a method where the return type depends on the value of the arguments,
357
  this can yield improved inference results at the cost of additional compile time.
358
- `Base.@constprop :none [ex]`: disable constant propagation. This can reduce compile
359
  times for functions that Julia might otherwise deem worthy of constant-propagation.
360
  Common cases are for functions with `Bool`- or `Symbol`-valued arguments or keyword arguments.
361

362
`Base.@constprop` can be applied immediately before a function definition or within a function body.
363

364
```julia
365
# annotate long-form definition
366
Base.@constprop :aggressive function longdef(x)
367
    ...
368
end
369

370
# annotate short-form definition
371
Base.@constprop :aggressive shortdef(x) = ...
372

373
# annotate anonymous function that a `do` block creates
374
f() do
375
    Base.@constprop :aggressive
376
    ...
377
end
378
```
379

380
!!! compat "Julia 1.10"
381
  The usage within a function body requires at least Julia 1.10.
382
"""
383
macro constprop(setting, ex)
32✔
384
    sym = constprop_setting(setting)
32✔
385
    isa(ex, Expr) && return esc(pushmeta!(ex, sym))
32✔
386
    throw(ArgumentError(LazyString("Bad expression `", ex, "` in `@constprop settings ex`")))
×
387
end
388
macro constprop(setting)
389
    sym = constprop_setting(setting)
390
    return Expr(:meta, sym)
391
end
392

393
function constprop_setting(@nospecialize setting)
32✔
394
    isa(setting, QuoteNode) && (setting = setting.value)
32✔
395
    if setting === :aggressive
32✔
396
        return :aggressive_constprop
21✔
397
    elseif setting === :none
11✔
398
        return :no_constprop
11✔
399
    end
400
    throw(ArgumentError(LazyString("@constprop "), setting, "not supported"))
×
401
end
402

403
"""
404
    Base.@assume_effects setting... [ex]
405

406
Override the compiler's effect modeling for the given method or foreign call.
407
`@assume_effects` can be applied immediately before a function definition or within a function body.
408
It can also be applied immediately before a `@ccall` expression.
409

410
!!! compat "Julia 1.8"
411
    Using `Base.@assume_effects` requires Julia version 1.8.
412

413
# Examples
414
```jldoctest
415
julia> Base.@assume_effects :terminates_locally function fact(x)
416
           # this :terminates_locally allows `fact` to be constant-folded
417
           res = 1
418
           0 ≤ x < 20 || error("bad fact")
419
           while x > 1
420
               res *= x
421
               x -= 1
422
           end
423
           return res
424
       end
425
fact (generic function with 1 method)
426

427
julia> code_typed() do
428
           fact(12)
429
       end |> only
430
CodeInfo(
431
1 ─     return 479001600
432
) => Int64
433

434
julia> code_typed() do
435
           map((2,3,4)) do x
436
               # this :terminates_locally allows this anonymous function to be constant-folded
437
               Base.@assume_effects :terminates_locally
438
               res = 1
439
               0 ≤ x < 20 || error("bad fact")
440
               while x > 1
441
                   res *= x
442
                   x -= 1
443
               end
444
               return res
445
           end
446
       end |> only
447
CodeInfo(
448
1 ─     return (2, 6, 24)
449
) => Tuple{Int64, Int64, Int64}
450

451
julia> Base.@assume_effects :total !:nothrow @ccall jl_type_intersection(Vector{Int}::Any, Vector{<:Integer}::Any)::Any
452
Vector{Int64} (alias for Array{Int64, 1})
453
```
454

455
!!! compat "Julia 1.10"
456
    The usage within a function body requires at least Julia 1.10.
457

458
!!! warning
459
    Improper use of this macro causes undefined behavior (including crashes,
460
    incorrect answers, or other hard to track bugs). Use with care and only as a
461
    last resort if absolutely required. Even in such a case, you SHOULD take all
462
    possible steps to minimize the strength of the effect assertion (e.g.,
463
    do not use `:total` if `:nothrow` would have been sufficient).
464

465
In general, each `setting` value makes an assertion about the behavior of the
466
function, without requiring the compiler to prove that this behavior is indeed
467
true. These assertions are made for all world ages. It is thus advisable to limit
468
the use of generic functions that may later be extended to invalidate the
469
assumption (which would cause undefined behavior).
470

471
The following `setting`s are supported.
472
- `:consistent`
473
- `:effect_free`
474
- `:nothrow`
475
- `:terminates_globally`
476
- `:terminates_locally`
477
- `:notaskstate`
478
- `:inaccessiblememonly`
479
- `:noub`
480
- `:foldable`
481
- `:removable`
482
- `:total`
483

484
# Extended help
485

486
---
487
## `:consistent`
488

489
The `:consistent` setting asserts that for egal (`===`) inputs:
490
- The manner of termination (return value, exception, non-termination) will always be the same.
491
- If the method returns, the results will always be egal.
492

493
!!! note
494
    This in particular implies that the method must not return a freshly allocated
495
    mutable object. Multiple allocations of mutable objects (even with identical
496
    contents) are not egal.
497

498
!!! note
499
    The `:consistent`-cy assertion is made world-age wise. More formally, write
500
    ``fáµ¢`` for the evaluation of ``f`` in world-age ``i``, then we require:
501
    ```math
502
    ∀ i, x, y: x ≡ y → fᵢ(x) ≡ fᵢ(y)
503
    ```
504
    However, for two world ages ``i``, ``j`` s.t. ``i ≠ j``, we may have ``fᵢ(x) ≢ fⱼ(y)``.
505

506
    A further implication is that `:consistent` functions may not make their
507
    return value dependent on the state of the heap or any other global state
508
    that is not constant for a given world age.
509

510
!!! note
511
    The `:consistent`-cy includes all legal rewrites performed by the optimizer.
512
    For example, floating-point fastmath operations are not considered `:consistent`,
513
    because the optimizer may rewrite them causing the output to not be `:consistent`,
514
    even for the same world age (e.g. because one ran in the interpreter, while
515
    the other was optimized).
516

517
!!! note
518
    If `:consistent` functions terminate by throwing an exception, that exception
519
    itself is not required to meet the egality requirement specified above.
520

521
---
522
## `:effect_free`
523

524
The `:effect_free` setting asserts that the method is free of externally semantically
525
visible side effects. The following is an incomplete list of externally semantically
526
visible side effects:
527
- Changing the value of a global variable.
528
- Mutating the heap (e.g. an array or mutable value), except as noted below
529
- Changing the method table (e.g. through calls to eval)
530
- File/Network/etc. I/O
531
- Task switching
532

533
However, the following are explicitly not semantically visible, even if they
534
may be observable:
535
- Memory allocations (both mutable and immutable)
536
- Elapsed time
537
- Garbage collection
538
- Heap mutations of objects whose lifetime does not exceed the method (i.e.
539
  were allocated in the method and do not escape).
540
- The returned value (which is externally visible, but not a side effect)
541

542
The rule of thumb here is that an externally visible side effect is anything
543
that would affect the execution of the remainder of the program if the function
544
were not executed.
545

546
!!! note
547
    The `:effect_free` assertion is made both for the method itself and any code
548
    that is executed by the method. Keep in mind that the assertion must be
549
    valid for all world ages and limit use of this assertion accordingly.
550

551
---
552
## `:nothrow`
553

554
The `:nothrow` settings asserts that this method does not terminate abnormally
555
(i.e. will either always return a value or never return).
556

557
!!! note
558
    It is permissible for `:nothrow` annotated methods to make use of exception
559
    handling internally as long as the exception is not rethrown out of the
560
    method itself.
561

562
!!! note
563
    `MethodErrors` and similar exceptions count as abnormal termination.
564

565
---
566
## `:terminates_globally`
567

568
The `:terminates_globally` settings asserts that this method will eventually terminate
569
(either normally or abnormally), i.e. does not loop indefinitely.
570

571
!!! note
572
    This `:terminates_globally` assertion covers any other methods called by the annotated method.
573

574
!!! note
575
    The compiler will consider this a strong indication that the method will
576
    terminate relatively *quickly* and may (if otherwise legal) call this
577
    method at compile time. I.e. it is a bad idea to annotate this setting
578
    on a method that *technically*, but not *practically*, terminates.
579

580
---
581
## `:terminates_locally`
582

583
The `:terminates_locally` setting is like `:terminates_globally`, except that it only
584
applies to syntactic control flow *within* the annotated method. It is thus
585
a much weaker (and thus safer) assertion that allows for the possibility of
586
non-termination if the method calls some other method that does not terminate.
587

588
!!! note
589
    `:terminates_globally` implies `:terminates_locally`.
590

591
---
592
## `:notaskstate`
593

594
The `:notaskstate` setting asserts that the method does not use or modify the
595
local task state (task local storage, RNG state, etc.) and may thus be safely
596
moved between tasks without observable results.
597

598
!!! note
599
    The implementation of exception handling makes use of state stored in the
600
    task object. However, this state is currently not considered to be within
601
    the scope of `:notaskstate` and is tracked separately using the `:nothrow`
602
    effect.
603

604
!!! note
605
    The `:notaskstate` assertion concerns the state of the *currently running task*.
606
    If a reference to a `Task` object is obtained by some other means that
607
    does not consider which task is *currently* running, the `:notaskstate`
608
    effect need not be tainted. This is true, even if said task object happens
609
    to be `===` to the currently running task.
610

611
!!! note
612
    Access to task state usually also results in the tainting of other effects,
613
    such as `:effect_free` (if task state is modified) or `:consistent` (if
614
    task state is used in the computation of the result). In particular,
615
    code that is not `:notaskstate`, but is `:effect_free` and `:consistent`
616
    may still be dead-code-eliminated and thus promoted to `:total`.
617

618
---
619
## `:inaccessiblememonly`
620

621
The `:inaccessiblememonly` setting asserts that the method does not access or modify
622
externally accessible mutable memory. This means the method can access or modify mutable
623
memory for newly allocated objects that is not accessible by other methods or top-level
624
execution before return from the method, but it can not access or modify any mutable
625
global state or mutable memory pointed to by its arguments.
626

627
!!! note
628
    Below is an incomplete list of examples that invalidate this assumption:
629
    - a global reference or `getglobal` call to access a mutable global variable
630
    - a global assignment or `setglobal!` call to perform assignment to a non-constant global variable
631
    - `setfield!` call that changes a field of a global mutable variable
632

633
!!! note
634
    This `:inaccessiblememonly` assertion covers any other methods called by the annotated method.
635

636
---
637
## `:noub`
638

639
The `:noub` setting asserts that the method will not execute any undefined behavior
640
(for any input). Note that undefined behavior may technically cause the method to violate
641
any other effect assertions (such as `:consistent` or `:effect_free`) as well, but we do
642
not model this, and they assume the absence of undefined behavior.
643

644
---
645
## `:foldable`
646

647
This setting is a convenient shortcut for the set of effects that the compiler
648
requires to be guaranteed to constant fold a call at compile time. It is
649
currently equivalent to the following `setting`s:
650
- `:consistent`
651
- `:effect_free`
652
- `:terminates_globally`
653
- `:noub`
654

655
!!! note
656
    This list in particular does not include `:nothrow`. The compiler will still
657
    attempt constant propagation and note any thrown error at compile time. Note
658
    however, that by the `:consistent`-cy requirements, any such annotated call
659
    must consistently throw given the same argument values.
660

661
!!! note
662
    An explicit `@inbounds` annotation inside the function will also disable
663
    constant folding and not be overridden by `:foldable`.
664

665
---
666
## `:removable`
667

668
This setting is a convenient shortcut for the set of effects that the compiler
669
requires to be guaranteed to delete a call whose result is unused at compile time.
670
It is currently equivalent to the following `setting`s:
671
- `:effect_free`
672
- `:nothrow`
673
- `:terminates_globally`
674

675
---
676
## `:total`
677

678
This `setting` is the maximum possible set of effects. It currently implies
679
the following other `setting`s:
680
- `:consistent`
681
- `:effect_free`
682
- `:nothrow`
683
- `:terminates_globally`
684
- `:notaskstate`
685
- `:inaccessiblememonly`
686
- `:noub`
687

688
!!! warning
689
    `:total` is a very strong assertion and will likely gain additional semantics
690
    in future versions of Julia (e.g. if additional effects are added and included
691
    in the definition of `:total`). As a result, it should be used with care.
692
    Whenever possible, prefer to use the minimum possible set of specific effect
693
    assertions required for a particular application. In cases where a large
694
    number of effect overrides apply to a set of functions, a custom macro is
695
    recommended over the use of `:total`.
696

697
---
698
## Negated effects
699

700
Effect names may be prefixed by `!` to indicate that the effect should be removed
701
from an earlier meta effect. For example, `:total !:nothrow` indicates that while
702
the call is generally total, it may however throw.
703
"""
704
macro assume_effects(args...)
23✔
705
    lastex = args[end]
23✔
706
    inner = unwrap_macrocalls(lastex)
23✔
707
    if is_function_def(inner)
23✔
708
        ex = lastex
17✔
709
        idx = length(args)-1
17✔
710
    elseif isexpr(lastex, :macrocall) && lastex.args[1] === Symbol("@ccall")
6✔
711
        ex = lastex
3✔
712
        idx = length(args)-1
3✔
713
    else # anonymous function case
714
        ex = nothing
3✔
715
        idx = length(args)
3✔
716
    end
717
    (consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly, noub) =
23✔
718
        (false, false, false, false, false, false, false, false, false)
719
    for org_setting in args[1:idx]
23✔
720
        (setting, val) = compute_assumed_setting(org_setting)
26✔
721
        if setting === :consistent
26✔
722
            consistent = val
×
723
        elseif setting === :effect_free
26✔
724
            effect_free = val
2✔
725
        elseif setting === :nothrow
24✔
726
            nothrow = val
3✔
727
        elseif setting === :terminates_globally
21✔
728
            terminates_globally = val
5✔
729
        elseif setting === :terminates_locally
16✔
730
            terminates_locally = val
3✔
731
        elseif setting === :notaskstate
13✔
732
            notaskstate = val
2✔
733
        elseif setting === :inaccessiblememonly
11✔
734
            inaccessiblememonly = val
×
735
        elseif setting === :noub
11✔
736
            noub = val
×
737
        elseif setting === :foldable
11✔
738
            consistent = effect_free = terminates_globally = noub = val
3✔
739
        elseif setting === :removable
8✔
740
            effect_free = nothrow = terminates_globally = val
2✔
741
        elseif setting === :total
6✔
742
            consistent = effect_free = nothrow = terminates_globally = notaskstate = inaccessiblememonly = noub = val
6✔
743
        else
744
            throw(ArgumentError("@assume_effects $org_setting not supported"))
×
745
        end
746
    end
26✔
747
    if is_function_def(inner)
23✔
748
        return esc(pushmeta!(ex, :purity,
17✔
749
            consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly, noub))
750
    elseif isexpr(ex, :macrocall) && ex.args[1] === Symbol("@ccall")
6✔
751
        ex.args[1] = GlobalRef(Base, Symbol("@ccall_effects"))
3✔
752
        insert!(ex.args, 3, Core.Compiler.encode_effects_override(Core.Compiler.EffectsOverride(
3✔
753
            consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly, noub)))
754
        return esc(ex)
3✔
755
    else # anonymous function case
756
        return Expr(:meta, Expr(:purity,
3✔
757
            consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly, noub))
758
    end
759
end
760

761
function compute_assumed_setting(@nospecialize(setting), val::Bool=true)
29✔
762
    if isexpr(setting, :call) && setting.args[1] === :(!)
133✔
763
        return compute_assumed_setting(setting.args[2], !val)
6✔
764
    elseif isa(setting, QuoteNode)
52✔
765
        return compute_assumed_setting(setting.value, val)
26✔
766
    else
767
        return (setting, val)
26✔
768
    end
769
end
770

771
"""
772
    Base.@nospecializeinfer function f(args...)
773
        @nospecialize ...
774
        ...
775
    end
776
    Base.@nospecializeinfer f(@nospecialize args...) = ...
777

778
Tells the compiler to infer `f` using the declared types of `@nospecialize`d arguments.
779
This can be used to limit the number of compiler-generated specializations during inference.
780

781
# Example
782

783
```julia
784
julia> f(A::AbstractArray) = g(A)
785
f (generic function with 1 method)
786

787
julia> @noinline Base.@nospecializeinfer g(@nospecialize(A::AbstractArray)) = A[1]
788
g (generic function with 1 method)
789

790
julia> @code_typed f([1.0])
791
CodeInfo(
792
1 ─ %1 = invoke Main.g(_2::AbstractArray)::Any
793
└──      return %1
794
) => Any
795
```
796

797
In this example, `f` will be inferred for each specific type of `A`,
798
but `g` will only be inferred once with the declared argument type `A::AbstractArray`,
799
meaning that the compiler will not likely see the excessive inference time on it
800
while it can not infer the concrete return type of it.
801
Without the `@nospecializeinfer`, `f([1.0])` would infer the return type of `g` as `Float64`,
802
indicating that inference ran for `g(::Vector{Float64})` despite the prohibition on
803
specialized code generation.
804
"""
805
macro nospecializeinfer(ex)
806
    esc(isa(ex, Expr) ? pushmeta!(ex, :nospecializeinfer) : ex)
807
end
808

809
"""
810
    @propagate_inbounds
811

812
Tells the compiler to inline a function while retaining the caller's inbounds context.
813
"""
814
macro propagate_inbounds(ex)
74✔
815
    if isa(ex, Expr)
74✔
816
        pushmeta!(ex, :inline)
74✔
817
        pushmeta!(ex, :propagate_inbounds)
74✔
818
    end
819
    esc(ex)
74✔
820
end
821

822
"""
823
    @polly
824

825
Tells the compiler to apply the polyhedral optimizer Polly to a function.
826
"""
827
macro polly(ex)
828
    esc(isa(ex, Expr) ? pushmeta!(ex, :polly) : ex)
829
end
830

831
## some macro utilities ##
832

833
unwrap_macrocalls(@nospecialize(x)) = x
1✔
834
function unwrap_macrocalls(ex::Expr)
22✔
835
    inner = ex
×
836
    while inner.head === :macrocall
2,894✔
837
        inner = inner.args[end]::Expr
48✔
838
    end
48✔
839
    return inner
2,846✔
840
end
841

842
function pushmeta!(ex::Expr, sym::Symbol, args::Any...)
1,491✔
843
    if isempty(args)
×
844
        tag = sym
×
845
    else
846
        tag = Expr(sym, args...)::Expr
17✔
847
    end
848

849
    inner = unwrap_macrocalls(ex)
1,523✔
850

851
    idx, exargs = findmeta(inner)
1,491✔
852
    if idx != 0
1,491✔
853
        push!(exargs[idx].args, tag)
210✔
854
    else
855
        body = inner.args[2]::Expr
1,386✔
856
        pushfirst!(body.args, Expr(:meta, tag))
1,386✔
857
    end
858
    ex
1,491✔
859
end
860

861
popmeta!(body, sym) = _getmeta(body, sym, true)
×
862
peekmeta(body, sym) = _getmeta(body, sym, false)
×
863

864
function _getmeta(body::Expr, sym::Symbol, delete::Bool)
×
865
    body.head === :block || return false, []
×
866
    _getmeta(body.args, sym, delete)
×
867
end
868
_getmeta(arg, sym, delete::Bool) = (false, [])
×
869
function _getmeta(body::Array{Any,1}, sym::Symbol, delete::Bool)
×
870
    idx, blockargs = findmeta_block(body, args -> findmetaarg(args,sym)!=0)
×
871
    if idx == 0
×
872
        return false, []
×
873
    end
874
    metaargs = blockargs[idx].args
×
875
    i = findmetaarg(blockargs[idx].args, sym)
×
876
    if i == 0
×
877
        return false, []
×
878
    end
879
    ret = isa(metaargs[i], Expr) ? (metaargs[i]::Expr).args : []
×
880
    if delete
×
881
        deleteat!(metaargs, i)
×
882
        isempty(metaargs) && deleteat!(blockargs, idx)
×
883
    end
884
    true, ret
×
885
end
886

887
# Find index of `sym` in a meta expression argument list, or 0.
888
function findmetaarg(metaargs, sym)
×
889
    for i = 1:length(metaargs)
×
890
        arg = metaargs[i]
×
891
        if (isa(arg, Symbol) && (arg::Symbol)    == sym) ||
×
892
           (isa(arg, Expr)   && (arg::Expr).head == sym)
893
            return i
×
894
        end
895
    end
×
896
    return 0
×
897
end
898

899
function annotate_meta_def_or_block(@nospecialize(ex), meta::Symbol)
1,333✔
900
    inner = unwrap_macrocalls(ex)
1,333✔
901
    if is_function_def(inner)
2,123✔
902
        # annotation on a definition
903
        return esc(pushmeta!(ex, meta))
1,294✔
904
    else
905
        # annotation on a block
906
        return Expr(:block,
39✔
907
                    Expr(meta, true),
908
                    Expr(:local, Expr(:(=), :val, esc(ex))),
909
                    Expr(meta, false),
910
                    :val)
911
    end
912
end
913

914
function is_short_function_def(@nospecialize(ex))
1,707✔
915
    isexpr(ex, :(=)) || return false
1,765✔
916
    while length(ex.args) >= 1 && isa(ex.args[1], Expr)
3,786✔
917
        (ex.args[1].head === :call) && return true
3,784✔
918
        (ex.args[1].head === :where || ex.args[1].head === :(::)) || return false
253✔
919
        ex = ex.args[1]
488✔
920
    end
244✔
921
    return false
1✔
922
end
923
is_function_def(@nospecialize(ex)) =
4,534✔
924
    return isexpr(ex, :function) || is_short_function_def(ex) || isexpr(ex, :->)
925

926
function findmeta(ex::Expr)
1,491✔
927
    if is_function_def(ex)
2,350✔
928
        body = ex.args[2]::Expr
1,491✔
929
        body.head === :block || error(body, " is not a block expression")
1,491✔
930
        return findmeta_block(ex.args)
1,491✔
931
    end
932
    error(ex, " is not a function expression")
×
933
end
934

935
findmeta(ex::Array{Any,1}) = findmeta_block(ex)
×
936

937
function findmeta_block(exargs, argsmatch=args->true)
1,491✔
938
    for i = 1:length(exargs)
10,434✔
939
        a = exargs[i]
8,539✔
940
        if isa(a, Expr)
8,539✔
941
            if a.head === :meta && argsmatch(a.args)
5,418✔
942
                return i, exargs
105✔
943
            elseif a.head === :block
5,313✔
944
                idx, exa = findmeta_block(a.args, argsmatch)
1,491✔
945
                if idx != 0
1,491✔
946
                    return idx, exa
105✔
947
                end
948
            end
949
        end
950
    end
13,886✔
951
    return 0, []
2,772✔
952
end
953

954
remove_linenums!(ex) = ex
48✔
955
function remove_linenums!(ex::Expr)
70,533✔
956
    if ex.head === :block || ex.head === :quote
136,440✔
957
        # remove line number expressions from metadata (not argument literal or inert) position
958
        filter!(ex.args) do x
5,518✔
959
            isa(x, Expr) && x.head === :line && return false
26,824✔
960
            isa(x, LineNumberNode) && return false
26,824✔
961
            return true
13,909✔
962
        end
963
    end
964
    for subex in ex.args
70,884✔
965
        subex isa Expr && remove_linenums!(subex)
153,094✔
966
    end
223,276✔
967
    return ex
70,533✔
968
end
969
function remove_linenums!(src::CodeInfo)
694✔
970
    src.codelocs .= 0
41,096✔
971
    length(src.linetable) > 1 && resize!(src.linetable, 1)
694✔
972
    return src
694✔
973
end
974

975
replace_linenums!(ex, ln::LineNumberNode) = ex
×
976
function replace_linenums!(ex::Expr, ln::LineNumberNode)
1,680✔
977
    if ex.head === :block || ex.head === :quote
2,940✔
978
        # replace line number expressions from metadata (not argument literal or inert) position
979
        map!(ex.args, ex.args) do @nospecialize(x)
840✔
980
            isa(x, Expr) && x.head === :line && length(x.args) == 1 && return Expr(:line, ln.line)
840✔
981
            isa(x, Expr) && x.head === :line && length(x.args) == 2 && return Expr(:line, ln.line, ln.file)
840✔
982
            isa(x, LineNumberNode) && return ln
840✔
983
            return x
420✔
984
        end
985
    end
986
    # preserve any linenums inside `esc(...)` guards
987
    if ex.head !== :escape
1,680✔
988
        for subex in ex.args
1,680✔
989
            subex isa Expr && replace_linenums!(subex, ln)
1,680✔
990
        end
2,520✔
991
    end
992
    return ex
1,680✔
993
end
994

995
macro generated()
3✔
996
    return Expr(:generated)
3✔
997
end
998

999
"""
1000
    @generated f
1001

1002
`@generated` is used to annotate a function which will be generated.
1003
In the body of the generated function, only types of arguments can be read
1004
(not the values). The function returns a quoted expression evaluated when the
1005
function is called. The `@generated` macro should not be used on functions mutating
1006
the global scope or depending on mutable elements.
1007

1008
See [Metaprogramming](@ref) for further details.
1009

1010
# Examples
1011
```jldoctest
1012
julia> @generated function bar(x)
1013
           if x <: Integer
1014
               return :(x ^ 2)
1015
           else
1016
               return :(x)
1017
           end
1018
       end
1019
bar (generic function with 1 method)
1020

1021
julia> bar(4)
1022
16
1023

1024
julia> bar("baz")
1025
"baz"
1026
```
1027
"""
1028
macro generated(f)
47✔
1029
    if isa(f, Expr) && (f.head === :function || is_short_function_def(f))
62✔
1030
        body = f.args[2]
47✔
1031
        lno = body.args[1]
47✔
1032
        tmp = gensym("tmp")
47✔
1033
        return Expr(:escape,
47✔
1034
                    Expr(f.head, f.args[1],
1035
                         Expr(:block,
1036
                              lno,
1037
                              Expr(:if, Expr(:generated),
1038
                                   body,
1039
                                   Expr(:block,
1040
                                        Expr(:meta, :generated_only),
1041
                                        Expr(:return, nothing))))))
1042
    else
1043
        error("invalid syntax; @generated must be used with a function definition")
×
1044
    end
1045
end
1046

1047

1048
"""
1049
    @atomic var
1050
    @atomic order ex
1051

1052
Mark `var` or `ex` as being performed atomically, if `ex` is a supported expression.
1053
If no `order` is specified it defaults to :sequentially_consistent.
1054

1055
    @atomic a.b.x = new
1056
    @atomic a.b.x += addend
1057
    @atomic :release a.b.x = new
1058
    @atomic :acquire_release a.b.x += addend
1059

1060
Perform the store operation expressed on the right atomically and return the
1061
new value.
1062

1063
With `=`, this operation translates to a `setproperty!(a.b, :x, new)` call.
1064
With any operator also, this operation translates to a `modifyproperty!(a.b,
1065
:x, +, addend)[2]` call.
1066

1067
    @atomic a.b.x max arg2
1068
    @atomic a.b.x + arg2
1069
    @atomic max(a.b.x, arg2)
1070
    @atomic :acquire_release max(a.b.x, arg2)
1071
    @atomic :acquire_release a.b.x + arg2
1072
    @atomic :acquire_release a.b.x max arg2
1073

1074
Perform the binary operation expressed on the right atomically. Store the
1075
result into the field in the first argument and return the values `(old, new)`.
1076

1077
This operation translates to a `modifyproperty!(a.b, :x, func, arg2)` call.
1078

1079

1080
See [Per-field atomics](@ref man-atomics) section in the manual for more details.
1081

1082
# Examples
1083
```jldoctest
1084
julia> mutable struct Atomic{T}; @atomic x::T; end
1085

1086
julia> a = Atomic(1)
1087
Atomic{Int64}(1)
1088

1089
julia> @atomic a.x # fetch field x of a, with sequential consistency
1090
1
1091

1092
julia> @atomic :sequentially_consistent a.x = 2 # set field x of a, with sequential consistency
1093
2
1094

1095
julia> @atomic a.x += 1 # increment field x of a, with sequential consistency
1096
3
1097

1098
julia> @atomic a.x + 1 # increment field x of a, with sequential consistency
1099
3 => 4
1100

1101
julia> @atomic a.x # fetch field x of a, with sequential consistency
1102
4
1103

1104
julia> @atomic max(a.x, 10) # change field x of a to the max value, with sequential consistency
1105
4 => 10
1106

1107
julia> @atomic a.x max 5 # again change field x of a to the max value, with sequential consistency
1108
10 => 10
1109
```
1110

1111
!!! compat "Julia 1.7"
1112
    This functionality requires at least Julia 1.7.
1113
"""
1114
macro atomic(ex)
37✔
1115
    if !isa(ex, Symbol) && !is_expr(ex, :(::))
37✔
1116
        return make_atomic(QuoteNode(:sequentially_consistent), ex)
29✔
1117
    end
1118
    return esc(Expr(:atomic, ex))
8✔
1119
end
1120
macro atomic(order, ex)
17✔
1121
    order isa QuoteNode || (order = esc(order))
17✔
1122
    return make_atomic(order, ex)
17✔
1123
end
1124
macro atomic(a1, op, a2)
3✔
1125
    return make_atomic(QuoteNode(:sequentially_consistent), a1, op, a2)
3✔
1126
end
1127
macro atomic(order, a1, op, a2)
2✔
1128
    order isa QuoteNode || (order = esc(order))
2✔
1129
    return make_atomic(order, a1, op, a2)
2✔
1130
end
1131
function make_atomic(order, ex)
46✔
1132
    @nospecialize
×
1133
    if ex isa Expr
46✔
1134
        if isexpr(ex, :., 2)
75✔
1135
            l, r = esc(ex.args[1]), esc(ex.args[2])
17✔
1136
            return :(getproperty($l, $r, $order))
17✔
1137
        elseif isexpr(ex, :call, 3)
49✔
1138
            return make_atomic(order, ex.args[2], ex.args[1], ex.args[3])
5✔
1139
        elseif ex.head === :(=)
24✔
1140
            l, r = ex.args[1], esc(ex.args[2])
13✔
1141
            if is_expr(l, :., 2)
13✔
1142
                ll, lr = esc(l.args[1]), esc(l.args[2])
12✔
1143
                return :(setproperty!($ll, $lr, $r, $order))
12✔
1144
            end
1145
        end
1146
        if length(ex.args) == 2
12✔
1147
            if ex.head === :(+=)
9✔
1148
                op = :+
5✔
1149
            elseif ex.head === :(-=)
4✔
1150
                op = :-
1✔
1151
            elseif @isdefined string
×
1152
                shead = string(ex.head)
3✔
1153
                if endswith(shead, '=')
3✔
1154
                    op = Symbol(shead[1:prevind(shead, end)])
2✔
1155
                end
1156
            end
1157
            if @isdefined(op)
9✔
1158
                return Expr(:ref, make_atomic(order, ex.args[1], op, ex.args[2]), 2)
8✔
1159
            end
1160
        end
1161
    end
1162
    error("could not parse @atomic expression $ex")
4✔
1163
end
1164
function make_atomic(order, a1, op, a2)
18✔
1165
    @nospecialize
×
1166
    is_expr(a1, :., 2) || error("@atomic modify expression missing field access")
22✔
1167
    a1l, a1r, op, a2 = esc(a1.args[1]), esc(a1.args[2]), esc(op), esc(a2)
14✔
1168
    return :(modifyproperty!($a1l, $a1r, $op, $a2, $order))
14✔
1169
end
1170

1171

1172
"""
1173
    @atomicswap a.b.x = new
1174
    @atomicswap :sequentially_consistent a.b.x = new
1175

1176
Stores `new` into `a.b.x` and returns the old value of `a.b.x`.
1177

1178
This operation translates to a `swapproperty!(a.b, :x, new)` call.
1179

1180
See [Per-field atomics](@ref man-atomics) section in the manual for more details.
1181

1182
# Examples
1183
```jldoctest
1184
julia> mutable struct Atomic{T}; @atomic x::T; end
1185

1186
julia> a = Atomic(1)
1187
Atomic{Int64}(1)
1188

1189
julia> @atomicswap a.x = 2+2 # replace field x of a with 4, with sequential consistency
1190
1
1191

1192
julia> @atomic a.x # fetch field x of a, with sequential consistency
1193
4
1194
```
1195

1196
!!! compat "Julia 1.7"
1197
    This functionality requires at least Julia 1.7.
1198
"""
1199
macro atomicswap(order, ex)
2✔
1200
    order isa QuoteNode || (order = esc(order))
2✔
1201
    return make_atomicswap(order, ex)
2✔
1202
end
1203
macro atomicswap(ex)
1✔
1204
    return make_atomicswap(QuoteNode(:sequentially_consistent), ex)
1✔
1205
end
1206
function make_atomicswap(order, ex)
3✔
1207
    @nospecialize
×
1208
    is_expr(ex, :(=), 2) || error("@atomicswap expression missing assignment")
3✔
1209
    l, val = ex.args[1], esc(ex.args[2])
3✔
1210
    is_expr(l, :., 2) || error("@atomicswap expression missing field access")
3✔
1211
    ll, lr = esc(l.args[1]), esc(l.args[2])
3✔
1212
    return :(swapproperty!($ll, $lr, $val, $order))
3✔
1213
end
1214

1215

1216
"""
1217
    @atomicreplace a.b.x expected => desired
1218
    @atomicreplace :sequentially_consistent a.b.x expected => desired
1219
    @atomicreplace :sequentially_consistent :monotonic a.b.x expected => desired
1220

1221
Perform the conditional replacement expressed by the pair atomically, returning
1222
the values `(old, success::Bool)`. Where `success` indicates whether the
1223
replacement was completed.
1224

1225
This operation translates to a `replaceproperty!(a.b, :x, expected, desired)` call.
1226

1227
See [Per-field atomics](@ref man-atomics) section in the manual for more details.
1228

1229
# Examples
1230
```jldoctest
1231
julia> mutable struct Atomic{T}; @atomic x::T; end
1232

1233
julia> a = Atomic(1)
1234
Atomic{Int64}(1)
1235

1236
julia> @atomicreplace a.x 1 => 2 # replace field x of a with 2 if it was 1, with sequential consistency
1237
(old = 1, success = true)
1238

1239
julia> @atomic a.x # fetch field x of a, with sequential consistency
1240
2
1241

1242
julia> @atomicreplace a.x 1 => 2 # replace field x of a with 2 if it was 1, with sequential consistency
1243
(old = 2, success = false)
1244

1245
julia> xchg = 2 => 0; # replace field x of a with 0 if it was 2, with sequential consistency
1246

1247
julia> @atomicreplace a.x xchg
1248
(old = 2, success = true)
1249

1250
julia> @atomic a.x # fetch field x of a, with sequential consistency
1251
0
1252
```
1253

1254
!!! compat "Julia 1.7"
1255
    This functionality requires at least Julia 1.7.
1256
"""
1257
macro atomicreplace(success_order, fail_order, ex, old_new)
4✔
1258
    fail_order isa QuoteNode || (fail_order = esc(fail_order))
4✔
1259
    success_order isa QuoteNode || (success_order = esc(success_order))
4✔
1260
    return make_atomicreplace(success_order, fail_order, ex, old_new)
4✔
1261
end
1262
macro atomicreplace(order, ex, old_new)
4✔
1263
    order isa QuoteNode || (order = esc(order))
4✔
1264
    return make_atomicreplace(order, order, ex, old_new)
4✔
1265
end
1266
macro atomicreplace(ex, old_new)
6✔
1267
    return make_atomicreplace(QuoteNode(:sequentially_consistent), QuoteNode(:sequentially_consistent), ex, old_new)
6✔
1268
end
1269
function make_atomicreplace(success_order, fail_order, ex, old_new)
14✔
1270
    @nospecialize
×
1271
    is_expr(ex, :., 2) || error("@atomicreplace expression missing field access")
15✔
1272
    ll, lr = esc(ex.args[1]), esc(ex.args[2])
13✔
1273
    if is_expr(old_new, :call, 3) && old_new.args[1] === :(=>)
13✔
1274
        exp, rep = esc(old_new.args[2]), esc(old_new.args[3])
8✔
1275
        return :(replaceproperty!($ll, $lr, $exp, $rep, $success_order, $fail_order))
8✔
1276
    else
1277
        old_new = esc(old_new)
5✔
1278
        return :(replaceproperty!($ll, $lr, $old_new::Pair..., $success_order, $fail_order))
5✔
1279
    end
1280
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc