Skip to content

Commit f313b08

Browse files
committed
add some intrinsic support for atomic operations
1 parent fd905fa commit f313b08

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+2769
-749
lines changed

Make.inc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,7 +1243,7 @@ else ifneq ($(USEMSVC), 1)
12431243
endif
12441244

12451245
ifeq ($(OS), Linux)
1246-
OSLIBS += -Wl,--no-as-needed -ldl -lrt -lpthread -Wl,--export-dynamic,--as-needed,--no-whole-archive
1246+
OSLIBS += -Wl,--no-as-needed -ldl -lrt -lpthread -latomic -Wl,--export-dynamic,--as-needed,--no-whole-archive
12471247
# Detect if ifunc is supported
12481248
IFUNC_DETECT_SRC := 'void (*f0(void))(void) { return (void(*)(void))0L; }; void f(void) __attribute__((ifunc("f0")));'
12491249
ifeq (supported, $(shell echo $(IFUNC_DETECT_SRC) | $(CC) -Werror -x c - -S -o /dev/null > /dev/null 2>&1 && echo supported))
@@ -1269,7 +1269,7 @@ endif
12691269

12701270
ifeq ($(OS), FreeBSD)
12711271
JLDFLAGS := -Wl,-Bdynamic
1272-
OSLIBS += -lelf -lkvm -lrt -lpthread
1272+
OSLIBS += -lelf -lkvm -lrt -lpthread -latomic
12731273

12741274
# Tweak order of libgcc_s in DT_NEEDED,
12751275
# make it loaded first to

NEWS.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,15 @@ Command-line option changes
4242
Multi-threading changes
4343
-----------------------
4444

