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

JuliaLang / julia / #37851

27 Jul 2024 03:03AM UTC coverage: 86.618% (-1.0%) from 87.596%
#37851

push

local

web-flow
Make `jl_*affinity` tests more portable (#55261)

Changes made:
- Use 0 for the thread ID to ensure it's always valid. The function
expects `0 <= tid < jl_n_threads` so 1 is incorrect if `jl_n_threads` is
1.
- After retrieving the affinity mask with `jl_getaffinity`, pass that
same mask back to `jl_setaffinity`. This ensures that the mask is always
valid. Using a mask of all ones results in `EINVAL` on FreeBSD. Based on
the discussion in #53402, this change may also fix Windows, so I've
tried reenabling it here.
- To check whether `jl_getaffinity` actually did something, we can check
that the mask is no longer all zeros after the call.

Fixes #54817

76775 of 88636 relevant lines covered (86.62%)

15433139.0 hits per line

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

85.99
/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)
2,928✔
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}, ())
354✔
15

16
gensym(s::String) = ccall(:jl_tagged_gensym, Ref{Symbol}, (Ptr{UInt8}, Csize_t), s, sizeof(s))
153✔
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)
46✔
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...)
28
    blk = Expr(:block)
29
    for name in names
30
        push!(blk.args, :($(esc(name)) = gensym($(string(name)))))
31
    end
32
    push!(blk.args, :nothing)
33
    return blk
34
end
35

36
## expressions ##
37

38
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === head
2,147,483,647✔
39
isexpr(@nospecialize(ex), head::Symbol, n::Int) = isa(ex, Expr) && ex.head === head && length(ex.args) == n
51,276✔
40

41
copy(e::Expr) = exprarray(e.head, copy_exprargs(e.args))
192,406,265✔
42

43
# copy parts of an AST that the compiler mutates
44
function copy_exprs(@nospecialize(x))
571,140,733✔
45
    if isa(x, Expr)
571,140,733✔
46
        return copy(x)
192,389,729✔
47
    elseif isa(x, PhiNode)
378,751,004✔
48
        values = x.values
2,465,957✔
49
        nvalues = length(values)
2,465,957✔
50
        new_values = Vector{Any}(undef, nvalues)
4,931,881✔
51
        @inbounds for i = 1:nvalues
2,465,957✔
52
            isassigned(values, i) || continue
4,747,009✔
53
            new_values[i] = copy_exprs(values[i])
4,746,989✔
54
        end
7,028,094✔
55
        return PhiNode(copy(x.edges), new_values)
2,465,957✔
56
    elseif isa(x, PhiCNode)
376,285,047✔
57
        values = x.values
10✔
58
        nvalues = length(values)
10✔
59
        new_values = Vector{Any}(undef, nvalues)
20✔
60
        @inbounds for i = 1:nvalues
10✔
61
            isassigned(values, i) || continue
15✔
62
            new_values[i] = copy_exprs(values[i])
15✔
63
        end
20✔
64
        return PhiCNode(new_values)
10✔
65
    end
66
    return x
376,285,037✔
67
end
68
copy_exprargs(x::Array{Any,1}) = Any[copy_exprs(@inbounds x[i]) for i in eachindex(x)]
485,355,823✔
69

70
@eval exprarray(head::Symbol, arg::Array{Any,1}) = $(Expr(:new, :Expr, :head, :arg))
192,408,277✔
71

72
# create copies of the CodeInfo definition, and any mutable fields
73
function copy(c::CodeInfo)
8,462,605✔
74
    cnew = ccall(:jl_copy_code_info, Ref{CodeInfo}, (Any,), c)
8,462,605✔
75
    cnew.code = copy_exprargs(cnew.code)
175,534,351✔
76
    cnew.slotnames = copy(cnew.slotnames)
8,462,605✔
77
    cnew.slotflags = copy(cnew.slotflags)
8,462,605✔
78
    if cnew.slottypes !== nothing
8,462,605✔
79
        cnew.slottypes = copy(cnew.slottypes::Vector{Any})
5,854,052✔
80
    end
81
    cnew.ssaflags  = copy(cnew.ssaflags)
8,462,605✔
82
    cnew.edges     = cnew.edges === nothing ? nothing : copy(cnew.edges::Vector)
8,462,681✔
83
    ssavaluetypes  = cnew.ssavaluetypes
8,462,605✔
84
    ssavaluetypes isa Vector{Any} && (cnew.ssavaluetypes = copy(ssavaluetypes))
8,462,605✔
85
    return cnew
8,462,605✔
86
end
87

88

89
==(x::Expr, y::Expr) = x.head === y.head && isequal(x.args, y.args)
13,678✔
90
==(x::QuoteNode, y::QuoteNode) = isequal(x.value, y.value)
1,523✔
91
==(stmt1::Core.PhiNode, stmt2::Core.PhiNode) = stmt1.edges == stmt2.edges && stmt1.values == stmt2.values
×
92

93
"""
94
    macroexpand(m::Module, x; recursive=true)
95

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

111
julia> macroexpand(M, :(@m2()), recursive=true)
112
42
113

114
julia> macroexpand(M, :(@m2()), recursive=false)
115
:(#= REPL[16]:6 =# M.@m1)
116
```
117
"""
118
function macroexpand(m::Module, @nospecialize(x); recursive=true)
289✔
119
    if recursive
28✔
120
        ccall(:jl_macroexpand, Any, (Any, Any), x, m)
288✔
121
    else
122
        ccall(:jl_macroexpand1, Any, (Any, Any), x, m)
1✔
123
    end
124
end
125

126
"""
127
    @macroexpand [mod,] ex
128

129
Return equivalent expression with all macros removed (expanded).
130
If two arguments are provided, the first is the module to evaluate in.
131

132
There are differences between `@macroexpand` and [`macroexpand`](@ref).
133

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

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

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

155
julia> macro m()
156
           2
157
       end
158
@m (macro with 1 method)
159

160
julia> M.f()
161
(1, 1, 2)
162
```
163
With `@macroexpand` the expression expands where `@macroexpand` appears in the code (module `M` in the example).
164
With `macroexpand` the expression expands in the module given as the first argument.
165

166
!!! compat "Julia 1.11"
167
    The two-argument form requires at least Julia 1.11.
168
"""
169
macro macroexpand(code)
29✔
170
    return :(macroexpand($__module__, $(QuoteNode(code)), recursive=true))
29✔
171
end
172
macro macroexpand(mod, code)
173
    return :(macroexpand($(esc(mod)), $(QuoteNode(code)), recursive=true))
174
end
175

176
"""
177
    @macroexpand1 [mod,] ex
178

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

188
## misc syntax ##
189

190
"""
191
    Core.eval(m::Module, expr)
192

193
Evaluate an expression in the given module and return the result.
194
"""
195
Core.eval
196

197
"""
198
    @inline
199

200
Give a hint to the compiler that this function is worth inlining.
201

