diff --git a/Make.inc b/Make.inc index 10f1b471fe7a6..19c9629e0423e 100644 --- a/Make.inc +++ b/Make.inc @@ -1243,7 +1243,7 @@ else ifneq ($(USEMSVC), 1) endif ifeq ($(OS), Linux) -OSLIBS += -Wl,--no-as-needed -ldl -lrt -lpthread -Wl,--export-dynamic,--as-needed,--no-whole-archive +OSLIBS += -Wl,--no-as-needed -ldl -lrt -lpthread -latomic -Wl,--export-dynamic,--as-needed,--no-whole-archive # Detect if ifunc is supported IFUNC_DETECT_SRC := 'void (*f0(void))(void) { return (void(*)(void))0L; }; void f(void) __attribute__((ifunc("f0")));' 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 ifeq ($(OS), FreeBSD) JLDFLAGS := -Wl,-Bdynamic -OSLIBS += -lelf -lkvm -lrt -lpthread +OSLIBS += -lelf -lkvm -lrt -lpthread -latomic # Tweak order of libgcc_s in DT_NEEDED, # make it loaded first to diff --git a/NEWS.md b/NEWS.md index fe085376609ab..ab7192af1fb87 100644 --- a/NEWS.md +++ b/NEWS.md @@ -42,10 +42,15 @@ Command-line option changes Multi-threading changes ----------------------- -* 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]) -* Every `Task` object has a local random number generator state, providing reproducible (schedule-independent) execution - of parallel simulation code by default. The default generator is also significantly faster in parallel than in - previous versions. +* Intrinsics for atomic pointer operations are now defined for certain byte sizes. ([#37847]) +* Support for declaring and using individual fields of a mutable struct as + atomic now available. ([#37847]) +* 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]) +* Every `Task` object has a local random number generator state, providing + reproducible (schedule-independent) execution of parallel simulation code by + default. The default generator is also significantly faster in parallel than + in previous versions. Build system changes diff --git a/base/Base.jl b/base/Base.jl index 1ffb747d982ae..42a506479326b 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -20,20 +20,48 @@ include(path::String) = include(Base, path) const is_primary_base_module = ccall(:jl_module_parent, Ref{Module}, (Any,), Base) === Core.Main ccall(:jl_set_istopmod, Cvoid, (Any, Bool), Base, is_primary_base_module) +# The real @inline macro is not available until after array.jl, so this +# internal macro splices the meta Expr directly into the function body. +macro _inline_meta() + Expr(:meta, :inline) +end +macro _noinline_meta() + Expr(:meta, :noinline) +end + # Try to help prevent users from shooting them-selves in the foot # with ambiguities by defining a few common and critical operations # (and these don't need the extra convert code) -getproperty(x::Module, f::Symbol) = getfield(x, f) -setproperty!(x::Module, f::Symbol, v) = setfield!(x, f, v) -getproperty(x::Type, f::Symbol) = getfield(x, f) -setproperty!(x::Type, f::Symbol, v) = setfield!(x, f, v) -getproperty(x::Tuple, f::Int) = getfield(x, f) +getproperty(x::Module, f::Symbol) = (@_inline_meta; getfield(x, f)) +setproperty!(x::Module, f::Symbol, v) = setfield!(x, f, v) # to get a decent error +getproperty(x::Type, f::Symbol) = (@_inline_meta; getfield(x, f)) +setproperty!(x::Type, f::Symbol, v) = error("setfield! fields of Types should not be changed") +getproperty(x::Tuple, f::Int) = (@_inline_meta; getfield(x, f)) setproperty!(x::Tuple, f::Int, v) = setfield!(x, f, v) # to get a decent error -getproperty(x, f::Symbol) = getfield(x, f) -dotgetproperty(x, f) = getproperty(x, f) +getproperty(x, f::Symbol) = (@_inline_meta; getfield(x, f)) setproperty!(x, f::Symbol, v) = setfield!(x, f, convert(fieldtype(typeof(x), f), v)) +dotgetproperty(x, f) = getproperty(x, f) + +getproperty(x::Module, f::Symbol, order::Symbol) = (@_inline_meta; getfield(x, f, order)) +setproperty!(x::Module, f::Symbol, v, order::Symbol) = setfield!(x, f, v, order) # to get a decent error +getproperty(x::Type, f::Symbol, order::Symbol) = (@_inline_meta; getfield(x, f, order)) +setproperty!(x::Type, f::Symbol, v, order::Symbol) = error("setfield! fields of Types should not be changed") +getproperty(x::Tuple, f::Int, order::Symbol) = (@_inline_meta; getfield(x, f, order)) +setproperty!(x::Tuple, f::Int, v, order::Symbol) = setfield!(x, f, v, order) # to get a decent error + +getproperty(x, f::Symbol, order::Symbol) = (@_inline_meta; getfield(x, f, order)) +setproperty!(x, f::Symbol, v, order::Symbol) = (@_inline_meta; setfield!(x, f, convert(fieldtype(typeof(x), f), v), order)) + +swapproperty!(x, f::Symbol, v, order::Symbol=:notatomic) = + (@_inline_meta; Core.swapfield!(x, f, convert(fieldtype(typeof(x), f), v), order)) +modifyproperty!(x, f::Symbol, op, v, order::Symbol=:notatomic) = + (@_inline_meta; Core.modifyfield!(x, f, op, v, order)) +replaceproperty!(x, f::Symbol, expected, desired, success_order::Symbol=:notatomic, fail_order::Symbol=success_order) = + (@_inline_meta; Core.replacefield!(x, f, expected, convert(fieldtype(typeof(x), f), desired), success_order, fail_order)) + + include("coreio.jl") eval(x) = Core.eval(Base, x) diff --git a/base/boot.jl b/base/boot.jl index 0d4c6cc57438e..dcf62a0b9cab2 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -187,11 +187,12 @@ export InterruptException, InexactError, OutOfMemoryError, ReadOnlyMemoryError, OverflowError, StackOverflowError, SegmentationFault, UndefRefError, UndefVarError, TypeError, ArgumentError, MethodError, AssertionError, LoadError, InitError, - UndefKeywordError, + UndefKeywordError, ConcurrencyViolationError, # AST representation Expr, QuoteNode, LineNumberNode, GlobalRef, # object model functions - fieldtype, getfield, setfield!, nfields, throw, tuple, ===, isdefined, eval, ifelse, + fieldtype, getfield, setfield!, swapfield!, modifyfield!, replacefield!, + nfields, throw, tuple, ===, isdefined, eval, ifelse, # sizeof # not exported, to avoid conflicting with Base.sizeof # type reflection <:, typeof, isa, typeassert, @@ -290,6 +291,9 @@ struct UndefRefError <: Exception end struct UndefVarError <: Exception var::Symbol end +struct ConcurrencyViolationError <: Exception + msg::AbstractString +end struct InterruptException <: Exception end struct DomainError <: Exception val diff --git a/base/compiler/compiler.jl b/base/compiler/compiler.jl index 3a2ee778cbe58..6a0362b78f759 100644 --- a/base/compiler/compiler.jl +++ b/base/compiler/compiler.jl @@ -10,6 +10,9 @@ import Core: print, println, show, write, unsafe_write, stdout, stderr, const getproperty = Core.getfield const setproperty! = Core.setfield! +const swapproperty! = Core.swapfield! +const modifyproperty! = Core.modifyfield! +const replaceproperty! = Core.replacefield! ccall(:jl_set_istopmod, Cvoid, (Any, Bool), Compiler, false) @@ -19,9 +22,14 @@ eval(m, x) = Core.eval(m, x) include(x) = Core.include(Compiler, x) include(mod, x) = Core.include(mod, x) -############# -# from Base # -############# +# The real @inline macro is not available until after array.jl, so this +# internal macro splices the meta Expr directly into the function body. +macro _inline_meta() + Expr(:meta, :inline) +end +macro _noinline_meta() + Expr(:meta, :noinline) +end # essential files and libraries include("essentials.jl") diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index 9983d9b94a44a..50483ffa79465 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -1,6 +1,5 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -@inline isexpr(@nospecialize(stmt), head::Symbol) = isa(stmt, Expr) && stmt.head === head Core.PhiNode() = Core.PhiNode(Int32[], Any[]) isterminator(@nospecialize(stmt)) = isa(stmt, GotoNode) || isa(stmt, GotoIfNot) || isa(stmt, ReturnNode) diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index 114b8c6033615..ca2331babb28c 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -560,14 +560,24 @@ function getfield_elim_pass!(ir::IRCode) #ndone += 1 result_t = compact_exprtype(compact, SSAValue(idx)) is_getfield = is_setfield = false + field_ordering = :unspecified is_ccall = false # Step 1: Check whether the statement we're looking at is a getfield/setfield! if is_known_call(stmt, setfield!, compact) is_setfield = true 4 <= length(stmt.args) <= 5 || continue + if length(stmt.args) == 5 + field_ordering = compact_exprtype(compact, stmt.args[5]) + end elseif is_known_call(stmt, getfield, compact) is_getfield = true - 3 <= length(stmt.args) <= 4 || continue + 3 <= length(stmt.args) <= 5 || continue + if length(stmt.args) == 5 + field_ordering = compact_exprtype(compact, stmt.args[5]) + elseif length(stmt.args) == 4 + field_ordering = compact_exprtype(compact, stmt.args[4]) + widenconst(field_ordering) === Bool && (field_ordering = :unspecified) + end elseif is_known_call(stmt, isa, compact) # TODO continue @@ -660,6 +670,11 @@ function getfield_elim_pass!(ir::IRCode) end isa(struct_typ, DataType) || continue + struct_typ.name.atomicfields == C_NULL || continue # TODO: handle more + if !(field_ordering === :unspecified || (field_ordering isa Const && field_ordering.val === :not_atomic)) + continue + end + def, typeconstraint = stmt.args[2], struct_typ if struct_typ.name.mutable diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 5ae32d31ec189..6e8f151a3de04 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -263,6 +263,7 @@ function isdefined_nothrow(argtypes::Array{Any, 1}) (argtypes[2] ⊑ Symbol || argtypes[2] ⊑ Int) : argtypes[2] ⊑ Symbol end +isdefined_tfunc(arg1, sym, order) = (@nospecialize; isdefined_tfunc(arg1, sym)) function isdefined_tfunc(@nospecialize(arg1), @nospecialize(sym)) if isa(arg1, Const) a1 = typeof(arg1.val) @@ -316,7 +317,7 @@ function isdefined_tfunc(@nospecialize(arg1), @nospecialize(sym)) end return Bool end -add_tfunc(isdefined, 2, 2, isdefined_tfunc, 1) +add_tfunc(isdefined, 2, 3, isdefined_tfunc, 1) function sizeof_nothrow(@nospecialize(x)) if isa(x, Const) @@ -470,22 +471,26 @@ add_tfunc(arraysize, 2, 2, (@nospecialize(a), @nospecialize(d))->Int, 4) function pointer_eltype(@nospecialize(ptr)) a = widenconst(ptr) if a <: Ptr - if isa(a,DataType) && isa(a.parameters[1],Type) + if isa(a, DataType) && isa(a.parameters[1], Type) return a.parameters[1] - elseif isa(a,UnionAll) && !has_free_typevars(a) + elseif isa(a, UnionAll) && !has_free_typevars(a) unw = unwrap_unionall(a) - if isa(unw,DataType) + if isa(unw, DataType) return rewrap_unionall(unw.parameters[1], a) end end end return Any end -add_tfunc(pointerref, 3, 3, - function (@nospecialize(a), @nospecialize(i), @nospecialize(align)) - return pointer_eltype(a) - end, 4) -add_tfunc(pointerset, 4, 4, (@nospecialize(a), @nospecialize(v), @nospecialize(i), @nospecialize(align)) -> a, 5) +add_tfunc(pointerref, 3, 3, (a, i, align) -> (@nospecialize; pointer_eltype(a)), 4) +add_tfunc(pointerset, 4, 4, (a, v, i, align) -> (@nospecialize; a), 5) + +add_tfunc(atomic_fence, 1, 1, (order) -> (@nospecialize; Nothing), 4) +add_tfunc(atomic_pointerref, 2, 2, (a, order) -> (@nospecialize; pointer_eltype(a)), 4) +add_tfunc(atomic_pointerset, 3, 3, (a, v, order) -> (@nospecialize; a), 5) +add_tfunc(atomic_pointerswap, 3, 3, (a, v, order) -> (@nospecialize; pointer_eltype(a)), 5) +add_tfunc(atomic_pointermodify, 4, 4, (a, op, v, order) -> (@nospecialize; T = pointer_eltype(a); Tuple{T, T}), 5) +add_tfunc(atomic_pointerreplace, 5, 5, (a, x, v, success_order, failure_order) -> (@nospecialize; Tuple{pointer_eltype(a), Bool}), 5) # more accurate typeof_tfunc for vararg tuples abstract only in length function typeof_concrete_vararg(t::DataType) @@ -675,14 +680,25 @@ function try_compute_fieldidx(typ::DataType, @nospecialize(field)) end function getfield_nothrow(argtypes::Vector{Any}) - 2 <= length(argtypes) <= 3 || return false - length(argtypes) == 2 && return getfield_nothrow(argtypes[1], argtypes[2], Const(true)) - return getfield_nothrow(argtypes[1], argtypes[2], argtypes[3]) + if length(argtypes) == 2 + boundscheck = Bool + elseif length(argtypes) == 3 + boundscheck = argtypes[3] + if boundscheck === Const(:not_atomic) # TODO: this is assuming not atomic + boundscheck = Bool + end + elseif length(argtypes) == 4 + boundscheck = argtypes[4] + else + return false + end + widenconst(boundscheck) !== Bool && return false + bounds_check_disabled = isa(boundscheck, Const) && boundscheck.val === false + return getfield_nothrow(argtypes[1], argtypes[2], !bounds_check_disabled) end -function getfield_nothrow(@nospecialize(s00), @nospecialize(name), @nospecialize(inbounds)) - bounds_check_disabled = isa(inbounds, Const) && inbounds.val === false - # If we don't have invounds and don't know the field, don't even bother - if !bounds_check_disabled +function getfield_nothrow(@nospecialize(s00), @nospecialize(name), boundscheck::Bool) + # If we don't have boundscheck and don't know the field, don't even bother + if boundscheck isa(name, Const) || return false end @@ -700,7 +716,7 @@ function getfield_nothrow(@nospecialize(s00), @nospecialize(name), @nospecialize end return isdefined(sv, name.val) end - if bounds_check_disabled && !isa(sv, Module) + if !boundscheck && !isa(sv, Module) # If bounds checking is disabled and all fields are assigned, # we may assume that we don't throw for i = 1:fieldcount(typeof(sv)) @@ -714,14 +730,15 @@ function getfield_nothrow(@nospecialize(s00), @nospecialize(name), @nospecialize s0 = widenconst(s00) s = unwrap_unionall(s0) if isa(s, Union) - return getfield_nothrow(rewrap(s.a, s00), name, inbounds) && - getfield_nothrow(rewrap(s.b, s00), name, inbounds) + return getfield_nothrow(rewrap(s.a, s00), name, boundscheck) && + getfield_nothrow(rewrap(s.b, s00), name, boundscheck) elseif isa(s, DataType) # Can't say anything about abstract types s.name.abstract && return false + s.name.atomicfields == C_NULL || return false # TODO: currently we're only testing for ordering == :not_atomic # If all fields are always initialized, and bounds check is disabled, we can assume # we don't throw - if bounds_check_disabled && !isvatuple(s) && s.name !== NamedTuple.body.body.name && fieldcount(s) == s.ninitialized + if !boundscheck && !isvatuple(s) && s.name !== NamedTuple.body.body.name && fieldcount(s) == s.ninitialized return true end # Else we need to know what the field is @@ -736,8 +753,8 @@ function getfield_nothrow(@nospecialize(s00), @nospecialize(name), @nospecialize return false end -getfield_tfunc(@nospecialize(s00), @nospecialize(name), @nospecialize(inbounds)) = - getfield_tfunc(s00, name) +getfield_tfunc(s00, name, boundscheck_or_order) = (@nospecialize; getfield_tfunc(s00, name)) +getfield_tfunc(s00, name, order, boundscheck) = (@nospecialize; getfield_tfunc(s00, name)) function getfield_tfunc(@nospecialize(s00), @nospecialize(name)) s = unwrap_unionall(s00) if isa(s, Union) @@ -892,10 +909,25 @@ function getfield_tfunc(@nospecialize(s00), @nospecialize(name)) end return rewrap_unionall(R, s00) end -add_tfunc(getfield, 2, 3, getfield_tfunc, 1) -add_tfunc(setfield!, 3, 3, (@nospecialize(o), @nospecialize(f), @nospecialize(v)) -> v, 3) -fieldtype_tfunc(@nospecialize(s0), @nospecialize(name), @nospecialize(inbounds)) = - fieldtype_tfunc(s0, name) + +setfield!_tfunc(o, f, v, order) = (@nospecialize; v) +setfield!_tfunc(o, f, v) = (@nospecialize; v) + +swapfield!_tfunc(o, f, v, order) = (@nospecialize; getfield_tfunc(o, f)) +swapfield!_tfunc(o, f, v) = (@nospecialize; getfield_tfunc(o, f)) +modifyfield!_tfunc(o, f, op, v, order) = (@nospecialize; T = getfield_tfunc(o, f); T === Bottom ? T : Tuple{T, T}) +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 +replacefield!_tfunc(o, f, x, v, success_order, failure_order) = (@nospecialize; replacefield!_tfunc(o, f, x, v)) +replacefield!_tfunc(o, f, x, v, success_order) = (@nospecialize; replacefield!_tfunc(o, f, x, v)) +replacefield!_tfunc(o, f, x, v) = (@nospecialize; T = getfield_tfunc(o, f); T === Bottom ? T : Tuple{widenconst(T), Bool}) +# we could use tuple_tfunc instead of widenconst, but `o` is mutable, so that is unlikely to be beneficial + +add_tfunc(getfield, 2, 4, getfield_tfunc, 1) +add_tfunc(setfield!, 3, 4, setfield!_tfunc, 3) + +add_tfunc(swapfield!, 3, 4, swapfield!_tfunc, 3) +add_tfunc(modifyfield!, 4, 5, modifyfield!_tfunc, 3) +add_tfunc(replacefield!, 4, 6, replacefield!_tfunc, 3) function fieldtype_nothrow(@nospecialize(s0), @nospecialize(name)) s0 === Bottom && return true # unreachable @@ -954,6 +986,7 @@ function _fieldtype_nothrow(@nospecialize(s), exact::Bool, name::Const) return true end +fieldtype_tfunc(s0, name, boundscheck) = (@nospecialize; fieldtype_tfunc(s0, name)) function fieldtype_tfunc(@nospecialize(s0), @nospecialize(name)) if s0 === Bottom return Bottom diff --git a/base/condition.jl b/base/condition.jl index cf0bca8d9dc46..be0f618865a48 100644 --- a/base/condition.jl +++ b/base/condition.jl @@ -5,7 +5,7 @@ @noinline function concurrency_violation() # can be useful for debugging #try; error(); catch; ccall(:jlbacktrace, Cvoid, ()); end - error("concurrency violation detected") + throw(ConcurrencyViolationError("lock must be held")) end """ diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index d45fbd3ee07d2..35e19e3742cf4 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -1914,10 +1914,14 @@ julia> Tuple(Real[1, 2, pi]) # takes a collection tuple """ - getfield(value, name::Symbol) - getfield(value, i::Int) - -Extract a field from a composite `value` by name or position. + getfield(value, name::Symbol, [order::Symbol]) + getfield(value, i::Int, [order::Symbol]) + +Extract a field from a composite `value` by name or position. Optionally, an +ordering can be defined for the operation. If the field was declared `@atomic`, +the specification is strongly recommended to be compatible with the stores to +that location. Otherwise, if not declared as `@atomic`, this parameter must be +`:not_atomic` if specified. See also [`getproperty`](@ref Base.getproperty) and [`fieldnames`](@ref). # Examples @@ -1938,10 +1942,14 @@ julia> getfield(a, 1) getfield """ - setfield!(value, name::Symbol, x) + setfield!(value, name::Symbol, x, [order::Symbol]) + setfield!(value, i::Int, x, [order::Symbol]) -Assign `x` to a named field in `value` of composite type. -The `value` must be mutable and `x` must be a subtype of `fieldtype(typeof(value), name)`. +Assign `x` to a named field in `value` of composite type. The `value` must be +mutable and `x` must be a subtype of `fieldtype(typeof(value), name)`. +Additionally, an ordering can be specified for this operation. If the field was +declared `@atomic`, this specification is mandatory. Otherwise, if not declared +as `@atomic`, it must be `:not_atomic` if specified. See also [`setproperty!`](@ref Base.setproperty!). # Examples @@ -1961,11 +1969,61 @@ julia> a = 1//2 1//2 julia> setfield!(a, :num, 3); -ERROR: setfield! immutable struct of type Rational cannot be changed +ERROR: setfield!: immutable struct of type Rational cannot be changed ``` """ setfield! +""" + swapfield!(value, name::Symbol, x, [order::Symbol]) + swapfield!(value, i::Int, x, [order::Symbol]) + +These atomically perform the operations to simultaneously get and set a field: + + y = getfield!(value, name) + setfield!(value, name, x) + return y +``` +""" +swapfield! + +""" + modifyfield!(value, name::Symbol, op, x, [order::Symbol]) + modifyfield!(value, i::Int, op, x, [order::Symbol]) + +These atomically perform the operations to get and set a field after applying +the function `op`. + + y = getfield!(value, name) + z = op(y, x) + setfield!(value, name, z) + return y, z + +If supported by the hardware (for example, atomic increment), this may be +optimized to the appropriate hardware instruction, otherwise it'll use a loop. +""" +modifyfield! + +""" + replacefield!(value, name::Symbol, cmp, expected, desired, + [success_order::Symbol, [fail_order::Symbol=success_order]) => + (old, Bool) + +These atomically perform the operations to get and conditionally set a field to +a given value. + + y = getfield!(value, name, fail_order) + ok = cmp(y, expected) + if ok + setfield!(value, name, desired, success_order) + end + return y, ok + +If the operation is `===` on a supported type, we'll use the relevant processor +instructions, otherwise it'll use a loop. +""" +replacefield! + """ typeof(x) @@ -1989,12 +2047,16 @@ Matrix{Float64} (alias for Array{Float64, 2}) typeof """ - isdefined(m::Module, s::Symbol) - isdefined(object, s::Symbol) - isdefined(object, index::Int) + isdefined(m::Module, s::Symbol, [order::Symbol]) + isdefined(object, s::Symbol, [order::Symbol]) + isdefined(object, index::Int, [order::Symbol]) -Tests whether a global variable or object field is defined. The arguments can be a module and a symbol -or a composite object and field name (as a symbol) or index. +Tests whether a global variable or object field is defined. The arguments can +be a module and a symbol or a composite object and field name (as a symbol) or +index. Optionally, an ordering can be defined for the operation. If the field +was declared `@atomic`, the specification is strongly recommended to be +compatible with the stores to that location. Otherwise, if not declared as +`@atomic`, this parameter must be `:not_atomic` if specified. To test whether an array element is defined, use [`isassigned`](@ref) instead. @@ -2582,8 +2644,11 @@ typeassert """ getproperty(value, name::Symbol) + getproperty(value, name::Symbol, order::Symbol) The syntax `a.b` calls `getproperty(a, :b)`. +The syntax `@atomic order a.b` calls `getproperty(a, :b, :order)` and +the syntax `@atomic a.b` calls `getproperty(a, :b, :sequentially_consistent)`. # Examples ```jldoctest @@ -2608,21 +2673,62 @@ julia> obj.x 1 ``` -See also [`propertynames`](@ref Base.propertynames) and +See also [`getfield`](@ref Core.getfield), +[`propertynames`](@ref Base.propertynames) and [`setproperty!`](@ref Base.setproperty!). """ Base.getproperty """ setproperty!(value, name::Symbol, x) + setproperty!(value, name::Symbol, x, order::Symbol) The syntax `a.b = c` calls `setproperty!(a, :b, c)`. +The syntax `@atomic order a.b = c` calls `setproperty!(a, :b, c, :order)` +and the syntax `@atomic a.b = c` calls `getproperty(a, :b, :sequentially_consistent)`. -See also [`propertynames`](@ref Base.propertynames) and +See also [`setfield!`](@ref Core.setfield!), +[`propertynames`](@ref Base.propertynames) and [`getproperty`](@ref Base.getproperty). """ Base.setproperty! +""" + swapproperty!(x, f::Symbol, v, order::Symbol=:not_atomic) + +The syntax `@atomic a.b, _ = c, a.b` returns `(c, swapproperty!(a, :b, c, :sequentially_consistent))`, +where there must be one getfield expression common to both sides. + +See also [`swapfield!`](@ref Core.swapfield!) +and [`setproperty!`](@ref Base.setproperty!). +""" +Base.swapproperty! + +""" + modifyproperty!(x, f::Symbol, op, v, order::Symbol=:not_atomic) + +The syntax `@atomic! max(a().b, c)` returns `modifyproperty!(a(), :b, +max, c, :sequentially_consistent))`, where the first argument must be a +`getfield` expression and is modified atomically. + +See also [`modifyfield!`](@ref Core.modifyfield!) +and [`setproperty!`](@ref Base.setproperty!). +""" +Base.modifyproperty! + +""" + replaceproperty!(x, f::Symbol, expected, desired, success_order::Symbol=:not_atomic, fail_order::Symbol=success_order) + +Perform a compare-and-swap operation on `x.f` from `expected` to `desired`, per +egal. The syntax `@atomic_replace! x.f expected => desired` can be used instead +of the function call form. + +See also [`replacefield!`](@ref Core.replacefield!) +and [`setproperty!`](@ref Base.setproperty!). +""" +Base.replaceproperty! + + """ StridedArray{T, N} diff --git a/base/essentials.jl b/base/essentials.jl index d9a9ab4b0d721..399630edb2685 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -42,16 +42,6 @@ pairs(::Type{NamedTuple}) = Pairs{Symbol, V, NTuple{N, Symbol}, NamedTuple{names #const NamedTuplePair{N, V, names, T<:NTuple{N, Any}} = Pairs{Symbol, V, NTuple{N, Symbol}, NamedTuple{names, T}} #export NamedTuplePair - -# The real @inline macro is not available until after array.jl, so this -# internal macro splices the meta Expr directly into the function body. -macro _inline_meta() - Expr(:meta, :inline) -end -macro _noinline_meta() - Expr(:meta, :noinline) -end - macro _gc_preserve_begin(arg1) Expr(:gc_preserve_begin, esc(arg1)) end diff --git a/base/exports.jl b/base/exports.jl index f27ac7f96c883..88933dad882ca 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -731,6 +731,9 @@ export convert, getproperty, setproperty!, + swapproperty!, + modifyproperty!, + replaceproperty!, fieldoffset, fieldname, fieldnames, @@ -1015,6 +1018,9 @@ export @polly, @assert, + @atomic, + @atomicswap, + @atomicreplace, @__dot__, @enum, @label, diff --git a/base/expr.jl b/base/expr.jl index 4d6401b002a76..94a4a3cbb059f 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -31,6 +31,9 @@ end ## expressions ## +isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === head +isexpr(@nospecialize(ex), head::Symbol, n::Int) = isa(ex, Expr) && ex.head === head && length(ex.args) == n + copy(e::Expr) = exprarray(e.head, copy_exprargs(e.args)) # copy parts of an AST that the compiler mutates @@ -408,7 +411,7 @@ the global scope or depending on mutable elements. See [Metaprogramming](@ref) for further details. ## Example: -```julia +```jldoctest julia> @generated function bar(x) if x <: Integer return :(x ^ 2) @@ -442,3 +445,225 @@ macro generated(f) error("invalid syntax; @generated must be used with a function definition") end end + + +""" + @atomic var + @atomic order ex + +Mark `var` or `ex` as being performed atomically, if `ex` is a supported expression. + + @atomic a.b.x = new + @atomic a.b.x += addend + @atomic :acquire_release a.b.x = new + @atomic :acquire_release a.b.x += addend + +Perform the store operation expressed on the right atomically and return the +new value. + +With `=`, this operation translates to a `setproperty!(a.b, :x, new)` call. +With any operator also, this operation translates to a `modifyproperty!(a.b, +:x, +, addend)[2]` call. + + @atomic a.b.x max arg2 + @atomic a.b.x + arg2 + @atomic max(a.b.x, arg2) + @atomic :acquire_release max(a.b.x, arg2) + @atomic :acquire_release a.b.x + arg2 + @atomic :acquire_release a.b.x max arg2 + +Perform the binary operation expressed on the right atomically. Store the +result into the field in the first argument and return the values `(old, new)`. + +This operation translates to a `modifyproperty!(a.b, :x, func, arg2)` call. + + +See [atomics](#man-atomics) in the manual for more details. + +```jldoctest +julia> mutable struct Atomic{T}; @atomic x::T; end + +julia> a = Atomic(1) +Atomic{Int64}(1) + +julia> @atomic a.x # fetch field x of a, with sequential consistency +1 + +julia> @atomic :sequentially_consistent a.x = 2 # set field x of a, with sequential consistency +2 + +julia> @atomic a.x += 1 # increment field x of a, with sequential consistency +3 + +julia> @atomic a.x + 1 # increment field x of a, with sequential consistency +(3, 4) + +julia> @atomic a.x # fetch field x of a, with sequential consistency +4 + +julia> @atomic max(a.x, 10) # change field x of a to the max value, with sequential consistency +(4, 10) + +julia> @atomic a.x max 5 # again change field x of a to the max value, with sequential consistency +(10, 10) +``` +""" +macro atomic(ex) + if !isa(ex, Symbol) && !is_expr(ex, :(::)) + return make_atomic(QuoteNode(:sequentially_consistent), ex) + end + return esc(Expr(:atomic, ex)) +end +macro atomic(order, ex) + order isa QuoteNode || (order = esc(order)) + return make_atomic(order, ex) +end +macro atomic(a1, op, a2) + return make_atomic(QuoteNode(:sequentially_consistent), a1, op, a2) +end +macro atomic(order, a1, op, a2) + order isa QuoteNode || (order = esc(order)) + return make_atomic(order, a1, op, a2) +end +function make_atomic(order, ex) + @nospecialize + if ex isa Expr + if isexpr(ex, :., 2) + l, r = esc(ex.args[1]), esc(ex.args[2]) + return :(getproperty($l, $r, $order)) + elseif isexpr(ex, :call, 3) + return make_atomic(order, ex.args[2], ex.args[1], ex.args[3]) + elseif ex.head === :(=) + l, r = ex.args[1], ex.args[2] + if is_expr(l, :., 2) + ll, lr = esc(l.args[1]), esc(l.args[2]) + return :(setproperty!($ll, $lr, $r, $order)) + end + end + if length(ex.args) == 2 + if ex.head === :(+=) + op = :+ + elseif ex.head === :(-=) + op = :- + elseif @isdefined string + shead = string(ex.head) + if endswith(shead, '=') + op = Symbol(shead[1:prevind(shead, end)]) + end + end + if @isdefined(op) + return Expr(:ref, make_atomic(order, ex.args[1], op, ex.args[2]), 2) + end + end + end + error("could not parse @atomic expression $ex") +end +function make_atomic(order, a1, op, a2) + @nospecialize + is_expr(a1, :., 2) || error("@atomic modify expression missing field access") + a1l, a1r, op, a2 = esc(a1.args[1]), esc(a1.args[2]), esc(op), esc(a2) + return :(modifyproperty!($a1l, $a1r, $op, $a2, $order)) +end + + +""" + @atomicswap a.b.x = new + @atomicswap :sequentially_consistent a.b.x = new + +Stores `new` into `a.b.x` and returns the old value of `a.b.x`. + +This operation translates to a `swapproperty!(a.b, :x, new)` call. + +See [atomics](#man-atomics) in the manual for more details. + +```jldoctest +julia> mutable struct Atomic{T}; @atomic x::T; end + +julia> a = Atomic(1) +Atomic{Int64}(1) + +julia> @atomicswap a.x = 2+2 # replace field x of a with 4, with sequential consistency +1 + +julia> @atomic a.x # fetch field x of a, with sequential consistency +4 +``` +""" +macro atomicswap(order, ex) + order isa QuoteNode || (order = esc(order)) + return make_atomicswap(order, ex) +end +macro atomicswap(ex) + return make_atomicswap(QuoteNode(:sequentially_consistent), ex) +end +function make_atomicswap(order, ex) + @nospecialize + is_expr(ex, :(=), 2) || error("@atomicswap expression missing assignment") + l, val = ex.args[1], esc(ex.args[2]) + is_expr(l, :., 2) || error("@atomicswap expression missing field access") + ll, lr = esc(l.args[1]), esc(l.args[2]) + return :(swapproperty!($ll, $lr, $val, $order)) +end + + +""" + @atomicreplace a.b.x expected => desired + @atomicreplace :sequentially_consistent a.b.x expected => desired + @atomicreplace :sequentially_consistent :monotonic a.b.x expected => desired + +Perform the conditional replacement expressed by the pair atomically, returning +the values `(old, success::Bool)`. Where `success` indicates whether the +replacement was completed. + +This operation translates to a `replaceproperty!(a.b, :x, expected, desired)` call. + +See [atomics](#man-atomics) in the manual for more details. + +```jldoctest +julia> mutable struct Atomic{T}; @atomic x::T; end + +julia> a = Atomic(1) +Atomic{Int64}(1) + +julia> @atomicreplace a.x 1 => 2 # replace field x of a with 2 if it was 1, with sequential consistency +(1, true) + +julia> @atomic a.x # fetch field x of a, with sequential consistency +2 + +julia> @atomicreplace a.x 1 => 2 # replace field x of a with 2 if it was 1, with sequential consistency +(2, false) + +julia> xchg = 2 => 0; # replace field x of a with 0 if it was 1, with sequential consistency + +julia> @atomicreplace a.x xchg +(2, true) + +julia> @atomic a.x # fetch field x of a, with sequential consistency +0 +``` +""" +macro atomicreplace(success_order, fail_order, ex, old_new) + fail_order isa QuoteNode || (fail_order = esc(fail_order)) + success_order isa QuoteNode || (success_order = esc(success_order)) + return make_atomicreplace(success_order, fail_order, ex, old_new) +end +macro atomicreplace(order, ex, old_new) + order isa QuoteNode || (order = esc(order)) + return make_atomicreplace(order, order, ex, old_new) +end +macro atomicreplace(ex, old_new) + return make_atomicreplace(QuoteNode(:sequentially_consistent), QuoteNode(:sequentially_consistent), ex, old_new) +end +function make_atomicreplace(success_order, fail_order, ex, old_new) + @nospecialize + is_expr(ex, :., 2) || error("@atomicreplace expression missing field access") + ll, lr = esc(ex.args[1]), esc(ex.args[2]) + if is_expr(old_new, :call, 3) && old_new.args[1] === :(=>) + exp, rep = esc(old_new.args[2]), esc(old_new.args[3]) + return :(replaceproperty!($ll, $lr, $exp, $rep, $success_order, $fail_order)) + else + old_new = esc(old_new) + return :(replaceproperty!($ll, $lr, $old_new::Pair..., $success_order, $fail_order)) + end +end diff --git a/base/meta.jl b/base/meta.jl index d4847dc476247..aaf29b551cd0d 100644 --- a/base/meta.jl +++ b/base/meta.jl @@ -19,6 +19,7 @@ export quot, @dump using Base: isidentifier, isoperator, isunaryoperator, isbinaryoperator, ispostfixoperator +import Base: isexpr """ Meta.quot(ex)::Expr @@ -73,9 +74,7 @@ julia> Meta.isexpr(ex, :call, 2) true ``` """ -isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === head isexpr(@nospecialize(ex), heads) = isa(ex, Expr) && in(ex.head, heads) -isexpr(@nospecialize(ex), head::Symbol, n::Int) = isa(ex, Expr) && ex.head === head && length(ex.args) == n isexpr(@nospecialize(ex), heads, n::Int) = isa(ex, Expr) && in(ex.head, heads) && length(ex.args) == n """ diff --git a/base/show.jl b/base/show.jl index fa69913ee1c3c..fcc3d32526f3a 100644 --- a/base/show.jl +++ b/base/show.jl @@ -1454,8 +1454,7 @@ function operator_associativity(s::Symbol) return :left end -is_expr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && (ex.head === head) -is_expr(@nospecialize(ex), head::Symbol, n::Int) = is_expr(ex, head) && length(ex.args) == n +const is_expr = isexpr is_quoted(ex) = false is_quoted(ex::QuoteNode) = true diff --git a/doc/src/base/multi-threading.md b/doc/src/base/multi-threading.md index 4f3e4e53634a9..cb8ad06488f1f 100644 --- a/doc/src/base/multi-threading.md +++ b/doc/src/base/multi-threading.md @@ -19,9 +19,27 @@ See also [Synchronization](@ref lib-task-sync). ## Atomic operations +```@docs +Base.@atomic +Base.@atomicswap +Base.@atomicreplace +``` + +!!! note + + The following APIs are fairly primitive, and will likely be exposed through an `unsafe_*`-like wrapper. + +``` +Core.Intrinsics.atomic_pointerref(pointer::Ptr{T}, order::Symbol) --> T +Core.Intrinsics.atomic_pointerset(pointer::Ptr{T}, new::T, order::Symbol) --> pointer +Core.Intrinsics.atomic_pointerswap(pointer::Ptr{T}, new::T, order::Symbol) --> old +Core.Intrinsics.atomic_pointermodify(pointer::Ptr{T}, function::(old::T,arg::S)->T, arg::S, order::Symbol) --> old +Core.Intrinsics.atomic_pointerreplace(pointer::Ptr{T}, expected::Any, new::T, success_order::Symbol, failure_order::Symbol) --> (old, cmp) +``` + !!! warning - The API for atomic operations has not yet been finalized and is likely to change. + The following APIs are deprecated, though support for them is likely to remain for several releases. ```@docs Base.Threads.Atomic diff --git a/doc/src/manual/multi-threading.md b/doc/src/manual/multi-threading.md index 952e7acea35ab..658bec21bbfb9 100644 --- a/doc/src/manual/multi-threading.md +++ b/doc/src/manual/multi-threading.md @@ -8,7 +8,7 @@ of Julia multi-threading features. By default, Julia starts up with a single thread of execution. This can be verified by using the command [`Threads.nthreads()`](@ref): -```julia-repl +```jldoctest julia> Threads.nthreads() 1 ``` @@ -37,7 +37,7 @@ julia> Threads.nthreads() But we are currently on the master thread. To check, we use the function [`Threads.threadid`](@ref) -```julia-repl +```jldoctest julia> Threads.threadid() 1 ``` @@ -147,7 +147,7 @@ to its assigned locations: ```julia-repl julia> a -10-element Array{Float64,1}: +10-element Vector{Float64}: 1.0 1.0 1.0 @@ -182,14 +182,17 @@ julia> Threads.@threads for id in 1:4 end julia> old_is -4-element Array{Float64,1}: +4-element Vector{Float64}: 0.0 1.0 7.0 3.0 +julia> i[] + 10 + julia> ids -4-element Array{Float64,1}: +4-element Vector{Float64}: 1.0 2.0 3.0 @@ -227,11 +230,25 @@ julia> acc[] 1000 ``` -!!! note - Not *all* primitive types can be wrapped in an `Atomic` tag. Supported types - are `Int8`, `Int16`, `Int32`, `Int64`, `Int128`, `UInt8`, `UInt16`, `UInt32`, - `UInt64`, `UInt128`, `Float16`, `Float32`, and `Float64`. Additionally, - `Int128` and `UInt128` are not supported on AAarch32 and ppc64le. + +## [Per-field atomics](@id man-atomics) + +We can also use atomics on a more granular level using the [`@atomic`](@ref +Base.@atomic), [`@atomicswap`](@ref Base.@atomicswap), and +[`@atomicreplace`](@ref Base.@atomicreplace) macros. + +Specific details of the memory model and other details of the design are written +in the [Julia Atomics +Manifesto](https://gist.github.com/vtjnash/11b0031f2e2a66c9c24d33e810b34ec0), +which will later be published formally. + +Any field in a struct declaration can be decorated with `@atomic`, and then any +write must be marked with `@atomic` also, and must use one of the defined atomic +orderings (:monotonic, :acquire, :release, :acquire\_release, or +:sequentially\_consistent). Any read of an atomic field can also be annotated +with an atomic ordering constraint, or will be done with monotonic (relaxed) +ordering if unspecified. + ## Side effects and mutable function arguments @@ -241,6 +258,7 @@ For instance functions that have a [name ending with `!`](@ref bang-convention) by convention modify their arguments and thus are not pure. + ## @threadcall External libraries, such as those called via [`ccall`](@ref), pose a problem for diff --git a/src/array.c b/src/array.c index 20ed168d54fce..7c07ddd2ca3f9 100644 --- a/src/array.c +++ b/src/array.c @@ -20,27 +20,31 @@ extern "C" { #define JL_ARRAY_ALIGN(jl_value, nbytes) LLT_ALIGN(jl_value, nbytes) -// this is a version of memcpy that preserves atomic memory ordering -// which makes it safe to use for objects that can contain memory references -// without risk of creating pointers out of thin air -// TODO: replace with LLVM's llvm.memmove.element.unordered.atomic.p0i8.p0i8.i32 -// aka `__llvm_memmove_element_unordered_atomic_8` (for 64 bit) -static void memmove_refs(void **dstp, void *const *srcp, size_t n) JL_NOTSAFEPOINT +static inline void arrayassign_safe(int hasptr, jl_value_t *parent, char *dst, const jl_value_t *src, size_t nb) JL_NOTSAFEPOINT { - size_t i; - if (dstp < srcp || dstp > srcp + n) { - for (i = 0; i < n; i++) { - jl_atomic_store_relaxed(dstp + i, jl_atomic_load_relaxed(srcp + i)); - } + // array can assume more alignment than a field would normally have + assert(nb >= jl_datatype_size(jl_typeof(src))); // nb might move some undefined bits, but we should be okay with that + if (hasptr) { + size_t nptr = nb / sizeof(void*); + memmove_refs((void**)dst, (void**)src, nptr); + jl_gc_multi_wb(parent, src); } else { - for (i = 0; i < n; i++) { - jl_atomic_store_relaxed(dstp + n - i - 1, jl_atomic_load_relaxed(srcp + n - i - 1)); + switch (nb) { + case 0: break; + case 1: *(uint8_t*)dst = *(uint8_t*)src; break; + case 2: *(uint16_t*)dst = *(uint16_t*)src; break; + case 4: *(uint32_t*)dst = *(uint32_t*)src; break; + case 8: *(uint64_t*)dst = *(uint64_t*)src; break; + case 16: + memcpy(jl_assume_aligned(dst, 16), jl_assume_aligned(src, 16), 16); + break; + default: memcpy(dst, src, nb); } } } -static void memmove_safe(int hasptr, char *dst, const char *src, size_t nb) JL_NOTSAFEPOINT +static inline void memmove_safe(int hasptr, char *dst, const char *src, size_t nb) JL_NOTSAFEPOINT { if (hasptr) memmove_refs((void**)dst, (void**)src, nb / sizeof(void*)); @@ -596,7 +600,10 @@ JL_DLLEXPORT jl_value_t *jl_arrayref(jl_array_t *a, size_t i) if (jl_is_datatype_singleton((jl_datatype_t*)eltype)) return ((jl_datatype_t*)eltype)->instance; } - return undefref_check((jl_datatype_t*)eltype, jl_new_bits(eltype, &((char*)a->data)[i * a->elsize])); + jl_value_t *r = undefref_check((jl_datatype_t*)eltype, jl_new_bits(eltype, &((char*)a->data)[i * a->elsize])); + if (__unlikely(r == NULL)) + jl_throw(jl_undefref_exception); + return r; } JL_DLLEXPORT int jl_array_isassigned(jl_array_t *a, size_t i) @@ -624,6 +631,7 @@ JL_DLLEXPORT void jl_arrayset(jl_array_t *a JL_ROOTING_ARGUMENT, jl_value_t *rhs JL_GC_POP(); } if (!a->flags.ptrarray) { + int hasptr; if (jl_is_uniontype(eltype)) { uint8_t *psel = &((uint8_t*)jl_array_typetagdata(a))[i]; unsigned nth = 0; @@ -632,15 +640,12 @@ JL_DLLEXPORT void jl_arrayset(jl_array_t *a JL_ROOTING_ARGUMENT, jl_value_t *rhs *psel = nth; if (jl_is_datatype_singleton((jl_datatype_t*)jl_typeof(rhs))) return; - } - if (a->flags.hasptr) { - memmove_refs((void**)&((char*)a->data)[i * a->elsize], (void**)rhs, a->elsize / sizeof(void*)); + hasptr = 0; } else { - jl_assign_bits(&((char*)a->data)[i * a->elsize], rhs); + hasptr = a->flags.hasptr; } - if (a->flags.hasptr) - jl_gc_multi_wb(jl_array_owner(a), rhs); + arrayassign_safe(hasptr, jl_array_owner(a), &((char*)a->data)[i * a->elsize], rhs, a->elsize); } else { jl_atomic_store_relaxed(((jl_value_t**)a->data) + i, rhs); diff --git a/src/ast.c b/src/ast.c index e7048eabc6598..9a06419332c2e 100644 --- a/src/ast.c +++ b/src/ast.c @@ -69,6 +69,16 @@ jl_sym_t *optlevel_sym; jl_sym_t *thismodule_sym; jl_sym_t *atom_sym; jl_sym_t *statement_sym; jl_sym_t *all_sym; jl_sym_t *compile_sym; jl_sym_t *infer_sym; +jl_sym_t *atomic_sym; +jl_sym_t *not_atomic_sym; +jl_sym_t *unordered_sym; +jl_sym_t *monotonic_sym; +jl_sym_t *acquire_sym; +jl_sym_t *release_sym; +jl_sym_t *acquire_release_sym; +jl_sym_t *sequentially_consistent_sym; + + static uint8_t flisp_system_image[] = { #include }; @@ -410,6 +420,14 @@ void jl_init_common_symbols(void) atom_sym = jl_symbol("atom"); statement_sym = jl_symbol("statement"); all_sym = jl_symbol("all"); + atomic_sym = jl_symbol("atomic"); + not_atomic_sym = jl_symbol("not_atomic"); + unordered_sym = jl_symbol("unordered"); + monotonic_sym = jl_symbol("monotonic"); + acquire_sym = jl_symbol("acquire"); + release_sym = jl_symbol("release"); + acquire_release_sym = jl_symbol("acquire_release"); + sequentially_consistent_sym = jl_symbol("sequentially_consistent"); } JL_DLLEXPORT void jl_lisp_prompt(void) diff --git a/src/ast.scm b/src/ast.scm index d89cae95ad185..005afb4647f5f 100644 --- a/src/ast.scm +++ b/src/ast.scm @@ -363,6 +363,12 @@ (define (decl? e) (and (pair? e) (eq? (car e) '|::|))) +(define (symdecl? e) + (or (symbol? e) (decl? e))) + +(define (eventually-decl? e) + (or (decl? e) (and (pair? e) (eq? (car e) 'atomic) (symdecl? (cadr e))))) + (define (make-decl n t) `(|::| ,n ,t)) (define (ssavalue? e) diff --git a/src/atomics.h b/src/atomics.h index d3aa9d8ba8b3b..4a33368745aa1 100644 --- a/src/atomics.h +++ b/src/atomics.h @@ -20,6 +20,19 @@ #endif #include +enum jl_memory_order { + jl_memory_order_unspecified = -2, + jl_memory_order_invalid = -1, + jl_memory_order_notatomic = 0, + jl_memory_order_unordered, + jl_memory_order_monotonic, + jl_memory_order_consume, + jl_memory_order_acquire, + jl_memory_order_release, + jl_memory_order_acq_rel, + jl_memory_order_seq_cst +}; + /** * Thread synchronization primitives: * @@ -30,23 +43,18 @@ * synchronization in order to lower the mutator overhead as much as possible. * * We use the compiler intrinsics to implement a similar API to the c11/c++11 - * one instead of using it directly because, - * - * 1. We support GCC 4.7 and GCC add support for c11 atomics in 4.9. - * Luckily, the __atomic intrinsics were added in GCC 4.7. - * 2. (most importantly) we need interoperability between code written - * in different languages. - * The current c++ standard (c++14) does not allow using c11 atomic - * functions or types and there's currently no guarantee that the two - * types are compatible (although most of them probably are). - * We also need to access these atomic variables from the LLVM JIT code - * which is very hard unless the layout of the object is fully - * specified. + * one instead of using it directly because, we need interoperability between + * code written in different languages. The current c++ standard (c++14) does + * not allow using c11 atomic functions or types and there's currently no + * guarantee that the two types are compatible (although most of them probably + * are). We also need to access these atomic variables from the LLVM JIT code + * which is very hard unless the layout of the object is fully specified. */ -#if defined(__GNUC__) -# define jl_fence() __atomic_thread_fence(__ATOMIC_SEQ_CST) -# define jl_fence_release() __atomic_thread_fence(__ATOMIC_RELEASE) -# define jl_signal_fence() __atomic_signal_fence(__ATOMIC_SEQ_CST) +#define jl_fence() __atomic_thread_fence(__ATOMIC_SEQ_CST) +#define jl_fence_release() __atomic_thread_fence(__ATOMIC_RELEASE) +#define jl_signal_fence() __atomic_signal_fence(__ATOMIC_SEQ_CST) + + # define jl_atomic_fetch_add_relaxed(obj, arg) \ __atomic_fetch_add(obj, arg, __ATOMIC_RELAXED) # define jl_atomic_fetch_add(obj, arg) \ @@ -61,22 +69,20 @@ __atomic_fetch_or(obj, arg, __ATOMIC_RELAXED) # define jl_atomic_fetch_or(obj, arg) \ __atomic_fetch_or(obj, arg, __ATOMIC_SEQ_CST) -// Returns the original value of `obj` -// Use the legacy __sync builtins for now, this can also be written using -// the __atomic builtins or c11 atomics with GNU extension or c11 _Generic -# define jl_atomic_compare_exchange(obj, expected, desired) \ - __sync_val_compare_and_swap(obj, expected, desired) -# define jl_atomic_bool_compare_exchange(obj, expected, desired) \ - __sync_bool_compare_and_swap(obj, expected, desired) +# define jl_atomic_cmpswap(obj, expected, desired) \ + __atomic_compare_exchange_n(obj, expected, desired, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) +# define jl_atomic_cmpswap_relaxed(obj, expected, desired) \ + __atomic_compare_exchange_n(obj, expected, desired, 0, __ATOMIC_RELAXED, __ATOMIC_RELAXED) +// TODO: Maybe add jl_atomic_cmpswap_weak for spin lock # define jl_atomic_exchange(obj, desired) \ __atomic_exchange_n(obj, desired, __ATOMIC_SEQ_CST) # define jl_atomic_exchange_relaxed(obj, desired) \ __atomic_exchange_n(obj, desired, __ATOMIC_RELAXED) -// TODO: Maybe add jl_atomic_compare_exchange_weak for spin lock # define jl_atomic_store(obj, val) \ __atomic_store_n(obj, val, __ATOMIC_SEQ_CST) # define jl_atomic_store_relaxed(obj, val) \ __atomic_store_n(obj, val, __ATOMIC_RELAXED) + # if defined(__clang__) || defined(__ICC) || defined(__INTEL_COMPILER) || \ !(defined(_CPU_X86_) || defined(_CPU_X86_64_)) // ICC and Clang doesn't have this bug... @@ -86,6 +92,7 @@ // Workaround a GCC bug when using store with release order by using the // stronger version instead. // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67458 +// fixed in https://gcc.gnu.org/git/?p=gcc.git&a=commit;h=d8c40eff56f69877b33c697ded756d50fde90c27 # define jl_atomic_store_release(obj, val) do { \ jl_signal_fence(); \ __atomic_store_n(obj, val, __ATOMIC_RELEASE); \ @@ -105,237 +112,50 @@ # define jl_atomic_load_relaxed(obj) \ __atomic_load_n(obj, __ATOMIC_RELAXED) #endif -#elif defined(_COMPILER_MICROSOFT_) -// TODO: these only define compiler barriers, and aren't correct outside of x86 -# define jl_fence() _ReadWriteBarrier() -# define jl_fence_release() _WriteBarrier() -# define jl_signal_fence() _ReadWriteBarrier() - -// add -template -static inline typename std::enable_if::type -jl_atomic_fetch_add(T *obj, T2 arg) -{ - return (T)_InterlockedExchangeAdd8((volatile char*)obj, (char)arg); -} -template -static inline typename std::enable_if::type -jl_atomic_fetch_add(T *obj, T2 arg) -{ - return (T)_InterlockedExchangeAdd16((volatile short*)obj, (short)arg); -} -template -static inline typename std::enable_if::type -jl_atomic_fetch_add(T *obj, T2 arg) -{ - return (T)_InterlockedExchangeAdd((volatile LONG*)obj, (LONG)arg); -} -template -static inline typename std::enable_if::type -jl_atomic_fetch_add(T *obj, T2 arg) -{ - return (T)_InterlockedExchangeAdd64((volatile __int64*)obj, (__int64)arg); -} -#define jl_atomic_fetch_add_relaxed(obj, arg) jl_atomic_fetch_add(obj, arg) - -// and -template -static inline typename std::enable_if::type -jl_atomic_fetch_and(T *obj, T2 arg) -{ - return (T)_InterlockedAnd8((volatile char*)obj, (char)arg); -} -template -static inline typename std::enable_if::type -jl_atomic_fetch_and(T *obj, T2 arg) -{ - return (T)_InterlockedAnd16((volatile short*)obj, (short)arg); -} -template -static inline typename std::enable_if::type -jl_atomic_fetch_and(T *obj, T2 arg) -{ - return (T)_InterlockedAnd((volatile LONG*)obj, (LONG)arg); -} -template -static inline typename std::enable_if::type -jl_atomic_fetch_and(T *obj, T2 arg) -{ - return (T)_InterlockedAnd64((volatile __int64*)obj, (__int64)arg); -} -#define jl_atomic_fetch_and_relaxed(obj, arg) jl_atomic_fetch_and(obj, arg) - -// or -template -static inline typename std::enable_if::type -jl_atomic_fetch_or(T *obj, T2 arg) -{ - return (T)_InterlockedOr8((volatile char*)obj, (char)arg); -} -template -static inline typename std::enable_if::type -jl_atomic_fetch_or(T *obj, T2 arg) -{ - return (T)_InterlockedOr16((volatile short*)obj, (short)arg); -} -template -static inline typename std::enable_if::type -jl_atomic_fetch_or(T *obj, T2 arg) -{ - return (T)_InterlockedOr((volatile LONG*)obj, (LONG)arg); -} -template -static inline typename std::enable_if::type -jl_atomic_fetch_or(T *obj, T2 arg) -{ - return (T)_InterlockedOr64((volatile __int64*)obj, (__int64)arg); -} -#define jl_atomic_fetch_or_relaxed(obj, arg) jl_atomic_fetch_or(obj, arg) - -// Returns the original value of `obj` -template -static inline typename std::enable_if::type -jl_atomic_compare_exchange(volatile T *obj, T2 expected, T3 desired) -{ - return (T)_InterlockedCompareExchange8((volatile char*)obj, - (char)desired, (char)expected); -} -template -static inline typename std::enable_if::type -jl_atomic_compare_exchange(volatile T *obj, T2 expected, T3 desired) -{ - return (T)_InterlockedCompareExchange16((volatile short*)obj, - (short)desired, (short)expected); -} -template -static inline typename std::enable_if::type -jl_atomic_compare_exchange(volatile T *obj, T2 expected, T3 desired) -{ - return (T)_InterlockedCompareExchange((volatile LONG*)obj, - (LONG)desired, (LONG)expected); -} -template -static inline typename std::enable_if::type -jl_atomic_compare_exchange(volatile T *obj, T2 expected, T3 desired) -{ - return (T)_InterlockedCompareExchange64((volatile __int64*)obj, - (__int64)desired, (__int64)expected); -} -// TODO: jl_atomic_bool_compare_exchange -// atomic exchange -template -static inline typename std::enable_if::type -jl_atomic_exchange(volatile T *obj, T2 val) -{ - return _InterlockedExchange8((volatile char*)obj, (char)val); -} -template -static inline typename std::enable_if::type -jl_atomic_exchange(volatile T *obj, T2 val) -{ - return _InterlockedExchange16((volatile short*)obj, (short)val); -} -template -static inline typename std::enable_if::type -jl_atomic_exchange(volatile T *obj, T2 val) -{ - return _InterlockedExchange((volatile LONG*)obj, (LONG)val); -} -template -static inline typename std::enable_if::type -jl_atomic_exchange(volatile T *obj, T2 val) -{ - return _InterlockedExchange64((volatile __int64*)obj, (__int64)val); -} -#define jl_atomic_exchange_relaxed(obj, val) jl_atomic_exchange(obj, val) -// atomic stores -template -static inline typename std::enable_if::type -jl_atomic_store(volatile T *obj, T2 val) -{ - _InterlockedExchange8((volatile char*)obj, (char)val); -} -template -static inline typename std::enable_if::type -jl_atomic_store(volatile T *obj, T2 val) -{ - _InterlockedExchange16((volatile short*)obj, (short)val); -} -template -static inline typename std::enable_if::type -jl_atomic_store(volatile T *obj, T2 val) -{ - _InterlockedExchange((volatile LONG*)obj, (LONG)val); -} -template -static inline typename std::enable_if::type -jl_atomic_store(volatile T *obj, T2 val) -{ - _InterlockedExchange64((volatile __int64*)obj, (__int64)val); -} -template -static inline void jl_atomic_store_release(volatile T *obj, T2 val) -{ - jl_signal_fence(); - *obj = (T)val; -} -template -static inline void jl_atomic_store_relaxed(volatile T *obj, T2 val) -{ - *obj = (T)val; -} -// atomic loads -template -static inline T jl_atomic_load(volatile T *obj) -{ - // Trick to generate cheaper instructions compare to `_InterlockedOr` - // Note that we don't care whether the exchange succeeded or not... - return jl_atomic_compare_exchange(obj, T(0), T(0)); -} -template -static inline T jl_atomic_load_acquire(volatile T *obj) -{ - T val = *obj; - jl_signal_fence(); - return val; -} -#else -# error "No atomic operations supported." -#endif #ifdef __clang_analyzer__ // for the purposes of the analyzer, we can turn these into non-atomic expressions with similar properties +// (for the sake of the analyzer, we don't care if it is an exact match for behavior) #undef jl_atomic_exchange #undef jl_atomic_exchange_relaxed #define jl_atomic_exchange(obj, desired) \ (__extension__({ \ - __typeof__((obj)) p = (obj); \ - __typeof__(*p) temp = *p; \ - *p = desired; \ - temp; \ + __typeof__((obj)) p__analyzer__ = (obj); \ + __typeof__(*p__analyzer__) temp__analyzer__ = *p__analyzer__; \ + *p__analyzer__ = (desired); \ + temp__analyzer__; \ })) #define jl_atomic_exchange_relaxed jl_atomic_exchange -#undef jl_atomic_compare_exchange -#define jl_atomic_compare_exchange(obj, expected, desired) ((expected), jl_atomic_exchange((obj), (desired))) - -#undef jl_atomic_bool_compare_exchange -#define jl_atomic_bool_compare_exchange(obj, expected, desired) ((expected) == jl_atomic_exchange((obj), (desired))) +#undef jl_atomic_cmpswap +#undef jl_atomic_cmpswap_relaxed +#define jl_atomic_cmpswap(obj, expected, desired) \ + (__extension__({ \ + __typeof__((obj)) p__analyzer__ = (obj); \ + __typeof__(*p__analyzer__) temp__analyzer__ = *p__analyzer__; \ + __typeof__((expected)) x__analyzer__ = (expected); \ + if (temp__analyzer__ == *x__analyzer__) \ + *p__analyzer__ = (desired); \ + else \ + *x__analyzer__ = temp__analyzer__; \ + temp__analyzer__ == *x__analyzer__; \ + })) +#define jl_atomic_cmpswap_relaxed jl_atomic_cmpswap #undef jl_atomic_store #undef jl_atomic_store_release #undef jl_atomic_store_relaxed #define jl_atomic_store(obj, val) (*(obj) = (val)) -#define jl_atomic_store_release(obj, val) (*(obj) = (val)) -#define jl_atomic_store_relaxed(obj, val) (*(obj) = (val)) +#define jl_atomic_store_release jl_atomic_store +#define jl_atomic_store_relaxed jl_atomic_store #undef jl_atomic_load #undef jl_atomic_load_acquire #undef jl_atomic_load_relaxed #define jl_atomic_load(obj) (*(obj)) -#define jl_atomic_load_acquire(obj) (*(obj)) -#define jl_atomic_load_relaxed(obj) (*(obj)) +#define jl_atomic_load_acquire jl_atomic_load +#define jl_atomic_load_relaxed jl_atomic_load #endif diff --git a/src/builtin_proto.h b/src/builtin_proto.h index c4d6166a5c194..49d3cd7fe87e1 100644 --- a/src/builtin_proto.h +++ b/src/builtin_proto.h @@ -19,22 +19,38 @@ extern "C" { extern jl_value_t *jl_builtin_##name #endif -DECLARE_BUILTIN(throw); DECLARE_BUILTIN(is); -DECLARE_BUILTIN(typeof); DECLARE_BUILTIN(sizeof); -DECLARE_BUILTIN(issubtype); DECLARE_BUILTIN(isa); +DECLARE_BUILTIN(applicable); +DECLARE_BUILTIN(_apply_iterate); DECLARE_BUILTIN(_apply_pure); -DECLARE_BUILTIN(_call_latest); DECLARE_BUILTIN(_apply_iterate); +DECLARE_BUILTIN(apply_type); +DECLARE_BUILTIN(arrayref); +DECLARE_BUILTIN(arrayset); +DECLARE_BUILTIN(arraysize); DECLARE_BUILTIN(_call_in_world); -DECLARE_BUILTIN(isdefined); DECLARE_BUILTIN(nfields); -DECLARE_BUILTIN(tuple); DECLARE_BUILTIN(svec); -DECLARE_BUILTIN(getfield); DECLARE_BUILTIN(setfield); -DECLARE_BUILTIN(fieldtype); DECLARE_BUILTIN(arrayref); +DECLARE_BUILTIN(_call_latest); +DECLARE_BUILTIN(replacefield); DECLARE_BUILTIN(const_arrayref); -DECLARE_BUILTIN(arrayset); DECLARE_BUILTIN(arraysize); -DECLARE_BUILTIN(apply_type); DECLARE_BUILTIN(applicable); -DECLARE_BUILTIN(invoke); DECLARE_BUILTIN(_expr); -DECLARE_BUILTIN(typeassert); DECLARE_BUILTIN(ifelse); -DECLARE_BUILTIN(_typevar); DECLARE_BUILTIN(_typebody); +DECLARE_BUILTIN(_expr); +DECLARE_BUILTIN(fieldtype); +DECLARE_BUILTIN(getfield); +DECLARE_BUILTIN(ifelse); +DECLARE_BUILTIN(invoke); +DECLARE_BUILTIN(is); +DECLARE_BUILTIN(isa); +DECLARE_BUILTIN(isdefined); +DECLARE_BUILTIN(issubtype); +DECLARE_BUILTIN(modifyfield); +DECLARE_BUILTIN(nfields); +DECLARE_BUILTIN(setfield); +DECLARE_BUILTIN(sizeof); +DECLARE_BUILTIN(svec); +DECLARE_BUILTIN(swapfield); +DECLARE_BUILTIN(throw); +DECLARE_BUILTIN(tuple); +DECLARE_BUILTIN(typeassert); +DECLARE_BUILTIN(_typebody); +DECLARE_BUILTIN(typeof); +DECLARE_BUILTIN(_typevar); JL_CALLABLE(jl_f_invoke_kwsorter); JL_CALLABLE(jl_f__structtype); diff --git a/src/builtins.c b/src/builtins.c index 31165ff556976..5e343eff06c75 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -32,7 +32,7 @@ extern "C" { // egal and object_id --------------------------------------------------------- -static int bits_equal(void *a, void *b, int sz) JL_NOTSAFEPOINT +static int bits_equal(const void *a, const void *b, int sz) JL_NOTSAFEPOINT { switch (sz) { case 1: return *(int8_t*)a == *(int8_t*)b; @@ -76,7 +76,7 @@ static int NOINLINE compare_svec(jl_svec_t *a, jl_svec_t *b) JL_NOTSAFEPOINT } // See comment above for an explanation of NOINLINE. -static int NOINLINE compare_fields(jl_value_t *a, jl_value_t *b, jl_datatype_t *dt) JL_NOTSAFEPOINT +static int NOINLINE compare_fields(const jl_value_t *a, const jl_value_t *b, jl_datatype_t *dt) JL_NOTSAFEPOINT { size_t f, nf = jl_datatype_nfields(dt); for (f = 0; f < nf; f++) { @@ -126,7 +126,7 @@ static int NOINLINE compare_fields(jl_value_t *a, jl_value_t *b, jl_datatype_t * return 1; } -static int egal_types(jl_value_t *a, jl_value_t *b, jl_typeenv_t *env, int tvar_names) JL_NOTSAFEPOINT +static int egal_types(const jl_value_t *a, const jl_value_t *b, jl_typeenv_t *env, int tvar_names) JL_NOTSAFEPOINT { if (a == b) return 1; @@ -192,13 +192,13 @@ JL_DLLEXPORT int jl_types_egal(jl_value_t *a, jl_value_t *b) return egal_types(a, b, NULL, 0); } -JL_DLLEXPORT int (jl_egal)(jl_value_t *a JL_MAYBE_UNROOTED, jl_value_t *b JL_MAYBE_UNROOTED) JL_NOTSAFEPOINT +JL_DLLEXPORT int (jl_egal)(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED) JL_NOTSAFEPOINT { // warning: a,b may NOT have been gc-rooted by the caller return jl_egal(a, b); } -int jl_egal__special(jl_value_t *a JL_MAYBE_UNROOTED, jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT +int jl_egal__special(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT { if (dt == jl_simplevector_type) return compare_svec((jl_svec_t*)a, (jl_svec_t*)b); @@ -221,7 +221,7 @@ int jl_egal__special(jl_value_t *a JL_MAYBE_UNROOTED, jl_value_t *b JL_MAYBE_UNR return 0; } -int jl_egal__bits(jl_value_t *a JL_MAYBE_UNROOTED, jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT +int jl_egal__bits(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT { size_t sz = jl_datatype_size(dt); if (sz == 0) @@ -784,7 +784,7 @@ JL_CALLABLE(jl_f_tuple) jl_task_t *ct = jl_current_task; jl_value_t *jv = jl_gc_alloc(ct->ptls, jl_datatype_size(tt), tt); for (i = 0; i < nargs; i++) - set_nth_field(tt, (void*)jv, i, args[i]); + set_nth_field(tt, jv, i, args[i], 0); return jv; } @@ -802,64 +802,183 @@ JL_CALLABLE(jl_f_svec) // struct operations ------------------------------------------------------------ +enum jl_memory_order jl_get_atomic_order(jl_sym_t *order, char loading, char storing) +{ + if (order == not_atomic_sym) + return jl_memory_order_notatomic; + if (order == unordered_sym && (loading || storing)) + return jl_memory_order_unordered; + if (order == monotonic_sym && (loading || storing)) + return jl_memory_order_monotonic; + if (order == acquire_sym && loading) + return jl_memory_order_acquire; + if (order == release_sym && storing) + return jl_memory_order_release; + if (order == acquire_release_sym && loading && storing) + return jl_memory_order_acq_rel; + if (order == sequentially_consistent_sym) + return jl_memory_order_seq_cst; + return jl_memory_order_invalid; +} + +enum jl_memory_order jl_get_atomic_order_checked(jl_sym_t *order, char loading, char storing) +{ + enum jl_memory_order mo = jl_get_atomic_order(order, loading, storing); + if (mo < 0) // invalid + jl_atomic_error("invalid atomic ordering"); + return mo; +} + +static inline size_t get_checked_fieldindex(const char *name, jl_datatype_t *st, jl_value_t *v, jl_value_t *arg, int mutabl) +{ + if (mutabl) { + if (st == jl_module_type) + jl_error("cannot assign variables in other modules"); + if (!st->name->mutabl) + jl_errorf("%s: immutable struct of type %s cannot be changed", name, jl_symbol_name(st->name->name)); + } + size_t idx; + if (jl_is_long(arg)) { + idx = jl_unbox_long(arg) - 1; + if (idx >= jl_datatype_nfields(st)) + jl_bounds_error(v, arg); + } + else { + JL_TYPECHKS(name, symbol, arg); + idx = jl_field_index(st, (jl_sym_t*)arg, 1); + } + return idx; +} + JL_CALLABLE(jl_f_getfield) { - if (nargs == 3) { - JL_TYPECHK(getfield, bool, args[2]); - nargs -= 1; + enum jl_memory_order order = jl_memory_order_unspecified; + JL_NARGS(getfield, 2, 4); + if (nargs == 4) { + JL_TYPECHK(getfield, symbol, args[3]); + JL_TYPECHK(getfield, bool, args[4]); + order = jl_get_atomic_order_checked((jl_sym_t*)args[3], 1, 0); + } + else if (nargs == 3) { + if (!jl_is_bool(args[2])) { + JL_TYPECHK(getfield, symbol, args[2]); + order = jl_get_atomic_order_checked((jl_sym_t*)args[2], 1, 0); + } } - JL_NARGS(getfield, 2, 2); jl_value_t *v = args[0]; - jl_value_t *vt = (jl_value_t*)jl_typeof(v); + jl_value_t *vt = jl_typeof(v); if (vt == (jl_value_t*)jl_module_type) { JL_TYPECHK(getfield, symbol, args[1]); - return jl_eval_global_var((jl_module_t*)v, (jl_sym_t*)args[1]); - } - if (!jl_is_datatype(vt)) - jl_type_error("getfield", (jl_value_t*)jl_datatype_type, v); - jl_datatype_t *st = (jl_datatype_t*)vt; - size_t idx; - if (jl_is_long(args[1])) { - idx = jl_unbox_long(args[1])-1; - if (idx >= jl_datatype_nfields(st)) - jl_bounds_error(args[0], args[1]); + v = jl_eval_global_var((jl_module_t*)v, (jl_sym_t*)args[1]); // is seq_cst already } else { - JL_TYPECHK(getfield, symbol, args[1]); - jl_sym_t *fld = (jl_sym_t*)args[1]; - idx = jl_field_index(st, fld, 1); + jl_datatype_t *st = (jl_datatype_t*)vt; + size_t idx = get_checked_fieldindex("getfield", st, v, args[1], 0); + int isatomic = jl_field_isatomic(st, idx); + if (!isatomic && order != jl_memory_order_notatomic && order != jl_memory_order_unspecified) + jl_atomic_error("getfield: non-atomic field cannot be accessed atomically"); + if (isatomic && order == jl_memory_order_notatomic) + jl_atomic_error("getfield: atomic field cannot be accessed non-atomically"); + v = jl_get_nth_field_checked(v, idx); + if (order >= jl_memory_order_acq_rel || order == jl_memory_order_acquire) + jl_fence(); // `v` already had at least consume ordering } - return jl_get_nth_field_checked(v, idx); + return v; } JL_CALLABLE(jl_f_setfield) { - JL_NARGS(setfield!, 3, 3); + enum jl_memory_order order = jl_memory_order_notatomic; + JL_NARGS(setfield!, 3, 4); + if (nargs == 4) { + JL_TYPECHK(getfield, symbol, args[3]); + order = jl_get_atomic_order_checked((jl_sym_t*)args[3], 0, 1); + } jl_value_t *v = args[0]; jl_datatype_t *st = (jl_datatype_t*)jl_typeof(v); - assert(jl_is_datatype(st)); - if (st == jl_module_type) - jl_error("cannot assign variables in other modules"); - if (!st->name->mutabl) - jl_errorf("setfield! immutable struct of type %s cannot be changed", jl_symbol_name(st->name->name)); - size_t idx; - if (jl_is_long(args[1])) { - idx = jl_unbox_long(args[1]) - 1; - if (idx >= jl_datatype_nfields(st)) - jl_bounds_error(args[0], args[1]); - } - else { - JL_TYPECHK(setfield!, symbol, args[1]); - idx = jl_field_index(st, (jl_sym_t*)args[1], 1); - } + size_t idx = get_checked_fieldindex("setfield!", st, v, args[1], 1); + int isatomic = !!jl_field_isatomic(st, idx); + if (isatomic == (order == jl_memory_order_notatomic)) + jl_atomic_error(isatomic ? "setfield!: atomic field cannot be written non-atomically" + : "setfield!: non-atomic field cannot be written atomically"); jl_value_t *ft = jl_field_type_concrete(st, idx); - if (!jl_isa(args[2], ft)) { + if (!jl_isa(args[2], ft)) jl_type_error("setfield!", ft, args[2]); - } - set_nth_field(st, (void*)v, idx, args[2]); + if (order >= jl_memory_order_acq_rel || order == jl_memory_order_release) + jl_fence(); // `st->[idx]` will have at least relaxed ordering + set_nth_field(st, v, idx, args[2], isatomic); return args[2]; } +JL_CALLABLE(jl_f_swapfield) +{ + enum jl_memory_order order = jl_memory_order_notatomic; + JL_NARGS(swapfield!, 3, 4); + if (nargs == 4) { + JL_TYPECHK(swapfield!, symbol, args[3]); + order = jl_get_atomic_order_checked((jl_sym_t*)args[3], 1, 1); + } + jl_value_t *v = args[0]; + jl_datatype_t *st = (jl_datatype_t*)jl_typeof(v); + size_t idx = get_checked_fieldindex("swapfield!", st, v, args[1], 1); + int isatomic = !!jl_field_isatomic(st, idx); + if (isatomic == (order == jl_memory_order_notatomic)) + jl_atomic_error(isatomic ? "swapfield!: atomic field cannot be written non-atomically" + : "swapfield!: non-atomic field cannot be written atomically"); + v = swap_nth_field(st, v, idx, args[2], isatomic); // always seq_cst, if isatomic needed at all + return v; +} + +JL_CALLABLE(jl_f_modifyfield) +{ + enum jl_memory_order order = jl_memory_order_notatomic; + JL_NARGS(modifyfield!, 4, 5); + if (nargs == 5) { + JL_TYPECHK(modifyfield!, symbol, args[4]); + order = jl_get_atomic_order_checked((jl_sym_t*)args[4], 1, 1); + } + jl_value_t *v = args[0]; + jl_datatype_t *st = (jl_datatype_t*)jl_typeof(v); + size_t idx = get_checked_fieldindex("modifyfield!", st, v, args[1], 1); + int isatomic = !!jl_field_isatomic(st, idx); + if (isatomic == (order == jl_memory_order_notatomic)) + jl_atomic_error(isatomic ? "modifyfield!: atomic field cannot be written non-atomically" + : "modifyfield!: non-atomic field cannot be written atomically"); + v = modify_nth_field(st, v, idx, args[2], args[3], isatomic); // always seq_cst, if isatomic needed at all + return v; +} + +JL_CALLABLE(jl_f_replacefield) +{ + enum jl_memory_order success_order = jl_memory_order_notatomic; + JL_NARGS(replacefield!, 4, 6); + if (nargs >= 5) { + JL_TYPECHK(replacefield!, symbol, args[4]); + success_order = jl_get_atomic_order_checked((jl_sym_t*)args[4], 1, 1); + } + enum jl_memory_order failure_order = success_order; + if (nargs == 6) { + JL_TYPECHK(replacefield!, symbol, args[5]); + failure_order = jl_get_atomic_order_checked((jl_sym_t*)args[5], 1, 0); + } + // TODO: filter more invalid ordering combinations + jl_value_t *v = args[0]; + jl_datatype_t *st = (jl_datatype_t*)jl_typeof(v); + size_t idx = get_checked_fieldindex("replacefield!", st, v, args[1], 1); + int isatomic = !!jl_field_isatomic(st, idx); + if (isatomic == (success_order == jl_memory_order_notatomic)) + jl_atomic_error(isatomic ? "replacefield!: atomic field cannot be written non-atomically" + : "replacefield!: non-atomic field cannot be written atomically"); + if (isatomic == (failure_order == jl_memory_order_notatomic)) + jl_atomic_error(isatomic ? "replacefield!: atomic field cannot be accessed non-atomically" + : "replacefield!: non-atomic field cannot be accessed atomically"); + if (failure_order > success_order) + jl_atomic_error("invalid atomic ordering"); + v = replace_nth_field(st, v, idx, args[2], args[3], isatomic); // always seq_cst, if isatomic needed at all + return v; +} + + static jl_value_t *get_fieldtype(jl_value_t *t, jl_value_t *f, int dothrow) { if (jl_is_unionall(t)) { @@ -939,11 +1058,10 @@ static jl_value_t *get_fieldtype(jl_value_t *t, jl_value_t *f, int dothrow) JL_CALLABLE(jl_f_fieldtype) { + JL_NARGS(fieldtype, 2, 3); if (nargs == 3) { JL_TYPECHK(fieldtype, bool, args[2]); - nargs -= 1; } - JL_NARGS(fieldtype, 2, 2); return get_fieldtype(args[0], args[1], 1); } @@ -958,29 +1076,53 @@ JL_CALLABLE(jl_f_isdefined) { jl_module_t *m = NULL; jl_sym_t *s = NULL; - JL_NARGS(isdefined, 2, 2); - if (!jl_is_module(args[0])) { - jl_datatype_t *vt = (jl_datatype_t*)jl_typeof(args[0]); - assert(jl_is_datatype(vt)); - size_t idx; - if (jl_is_long(args[1])) { - idx = jl_unbox_long(args[1]) - 1; - if (idx >= jl_datatype_nfields(vt)) - return jl_false; + JL_NARGS(isdefined, 2, 3); + enum jl_memory_order order = jl_memory_order_unspecified; + if (nargs == 3) { + JL_TYPECHK(isdefined, symbol, args[2]); + order = jl_get_atomic_order_checked((jl_sym_t*)args[2], 1, 0); + } + if (jl_is_module(args[0])) { + JL_TYPECHK(isdefined, symbol, args[1]); + m = (jl_module_t*)args[0]; + s = (jl_sym_t*)args[1]; + return jl_boundp(m, s) ? jl_true : jl_false; // is seq_cst already + } + jl_datatype_t *vt = (jl_datatype_t*)jl_typeof(args[0]); + assert(jl_is_datatype(vt)); + size_t idx; + if (jl_is_long(args[1])) { + idx = jl_unbox_long(args[1]) - 1; + if (idx >= jl_datatype_nfields(vt)) { + if (order != jl_memory_order_unspecified) + jl_atomic_error("isdefined: atomic ordering cannot be specified for nonexistent field"); + return jl_false; } - else { - JL_TYPECHK(isdefined, symbol, args[1]); - idx = jl_field_index(vt, (jl_sym_t*)args[1], 0); - if ((int)idx == -1) - return jl_false; + } + else { + JL_TYPECHK(isdefined, symbol, args[1]); + idx = jl_field_index(vt, (jl_sym_t*)args[1], 0); + if ((int)idx == -1) { + if (order != jl_memory_order_unspecified) + jl_atomic_error("isdefined: atomic ordering cannot be specified for nonexistent field"); + return jl_false; } - return jl_field_isdefined(args[0], idx) ? jl_true : jl_false; } - JL_TYPECHK(isdefined, module, args[0]); - JL_TYPECHK(isdefined, symbol, args[1]); - m = (jl_module_t*)args[0]; - s = (jl_sym_t*)args[1]; - return jl_boundp(m, s) ? jl_true : jl_false; + int isatomic = jl_field_isatomic(vt, idx); + if (!isatomic && order != jl_memory_order_notatomic && order != jl_memory_order_unspecified) + jl_atomic_error("isdefined: non-atomic field cannot be accessed atomically"); + if (isatomic && order == jl_memory_order_notatomic) + jl_atomic_error("isdefined: atomic field cannot be accessed non-atomically"); + int v = jl_field_isdefined(args[0], idx); + if (v == 2) { + if (order > jl_memory_order_notatomic) + jl_fence(); // isbits case has no ordering already + } + else { + if (order >= jl_memory_order_acq_rel || order == jl_memory_order_acquire) + jl_fence(); // `v` already gave at least consume ordering + } + return v ? jl_true : jl_false; } @@ -1247,18 +1389,20 @@ JL_CALLABLE(jl_f_arrayset) JL_CALLABLE(jl_f__structtype) { - JL_NARGS(_structtype, 6, 6); + JL_NARGS(_structtype, 7, 7); JL_TYPECHK(_structtype, module, args[0]); JL_TYPECHK(_structtype, symbol, args[1]); JL_TYPECHK(_structtype, simplevector, args[2]); JL_TYPECHK(_structtype, simplevector, args[3]); - JL_TYPECHK(_structtype, bool, args[4]); - JL_TYPECHK(_structtype, long, args[5]); + JL_TYPECHK(_structtype, simplevector, args[4]); + JL_TYPECHK(_structtype, bool, args[5]); + JL_TYPECHK(_structtype, long, args[6]); jl_value_t *fieldnames = args[3]; + jl_value_t *fieldattrs = args[4]; jl_datatype_t *dt = NULL; dt = jl_new_datatype((jl_sym_t*)args[1], (jl_module_t*)args[0], NULL, (jl_svec_t*)args[2], - (jl_svec_t*)fieldnames, NULL, - 0, args[4]==jl_true ? 1 : 0, jl_unbox_long(args[5])); + (jl_svec_t*)fieldnames, NULL, (jl_svec_t*)fieldattrs, + 0, args[5]==jl_true ? 1 : 0, jl_unbox_long(args[6])); return dt->name->wrapper; } @@ -1424,6 +1568,10 @@ static int equiv_type(jl_value_t *ta, jl_value_t *tb) dta->name->name == dtb->name->name && (jl_svec_len(jl_field_names(dta)) != 0 || dta->size == dtb->size) && dta->ninitialized == dtb->ninitialized && + (dta->name->atomicfields == NULL + ? dtb->name->atomicfields == NULL + : (dtb->name->atomicfields != NULL && + memcmp(dta->name->atomicfields, dtb->name->atomicfields, (jl_svec_len(dta->name->names) + 31) / 32 * sizeof(uint32_t)) == 0)) && jl_egal((jl_value_t*)jl_field_names(dta), (jl_value_t*)jl_field_names(dtb)) && jl_nparams(dta) == jl_nparams(dtb))) return 0; @@ -1490,6 +1638,7 @@ JL_CALLABLE(jl_f_intrinsic_call) jl_value_t *(*call2)(jl_value_t*, jl_value_t*); jl_value_t *(*call3)(jl_value_t*, jl_value_t*, jl_value_t*); jl_value_t *(*call4)(jl_value_t*, jl_value_t*, jl_value_t*, jl_value_t*); + jl_value_t *(*call5)(jl_value_t*, jl_value_t*, jl_value_t*, jl_value_t*, jl_value_t*); } fptr; fptr.fptr = runtime_fp[f]; switch (fargs) { @@ -1501,6 +1650,8 @@ JL_CALLABLE(jl_f_intrinsic_call) return fptr.call3(args[0], args[1], args[2]); case 4: return fptr.call4(args[0], args[1], args[2], args[3]); + case 5: + return fptr.call5(args[0], args[1], args[2], args[3], args[4]); default: assert(0 && "unexpected number of arguments to an intrinsic function"); } @@ -1604,6 +1755,9 @@ void jl_init_primitives(void) JL_GC_DISABLED // field access jl_builtin_getfield = add_builtin_func("getfield", jl_f_getfield); jl_builtin_setfield = add_builtin_func("setfield!", jl_f_setfield); + jl_builtin_swapfield = add_builtin_func("swapfield!", jl_f_swapfield); + jl_builtin_modifyfield = add_builtin_func("modifyfield!", jl_f_modifyfield); + jl_builtin_replacefield = add_builtin_func("replacefield!", jl_f_replacefield); jl_builtin_fieldtype = add_builtin_func("fieldtype", jl_f_fieldtype); jl_builtin_nfields = add_builtin_func("nfields", jl_f_nfields); jl_builtin_isdefined = add_builtin_func("isdefined", jl_f_isdefined); diff --git a/src/cgutils.cpp b/src/cgutils.cpp index c5014fdbdbe17..bed7e45d7a438 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -53,6 +53,22 @@ static Value *mark_callee_rooted(jl_codectx_t &ctx, Value *V) PointerType::get(T_jlvalue, AddressSpace::CalleeRooted)); } +AtomicOrdering get_llvm_atomic_order(enum jl_memory_order order) +{ + switch (order) { + case jl_memory_order_notatomic: return AtomicOrdering::NotAtomic; + case jl_memory_order_unordered: return AtomicOrdering::Unordered; + case jl_memory_order_monotonic: return AtomicOrdering::Monotonic; + case jl_memory_order_acquire: return AtomicOrdering::Acquire; + case jl_memory_order_release: return AtomicOrdering::Release; + case jl_memory_order_acq_rel: return AtomicOrdering::AcquireRelease; + case jl_memory_order_seq_cst: return AtomicOrdering::SequentiallyConsistent; + default: + assert("invalid atomic ordering"); + abort(); + } +} + // --- language feature checks --- #define JL_FEAT_TEST(ctx, feature) ((ctx).params->feature) @@ -124,6 +140,7 @@ static DIType *_julia_type_to_di(jl_codegen_params_t *ctx, jl_value_t *jt, DIBui DIType *di; if (jl_field_isptr(jdt, i)) di = jl_pvalue_dillvmt; + // TODO: elseif jl_islayout_inline else di = _julia_type_to_di(ctx, el, dbuilder, false); Elements[i] = di; @@ -595,6 +612,11 @@ static Type *_julia_struct_to_llvm(jl_codegen_params_t *ctx, jl_value_t *jt, boo if (jlasttype != NULL && ty != jlasttype) isvector = false; jlasttype = ty; + if (jl_field_isatomic(jst, i)) { + // TODO: eventually support this? + // though it's a bit unclear how the implicit load should be interpreted + return NULL; + } Type *lty; if (jl_field_isptr(jst, i)) { lty = T_prjlvalue; @@ -610,20 +632,22 @@ static Type *_julia_struct_to_llvm(jl_codegen_params_t *ctx, jl_value_t *jt, boo size_t fsz = 0, al = 0; bool isptr = !jl_islayout_inline(ty, &fsz, &al); assert(!isptr && fsz == jl_field_size(jst, i) - 1); (void)isptr; - if (al > MAX_ALIGN) { - Type *AlignmentType; - AlignmentType = ArrayType::get(FixedVectorType::get(T_int8, al), 0); - latypes.push_back(AlignmentType); - al = MAX_ALIGN; + if (fsz > 0) { + if (al > MAX_ALIGN) { + Type *AlignmentType; + AlignmentType = ArrayType::get(FixedVectorType::get(T_int8, al), 0); + latypes.push_back(AlignmentType); + al = MAX_ALIGN; + } + Type *AlignmentType = IntegerType::get(jl_LLVMContext, 8 * al); + unsigned NumATy = fsz / al; + unsigned remainder = fsz % al; + assert(al == 1 || NumATy > 0); + while (NumATy--) + latypes.push_back(AlignmentType); + while (remainder--) + latypes.push_back(T_int8); } - Type *AlignmentType = IntegerType::get(jl_LLVMContext, 8 * al); - unsigned NumATy = fsz / al; - unsigned remainder = fsz % al; - assert(al == 1 || NumATy > 0); - while (NumATy--) - latypes.push_back(AlignmentType); - while (remainder--) - latypes.push_back(T_int8); latypes.push_back(T_int8); isarray = false; allghost = false; @@ -1000,27 +1024,32 @@ static Value *emit_datatype_name(jl_codectx_t &ctx, Value *dt) // the error is always thrown. This may cause non dominated use // of SSA value error in the verifier. -static void just_emit_error(jl_codectx_t &ctx, const std::string &txt) +static void just_emit_error(jl_codectx_t &ctx, Function *F, const std::string &txt) { - ctx.builder.CreateCall(prepare_call(jlerror_func), stringConstPtr(ctx.emission_context, ctx.builder, txt)); + ctx.builder.CreateCall(F, stringConstPtr(ctx.emission_context, ctx.builder, txt)); } -static void emit_error(jl_codectx_t &ctx, const std::string &txt) +static void emit_error(jl_codectx_t &ctx, Function *F, const std::string &txt) { - just_emit_error(ctx, txt); + just_emit_error(ctx, F, txt); ctx.builder.CreateUnreachable(); - BasicBlock *cont = BasicBlock::Create(jl_LLVMContext,"after_error",ctx.f); + BasicBlock *cont = BasicBlock::Create(jl_LLVMContext, "after_error", ctx.f); ctx.builder.SetInsertPoint(cont); } +static void emit_error(jl_codectx_t &ctx, const std::string &txt) +{ + emit_error(ctx, prepare_call(jlerror_func), txt); +} + // DO NOT PASS IN A CONST CONDITION! static void error_unless(jl_codectx_t &ctx, Value *cond, const std::string &msg) { - BasicBlock *failBB = BasicBlock::Create(jl_LLVMContext,"fail",ctx.f); - BasicBlock *passBB = BasicBlock::Create(jl_LLVMContext,"pass"); + BasicBlock *failBB = BasicBlock::Create(jl_LLVMContext, "fail", ctx.f); + BasicBlock *passBB = BasicBlock::Create(jl_LLVMContext, "pass"); ctx.builder.CreateCondBr(cond, passBB, failBB); ctx.builder.SetInsertPoint(failBB); - just_emit_error(ctx, msg); + just_emit_error(ctx, prepare_call(jlerror_func), msg); ctx.builder.CreateUnreachable(); ctx.f->getBasicBlockList().push_back(passBB); ctx.builder.SetInsertPoint(passBB); @@ -1387,14 +1416,20 @@ Value *extract_first_ptr(jl_codectx_t &ctx, Value *V) // If `nullcheck` is not NULL and a pointer NULL check is necessary // store the pointer to be checked in `*nullcheck` instead of checking it static jl_cgval_t typed_load(jl_codectx_t &ctx, Value *ptr, Value *idx_0based, jl_value_t *jltype, - MDNode *tbaa, MDNode *aliasscope, + MDNode *tbaa, MDNode *aliasscope, bool isboxed, AtomicOrdering Order, bool maybe_null_if_boxed = true, unsigned alignment = 0, Value **nullcheck = nullptr) { - bool isboxed; - Type *elty = julia_type_to_llvm(ctx, jltype, &isboxed); + Type *elty = isboxed ? T_prjlvalue : julia_type_to_llvm(ctx, jltype); if (type_is_ghost(elty)) return ghostValue(jltype); + AllocaInst *intcast = NULL; + if (!isboxed && Order != AtomicOrdering::NotAtomic && !elty->isIntOrPtrTy() && !elty->isFloatingPointTy()) { + const DataLayout &DL = jl_data_layout; + unsigned nb = DL.getTypeSizeInBits(elty); + intcast = ctx.builder.CreateAlloca(elty); + elty = Type::getIntNTy(jl_LLVMContext, nb); + } Type *ptrty = PointerType::get(elty, ptr->getType()->getPointerAddressSpace()); Value *data; if (ptr->getType() != ptrty) @@ -1414,14 +1449,17 @@ static jl_cgval_t typed_load(jl_codectx_t &ctx, Value *ptr, Value *idx_0based, j else if (!alignment) alignment = julia_alignment(jltype); load = ctx.builder.CreateAlignedLoad(data, Align(alignment), false); + cast(load)->setOrdering(Order); if (aliasscope) load->setMetadata("alias.scope", aliasscope); - if (isboxed) { - cast(load)->setOrdering(AtomicOrdering::Unordered); + if (isboxed) load = maybe_mark_load_dereferenceable(load, true, jltype); - } if (tbaa) load = tbaa_decorate(tbaa, load); + if (intcast) { + ctx.builder.CreateStore(load, ctx.builder.CreateBitCast(intcast, load->getType()->getPointerTo())); + load = ctx.builder.CreateLoad(intcast); + } if (maybe_null_if_boxed) { Value *first_ptr = isboxed ? load : extract_first_ptr(ctx, load); if (first_ptr) @@ -1442,12 +1480,16 @@ static void typed_store(jl_codectx_t &ctx, Value *ptr, Value *idx_0based, const jl_cgval_t &rhs, jl_value_t *jltype, MDNode *tbaa, MDNode *aliasscope, Value *parent, // for the write barrier, NULL if no barrier needed - unsigned alignment = 0) + bool isboxed, AtomicOrdering Order, unsigned alignment = 0) { - bool isboxed; - Type *elty = julia_type_to_llvm(ctx, jltype, &isboxed); + Type *elty = isboxed ? T_prjlvalue : julia_type_to_llvm(ctx, jltype); if (type_is_ghost(elty)) return; + if (!isboxed && Order != AtomicOrdering::NotAtomic && !elty->isIntOrPtrTy() && !elty->isFloatingPointTy()) { + const DataLayout &DL = jl_data_layout; + unsigned nb = DL.getTypeSizeInBits(elty); + elty = Type::getIntNTy(jl_LLVMContext, nb); + } Value *r; if (!isboxed) r = emit_unbox(ctx, elty, rhs, jltype); @@ -1463,8 +1505,7 @@ static void typed_store(jl_codectx_t &ctx, else if (!alignment) alignment = julia_alignment(jltype); StoreInst *store = ctx.builder.CreateAlignedStore(r, ptr, Align(alignment)); - if (isboxed) // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0 - store->setOrdering(AtomicOrdering::Unordered); + store->setOrdering(Order); if (aliasscope) store->setMetadata("noalias", aliasscope); if (tbaa) @@ -1580,14 +1621,19 @@ static void emit_memcpy(jl_codectx_t &ctx, Value *dst, MDNode *tbaa_dst, const j } +static void emit_atomic_error(jl_codectx_t &ctx, const std::string &msg) +{ + emit_error(ctx, prepare_call(jlatomicerror_func), msg); +} static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &strct, unsigned idx, jl_datatype_t *jt, - Value **nullcheck = nullptr); + enum jl_memory_order order, Value **nullcheck=nullptr); static bool emit_getfield_unknownidx(jl_codectx_t &ctx, jl_cgval_t *ret, jl_cgval_t strct, - Value *idx, jl_datatype_t *stt, jl_value_t *inbounds) + Value *idx, jl_datatype_t *stt, jl_value_t *inbounds, + enum jl_memory_order order) { size_t nfields = jl_datatype_nfields(stt); bool maybe_null = (unsigned)stt->ninitialized != nfields; @@ -1601,7 +1647,7 @@ static bool emit_getfield_unknownidx(jl_codectx_t &ctx, } if (nfields == 1) { (void)idx0(); - *ret = emit_getfield_knownidx(ctx, strct, 0, stt); + *ret = emit_getfield_knownidx(ctx, strct, 0, stt, order); return true; } assert(!jl_is_vecelement_type((jl_value_t*)stt)); @@ -1659,7 +1705,13 @@ static bool emit_getfield_unknownidx(jl_codectx_t &ctx, } } - if (strct.ispointer()) { // boxed or stack + bool maybeatomic = stt->name->atomicfields != NULL; + if (strct.ispointer() && !maybeatomic) { // boxed or stack + if (order != jl_memory_order_notatomic && order != jl_memory_order_unspecified) { + emit_atomic_error(ctx, "getfield: non-atomic field cannot be accessed atomically"); + *ret = jl_cgval_t(); // unreachable + return true; + } if (is_datatype_all_pointers(stt)) { size_t minimum_field_size = std::numeric_limits::max(); size_t minimum_align = JL_HEAP_ALIGNMENT; @@ -1701,7 +1753,7 @@ static bool emit_getfield_unknownidx(jl_codectx_t &ctx, *ret = mark_julia_slot(addr, jft, NULL, strct.tbaa); return true; } - *ret = typed_load(ctx, ptr, idx, jft, strct.tbaa, nullptr, maybe_null); + *ret = typed_load(ctx, ptr, idx, jft, strct.tbaa, nullptr, false, AtomicOrdering::NotAtomic, maybe_null); return true; } else if (strct.isboxed) { @@ -1714,13 +1766,30 @@ static bool emit_getfield_unknownidx(jl_codectx_t &ctx, return false; } +static void emit_lockstate_value(jl_codectx_t &ctx, const jl_cgval_t &strct, bool newstate) +{ + assert(strct.isboxed); + Value *v = mark_callee_rooted(ctx, boxed(ctx, strct)); + ctx.builder.CreateCall(prepare_call(newstate ? jllockvalue_func : jlunlockvalue_func), v); +} + // If `nullcheck` is not NULL and a pointer NULL check is necessary // store the pointer to be checked in `*nullcheck` instead of checking it static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &strct, unsigned idx, jl_datatype_t *jt, - Value **nullcheck) + enum jl_memory_order order, Value **nullcheck) { jl_value_t *jfty = jl_field_type(jt, idx); + bool isatomic = jl_field_isatomic(jt, idx); + bool needlock = isatomic && !jl_field_isptr(jt, idx) && jl_datatype_size(jfty) > MAX_ATOMIC_SIZE; + if (!isatomic && order != jl_memory_order_notatomic && order != jl_memory_order_unspecified) { + emit_atomic_error(ctx, "getfield: non-atomic field cannot be accessed atomically"); + return jl_cgval_t(); // unreachable + } + if (isatomic && order == jl_memory_order_notatomic) { + emit_atomic_error(ctx, "getfield: atomic field cannot be accessed non-atomically"); + return jl_cgval_t(); // unreachable + } if (jfty == jl_bottom_type) { raise_exception(ctx, literal_pointer_val(ctx, jl_undefref_exception)); return jl_cgval_t(); // unreachable @@ -1762,7 +1831,7 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st } if (jl_field_isptr(jt, idx)) { LoadInst *Load = ctx.builder.CreateAlignedLoad(T_prjlvalue, maybe_bitcast(ctx, addr, T_pprjlvalue), Align(sizeof(void*))); - Load->setOrdering(AtomicOrdering::Unordered); + Load->setOrdering(order <= jl_memory_order_notatomic ? AtomicOrdering::Unordered : get_llvm_atomic_order(order)); maybe_mark_load_dereferenceable(Load, maybe_null, jl_field_type(jt, idx)); Value *fldv = tbaa_decorate(tbaa, Load); if (maybe_null) @@ -1803,7 +1872,14 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st return mark_julia_slot(addr, jfty, NULL, tbaa); } unsigned align = jl_field_align(jt, idx); - return typed_load(ctx, addr, NULL, jfty, tbaa, nullptr, maybe_null, align, nullcheck); + if (needlock) + emit_lockstate_value(ctx, strct, true); + jl_cgval_t ret = typed_load(ctx, addr, NULL, jfty, tbaa, nullptr, false, + needlock || order <= jl_memory_order_notatomic ? AtomicOrdering::NotAtomic : get_llvm_atomic_order(order), // TODO: we should use unordered for anything with CountTrackedPointers(elty).count > 0 + maybe_null, align, nullcheck); + if (needlock) + emit_lockstate_value(ctx, strct, false); + return ret; } else if (isa(strct.V)) { return jl_cgval_t(); @@ -2814,7 +2890,7 @@ static void emit_write_multibarrier(jl_codectx_t &ctx, Value *parent, Value *agg static void emit_setfield(jl_codectx_t &ctx, jl_datatype_t *sty, const jl_cgval_t &strct, size_t idx0, - const jl_cgval_t &rhs, bool checked, bool wb) + const jl_cgval_t &rhs, bool checked, bool wb, AtomicOrdering Order) { if (sty->name->mutabl || !checked) { assert(strct.ispointer()); @@ -2827,16 +2903,7 @@ static void emit_setfield(jl_codectx_t &ctx, ConstantInt::get(T_size, byte_offset)); // TODO: use emit_struct_gep } jl_value_t *jfty = jl_svecref(sty->types, idx0); - if (jl_field_isptr(sty, idx0)) { - Value *r = boxed(ctx, rhs); // don't need a temporary gcroot since it'll be rooted by strct - cast(tbaa_decorate(strct.tbaa, ctx.builder.CreateAlignedStore(r, - emit_bitcast(ctx, addr, T_pprjlvalue), - Align(sizeof(jl_value_t*))))) - ->setOrdering(AtomicOrdering::Unordered); - if (wb && strct.isboxed && !type_is_permalloc(rhs.typ)) - emit_write_barrier(ctx, boxed(ctx, strct), r); - } - else if (jl_is_uniontype(jfty)) { + if (!jl_field_isptr(sty, idx0) && jl_is_uniontype(jfty)) { int fsz = jl_field_size(sty, idx0) - 1; // compute tindex from rhs jl_cgval_t rhs_union = convert_julia_type(ctx, rhs, jfty); @@ -2853,13 +2920,14 @@ static void emit_setfield(jl_codectx_t &ctx, } else { unsigned align = jl_field_align(sty, idx0); - typed_store(ctx, addr, NULL, rhs, jfty, - strct.tbaa, nullptr, maybe_bitcast(ctx, - data_pointer(ctx, strct), T_pjlvalue), align); + bool isboxed = jl_field_isptr(sty, idx0); + typed_store(ctx, addr, NULL, rhs, jfty, strct.tbaa, nullptr, + wb ? maybe_bitcast(ctx, data_pointer(ctx, strct), T_pjlvalue) : nullptr, + isboxed, Order, align); } } else { - std::string msg = "setfield! immutable struct of type " + std::string msg = "setfield!: immutable struct of type " + std::string(jl_symbol_name(sty->name->name)) + " cannot be changed"; emit_error(ctx, msg); @@ -3041,7 +3109,7 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg else need_wb = false; emit_typecheck(ctx, rhs, jl_svecref(sty->types, i), "new"); - emit_setfield(ctx, sty, strctinfo, i, rhs, false, need_wb); + emit_setfield(ctx, sty, strctinfo, i, rhs, false, need_wb, AtomicOrdering::NotAtomic); } return strctinfo; } diff --git a/src/codegen.cpp b/src/codegen.cpp index 3f96a280df5b2..13fa2130a922a 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -462,6 +462,12 @@ static const auto jlerror_func = new JuliaFunction{ {T_pint8}, false); }, get_attrs_noreturn, }; +static const auto jlatomicerror_func = new JuliaFunction{ + "jl_atomic_error", + [](LLVMContext &C) { return FunctionType::get(T_void, + {T_pint8}, false); }, + get_attrs_noreturn, +}; static const auto jltypeerror_func = new JuliaFunction{ "jl_type_error", [](LLVMContext &C) { return FunctionType::get(T_void, @@ -576,6 +582,24 @@ static const auto jlgenericfunction_func = new JuliaFunction{ {T_pjlvalue, T_pjlvalue, T_pprjlvalue, T_pjlvalue, T_pjlvalue}, false); }, nullptr, }; +static const auto jllockvalue_func = new JuliaFunction{ + "jl_lock_value", + [](LLVMContext &C) { return FunctionType::get(T_void, + {PointerType::get(T_jlvalue, AddressSpace::CalleeRooted)}, false); }, + [](LLVMContext &C) { return AttributeList::get(C, + AttributeSet(), + AttributeSet(), + {Attributes(C, {Attribute::NoCapture})}); }, +}; +static const auto jlunlockvalue_func = new JuliaFunction{ + "jl_unlock_value", + [](LLVMContext &C) { return FunctionType::get(T_void, + {PointerType::get(T_jlvalue, AddressSpace::CalleeRooted)}, false); }, + [](LLVMContext &C) { return AttributeList::get(C, + AttributeSet(), + AttributeSet(), + {Attributes(C, {Attribute::NoCapture})}); }, +}; static const auto jlenter_func = new JuliaFunction{ "jl_enter_handler", [](LLVMContext &C) { return FunctionType::get(T_void, @@ -834,7 +858,6 @@ static const auto pointer_from_objref_func = new JuliaFunction{ }; static const auto jltuple_func = new JuliaFunction{"jl_f_tuple", get_func_sig, get_func_attrs}; -static const auto jlgetfield_func = new JuliaFunction{"jl_f_getfield", get_func_sig, get_func_attrs}; static const std::map builtin_func_map = { { &jl_f_is, new JuliaFunction{"jl_f_is", get_func_sig, get_func_attrs} }, { &jl_f_typeof, new JuliaFunction{"jl_f_typeof", get_func_sig, get_func_attrs} }, @@ -854,7 +877,7 @@ static const std::map builtin_func_map = { { &jl_f_invoke, new JuliaFunction{"jl_f_invoke", get_func_sig, get_func_attrs} }, { &jl_f_invoke_kwsorter, new JuliaFunction{"jl_f_invoke_kwsorter", get_func_sig, get_func_attrs} }, { &jl_f_isdefined, new JuliaFunction{"jl_f_isdefined", get_func_sig, get_func_attrs} }, - { &jl_f_getfield, jlgetfield_func }, + { &jl_f_getfield, new JuliaFunction{"jl_f_getfield", get_func_sig, get_func_attrs} }, { &jl_f_setfield, new JuliaFunction{"jl_f_setfield", get_func_sig, get_func_attrs} }, { &jl_f_fieldtype, new JuliaFunction{"jl_f_fieldtype", get_func_sig, get_func_attrs} }, { &jl_f_nfields, new JuliaFunction{"jl_f_nfields", get_func_sig, get_func_attrs} }, @@ -1211,6 +1234,9 @@ static AllocaInst *emit_static_alloca(jl_codectx_t &ctx, Type *lty) static void undef_derived_strct(IRBuilder<> &irbuilder, Value *ptr, jl_datatype_t *sty, MDNode *tbaa) { assert(ptr->getType()->getPointerAddressSpace() != AddressSpace::Tracked); + size_t first_offset = sty->layout->nfields ? jl_field_offset(sty, 0) : 0; + if (first_offset != 0) + irbuilder.CreateMemSet(ptr, ConstantInt::get(T_int8, 0), first_offset, MaybeAlign(0)); size_t i, np = sty->layout->npointers; if (np == 0) return; @@ -2325,33 +2351,6 @@ static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t * return emit_checked_var(ctx, bp, name, false, tbaa_binding); } -static jl_cgval_t emit_getfield(jl_codectx_t &ctx, const jl_cgval_t &strct, jl_sym_t *name) -{ - if (strct.constant && jl_is_module(strct.constant)) - return emit_globalref(ctx, (jl_module_t*)strct.constant, name); - - jl_datatype_t *sty = (jl_datatype_t*)strct.typ; - if (jl_is_type_type((jl_value_t*)sty) && jl_is_concrete_type(jl_tparam0(sty))) - sty = (jl_datatype_t*)jl_typeof(jl_tparam0(sty)); - sty = (jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)sty); - if (jl_is_structtype(sty) && sty != jl_module_type && sty->layout) { - unsigned idx = jl_field_index(sty, name, 0); - if (idx != (unsigned)-1) { - return emit_getfield_knownidx(ctx, strct, idx, sty); - } - } - // TODO: attempt better codegen for approximate types, if the types - // and offsets of some fields are independent of parameters. - - // TODO: generic getfield func with more efficient calling convention - jl_cgval_t myargs_array[2] = { - strct, - mark_julia_const((jl_value_t*)name) - }; - Value *result = emit_jlcall(ctx, jlgetfield_func, V_rnull, myargs_array, 2, JLCALL_F_CC); - return mark_julia_type(ctx, result, true, jl_any_type); -} - template static Value *emit_guarded_test(jl_codectx_t &ctx, Value *ifnot, Constant *defval, Func &&func) { @@ -2552,8 +2551,8 @@ static Value *emit_bits_compare(jl_codectx_t &ctx, jl_cgval_t arg1, jl_cgval_t a continue; Value *nullcheck1 = nullptr; Value *nullcheck2 = nullptr; - auto fld1 = emit_getfield_knownidx(ctx, arg1, i, sty, &nullcheck1); - auto fld2 = emit_getfield_knownidx(ctx, arg2, i, sty, &nullcheck2); + auto fld1 = emit_getfield_knownidx(ctx, arg1, i, sty, jl_memory_order_notatomic, &nullcheck1); + auto fld2 = emit_getfield_knownidx(ctx, arg2, i, sty, jl_memory_order_notatomic, &nullcheck2); Value *fld_answer; if (jl_field_isptr(sty, i) && jl_is_concrete_immutable(fldty)) { // concrete immutables that are !isinlinealloc might be reference cycles @@ -2839,6 +2838,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, ety = (jl_value_t*)jl_any_type; ssize_t nd = jl_is_long(ndp) ? jl_unbox_long(ndp) : -1; jl_value_t *boundscheck = argv[1].constant; + emit_typecheck(ctx, argv[1], (jl_value_t*)jl_bool_type, "arrayref"); Value *idx = emit_array_nd_index(ctx, ary, ary_ex, nd, &argv[3], nargs - 2, boundscheck); if (!isboxed && jl_is_datatype(ety) && jl_datatype_size(ety) == 0) { assert(((jl_datatype_t*)ety)->instance != NULL); @@ -2873,7 +2873,10 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, *ret = typed_load(ctx, emit_arrayptr(ctx, ary, ary_ex), idx, ety, - !isboxed ? tbaa_arraybuf : tbaa_ptrarraybuf, aliasscope); + isboxed ? tbaa_ptrarraybuf : tbaa_arraybuf, + aliasscope, + isboxed, + AtomicOrdering::NotAtomic); } return true; } @@ -2904,6 +2907,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, jl_value_t *ary_ex = jl_exprarg(ex, 2); ssize_t nd = jl_is_long(ndp) ? jl_unbox_long(ndp) : -1; jl_value_t *boundscheck = argv[1].constant; + emit_typecheck(ctx, argv[1], (jl_value_t*)jl_bool_type, "arrayset"); Value *idx = emit_array_nd_index(ctx, ary, ary_ex, nd, &argv[4], nargs - 3, boundscheck); if (!isboxed && jl_is_datatype(ety) && jl_datatype_size(ety) == 0) { // no-op @@ -2942,7 +2946,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, data_owner->addIncoming(aryv, curBB); data_owner->addIncoming(own_ptr, ownedBB); } - if (jl_is_uniontype(ety)) { + if (!isboxed && jl_is_uniontype(ety)) { Type *AT = ArrayType::get(IntegerType::get(jl_LLVMContext, 8 * al), (elsz + al - 1) / al); Value *data = emit_bitcast(ctx, emit_arrayptr(ctx, ary, ary_ex), AT->getPointerTo()); // compute tindex from val @@ -2973,8 +2977,12 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, typed_store(ctx, emit_arrayptr(ctx, ary, ary_ex, isboxed), idx, val, ety, - !isboxed ? tbaa_arraybuf : tbaa_ptrarraybuf, - ctx.aliasscope, data_owner, 0); + isboxed ? tbaa_ptrarraybuf : tbaa_arraybuf, + ctx.aliasscope, + data_owner, + isboxed, + isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic, // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0 + 0); } } *ret = ary; @@ -2984,15 +2992,58 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, } } - else if (f == jl_builtin_getfield && (nargs == 2 || nargs == 3)) { + else if (f == jl_builtin_getfield && (nargs == 2 || nargs == 3 || nargs == 4)) { const jl_cgval_t &obj = argv[1]; const jl_cgval_t &fld = argv[2]; - if (fld.constant && fld.typ == (jl_value_t*)jl_symbol_type) { - *ret = emit_getfield(ctx, argv[1], (jl_sym_t*)fld.constant); + enum jl_memory_order order = jl_memory_order_unspecified; + jl_value_t *boundscheck = jl_true; + + if (nargs == 4) { + const jl_cgval_t &ord = argv[3]; + const jl_cgval_t &inb = argv[4]; + emit_typecheck(ctx, ord, (jl_value_t*)jl_symbol_type, "getfield"); + emit_typecheck(ctx, inb, (jl_value_t*)jl_bool_type, "getfield"); + if (!ord.constant) + return false; + order = jl_get_atomic_order((jl_sym_t*)ord.constant, true, false); + if (inb.constant == jl_false) + boundscheck = jl_false; + } + else if (nargs == 3) { + const jl_cgval_t &arg3 = argv[3]; + if (arg3.typ == (jl_value_t*)jl_symbol_type && arg3.constant) + order = jl_get_atomic_order((jl_sym_t*)arg3.constant, true, false); + else if (arg3.constant == jl_false) + boundscheck = jl_false; + else if (arg3.typ != (jl_value_t*)jl_bool_type) + return false; + } + if (order == jl_memory_order_invalid) { + emit_atomic_error(ctx, "invalid atomic ordering"); + *ret = jl_cgval_t(); // unreachable return true; } - if (fld.typ == (jl_value_t*)jl_long_type) { + jl_datatype_t *utt = (jl_datatype_t*)jl_unwrap_unionall(obj.typ); + if (jl_is_type_type((jl_value_t*)utt) && jl_is_concrete_type(jl_tparam0(utt))) + utt = (jl_datatype_t*)jl_typeof(jl_tparam0(utt)); + + if (fld.constant && fld.typ == (jl_value_t*)jl_symbol_type) { + jl_sym_t *name = (jl_sym_t*)fld.constant; + if (obj.constant && jl_is_module(obj.constant)) { + *ret = emit_globalref(ctx, (jl_module_t*)obj.constant, name); + return true; + } + + if (jl_is_datatype(utt) && utt->layout) { + ssize_t idx = jl_field_index(utt, name, 0); + if (idx != -1) { + *ret = emit_getfield_knownidx(ctx, obj, idx, utt, order); + return true; + } + } + } + else if (fld.typ == (jl_value_t*)jl_long_type) { if (ctx.vaSlot > 0) { // optimize VA tuple if (LoadInst *load = dyn_cast_or_null(obj.V)) { @@ -3002,7 +3053,6 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, ctx.builder.CreateInBoundsGEP(T_prjlvalue, ctx.argArray, ConstantInt::get(T_size, ctx.nReqArgs)), NULL, false, NULL, NULL); Value *idx = emit_unbox(ctx, T_size, fld, (jl_value_t*)jl_long_type); - jl_value_t *boundscheck = (nargs == 3 ? argv[3].constant : jl_true); idx = emit_bounds_check(ctx, va_ary, NULL, idx, valen, boundscheck); idx = ctx.builder.CreateAdd(idx, ConstantInt::get(T_size, ctx.nReqArgs)); Instruction *v = ctx.builder.CreateAlignedLoad(T_prjlvalue, ctx.builder.CreateInBoundsGEP(ctx.argArray, idx), Align(sizeof(void*))); @@ -3014,33 +3064,34 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, } } - jl_datatype_t *utt = (jl_datatype_t*)jl_unwrap_unionall(obj.typ); - if (jl_is_datatype(utt) && utt->layout) { - if ((jl_is_structtype(utt) || jl_is_tuple_type(utt)) && !jl_subtype((jl_value_t*)jl_module_type, obj.typ)) { + if (jl_is_datatype(utt)) { + if (jl_is_structtype(utt) && utt->layout) { size_t nfields = jl_datatype_nfields(utt); // integer index size_t idx; if (fld.constant && (idx = jl_unbox_long(fld.constant) - 1) < nfields) { // known index - *ret = emit_getfield_knownidx(ctx, obj, idx, utt); + *ret = emit_getfield_knownidx(ctx, obj, idx, utt, order); return true; } else { // unknown index Value *vidx = emit_unbox(ctx, T_size, fld, (jl_value_t*)jl_long_type); - jl_value_t *boundscheck = (nargs == 3 ? argv[3].constant : jl_true); - if (emit_getfield_unknownidx(ctx, ret, obj, vidx, utt, boundscheck)) { + if (emit_getfield_unknownidx(ctx, ret, obj, vidx, utt, boundscheck, order)) { return true; } } } - } - else { - if (jl_is_tuple_type(utt) && is_tupletype_homogeneous(utt->types, true)) { + if (jl_is_tuple_type(utt) && is_tupletype_homogeneous(utt->parameters, true)) { // For tuples, we can emit code even if we don't know the exact // type (e.g. because we don't know the length). This is possible // as long as we know that all elements are of the same (leaf) type. if (obj.ispointer()) { + if (order != jl_memory_order_notatomic && order != jl_memory_order_unspecified) { + emit_atomic_error(ctx, "getfield: non-atomic field cannot be accessed atomically"); + *ret = jl_cgval_t(); // unreachable + return true; + } // Determine which was the type that was homogenous jl_value_t *jt = jl_tparam0(utt); if (jl_is_vararg(jt)) @@ -3049,7 +3100,6 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, Value *vidx = emit_unbox(ctx, T_size, fld, (jl_value_t*)jl_long_type); // This is not necessary for correctness, but allows to omit // the extra code for getting the length of the tuple - jl_value_t *boundscheck = (nargs == 3 ? argv[3].constant : jl_true); if (!bounds_check_enabled(ctx, boundscheck)) { vidx = ctx.builder.CreateSub(vidx, ConstantInt::get(T_size, 1)); } else { @@ -3061,22 +3111,39 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, Value *ptr = maybe_decay_tracked(ctx, data_pointer(ctx, obj)); *ret = typed_load(ctx, ptr, vidx, isboxed ? (jl_value_t*)jl_any_type : jt, - obj.tbaa, nullptr, false); + obj.tbaa, nullptr, isboxed, AtomicOrdering::NotAtomic, false); return true; } } } } + // TODO: attempt better codegen for approximate types, if the types + // and offsets of some fields are independent of parameters. + // TODO: generic getfield func with more efficient calling convention + return false; } - else if (f == jl_builtin_setfield && nargs == 3) { + else if (f == jl_builtin_setfield && (nargs == 3 || nargs == 4)) { const jl_cgval_t &obj = argv[1]; const jl_cgval_t &fld = argv[2]; const jl_cgval_t &val = argv[3]; + enum jl_memory_order order = jl_memory_order_notatomic; + if (nargs == 4) { + const jl_cgval_t &ord = argv[4]; + emit_typecheck(ctx, ord, (jl_value_t*)jl_symbol_type, "setfield!"); + if (!ord.constant) + return false; + order = jl_get_atomic_order((jl_sym_t*)ord.constant, false, true); + } + if (order == jl_memory_order_invalid) { + emit_atomic_error(ctx, "invalid atomic ordering"); + *ret = jl_cgval_t(); // unreachable + return true; + } jl_datatype_t *uty = (jl_datatype_t*)jl_unwrap_unionall(obj.typ); - if (jl_is_structtype(uty) && uty != jl_module_type && uty->layout) { - size_t idx = (size_t)-1; + if (jl_is_structtype(uty) && uty->layout) { + ssize_t idx = -1; if (fld.constant && fld.typ == (jl_value_t*)jl_symbol_type) { idx = jl_field_index(uty, (jl_sym_t*)fld.constant, 0); } @@ -3085,11 +3152,28 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, if (i > 0 && i <= jl_datatype_nfields(uty)) idx = i - 1; } - if (idx != (size_t)-1) { + if (idx != -1) { jl_value_t *ft = jl_svecref(uty->types, idx); if (jl_subtype(val.typ, ft)) { // TODO: attempt better codegen for approximate types - emit_setfield(ctx, uty, obj, idx, val, true, true); + bool isboxed = jl_field_isptr(uty, idx); + bool isatomic = jl_field_isatomic(uty, idx); + bool needlock = isatomic && !isboxed && jl_datatype_size(jl_field_type(uty, idx)) > MAX_ATOMIC_SIZE; + if (isatomic == (order == jl_memory_order_notatomic)) { + emit_atomic_error(ctx, + isatomic ? "setfield!: atomic field cannot be written non-atomically" + : "setfield!: non-atomic field cannot be written atomically"); + *ret = jl_cgval_t(); + return true; + } + if (needlock) + emit_lockstate_value(ctx, obj, true); + emit_setfield(ctx, uty, obj, idx, val, true, true, + (needlock || order <= jl_memory_order_notatomic) + ? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0 + : get_llvm_atomic_order(order)); + if (needlock) + emit_lockstate_value(ctx, obj, false); *ret = val; return true; } @@ -3141,6 +3225,8 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, Value *types_len = emit_datatype_nfields(ctx, tyv); Value *idx = emit_unbox(ctx, T_size, fld, (jl_value_t*)jl_long_type); jl_value_t *boundscheck = (nargs == 3 ? argv[3].constant : jl_true); + if (nargs == 3) + emit_typecheck(ctx, argv[3], (jl_value_t*)jl_bool_type, "fieldtype"); emit_bounds_check(ctx, typ, (jl_value_t*)jl_datatype_type, idx, types_len, boundscheck); Value *fieldtyp_p = ctx.builder.CreateInBoundsGEP(T_prjlvalue, decay_derived(ctx, emit_bitcast(ctx, types_svec, T_pprjlvalue)), idx); Value *fieldtyp = tbaa_decorate(tbaa_const, ctx.builder.CreateAlignedLoad(T_prjlvalue, fieldtyp_p, Align(sizeof(void*)))); @@ -3211,7 +3297,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, } } - else if (f == jl_builtin_isdefined && nargs == 2) { + else if (f == jl_builtin_isdefined && (nargs == 2 || nargs == 3)) { const jl_cgval_t &obj = argv[1]; const jl_cgval_t &fld = argv[2]; jl_datatype_t *stt = (jl_datatype_t*)obj.typ; @@ -3241,10 +3327,40 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, else { return false; } + enum jl_memory_order order = jl_memory_order_unspecified; + if (nargs == 3) { + const jl_cgval_t &ord = argv[3]; + emit_typecheck(ctx, ord, (jl_value_t*)jl_symbol_type, "isdefined"); + if (!ord.constant) + return false; + order = jl_get_atomic_order((jl_sym_t*)ord.constant, true, false); + } + if (order == jl_memory_order_invalid) { + emit_atomic_error(ctx, "invalid atomic ordering"); + *ret = jl_cgval_t(); // unreachable + return true; + } if (fieldidx < 0 || fieldidx >= jl_datatype_nfields(stt)) { + if (order != jl_memory_order_unspecified) { + emit_atomic_error(ctx, "isdefined: atomic ordering cannot be specified for nonexistent field"); + *ret = jl_cgval_t(); // unreachable + return true; + } *ret = mark_julia_const(jl_false); + return true; + } + bool isatomic = jl_field_isatomic(stt, fieldidx); + if (!isatomic && order != jl_memory_order_notatomic && order != jl_memory_order_unspecified) { + emit_atomic_error(ctx, "isdefined: non-atomic field cannot be accessed atomically"); + *ret = jl_cgval_t(); // unreachable + return true; } - else if (fieldidx < stt->ninitialized) { + if (isatomic && order == jl_memory_order_notatomic) { + emit_atomic_error(ctx, "isdefined: atomic field cannot be accessed non-atomically"); + *ret = jl_cgval_t(); // unreachable + return true; + } + if (fieldidx < stt->ninitialized) { *ret = mark_julia_const(jl_true); } else if (jl_field_isptr(stt, fieldidx) || jl_type_hasptr(jl_field_type(stt, fieldidx))) { @@ -3261,7 +3377,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, // emit this using the same type as emit_getfield_knownidx // so that LLVM may be able to load-load forward them and fold the result fldv = tbaa_decorate(tbaa, ctx.builder.CreateAlignedLoad(T_prjlvalue, addr, Align(sizeof(size_t)))); - cast(fldv)->setOrdering(AtomicOrdering::Unordered); + cast(fldv)->setOrdering(order <= jl_memory_order_notatomic ? AtomicOrdering::Unordered : get_llvm_atomic_order(order)); } else { fldv = ctx.builder.CreateExtractValue(obj.V, offs); @@ -3276,6 +3392,10 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, else { *ret = mark_julia_const(jl_true); } + if (order > jl_memory_order_monotonic && ret->constant) { + // fence instructions may only have acquire, release, acq_rel, or seq_cst ordering. + ctx.builder.CreateFence(get_llvm_atomic_order(order)); + } return true; } @@ -6531,7 +6651,7 @@ static std::pair, jl_llvm_functions_t> ConstantInt::get(T_size, offsetof(jl_opaque_closure_t, world))); jl_cgval_t closure_world = typed_load(ctx, worldaddr, NULL, (jl_value_t*)jl_long_type, - theArg.tbaa, nullptr, false, sizeof(size_t)); + theArg.tbaa, nullptr, false, AtomicOrdering::NotAtomic, false, sizeof(size_t)); emit_unbox(ctx, T_size, closure_world, (jl_value_t*)jl_long_type, ctx.world_age_field, tbaa_gcframe); // Load closure env @@ -6540,7 +6660,7 @@ static std::pair, jl_llvm_functions_t> ConstantInt::get(T_size, offsetof(jl_opaque_closure_t, captures))); jl_cgval_t closure_env = typed_load(ctx, envaddr, NULL, (jl_value_t*)jl_any_type, - theArg.tbaa, nullptr, false, sizeof(void*)); + theArg.tbaa, nullptr, true, AtomicOrdering::NotAtomic, false, sizeof(void*)); theArg = convert_julia_type(ctx, closure_env, vi.value.typ); } @@ -7815,6 +7935,7 @@ static void init_jit_functions(void) add_named_global("__stack_chk_fail", &__stack_chk_fail); add_named_global(jlpgcstack_func, (void*)NULL); add_named_global(jlerror_func, &jl_error); + add_named_global(jlatomicerror_func, &jl_atomic_error); add_named_global(jlthrow_func, &jl_throw); add_named_global(jlundefvarerror_func, &jl_undefined_var_error); add_named_global(jlboundserrorv_func, &jl_bounds_error_ints); diff --git a/src/datatype.c b/src/datatype.c index 7e4405dc6235a..0dfe2098f6d9a 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -79,6 +79,7 @@ JL_DLLEXPORT jl_typename_t *jl_new_typename_in(jl_sym_t *name, jl_module_t *modu tn->mayinlinealloc = 0; tn->mt = NULL; tn->partial = NULL; + tn->atomicfields = NULL; return tn; } @@ -86,7 +87,7 @@ JL_DLLEXPORT jl_typename_t *jl_new_typename_in(jl_sym_t *name, jl_module_t *modu jl_datatype_t *jl_new_abstracttype(jl_value_t *name, jl_module_t *module, jl_datatype_t *super, jl_svec_t *parameters) { - return jl_new_datatype((jl_sym_t*)name, module, super, parameters, jl_emptysvec, jl_emptysvec, 1, 0, 0); + return jl_new_datatype((jl_sym_t*)name, module, super, parameters, jl_emptysvec, jl_emptysvec, jl_emptysvec, 1, 0, 0); } jl_datatype_t *jl_new_uninitialized_datatype(void) @@ -426,12 +427,14 @@ void jl_compute_field_offsets(jl_datatype_t *st) int zeroinit = 0; int haspadding = 0; int homogeneous = 1; + int needlock = 0; uint32_t npointers = 0; jl_value_t *firstty = jl_field_type(st, 0); for (i = 0; i < nfields; i++) { jl_value_t *fld = jl_field_type(st, i); + int isatomic = jl_field_isatomic(st, i); size_t fsz = 0, al = 1; - if (jl_islayout_inline(fld, &fsz, &al)) { // aka jl_datatype_isinlinealloc + if (jl_islayout_inline(fld, &fsz, &al) && (!isatomic || jl_is_datatype(fld))) { // aka jl_datatype_isinlinealloc if (__unlikely(fsz > max_size)) // Should never happen throw_ovf(should_malloc, desc, st, fsz); @@ -469,9 +472,13 @@ void jl_compute_field_offsets(jl_datatype_t *st) haspadding = 1; } } + if (isatomic && fsz > MAX_ATOMIC_SIZE) + needlock = 1; + if (isatomic && fsz <= MAX_ATOMIC_SIZE) + al = fsz = next_power_of_two(fsz); if (al != 0) { size_t alsz = LLT_ALIGN(sz, al); - if (sz & (al - 1)) + if (alsz != sz) haspadding = 1; sz = alsz; if (al > alignm) @@ -484,6 +491,16 @@ void jl_compute_field_offsets(jl_datatype_t *st) throw_ovf(should_malloc, desc, st, sz); sz += fsz; } + if (needlock) { + size_t offset = LLT_ALIGN(sizeof(jl_mutex_t), alignm); + for (i = 0; i < nfields; i++) { + desc[i].offset += offset; + } + if (__unlikely(max_offset - sz < offset)) + throw_ovf(should_malloc, desc, st, sz); + sz += offset; + haspadding = 1; + } if (homogeneous && jl_is_tuple_type(st)) { // Some tuples become LLVM vectors with stronger alignment than what was calculated above. unsigned al = jl_special_vector_alignment(nfields, firstty); @@ -542,6 +559,7 @@ JL_DLLEXPORT jl_datatype_t *jl_new_datatype( jl_svec_t *parameters, jl_svec_t *fnames, jl_svec_t *ftypes, + jl_svec_t *fattrs, int abstract, int mutabl, int ninitialized) { @@ -589,6 +607,40 @@ JL_DLLEXPORT jl_datatype_t *jl_new_datatype( t->name->names = fnames; jl_gc_wb(t->name, t->name->names); + uint32_t *volatile atomicfields = NULL; + int i; + JL_TRY { + for (i = 0; i + 1 < jl_svec_len(fattrs); i += 2) { + jl_value_t *fldi = jl_svecref(fattrs, i); + jl_sym_t *attr = (jl_sym_t*)jl_svecref(fattrs, i + 1); + JL_TYPECHK(typeassert, long, fldi); + JL_TYPECHK(typeassert, symbol, (jl_value_t*)attr); + size_t fldn = jl_unbox_long(fldi); + if (fldn < 1 || fldn > jl_svec_len(fnames)) + jl_errorf("invalid field attribute %lld", (long long)fldn); + fldn--; + if (attr == atomic_sym) { + if (!mutabl) + jl_errorf("invalid field attribute atomic for immutable struct"); + if (atomicfields == NULL) { + size_t nb = (jl_svec_len(fnames) + 31) / 32 * sizeof(uint32_t); + atomicfields = (uint32_t*)malloc_s(nb); + memset(atomicfields, 0, nb); + } + atomicfields[fldn / 32] |= 1 << (fldn % 32); + } + else { + jl_errorf("invalid field attribute %s", jl_symbol_name(attr)); + } + } + } + JL_CATCH { + if (atomicfields) + free(atomicfields); + jl_rethrow(); + } + tn->atomicfields = atomicfields; + if (t->name->wrapper == NULL) { t->name->wrapper = (jl_value_t*)t; jl_gc_wb(t->name, t); @@ -614,7 +666,7 @@ JL_DLLEXPORT jl_datatype_t *jl_new_primitivetype(jl_value_t *name, jl_module_t * jl_svec_t *parameters, size_t nbits) { jl_datatype_t *bt = jl_new_datatype((jl_sym_t*)name, module, super, parameters, - jl_emptysvec, jl_emptysvec, 0, 0, 0); + jl_emptysvec, jl_emptysvec, jl_emptysvec, 0, 0, 0); uint32_t nbytes = (nbits + 7) / 8; uint32_t alignm = next_power_of_two(nbytes); if (alignm > MAX_ALIGN) @@ -635,7 +687,7 @@ JL_DLLEXPORT jl_datatype_t * jl_new_foreign_type(jl_sym_t *name, int large) { jl_datatype_t *bt = jl_new_datatype(name, module, super, - jl_emptysvec, jl_emptysvec, jl_emptysvec, 0, 1, 0); + jl_emptysvec, jl_emptysvec, jl_emptysvec, jl_emptysvec, 0, 1, 0); bt->size = large ? GC_MAX_SZCLASS+1 : 0; jl_datatype_layout_t *layout = (jl_datatype_layout_t *) jl_gc_perm_alloc(sizeof(jl_datatype_layout_t) + sizeof(jl_fielddescdyn_t), @@ -662,7 +714,17 @@ JL_DLLEXPORT int jl_is_foreign_type(jl_datatype_t *dt) // bits constructors ---------------------------------------------------------- -JL_DLLEXPORT jl_value_t *jl_new_bits(jl_value_t *dt, void *data) +#if MAX_ATOMIC_SIZE > MAX_POINTERATOMIC_SIZE +#error MAX_ATOMIC_SIZE too large +#endif +#if MAX_POINTERATOMIC_SIZE > 16 +#error MAX_POINTERATOMIC_SIZE too large +#endif +#if MAX_POINTERATOMIC_SIZE >= 16 +typedef __uint128_t uint128_t; +#endif + +JL_DLLEXPORT jl_value_t *jl_new_bits(jl_value_t *dt, const void *data) { // data may not have the alignment required by the size // but will always have the alignment required by the datatype @@ -697,31 +759,268 @@ JL_DLLEXPORT jl_value_t *jl_new_bits(jl_value_t *dt, void *data) return v; } -// used by boot.jl -JL_DLLEXPORT jl_value_t *jl_typemax_uint(jl_value_t *bt) +JL_DLLEXPORT jl_value_t *jl_atomic_new_bits(jl_value_t *dt, const char *data) { - uint64_t data = 0xffffffffffffffffULL; + // data must have the required alignment for an atomic of the given size + assert(jl_is_datatype(dt)); + jl_datatype_t *bt = (jl_datatype_t*)dt; + size_t nb = jl_datatype_size(bt); + // some types have special pools to minimize allocations + if (nb == 0) return jl_new_struct_uninit(bt); // returns bt->instance + if (bt == jl_bool_type) return (1 & jl_atomic_load((int8_t*)data)) ? jl_true : jl_false; + if (bt == jl_uint8_type) return jl_box_uint8(jl_atomic_load((uint8_t*)data)); + if (bt == jl_int64_type) return jl_box_int64(jl_atomic_load((int64_t*)data)); + if (bt == jl_int32_type) return jl_box_int32(jl_atomic_load((int32_t*)data)); + if (bt == jl_int8_type) return jl_box_int8(jl_atomic_load((int8_t*)data)); + if (bt == jl_int16_type) return jl_box_int16(jl_atomic_load((int16_t*)data)); + if (bt == jl_uint64_type) return jl_box_uint64(jl_atomic_load((uint64_t*)data)); + if (bt == jl_uint32_type) return jl_box_uint32(jl_atomic_load((uint32_t*)data)); + if (bt == jl_uint16_type) return jl_box_uint16(jl_atomic_load((uint16_t*)data)); + if (bt == jl_char_type) return jl_box_char(jl_atomic_load((uint32_t*)data)); + jl_task_t *ct = jl_current_task; - jl_value_t *v = jl_gc_alloc(ct->ptls, sizeof(size_t), bt); - memcpy(v, &data, sizeof(size_t)); + jl_value_t *v = jl_gc_alloc(ct->ptls, nb, bt); + switch (nb) { + case 1: *(uint8_t*) v = jl_atomic_load((uint8_t*)data); break; + case 2: *(uint16_t*)v = jl_atomic_load((uint16_t*)data); break; + case 4: *(uint32_t*)v = jl_atomic_load((uint32_t*)data); break; +#if MAX_POINTERATOMIC_SIZE >= 8 + case 8: *(uint64_t*)v = jl_atomic_load((uint64_t*)data); break; +#endif +#if MAX_POINTERATOMIC_SIZE >= 16 + case 16: *(uint128_t*)v = jl_atomic_load((uint128_t*)data); break; +#endif + default: + abort(); + } return v; } -void jl_assign_bits(void *dest, jl_value_t *bits) JL_NOTSAFEPOINT +JL_DLLEXPORT void jl_atomic_store_bits(char *dst, const jl_value_t *src, int nb) { - // bits must be a heap box. - size_t nb = jl_datatype_size(jl_typeof(bits)); - if (nb == 0) return; + // dst must have the required alignment for an atomic of the given size + // src must be aligned by the GC switch (nb) { - case 1: *(uint8_t*)dest = *(uint8_t*)bits; break; - case 2: jl_store_unaligned_i16(dest, *(uint16_t*)bits); break; - case 4: jl_store_unaligned_i32(dest, *(uint32_t*)bits); break; - case 8: jl_store_unaligned_i64(dest, *(uint64_t*)bits); break; - case 16: - memcpy(dest, jl_assume_aligned(bits, 16), 16); + case 0: break; + case 1: jl_atomic_store((uint8_t*)dst, *(uint8_t*)src); break; + case 2: jl_atomic_store((uint16_t*)dst, *(uint16_t*)src); break; + case 4: jl_atomic_store((uint32_t*)dst, *(uint32_t*)src); break; +#if MAX_POINTERATOMIC_SIZE >= 8 + case 8: jl_atomic_store((uint64_t*)dst, *(uint64_t*)src); break; +#endif +#if MAX_POINTERATOMIC_SIZE >= 16 + case 16: jl_atomic_store((uint128_t*)dst, *(uint128_t*)src); break; +#endif + default: + abort(); + } +} + +JL_DLLEXPORT jl_value_t *jl_atomic_swap_bits(jl_value_t *dt, char *dst, const jl_value_t *src, int nb) +{ + // dst must have the required alignment for an atomic of the given size + assert(jl_is_datatype(dt)); + jl_datatype_t *bt = (jl_datatype_t*)dt; + // some types have special pools to minimize allocations + if (nb == 0) return jl_new_struct_uninit(bt); // returns bt->instance + if (bt == jl_bool_type) return (1 & jl_atomic_exchange((int8_t*)dst, 1 & *(int8_t*)src)) ? jl_true : jl_false; + if (bt == jl_uint8_type) return jl_box_uint8(jl_atomic_exchange((uint8_t*)dst, *(int8_t*)src)); + if (bt == jl_int64_type) return jl_box_int64(jl_atomic_exchange((int64_t*)dst, *(int64_t*)src)); + if (bt == jl_int32_type) return jl_box_int32(jl_atomic_exchange((int32_t*)dst, *(int32_t*)src)); + if (bt == jl_int8_type) return jl_box_int8(jl_atomic_exchange((int8_t*)dst, *(int8_t*)src)); + if (bt == jl_int16_type) return jl_box_int16(jl_atomic_exchange((int16_t*)dst, *(int16_t*)src)); + if (bt == jl_uint64_type) return jl_box_uint64(jl_atomic_exchange((uint64_t*)dst, *(uint64_t*)src)); + if (bt == jl_uint32_type) return jl_box_uint32(jl_atomic_exchange((uint32_t*)dst, *(uint32_t*)src)); + if (bt == jl_uint16_type) return jl_box_uint16(jl_atomic_exchange((uint16_t*)dst, *(uint16_t*)src)); + if (bt == jl_char_type) return jl_box_char(jl_atomic_exchange((uint32_t*)dst, *(uint32_t*)src)); + + jl_task_t *ct = jl_current_task; + jl_value_t *v = jl_gc_alloc(ct->ptls, jl_datatype_size(bt), bt); + switch (nb) { + case 1: *(uint8_t*) v = jl_atomic_exchange((uint8_t*)dst, *(uint8_t*)src); break; + case 2: *(uint16_t*)v = jl_atomic_exchange((uint16_t*)dst, *(uint16_t*)src); break; + case 4: *(uint32_t*)v = jl_atomic_exchange((uint32_t*)dst, *(uint32_t*)src); break; +#if MAX_POINTERATOMIC_SIZE >= 8 + case 8: *(uint64_t*)v = jl_atomic_exchange((uint64_t*)dst, *(uint64_t*)src); break; +#endif +#if MAX_POINTERATOMIC_SIZE >= 16 + case 16: *(uint128_t*)v = jl_atomic_exchange((uint128_t*)dst, *(uint128_t*)src); break; +#endif + default: + abort(); + } + return v; +} + +JL_DLLEXPORT int jl_atomic_bool_cmpswap_bits(char *dst, const jl_value_t *expected, const jl_value_t *src, int nb) +{ + // dst must have the required alignment for an atomic of the given size + // n.b.: this can spuriously fail if there are padding bits, the caller should deal with that + int success; + switch (nb) { + case 0: { + success = 1; break; - default: memcpy(dest, bits, nb); } + case 1: { + uint8_t y = *(uint8_t*)expected; + success = jl_atomic_cmpswap((uint8_t*)dst, &y, *(uint8_t*)src); + break; + } + case 2: { + uint16_t y = *(uint16_t*)expected; + success = jl_atomic_cmpswap((uint16_t*)dst, &y, *(uint16_t*)src); + break; + } + case 4: { + uint32_t y = *(uint32_t*)expected; + success = jl_atomic_cmpswap((uint32_t*)dst, &y, *(uint32_t*)src); + break; + } +#if MAX_POINTERATOMIC_SIZE >= 8 + case 8: { + uint64_t y = *(uint64_t*)expected; + success = jl_atomic_cmpswap((uint64_t*)dst, &y, *(uint64_t*)src); + break; + } +#endif +#if MAX_POINTERATOMIC_SIZE >= 16 + case 16: { + uint128_t y = *(uint128_t*)expected; + success = jl_atomic_cmpswap((uint128_t*)dst, &y, *(uint128_t*)src); + break; + } +#endif + default: + abort(); + } + return success; +} + +JL_DLLEXPORT jl_value_t *jl_atomic_cmpswap_bits(jl_datatype_t *dt, char *dst, const jl_value_t *expected, const jl_value_t *src, int nb) +{ + // dst must have the required alignment for an atomic of the given size + // n.b.: this does not spuriously fail if there are padding bits + jl_value_t *params[2]; + params[0] = (jl_value_t*)dt; + params[1] = (jl_value_t*)jl_bool_type; + jl_datatype_t *tuptyp = jl_apply_tuple_type_v(params, 2); + JL_GC_PROMISE_ROOTED(tuptyp); // (JL_ALWAYS_LEAFTYPE) + int isptr = jl_field_isptr(tuptyp, 0); + jl_task_t *ct = jl_current_task; + jl_value_t *y = jl_gc_alloc(ct->ptls, isptr ? nb : tuptyp->size, isptr ? dt : tuptyp); + int success; + jl_datatype_t *et = (jl_datatype_t*)jl_typeof(expected); + switch (nb) { + case 0: { + success = (dt == et); + break; + } + case 1: { + uint8_t *y8 = (uint8_t*)y; + if (dt == et) { + *y8 = *(uint8_t*)expected; + success = jl_atomic_cmpswap((uint8_t*)dst, y8, *(uint8_t*)src); + } + else { + *y8 = jl_atomic_load((uint8_t*)dst); + success = 0; + } + break; + } + case 2: { + uint16_t *y16 = (uint16_t*)y; + if (dt == et) { + *y16 = *(uint16_t*)expected; + while (1) { + success = jl_atomic_cmpswap((uint16_t*)dst, y16, *(uint16_t*)src); + if (success || !dt->layout->haspadding || !jl_egal__bits(y, expected, dt)) + break; + } + } + else { + *y16 = jl_atomic_load((uint16_t*)dst); + success = 0; + } + break; + } + case 4: { + uint32_t *y32 = (uint32_t*)y; + if (dt == et) { + *y32 = *(uint32_t*)expected; + while (1) { + success = jl_atomic_cmpswap((uint32_t*)dst, y32, *(uint32_t*)src); + if (success || !dt->layout->haspadding || !jl_egal__bits(y, expected, dt)) + break; + } + } + else { + *y32 = jl_atomic_load((uint32_t*)dst); + success = 0; + } + break; + } +#if MAX_POINTERATOMIC_SIZE >= 8 + case 8: { + uint64_t *y64 = (uint64_t*)y; + if (dt == et) { + *y64 = *(uint64_t*)expected; + while (1) { + success = jl_atomic_cmpswap((uint64_t*)dst, y64, *(uint64_t*)src); + if (success || !dt->layout->haspadding || !jl_egal__bits(y, expected, dt)) + break; + } + } + else { + *y64 = jl_atomic_load((uint64_t*)dst); + success = 0; + } + break; + } +#endif +#if MAX_POINTERATOMIC_SIZE >= 16 + case 16: { + uint128_t *y128 = (uint128_t*)y; + if (dt == et) { + *y128 = *(uint128_t*)expected; + while (1) { + success = jl_atomic_cmpswap((uint128_t*)dst, y128, *(uint128_t*)src); + if (success || !dt->layout->haspadding || !jl_egal__bits(y, expected, dt)) + break; + } + } + else { + *y128 = jl_atomic_load((uint128_t*)dst); + success = 0; + } + break; + } +#endif + default: + abort(); + } + if (isptr) { + JL_GC_PUSH1(&y); + jl_value_t *z = jl_gc_alloc(ct->ptls, tuptyp->size, tuptyp); + *(jl_value_t**)z = y; + JL_GC_POP(); + y = z; + nb = sizeof(jl_value_t*); + } + *((uint8_t*)y + nb) = success ? 1 : 0; + return y; +} + + + +// used by boot.jl +JL_DLLEXPORT jl_value_t *jl_typemax_uint(jl_value_t *bt) +{ + uint64_t data = 0xffffffffffffffffULL; + jl_task_t *ct = jl_current_task; + jl_value_t *v = jl_gc_alloc(ct->ptls, sizeof(size_t), bt); + memcpy(v, &data, sizeof(size_t)); + return v; } #define PERMBOXN_FUNC(nb,nw) \ @@ -894,25 +1193,19 @@ JL_DLLEXPORT jl_value_t *jl_new_struct(jl_datatype_t *type, ...) jl_task_t *ct = jl_current_task; if (type->instance != NULL) return type->instance; va_list args; - size_t nf = jl_datatype_nfields(type); + size_t i, nf = jl_datatype_nfields(type); va_start(args, type); jl_value_t *jv = jl_gc_alloc(ct->ptls, jl_datatype_size(type), type); - for (size_t i = 0; i < nf; i++) { - set_nth_field(type, (void*)jv, i, va_arg(args, jl_value_t*)); + if (nf > 0 && jl_field_offset(type, 0) != 0) { + memset(jv, 0, jl_field_offset(type, 0)); + } + for (i = 0; i < nf; i++) { + set_nth_field(type, jv, i, va_arg(args, jl_value_t*), 0); } va_end(args); return jv; } -static void init_struct_tail(jl_datatype_t *type, jl_value_t *jv, size_t na) JL_NOTSAFEPOINT -{ - if (na < jl_datatype_nfields(type)) { - char *data = (char*)jl_data_ptr(jv); - size_t offs = jl_field_offset(type, na); - memset(data + offs, 0, jl_datatype_size(type) - offs); - } -} - JL_DLLEXPORT jl_value_t *jl_new_structv(jl_datatype_t *type, jl_value_t **args, uint32_t na) { jl_task_t *ct = jl_current_task; @@ -929,10 +1222,21 @@ JL_DLLEXPORT jl_value_t *jl_new_structv(jl_datatype_t *type, jl_value_t **args, if (type->instance != NULL) return type->instance; jl_value_t *jv = jl_gc_alloc(ct->ptls, jl_datatype_size(type), type); - for (size_t i = 0; i < na; i++) { - set_nth_field(type, (void*)jv, i, args[i]); + if (jl_datatype_nfields(type) > 0) { + if (jl_field_offset(type, 0) != 0) { + memset(jl_data_ptr(jv), 0, jl_field_offset(type, 0)); + } + JL_GC_PUSH1(&jv); + for (size_t i = 0; i < na; i++) { + set_nth_field(type, jv, i, args[i], 0); + } + if (na < jl_datatype_nfields(type)) { + char *data = (char*)jl_data_ptr(jv); + size_t offs = jl_field_offset(type, na); + memset(data + offs, 0, jl_datatype_size(type) - offs); + } + JL_GC_POP(); } - init_struct_tail(type, jv, na); return jv; } @@ -957,13 +1261,19 @@ JL_DLLEXPORT jl_value_t *jl_new_structt(jl_datatype_t *type, jl_value_t *tup) } return type->instance; } - jl_value_t *jv = jl_gc_alloc(ct->ptls, jl_datatype_size(type), type); + size_t size = jl_datatype_size(type); + jl_value_t *jv = jl_gc_alloc(ct->ptls, size, type); + if (nf == 0) + return jv; jl_value_t *fi = NULL; - if (type->layout->npointers > 0) { + if (type->zeroinit) { // if there are references, zero the space first to prevent the GC // from seeing uninitialized references during jl_get_nth_field and jl_isa, // which can allocate. - memset(jl_data_ptr(jv), 0, jl_datatype_size(type)); + memset(jl_data_ptr(jv), 0, size); + } + else if (jl_field_offset(type, 0) != 0) { + memset(jl_data_ptr(jv), 0, jl_field_offset(type, 0)); } JL_GC_PUSH2(&jv, &fi); for (size_t i = 0; i < nargs; i++) { @@ -971,7 +1281,7 @@ JL_DLLEXPORT jl_value_t *jl_new_structt(jl_datatype_t *type, jl_value_t *tup) fi = jl_get_nth_field(tup, i); if (!jl_isa(fi, ft)) jl_type_error("new", ft, fi); - set_nth_field(type, (void*)jv, i, fi); + set_nth_field(type, jv, i, fi, 0); } JL_GC_POP(); return jv; @@ -990,6 +1300,16 @@ JL_DLLEXPORT jl_value_t *jl_new_struct_uninit(jl_datatype_t *type) // field access --------------------------------------------------------------- +JL_DLLEXPORT void jl_lock_value(jl_value_t *v) JL_NOTSAFEPOINT +{ + JL_LOCK_NOGC((jl_mutex_t*)v); +} + +JL_DLLEXPORT void jl_unlock_value(jl_value_t *v) JL_NOTSAFEPOINT +{ + JL_UNLOCK_NOGC((jl_mutex_t*)v); +} + JL_DLLEXPORT int jl_field_index(jl_datatype_t *t, jl_sym_t *fld, int err) { jl_svec_t *fn = jl_field_names(t); @@ -1023,19 +1343,39 @@ JL_DLLEXPORT int jl_field_index(jl_datatype_t *t, jl_sym_t *fld, int err) JL_DLLEXPORT jl_value_t *jl_get_nth_field(jl_value_t *v, size_t i) { jl_datatype_t *st = (jl_datatype_t*)jl_typeof(v); - assert(i < jl_datatype_nfields(st)); + if (i >= jl_datatype_nfields(st)) + jl_bounds_error_int(v, i + 1); size_t offs = jl_field_offset(st, i); if (jl_field_isptr(st, i)) { return jl_atomic_load_relaxed((jl_value_t**)((char*)v + offs)); } jl_value_t *ty = jl_field_type_concrete(st, i); + int isatomic = jl_field_isatomic(st, i); if (jl_is_uniontype(ty)) { - uint8_t sel = ((uint8_t*)v)[offs + jl_field_size(st, i) - 1]; + assert(!isatomic); + size_t fsz = jl_field_size(st, i); + uint8_t sel = ((uint8_t*)v)[offs + fsz - 1]; ty = jl_nth_union_component(ty, sel); if (jl_is_datatype_singleton((jl_datatype_t*)ty)) return ((jl_datatype_t*)ty)->instance; } - return jl_new_bits(ty, (char*)v + offs); + jl_value_t *r; + size_t fsz = jl_datatype_size(ty); + int needlock = (isatomic && fsz > MAX_ATOMIC_SIZE); + if (isatomic && !needlock) { + r = jl_atomic_new_bits(ty, (char*)v + offs); + } + else if (needlock) { + jl_task_t *ct = jl_current_task; + r = jl_gc_alloc(ct->ptls, fsz, ty); + jl_lock_value(v); + memcpy((char*)r, (char*)v + offs, fsz); + jl_unlock_value(v); + } + else { + r = jl_new_bits(ty, (char*)v + offs); + } + return undefref_check((jl_datatype_t*)ty, r); } JL_DLLEXPORT jl_value_t *jl_get_nth_field_noalloc(jl_value_t *v JL_PROPAGATES_ROOT, size_t i) JL_NOTSAFEPOINT @@ -1049,28 +1389,39 @@ JL_DLLEXPORT jl_value_t *jl_get_nth_field_noalloc(jl_value_t *v JL_PROPAGATES_RO JL_DLLEXPORT jl_value_t *jl_get_nth_field_checked(jl_value_t *v, size_t i) { - jl_datatype_t *st = (jl_datatype_t*)jl_typeof(v); - if (i >= jl_datatype_nfields(st)) - jl_bounds_error_int(v, i + 1); - size_t offs = jl_field_offset(st, i); - if (jl_field_isptr(st, i)) { - jl_value_t *fval = jl_atomic_load_relaxed((jl_value_t**)((char*)v + offs)); - if (__unlikely(fval == NULL)) - jl_throw(jl_undefref_exception); - return fval; + jl_value_t *r = jl_get_nth_field(v, i); + if (__unlikely(r == NULL)) + jl_throw(jl_undefref_exception); + return r; +} + +static inline void memassign_safe(int hasptr, jl_value_t *parent, char *dst, const jl_value_t *src, size_t nb) JL_NOTSAFEPOINT +{ + if (hasptr) { + // assert that although dst might have some undefined bits, the src heap box should be okay with that + assert(LLT_ALIGN(nb, sizeof(void*)) == LLT_ALIGN(jl_datatype_size(jl_typeof(src)), sizeof(void*))); + size_t nptr = nb / sizeof(void*); + memmove_refs((void**)dst, (void**)src, nptr); + jl_gc_multi_wb(parent, src); + src = (jl_value_t*)((char*)src + nptr * sizeof(void*)); + nb -= nptr * sizeof(void*); } - jl_value_t *ty = jl_field_type_concrete(st, i); - if (jl_is_uniontype(ty)) { - size_t fsz = jl_field_size(st, i); - uint8_t sel = ((uint8_t*)v)[offs + fsz - 1]; - ty = jl_nth_union_component(ty, sel); - if (jl_is_datatype_singleton((jl_datatype_t*)ty)) - return ((jl_datatype_t*)ty)->instance; + else { + // src must be a heap box. + assert(nb == jl_datatype_size(jl_typeof(src))); + } + switch (nb) { + case 0: break; + case 1: *(uint8_t*)dst = *(uint8_t*)src; break; + case 2: jl_store_unaligned_i16(dst, *(uint16_t*)src); break; + case 4: jl_store_unaligned_i32(dst, *(uint32_t*)src); break; + case 8: jl_store_unaligned_i64(dst, *(uint64_t*)src); break; + case 16: memcpy(dst, jl_assume_aligned(src, 16), 16); break; + default: memcpy(dst, src, nb); break; } - return undefref_check((jl_datatype_t*)ty, jl_new_bits(ty, (char*)v + offs)); } -void set_nth_field(jl_datatype_t *st, void *v, size_t i, jl_value_t *rhs) JL_NOTSAFEPOINT +void set_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *rhs, int isatomic) JL_NOTSAFEPOINT { size_t offs = jl_field_offset(st, i); if (rhs == NULL) { // TODO: this should be invalid, but it happens frequently in ircode.c @@ -1083,34 +1434,301 @@ void set_nth_field(jl_datatype_t *st, void *v, size_t i, jl_value_t *rhs) JL_NOT } else { jl_value_t *ty = jl_field_type_concrete(st, i); - if (jl_is_uniontype(ty)) { - uint8_t *psel = &((uint8_t*)v)[offs + jl_field_size(st, i) - 1]; + jl_value_t *rty = jl_typeof(rhs); + int hasptr; + int isunion = jl_is_uniontype(ty); + if (isunion) { + assert(!isatomic); + size_t fsz = jl_field_size(st, i); + uint8_t *psel = &((uint8_t*)v)[offs + fsz - 1]; unsigned nth = 0; - if (!jl_find_union_component(ty, jl_typeof(rhs), &nth)) + if (!jl_find_union_component(ty, rty, &nth)) assert(0 && "invalid field assignment to isbits union"); *psel = nth; - if (jl_is_datatype_singleton((jl_datatype_t*)jl_typeof(rhs))) + if (jl_is_datatype_singleton((jl_datatype_t*)rty)) return; + hasptr = 0; + } + else { + hasptr = ((jl_datatype_t*)ty)->layout->npointers > 0; + } + size_t fsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the final copy + int needlock = (isatomic && fsz > MAX_ATOMIC_SIZE); + if (isatomic && !needlock) { + jl_atomic_store_bits((char*)v + offs, rhs, fsz); + if (hasptr) + jl_gc_multi_wb(v, rhs); // rhs is immutable + } + else if (needlock) { + jl_lock_value(v); + memcpy((char*)v + offs, (char*)rhs, fsz); + jl_unlock_value(v); + } + else { + memassign_safe(hasptr, v, (char*)v + offs, rhs, fsz); } - jl_assign_bits((char*)v + offs, rhs); - jl_gc_multi_wb(v, rhs); } } -JL_DLLEXPORT int jl_field_isdefined(jl_value_t *v, size_t i) JL_NOTSAFEPOINT +jl_value_t *swap_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *rhs, int isatomic) { - jl_datatype_t *st = (jl_datatype_t*)jl_typeof(v); + jl_value_t *ty = jl_field_type_concrete(st, i); + if (!jl_isa(rhs, ty)) + jl_type_error("swapfield!", ty, rhs); + size_t offs = jl_field_offset(st, i); + jl_value_t *r; + if (jl_field_isptr(st, i)) { + if (isatomic) + r = jl_atomic_exchange((jl_value_t**)((char*)v + offs), rhs); + else + r = jl_atomic_exchange_relaxed((jl_value_t**)((char*)v + offs), rhs); + jl_gc_wb(v, rhs); + } + else { + jl_value_t *rty = jl_typeof(rhs); + int hasptr; + int isunion = jl_is_uniontype(ty); + if (isunion) { + assert(!isatomic); + r = jl_get_nth_field(v, i); + size_t fsz = jl_field_size(st, i); + uint8_t *psel = &((uint8_t*)v)[offs + fsz - 1]; + unsigned nth = 0; + if (!jl_find_union_component(ty, rty, &nth)) + assert(0 && "invalid field assignment to isbits union"); + *psel = nth; + if (jl_is_datatype_singleton((jl_datatype_t*)rty)) + return r; + hasptr = 0; + } + else { + hasptr = ((jl_datatype_t*)ty)->layout->npointers > 0; + } + size_t fsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the final copy + int needlock = (isatomic && fsz > MAX_ATOMIC_SIZE); + if (isatomic && !needlock) { + r = jl_atomic_swap_bits(rty, (char*)v + offs, rhs, fsz); + if (hasptr) + jl_gc_multi_wb(v, rhs); // rhs is immutable + } + else { + if (needlock) { + jl_task_t *ct = jl_current_task; + r = jl_gc_alloc(ct->ptls, fsz, ty); + jl_lock_value(v); + memcpy((char*)r, (char*)v + offs, fsz); + memcpy((char*)v + offs, (char*)rhs, fsz); + jl_unlock_value(v); + } + else { + if (!isunion) + r = jl_new_bits(ty, (char*)v + offs); + memassign_safe(hasptr, v, (char*)v + offs, rhs, fsz); + } + if (needlock || !isunion) + r = undefref_check((jl_datatype_t*)ty, r); + } + } + if (__unlikely(r == NULL)) + jl_throw(jl_undefref_exception); + return r; +} + +jl_value_t *modify_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *op, jl_value_t *rhs, int isatomic) +{ + size_t offs = jl_field_offset(st, i); + jl_value_t *ty = jl_field_type_concrete(st, i); + jl_value_t *r = jl_get_nth_field_checked(v, i); + if (isatomic && jl_field_isptr(st, i)) + jl_fence(); // load was previously only relaxed + jl_value_t **args; + JL_GC_PUSHARGS(args, 2); + args[0] = r; + while (1) { + args[1] = rhs; + jl_value_t *y = jl_apply_generic(op, args, 2); + args[1] = y; + if (!jl_isa(y, ty)) + jl_type_error("modifyfield!", ty, y); + if (jl_field_isptr(st, i)) { + jl_value_t **p = (jl_value_t**)((char*)v + offs); + if (isatomic ? jl_atomic_cmpswap(p, &r, y) : jl_atomic_cmpswap_relaxed(p, &r, y)) + break; + } + else { + jl_value_t *yty = jl_typeof(y); + jl_value_t *rty = jl_typeof(r); + int hasptr; + int isunion = jl_is_uniontype(ty); + if (isunion) { + assert(!isatomic); + hasptr = 0; + } + else { + hasptr = ((jl_datatype_t*)ty)->layout->npointers > 0; + } + size_t fsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the final copy + int needlock = (isatomic && fsz > MAX_ATOMIC_SIZE); + if (isatomic && !needlock) { + if (jl_atomic_bool_cmpswap_bits((char*)v + offs, r, y, fsz)) { + if (hasptr) + jl_gc_multi_wb(v, y); // y is immutable + break; + } + r = jl_atomic_new_bits(ty, (char*)v + offs); + } + else { + if (needlock) + jl_lock_value(v); + int success = memcmp((char*)v + offs, r, fsz) == 0; + if (success) { + if (isunion) { + size_t fsz = jl_field_size(st, i); + uint8_t *psel = &((uint8_t*)v)[offs + fsz - 1]; + success = (jl_typeof(r) == jl_nth_union_component(ty, *psel)); + if (success) { + unsigned nth = 0; + if (!jl_find_union_component(ty, yty, &nth)) + assert(0 && "invalid field assignment to isbits union"); + *psel = nth; + if (jl_is_datatype_singleton((jl_datatype_t*)yty)) + break; + } + fsz = jl_datatype_size((jl_datatype_t*)yty); // need to shrink-wrap the final copy + } + else { + assert(yty == ty && rty == ty); + } + memassign_safe(hasptr, v, (char*)v + offs, y, fsz); + } + if (needlock) + jl_unlock_value(v); + if (success) + break; + r = jl_get_nth_field(v, i); + } + } + args[0] = r; + jl_gc_safepoint(); + } + // args[0] == r (old); args[1] == y (new) + args[0] = jl_f_tuple(NULL, args, 2); + JL_GC_POP(); + return args[0]; +} + +jl_value_t *replace_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *expected, jl_value_t *rhs, int isatomic) +{ + jl_value_t *ty = jl_field_type_concrete(st, i); + if (!jl_isa(rhs, ty)) + jl_type_error("replacefield!", ty, rhs); size_t offs = jl_field_offset(st, i); - char *fld = (char*)v + offs; + jl_value_t *r = expected; if (jl_field_isptr(st, i)) { - jl_value_t *fval = jl_atomic_load_relaxed((jl_value_t**)fld); - return fval != NULL; + jl_value_t **p = (jl_value_t**)((char*)v + offs); + int success; + while (1) { + success = isatomic ? jl_atomic_cmpswap(p, &r, rhs) : jl_atomic_cmpswap_relaxed(p, &r, rhs); + if (success) + jl_gc_wb(v, rhs); + if (__unlikely(r == NULL)) + jl_throw(jl_undefref_exception); + if (success || !jl_egal(r, expected)) + break; + } + jl_value_t **args; + JL_GC_PUSHARGS(args, 2); + args[0] = r; + args[1] = success ? jl_true : jl_false; + r = jl_f_tuple(NULL, args, 2); + JL_GC_POP(); + } + else { + int hasptr; + int isunion = jl_is_uniontype(ty); + int needlock; + jl_value_t *rty = ty; + size_t fsz; + if (isunion) { + assert(!isatomic); + hasptr = 0; + needlock = 0; + isatomic = 0; // this makes GCC happy + } + else { + hasptr = ((jl_datatype_t*)ty)->layout->npointers > 0; + fsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the final copy + needlock = (isatomic && fsz > MAX_ATOMIC_SIZE); + } + if (isatomic && !needlock) { + r = jl_atomic_cmpswap_bits((jl_datatype_t*)rty, (char*)v + offs, r, rhs, fsz); + int success = *((uint8_t*)r + fsz); + if (success && hasptr) + jl_gc_multi_wb(v, rhs); // rhs is immutable + } + else { + jl_task_t *ct = jl_current_task; + uint8_t *psel; + if (isunion) { + size_t fsz = jl_field_size(st, i); + psel = &((uint8_t*)v)[offs + fsz - 1]; + rty = jl_nth_union_component(rty, *psel); + } + jl_value_t *params[2]; + params[0] = rty; + params[1] = (jl_value_t*)jl_bool_type; + jl_datatype_t *tuptyp = jl_apply_tuple_type_v(params, 2); + JL_GC_PROMISE_ROOTED(tuptyp); // (JL_ALWAYS_LEAFTYPE) + assert(!jl_field_isptr(tuptyp, 0)); + r = jl_gc_alloc(ct->ptls, tuptyp->size, (jl_value_t*)tuptyp); + int success = (rty == jl_typeof(expected)); + if (needlock) + jl_lock_value(v); + size_t fsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the final copy + memcpy((char*)r, (char*)v + offs, fsz); + if (success) { + if (((jl_datatype_t*)rty)->layout->haspadding) + success = jl_egal__bits(r, expected, (jl_datatype_t*)rty); + else + success = memcmp((char*)r, (char*)expected, fsz) == 0; + } + *((uint8_t*)r + fsz) = success ? 1 : 0; + if (success) { + jl_value_t *rty = jl_typeof(rhs); + size_t fsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the final copy + if (isunion) { + unsigned nth = 0; + if (!jl_find_union_component(ty, rty, &nth)) + assert(0 && "invalid field assignment to isbits union"); + *psel = nth; + if (jl_is_datatype_singleton((jl_datatype_t*)rty)) + return r; + } + memassign_safe(hasptr, v, (char*)v + offs, rhs, fsz); + } + if (needlock) + jl_unlock_value(v); + } + r = undefref_check((jl_datatype_t*)rty, r); + if (__unlikely(r == NULL)) + jl_throw(jl_undefref_exception); } - jl_datatype_t *ft = (jl_datatype_t*)jl_field_type_concrete(st, i); - if (jl_is_datatype(ft) && ft->layout->first_ptr >= 0) { - return ((jl_value_t**)fld)[ft->layout->first_ptr] != NULL; + return r; +} + +JL_DLLEXPORT int jl_field_isdefined(jl_value_t *v, size_t i) JL_NOTSAFEPOINT +{ + jl_datatype_t *st = (jl_datatype_t*)jl_typeof(v); + size_t offs = jl_field_offset(st, i); + jl_value_t **fld = (jl_value_t**)((char*)v + offs); + if (!jl_field_isptr(st, i)) { + jl_datatype_t *ft = (jl_datatype_t*)jl_field_type_concrete(st, i); + if (!jl_is_datatype(ft) || ft->layout->first_ptr < 0) + return 2; // isbits are always defined + fld += ft->layout->first_ptr; } - return 1; + jl_value_t *fval = jl_atomic_load_relaxed(fld); + return fval != NULL ? 1 : 0; } JL_DLLEXPORT size_t jl_get_field_offset(jl_datatype_t *ty, int field) JL_NOTSAFEPOINT diff --git a/src/dump.c b/src/dump.c index 9a2627e362b2e..efe6de98db037 100644 --- a/src/dump.c +++ b/src/dump.c @@ -816,6 +816,10 @@ static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v, int as_li jl_serialize_value(s, tn->mt); ios_write(s->s, (char*)&tn->hash, sizeof(tn->hash)); write_uint8(s->s, tn->abstract | (tn->mutabl << 1) | (tn->references_self << 2) | (tn->mayinlinealloc << 3)); + size_t nb = tn->atomicfields ? (jl_svec_len(tn->names) + 31) / 32 * sizeof(uint32_t) : 0; + write_int32(s->s, nb); + if (nb) + ios_write(s->s, (char*)tn->atomicfields, nb); } return; } @@ -1717,7 +1721,6 @@ static jl_value_t *jl_deserialize_value_any(jl_serializer_state *s, uint8_t tag, memset(tn, 0, sizeof(jl_typename_t)); tn->cache = jl_emptysvec; // the cache is refilled later (tag 5) tn->linearcache = jl_emptysvec; // the cache is refilled later (tag 5) - tn->partial = NULL; backref_list.items[pos] = tn; } jl_module_t *m = (jl_module_t*)jl_deserialize_value(s, NULL); @@ -1737,6 +1740,11 @@ static jl_value_t *jl_deserialize_value_any(jl_serializer_state *s, uint8_t tag, tn->mutabl = (flags>>1) & 1; tn->references_self = (flags>>2) & 1; tn->mayinlinealloc = (flags>>3) & 1; + size_t nfields = read_int32(s->s); + if (nfields) { + tn->atomicfields = (uint32_t*)malloc(nfields); + ios_read(s->s, (char*)tn->atomicfields, nfields); + } } else { jl_datatype_t *dt = (jl_datatype_t*)jl_unwrap_unionall(jl_get_global(m, sym)); diff --git a/src/gc.c b/src/gc.c index 45a2cb11e30d8..5429510f08651 100644 --- a/src/gc.c +++ b/src/gc.c @@ -331,7 +331,7 @@ static void finalize_object(arraylist_t *list, jl_value_t *o, // The `memset` (like any other content mutation) has to be done // **before** the `cmpxchg` which publishes the length. memset(&items[len], 0, (oldlen - len) * sizeof(void*)); - jl_atomic_compare_exchange(&list->len, oldlen, len); + jl_atomic_cmpswap(&list->len, &oldlen, len); } else { list->len = len; @@ -1549,7 +1549,7 @@ static void gc_sweep_perm_alloc(void) // mark phase -JL_DLLEXPORT void jl_gc_queue_root(jl_value_t *ptr) +JL_DLLEXPORT void jl_gc_queue_root(const jl_value_t *ptr) { jl_ptls_t ptls = jl_current_task->ptls; jl_taggedvalue_t *o = jl_astaggedvalue(ptr); @@ -1558,11 +1558,11 @@ JL_DLLEXPORT void jl_gc_queue_root(jl_value_t *ptr) // write GC_OLD to the GC bits outside GC. This could cause // duplicated objects in the remset but that shouldn't be a problem. o->bits.gc = GC_MARKED; - arraylist_push(ptls->heap.remset, ptr); + arraylist_push(ptls->heap.remset, (jl_value_t*)ptr); ptls->heap.remset_nptr++; // conservative } -void jl_gc_queue_multiroot(jl_value_t *parent, jl_value_t *ptr) JL_NOTSAFEPOINT +void jl_gc_queue_multiroot(const jl_value_t *parent, const jl_value_t *ptr) JL_NOTSAFEPOINT { // first check if this is really necessary // TODO: should we store this info in one of the extra gc bits? diff --git a/src/gf.c b/src/gf.c index 0c02fd1602f08..fda7a56c06c4c 100644 --- a/src/gf.c +++ b/src/gf.c @@ -2537,7 +2537,8 @@ jl_function_t *jl_new_generic_function_with_supertype(jl_sym_t *name, jl_module_ jl_sym_t *tname = jl_symbol(prefixed); free(prefixed); jl_datatype_t *ftype = (jl_datatype_t*)jl_new_datatype( - tname, module, st, jl_emptysvec, jl_emptysvec, jl_emptysvec, 0, 0, 0); + tname, module, st, jl_emptysvec, jl_emptysvec, jl_emptysvec, jl_emptysvec, + 0, 0, 0); assert(jl_is_datatype(ftype)); JL_GC_PUSH1(&ftype); ftype->name->mt->name = name; diff --git a/src/init.c b/src/init.c index 5f49e2becd7c5..31d5d467ed14d 100644 --- a/src/init.c +++ b/src/init.c @@ -835,6 +835,7 @@ static void post_boot_hooks(void) jl_diverror_exception = jl_new_struct_uninit((jl_datatype_t*)core("DivideError")); jl_undefref_exception = jl_new_struct_uninit((jl_datatype_t*)core("UndefRefError")); jl_undefvarerror_type = (jl_datatype_t*)core("UndefVarError"); + jl_atomicerror_type = (jl_datatype_t*)core("ConcurrencyViolationError"); jl_interrupt_exception = jl_new_struct_uninit((jl_datatype_t*)core("InterruptException")); jl_boundserror_type = (jl_datatype_t*)core("BoundsError"); jl_memory_exception = jl_new_struct_uninit((jl_datatype_t*)core("OutOfMemoryError")); diff --git a/src/intrinsics.cpp b/src/intrinsics.cpp index 173a9bb429c08..904623e4ec43c 100644 --- a/src/intrinsics.cpp +++ b/src/intrinsics.cpp @@ -12,6 +12,7 @@ FunctionType *get_intr_args1(LLVMContext &C) { return FunctionType::get(T_prjlva FunctionType *get_intr_args2(LLVMContext &C) { return FunctionType::get(T_prjlvalue, {T_prjlvalue, T_prjlvalue}, false); } FunctionType *get_intr_args3(LLVMContext &C) { return FunctionType::get(T_prjlvalue, {T_prjlvalue, T_prjlvalue, T_prjlvalue}, false); } FunctionType *get_intr_args4(LLVMContext &C) { return FunctionType::get(T_prjlvalue, {T_prjlvalue, T_prjlvalue, T_prjlvalue, T_prjlvalue}, false); } +FunctionType *get_intr_args5(LLVMContext &C) { return FunctionType::get(T_prjlvalue, {T_prjlvalue, T_prjlvalue, T_prjlvalue, T_prjlvalue, T_prjlvalue}, false); } static JuliaFunction *runtime_func[num_intrinsics] = { #define ADD_I(name, nargs) new JuliaFunction{"jl_"#name, get_intr_args##nargs, nullptr}, @@ -281,6 +282,9 @@ static jl_cgval_t ghostValue(jl_value_t *ty); static Value *emit_unboxed_coercion(jl_codectx_t &ctx, Type *to, Value *unboxed) { Type *ty = unboxed->getType(); + if (ty == to) + return unboxed; + assert(to->isIntOrPtrTy() || to->isFloatingPointTy()); bool frompointer = ty->isPointerTy(); bool topointer = to->isPointerTy(); const DataLayout &DL = jl_data_layout; @@ -300,6 +304,14 @@ static Value *emit_unboxed_coercion(jl_codectx_t &ctx, Type *to, Value *unboxed) if (frompointer && topointer) { unboxed = emit_bitcast(ctx, unboxed, to); } + else if (!ty->isIntOrPtrTy() && !ty->isFloatingPointTy()) { + const DataLayout &DL = jl_data_layout; + unsigned nb = DL.getTypeSizeInBits(ty); + assert(nb == DL.getTypeSizeInBits(to)); + AllocaInst *cast = ctx.builder.CreateAlloca(ty); + ctx.builder.CreateStore(unboxed, cast); + unboxed = ctx.builder.CreateLoad(to, ctx.builder.CreateBitCast(cast, to->getPointerTo())); + } else if (frompointer) { Type *INTT_to = INTT(to); unboxed = ctx.builder.CreatePtrToInt(unboxed, INTT_to); @@ -312,7 +324,7 @@ static Value *emit_unboxed_coercion(jl_codectx_t &ctx, Type *to, Value *unboxed) unboxed = ctx.builder.CreateBitCast(unboxed, INTT_to); unboxed = emit_inttoptr(ctx, unboxed, to); } - else if (ty != to) { + else { unboxed = ctx.builder.CreateBitCast(unboxed, to); } return unboxed; @@ -610,7 +622,7 @@ static jl_cgval_t emit_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) assert(!isboxed); if (!type_is_ghost(ptrty)) { Value *thePtr = emit_unbox(ctx, ptrty->getPointerTo(), e, e.typ); - return typed_load(ctx, thePtr, im1, ety, tbaa_data, nullptr, true, align_nb); + return typed_load(ctx, thePtr, im1, ety, tbaa_data, nullptr, isboxed, AtomicOrdering::NotAtomic, true, align_nb); } else { return ghostValue(ety); @@ -678,7 +690,7 @@ static jl_cgval_t emit_pointerset(jl_codectx_t &ctx, jl_cgval_t *argv) assert(!isboxed); if (!type_is_ghost(ptrty)) { thePtr = emit_unbox(ctx, ptrty->getPointerTo(), e, e.typ); - typed_store(ctx, thePtr, im1, x, ety, tbaa_data, nullptr, nullptr, align_nb); + typed_store(ctx, thePtr, im1, x, ety, tbaa_data, nullptr, nullptr, isboxed, AtomicOrdering::NotAtomic, align_nb); } } return e; @@ -911,6 +923,13 @@ static jl_cgval_t emit_intrinsic(jl_codectx_t &ctx, intrinsic f, jl_value_t **ar return emit_pointerref(ctx, argv); case pointerset: return emit_pointerset(ctx, argv); + case atomic_fence: + case atomic_pointerref: + case atomic_pointerset: + case atomic_pointerswap: + case atomic_pointermodify: + case atomic_pointerreplace: + return emit_runtime_call(ctx, f, argv, nargs); case bitcast: return generic_bitcast(ctx, argv); case trunc_int: diff --git a/src/intrinsics.h b/src/intrinsics.h index a639e06a6316e..52988a313c990 100644 --- a/src/intrinsics.h +++ b/src/intrinsics.h @@ -91,10 +91,17 @@ /* pointer access */ \ ADD_I(pointerref, 3) \ ADD_I(pointerset, 4) \ - /* c interface */ \ + /* pointer atomics */ \ + ADD_I(atomic_fence, 1) \ + ADD_I(atomic_pointerref, 2) \ + ADD_I(atomic_pointerset, 3) \ + ADD_I(atomic_pointerswap, 3) \ + ADD_I(atomic_pointermodify, 4) \ + ADD_I(atomic_pointerreplace, 5) \ + /* c interface */ \ ADD_I(cglobal, 2) \ ALIAS(llvmcall, llvmcall) \ - /* object access */ \ + /* object access */ \ ADD_I(arraylen, 1) \ /* hidden intrinsics */ \ ADD_HIDDEN(cglobal_auto, 1) diff --git a/src/ircode.c b/src/ircode.c index e86f022df04ec..212febe121a75 100644 --- a/src/ircode.c +++ b/src/ircode.c @@ -608,20 +608,20 @@ static jl_value_t *jl_decode_value(jl_ircode_state *s) JL_GC_DISABLED return jl_decode_value_phic(s, tag); case TAG_GOTONODE: JL_FALLTHROUGH; case TAG_QUOTENODE: v = jl_new_struct_uninit(tag == TAG_GOTONODE ? jl_gotonode_type : jl_quotenode_type); - set_nth_field(tag == TAG_GOTONODE ? jl_gotonode_type : jl_quotenode_type, (void*)v, 0, jl_decode_value(s)); + set_nth_field(tag == TAG_GOTONODE ? jl_gotonode_type : jl_quotenode_type, v, 0, jl_decode_value(s), 0); return v; case TAG_GOTOIFNOT: v = jl_new_struct_uninit(jl_gotoifnot_type); - set_nth_field(jl_gotoifnot_type, (void*)v, 0, jl_decode_value(s)); - set_nth_field(jl_gotoifnot_type, (void*)v, 1, jl_decode_value(s)); + set_nth_field(jl_gotoifnot_type, v, 0, jl_decode_value(s), 0); + set_nth_field(jl_gotoifnot_type, v, 1, jl_decode_value(s), 0); return v; case TAG_ARGUMENT: v = jl_new_struct_uninit(jl_argument_type); - set_nth_field(jl_argument_type, (void*)v, 0, jl_decode_value(s)); + set_nth_field(jl_argument_type, v, 0, jl_decode_value(s), 0); return v; case TAG_RETURNNODE: v = jl_new_struct_uninit(jl_returnnode_type); - set_nth_field(jl_returnnode_type, (void*)v, 0, jl_decode_value(s)); + set_nth_field(jl_returnnode_type, v, 0, jl_decode_value(s), 0); return v; case TAG_SHORTER_INT64: v = jl_box_int64((int16_t)read_uint16(s->s)); @@ -670,7 +670,7 @@ static jl_value_t *jl_decode_value(jl_ircode_state *s) JL_GC_DISABLED v = jl_new_struct_uninit(jl_lineinfonode_type); for (i = 0; i < jl_datatype_nfields(jl_lineinfonode_type); i++) { //size_t offs = jl_field_offset(jl_lineinfonode_type, i); - set_nth_field(jl_lineinfonode_type, (void*)v, i, jl_decode_value(s)); + set_nth_field(jl_lineinfonode_type, v, i, jl_decode_value(s), 0); } return v; default: diff --git a/src/jl_exported_data.inc b/src/jl_exported_data.inc index 1f10f6ef43991..bad61d4ade35a 100644 --- a/src/jl_exported_data.inc +++ b/src/jl_exported_data.inc @@ -16,6 +16,7 @@ XX(jl_array_type) \ XX(jl_array_typename) \ XX(jl_array_uint8_type) \ + XX(jl_atomicerror_type) \ XX(jl_base_module) \ XX(jl_bool_type) \ XX(jl_bottom_type) \ diff --git a/src/jltypes.c b/src/jltypes.c index de1b7c9408294..3fdb2b7337513 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -1998,14 +1998,17 @@ void jl_init_types(void) JL_GC_DISABLED jl_typename_type->name->mt = jl_nonfunction_mt; jl_typename_type->super = jl_any_type; jl_typename_type->parameters = jl_emptysvec; - jl_typename_type->name->names = jl_perm_symsvec(13, "name", "module", - "names", "wrapper", - "cache", "linearcache", + jl_typename_type->name->names = jl_perm_symsvec(14, "name", "module", + "names", "atomicfields", + "wrapper", "cache", "linearcache", "hash", "abstract", "mutable", "references_self", "mayinlinealloc", "mt", "partial"); - jl_typename_type->types = jl_svec(13, jl_symbol_type, jl_any_type, jl_simplevector_type, + jl_typename_type->types = jl_svec(14, jl_symbol_type, jl_any_type/*jl_module_type*/, + jl_simplevector_type, jl_any_type/*jl_voidpointer_type*/, jl_type_type, jl_simplevector_type, jl_simplevector_type, - jl_any_type, jl_any_type, jl_any_type, jl_any_type, jl_any_type, + jl_any_type/*jl_long_type*/, jl_any_type/*jl_bool_type*/, + jl_any_type/*jl_bool_type*/, jl_any_type/*jl_bool_type*/, + jl_any_type/*jl_bool_type*/, jl_methtable_type, jl_any_type); jl_typename_type->ninitialized = 2; jl_precompute_memoized_dt(jl_typename_type, 1); @@ -2050,40 +2053,40 @@ void jl_init_types(void) JL_GC_DISABLED // now they can be used to create the remaining base kinds and types jl_nothing_type = jl_new_datatype(jl_symbol("Nothing"), core, jl_any_type, jl_emptysvec, - jl_emptysvec, jl_emptysvec, 0, 0, 0); + jl_emptysvec, jl_emptysvec, jl_emptysvec, 0, 0, 0); jl_void_type = jl_nothing_type; // deprecated alias jl_astaggedvalue(jl_nothing)->header = ((uintptr_t)jl_nothing_type) | GC_OLD_MARKED; jl_nothing_type->instance = jl_nothing; jl_datatype_t *type_type = (jl_datatype_t*)jl_type_type; jl_typeofbottom_type = jl_new_datatype(jl_symbol("TypeofBottom"), core, type_type, jl_emptysvec, - jl_emptysvec, jl_emptysvec, 0, 0, 0); + jl_emptysvec, jl_emptysvec, jl_emptysvec, 0, 0, 0); jl_bottom_type = jl_new_struct(jl_typeofbottom_type); jl_typeofbottom_type->instance = jl_bottom_type; jl_uniontype_type = jl_new_datatype(jl_symbol("Union"), core, type_type, jl_emptysvec, jl_perm_symsvec(2, "a", "b"), jl_svec(2, jl_any_type, jl_any_type), - 0, 0, 2); + jl_emptysvec, 0, 0, 2); jl_tvar_type = jl_new_datatype(jl_symbol("TypeVar"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(3, "name", "lb", "ub"), jl_svec(3, jl_symbol_type, jl_any_type, jl_any_type), - 0, 1, 3); + jl_emptysvec, 0, 1, 3); jl_unionall_type = jl_new_datatype(jl_symbol("UnionAll"), core, type_type, jl_emptysvec, jl_perm_symsvec(2, "var", "body"), jl_svec(2, jl_tvar_type, jl_any_type), - 0, 0, 2); + jl_emptysvec, 0, 0, 2); jl_vararg_type = jl_new_datatype(jl_symbol("TypeofVararg"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(2, "T", "N"), jl_svec(2, jl_any_type, jl_any_type), - 0, 0, 0); + jl_emptysvec, 0, 0, 0); jl_svec_t *anytuple_params = jl_svec(1, jl_wrap_vararg((jl_value_t*)jl_any_type, (jl_value_t*)NULL)); jl_anytuple_type = jl_new_datatype(jl_symbol("Tuple"), core, jl_any_type, anytuple_params, - jl_emptysvec, anytuple_params, 0, 0, 0); + jl_emptysvec, anytuple_params, jl_emptysvec, 0, 0, 0); jl_tuple_typename = jl_anytuple_type->name; // fix some miscomputed values, since we didn't know this was going to be a Tuple in jl_precompute_memoized_dt jl_tuple_typename->wrapper = (jl_value_t*)jl_anytuple_type; // remove UnionAll wrappers @@ -2119,22 +2122,26 @@ void jl_init_types(void) JL_GC_DISABLED jl_ssavalue_type = jl_new_datatype(jl_symbol("SSAValue"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(1, "id"), - jl_svec1(jl_long_type), 0, 0, 1); + jl_svec1(jl_long_type), + jl_emptysvec, 0, 0, 1); jl_abstractslot_type = jl_new_abstracttype((jl_value_t*)jl_symbol("Slot"), core, jl_any_type, jl_emptysvec); jl_slotnumber_type = jl_new_datatype(jl_symbol("SlotNumber"), core, jl_abstractslot_type, jl_emptysvec, jl_perm_symsvec(1, "id"), - jl_svec1(jl_long_type), 0, 0, 1); + jl_svec1(jl_long_type), + jl_emptysvec, 0, 0, 1); jl_typedslot_type = jl_new_datatype(jl_symbol("TypedSlot"), core, jl_abstractslot_type, jl_emptysvec, jl_perm_symsvec(2, "id", "typ"), - jl_svec(2, jl_long_type, jl_any_type), 0, 0, 2); + jl_svec(2, jl_long_type, jl_any_type), + jl_emptysvec, 0, 0, 2); jl_argument_type = jl_new_datatype(jl_symbol("Argument"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(1, "n"), - jl_svec1(jl_long_type), 0, 0, 1); + jl_svec1(jl_long_type), + jl_emptysvec, 0, 0, 1); jl_init_int32_int64_cache(); @@ -2146,7 +2153,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_abstractstring_type = jl_new_abstracttype((jl_value_t*)jl_symbol("AbstractString"), core, jl_any_type, jl_emptysvec); jl_string_type = jl_new_datatype(jl_symbol("String"), core, jl_abstractstring_type, jl_emptysvec, - jl_emptysvec, jl_emptysvec, 0, 1, 0); + jl_emptysvec, jl_emptysvec, jl_emptysvec, 0, 1, 0); jl_string_type->instance = NULL; jl_compute_field_offsets(jl_string_type); jl_an_empty_string = jl_pchar_to_string("\0", 1); @@ -2168,6 +2175,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_any_type, jl_any_type, jl_any_type), + jl_emptysvec, 0, 1, 6); jl_typemap_entry_type = @@ -2194,6 +2202,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_bool_type, jl_bool_type, jl_bool_type), + jl_emptysvec, 0, 1, 4); jl_function_type = jl_new_abstracttype((jl_value_t*)jl_symbol("Function"), core, jl_any_type, jl_emptysvec); @@ -2215,10 +2224,8 @@ void jl_init_types(void) JL_GC_DISABLED tv = jl_svec2(tvar("T"), tvar("N")); jl_array_type = (jl_unionall_t*) jl_new_datatype(jl_symbol("Array"), core, - (jl_datatype_t*) - jl_apply_type((jl_value_t*)jl_densearray_type, jl_svec_data(tv), 2), - tv, - jl_emptysvec, jl_emptysvec, 0, 1, 0)->name->wrapper; + (jl_datatype_t*)jl_apply_type((jl_value_t*)jl_densearray_type, jl_svec_data(tv), 2), + tv, jl_emptysvec, jl_emptysvec, jl_emptysvec, 0, 1, 0)->name->wrapper; jl_array_typename = ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_array_type))->name; jl_compute_field_offsets((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_array_type)); @@ -2235,11 +2242,11 @@ void jl_init_types(void) JL_GC_DISABLED jl_any_type, jl_emptysvec, jl_perm_symsvec(2, "head", "args"), jl_svec(2, jl_symbol_type, jl_array_any_type), - 0, 1, 2); + jl_emptysvec, 0, 1, 2); jl_module_type = jl_new_datatype(jl_symbol("Module"), core, jl_any_type, jl_emptysvec, - jl_emptysvec, jl_emptysvec, 0, 1, 0); + jl_emptysvec, jl_emptysvec, jl_emptysvec, 0, 1, 0); jl_module_type->instance = NULL; jl_compute_field_offsets(jl_module_type); @@ -2247,63 +2254,74 @@ void jl_init_types(void) JL_GC_DISABLED jl_linenumbernode_type = jl_new_datatype(jl_symbol("LineNumberNode"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(2, "line", "file"), - jl_svec(2, jl_long_type, jl_type_union(symornothing, 2)), 0, 0, 2); + jl_svec(2, jl_long_type, jl_type_union(symornothing, 2)), + jl_emptysvec, 0, 0, 2); jl_lineinfonode_type = jl_new_datatype(jl_symbol("LineInfoNode"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(5, "module", "method", "file", "line", "inlined_at"), jl_svec(5, jl_module_type, jl_any_type, jl_symbol_type, jl_long_type, jl_long_type), - 0, 0, 5); + jl_emptysvec, 0, 0, 5); jl_gotonode_type = jl_new_datatype(jl_symbol("GotoNode"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(1, "label"), - jl_svec(1, jl_long_type), 0, 0, 1); + jl_svec(1, jl_long_type), + jl_emptysvec, 0, 0, 1); jl_gotoifnot_type = jl_new_datatype(jl_symbol("GotoIfNot"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(2, "cond", "dest"), - jl_svec(2, jl_any_type, jl_long_type), 0, 0, 2); + jl_svec(2, jl_any_type, jl_long_type), + jl_emptysvec, 0, 0, 2); jl_returnnode_type = jl_new_datatype(jl_symbol("ReturnNode"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(1, "val"), - jl_svec(1, jl_any_type), 0, 0, 0); + jl_svec(1, jl_any_type), + jl_emptysvec, 0, 0, 0); jl_pinode_type = jl_new_datatype(jl_symbol("PiNode"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(2, "val", "typ"), - jl_svec(2, jl_any_type, jl_any_type), 0, 0, 2); + jl_svec(2, jl_any_type, jl_any_type), + jl_emptysvec, 0, 0, 2); jl_phinode_type = jl_new_datatype(jl_symbol("PhiNode"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(2, "edges", "values"), - jl_svec(2, jl_array_int32_type, jl_array_any_type), 0, 0, 2); + jl_svec(2, jl_array_int32_type, jl_array_any_type), + jl_emptysvec, 0, 0, 2); jl_phicnode_type = jl_new_datatype(jl_symbol("PhiCNode"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(1, "values"), - jl_svec(1, jl_array_any_type), 0, 0, 1); + jl_svec(1, jl_array_any_type), + jl_emptysvec, 0, 0, 1); jl_upsilonnode_type = jl_new_datatype(jl_symbol("UpsilonNode"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(1, "val"), - jl_svec(1, jl_any_type), 0, 0, 0); + jl_svec(1, jl_any_type), + jl_emptysvec, 0, 0, 0); jl_quotenode_type = jl_new_datatype(jl_symbol("QuoteNode"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(1, "value"), - jl_svec(1, jl_any_type), 0, 0, 1); + jl_svec(1, jl_any_type), + jl_emptysvec, 0, 0, 1); jl_newvarnode_type = jl_new_datatype(jl_symbol("NewvarNode"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(1, "slot"), - jl_svec(1, jl_slotnumber_type), 0, 0, 1); + jl_svec(1, jl_slotnumber_type), + jl_emptysvec, 0, 0, 1); jl_globalref_type = jl_new_datatype(jl_symbol("GlobalRef"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(2, "mod", "name"), - jl_svec(2, jl_module_type, jl_symbol_type), 0, 0, 2); + jl_svec(2, jl_module_type, jl_symbol_type), + jl_emptysvec, 0, 0, 2); jl_code_info_type = jl_new_datatype(jl_symbol("CodeInfo"), core, @@ -2348,6 +2366,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_bool_type, jl_bool_type, jl_bool_type), + jl_emptysvec, 0, 1, 19); jl_method_type = @@ -2405,6 +2424,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_bool_type, jl_bool_type, jl_bool_type), + jl_emptysvec, 0, 1, 10); jl_method_instance_type = @@ -2428,6 +2448,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_any_type, jl_any_type, jl_bool_type), + jl_emptysvec, 0, 1, 3); jl_code_instance_type = @@ -2457,24 +2478,29 @@ void jl_init_types(void) JL_GC_DISABLED jl_bool_type, jl_bool_type, jl_any_type, jl_any_type), // fptrs + jl_emptysvec, 0, 1, 1); jl_svecset(jl_code_instance_type->types, 1, jl_code_instance_type); jl_const_type = jl_new_datatype(jl_symbol("Const"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(1, "val"), - jl_svec1(jl_any_type), 0, 0, 1); + jl_svec1(jl_any_type), + jl_emptysvec, 0, 0, 1); jl_partial_struct_type = jl_new_datatype(jl_symbol("PartialStruct"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(2, "typ", "fields"), - jl_svec2(jl_any_type, jl_array_any_type), 0, 0, 2); + jl_svec2(jl_any_type, jl_array_any_type), + jl_emptysvec, 0, 0, 2); jl_interconditional_type = jl_new_datatype(jl_symbol("InterConditional"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(3, "slot", "vtype", "elsetype"), - jl_svec(3, jl_long_type, jl_any_type, jl_any_type), 0, 0, 3); + jl_svec(3, jl_long_type, jl_any_type, jl_any_type), + jl_emptysvec, 0, 0, 3); jl_method_match_type = jl_new_datatype(jl_symbol("MethodMatch"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(4, "spec_types", "sparams", "method", "fully_covers"), - jl_svec(4, jl_type_type, jl_simplevector_type, jl_method_type, jl_bool_type), 0, 0, 4); + jl_svec(4, jl_type_type, jl_simplevector_type, jl_method_type, jl_bool_type), + jl_emptysvec, 0, 0, 4); // all Kinds share the Type method table (not the nonfunction one) jl_unionall_type->name->mt = jl_uniontype_type->name->mt = jl_datatype_type->name->mt = @@ -2514,7 +2540,7 @@ void jl_init_types(void) JL_GC_DISABLED (jl_value_t*)jl_anytuple_type); tv = jl_svec2(tvar("names"), ntval_var); jl_datatype_t *ntt = jl_new_datatype(jl_symbol("NamedTuple"), core, jl_any_type, tv, - jl_emptysvec, jl_emptysvec, 0, 0, 0); + jl_emptysvec, jl_emptysvec, jl_emptysvec, 0, 0, 0); jl_namedtuple_type = (jl_unionall_t*)ntt->name->wrapper; ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_namedtuple_type))->layout = NULL; jl_namedtuple_typename = ntt->name; @@ -2554,6 +2580,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_uint64_type, jl_uint64_type, jl_uint64_type), + jl_emptysvec, 0, 1, 6); jl_value_t *listt = jl_new_struct(jl_uniontype_type, jl_task_type, jl_nothing_type); jl_svecset(jl_task_type->types, 0, listt); @@ -2564,7 +2591,8 @@ void jl_init_types(void) JL_GC_DISABLED tv = jl_svec2(tvar("A"), tvar("R")); jl_opaque_closure_type = (jl_unionall_t*)jl_new_datatype(jl_symbol("OpaqueClosure"), core, jl_function_type, tv, jl_perm_symsvec(6, "captures", "isva", "world", "source", "invoke", "specptr"), - jl_svec(6, jl_any_type, jl_bool_type, jl_long_type, jl_any_type, pointer_void, pointer_void), 0, 0, 6)->name->wrapper; + jl_svec(6, jl_any_type, jl_bool_type, jl_long_type, jl_any_type, pointer_void, pointer_void), + jl_emptysvec, 0, 0, 6)->name->wrapper; jl_opaque_closure_typename = ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_opaque_closure_type))->name; jl_compute_field_offsets((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_opaque_closure_type)); @@ -2572,7 +2600,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_partial_opaque_type = jl_new_datatype(jl_symbol("PartialOpaque"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(5, "typ", "env", "isva", "parent", "source"), jl_svec(5, jl_type_type, jl_any_type, jl_bool_type, jl_method_instance_type, jl_method_type), - 0, 0, 5); + jl_emptysvec, 0, 0, 5); // complete builtin type metadata jl_voidpointer_type = (jl_datatype_t*)pointer_void; @@ -2589,12 +2617,13 @@ void jl_init_types(void) JL_GC_DISABLED jl_svecset(jl_datatype_type->types, 15, jl_bool_type); jl_svecset(jl_datatype_type->types, 16, jl_bool_type); jl_svecset(jl_typename_type->types, 1, jl_module_type); - jl_svecset(jl_typename_type->types, 3, jl_type_type); - jl_svecset(jl_typename_type->types, 6, jl_long_type); - jl_svecset(jl_typename_type->types, 7, jl_bool_type); + jl_svecset(jl_typename_type->types, 3, jl_voidpointer_type); + jl_svecset(jl_typename_type->types, 4, jl_type_type); + jl_svecset(jl_typename_type->types, 7, jl_long_type); jl_svecset(jl_typename_type->types, 8, jl_bool_type); jl_svecset(jl_typename_type->types, 9, jl_bool_type); jl_svecset(jl_typename_type->types, 10, jl_bool_type); + jl_svecset(jl_typename_type->types, 11, jl_bool_type); jl_svecset(jl_methtable_type->types, 4, jl_long_type); jl_svecset(jl_methtable_type->types, 6, jl_module_type); jl_svecset(jl_methtable_type->types, 7, jl_array_any_type); diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 49404891267da..3dd57893579b6 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -666,8 +666,7 @@ (define (throw-unassigned argname) `(call (core throw) (call (core UndefKeywordError) (inert ,argname)))) (define (to-kw x) - (cond ((symbol? x) `(kw ,x ,(throw-unassigned x))) - ((decl? x) `(kw ,x ,(throw-unassigned (cadr x)))) + (cond ((symdecl? x) `(kw ,x ,(throw-unassigned (decl-var x)))) ((nospecialize-meta? x) `(meta ,(cadr x) ,(to-kw (caddr x)))) (else x))) (if (has-parameters? argl) @@ -884,9 +883,20 @@ (define (struct-def-expr- name params bounds super fields0 mut) (receive - (fields defs) (separate (lambda (x) (or (symbol? x) (decl? x))) + (fields defs) (separate (lambda (x) (or (symbol? x) (eventually-decl? x))) fields0) - (let* ((defs (filter (lambda (x) (not (effect-free? x))) defs)) + (let* ((attrs ()) + (fields (let ((n 0)) + (map (lambda (x) + (set! n (+ n 1)) + (if (and (pair? x) (not (decl? x))) + (begin + (set! attrs (cons (quotify (car x)) (cons n attrs))) + (cadr x)) + x)) + fields))) + (attrs (reverse attrs)) + (defs (filter (lambda (x) (not (effect-free? x))) defs)) (locs (if (and (pair? fields0) (linenum? (car fields0))) (list (car fields0)) '())) @@ -914,6 +924,7 @@ (toplevel-only struct (outerref ,name)) (= ,name (call (core _structtype) (thismodule) (inert ,name) (call (core svec) ,@params) (call (core svec) ,@(map quotify field-names)) + (call (core svec) ,@attrs) ,mut ,min-initialized)) (call (core _setsuper!) ,name ,super) (if (isdefined (outerref ,name)) @@ -1196,7 +1207,7 @@ (if (null? binds) blk (cond - ((or (symbol? (car binds)) (decl? (car binds))) + ((symdecl? (car binds)) ;; just symbol -> add local (loop (cdr binds) `(scope-block @@ -1220,8 +1231,7 @@ `(local-def ,name)) ,(car binds) ,blk))))) - ((or (symbol? (cadar binds)) - (decl? (cadar binds))) + ((symdecl? (cadar binds)) (let ((vname (decl-var (cadar binds)))) (loop (cdr binds) (let ((tmp (make-ssavalue))) @@ -1276,9 +1286,9 @@ (if (null? f) '() (let ((x (car f))) - (cond ((or (symbol? x) (decl? x) (linenum? x)) + (cond ((or (symdecl? x) (linenum? x)) (loop (cdr f))) - ((and (assignment? x) (or (symbol? (cadr x)) (decl? (cadr x)))) + ((and (assignment? x) (symdecl? (cadr x))) (error (string "\"" (deparse x) "\" inside type definition is reserved"))) (else '()))))) (expand-forms @@ -1364,6 +1374,9 @@ (expand-forms (expand-decls 'const (cdr e) #f))) (else e))))) +(define (expand-atomic-decl e) + (error "unimplemented or unsupported atomic declaration")) + (define (expand-local-or-global-decl e) (if (and (symbol? (cadr e)) (length= e 2)) e @@ -2276,6 +2289,7 @@ (lambda (e) (expand-forms (expand-wheres (cadr e) (cddr e)))) 'const expand-const-decl + 'atomic expand-atomic-decl 'local expand-local-or-global-decl 'global expand-local-or-global-decl 'local-def expand-local-or-global-decl @@ -2746,15 +2760,13 @@ ,result))))) (define (lhs-vars e) - (cond ((symbol? e) (list e)) - ((decl? e) (list (decl-var e))) + (cond ((symdecl? e) (list (decl-var e))) ((and (pair? e) (eq? (car e) 'tuple)) (apply append (map lhs-vars (cdr e)))) (else '()))) (define (lhs-decls e) - (cond ((symbol? e) (list e)) - ((decl? e) (list e)) + (cond ((symdecl? e) (list e)) ((and (pair? e) (eq? (car e) 'tuple)) (apply append (map lhs-decls (cdr e)))) (else '()))) @@ -3231,6 +3243,7 @@ f(x) = yt(x) ,@(map (lambda (p n) `(= ,p (call (core TypeVar) ',n (core Any)))) P names) (= ,s (call (core _structtype) (thismodule) (inert ,name) (call (core svec) ,@P) (call (core svec) ,@(map quotify fields)) + (call (core svec)) (false) ,(length fields))) (= (outerref ,name) ,s) (call (core _setsuper!) ,name ,super) @@ -3244,6 +3257,7 @@ f(x) = yt(x) (block (global ,name) (const ,name) (= ,s (call (core _structtype) (thismodule) (inert ,name) (call (core svec)) (call (core svec) ,@(map quotify fields)) + (call (core svec)) (false) ,(length fields))) (= (outerref ,name) ,s) (call (core _setsuper!) ,name ,super) @@ -3470,8 +3484,8 @@ f(x) = yt(x) meta inbounds boundscheck loopinfo decl aliasscope popaliasscope thunk with-static-parameters toplevel-only global globalref outerref const-if-global thismodule - const null true false ssavalue isdefined toplevel module lambda error - gc_preserve_begin gc_preserve_end import using export))) + const atomic null true false ssavalue isdefined toplevel module lambda + error gc_preserve_begin gc_preserve_end import using export))) (define (local-in? s lam) (or (assq s (car (lam:vinfo lam))) @@ -3718,6 +3732,7 @@ f(x) = yt(x) '(null) `(newvar ,(cadr e)))))) ((const) e) + ((atomic) e) ((const-if-global) (if (local-in? (cadr e) lam) '(null) @@ -4501,6 +4516,7 @@ f(x) = yt(x) (if (not global-const-error) (set! global-const-error current-loc)) (emit e)))) + ((atomic) (error "misplaced atomic declaration")) ((isdefined) (if tail (emit-return e) e)) ((boundscheck) (if tail (emit-return e) e)) diff --git a/src/julia.h b/src/julia.h index 69b771bfca073..f65b07188fc3d 100644 --- a/src/julia.h +++ b/src/julia.h @@ -38,6 +38,13 @@ # define MAX_ALIGN 8 #endif +// Define the largest size (bytes) of a properly aligned object that the +// processor family and compiler typically supports without a lock +// (assumed to be at least a pointer size). Since C is bad at handling 16-byte +// types, we currently use 8 here as the default. +#define MAX_ATOMIC_SIZE 8 +#define MAX_POINTERATOMIC_SIZE 8 + #ifdef _P64 #define NWORDS(sz) (((sz)+7)>>3) #else @@ -423,6 +430,8 @@ typedef struct { jl_sym_t *name; struct _jl_module_t *module; jl_svec_t *names; // field names + const uint32_t *atomicfields; // if any fields are atomic, we record them here + //const uint32_t *constfields; // if any fields are const, we record them here // `wrapper` is either the only instantiation of the type (if no parameters) // or a UnionAll accepting parameters to make an instantiation. jl_value_t *wrapper; @@ -677,6 +686,7 @@ extern JL_DLLIMPORT jl_datatype_t *jl_initerror_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_typeerror_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_methoderror_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_undefvarerror_type JL_GLOBALLY_ROOTED; +extern JL_DLLEXPORT jl_datatype_t *jl_atomicerror_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_lineinfonode_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_stackovf_exception JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_memory_exception JL_GLOBALLY_ROOTED; @@ -855,10 +865,10 @@ JL_DLLEXPORT void jl_gc_use(jl_value_t *a); JL_DLLEXPORT void jl_clear_malloc_data(void); // GC write barriers -JL_DLLEXPORT void jl_gc_queue_root(jl_value_t *root) JL_NOTSAFEPOINT; -JL_DLLEXPORT void jl_gc_queue_multiroot(jl_value_t *root, jl_value_t *stored) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_gc_queue_root(const jl_value_t *root) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_gc_queue_multiroot(const jl_value_t *root, const jl_value_t *stored) JL_NOTSAFEPOINT; -STATIC_INLINE void jl_gc_wb(void *parent, void *ptr) JL_NOTSAFEPOINT +STATIC_INLINE void jl_gc_wb(const void *parent, const void *ptr) JL_NOTSAFEPOINT { // parent and ptr isa jl_value_t* if (__unlikely(jl_astaggedvalue(parent)->bits.gc == 3 && // parent is old and not in remset @@ -866,7 +876,7 @@ STATIC_INLINE void jl_gc_wb(void *parent, void *ptr) JL_NOTSAFEPOINT jl_gc_queue_root((jl_value_t*)parent); } -STATIC_INLINE void jl_gc_wb_back(void *ptr) JL_NOTSAFEPOINT // ptr isa jl_value_t* +STATIC_INLINE void jl_gc_wb_back(const void *ptr) JL_NOTSAFEPOINT // ptr isa jl_value_t* { // if ptr is old if (__unlikely(jl_astaggedvalue(ptr)->bits.gc == 3)) { @@ -874,7 +884,7 @@ STATIC_INLINE void jl_gc_wb_back(void *ptr) JL_NOTSAFEPOINT // ptr isa jl_value_ } } -STATIC_INLINE void jl_gc_multi_wb(void *parent, jl_value_t *ptr) JL_NOTSAFEPOINT +STATIC_INLINE void jl_gc_multi_wb(const void *parent, const jl_value_t *ptr) JL_NOTSAFEPOINT { // ptr is an immutable object if (__likely(jl_astaggedvalue(parent)->bits.gc != 3)) @@ -1124,6 +1134,17 @@ static inline uint32_t jl_ptr_offset(jl_datatype_t *st, int i) JL_NOTSAFEPOINT } } +static inline int jl_field_isatomic(jl_datatype_t *st, int i) JL_NOTSAFEPOINT +{ + // if (!st->mutable) return 0; // TODO: is this fast-path helpful? + const uint32_t *atomicfields = st->name->atomicfields; + if (atomicfields != NULL) { + if (atomicfields[i / 32] & (1 << (i % 32))) + return 1; + } + return 0; +} + static inline int jl_is_layout_opaque(const jl_datatype_layout_t *l) JL_NOTSAFEPOINT { return l->nfields == 0 && l->npointers > 0; @@ -1303,12 +1324,12 @@ STATIC_INLINE int jl_is_array_zeroinit(jl_array_t *a) JL_NOTSAFEPOINT } // object identity -JL_DLLEXPORT int jl_egal(jl_value_t *a JL_MAYBE_UNROOTED, jl_value_t *b JL_MAYBE_UNROOTED) JL_NOTSAFEPOINT; -JL_DLLEXPORT int jl_egal__bits(jl_value_t *a JL_MAYBE_UNROOTED, jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT; -JL_DLLEXPORT int jl_egal__special(jl_value_t *a JL_MAYBE_UNROOTED, jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT; +JL_DLLEXPORT int jl_egal(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED) JL_NOTSAFEPOINT; +JL_DLLEXPORT int jl_egal__bits(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT; +JL_DLLEXPORT int jl_egal__special(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT; JL_DLLEXPORT uintptr_t jl_object_id(jl_value_t *v) JL_NOTSAFEPOINT; -STATIC_INLINE int jl_egal_(jl_value_t *a JL_MAYBE_UNROOTED, jl_value_t *b JL_MAYBE_UNROOTED) JL_NOTSAFEPOINT +STATIC_INLINE int jl_egal_(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED) JL_NOTSAFEPOINT { if (a == b) return 1; @@ -1367,7 +1388,9 @@ JL_DLLEXPORT jl_datatype_t *jl_new_datatype(jl_sym_t *name, jl_module_t *module, jl_datatype_t *super, jl_svec_t *parameters, - jl_svec_t *fnames, jl_svec_t *ftypes, + jl_svec_t *fnames, + jl_svec_t *ftypes, + jl_svec_t *fattrs, int abstract, int mutabl, int ninitialized); JL_DLLEXPORT jl_datatype_t *jl_new_primitivetype(jl_value_t *name, @@ -1376,7 +1399,12 @@ JL_DLLEXPORT jl_datatype_t *jl_new_primitivetype(jl_value_t *name, jl_svec_t *parameters, size_t nbits); // constructors -JL_DLLEXPORT jl_value_t *jl_new_bits(jl_value_t *bt, void *data); +JL_DLLEXPORT jl_value_t *jl_new_bits(jl_value_t *bt, const void *src); +JL_DLLEXPORT jl_value_t *jl_atomic_new_bits(jl_value_t *dt, const char *src); +JL_DLLEXPORT void jl_atomic_store_bits(char *dst, const jl_value_t *src, int nb); +JL_DLLEXPORT jl_value_t *jl_atomic_swap_bits(jl_value_t *dt, char *dst, const jl_value_t *src, int nb); +JL_DLLEXPORT int jl_atomic_bool_cmpswap_bits(char *dst, const jl_value_t *expected, const jl_value_t *src, int nb); +JL_DLLEXPORT jl_value_t *jl_atomic_cmpswap_bits(jl_datatype_t *dt, char *dst, const jl_value_t *expected, const jl_value_t *src, int nb); JL_DLLEXPORT jl_value_t *jl_new_struct(jl_datatype_t *type, ...); JL_DLLEXPORT jl_value_t *jl_new_structv(jl_datatype_t *type, jl_value_t **args, uint32_t na); JL_DLLEXPORT jl_value_t *jl_new_structt(jl_datatype_t *type, jl_value_t *tup); @@ -1463,8 +1491,7 @@ JL_DLLEXPORT jl_value_t *jl_get_nth_field(jl_value_t *v, size_t i); // Like jl_get_nth_field above, but asserts if it needs to allocate JL_DLLEXPORT jl_value_t *jl_get_nth_field_noalloc(jl_value_t *v JL_PROPAGATES_ROOT, size_t i) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_value_t *jl_get_nth_field_checked(jl_value_t *v, size_t i); -JL_DLLEXPORT void jl_set_nth_field(jl_value_t *v, size_t i, - jl_value_t *rhs) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_set_nth_field(jl_value_t *v, size_t i, jl_value_t *rhs) JL_NOTSAFEPOINT; JL_DLLEXPORT int jl_field_isdefined(jl_value_t *v, size_t i) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_value_t *jl_get_field(jl_value_t *o, const char *fld); JL_DLLEXPORT jl_value_t *jl_value_ptr(jl_value_t *a); @@ -1593,6 +1620,7 @@ JL_DLLEXPORT void JL_NORETURN jl_type_error_rt(const char *fname, jl_value_t *ty JL_MAYBE_UNROOTED, jl_value_t *got JL_MAYBE_UNROOTED); JL_DLLEXPORT void JL_NORETURN jl_undefined_var_error(jl_sym_t *var); +JL_DLLEXPORT void JL_NORETURN jl_atomic_error(char *str); JL_DLLEXPORT void JL_NORETURN jl_bounds_error(jl_value_t *v JL_MAYBE_UNROOTED, jl_value_t *t JL_MAYBE_UNROOTED); JL_DLLEXPORT void JL_NORETURN jl_bounds_error_v(jl_value_t *v JL_MAYBE_UNROOTED, @@ -2120,7 +2148,7 @@ typedef struct { float value; } jl_nullable_float32_t; -#define jl_root_task (jl_get_ptls_states()->root_task) +#define jl_root_task (jl_current_task->ptls->root_task) JL_DLLEXPORT jl_task_t *jl_get_current_task(void) JL_NOTSAFEPOINT; diff --git a/src/julia_internal.h b/src/julia_internal.h index eadad66392d63..fe84d90fa7b27 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -175,6 +175,26 @@ STATIC_INLINE uint32_t jl_int32hash_fast(uint32_t a) } +// this is a version of memcpy that preserves atomic memory ordering +// which makes it safe to use for objects that can contain memory references +// without risk of creating pointers out of thin air +// TODO: replace with LLVM's llvm.memmove.element.unordered.atomic.p0i8.p0i8.i32 +// aka `__llvm_memmove_element_unordered_atomic_8` (for 64 bit) +static inline void memmove_refs(void **dstp, void *const *srcp, size_t n) JL_NOTSAFEPOINT +{ + size_t i; + if (dstp < srcp || dstp > srcp + n) { + for (i = 0; i < n; i++) { + jl_atomic_store_relaxed(dstp + i, jl_atomic_load_relaxed(srcp + i)); + } + } + else { + for (i = 0; i < n; i++) { + jl_atomic_store_relaxed(dstp + n - i - 1, jl_atomic_load_relaxed(srcp + n - i - 1)); + } + } +} + // -- gc.c -- // #define GC_CLEAN 0 // freshly allocated @@ -430,7 +450,7 @@ STATIC_INLINE jl_value_t *undefref_check(jl_datatype_t *dt, jl_value_t *v) JL_NO if (dt->layout->first_ptr >= 0) { jl_value_t *nullp = ((jl_value_t**)v)[dt->layout->first_ptr]; if (__unlikely(nullp == NULL)) - jl_throw(jl_undefref_exception); + return NULL; } return v; } @@ -525,8 +545,10 @@ jl_vararg_t *jl_wrap_vararg(jl_value_t *t, jl_value_t *n); void jl_reinstantiate_inner_types(jl_datatype_t *t); jl_datatype_t *jl_lookup_cache_type_(jl_datatype_t *type); void jl_cache_type_(jl_datatype_t *type); -void jl_assign_bits(void *dest, jl_value_t *bits) JL_NOTSAFEPOINT; -void set_nth_field(jl_datatype_t *st, void *v, size_t i, jl_value_t *rhs) JL_NOTSAFEPOINT; +void set_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *rhs, int isatomic) JL_NOTSAFEPOINT; +jl_value_t *swap_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *rhs, int isatomic); +jl_value_t *modify_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *op, jl_value_t *rhs, int isatomic); +jl_value_t *replace_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *expected, jl_value_t *rhs, int isatomic); jl_expr_t *jl_exprn(jl_sym_t *head, size_t n); jl_function_t *jl_new_generic_function(jl_sym_t *name, jl_module_t *module); jl_function_t *jl_new_generic_function_with_supertype(jl_sym_t *name, jl_module_t *module, jl_datatype_t *st); @@ -1088,6 +1110,12 @@ unsigned jl_intrinsic_nargs(int f) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_value_t *jl_bitcast(jl_value_t *ty, jl_value_t *v); JL_DLLEXPORT jl_value_t *jl_pointerref(jl_value_t *p, jl_value_t *i, jl_value_t *align); JL_DLLEXPORT jl_value_t *jl_pointerset(jl_value_t *p, jl_value_t *x, jl_value_t *align, jl_value_t *i); +JL_DLLEXPORT jl_value_t *jl_atomic_fence(jl_value_t *order); +JL_DLLEXPORT jl_value_t *jl_atomic_pointerref(jl_value_t *p, jl_value_t *order); +JL_DLLEXPORT jl_value_t *jl_atomic_pointerset(jl_value_t *p, jl_value_t *x, jl_value_t *order); +JL_DLLEXPORT jl_value_t *jl_atomic_pointerswap(jl_value_t *p, jl_value_t *x, jl_value_t *order); +JL_DLLEXPORT jl_value_t *jl_atomic_pointermodify(jl_value_t *p, jl_value_t *f, jl_value_t *x, jl_value_t *order); +JL_DLLEXPORT jl_value_t *jl_atomic_pointerreplace(jl_value_t *p, jl_value_t *x, jl_value_t *expected, jl_value_t *success_order, jl_value_t *failure_order); JL_DLLEXPORT jl_value_t *jl_cglobal(jl_value_t *v, jl_value_t *ty); JL_DLLEXPORT jl_value_t *jl_cglobal_auto(jl_value_t *v); @@ -1331,6 +1359,17 @@ extern jl_sym_t *optlevel_sym; extern jl_sym_t *compile_sym; extern jl_sym_t *infer_sym; extern jl_sym_t *atom_sym; extern jl_sym_t *statement_sym; extern jl_sym_t *all_sym; +extern jl_sym_t *atomic_sym; +extern jl_sym_t *not_atomic_sym; +extern jl_sym_t *unordered_sym; +extern jl_sym_t *monotonic_sym; // or relaxed_sym? +extern jl_sym_t *acquire_sym; +extern jl_sym_t *release_sym; +extern jl_sym_t *acquire_release_sym; +extern jl_sym_t *sequentially_consistent_sym; // or strong_sym? +enum jl_memory_order jl_get_atomic_order(jl_sym_t *order, char loading, char storing); +enum jl_memory_order jl_get_atomic_order_checked(jl_sym_t *order, char loading, char storing); + struct _jl_sysimg_fptrs_t; void jl_register_fptrs(uint64_t sysimage_base, const struct _jl_sysimg_fptrs_t *fptrs, diff --git a/src/llvm-late-gc-lowering.cpp b/src/llvm-late-gc-lowering.cpp index c731b7cb0254d..50015045151bc 100644 --- a/src/llvm-late-gc-lowering.cpp +++ b/src/llvm-late-gc-lowering.cpp @@ -1515,6 +1515,7 @@ State LateLowerGCFrame::LocalScan(Function &F) { if (callee == pointer_from_objref_func || callee == gc_preserve_begin_func || callee == gc_preserve_end_func || callee == typeof_func || callee == pgcstack_getter || + callee->getName() == "jl_lock_value" || callee->getName() == "jl_unlock_value" || callee == write_barrier_func || callee->getName() == "memcmp") { continue; } diff --git a/src/locks.h b/src/locks.h index d993f71beefdd..0605cefbd1218 100644 --- a/src/locks.h +++ b/src/locks.h @@ -28,8 +28,7 @@ static inline void jl_mutex_wait(jl_mutex_t *lock, int safepoint) return; } while (1) { - if (owner == 0 && - jl_atomic_compare_exchange(&lock->owner, 0, self) == 0) { + if (owner == 0 && jl_atomic_cmpswap(&lock->owner, &owner, self)) { lock->count = 1; return; } @@ -97,8 +96,7 @@ static inline int jl_mutex_trylock_nogc(jl_mutex_t *lock) lock->count++; return 1; } - if (owner == 0 && - jl_atomic_compare_exchange(&lock->owner, 0, self) == 0) { + if (owner == 0 && jl_atomic_cmpswap(&lock->owner, &owner, self)) { lock->count = 1; return 1; } diff --git a/src/method.c b/src/method.c index ed0593cb77d7d..2573a2eddfb89 100644 --- a/src/method.c +++ b/src/method.c @@ -65,7 +65,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve intptr_t label = jl_gotoifnot_label(expr); JL_GC_PUSH1(&cond); expr = jl_new_struct_uninit(jl_gotoifnot_type); - set_nth_field(jl_gotoifnot_type, expr, 0, cond); + set_nth_field(jl_gotoifnot_type, expr, 0, cond, 0); jl_gotoifnot_label(expr) = label; JL_GC_POP(); } diff --git a/src/module.c b/src/module.c index dc71fb86c036a..eee3bc8137ed0 100644 --- a/src/module.c +++ b/src/module.c @@ -634,8 +634,10 @@ JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var { jl_binding_t *bp = jl_get_binding_wr(m, var, 1); if (bp->value == NULL) { - if (jl_atomic_bool_compare_exchange(&bp->constp, 0, 1)) { - if (jl_atomic_bool_compare_exchange(&bp->value, NULL, val)) { + uint8_t constp = 0; + if (jl_atomic_cmpswap(&bp->constp, &constp, 1)) { + jl_value_t *old = NULL; + if (jl_atomic_cmpswap(&bp->value, &old, val)) { jl_gc_wb_binding(bp, val); return; } @@ -759,8 +761,8 @@ void jl_binding_deprecation_warning(jl_module_t *m, jl_binding_t *b) JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_value_t *rhs) JL_NOTSAFEPOINT { if (b->constp) { - jl_value_t *old = jl_atomic_compare_exchange(&b->value, NULL, rhs); - if (old == NULL) { + jl_value_t *old = NULL; + if (jl_atomic_cmpswap(&b->value, &old, rhs)) { jl_gc_wb_binding(b, rhs); return; } diff --git a/src/partr.c b/src/partr.c index c9d4885cc34b5..bfe1f29fa4d1b 100644 --- a/src/partr.c +++ b/src/partr.c @@ -44,7 +44,7 @@ JL_DLLEXPORT int jl_set_task_tid(jl_task_t *task, int tid) JL_NOTSAFEPOINT if (was == tid) return 1; if (was == -1) - return jl_atomic_bool_compare_exchange(&task->tid, -1, tid); + return jl_atomic_cmpswap(&task->tid, &was, tid); return 0; } @@ -329,7 +329,9 @@ static int sleep_check_after_threshold(uint64_t *start_cycles) static void wake_thread(int16_t tid) { jl_ptls_t other = jl_all_tls_states[tid]; - if (jl_atomic_bool_compare_exchange(&other->sleep_check_state, sleeping, not_sleeping)) { + uint8_t state = sleeping; + jl_atomic_cmpswap(&other->sleep_check_state, &state, not_sleeping); + if (state == sleeping) { uv_mutex_lock(&other->sleep_lock); uv_cond_signal(&other->wake_signal); uv_mutex_unlock(&other->sleep_lock); diff --git a/src/rtutils.c b/src/rtutils.c index 07e4c969ddbfa..e6fcc4726587d 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -132,6 +132,14 @@ JL_DLLEXPORT void JL_NORETURN jl_undefined_var_error(jl_sym_t *var) jl_throw(jl_new_struct(jl_undefvarerror_type, var)); } +JL_DLLEXPORT void JL_NORETURN jl_atomic_error(char *str) // == jl_exceptionf(jl_atomicerror_type, "%s", str) +{ + jl_value_t *msg = jl_pchar_to_string((char*)str, strlen(str)); + JL_GC_PUSH1(&msg); + jl_throw(jl_new_struct(jl_atomicerror_type, msg)); +} + + JL_DLLEXPORT void JL_NORETURN jl_bounds_error(jl_value_t *v, jl_value_t *t) { JL_GC_PUSH2(&v, &t); // root arguments so the caller doesn't need to @@ -354,14 +362,16 @@ JL_DLLEXPORT void jl_set_nth_field(jl_value_t *v, size_t idx0, jl_value_t *rhs) { jl_datatype_t *st = (jl_datatype_t*)jl_typeof(v); if (!st->name->mutabl) - jl_errorf("setfield! immutable struct of type %s cannot be changed", jl_symbol_name(st->name->name)); + jl_errorf("setfield!: immutable struct of type %s cannot be changed", jl_symbol_name(st->name->name)); if (idx0 >= jl_datatype_nfields(st)) jl_bounds_error_int(v, idx0 + 1); //jl_value_t *ft = jl_field_type(st, idx0); //if (!jl_isa(rhs, ft)) { // jl_type_error("setfield!", ft, rhs); //} - set_nth_field(st, (void*)v, idx0, rhs); + //int isatomic = jl_field_isatomic(st, idx0); + //if (isatomic) ... + set_nth_field(st, v, idx0, rhs, 0); } diff --git a/src/runtime_intrinsics.c b/src/runtime_intrinsics.c index e284fa5acfff6..c7c31bb98a86d 100644 --- a/src/runtime_intrinsics.c +++ b/src/runtime_intrinsics.c @@ -65,16 +65,167 @@ JL_DLLEXPORT jl_value_t *jl_pointerset(jl_value_t *p, jl_value_t *x, jl_value_t else { if (!jl_is_datatype(ety)) jl_error("pointerset: invalid pointer"); + if (jl_typeof(x) != ety) + jl_type_error("pointerset", ety, x); size_t elsz = jl_datatype_size(ety); size_t nb = LLT_ALIGN(elsz, jl_datatype_align(ety)); char *pp = (char*)jl_unbox_long(p) + (jl_unbox_long(i)-1)*nb; + memcpy(pp, x, elsz); + } + return p; +} + +JL_DLLEXPORT jl_value_t *jl_atomic_pointerref(jl_value_t *p, jl_value_t *order) +{ + JL_TYPECHK(pointerref, pointer, p); + JL_TYPECHK(pointerref, symbol, order) + (void)jl_get_atomic_order_checked((jl_sym_t*)order, 1, 0); + jl_value_t *ety = jl_tparam0(jl_typeof(p)); + char *pp = (char*)jl_unbox_long(p); + if (ety == (jl_value_t*)jl_any_type) { + return jl_atomic_load((jl_value_t**)pp); + } + else { + if (!jl_is_datatype(ety)) + jl_error("pointerref: invalid pointer"); + size_t nb = jl_datatype_size(ety); + if ((nb & (nb - 1)) != 0 || nb > MAX_POINTERATOMIC_SIZE) + jl_error("pointerref: invalid pointer for atomic operation"); + return jl_atomic_new_bits(ety, pp); + } +} + +JL_DLLEXPORT jl_value_t *jl_atomic_pointerset(jl_value_t *p, jl_value_t *x, jl_value_t *order) +{ + JL_TYPECHK(pointerset, pointer, p); + JL_TYPECHK(pointerset, symbol, order); + (void)jl_get_atomic_order_checked((jl_sym_t*)order, 0, 1); + jl_value_t *ety = jl_tparam0(jl_typeof(p)); + char *pp = (char*)jl_unbox_long(p); + if (ety == (jl_value_t*)jl_any_type) { + jl_atomic_store((jl_value_t**)pp, x); + } + else { + if (!jl_is_datatype(ety)) + jl_error("pointerset: invalid pointer"); if (jl_typeof(x) != ety) jl_type_error("pointerset", ety, x); - memcpy(pp, x, elsz); + size_t nb = jl_datatype_size(ety); + if ((nb & (nb - 1)) != 0 || nb > MAX_POINTERATOMIC_SIZE) + jl_error("pointerset: invalid pointer for atomic operation"); + jl_atomic_store_bits(pp, x, nb); } return p; } +JL_DLLEXPORT jl_value_t *jl_atomic_pointerswap(jl_value_t *p, jl_value_t *x, jl_value_t *order) +{ + JL_TYPECHK(pointerswap, pointer, p); + JL_TYPECHK(pointerswap, symbol, order); + (void)jl_get_atomic_order_checked((jl_sym_t*)order, 1, 1); + jl_value_t *ety = jl_tparam0(jl_typeof(p)); + jl_value_t *y; + char *pp = (char*)jl_unbox_long(p); + if (ety == (jl_value_t*)jl_any_type) { + y = jl_atomic_exchange((jl_value_t**)pp, x); + } + else { + if (!jl_is_datatype(ety)) + jl_error("pointerswap: invalid pointer"); + if (jl_typeof(x) != ety) + jl_type_error("pointerswap", ety, x); + size_t nb = jl_datatype_size(ety); + if ((nb & (nb - 1)) != 0 || nb > MAX_POINTERATOMIC_SIZE) + jl_error("pointerswap: invalid pointer for atomic operation"); + y = jl_atomic_swap_bits(ety, pp, x, nb); + } + return y; +} + +JL_DLLEXPORT jl_value_t *jl_atomic_pointermodify(jl_value_t *p, jl_value_t *f, jl_value_t *x, jl_value_t *order_sym) +{ + // n.b. we use seq_cst always here, but need to verify the order sym + // against the weaker load-only that happens first + if (order_sym == (jl_value_t*)acquire_release_sym) + order_sym = (jl_value_t*)acquire_sym; + jl_value_t *expected = jl_atomic_pointerref(p, order_sym); + jl_value_t *ety = jl_tparam0(jl_typeof(p)); + char *pp = (char*)jl_unbox_long(p); + jl_value_t **args; + JL_GC_PUSHARGS(args, 2); + args[0] = expected; + while (1) { + args[1] = x; + jl_value_t *y = jl_apply_generic(f, args, 2); + args[1] = y; + if (ety == (jl_value_t*)jl_any_type) { + if (jl_atomic_cmpswap((jl_value_t**)pp, &expected, y)) + break; + } + else { + if (jl_typeof(y) != ety) + jl_type_error("pointermodify", ety, y); + size_t nb = jl_datatype_size(ety); + if (jl_atomic_bool_cmpswap_bits(pp, expected, y, nb)) + break; + expected = jl_atomic_new_bits(ety, pp); + } + args[0] = expected; + jl_gc_safepoint(); + } + // args[0] == expected (old); args[1] == y (new) + args[0] = jl_f_tuple(NULL, args, 2); + JL_GC_POP(); + return args[0]; +} + +JL_DLLEXPORT jl_value_t *jl_atomic_pointerreplace(jl_value_t *p, jl_value_t *expected, jl_value_t *x, jl_value_t *success_order_sym, jl_value_t *failure_order_sym) +{ + JL_TYPECHK(pointerreplace, pointer, p); + JL_TYPECHK(pointerreplace, symbol, success_order_sym); + JL_TYPECHK(pointerreplace, symbol, failure_order_sym); + enum jl_memory_order success_order = jl_get_atomic_order_checked((jl_sym_t*)success_order_sym, 1, 1); + enum jl_memory_order failure_order = jl_get_atomic_order_checked((jl_sym_t*)failure_order_sym, 1, 0); + if (failure_order > success_order) + jl_atomic_error("pointerreplace: invalid atomic ordering"); + // TODO: filter other invalid orderings + jl_value_t *ety = jl_tparam0(jl_typeof(p)); + char *pp = (char*)jl_unbox_long(p); + if (ety == (jl_value_t*)jl_any_type) { + jl_value_t **result; + JL_GC_PUSHARGS(result, 2); + result[0] = expected; + int success; + while (1) { + success = jl_atomic_cmpswap((jl_value_t**)pp, &result[0], x); + if (success || !jl_egal(result[0], expected)) + break; + } + result[1] = success ? jl_true : jl_false; + result[0] = jl_f_tuple(NULL, result, 2); + JL_GC_POP(); + return result[0]; + } + else { + if (!jl_is_datatype(ety)) + jl_error("pointerreplace: invalid pointer"); + if (jl_typeof(x) != ety) + jl_type_error("pointerreplace", ety, x); + size_t nb = jl_datatype_size(ety); + if ((nb & (nb - 1)) != 0 || nb > MAX_POINTERATOMIC_SIZE) + jl_error("pointerreplace: invalid pointer for atomic operation"); + return jl_atomic_cmpswap_bits((jl_datatype_t*)ety, pp, expected, x, nb); + } +} + +JL_DLLEXPORT jl_value_t *jl_atomic_fence(jl_value_t *order) +{ + JL_TYPECHK(fence, symbol, order); + (void)jl_get_atomic_order_checked((jl_sym_t*)order, 0, 0); + jl_fence(); + return jl_nothing; +} + JL_DLLEXPORT jl_value_t *jl_cglobal(jl_value_t *v, jl_value_t *ty) { JL_TYPECHK(cglobal, type, ty); @@ -644,7 +795,7 @@ static inline jl_value_t *jl_intrinsiclambda_checked(jl_value_t *ty, void *pa, v params[0] = ty; params[1] = (jl_value_t*)jl_bool_type; jl_datatype_t *tuptyp = jl_apply_tuple_type_v(params, 2); - JL_GC_PROMISE_ROOTED(tuptyp); // (JL_ALAWYS_LEAFTYPE) + JL_GC_PROMISE_ROOTED(tuptyp); // (JL_ALWAYS_LEAFTYPE) jl_task_t *ct = jl_current_task; jl_value_t *newv = jl_gc_alloc(ct->ptls, ((jl_datatype_t*)tuptyp)->size, tuptyp); diff --git a/src/safepoint.c b/src/safepoint.c index fcd09ffef0334..d54c7c62bec56 100644 --- a/src/safepoint.c +++ b/src/safepoint.c @@ -121,7 +121,8 @@ int jl_safepoint_start_gc(void) // one of them to actually run the collection. We can't just let the // master thread do the GC since it might be running unmanaged code // and can take arbitrarily long time before hitting a safe point. - if (jl_atomic_compare_exchange(&jl_gc_running, 0, 1) != 0) { + uint32_t running = 0; + if (!jl_atomic_cmpswap(&jl_gc_running, &running, 1)) { jl_mutex_unlock_nogc(&safepoint_lock); jl_safepoint_wait_gc(); return 0; diff --git a/src/stackwalk.c b/src/stackwalk.c index b686bbf5be847..2994e653cb462 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -702,8 +702,8 @@ void jl_rec_backtrace(jl_task_t *t) } if (t->copy_stack || !t->started || t->stkbuf == NULL) return; - int old = jl_atomic_compare_exchange(&t->tid, -1, ptls->tid); - if (old != -1 && old != ptls->tid) + int16_t old = -1; + if (!jl_atomic_cmpswap(&t->tid, &old, ptls->tid) && old != ptls->tid) return; bt_context_t *context = NULL; #if defined(_OS_WINDOWS_) diff --git a/src/staticdata.c b/src/staticdata.c index e2d3116b258d7..2425e8bc44450 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -30,7 +30,7 @@ extern "C" { // TODO: put WeakRefs on the weak_refs list during deserialization // TODO: handle finalizers -#define NUM_TAGS 147 +#define NUM_TAGS 150 // An array of references that need to be restored from the sysimg // This is a manually constructed dual of the gvars array, which would be produced by codegen for Julia code, for C. @@ -153,6 +153,7 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_memory_exception); INSERT_TAG(jl_undefref_exception); INSERT_TAG(jl_readonlymemory_exception); + INSERT_TAG(jl_atomicerror_type); // other special values INSERT_TAG(jl_emptysvec); @@ -185,6 +186,9 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_builtin_svec); INSERT_TAG(jl_builtin_getfield); INSERT_TAG(jl_builtin_setfield); + INSERT_TAG(jl_builtin_swapfield); + INSERT_TAG(jl_builtin_modifyfield); + INSERT_TAG(jl_builtin_replacefield); INSERT_TAG(jl_builtin_fieldtype); INSERT_TAG(jl_builtin_arrayref); INSERT_TAG(jl_builtin_const_arrayref); @@ -240,7 +244,8 @@ static const jl_fptr_args_t id_to_fptrs[] = { &jl_f_typeassert, &jl_f__apply_iterate, &jl_f__apply_pure, &jl_f__call_latest, &jl_f__call_in_world, &jl_f_isdefined, &jl_f_tuple, &jl_f_svec, &jl_f_intrinsic_call, &jl_f_invoke_kwsorter, - &jl_f_getfield, &jl_f_setfield, &jl_f_fieldtype, &jl_f_nfields, + &jl_f_getfield, &jl_f_setfield, &jl_f_swapfield, &jl_f_modifyfield, + &jl_f_replacefield, &jl_f_fieldtype, &jl_f_nfields, &jl_f_arrayref, &jl_f_const_arrayref, &jl_f_arrayset, &jl_f_arraysize, &jl_f_apply_type, &jl_f_applicable, &jl_f_invoke, &jl_f_sizeof, &jl_f__expr, &jl_f__typevar, &jl_f_ifelse, &jl_f__structtype, &jl_f__abstracttype, &jl_f__primitivetype, @@ -1018,6 +1023,20 @@ static void jl_write_values(jl_serializer_state *s) ios_write(s->const_data, flddesc, fldsize); } } + else if (jl_is_typename(v)) { + jl_typename_t *tn = (jl_typename_t*)v; + jl_typename_t *newtn = (jl_typename_t*)&s->s->buf[reloc_offset]; + if (tn->atomicfields != NULL) { + size_t nf = jl_svec_len(tn->names); + uintptr_t layout = LLT_ALIGN(ios_pos(s->const_data), sizeof(void*)); + write_padding(s->const_data, layout - ios_pos(s->const_data)); // realign stream + newtn->atomicfields = NULL; // relocation offset + layout /= sizeof(void*); + arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_typename_t, atomicfields))); // relocation location + arraylist_push(&s->relocs_list, (void*)(((uintptr_t)ConstDataRef << RELOC_TAG_OFFSET) + layout)); // relocation target + ios_write(s->const_data, (char*)tn->atomicfields, nf); + } + } else if (((jl_datatype_t*)(jl_typeof(v)))->name == jl_idtable_typename) { // will need to rehash this, later (after types are fully constructed) arraylist_push(&reinit_list, (void*)item); diff --git a/src/toplevel.c b/src/toplevel.c index 33054299b4989..27d36a564f0d8 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -149,8 +149,8 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex newm->parent = parent_module; jl_binding_t *b = jl_get_binding_wr(parent_module, name, 1); jl_declare_constant(b); - jl_value_t *old = jl_atomic_compare_exchange(&b->value, NULL, (jl_value_t*)newm); - if (old != NULL) { + jl_value_t *old = NULL; + if (!jl_atomic_cmpswap(&b->value, &old, (jl_value_t*)newm)) { if (!jl_is_module(old)) { jl_errorf("invalid redefinition of constant %s", jl_symbol_name(name)); } diff --git a/stdlib/Serialization/src/Serialization.jl b/stdlib/Serialization/src/Serialization.jl index 22782eff99816..2bc12cab77ce5 100644 --- a/stdlib/Serialization/src/Serialization.jl +++ b/stdlib/Serialization/src/Serialization.jl @@ -1264,6 +1264,7 @@ function deserialize_typename(s::AbstractSerializer, number) super = deserialize(s)::Type parameters = deserialize(s)::SimpleVector types = deserialize(s)::SimpleVector + attrs = Core.svec() has_instance = deserialize(s)::Bool abstr = deserialize(s)::Bool mutabl = deserialize(s)::Bool @@ -1274,8 +1275,8 @@ function deserialize_typename(s::AbstractSerializer, number) # TODO: there's an unhanded cycle in the dependency graph at this point: # while deserializing super and/or types, we may have encountered # tn.wrapper and throw UndefRefException before we get to this point - ndt = ccall(:jl_new_datatype, Any, (Any, Any, Any, Any, Any, Any, Cint, Cint, Cint), - tn, tn.module, super, parameters, names, types, + ndt = ccall(:jl_new_datatype, Any, (Any, Any, Any, Any, Any, Any, Any, Cint, Cint, Cint), + tn, tn.module, super, parameters, names, types, attrs, abstr, mutabl, ninitialized) tn.wrapper = ndt.name.wrapper ccall(:jl_set_const, Cvoid, (Any, Any, Any), tn.module, tn.name, tn.wrapper) diff --git a/test/atomics.jl b/test/atomics.jl new file mode 100644 index 0000000000000..9ffd65d5ded49 --- /dev/null +++ b/test/atomics.jl @@ -0,0 +1,338 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +using Test, Base.Threads +using Core: ConcurrencyViolationError +import Base: copy + +mutable struct ARefxy{T} + @atomic x::T + y::T + ARefxy(x::T, y::T) where {T} = new{T}(x, y) + ARefxy{T}(x, y) where {T} = new{T}(x, y) + ARefxy{T}() where {T} = new{T}() +end + +mutable struct Refxy{T} + x::T + y::T + Refxy(x::T, y::T) where {T} = new{T}(x, y) + Refxy{T}(x, y) where {T} = new{T}(x, y) + Refxy{T}() where {T} = new() # unused, but sets ninitialized to 0 +end + +@test_throws ErrorException("invalid redefinition of constant ARefxy") @eval mutable struct ARefxy{T} + @atomic x::T + @atomic y::T +end +@test_throws ErrorException("invalid redefinition of constant ARefxy") @eval mutable struct ARefxy{T} + x::T + y::T +end +@test_throws ErrorException("invalid redefinition of constant ARefxy") @eval mutable struct ARefxy{T} + x::T + @atomic y::T +end +@test_throws ErrorException("invalid redefinition of constant Refxy") @eval mutable struct Refxy{T} + x::T + @atomic y::T +end + +copy(r::Union{Refxy,ARefxy}) = typeof(r)(r.x, r.y) +function add(x::T, y)::T where {T}; x + y; end +swap(x, y) = y + +let T1 = Refxy{NTuple{3,UInt8}}, + T2 = ARefxy{NTuple{3,UInt8}} + @test sizeof(T1) == 6 + @test sizeof(T2) == 8 + @test fieldoffset(T1, 1) == 0 + @test fieldoffset(T2, 1) == 0 + @test fieldoffset(T1, 2) == 3 + @test fieldoffset(T2, 2) == 4 + @test !Base.datatype_haspadding(T1) + @test Base.datatype_haspadding(T2) + @test Base.datatype_alignment(T1) == 1 + @test Base.datatype_alignment(T2) == 4 +end + +# check that very large types are getting locks +let (x, y) = (Complex{Int128}(10, 30), Complex{Int128}(20, 40)) + ar = ARefxy(x, y) + r = Refxy(x, y) + @test 64 == sizeof(r) < sizeof(ar) + @test sizeof(r) == sizeof(ar) - Int(fieldoffset(typeof(ar), 1)) +end + +@noinline function _test_field_operators(r) + r = r[] + T = typeof(getfield(r, :x)) + @test getfield(r, :x, :sequentially_consistent) === T(12345_10) + @test setfield!(r, :x, T(12345_1), :sequentially_consistent) === T(12345_1) + @test getfield(r, :x, :sequentially_consistent) === T(12345_1) + @test replacefield!(r, :x, 12345_1 % UInt, T(12345_100), :sequentially_consistent, :sequentially_consistent) === (T(12345_1), false) + @test replacefield!(r, :x, T(12345_1), T(12345_100), :sequentially_consistent, :sequentially_consistent) === (T(12345_1), true) + @test getfield(r, :x, :sequentially_consistent) === T(12345_100) + @test replacefield!(r, :x, T(12345_1), T(12345_1), :sequentially_consistent, :sequentially_consistent) === (T(12345_100), false) + @test getfield(r, :x, :sequentially_consistent) === T(12345_100) + @test modifyfield!(r, :x, add, 1, :sequentially_consistent) === (T(12345_100), T(12345_101)) + @test modifyfield!(r, :x, add, 1, :sequentially_consistent) === (T(12345_101), T(12345_102)) + @test getfield(r, :x, :sequentially_consistent) === T(12345_102) + @test swapfield!(r, :x, T(12345_1), :sequentially_consistent) === T(12345_102) + @test getfield(r, :x, :sequentially_consistent) === T(12345_1) + nothing +end +@noinline function test_field_operators(r) + _test_field_operators(Ref(copy(r))) + _test_field_operators(Ref{Any}(copy(r))) + nothing +end +test_field_operators(ARefxy{Int}(12345_10, 12345_20)) +test_field_operators(ARefxy{Any}(12345_10, 12345_20)) +test_field_operators(ARefxy{Union{Nothing,Int}}(12345_10, nothing)) +test_field_operators(ARefxy{Complex{Int32}}(12345_10, 12345_20)) +test_field_operators(ARefxy{Complex{Int128}}(12345_10, 12345_20)) + +@noinline function _test_field_orderings(r, x, y) + @nospecialize x y + r = r[] + + @test getfield(r, :x) === x + @test_throws ConcurrencyViolationError("invalid atomic ordering") getfield(r, :x, :u) + @test_throws ConcurrencyViolationError("getfield: atomic field cannot be accessed non-atomically") getfield(r, :x, :not_atomic) + @test getfield(r, :x, :unordered) === x + @test getfield(r, :x, :monotonic) === x + @test getfield(r, :x, :acquire) === x + @test_throws ConcurrencyViolationError("invalid atomic ordering") getfield(r, :x, :release) === x + @test_throws ConcurrencyViolationError("invalid atomic ordering") getfield(r, :x, :acquire_release) === x + @test getfield(r, :x, :sequentially_consistent) === x + @test isdefined(r, :x) + @test_throws ConcurrencyViolationError("invalid atomic ordering") isdefined(r, :x, :u) + @test_throws ConcurrencyViolationError("isdefined: atomic field cannot be accessed non-atomically") isdefined(r, :x, :not_atomic) + @test isdefined(r, :x, :unordered) + @test isdefined(r, :x, :monotonic) + @test isdefined(r, :x, :acquire) + @test_throws ConcurrencyViolationError("invalid atomic ordering") isdefined(r, :x, :release) + @test_throws ConcurrencyViolationError("invalid atomic ordering") isdefined(r, :x, :acquire_release) + @test isdefined(r, :x, :sequentially_consistent) + + @test getfield(r, :y) === y + @test_throws ConcurrencyViolationError("invalid atomic ordering") getfield(r, :y, :u) + @test getfield(r, :y, :not_atomic) === y + @test_throws ConcurrencyViolationError("getfield: non-atomic field cannot be accessed atomically") getfield(r, :y, :unordered) + @test_throws ConcurrencyViolationError("getfield: non-atomic field cannot be accessed atomically") getfield(r, :y, :monotonic) + @test_throws ConcurrencyViolationError("getfield: non-atomic field cannot be accessed atomically") getfield(r, :y, :acquire) + @test_throws ConcurrencyViolationError("invalid atomic ordering") getfield(r, :y, :release) + @test_throws ConcurrencyViolationError("invalid atomic ordering") getfield(r, :y, :acquire_release) + @test_throws ConcurrencyViolationError("getfield: non-atomic field cannot be accessed atomically") getfield(r, :y, :sequentially_consistent) + @test isdefined(r, :y) + @test_throws ConcurrencyViolationError("invalid atomic ordering") isdefined(r, :y, :u) + @test isdefined(r, :y, :not_atomic) + @test_throws ConcurrencyViolationError("isdefined: non-atomic field cannot be accessed atomically") isdefined(r, :y, :unordered) + @test_throws ConcurrencyViolationError("isdefined: non-atomic field cannot be accessed atomically") isdefined(r, :y, :monotonic) + @test_throws ConcurrencyViolationError("isdefined: non-atomic field cannot be accessed atomically") isdefined(r, :y, :acquire) + @test_throws ConcurrencyViolationError("invalid atomic ordering") isdefined(r, :y, :release) + @test_throws ConcurrencyViolationError("invalid atomic ordering") isdefined(r, :y, :acquire_release) + @test_throws ConcurrencyViolationError("isdefined: non-atomic field cannot be accessed atomically") isdefined(r, :y, :sequentially_consistent) + + @test_throws ConcurrencyViolationError("invalid atomic ordering") setfield!(r, :x, y, :u) + @test_throws ConcurrencyViolationError("setfield!: atomic field cannot be written non-atomically") setfield!(r, :x, y) + @test_throws ConcurrencyViolationError("setfield!: atomic field cannot be written non-atomically") setfield!(r, :x, y, :not_atomic) + @test getfield(r, :x) === x + @test setfield!(r, :x, y, :unordered) === y + @test setfield!(r, :x, y, :monotonic) === y + @test_throws ConcurrencyViolationError("invalid atomic ordering") setfield!(r, :x, y, :acquire) === y + @test setfield!(r, :x, y, :release) === y + @test_throws ConcurrencyViolationError("invalid atomic ordering") setfield!(r, :x, y, :acquire_release) === y + @test setfield!(r, :x, y, :sequentially_consistent) === y + @test getfield(r, :x) === y + + @test_throws ConcurrencyViolationError("invalid atomic ordering") setfield!(r, :y, x, :u) + @test_throws ConcurrencyViolationError("setfield!: non-atomic field cannot be written atomically") setfield!(r, :y, x, :unordered) + @test_throws ConcurrencyViolationError("setfield!: non-atomic field cannot be written atomically") setfield!(r, :y, x, :monotonic) + @test_throws ConcurrencyViolationError("invalid atomic ordering") setfield!(r, :y, x, :acquire) + @test_throws ConcurrencyViolationError("setfield!: non-atomic field cannot be written atomically") setfield!(r, :y, x, :release) + @test_throws ConcurrencyViolationError("invalid atomic ordering") setfield!(r, :y, x, :acquire_release) + @test_throws ConcurrencyViolationError("setfield!: non-atomic field cannot be written atomically") setfield!(r, :y, x, :sequentially_consistent) + @test getfield(r, :y) === y + @test setfield!(r, :y, x) === x + @test setfield!(r, :y, x, :not_atomic) === x + @test getfield(r, :y) === x + + @test_throws ConcurrencyViolationError("invalid atomic ordering") swapfield!(r, :y, y, :u) + @test_throws ConcurrencyViolationError("swapfield!: non-atomic field cannot be written atomically") swapfield!(r, :y, y, :unordered) + @test_throws ConcurrencyViolationError("swapfield!: non-atomic field cannot be written atomically") swapfield!(r, :y, y, :monotonic) + @test_throws ConcurrencyViolationError("swapfield!: non-atomic field cannot be written atomically") swapfield!(r, :y, y, :acquire) + @test_throws ConcurrencyViolationError("swapfield!: non-atomic field cannot be written atomically") swapfield!(r, :y, y, :release) + @test_throws ConcurrencyViolationError("swapfield!: non-atomic field cannot be written atomically") swapfield!(r, :y, y, :acquire_release) + @test_throws ConcurrencyViolationError("swapfield!: non-atomic field cannot be written atomically") swapfield!(r, :y, y, :sequentially_consistent) + @test swapfield!(r, :y, y, :not_atomic) === x + + @test_throws ConcurrencyViolationError("invalid atomic ordering") modifyfield!(r, :y, swap, y, :u) + @test_throws ConcurrencyViolationError("modifyfield!: non-atomic field cannot be written atomically") modifyfield!(r, :y, swap, y, :unordered) + @test_throws ConcurrencyViolationError("modifyfield!: non-atomic field cannot be written atomically") modifyfield!(r, :y, swap, y, :monotonic) + @test_throws ConcurrencyViolationError("modifyfield!: non-atomic field cannot be written atomically") modifyfield!(r, :y, swap, y, :acquire) + @test_throws ConcurrencyViolationError("modifyfield!: non-atomic field cannot be written atomically") modifyfield!(r, :y, swap, y, :release) + @test_throws ConcurrencyViolationError("modifyfield!: non-atomic field cannot be written atomically") modifyfield!(r, :y, swap, y, :acquire_release) + @test_throws ConcurrencyViolationError("modifyfield!: non-atomic field cannot be written atomically") modifyfield!(r, :y, swap, y, :sequentially_consistent) + @test modifyfield!(r, :y, swap, x, :not_atomic) === (y, x) + + @test_throws ConcurrencyViolationError("invalid atomic ordering") replacefield!(r, :y, y, y, :u, :not_atomic) + @test_throws ConcurrencyViolationError("replacefield!: non-atomic field cannot be written atomically") replacefield!(r, :y, y, y, :unordered, :not_atomic) + @test_throws ConcurrencyViolationError("replacefield!: non-atomic field cannot be written atomically") replacefield!(r, :y, y, y, :monotonic, :not_atomic) + @test_throws ConcurrencyViolationError("replacefield!: non-atomic field cannot be written atomically") replacefield!(r, :y, y, y, :acquire, :not_atomic) + @test_throws ConcurrencyViolationError("replacefield!: non-atomic field cannot be written atomically") replacefield!(r, :y, y, y, :release, :not_atomic) + @test_throws ConcurrencyViolationError("replacefield!: non-atomic field cannot be written atomically") replacefield!(r, :y, y, y, :acquire_release, :not_atomic) + @test_throws ConcurrencyViolationError("replacefield!: non-atomic field cannot be written atomically") replacefield!(r, :y, y, y, :sequentially_consistent, :not_atomic) + @test_throws ConcurrencyViolationError("invalid atomic ordering") replacefield!(r, :y, y, y, :not_atomic, :u) + @test_throws ConcurrencyViolationError("replacefield!: non-atomic field cannot be accessed atomically") replacefield!(r, :y, y, y, :not_atomic, :unordered) + @test_throws ConcurrencyViolationError("replacefield!: non-atomic field cannot be accessed atomically") replacefield!(r, :y, y, y, :not_atomic, :monotonic) + @test_throws ConcurrencyViolationError("replacefield!: non-atomic field cannot be accessed atomically") replacefield!(r, :y, y, y, :not_atomic, :acquire) + @test_throws ConcurrencyViolationError("invalid atomic ordering") replacefield!(r, :y, y, y, :not_atomic, :release) + @test_throws ConcurrencyViolationError("invalid atomic ordering") replacefield!(r, :y, y, y, :not_atomic, :acquire_release) + @test_throws ConcurrencyViolationError("replacefield!: non-atomic field cannot be accessed atomically") replacefield!(r, :y, y, y, :not_atomic, :sequentially_consistent) + @test replacefield!(r, :y, x, y, :not_atomic, :not_atomic) === (x, true) + @test replacefield!(r, :y, x, y, :not_atomic, :not_atomic) === (y, x === y) + @test replacefield!(r, :y, y, y, :not_atomic) === (y, true) + @test replacefield!(r, :y, y, y) === (y, true) + + @test_throws ConcurrencyViolationError("invalid atomic ordering") swapfield!(r, :x, x, :u) + @test_throws ConcurrencyViolationError("swapfield!: atomic field cannot be written non-atomically") swapfield!(r, :x, x, :not_atomic) + @test_throws ConcurrencyViolationError("swapfield!: atomic field cannot be written non-atomically") swapfield!(r, :x, x) + @test swapfield!(r, :x, x, :unordered) === y + @test swapfield!(r, :x, x, :monotonic) === x + @test swapfield!(r, :x, x, :acquire) === x + @test swapfield!(r, :x, x, :release) === x + @test swapfield!(r, :x, x, :acquire_release) === x + @test swapfield!(r, :x, x, :sequentially_consistent) === x + + @test_throws ConcurrencyViolationError("invalid atomic ordering") modifyfield!(r, :x, swap, x, :u) + @test_throws ConcurrencyViolationError("modifyfield!: atomic field cannot be written non-atomically") modifyfield!(r, :x, swap, x, :not_atomic) + @test_throws ConcurrencyViolationError("modifyfield!: atomic field cannot be written non-atomically") modifyfield!(r, :x, swap, x) + @test modifyfield!(r, :x, swap, x, :unordered) === (x, x) + @test modifyfield!(r, :x, swap, x, :monotonic) === (x, x) + @test modifyfield!(r, :x, swap, x, :acquire) === (x, x) + @test modifyfield!(r, :x, swap, x, :release) === (x, x) + @test modifyfield!(r, :x, swap, x, :acquire_release) === (x, x) + @test modifyfield!(r, :x, swap, x, :sequentially_consistent) === (x, x) + + @test_throws ConcurrencyViolationError("invalid atomic ordering") replacefield!(r, :x, x, x, :u, :not_atomic) + @test_throws ConcurrencyViolationError("replacefield!: atomic field cannot be written non-atomically") replacefield!(r, :x, x, x) + @test_throws ConcurrencyViolationError("replacefield!: atomic field cannot be written non-atomically") replacefield!(r, :x, y, x, :not_atomic, :not_atomic) + @test_throws ConcurrencyViolationError("replacefield!: atomic field cannot be accessed non-atomically") replacefield!(r, :x, x, x, :unordered, :not_atomic) + @test_throws ConcurrencyViolationError("replacefield!: atomic field cannot be accessed non-atomically") replacefield!(r, :x, x, x, :monotonic, :not_atomic) + @test_throws ConcurrencyViolationError("replacefield!: atomic field cannot be accessed non-atomically") replacefield!(r, :x, x, x, :acquire, :not_atomic) + @test_throws ConcurrencyViolationError("replacefield!: atomic field cannot be accessed non-atomically") replacefield!(r, :x, x, x, :release, :not_atomic) + @test_throws ConcurrencyViolationError("replacefield!: atomic field cannot be accessed non-atomically") replacefield!(r, :x, x, x, :acquire_release, :not_atomic) + @test_throws ConcurrencyViolationError("replacefield!: atomic field cannot be accessed non-atomically") replacefield!(r, :x, x, x, :sequentially_consistent, :not_atomic) + @test_throws ConcurrencyViolationError("invalid atomic ordering") replacefield!(r, :x, x, x, :not_atomic, :u) + @test_throws ConcurrencyViolationError("replacefield!: atomic field cannot be written non-atomically") replacefield!(r, :x, x, x, :not_atomic, :unordered) + @test_throws ConcurrencyViolationError("replacefield!: atomic field cannot be written non-atomically") replacefield!(r, :x, x, x, :not_atomic, :monotonic) + @test_throws ConcurrencyViolationError("replacefield!: atomic field cannot be written non-atomically") replacefield!(r, :x, x, x, :not_atomic, :acquire) + @test_throws ConcurrencyViolationError("invalid atomic ordering") replacefield!(r, :x, x, x, :not_atomic, :release) + @test_throws ConcurrencyViolationError("invalid atomic ordering") replacefield!(r, :x, x, x, :not_atomic, :acquire_release) + @test_throws ConcurrencyViolationError("replacefield!: atomic field cannot be written non-atomically") replacefield!(r, :x, x, x, :not_atomic, :sequentially_consistent) + @test replacefield!(r, :x, x, y, :sequentially_consistent, :sequentially_consistent) === (x, true) + @test replacefield!(r, :x, x, y, :sequentially_consistent, :sequentially_consistent) === (y, x === y) + @test replacefield!(r, :x, y, x, :sequentially_consistent) === (y, true) + nothing +end +@noinline function test_field_orderings(r, x, y) + _test_field_orderings(Ref(copy(r)), x, y) + _test_field_orderings(Ref{Any}(copy(r)), x, y) + nothing +end +@noinline test_field_orderings(x, y) = (@nospecialize; test_field_orderings(ARefxy(x, y), x, y)) +test_field_orderings(10, 20) +test_field_orderings(true, false) +test_field_orderings("hi", "bye") +test_field_orderings(:hi, :bye) +test_field_orderings(nothing, nothing) +test_field_orderings(ARefxy{Any}(12345_10, 12345_20), 12345_10, 12345_20) +test_field_orderings(ARefxy{Any}(true, false), true, false) +test_field_orderings(ARefxy{Union{Nothing,Missing}}(nothing, missing), nothing, missing) +test_field_orderings(ARefxy{Union{Nothing,Int}}(nothing, 12345_1), nothing, 12345_1) +test_field_orderings(Complex{Int128}(10, 30), Complex{Int128}(20, 40)) + +struct UndefComplex{T} + re::T + im::T + UndefComplex{T}() where {T} = new{T}() +end +Base.convert(T::Type{<:UndefComplex}, S) = T() +@noinline function _test_field_undef(r) + r = r[] + T = fieldtype(typeof(r), :x) + x = convert(T, 12345_10) + @test_throws UndefRefError getfield(r, :x) + @test_throws UndefRefError getfield(r, :x, :sequentially_consistent) + @test_throws UndefRefError modifyfield!(r, :x, add, 1, :sequentially_consistent) + @test_throws (T === Any ? UndefRefError : TypeError) replacefield!(r, :x, 1, 1.0, :sequentially_consistent) + @test_throws UndefRefError replacefield!(r, :x, 1, x, :sequentially_consistent) + @test_throws UndefRefError getfield(r, :x, :sequentially_consistent) + @test_throws UndefRefError swapfield!(r, :x, x, :sequentially_consistent) + @test getfield(r, :x, :sequentially_consistent) === x === getfield(r, :x) + nothing +end +@noinline function test_field_undef(T) + _test_field_undef(Ref(T())) + _test_field_undef(Ref{Any}(T())) + nothing +end +test_field_undef(ARefxy{BigInt}) +test_field_undef(ARefxy{Any}) +test_field_undef(ARefxy{Union{Nothing,Integer}}) +test_field_undef(ARefxy{UndefComplex{Any}}) +test_field_undef(ARefxy{UndefComplex{UndefComplex{Any}}}) + +@test_throws ErrorException @macroexpand @atomic foo() +@test_throws ErrorException @macroexpand @atomic foo += bar +@test_throws ErrorException @macroexpand @atomic foo += bar +@test_throws ErrorException @macroexpand @atomic foo = bar +@test_throws ErrorException @macroexpand @atomic foo() +@test_throws ErrorException @macroexpand @atomic foo(bar) +@test_throws ErrorException @macroexpand @atomic foo(bar, baz) +@test_throws ErrorException @macroexpand @atomic foo(bar, baz, bax) +@test_throws ErrorException @macroexpand @atomicreplace foo bar + +# test macroexpansions +let a = ARefxy(1, -1) + @test 1 === @atomic a.x + @test 2 === @atomic :sequentially_consistent a.x = 2 + @test 3 === @atomic :monotonic a.x = 3 + @test_throws ConcurrencyViolationError @atomic :not_atomic a.x = 2 + @test_throws ConcurrencyViolationError @atomic :not_atomic a.x + @test_throws ConcurrencyViolationError @atomic :not_atomic a.x += 1 + + @test 3 === @atomic :monotonic a.x + @test 5 === @atomic a.x += 2 + @test 4 === @atomic :monotonic a.x -= 1 + @test 12 === @atomic :monotonic a.x *= 3 + + @test 12 === @atomic a.x + @test (12, 13) === @atomic a.x + 1 + @test (13, 15) === @atomic :monotonic a.x + 2 + @test (15, 19) === @atomic a.x max 19 + @test (19, 20) === @atomic :monotonic a.x max 20 + @test_throws ConcurrencyViolationError @atomic :not_atomic a.x + 1 + @test_throws ConcurrencyViolationError @atomic :not_atomic a.x max 30 + + @test 20 === @atomic a.x + @test 20 === @atomicswap a.x = 1 + @test 1 === @atomicswap :monotonic a.x = 2 + @test_throws ConcurrencyViolationError @atomicswap :not_atomic a.x = 1 + + @test 2 === @atomic a.x + @test (2, true) === @atomicreplace a.x 2 => 1 + @test (1, false) === @atomicreplace :monotonic a.x 2 => 1 + @test (1, false) === @atomicreplace :monotonic :monotonic a.x 2 => 1 + @test_throws ConcurrencyViolationError @atomicreplace :not_atomic a.x 1 => 2 + @test_throws ConcurrencyViolationError @atomicreplace :monotonic :acquire a.x 1 => 2 + + @test 1 === @atomic a.x + xchg = 1 => 2 + @test (1, true) === @atomicreplace a.x xchg + @test (2, false) === @atomicreplace :monotonic a.x xchg + @test (2, false) === @atomicreplace :acquire_release :monotonic a.x xchg + @test_throws ConcurrencyViolationError @atomicreplace :not_atomic a.x xchg + @test_throws ConcurrencyViolationError @atomicreplace :monotonic :acquire a.x xchg +end diff --git a/test/choosetests.jl b/test/choosetests.jl index 4a3b4c7ddd028..e34a1b4dacf67 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -33,7 +33,7 @@ in the `choices` argument: """ function choosetests(choices = []) testnames = [ - "subarray", "core", "compiler", "worlds", + "subarray", "core", "compiler", "worlds", "atomics", "keywordargs", "numbers", "subtype", "char", "strings", "triplequote", "unicode", "intrinsics", "dict", "hashing", "iobuffer", "staged", "offsetarray", diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 5cbd39d8468a6..045d396934c58 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -2953,7 +2953,8 @@ end @test Core.Compiler.return_type(apply26826, Tuple{typeof(setfield!), Any, Vararg{Symbol}}) == Symbol @test Core.Compiler.return_type(apply26826, Tuple{typeof(setfield!), Any, Symbol, Vararg{Integer}}) == Integer @test Core.Compiler.return_type(apply26826, Tuple{typeof(setfield!), Any, Symbol, Integer, Vararg}) == Integer -@test Core.Compiler.return_type(apply26826, Tuple{typeof(setfield!), Any, Symbol, Integer, Any, Vararg}) == Union{} +@test Core.Compiler.return_type(apply26826, Tuple{typeof(setfield!), Any, Symbol, Integer, Any, Vararg}) == Integer +@test Core.Compiler.return_type(apply26826, Tuple{typeof(setfield!), Any, Symbol, Integer, Any, Any, Vararg}) == Union{} @test Core.Compiler.return_type(apply26826, Tuple{typeof(Core._expr), Vararg}) == Expr @test Core.Compiler.return_type(apply26826, Tuple{typeof(Core._expr), Any, Vararg}) == Expr @test Core.Compiler.return_type(apply26826, Tuple{typeof(Core._expr), Any, Any, Vararg}) == Expr @@ -2964,7 +2965,8 @@ end @test Core.Compiler.return_type(apply26826, Tuple{typeof(getfield), Tuple{Int}, Vararg}) == Int @test Core.Compiler.return_type(apply26826, Tuple{typeof(getfield), Tuple{Int}, Any, Vararg}) == Int @test Core.Compiler.return_type(apply26826, Tuple{typeof(getfield), Tuple{Int}, Any, Any, Vararg}) == Int -@test Core.Compiler.return_type(apply26826, Tuple{typeof(getfield), Any, Any, Any, Any, Vararg}) == Union{} +@test Core.Compiler.return_type(apply26826, Tuple{typeof(getfield), Tuple{Int}, Any, Any, Any, Vararg}) == Int +@test Core.Compiler.return_type(apply26826, Tuple{typeof(getfield), Any, Any, Any, Any, Any, Vararg}) == Union{} @test Core.Compiler.return_type(apply26826, Tuple{typeof(fieldtype), Vararg}) == Any @test Core.Compiler.return_type(apply26826, Tuple{typeof(fieldtype), Any, Vararg}) == Any @test Core.Compiler.return_type(apply26826, Tuple{typeof(fieldtype), Any, Any, Vararg}) == Any diff --git a/test/core.jl b/test/core.jl index 5036df4ec8218..3a9c03f7aed37 100644 --- a/test/core.jl +++ b/test/core.jl @@ -1099,9 +1099,9 @@ end let strct = LoadError("yofile", 0, "bad") @test nfields(strct) == 3 # sanity test @test_throws BoundsError(strct, 10) getfield(strct, 10) - @test_throws ErrorException("setfield! immutable struct of type LoadError cannot be changed") setfield!(strct, 0, "") - @test_throws ErrorException("setfield! immutable struct of type LoadError cannot be changed") setfield!(strct, 4, "") - @test_throws ErrorException("setfield! immutable struct of type LoadError cannot be changed") setfield!(strct, :line, 0) + @test_throws ErrorException("setfield!: immutable struct of type LoadError cannot be changed") setfield!(strct, 0, "") + @test_throws ErrorException("setfield!: immutable struct of type LoadError cannot be changed") setfield!(strct, 4, "") + @test_throws ErrorException("setfield!: immutable struct of type LoadError cannot be changed") setfield!(strct, :line, 0) @test strct.file == "yofile" @test strct.line === 0 @test strct.error == "bad" @@ -1123,7 +1123,7 @@ let mstrct = TestMutable("melm", 1, nothing) @test_throws BoundsError(mstrct, 4) setfield!(mstrct, 4, "") end let strct = LoadError("yofile", 0, "bad") - @test_throws(ErrorException("setfield! immutable struct of type LoadError cannot be changed"), + @test_throws(ErrorException("setfield!: immutable struct of type LoadError cannot be changed"), ccall(:jl_set_nth_field, Cvoid, (Any, Csize_t, Any), strct, 0, "")) end let mstrct = TestMutable("melm", 1, nothing) diff --git a/test/intrinsics.jl b/test/intrinsics.jl index 47560d7dbd626..c1d3019f8db35 100644 --- a/test/intrinsics.jl +++ b/test/intrinsics.jl @@ -101,8 +101,17 @@ let f = Core.Intrinsics.ashr_int end # issue #29929 -@test unsafe_store!(Ptr{Nothing}(C_NULL), nothing) === Ptr{Nothing}(0) -@test unsafe_load(Ptr{Nothing}(0)) === nothing +let p = Ptr{Nothing}(0) + @test unsafe_store!(p, nothing) === C_NULL + @test unsafe_load(p) === nothing + @test Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) === nothing + @test Core.Intrinsics.atomic_pointerset(p, nothing, :sequentially_consistent) === p + @test Core.Intrinsics.atomic_pointerswap(p, nothing, :sequentially_consistent) === nothing + @test Core.Intrinsics.atomic_pointermodify(p, (i, j) -> j, nothing, :sequentially_consistent) === (nothing, nothing) + @test Core.Intrinsics.atomic_pointerreplace(p, nothing, nothing, :sequentially_consistent, :sequentially_consistent) === (nothing, true) + @test Core.Intrinsics.atomic_pointerreplace(p, missing, nothing, :sequentially_consistent, :sequentially_consistent) === (nothing, false) +end + struct GhostStruct end @test unsafe_load(Ptr{GhostStruct}(rand(Int))) === GhostStruct() @@ -152,3 +161,61 @@ end @test_intrinsic Core.Intrinsics.fptosi Int Float16(3.3) 3 @test_intrinsic Core.Intrinsics.fptoui UInt Float16(3.3) UInt(3) end + +@test Core.Intrinsics.atomic_fence(:sequentially_consistent) === nothing +@test Core.Intrinsics.atomic_pointerref(C_NULL, :sequentially_consistent) == nothing + +primitive type Int256 <: Signed 256 end +Int256(i::Int) = Core.Intrinsics.sext_int(Int256, i) +primitive type Int512 <: Signed 512 end +Int512(i::Int) = Core.Intrinsics.sext_int(Int512, i) +function add(i::T, j)::T where {T}; return i + j; end +swap(i, j) = j +for TT in (Int8, Int16, Int32, Int64, Int128, Int256, Int512, Complex{Int32}, Complex{Int512}, Any) + T(x) = convert(TT, x) + r = Ref{TT}(10) + p = Base.unsafe_convert(Ptr{eltype(r)}, r) + GC.@preserve r begin + S = UInt32 + if TT !== Any + @test_throws TypeError Core.Intrinsics.atomic_pointerset(p, S(1), :sequentially_consistent) + @test_throws TypeError Core.Intrinsics.atomic_pointerswap(p, S(100), :sequentially_consistent) + @test_throws TypeError Core.Intrinsics.atomic_pointerreplace(p, T(100), S(2), :sequentially_consistent, :sequentially_consistent) + end + @test Core.Intrinsics.pointerref(p, 1, 1) === T(10) === r[] + if sizeof(r) > 8 + @test_throws ErrorException("pointerref: invalid pointer for atomic operation") Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) + @test_throws ErrorException("pointerset: invalid pointer for atomic operation") Core.Intrinsics.atomic_pointerset(p, T(1), :sequentially_consistent) + @test_throws ErrorException("pointerswap: invalid pointer for atomic operation") Core.Intrinsics.atomic_pointerswap(p, T(100), :sequentially_consistent) + @test_throws ErrorException("pointerref: invalid pointer for atomic operation") Core.Intrinsics.atomic_pointermodify(p, add, T(1), :sequentially_consistent) + @test_throws ErrorException("pointerref: invalid pointer for atomic operation") Core.Intrinsics.atomic_pointermodify(p, swap, S(1), :sequentially_consistent) + @test_throws ErrorException("pointerreplace: invalid pointer for atomic operation") Core.Intrinsics.atomic_pointerreplace(p, T(100), T(2), :sequentially_consistent, :sequentially_consistent) + @test_throws ErrorException("pointerreplace: invalid pointer for atomic operation") Core.Intrinsics.atomic_pointerreplace(p, S(100), T(2), :sequentially_consistent, :sequentially_consistent) + @test Core.Intrinsics.pointerref(p, 1, 1) === T(10) === r[] + else + TT !== Any && @test_throws TypeError Core.Intrinsics.atomic_pointermodify(p, swap, S(1), :sequentially_consistent) + @test Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) === T(10) + @test Core.Intrinsics.atomic_pointerset(p, T(1), :sequentially_consistent) === p + @test Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) === T(1) + @test Core.Intrinsics.atomic_pointerreplace(p, T(1), T(100), :sequentially_consistent, :sequentially_consistent) === (T(1), true) + @test Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) === T(100) + @test Core.Intrinsics.atomic_pointerreplace(p, T(1), T(1), :sequentially_consistent, :sequentially_consistent) === (T(100), false) + @test Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) === T(100) + @test Core.Intrinsics.atomic_pointermodify(p, add, T(1), :sequentially_consistent) === (T(100), T(101)) + @test Core.Intrinsics.atomic_pointermodify(p, add, T(1), :sequentially_consistent) === (T(101), T(102)) + @test Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) === T(102) + @test Core.Intrinsics.atomic_pointerswap(p, T(103), :sequentially_consistent) === T(102) + @test Core.Intrinsics.atomic_pointerreplace(p, S(100), T(2), :sequentially_consistent, :sequentially_consistent) === (T(103), false) + @test Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) === T(103) + end + if TT === Any + @test Core.Intrinsics.atomic_pointermodify(p, swap, S(103), :sequentially_consistent) === (T(103), S(103)) + @test Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) === S(103) + @test Core.Intrinsics.atomic_pointerset(p, S(1), :sequentially_consistent) === p + @test Core.Intrinsics.atomic_pointerswap(p, S(100), :sequentially_consistent) === S(1) + @test Core.Intrinsics.atomic_pointerreplace(p, T(100), S(2), :sequentially_consistent, :sequentially_consistent) === (S(100), false) + @test Core.Intrinsics.atomic_pointerreplace(p, S(100), T(2), :sequentially_consistent, :sequentially_consistent) === (S(100), true) + @test Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) === T(2) + end + end +end