45-
* If the `JULIA_NUM_THREADS` environment variable is set to `auto`, then the number of threads will be set to the number of CPU threads ([#38952])
46-
* Every `Task` object has a local random number generator state, providing reproducible (schedule-independent) execution
47-
of parallel simulation code by default. The default generator is also significantly faster in parallel than in
48-
previous versions.
45+
* Intrinsics for atomic pointer operations are now defined for certain byte sizes. ([#37847])
46+
* Support for declaring and using individual fields of a mutable struct as
47+
atomic now available. ([#37847])
48+
* If the `JULIA_NUM_THREADS` environment variable is set to `auto`, then the
49+
number of threads will be set to the number of CPU threads ([#38952])
50+
* Every `Task` object has a local random number generator state, providing
51+
reproducible (schedule-independent) execution of parallel simulation code by
52+
default. The default generator is also significantly faster in parallel than
53+
in previous versions.
4954

5055

5156
Build system changes

base/Base.jl

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,48 @@ include(path::String) = include(Base, path)
2020
const is_primary_base_module = ccall(:jl_module_parent, Ref{Module}, (Any,), Base) === Core.Main
2121
ccall(:jl_set_istopmod, Cvoid, (Any, Bool), Base, is_primary_base_module)
2222

23+
# The real @inline macro is not available until after array.jl, so this
24+
# internal macro splices the meta Expr directly into the function body.
25+
macro _inline_meta()
26+
Expr(:meta, :inline)
27+
end
28+
macro _noinline_meta()
29+
Expr(:meta, :noinline)
30+
end
31+
2332
# Try to help prevent users from shooting them-selves in the foot
2433
# with ambiguities by defining a few common and critical operations
2534
# (and these don't need the extra convert code)
26-
getproperty(x::Module, f::Symbol) = getfield(x, f)
27-
setproperty!(x::Module, f::Symbol, v) = setfield!(x, f, v)
28-
getproperty(x::Type, f::Symbol) = getfield(x, f)
29-
setproperty!(x::Type, f::Symbol, v) = setfield!(x, f, v)
30-
getproperty(x::Tuple, f::Int) = getfield(x, f)
35+
getproperty(x::Module, f::Symbol) = (@_inline_meta; getfield(x, f))
36+
setproperty!(x::Module, f::Symbol, v) = setfield!(x, f, v) # to get a decent error
37+
getproperty(x::Type, f::Symbol) = (@_inline_meta; getfield(x, f))
38+
setproperty!(x::Type, f::Symbol, v) = error("setfield! fields of Types should not be changed")
39+
getproperty(x::Tuple, f::Int) = (@_inline_meta; getfield(x, f))
3140
setproperty!(x::Tuple, f::Int, v) = setfield!(x, f, v) # to get a decent error
3241

33-
getproperty(x, f::Symbol) = getfield(x, f)
34-
dotgetproperty(x, f) = getproperty(x, f)
42+
getproperty(x, f::Symbol) = (@_inline_meta; getfield(x, f))
3543
setproperty!(x, f::Symbol, v) = setfield!(x, f, convert(fieldtype(typeof(x), f), v))
3644

45+
dotgetproperty(x, f) = getproperty(x, f)
46+
47+
getproperty(x::Module, f::Symbol, order::Symbol) = (@_inline_meta; getfield(x, f, order))
48+
setproperty!(x::Module, f::Symbol, v, order::Symbol) = setfield!(x, f, v, order) # to get a decent error
49+
getproperty(x::Type, f::Symbol, order::Symbol) = (@_inline_meta; getfield(x, f, order))
50+
setproperty!(x::Type, f::Symbol, v, order::Symbol) = error("setfield! fields of Types should not be changed")
51+
getproperty(x::Tuple, f::Int, order::Symbol) = (@_inline_meta; getfield(x, f, order))
52+
setproperty!(x::Tuple, f::Int, v, order::Symbol) = setfield!(x, f, v, order) # to get a decent error
53+
54+
getproperty(x, f::Symbol, order::Symbol) = (@_inline_meta; getfield(x, f, order))
55+
setproperty!(x, f::Symbol, v, order::Symbol) = (@_inline_meta; setfield!(x, f, convert(fieldtype(typeof(x), f), v), order))
56+
57+
swapproperty!(x, f::Symbol, v, order::Symbol=:notatomic) =
58+
(@_inline_meta; Core.swapfield!(x, f, convert(fieldtype(typeof(x), f), v), order))
59+
modifyproperty!(x, f::Symbol, op, v, order::Symbol=:notatomic) =
60+
(@_inline_meta; Core.modifyfield!(x, f, op, v, order))
61+
replaceproperty!(x, f::Symbol, expected, desired, success_order::Symbol=:notatomic, fail_order::Symbol=success_order) =
62+
(@_inline_meta; Core.replacefield!(x, f, expected, convert(fieldtype(typeof(x), f), desired), success_order, fail_order))
63+
64+
3765
include("coreio.jl")
3866

3967
eval(x) = Core.eval(Base, x)

base/boot.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,11 +187,12 @@ export
187187
InterruptException, InexactError, OutOfMemoryError, ReadOnlyMemoryError,
188188
OverflowError, StackOverflowError, SegmentationFault, UndefRefError, UndefVarError,
189189
TypeError, ArgumentError, MethodError, AssertionError, LoadError, InitError,
190-
UndefKeywordError,
190+
UndefKeywordError, ConcurrencyViolationError,
191191
# AST representation
192192
Expr, QuoteNode, LineNumberNode, GlobalRef,
193193
# object model functions
194-
fieldtype, getfield, setfield!, nfields, throw, tuple, ===, isdefined, eval, ifelse,
194+
fieldtype, getfield, setfield!, swapfield!, modifyfield!, replacefield!,
195+
nfields, throw, tuple, ===, isdefined, eval, ifelse,
195196
# sizeof # not exported, to avoid conflicting with Base.sizeof
196197
# type reflection
197198
<:, typeof, isa, typeassert,
@@ -290,6 +291,9 @@ struct UndefRefError <: Exception end
290291
struct UndefVarError <: Exception
291292
var::Symbol
292293
end
294+
struct ConcurrencyViolationError <: Exception
295+
msg::AbstractString
296+
end
293297
struct InterruptException <: Exception end
294298
struct DomainError <: Exception
295299
val

base/compiler/compiler.jl

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import Core: print, println, show, write, unsafe_write, stdout, stderr,
1010

1111
const getproperty = Core.getfield
1212
const setproperty! = Core.setfield!
13+
const swapproperty! = Core.swapfield!
14+
const modifyproperty! = Core.modifyfield!
15+
const replaceproperty! = Core.replacefield!
1316

1417
ccall(:jl_set_istopmod, Cvoid, (Any, Bool), Compiler, false)
1518

@@ -19,9 +22,14 @@ eval(m, x) = Core.eval(m, x)
1922
include(x) = Core.include(Compiler, x)
2023
include(mod, x) = Core.include(mod, x)
2124

22-
#############
23-
# from Base #
24-
#############
25+
# The real @inline macro is not available until after array.jl, so this
26+
# internal macro splices the meta Expr directly into the function body.
27+
macro _inline_meta()
28+
Expr(:meta, :inline)
29+
end
30+
macro _noinline_meta()
31+
Expr(:meta, :noinline)
32+
end
2533

2634
# essential files and libraries
2735
include("essentials.jl")

base/compiler/ssair/ir.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# This file is a part of Julia. License is MIT: https://julialang.org/license
22

3-
@inline isexpr(@nospecialize(stmt), head::Symbol) = isa(stmt, Expr) && stmt.head === head
43
Core.PhiNode() = Core.PhiNode(Int32[], Any[])
54

65
isterminator(@nospecialize(stmt)) = isa(stmt, GotoNode) || isa(stmt, GotoIfNot) || isa(stmt, ReturnNode)

base/compiler/ssair/passes.jl

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -560,14 +560,24 @@ function getfield_elim_pass!(ir::IRCode)
560560
#ndone += 1
561561
result_t = compact_exprtype(compact, SSAValue(idx))
562562
is_getfield = is_setfield = false
563+
field_ordering = :unspecified
563564
is_ccall = false
564565
# Step 1: Check whether the statement we're looking at is a getfield/setfield!
565566
if is_known_call(stmt, setfield!, compact)
566567
is_setfield = true
567568
4 <= length(stmt.args) <= 5 || continue
569+
if length(stmt.args) == 5
570+
field_ordering = compact_exprtype(compact, stmt.args[5])
571+
end
568572
elseif is_known_call(stmt, getfield, compact)
569573
is_getfield = true
570-
3 <= length(stmt.args) <= 4 || continue
574+
3 <= length(stmt.args) <= 5 || continue
575+
if length(stmt.args) == 5
576+
field_ordering = compact_exprtype(compact, stmt.args[5])
577+
elseif length(stmt.args) == 4
578+
field_ordering = compact_exprtype(compact, stmt.args[4])
579+
widenconst(field_ordering) === Bool && (field_ordering = :unspecified)
580+
end
571581
elseif is_known_call(stmt, isa, compact)
572582
# TODO
573583
continue
@@ -660,6 +670,11 @@ function getfield_elim_pass!(ir::IRCode)
660670
end
661671
isa(struct_typ, DataType) || continue
662672

673+
struct_typ.name.atomicfields == C_NULL || continue # TODO: handle more
674+
if !(field_ordering === :unspecified || (field_ordering isa Const && field_ordering.val === :not_atomic))
675+
continue
676+
end
677+
663678
def, typeconstraint = stmt.args[2], struct_typ
664679

665680
if struct_typ.name.mutable

base/compiler/tfuncs.jl

Lines changed: 59 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ function isdefined_nothrow(argtypes::Array{Any, 1})
263263
(argtypes[2] Symbol || argtypes[2] Int) :
264264
argtypes[2] Symbol
265265
end
266+
isdefined_tfunc(arg1, sym, order) = (@nospecialize; isdefined_tfunc(arg1, sym))
266267
function isdefined_tfunc(@nospecialize(arg1), @nospecialize(sym))
267268
if isa(arg1, Const)
268269
a1 = typeof(arg1.val)
@@ -316,7 +317,7 @@ function isdefined_tfunc(@nospecialize(arg1), @nospecialize(sym))
316317
end
317318
return Bool
318319
end
319-
add_tfunc(isdefined, 2, 2, isdefined_tfunc, 1)
320+
add_tfunc(isdefined, 2, 3, isdefined_tfunc, 1)
320321

321322
function sizeof_nothrow(@nospecialize(x))
322323
if isa(x, Const)
@@ -470,22 +471,26 @@ add_tfunc(arraysize, 2, 2, (@nospecialize(a), @nospecialize(d))->Int, 4)
470471
function pointer_eltype(@nospecialize(ptr))
471472
a = widenconst(ptr)
472473
if a <: Ptr
473-
if isa(a,DataType) && isa(a.parameters[1],Type)
474+
if isa(a, DataType) && isa(a.parameters[1], Type)
474475
return a.parameters[1]
475-
elseif isa(a,UnionAll) && !has_free_typevars(a)
476+
elseif isa(a, UnionAll) && !has_free_typevars(a)
476477
unw = unwrap_unionall(a)
477-
if isa(unw,DataType)
478+
if isa(unw, DataType)
478479
return rewrap_unionall(unw.parameters[1], a)
479480
end
480481
end
481482
end
482483
return Any
483484
end
484-
add_tfunc(pointerref, 3, 3,
485-
function (@nospecialize(a), @nospecialize(i), @nospecialize(align))
486-
return pointer_eltype(a)
487-
end, 4)
488-
add_tfunc(pointerset, 4, 4, (@nospecialize(a), @nospecialize(v), @nospecialize(i), @nospecialize(align)) -> a, 5)
485+
add_tfunc(pointerref, 3, 3, (a, i, align) -> (@nospecialize; pointer_eltype(a)), 4)
486+
add_tfunc(pointerset, 4, 4, (a, v, i, align) -> (@nospecialize; a), 5)
487+
488+
add_tfunc(atomic_fence, 1, 1, (order) -> (@nospecialize; Nothing), 4)
489+
add_tfunc(atomic_pointerref, 2, 2, (a, order) -> (@nospecialize; pointer_eltype(a)), 4)
490+
add_tfunc(atomic_pointerset, 3, 3, (a, v, order) -> (@nospecialize; a), 5)
491+
add_tfunc(atomic_pointerswap, 3, 3, (a, v, order) -> (@nospecialize; pointer_eltype(a)), 5)
492+
add_tfunc(atomic_pointermodify, 4, 4, (a, op, v, order) -> (@nospecialize; T = pointer_eltype(a); Tuple{T, T}), 5)
493+
add_tfunc(atomic_pointerreplace, 5, 5, (a, x, v, success_order, failure_order) -> (@nospecialize; Tuple{pointer_eltype(a), Bool}), 5)
489494

490495
# more accurate typeof_tfunc for vararg tuples abstract only in length
491496
function typeof_concrete_vararg(t::DataType)
@@ -675,14 +680,25 @@ function try_compute_fieldidx(typ::DataType, @nospecialize(field))
675680
end
676681

677682
function getfield_nothrow(argtypes::Vector{Any})
678-
2 <= length(argtypes) <= 3 || return false
679-
length(argtypes) == 2 && return getfield_nothrow(argtypes[1], argtypes[2], Const(true))
680-
return getfield_nothrow(argtypes[1], argtypes[2], argtypes[3])
683+
if length(argtypes) == 2
684+
boundscheck = Bool
685+
elseif length(argtypes) == 3
686+
boundscheck = argtypes[3]
687+
if boundscheck === Const(:not_atomic) # TODO: this is assuming not atomic
688+
boundscheck = Bool
689+
end
690+
elseif length(argtypes) == 4
691+
boundscheck = argtypes[4]
692+
else
693+
return false
694+
end
695+
widenconst(boundscheck) !== Bool && return false
696+
bounds_check_disabled = isa(boundscheck, Const) && boundscheck.val === false
697+
return getfield_nothrow(argtypes[1], argtypes[2], !bounds_check_disabled)
681698
end
682-
function getfield_nothrow(@nospecialize(s00), @nospecialize(name), @nospecialize(inbounds))
683-
bounds_check_disabled = isa(inbounds, Const) && inbounds.val === false
684-
# If we don't have invounds and don't know the field, don't even bother
685-
if !bounds_check_disabled
699+
function getfield_nothrow(@nospecialize(s00), @nospecialize(name), boundscheck::Bool)
700+
# If we don't have boundscheck and don't know the field, don't even bother
701+
if boundscheck
686702
isa(name, Const) || return false
687703
end
688704

@@ -700,7 +716,7 @@ function getfield_nothrow(@nospecialize(s00), @nospecialize(name), @nospecialize
700716
end
701717
return isdefined(sv, name.val)
702718
end
703-
if bounds_check_disabled && !isa(sv, Module)
719+
if !boundscheck && !isa(sv, Module)
704720
# If bounds checking is disabled and all fields are assigned,
705721
# we may assume that we don't throw
706722
for i = 1:fieldcount(typeof(sv))
@@ -714,14 +730,15 @@ function getfield_nothrow(@nospecialize(s00), @nospecialize(name), @nospecialize
714730
s0 = widenconst(s00)
715731
s = unwrap_unionall(s0)
716732
if isa(s, Union)
717-
return getfield_nothrow(rewrap(s.a, s00), name, inbounds) &&
718-
getfield_nothrow(rewrap(s.b, s00), name, inbounds)
733+
return getfield_nothrow(rewrap(s.a, s00), name, boundscheck) &&
734+
getfield_nothrow(rewrap(s.b, s00), name, boundscheck)
719735
elseif isa(s, DataType)
720736
# Can't say anything about abstract types
721737
s.name.abstract && return false
738+
s.name.atomicfields == C_NULL || return false # TODO: currently we're only testing for ordering == :not_atomic
722739
# If all fields are always initialized, and bounds check is disabled, we can assume
723740
# we don't throw
724-
if bounds_check_disabled && !isvatuple(s) && s.name !== NamedTuple.body.body.name && fieldcount(s) == s.ninitialized
741+
if !boundscheck && !isvatuple(s) && s.name !== NamedTuple.body.body.name && fieldcount(s) == s.ninitialized
725742
return true
726743
end
727744
# Else we need to know what the field is
@@ -736,8 +753,8 @@ function getfield_nothrow(@nospecialize(s00), @nospecialize(name), @nospecialize
736753
return false
737754
end
738755

739-
getfield_tfunc(@nospecialize(s00), @nospecialize(name), @nospecialize(inbounds)) =
740-
getfield_tfunc(s00, name)
756+
getfield_tfunc(s00, name, boundscheck_or_order) = (@nospecialize; getfield_tfunc(s00, name))
757+
getfield_tfunc(s00, name, order, boundscheck) = (@nospecialize; getfield_tfunc(s00, name))
741758
function getfield_tfunc(@nospecialize(s00), @nospecialize(name))
742759
s = unwrap_unionall(s00)
743760
if isa(s, Union)
@@ -892,10 +909,25 @@ function getfield_tfunc(@nospecialize(s00), @nospecialize(name))
892909
end
893910
return rewrap_unionall(R, s00)
894911
end
895-
add_tfunc(getfield, 2, 3, getfield_tfunc, 1)
896-
add_tfunc(setfield!, 3, 3, (@nospecialize(o), @nospecialize(f), @nospecialize(v)) -> v, 3)
897-
fieldtype_tfunc(@nospecialize(s0), @nospecialize(name), @nospecialize(inbounds)) =
898-
fieldtype_tfunc(s0, name)
912+
913+
setfield!_tfunc(o, f, v, order) = (@nospecialize; v)
914+
setfield!_tfunc(o, f, v) = (@nospecialize; v)
915+
916+
swapfield!_tfunc(o, f, v, order) = (@nospecialize; getfield_tfunc(o, f))
917+
swapfield!_tfunc(o, f, v) = (@nospecialize; getfield_tfunc(o, f))
918+
modifyfield!_tfunc(o, f, op, v, order) = (@nospecialize; T = getfield_tfunc(o, f); T === Bottom ? T : Tuple{T, T})
919+
modifyfield!_tfunc(o, f, op, v) = (@nospecialize; T = getfield_tfunc(o, f); T === Bottom ? T : Tuple{T, T}) # TODO: also model op(o.f, v) call
920+
replacefield!_tfunc(o, f, x, v, success_order, failure_order) = (@nospecialize; replacefield!_tfunc(o, f, x, v))
921+
replacefield!_tfunc(o, f, x, v, success_order) = (@nospecialize; replacefield!_tfunc(o, f, x, v))
922+
replacefield!_tfunc(o, f, x, v) = (@nospecialize; T = getfield_tfunc(o, f); T === Bottom ? T : Tuple{widenconst(T), Bool})
923+
# we could use tuple_tfunc instead of widenconst, but `o` is mutable, so that is unlikely to be beneficial
924+
925+
add_tfunc(getfield, 2, 4, getfield_tfunc, 1)
926+
add_tfunc(setfield!, 3, 4, setfield!_tfunc, 3)
927+
928+
add_tfunc(swapfield!, 3, 4, swapfield!_tfunc, 3)
929+
add_tfunc(modifyfield!, 4, 5, modifyfield!_tfunc, 3)
930+
add_tfunc(replacefield!, 4, 6, replacefield!_tfunc, 3)
899931

900932
function fieldtype_nothrow(@nospecialize(s0), @nospecialize(name))
901933
s0 === Bottom && return true # unreachable
@@ -954,6 +986,7 @@ function _fieldtype_nothrow(@nospecialize(s), exact::Bool, name::Const)
954986
return true
955987
end
956988

989+
fieldtype_tfunc(s0, name, boundscheck) = (@nospecialize; fieldtype_tfunc(s0, name))
957990
function fieldtype_tfunc(@nospecialize(s0), @nospecialize(name))
958991
if s0 === Bottom
959992
return Bottom

base/condition.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
@noinline function concurrency_violation()
66
# can be useful for debugging
77
#try; error(); catch; ccall(:jlbacktrace, Cvoid, ()); end
8-
error("concurrency violation detected")
8+
throw(ConcurrencyViolationError("lock must be held"))
99
end
1010

1111
"""

0 commit comments

Comments
 (0)