202
Small functions typically do not need the `@inline` annotation,
203
as the compiler does it automatically. By using `@inline` on bigger functions,
204
an extra nudge can be given to the compiler to inline it.
205

206
`@inline` can be applied immediately before a function definition or within a function body.
207

208
```julia
209
# annotate long-form definition
210
@inline function longdef(x)
211
    ...
212
end
213

214
# annotate short-form definition
215
@inline shortdef(x) = ...
216

217
# annotate anonymous function that a `do` block creates
218
f() do
219
    @inline
220
    ...
221
end
222
```
223

224
!!! compat "Julia 1.8"
225
    The usage within a function body requires at least Julia 1.8.
226

227
---
228
    @inline block
229

230
Give a hint to the compiler that calls within `block` are worth inlining.
231

232
```julia
233
# The compiler will try to inline `f`
234
@inline f(...)
235

236
# The compiler will try to inline `f`, `g` and `+`
237
@inline f(...) + g(...)
238
```
239

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

248
    let
249
        @inline explicit_noinline(args...) # will be inlined
250
    end
251
    ```
252

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

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

268
!!! compat "Julia 1.8"
269
    The callsite annotation requires at least Julia 1.8.
270
"""
271
macro inline(x)
1,430✔
272
    return annotate_meta_def_or_block(x, :inline)
1,430✔
273
end
274

275
"""
276
    @noinline
277

278
Give a hint to the compiler that it should not inline a function.
279

280
Small functions are typically inlined automatically.
281
By using `@noinline` on small functions, auto-inlining can be
282
prevented.
283

284
`@noinline` can be applied immediately before a function definition or within a function body.
285

286
```julia
287
# annotate long-form definition
288
@noinline function longdef(x)
289
    ...
290
end
291

292
# annotate short-form definition
293
@noinline shortdef(x) = ...
294

295
# annotate anonymous function that a `do` block creates
296
f() do
297
    @noinline
298
    ...
299
end
300
```
301

302
!!! compat "Julia 1.8"
303
    The usage within a function body requires at least Julia 1.8.
304

305
---
306
    @noinline block
307

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

310
```julia
311
# The compiler will try to not inline `f`
312
@noinline f(...)
313

314
# The compiler will try to not inline `f`, `g` and `+`
315
@noinline f(...) + g(...)
316
```
317

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

326
    let
327
        @noinline explicit_inline(args...) # will not be inlined
328
    end
329
    ```
330

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

341
!!! compat "Julia 1.8"
342
    The callsite annotation requires at least Julia 1.8.
343

344
---
345
!!! note
346
    If the function is trivial (for example returning a constant) it might get inlined anyway.
347
"""
348
macro noinline(x)
294✔
349
    return annotate_meta_def_or_block(x, :noinline)
294✔
350
end
351

352
"""
353
    Base.@constprop setting [ex]
354

355
Control the mode of interprocedural constant propagation for the annotated function.
356

357
Two `setting`s are supported:
358

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

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

368
```julia
369
# annotate long-form definition
370
Base.@constprop :aggressive function longdef(x)
371
    ...
372
end
373

374
# annotate short-form definition
375
Base.@constprop :aggressive shortdef(x) = ...
376

377
# annotate anonymous function that a `do` block creates
378
f() do
379
    Base.@constprop :aggressive
380
    ...
381
end
382
```
383

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

397
function constprop_setting(@nospecialize setting)
47✔
398
    isa(setting, QuoteNode) && (setting = setting.value)
47✔
399
    if setting === :aggressive
47✔
400
        return :aggressive_constprop
34✔
401
    elseif setting === :none
13✔
402
        return :no_constprop
13✔
403
    end
404
    throw(ArgumentError(LazyString("@constprop "), setting, "not supported"))
×
405
end
406

407
"""
408
    Base.@assume_effects setting... [ex]
409

410
Override the compiler's effect modeling.
411
This macro can be used in several contexts:
412
1. Immediately before a method definition, to override the entire effect modeling of the applied method.
413
2. Within a function body without any arguments, to override the entire effect modeling of the enclosing method.
414
3. Applied to a code block, to override the local effect modeling of the applied code block.
415

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

431
julia> code_typed() do
432
           fact(12)
433
       end |> only
434
CodeInfo(
435
1 ─     return 479001600
436
) => Int64
437

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

456
julia> code_typed() do
457
           map((2,3,4)) do x
458
               res = 1
459
               0 ≤ x < 20 || error("bad fact")
460
               # usage 3:
461
               # with this :terminates_locally annotation the compiler skips tainting
462
               # `:terminates` effect within this `while` block, allowing the parent
463
               # anonymous function to be constant-folded
464
               Base.@assume_effects :terminates_locally while x > 1
465
                   res *= x
466
                   x -= 1
467
               end
468
               return res
469
           end
470
       end |> only
471
CodeInfo(
472
1 ─     return (2, 6, 24)
473
) => Tuple{Int64, Int64, Int64}
474
```
475

476
!!! compat "Julia 1.8"
477
    Using `Base.@assume_effects` requires Julia version 1.8.
478

479
!!! compat "Julia 1.10"
480
    The usage within a function body requires at least Julia 1.10.
481

482
!!! compat "Julia 1.11"
483
    The code block annotation requires at least Julia 1.11.
484

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

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

498
The following `setting`s are supported.
499
- `:consistent`
500
- `:effect_free`
501
- `:nothrow`
502
- `:terminates_globally`
503
- `:terminates_locally`
504
- `:notaskstate`
505
- `:inaccessiblememonly`
506
- `:noub`
507
- `:noub_if_noinbounds`
508
- `:foldable`
509
- `:removable`
510
- `:total`
511

512
# Extended help
513

514
---
515
## `:consistent`
516

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

521
!!! note
522
    This in particular implies that the method must not return a freshly allocated
523
    mutable object. Multiple allocations of mutable objects (even with identical
524
    contents) are not egal.
525

526
!!! note
527
    The `:consistent`-cy assertion is made world-age wise. More formally, write
528
    ``fáµ¢`` for the evaluation of ``f`` in world-age ``i``, then this setting requires:
529
    ```math
530
    ∀ i, x, y: x ≡ y → fᵢ(x) ≡ fᵢ(y)
531
    ```
532
    However, for two world ages ``i``, ``j`` s.t. ``i ≠ j``, we may have ``fᵢ(x) ≢ fⱼ(y)``.
533

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

538
!!! note
539
    The `:consistent`-cy includes all legal rewrites performed by the optimizer.
540
    For example, floating-point fastmath operations are not considered `:consistent`,
541
    because the optimizer may rewrite them causing the output to not be `:consistent`,
542
    even for the same world age (e.g. because one ran in the interpreter, while
543
    the other was optimized).
544

545
!!! note
546
    If `:consistent` functions terminate by throwing an exception, that exception
547
    itself is not required to meet the egality requirement specified above.
548

549
---
550
## `:effect_free`
551

552
The `:effect_free` setting asserts that the method is free of externally semantically
553
visible side effects. The following is an incomplete list of externally semantically
554
visible side effects:
555
- Changing the value of a global variable.
556
- Mutating the heap (e.g. an array or mutable value), except as noted below
557
- Changing the method table (e.g. through calls to eval)
558
- File/Network/etc. I/O
559
- Task switching
560

561
However, the following are explicitly not semantically visible, even if they
562
may be observable:
563
- Memory allocations (both mutable and immutable)
564
- Elapsed time
565
- Garbage collection
566
- Heap mutations of objects whose lifetime does not exceed the method (i.e.
567
  were allocated in the method and do not escape).
568
- The returned value (which is externally visible, but not a side effect)
569

570
The rule of thumb here is that an externally visible side effect is anything
571
that would affect the execution of the remainder of the program if the function
572
were not executed.
573

574
!!! note
575
    The `:effect_free` assertion is made both for the method itself and any code
576
    that is executed by the method. Keep in mind that the assertion must be
577
    valid for all world ages and limit use of this assertion accordingly.
578

579
---
580
## `:nothrow`
581

582
The `:nothrow` settings asserts that this method does not throw an exception
583
(i.e. will either always return a value or never return).
584

585
!!! note
586
    It is permissible for `:nothrow` annotated methods to make use of exception
587
    handling internally as long as the exception is not rethrown out of the
588
    method itself.
589

590
!!! note
591
    If the execution of a method may raise `MethodError`s and similar exceptions, then
592
    the method is not considered as `:nothrow`.
593
    However, note that environment-dependent errors like `StackOverflowError` or `InterruptException`
594
    are not modeled by this effect and thus a method that may result in `StackOverflowError`
595
    does not necessarily need to be `!:nothrow` (although it should usually be `!:terminates` too).
596

597
---
598
## `:terminates_globally`
599

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

603
!!! note
604
    This `:terminates_globally` assertion covers any other methods called by the annotated method.
605

606
!!! note
607
    The compiler will consider this a strong indication that the method will
608
    terminate relatively *quickly* and may (if otherwise legal) call this
609
    method at compile time. I.e. it is a bad idea to annotate this setting
610
    on a method that *technically*, but not *practically*, terminates.
611

612
---
613
## `:terminates_locally`
614

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

620
!!! note
621
    `:terminates_globally` implies `:terminates_locally`.
622

623
---
624
## `:notaskstate`
625

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

630
!!! note
631
    The implementation of exception handling makes use of state stored in the
632
    task object. However, this state is currently not considered to be within
633
    the scope of `:notaskstate` and is tracked separately using the `:nothrow`
634
    effect.
635

636
!!! note
637
    The `:notaskstate` assertion concerns the state of the *currently running task*.
638
    If a reference to a `Task` object is obtained by some other means that
639
    does not consider which task is *currently* running, the `:notaskstate`
640
    effect need not be tainted. This is true, even if said task object happens
641
    to be `===` to the currently running task.
642

643
!!! note
644
    Access to task state usually also results in the tainting of other effects,
645
    such as `:effect_free` (if task state is modified) or `:consistent` (if
646
    task state is used in the computation of the result). In particular,
647
    code that is not `:notaskstate`, but is `:effect_free` and `:consistent`
648
    may still be dead-code-eliminated and thus promoted to `:total`.
649

650
---
651
## `:inaccessiblememonly`
652

653
The `:inaccessiblememonly` setting asserts that the method does not access or modify
654
externally accessible mutable memory. This means the method can access or modify mutable
655
memory for newly allocated objects that is not accessible by other methods or top-level
656
execution before return from the method, but it can not access or modify any mutable
657
global state or mutable memory pointed to by its arguments.
658

659
!!! note
660
    Below is an incomplete list of examples that invalidate this assumption:
661
    - a global reference or `getglobal` call to access a mutable global variable
662
    - a global assignment or `setglobal!` call to perform assignment to a non-constant global variable
663
    - `setfield!` call that changes a field of a global mutable variable
664

665
!!! note
666
    This `:inaccessiblememonly` assertion covers any other methods called by the annotated method.
667

668
---
669
## `:noub`
670

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

676
---
677
## `:foldable`
678

679
This setting is a convenient shortcut for the set of effects that the compiler
680
requires to be guaranteed to constant fold a call at compile time. It is
681
currently equivalent to the following `setting`s:
682
- `:consistent`
683
- `:effect_free`
684
- `:terminates_globally`
685
- `:noub`
686

687
!!! note
688
    This list in particular does not include `:nothrow`. The compiler will still
689
    attempt constant propagation and note any thrown error at compile time. Note
690
    however, that by the `:consistent`-cy requirements, any such annotated call
691
    must consistently throw given the same argument values.
692

693
!!! note
694
    An explicit `@inbounds` annotation inside the function will also disable
695
    constant folding and not be overridden by `:foldable`.
696

697
---
698
## `:removable`
699

700
This setting is a convenient shortcut for the set of effects that the compiler
701
requires to be guaranteed to delete a call whose result is unused at compile time.
702
It is currently equivalent to the following `setting`s:
703
- `:effect_free`
704
- `:nothrow`
705
- `:terminates_globally`
706

707
---
708
## `:total`
709

710
This `setting` is the maximum possible set of effects. It currently implies
711
the following other `setting`s:
712
- `:consistent`
713
- `:effect_free`
714
- `:nothrow`
715
- `:terminates_globally`
716
- `:notaskstate`
717
- `:inaccessiblememonly`
718
- `:noub`
719

720
!!! warning
721
    `:total` is a very strong assertion and will likely gain additional semantics
722
    in future versions of Julia (e.g. if additional effects are added and included
723
    in the definition of `:total`). As a result, it should be used with care.
724
    Whenever possible, prefer to use the minimum possible set of specific effect
725
    assertions required for a particular application. In cases where a large
726
    number of effect overrides apply to a set of functions, a custom macro is
727
    recommended over the use of `:total`.
728

729
---
730
## Negated effects
731

732
Effect names may be prefixed by `!` to indicate that the effect should be removed
733
from an earlier meta effect. For example, `:total !:nothrow` indicates that while
734
the call is generally total, it may however throw.
735
"""
736
macro assume_effects(args...)
42✔
737
    lastex = args[end]
42✔
738
    override = compute_assumed_settings(args[begin:end-1])
42✔
739
    if is_function_def(unwrap_macrocalls(lastex))
42✔
740
        return esc(pushmeta!(lastex::Expr, form_purity_expr(override)))
31✔
741
    elseif isexpr(lastex, :macrocall) && lastex.args[1] === Symbol("@ccall")
11✔
742
        lastex.args[1] = GlobalRef(Base, Symbol("@ccall_effects"))
2✔
743
        insert!(lastex.args, 3, Core.Compiler.encode_effects_override(override))
2✔
744
        return esc(lastex)
2✔
745
    end
746
    override′ = compute_assumed_setting(override, lastex)
9✔
747
    if override′ !== nothing
9✔
748
        # anonymous function case
749
        return Expr(:meta, form_purity_expr(override′))
3✔
750
    else
751
        # call site annotation case
752
        return Expr(:block,
6✔
753
                    form_purity_expr(override),
754
                    Expr(:local, Expr(:(=), :val, esc(lastex))),
755
                    Expr(:purity), # region end token
756
                    :val)
757
    end
758
end
759

760
function compute_assumed_settings(settings)
42✔
761
    override = EffectsOverride()
1✔
762
    for setting in settings
42✔
763
        override = compute_assumed_setting(override, setting)
45✔
764
        override === nothing &&
45✔
765
            throw(ArgumentError("`@assume_effects $setting` not supported"))
766
    end
10✔
767
    return override
42✔
768
end
769

770
using Core.Compiler: EffectsOverride
771

772
function compute_assumed_setting(override::EffectsOverride, @nospecialize(setting), val::Bool=true)
69✔
773
    if isexpr(setting, :call) && setting.args[1] === :(!)
115✔
774
        return compute_assumed_setting(override, setting.args[2], !val)
3✔
775
    elseif isa(setting, QuoteNode)
58✔
776
        return compute_assumed_setting(override, setting.value, val)
48✔
777
    end
778
    if setting === :consistent
54✔
779
        return EffectsOverride(override; consistent = val)
1✔
780
    elseif setting === :effect_free
53✔
781
        return EffectsOverride(override; effect_free = val)
4✔
782
    elseif setting === :nothrow
49✔
783
        return EffectsOverride(override; nothrow = val)
8✔
784
    elseif setting === :terminates_globally
41✔
785
        return EffectsOverride(override; terminates_globally = val)
7✔
786
    elseif setting === :terminates_locally
34✔
787
        return EffectsOverride(override; terminates_locally = val)
5✔
788
    elseif setting === :notaskstate
29✔
789
        return EffectsOverride(override; notaskstate = val)
2✔
790
    elseif setting === :inaccessiblememonly
27✔
791
        return EffectsOverride(override; inaccessiblememonly = val)
×
792
    elseif setting === :noub
27✔
793
        return EffectsOverride(override; noub = val)
1✔
794
    elseif setting === :noub_if_noinbounds
26✔
795
        return EffectsOverride(override; noub_if_noinbounds = val)
×
796
    elseif setting === :foldable
26✔
797
        consistent = effect_free = terminates_globally = noub = val
×
798
        return EffectsOverride(override; consistent, effect_free, terminates_globally, noub)
11✔
799
    elseif setting === :removable
15✔
800
        effect_free = nothrow = terminates_globally = val
×
801
        return EffectsOverride(override; effect_free, nothrow, terminates_globally)
2✔
802
    elseif setting === :total
13✔
803
        consistent = effect_free = nothrow = terminates_globally = notaskstate =
×
804
            inaccessiblememonly = noub = val
805
        return EffectsOverride(override;
7✔
806
            consistent, effect_free, nothrow, terminates_globally, notaskstate,
807
            inaccessiblememonly, noub)
808
    end
809
    return nothing
6✔
810
end
811

812
function form_purity_expr(override::EffectsOverride)
43✔
813
    ex = Expr(:purity)
43✔
814
    for i = 1:Core.Compiler.NUM_EFFECTS_OVERRIDES
430✔
815
        push!(ex.args, getfield(override, i))
430✔
816
    end
817✔
817
    return ex
43✔
818
end
819

820
"""
821
    Base.@nospecializeinfer function f(args...)
822
        @nospecialize ...
823
        ...
824
    end
825
    Base.@nospecializeinfer f(@nospecialize args...) = ...
826

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

830
# Examples
831

832
```julia
833
julia> f(A::AbstractArray) = g(A)
834
f (generic function with 1 method)
835

836
julia> @noinline Base.@nospecializeinfer g(@nospecialize(A::AbstractArray)) = A[1]
837
g (generic function with 1 method)
838

839
julia> @code_typed f([1.0])
840
CodeInfo(
841
1 ─ %1 = invoke Main.g(_2::AbstractArray)::Any
842
└──      return %1
843
) => Any
844
```
845

846
In this example, `f` will be inferred for each specific type of `A`,
847
but `g` will only be inferred once with the declared argument type `A::AbstractArray`,
848
meaning that the compiler will not likely see the excessive inference time on it
849
while it can not infer the concrete return type of it.
850
Without the `@nospecializeinfer`, `f([1.0])` would infer the return type of `g` as `Float64`,
851
indicating that inference ran for `g(::Vector{Float64})` despite the prohibition on
852
specialized code generation.
853

854
!!! compat "Julia 1.10"
855
    Using `Base.@nospecializeinfer` requires Julia version 1.10.
856
"""
857
macro nospecializeinfer(ex)
4✔
858
    esc(isa(ex, Expr) ? pushmeta!(ex, :nospecializeinfer) : ex)
4✔
859
end
860

861
"""
862
    @propagate_inbounds
863

864
Tells the compiler to inline a function while retaining the caller's inbounds context.
865
"""
866
macro propagate_inbounds(ex)
101✔
867
    if isa(ex, Expr)
101✔
868
        pushmeta!(ex, :inline)
101✔
869
        pushmeta!(ex, :propagate_inbounds)
101✔
870
    end
871
    esc(ex)
101✔
872
end
873

874
"""
875
    @polly
876

877
Tells the compiler to apply the polyhedral optimizer Polly to a function.
878
"""
879
macro polly(ex)
880
    esc(isa(ex, Expr) ? pushmeta!(ex, :polly) : ex)
881
end
882

883
## some macro utilities ##
884

885
unwrap_macrocalls(@nospecialize(x)) = x
1✔
886
function unwrap_macrocalls(ex::Expr)
58✔
887
    inner = ex
×
888
    while inner.head === :macrocall
3,809✔
889
        inner = inner.args[end]::Expr
77✔
890
    end
77✔
891
    return inner
3,732✔
892
end
893

894
function pushmeta!(ex::Expr, tag::Union{Symbol,Expr})
1,950✔
895
    inner = unwrap_macrocalls(ex)
1,998✔
896
    idx, exargs = findmeta(inner)
1,950✔
897
    if idx != 0
1,950✔
898
        metastmt = exargs[idx]::Expr
147✔
899
        push!(metastmt.args, tag)
147✔
900
    else
901
        body = inner.args[2]::Expr
1,803✔
902
        pushfirst!(body.args, Expr(:meta, tag))
1,803✔
903
    end
904
    return ex
1,950✔
905
end
906

907
popmeta!(body, sym) = _getmeta(body, sym, true)
×
908
peekmeta(body, sym) = _getmeta(body, sym, false)
×
909

910
function _getmeta(body::Expr, sym::Symbol, delete::Bool)
×
911
    body.head === :block || return false, []
×
912
    _getmeta(body.args, sym, delete)
×
913
end
914
_getmeta(arg, sym, delete::Bool) = (false, [])
×
915
function _getmeta(body::Array{Any,1}, sym::Symbol, delete::Bool)
×
916
    idx, blockargs = findmeta_block(body, args -> findmetaarg(args,sym)!=0)
×
917
    if idx == 0
×
918
        return false, []
×
919
    end
920
    metaargs = blockargs[idx].args
×
921
    i = findmetaarg(blockargs[idx].args, sym)
×
922
    if i == 0
×
923
        return false, []
×
924
    end
925
    ret = isa(metaargs[i], Expr) ? (metaargs[i]::Expr).args : []
×
926
    if delete
×
927
        deleteat!(metaargs, i)
×
928
        isempty(metaargs) && deleteat!(blockargs, idx)
×
929
    end
930
    true, ret
×
931
end
932

933
# Find index of `sym` in a meta expression argument list, or 0.
934
function findmetaarg(metaargs, sym)
×
935
    for i = 1:length(metaargs)
×
936
        arg = metaargs[i]
×
937
        if (isa(arg, Symbol) && (arg::Symbol)    == sym) ||
×
938
           (isa(arg, Expr)   && (arg::Expr).head == sym)
939
            return i
×
940
        end
941
    end
×
942
    return 0
×
943
end
944

945
function annotate_meta_def_or_block(@nospecialize(ex), meta::Symbol)
1,724✔
946
    inner = unwrap_macrocalls(ex)
1,724✔
947
    if is_function_def(inner)
2,733✔
948
        # annotation on a definition
949
        return esc(pushmeta!(ex, meta))
1,664✔
950
    else
951
        # annotation on a block
952
        return Expr(:block,
60✔
953
                    Expr(meta, true),
954
                    Expr(:local, Expr(:(=), :val, esc(ex))),
955
                    Expr(meta, false),
956
                    :val)
957
    end
958
end
959

960
function is_short_function_def(@nospecialize(ex))
2,167✔
961
    isexpr(ex, :(=)) || return false
2,247✔
962
    while length(ex.args) >= 1 && isa(ex.args[1], Expr)
4,788✔
963
        (ex.args[1].head === :call) && return true
4,786✔
964
        (ex.args[1].head === :where || ex.args[1].head === :(::)) || return false
316✔
965
        ex = ex.args[1]
614✔
966
    end
307✔
967
    return false
1✔
968
end
969
is_function_def(@nospecialize(ex)) =
5,844✔
970
    return isexpr(ex, :function) || is_short_function_def(ex) || isexpr(ex, :->)
971

972
function findmeta(ex::Expr)
1,950✔
973
    if is_function_def(ex)
3,052✔
974
        body = ex.args[2]::Expr
1,950✔
975
        body.head === :block || error(body, " is not a block expression")
1,950✔
976
        return findmeta_block(ex.args)
1,950✔
977
    end
978
    error(ex, " is not a function expression")
×
979
end
980

981
findmeta(ex::Array{Any,1}) = findmeta_block(ex)
×
982

983
function findmeta_block(exargs, argsmatch=args->true)
1,950✔
984
    for i = 1:length(exargs)
9,747✔
985
        a = exargs[i]
11,493✔
986
        if isa(a, Expr)
11,493✔
987
            if a.head === :meta && argsmatch(a.args)
7,235✔
988
                return i, exargs
147✔
989
            elseif a.head === :block
7,088✔
990
                idx, exa = findmeta_block(a.args, argsmatch)
1,950✔
991
                if idx != 0
1,950✔
992
                    return idx, exa
147✔
993
                end
994
            end
995
        end
996
    end
18,792✔
997
    return 0, []
3,606✔
998
end
999

1000
"""
1001
    Base.remove_linenums!(ex)
1002

1003
Remove all line-number metadata from expression-like object `ex`.
1004
"""
1005
function remove_linenums!(@nospecialize ex)
74,005✔
1006
    if ex isa Expr
74,005✔
1007
        if ex.head === :block || ex.head === :quote
141,372✔
1008
            # remove line number expressions from metadata (not argument literal or inert) position
1009
            filter!(ex.args) do x
11,218✔
1010
                isa(x, Expr) && x.head === :line && return false
28,036✔
1011
                isa(x, LineNumberNode) && return false
28,036✔
1012
                return true
14,529✔
1013
            end
1014
        end
1015
        for subex in ex.args
73,299✔
1016
            subex isa Expr && remove_linenums!(subex)
156,290✔
1017
        end
156,290✔
1018
    elseif ex isa CodeInfo
706✔
1019
        ex.debuginfo = Core.DebugInfo(ex.debuginfo.def) # TODO: filter partially, but keep edges
658✔
1020
    end
1021
    return ex
74,005✔
1022
end
1023

1024
replace_linenums!(ex, ln::LineNumberNode) = ex
×
1025
function replace_linenums!(ex::Expr, ln::LineNumberNode)
772✔
1026
    if ex.head === :block || ex.head === :quote
1,351✔
1027
        # replace line number expressions from metadata (not argument literal or inert) position
1028
        map!(ex.args, ex.args) do @nospecialize(x)
386✔
1029
            isa(x, Expr) && x.head === :line && length(x.args) == 1 && return Expr(:line, ln.line)
386✔
1030
            isa(x, Expr) && x.head === :line && length(x.args) == 2 && return Expr(:line, ln.line, ln.file)
386✔
1031
            isa(x, LineNumberNode) && return ln
386✔
1032
            return x
193✔
1033
        end
1034
    end
1035
    # preserve any linenums inside `esc(...)` guards
1036
    if ex.head !== :escape
772✔
1037
        for subex in ex.args
579✔
1038
            subex isa Expr && replace_linenums!(subex, ln)
772✔
1039
        end
772✔
1040
    end
1041
    return ex
772✔
1042
end
1043

1044
macro generated()
4✔
1045
    return Expr(:generated)
4✔
1046
end
1047

1048
"""
1049
    @generated f
1050

1051
`@generated` is used to annotate a function which will be generated.
1052
In the body of the generated function, only types of arguments can be read
1053
(not the values). The function returns a quoted expression evaluated when the
1054
function is called. The `@generated` macro should not be used on functions mutating
1055
the global scope or depending on mutable elements.
1056

1057
See [Metaprogramming](@ref) for further details.
1058

1059
# Examples
1060
```jldoctest
1061
julia> @generated function bar(x)
1062
           if x <: Integer
1063
               return :(x ^ 2)
1064
           else
1065
               return :(x)
1066
           end
1067
       end
1068
bar (generic function with 1 method)
1069

1070
julia> bar(4)
1071
16
1072

1073
julia> bar("baz")
1074
"baz"
1075
```
1076
"""
1077
macro generated(f)
49✔
1078
    if isa(f, Expr) && (f.head === :function || is_short_function_def(f))
66✔
1079
        body = f.args[2]
49✔
1080
        lno = body.args[1]
49✔
1081
        return Expr(:escape,
49✔
1082
                    Expr(f.head, f.args[1],
1083
                         Expr(:block,
1084
                              lno,
1085
                              Expr(:if, Expr(:generated),
1086
                                   body,
1087
                                   Expr(:block,
1088
                                        Expr(:meta, :generated_only),
1089
                                        Expr(:return, nothing))))))
1090
    else
1091
        error("invalid syntax; @generated must be used with a function definition")
×
1092
    end
1093
end
1094

1095

1096
"""
1097
    @atomic var
1098
    @atomic order ex
1099

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

1103
    @atomic a.b.x = new
1104
    @atomic a.b.x += addend
1105
    @atomic :release a.b.x = new
1106
    @atomic :acquire_release a.b.x += addend
1107
    @atomic m[idx] = new
1108
    @atomic m[idx] += addend
1109
    @atomic :release m[idx] = new
1110
    @atomic :acquire_release m[idx] += addend
1111

1112
Perform the store operation expressed on the right atomically and return the
1113
new value.
1114

1115
With assignment (`=`), this operation translates to a `setproperty!(a.b, :x, new)`
1116
or, in case of reference, to a `setindex_atomic!(m, order, new, idx)` call,
1117
with `order` defaulting to `:sequentially_consistent`.
1118

1119
With any modifying operator this operation translates to a
1120
`modifyproperty!(a.b, :x, op, addend)[2]` or, in case of reference, to a
1121
`modifyindex_atomic!(m, order, op, addend, idx...)[2]` call,
1122
with `order` defaulting to `:sequentially_consistent`.
1123

1124
    @atomic a.b.x max arg2
1125
    @atomic a.b.x + arg2
1126
    @atomic max(a.b.x, arg2)
1127
    @atomic :acquire_release max(a.b.x, arg2)
1128
    @atomic :acquire_release a.b.x + arg2
1129
    @atomic :acquire_release a.b.x max arg2
1130
    @atomic m[idx] max arg2
1131
    @atomic m[idx] + arg2
1132
    @atomic max(m[idx], arg2)
1133
    @atomic :acquire_release max(m[idx], arg2)
1134
    @atomic :acquire_release m[idx] + arg2
1135
    @atomic :acquire_release m[idx] max arg2
1136

1137
Perform the binary operation expressed on the right atomically. Store the
1138
result into the field or the reference in the first argument, and return the values
1139
`(old, new)`.
1140

1141
This operation translates to a `modifyproperty!(a.b, :x, func, arg2)` or,
1142
in case of reference to a `modifyindex_atomic!(m, order, func, arg2, idx)` call,
1143
with `order` defaulting to `:sequentially_consistent`.
1144

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

1147
# Examples
1148
```jldoctest
1149
julia> mutable struct Atomic{T}; @atomic x::T; end
1150

1151
julia> a = Atomic(1)
1152
Atomic{Int64}(1)
1153

1154
julia> @atomic a.x # fetch field x of a, with sequential consistency
1155
1
1156

1157
julia> @atomic :sequentially_consistent a.x = 2 # set field x of a, with sequential consistency
1158
2
1159

1160
julia> @atomic a.x += 1 # increment field x of a, with sequential consistency
1161
3
1162

1163
julia> @atomic a.x + 1 # increment field x of a, with sequential consistency
1164
3 => 4
1165

1166
julia> @atomic a.x # fetch field x of a, with sequential consistency
1167
4
1168

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

1172
julia> @atomic a.x max 5 # again change field x of a to the max value, with sequential consistency
1173
10 => 10
1174
```
1175

1176
```jldoctest
1177
julia> mem = AtomicMemory{Int}(undef, 2);
1178

1179
julia> @atomic mem[1] = 2 # set mem[1] to value 2 with sequential consistency
1180
2
1181

1182
julia> @atomic :monotonic mem[1] # fetch the first value of mem, with monotonic consistency
1183
2
1184

1185
julia> @atomic mem[1] += 1 # increment the first value of mem, with sequential consistency
1186
3
1187

1188
julia> @atomic mem[1] + 1 # increment the first value of mem, with sequential consistency
1189
3 => 4
1190

1191
julia> @atomic mem[1] # fetch the first value of mem, with sequential consistency
1192
4
1193

1194
julia> @atomic max(mem[1], 10) # change the first value of mem to the max value, with sequential consistency
1195
4 => 10
1196

1197
julia> @atomic mem[1] max 5 # again change the first value of mem to the max value, with sequential consistency
1198
10 => 10
1199
```
1200

1201
!!! compat "Julia 1.7"
1202
    Atomic fields functionality requires at least Julia 1.7.
1203

1204
!!! compat "Julia 1.12"
1205
    Atomic reference functionality requires at least Julia 1.12.
1206
"""
1207
macro atomic(ex)
52✔
1208
    if !isa(ex, Symbol) && !is_expr(ex, :(::))
52✔
1209
        return make_atomic(QuoteNode(:sequentially_consistent), ex)
43✔
1210
    end
1211
    return esc(Expr(:atomic, ex))
9✔
1212
end
1213
macro atomic(order, ex)
24✔
1214
    order isa QuoteNode || (order = esc(order))
24✔
1215
    return make_atomic(order, ex)
24✔
1216
end
1217
macro atomic(a1, op, a2)
4✔
1218
    return make_atomic(QuoteNode(:sequentially_consistent), a1, op, a2)
4✔
1219
end
1220
macro atomic(order, a1, op, a2)
4✔
1221
    order isa QuoteNode || (order = esc(order))
4✔
1222
    return make_atomic(order, a1, op, a2)
4✔
1223
end
1224
function make_atomic(order, ex)
67✔
1225
    @nospecialize
×
1226
    if ex isa Expr
67✔
1227
        if isexpr(ex, :., 2)
67✔
1228
            l, r = esc(ex.args[1]), esc(ex.args[2])
16✔
1229
            return :(getproperty($l, $r, $order))
16✔
1230
        elseif isexpr(ex, :call, 3)
51✔
1231
            return make_atomic(order, ex.args[2], ex.args[1], ex.args[3])
8✔
1232
        elseif isexpr(ex, :ref)
43✔
1233
            x, idcs = esc(ex.args[1]), map(esc, ex.args[2:end])
6✔
1234
            return :(getindex_atomic($x, $order, $(idcs...)))
3✔
1235
        elseif ex.head === :(=)
40✔
1236
            l, r = ex.args[1], esc(ex.args[2])
23✔
1237
            if is_expr(l, :., 2)
23✔
1238
                ll, lr = esc(l.args[1]), esc(l.args[2])
14✔
1239
                return :(setproperty!($ll, $lr, $r, $order))
14✔
1240
            elseif is_expr(l, :ref)
9✔
1241
                x, idcs = esc(l.args[1]), map(esc, l.args[2:end])
16✔
1242
                return :(setindex_atomic!($x, $order, $r, $(idcs...)))
8✔
1243
            end
1244
        end
1245
        if length(ex.args) == 2
18✔
1246
            if ex.head === :(+=)
15✔
1247
                op = :+
×
1248
            elseif ex.head === :(-=)
6✔
1249
                op = :-
×
1250
            elseif @isdefined string
×
1251
                shead = string(ex.head)
4✔
1252
                if endswith(shead, '=')
4✔
1253
                    op = Symbol(shead[1:prevind(shead, end)])
3✔
1254
                end
1255
            end
1256
            if @isdefined(op)
15✔
1257
                return Expr(:ref, make_atomic(order, ex.args[1], op, ex.args[2]), 2)
14✔
1258
            end
1259
        end
1260
    end
1261
    error("could not parse @atomic expression $ex")
4✔
1262
end
1263
function make_atomic(order, a1, op, a2)
30✔
1264
    @nospecialize
×
1265
    if is_expr(a1, :., 2)
30✔
1266
        a1l, a1r, op, a2 = esc(a1.args[1]), esc(a1.args[2]), esc(op), esc(a2)
25✔
1267
        return :(modifyproperty!($a1l, $a1r, $op, $a2, $order))
25✔
1268
    elseif is_expr(a1, :ref)
5✔
1269
        x, idcs, op, a2 = esc(a1.args[1]), map(esc, a1.args[2:end]), esc(op), esc(a2)
2✔
1270
        return :(modifyindex_atomic!($x, $order, $op, $a2, $(idcs...)))
1✔
1271
    end
1272
    error("@atomic modify expression missing field access or indexing")
4✔
1273
end
1274

1275

1276
"""
1277
    @atomicswap a.b.x = new
1278
    @atomicswap :sequentially_consistent a.b.x = new
1279
    @atomicswap m[idx] = new
1280
    @atomicswap :sequentially_consistent m[idx] = new
1281

1282
Stores `new` into `a.b.x` (`m[idx]` in case of reference) and returns the old
1283
value of `a.b.x` (the old value stored at `m[idx]`, respectively).
1284

1285
This operation translates to a `swapproperty!(a.b, :x, new)` or,
1286
in case of reference, `swapindex_atomic!(mem, order, new, idx)` call,
1287
with `order` defaulting to `:sequentially_consistent`.
1288

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

1291
# Examples
1292
```jldoctest
1293
julia> mutable struct Atomic{T}; @atomic x::T; end
1294

1295
julia> a = Atomic(1)
1296
Atomic{Int64}(1)
1297

1298
julia> @atomicswap a.x = 2+2 # replace field x of a with 4, with sequential consistency
1299
1
1300

1301
julia> @atomic a.x # fetch field x of a, with sequential consistency
1302
4
1303
```
1304

1305
```jldoctest
1306
julia> mem = AtomicMemory{Int}(undef, 2);
1307

1308
julia> @atomic mem[1] = 1;
1309

1310
julia> @atomicswap mem[1] = 4 # replace the first value of `mem` with 4, with sequential consistency
1311
1
1312

1313
julia> @atomic mem[1] # fetch the first value of mem, with sequential consistency
1314
4
1315
```
1316

1317
!!! compat "Julia 1.7"
1318
    Atomic fields functionality requires at least Julia 1.7.
1319

1320
!!! compat "Julia 1.12"
1321
    Atomic reference functionality requires at least Julia 1.12.
1322
"""
1323
macro atomicswap(order, ex)
4✔
1324
    order isa QuoteNode || (order = esc(order))
4✔
1325
    return make_atomicswap(order, ex)
4✔
1326
end
1327
macro atomicswap(ex)
3✔
1328
    return make_atomicswap(QuoteNode(:sequentially_consistent), ex)
3✔
1329
end
1330
function make_atomicswap(order, ex)
7✔
1331
    @nospecialize
×
1332
    is_expr(ex, :(=), 2) || error("@atomicswap expression missing assignment")
7✔
1333
    l, val = ex.args[1], esc(ex.args[2])
7✔
1334
    if is_expr(l, :., 2)
7✔
1335
        ll, lr = esc(l.args[1]), esc(l.args[2])
6✔
1336
        return :(swapproperty!($ll, $lr, $val, $order))
6✔
1337
    elseif is_expr(l, :ref)
1✔
1338
        x, idcs = esc(l.args[1]), map(esc, l.args[2:end])
2✔
1339
        return :(swapindex_atomic!($x, $order, $val, $(idcs...)))
1✔
1340
    end
1341
    error("@atomicswap expression missing field access or indexing")
×
1342
end
1343

1344

1345
"""
1346
    @atomicreplace a.b.x expected => desired
1347
    @atomicreplace :sequentially_consistent a.b.x expected => desired
1348
    @atomicreplace :sequentially_consistent :monotonic a.b.x expected => desired
1349
    @atomicreplace m[idx] expected => desired
1350
    @atomicreplace :sequentially_consistent m[idx] expected => desired
1351
    @atomicreplace :sequentially_consistent :monotonic m[idx] expected => desired
1352

1353
Perform the conditional replacement expressed by the pair atomically, returning
1354
the values `(old, success::Bool)`. Where `success` indicates whether the
1355
replacement was completed.
1356

1357
This operation translates to a `replaceproperty!(a.b, :x, expected, desired)` or,
1358
in case of reference, to a
1359
`replaceindex_atomic!(mem, success_order, fail_order, expected, desired, idx)` call,
1360
with both orders defaulting to `:sequentially_consistent`.
1361

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

1364
# Examples
1365
```jldoctest
1366
julia> mutable struct Atomic{T}; @atomic x::T; end
1367

1368
julia> a = Atomic(1)
1369
Atomic{Int64}(1)
1370

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

1374
julia> @atomic a.x # fetch field x of a, with sequential consistency
1375
2
1376

1377
julia> @atomicreplace a.x 1 => 3 # replace field x of a with 2 if it was 1, with sequential consistency
1378
(old = 2, success = false)
1379

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

1382
julia> @atomicreplace a.x xchg
1383
(old = 2, success = true)
1384

1385
julia> @atomic a.x # fetch field x of a, with sequential consistency
1386
0
1387
```
1388

1389
```jldoctest
1390
julia> mem = AtomicMemory{Int}(undef, 2);
1391

1392
julia> @atomic mem[1] = 1;
1393

1394
julia> @atomicreplace mem[1] 1 => 2 # replace the first value of mem with 2 if it was 1, with sequential consistency
1395
(old = 1, success = true)
1396

1397
julia> @atomic mem[1] # fetch the first value of mem, with sequential consistency
1398
2
1399

1400
julia> @atomicreplace mem[1] 1 => 3 # replace field x of a with 2 if it was 1, with sequential consistency
1401
(old = 2, success = false)
1402

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

1405
julia> @atomicreplace mem[1] xchg
1406
(old = 2, success = true)
1407

1408
julia> @atomic mem[1] # fetch the first value of mem, with sequential consistency
1409
0
1410
```
1411

1412
!!! compat "Julia 1.7"
1413
    Atomic fields functionality requires at least Julia 1.7.
1414

1415
!!! compat "Julia 1.12"
1416
    Atomic reference functionality requires at least Julia 1.12.
1417
"""
1418
macro atomicreplace(success_order, fail_order, ex, old_new)
8✔
1419
    fail_order isa QuoteNode || (fail_order = esc(fail_order))
8✔
1420
    success_order isa QuoteNode || (success_order = esc(success_order))
8✔
1421
    return make_atomicreplace(success_order, fail_order, ex, old_new)
8✔
1422
end
1423
macro atomicreplace(order, ex, old_new)
8✔
1424
    order isa QuoteNode || (order = esc(order))
8✔
1425
    return make_atomicreplace(order, order, ex, old_new)
8✔
1426
end
1427
macro atomicreplace(ex, old_new)
9✔
1428
    return make_atomicreplace(QuoteNode(:sequentially_consistent), QuoteNode(:sequentially_consistent), ex, old_new)
9✔
1429
end
1430
function make_atomicreplace(success_order, fail_order, ex, old_new)
25✔
1431
    @nospecialize
×
1432
    if is_expr(ex, :., 2)
25✔
1433
        ll, lr = esc(ex.args[1]), esc(ex.args[2])
20✔
1434
        if is_expr(old_new, :call, 3) && old_new.args[1] === :(=>)
20✔
1435
            exp, rep = esc(old_new.args[2]), esc(old_new.args[3])
10✔
1436
            return :(replaceproperty!($ll, $lr, $exp, $rep, $success_order, $fail_order))
10✔
1437
        else
1438
            old_new = esc(old_new)
10✔
1439
            return :(replaceproperty!($ll, $lr, $old_new::Pair..., $success_order, $fail_order))
10✔
1440
        end
1441
    elseif is_expr(ex, :ref)
5✔
1442
        x, idcs = esc(ex.args[1]), map(esc, ex.args[2:end])
8✔
1443
        if is_expr(old_new, :call, 3) && old_new.args[1] === :(=>)
4✔
1444
            exp, rep = esc(old_new.args[2]), esc(old_new.args[3])
2✔
1445
            return :(replaceindex_atomic!($x, $success_order, $fail_order, $exp, $rep, $(idcs...)))
2✔
1446
        else
1447
            old_new = esc(old_new)
2✔
1448
            return :(replaceindex_atomic!($x, $success_order, $fail_order, $old_new::Pair..., $(idcs...)))
2✔
1449
        end
1450
    end
1451
    error("@atomicreplace expression missing field access or indexing")
1✔
1452
end
1453

1454
"""
1455
    @atomiconce a.b.x = value
1456
    @atomiconce :sequentially_consistent a.b.x = value
1457
    @atomiconce :sequentially_consistent :monotonic a.b.x = value
1458
    @atomiconce m[idx] = value
1459
    @atomiconce :sequentially_consistent m[idx] = value
1460
    @atomiconce :sequentially_consistent :monotonic m[idx] = value
1461

1462
Perform the conditional assignment of value atomically if it was previously
1463
unset. Returned value `success::Bool` indicates whether the assignment was completed.
1464

1465
This operation translates to a `setpropertyonce!(a.b, :x, value)` or,
1466
in case of reference, to a `setindexonce_atomic!(m, success_order, fail_order, value, idx)` call,
1467
with both orders defaulting to `:sequentially_consistent`.
1468

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

1471
# Examples
1472
```jldoctest
1473
julia> mutable struct AtomicOnce
1474
           @atomic x
1475
           AtomicOnce() = new()
1476
       end
1477

1478
julia> a = AtomicOnce()
1479
AtomicOnce(#undef)
1480

1481
julia> @atomiconce a.x = 1 # set field x of a to 1, if unset, with sequential consistency
1482
true
1483

1484
julia> @atomic a.x # fetch field x of a, with sequential consistency
1485
1
1486

1487
julia> @atomiconce :monotonic a.x = 2 # set field x of a to 1, if unset, with monotonic consistence
1488
false
1489
```
1490

1491
```jldoctest
1492
julia> mem = AtomicMemory{Vector{Int}}(undef, 1);
1493

1494
julia> isassigned(mem, 1)
1495
false
1496

1497
julia> @atomiconce mem[1] = [1] # set the first value of mem to [1], if unset, with sequential consistency
1498
true
1499

1500
julia> isassigned(mem, 1)
1501
true
1502

1503
julia> @atomic mem[1] # fetch the first value of mem, with sequential consistency
1504
1-element Vector{Int64}:
1505
 1
1506

1507
julia> @atomiconce :monotonic mem[1] = [2] # set the first value of mem to [2], if unset, with monotonic
1508
false
1509

1510
julia> @atomic mem[1]
1511
1-element Vector{Int64}:
1512
 1
1513
```
1514

1515
!!! compat "Julia 1.11"
1516
    Atomic fields functionality requires at least Julia 1.11.
1517

1518
!!! compat "Julia 1.12"
1519
    Atomic reference functionality requires at least Julia 1.12.
1520
"""
1521
macro atomiconce(success_order, fail_order, ex)
1522
    fail_order isa QuoteNode || (fail_order = esc(fail_order))
1523
    success_order isa QuoteNode || (success_order = esc(success_order))
1524
    return make_atomiconce(success_order, fail_order, ex)
1525
end
1526
macro atomiconce(order, ex)
2✔
1527
    order isa QuoteNode || (order = esc(order))
2✔
1528
    return make_atomiconce(order, order, ex)
2✔
1529
end
1530
macro atomiconce(ex)
7✔
1531
    return make_atomiconce(QuoteNode(:sequentially_consistent), QuoteNode(:sequentially_consistent), ex)
7✔
1532
end
1533
function make_atomiconce(success_order, fail_order, ex)
9✔
1534
    @nospecialize
9✔
1535
    is_expr(ex, :(=), 2) || error("@atomiconce expression missing assignment")
9✔
1536
    l, val = ex.args[1], esc(ex.args[2])
9✔
1537
    if is_expr(l, :., 2)
9✔
1538
        ll, lr = esc(l.args[1]), esc(l.args[2])
6✔
1539
        return :(setpropertyonce!($ll, $lr, $val, $success_order, $fail_order))
6✔
1540
    elseif is_expr(l, :ref)
3✔
1541
        x, idcs = esc(l.args[1]), map(esc, l.args[2:end])
6✔
1542
        return :(setindexonce_atomic!($x, $success_order, $fail_order, $val, $(idcs...)))
3✔
1543
    end
1544
    error("@atomiconce expression missing field access or indexing")
×
1545
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

© 2026 Coveralls, Inc