From 0f86508e7f2dbe80631ef907a2048a36ab299d45 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 14 Jul 2021 15:22:39 -0400 Subject: [PATCH] atomics: switch to using exact types for return pairs This makes many more functions type-stable, by directly preserving the element type when making the copy, and prints as `old => new`, like the argument to atomic replace. For replace, we use Pair{FT, FT} (like the argument). For modify, we use NamedTuple{(:old, :success), Tuple{FT, Bool}}. --- base/Base.jl | 3 ++ base/boot.jl | 14 ++++++++- base/compiler/tfuncs.jl | 64 ++++++++++++++++++++++++++++++--------- base/expr.jl | 12 ++++---- base/pair.jl | 13 -------- src/cgutils.cpp | 17 +++++------ src/codegen.cpp | 1 + src/datatype.c | 47 ++++++++++++---------------- src/gc.c | 3 ++ src/init.c | 1 + src/jl_exported_data.inc | 1 + src/jl_exported_funcs.inc | 2 ++ src/jltypes.c | 44 +++++++++++++++++++++------ src/julia.h | 5 ++- src/runtime_intrinsics.c | 27 ++++++++++------- src/staticdata.c | 3 +- test/atomics.jl | 60 +++++++++++++++++++----------------- test/intrinsics.jl | 24 ++++++++------- test/show.jl | 2 +- 19 files changed, 209 insertions(+), 134 deletions(-) diff --git a/base/Base.jl b/base/Base.jl index 42a506479326b..09fdcb1689de3 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -107,6 +107,9 @@ include("options.jl") include("promotion.jl") include("tuple.jl") include("expr.jl") +Pair{A, B}(@nospecialize(a), @nospecialize(b)) where {A, B} = (@_inline_meta; Pair{A, B}(convert(A, a)::A, convert(B, b)::B)) +#Pair{Any, B}(@nospecialize(a::Any), b) where {B} = (@_inline_meta; Pair{Any, B}(a, Base.convert(B, b)::B)) +#Pair{A, Any}(a, @nospecialize(b::Any)) where {A} = (@_inline_meta; Pair{A, Any}(Base.convert(A, a)::A, b)) include("pair.jl") include("traits.jl") include("range.jl") diff --git a/base/boot.jl b/base/boot.jl index 98b8cf2e9cf40..af66e471ae1d0 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -171,7 +171,7 @@ export # key types Any, DataType, Vararg, NTuple, Tuple, Type, UnionAll, TypeVar, Union, Nothing, Cvoid, - AbstractArray, DenseArray, NamedTuple, + AbstractArray, DenseArray, NamedTuple, Pair, # special objects Function, Method, Module, Symbol, Task, Array, UndefInitializer, undef, WeakRef, VecElement, @@ -813,4 +813,16 @@ _parse = nothing # support for deprecated uses of internal _apply function _apply(x...) = Core._apply_iterate(Main.Base.iterate, x...) +struct Pair{A, B} + first::A + second::B + # if we didn't inline this, it's probably because the callsite was actually dynamic + # to avoid potentially compiling many copies of this, we mark the arguments with `@nospecialize` + # but also mark the whole function with `@inline` to ensure we will inline it whenever possible + # (even if `convert(::Type{A}, a::A)` for some reason was expensive) + Pair(a, b) = new{typeof(a), typeof(b)}(a, b) + Pair{A, B}(a::A, b::B) where {A, B} = new(a, b) + Pair{Any, Any}(@nospecialize(a::Any), @nospecialize(b::Any)) = new(a, b) +end + ccall(:jl_set_istopmod, Cvoid, (Any, Bool), Core, true) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 772db09417393..b7d280cc15c1b 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -466,29 +466,51 @@ add_tfunc(Core._typevar, 3, 3, typevar_tfunc, 100) add_tfunc(applicable, 1, INT_INF, (@nospecialize(f), args...)->Bool, 100) add_tfunc(Core.Intrinsics.arraylen, 1, 1, @nospecialize(x)->Int, 4) 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) - return a.parameters[1] - elseif isa(a, UnionAll) && !has_free_typevars(a) - unw = unwrap_unionall(a) - if isa(unw, DataType) - return rewrap_unionall(unw.parameters[1], a) - end + if !has_free_typevars(a) + unw = unwrap_unionall(a) + if isa(unw, DataType) && unw.name === Ptr.body.name + T = unw.parameters[1] + T isa Type && return rewrap_unionall(T, a) end end return Any end +function atomic_pointermodify_tfunc(ptr, op, v, order) + @nospecialize + a = widenconst(ptr) + if !has_free_typevars(a) + unw = unwrap_unionall(a) + if isa(unw, DataType) && unw.name === Ptr.body.name + T = unw.parameters[1] + # note: we could sometimes refine this to a PartialStruct if we analyzed `op(T, T)::T` + T isa Type && return rewrap_unionall(Pair{T, T}, a) + end + end + return Pair +end +function atomic_pointerreplace_tfunc(ptr, x, v, success_order, failure_order) + @nospecialize + a = widenconst(ptr) + if !has_free_typevars(a) + unw = unwrap_unionall(a) + if isa(unw, DataType) && unw.name === Ptr.body.name + T = unw.parameters[1] + T isa Type && return rewrap_unionall(ccall(:jl_apply_cmpswap_type, Any, (Any,), T), a) + end + end + return ccall(:jl_apply_cmpswap_type, Any, (Any,), T) where T +end 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) +add_tfunc(atomic_pointermodify, 4, 4, atomic_pointermodify_tfunc, 5) +add_tfunc(atomic_pointerreplace, 5, 5, atomic_pointerreplace_tfunc, 5) # more accurate typeof_tfunc for vararg tuples abstract only in length function typeof_concrete_vararg(t::DataType) @@ -911,11 +933,25 @@ 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 +modifyfield!_tfunc(o, f, op, v, order) = (@nospecialize; modifyfield!_tfunc(o, f, op, v)) +function modifyfield!_tfunc(o, f, op, v) + @nospecialize + T = _fieldtype_tfunc(o, isconcretetype(o), f) + T === Bottom && return Bottom + # note: we could sometimes refine this to a PartialStruct if we analyzed `op(o.f, v)::T` + PT = Const(Pair) + return instanceof_tfunc(apply_type_tfunc(PT, T, T))[1] +end 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}) +function replacefield!_tfunc(o, f, x, v) + @nospecialize + T = _fieldtype_tfunc(o, isconcretetype(o), f) + T === Bottom && return Bottom + PT = Const(ccall(:jl_apply_cmpswap_type, Any, (Any,), T) where T) + return instanceof_tfunc(apply_type_tfunc(PT, T))[1] +end + # 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) diff --git a/base/expr.jl b/base/expr.jl index 9df363714679e..e0304f7f7382b 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -523,16 +523,16 @@ 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) +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) +4 => 10 julia> @atomic a.x max 5 # again change field x of a to the max value, with sequential consistency -(10, 10) +10 => 10 ``` """ macro atomic(ex) @@ -653,18 +653,18 @@ 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) +(old = 1, success = 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) +(old = 2, success = 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) +(old = 2, success = true) julia> @atomic a.x # fetch field x of a, with sequential consistency 0 diff --git a/base/pair.jl b/base/pair.jl index 7481d50b7458b..b5dffbb4e7e86 100644 --- a/base/pair.jl +++ b/base/pair.jl @@ -1,18 +1,5 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -struct Pair{A, B} - first::A - second::B - function Pair{A, B}(@nospecialize(a), @nospecialize(b)) where {A, B} - @_inline_meta - # if we didn't inline this, it's probably because the callsite was actually dynamic - # to avoid potentially compiling many copies of this, we mark the arguments with `@nospecialize` - # but also mark the whole function with `@inline` to ensure we will inline it whenever possible - # (even if `convert(::Type{A}, a::A)` for some reason was expensive) - return new(a, b) - end -end -Pair(a, b) = Pair{typeof(a), typeof(b)}(a, b) const => = Pair """ diff --git a/src/cgutils.cpp b/src/cgutils.cpp index 94ac4c071770e..a453393bbd2cc 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -1560,9 +1560,8 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, Value *Success = emit_f_is(ctx, cmp, ghostValue(jltype)); Success = ctx.builder.CreateZExt(Success, T_int8); jl_cgval_t argv[2] = {ghostValue(jltype), mark_julia_type(ctx, Success, false, jl_bool_type)}; - // TODO: do better here - Value *instr = emit_jlcall(ctx, jltuple_func, V_rnull, argv, 2, JLCALL_F_CC); - return mark_julia_type(ctx, instr, true, jl_any_type); + jl_datatype_t *rettyp = jl_apply_cmpswap_type(jltype); + return emit_new_struct(ctx, (jl_value_t*)rettyp, 2, argv); } else { return ghostValue(jltype); @@ -1803,10 +1802,10 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, } oldval = mark_julia_type(ctx, instr, isboxed, jltype); if (isreplacefield) { - // TODO: do better here + Success = ctx.builder.CreateZExt(Success, T_int8); jl_cgval_t argv[2] = {oldval, mark_julia_type(ctx, Success, false, jl_bool_type)}; - instr = emit_jlcall(ctx, jltuple_func, V_rnull, argv, 2, JLCALL_F_CC); - oldval = mark_julia_type(ctx, instr, true, jl_any_type); + jl_datatype_t *rettyp = jl_apply_cmpswap_type(jltype); + oldval = emit_new_struct(ctx, (jl_value_t*)rettyp, 2, argv); } } return oldval; @@ -3247,10 +3246,10 @@ static jl_cgval_t emit_setfield(jl_codectx_t &ctx, if (needlock) emit_lockstate_value(ctx, strct, false); if (isreplacefield) { + Success = ctx.builder.CreateZExt(Success, T_int8); jl_cgval_t argv[2] = {oldval, mark_julia_type(ctx, Success, false, jl_bool_type)}; - // TODO: do better here - Value *instr = emit_jlcall(ctx, jltuple_func, V_rnull, argv, 2, JLCALL_F_CC); - oldval = mark_julia_type(ctx, instr, true, jl_any_type); + jl_datatype_t *rettyp = jl_apply_cmpswap_type(jfty); + oldval = emit_new_struct(ctx, (jl_value_t*)rettyp, 2, argv); } return oldval; } diff --git a/src/codegen.cpp b/src/codegen.cpp index ba583799e1c97..f742bf8937f81 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1170,6 +1170,7 @@ static CallInst *emit_jlcall(jl_codectx_t &ctx, JuliaFunction *theFptr, Value *t jl_cgval_t *args, size_t nargs, CallingConv::ID cc); static Value *emit_f_is(jl_codectx_t &ctx, const jl_cgval_t &arg1, const jl_cgval_t &arg2, Value *nullcheck1 = nullptr, Value *nullcheck2 = nullptr); +static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t nargs, const jl_cgval_t *argv); static Value *literal_pointer_val(jl_codectx_t &ctx, jl_value_t *p); static GlobalVariable *prepare_global_in(Module *M, GlobalVariable *G); diff --git a/src/datatype.c b/src/datatype.c index 1a3ffa78170ac..aecbe6f407ae6 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -953,18 +953,13 @@ JL_DLLEXPORT int jl_atomic_bool_cmpswap_bits(char *dst, const jl_value_t *expect 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) +JL_DLLEXPORT jl_value_t *jl_atomic_cmpswap_bits(jl_datatype_t *dt, jl_datatype_t *rettyp, 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 isptr = jl_field_isptr(rettyp, 0); + jl_value_t *y = jl_gc_alloc(ct->ptls, isptr ? nb : rettyp->size, isptr ? dt : rettyp); int success; jl_datatype_t *et = (jl_datatype_t*)jl_typeof(expected); if (nb == 0) { @@ -1053,7 +1048,7 @@ JL_DLLEXPORT jl_value_t *jl_atomic_cmpswap_bits(jl_datatype_t *dt, char *dst, co } if (isptr) { JL_GC_PUSH1(&y); - jl_value_t *z = jl_gc_alloc(ct->ptls, tuptyp->size, tuptyp); + jl_value_t *z = jl_gc_alloc(ct->ptls, rettyp->size, rettyp); *(jl_value_t**)z = y; JL_GC_POP(); y = z; @@ -1658,8 +1653,11 @@ jl_value_t *modify_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_valu args[0] = r; jl_gc_safepoint(); } - // args[0] == r (old); args[1] == y (new) - args[0] = jl_f_tuple(NULL, args, 2); + // args[0] == r (old) + // args[1] == y (new) + jl_datatype_t *rettyp = jl_apply_modify_type(ty); + JL_GC_PROMISE_ROOTED(rettyp); // (JL_ALWAYS_LEAFTYPE) + args[0] = jl_new_struct(rettyp, args[0], args[1]); JL_GC_POP(); return args[0]; } @@ -1671,6 +1669,8 @@ jl_value_t *replace_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_val jl_type_error("replacefield!", ty, rhs); size_t offs = jl_field_offset(st, i); jl_value_t *r = expected; + jl_datatype_t *rettyp = jl_apply_cmpswap_type(ty); + JL_GC_PROMISE_ROOTED(rettyp); // (JL_ALWAYS_LEAFTYPE) if (jl_field_isptr(st, i)) { jl_value_t **p = (jl_value_t**)((char*)v + offs); int success; @@ -1683,11 +1683,8 @@ jl_value_t *replace_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_val 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_PUSH1(&r); + r = jl_new_struct(rettyp, r, success ? jl_true : jl_false); JL_GC_POP(); } else { @@ -1695,7 +1692,7 @@ jl_value_t *replace_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_val int isunion = jl_is_uniontype(ty); int needlock; jl_value_t *rty = ty; - size_t fsz; + size_t fsz = jl_field_size(st, i); if (isunion) { assert(!isatomic); hasptr = 0; @@ -1708,7 +1705,7 @@ jl_value_t *replace_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_val needlock = (isatomic && fsz > MAX_ATOMIC_SIZE); } if (isatomic && !needlock) { - r = jl_atomic_cmpswap_bits((jl_datatype_t*)rty, (char*)v + offs, r, rhs, fsz); + r = jl_atomic_cmpswap_bits((jl_datatype_t*)ty, rettyp, (char*)v + offs, r, rhs, fsz); int success = *((uint8_t*)r + fsz); if (success && hasptr) jl_gc_multi_wb(v, rhs); // rhs is immutable @@ -1717,23 +1714,17 @@ jl_value_t *replace_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_val 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); + assert(!jl_field_isptr(rettyp, 0)); + r = jl_gc_alloc(ct->ptls, rettyp->size, (jl_value_t*)rettyp); 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); + memcpy((char*)r, (char*)v + offs, fsz); // copy field, including union bits if (success) { + size_t fsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the final copy if (((jl_datatype_t*)rty)->layout->haspadding) success = jl_egal__bits(r, expected, (jl_datatype_t*)rty); else diff --git a/src/gc.c b/src/gc.c index f923b826de544..91e4879f9bb93 100644 --- a/src/gc.c +++ b/src/gc.c @@ -2780,6 +2780,7 @@ static void jl_gc_queue_thread_local(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp } void jl_gc_mark_enqueued_tasks(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp); +extern jl_value_t *cmpswap_names JL_GLOBALLY_ROOTED; // mark the initial root set static void mark_roots(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp) @@ -2811,6 +2812,8 @@ static void mark_roots(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp) // constants gc_mark_queue_obj(gc_cache, sp, jl_emptytuple_type); + if (cmpswap_names != NULL) + gc_mark_queue_obj(gc_cache, sp, cmpswap_names); } // find unmarked objects that need to be finalized from the finalizer list "list". diff --git a/src/init.c b/src/init.c index 77833033d95bd..1c58753506fb7 100644 --- a/src/init.c +++ b/src/init.c @@ -841,6 +841,7 @@ static void post_boot_hooks(void) jl_methoderror_type = (jl_datatype_t*)core("MethodError"); jl_loaderror_type = (jl_datatype_t*)core("LoadError"); jl_initerror_type = (jl_datatype_t*)core("InitError"); + jl_pair_type = core("Pair"); jl_weakref_type = (jl_datatype_t*)core("WeakRef"); jl_vecelement_typename = ((jl_datatype_t*)jl_unwrap_unionall(core("VecElement")))->name; diff --git a/src/jl_exported_data.inc b/src/jl_exported_data.inc index 3cebe459bf643..b8d5ae0e35b29 100644 --- a/src/jl_exported_data.inc +++ b/src/jl_exported_data.inc @@ -74,6 +74,7 @@ XX(jl_number_type) \ XX(jl_opaque_closure_type) \ XX(jl_opaque_closure_typename) \ + XX(jl_pair_type) \ XX(jl_partial_opaque_type) \ XX(jl_partial_struct_type) \ XX(jl_phicnode_type) \ diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index 8e2c21fb6dfec..31eaaff7d1526 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -11,6 +11,7 @@ XX(jl_alloc_svec_uninit) \ XX(jl_alloc_vec_any) \ XX(jl_apply_array_type) \ + XX(jl_apply_cmpswap_type) \ XX(jl_apply_generic) \ XX(jl_apply_tuple_type) \ XX(jl_apply_tuple_type_v) \ @@ -549,3 +550,4 @@ XX(jl_vprintf) \ XX(jl_wakeup_thread) \ XX(jl_yield) \ + diff --git a/src/jltypes.c b/src/jltypes.c index 1ae49c0a32eab..aacd2ba19ccca 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -19,6 +19,8 @@ extern "C" { #endif +jl_value_t *cmpswap_names JL_GLOBALLY_ROOTED; + // compute empirical max-probe for a given size #define max_probe(size) ((size) <= 1024 ? 16 : (size) >> 6) #define h2index(hv, sz) (size_t)((hv) & ((sz)-1)) @@ -977,20 +979,42 @@ jl_value_t *jl_apply_type(jl_value_t *tc, jl_value_t **params, size_t n) JL_DLLEXPORT jl_value_t *jl_apply_type1(jl_value_t *tc, jl_value_t *p1) { - JL_GC_PUSH1(&p1); - jl_value_t *t = jl_apply_type(tc, &p1, 1); - JL_GC_POP(); - return t; + return jl_apply_type(tc, &p1, 1); } JL_DLLEXPORT jl_value_t *jl_apply_type2(jl_value_t *tc, jl_value_t *p1, jl_value_t *p2) { - jl_value_t **args; - JL_GC_PUSHARGS(args, 2); - args[0] = p1; args[1] = p2; - jl_value_t *t = jl_apply_type(tc, args, 2); - JL_GC_POP(); - return t; + jl_value_t *args[2]; + args[0] = p1; + args[1] = p2; + return jl_apply_type(tc, args, 2); +} + +jl_datatype_t *jl_apply_modify_type(jl_value_t *dt) +{ + jl_datatype_t *rettyp = (jl_datatype_t*)jl_apply_type2(jl_pair_type, dt, dt); + JL_GC_PROMISE_ROOTED(rettyp); // (JL_ALWAYS_LEAFTYPE) + return rettyp; +} + +jl_datatype_t *jl_apply_cmpswap_type(jl_value_t *dt) +{ + jl_value_t *params[2]; + jl_value_t *names = jl_atomic_load_relaxed(&cmpswap_names); + if (names == NULL) { + params[0] = jl_symbol("old"); + params[1] = jl_symbol("success"); + jl_value_t *lnames = jl_f_tuple(NULL, params, 2); + if (jl_atomic_cmpswap(&cmpswap_names, &names, lnames)) + names = jl_atomic_load_relaxed(&cmpswap_names); // == lnames + } + params[0] = 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) + jl_datatype_t *rettyp = (jl_datatype_t*)jl_apply_type2(jl_namedtuple_type, names, tuptyp); + JL_GC_PROMISE_ROOTED(rettyp); // (JL_ALWAYS_LEAFTYPE) + return rettyp; } JL_DLLEXPORT jl_value_t *jl_tupletype_fill(size_t n, jl_value_t *v) diff --git a/src/julia.h b/src/julia.h index 9afd7301fc5bc..e6edc71b42dfa 100644 --- a/src/julia.h +++ b/src/julia.h @@ -707,6 +707,7 @@ extern JL_DLLIMPORT jl_typename_t *jl_llvmpointer_typename JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_typename_t *jl_namedtuple_typename JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_unionall_t *jl_namedtuple_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_task_type JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_value_t *jl_pair_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_array_uint8_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_array_any_type JL_GLOBALLY_ROOTED; @@ -1362,6 +1363,8 @@ JL_DLLEXPORT jl_value_t *jl_instantiate_unionall(jl_unionall_t *u, jl_value_t *p JL_DLLEXPORT jl_value_t *jl_apply_type(jl_value_t *tc, jl_value_t **params, size_t n); JL_DLLEXPORT jl_value_t *jl_apply_type1(jl_value_t *tc, jl_value_t *p1); JL_DLLEXPORT jl_value_t *jl_apply_type2(jl_value_t *tc, jl_value_t *p1, jl_value_t *p2); +JL_DLLEXPORT jl_datatype_t *jl_apply_modify_type(jl_value_t *dt); +JL_DLLEXPORT jl_datatype_t *jl_apply_cmpswap_type(jl_value_t *dt); JL_DLLEXPORT jl_tupletype_t *jl_apply_tuple_type(jl_svec_t *params); JL_DLLEXPORT jl_tupletype_t *jl_apply_tuple_type_v(jl_value_t **p, size_t np); JL_DLLEXPORT jl_datatype_t *jl_new_datatype(jl_sym_t *name, @@ -1384,7 +1387,7 @@ 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_atomic_cmpswap_bits(jl_datatype_t *dt, jl_datatype_t *rettype, 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); diff --git a/src/runtime_intrinsics.c b/src/runtime_intrinsics.c index 7cb58bc230294..be78be74172cb 100644 --- a/src/runtime_intrinsics.c +++ b/src/runtime_intrinsics.c @@ -175,12 +175,16 @@ JL_DLLEXPORT jl_value_t *jl_atomic_pointermodify(jl_value_t *p, jl_value_t *f, j args[0] = expected; jl_gc_safepoint(); } - // args[0] == expected (old); args[1] == y (new) - args[0] = jl_f_tuple(NULL, args, 2); + // args[0] == expected (old) + // args[1] == y (new) + jl_datatype_t *rettyp = jl_apply_modify_type(ety); + JL_GC_PROMISE_ROOTED(rettyp); // (JL_ALWAYS_LEAFTYPE) + args[0] = jl_new_struct(rettyp, args[0], args[1]); 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(atomic_pointerreplace, pointer, p); @@ -193,20 +197,21 @@ JL_DLLEXPORT jl_value_t *jl_atomic_pointerreplace(jl_value_t *p, jl_value_t *exp // TODO: filter other invalid orderings jl_value_t *ety = jl_tparam0(jl_typeof(p)); char *pp = (char*)jl_unbox_long(p); + jl_datatype_t *rettyp = jl_apply_cmpswap_type(ety); + JL_GC_PROMISE_ROOTED(rettyp); // (JL_ALWAYS_LEAFTYPE) if (ety == (jl_value_t*)jl_any_type) { - jl_value_t **result; - JL_GC_PUSHARGS(result, 2); - result[0] = expected; + jl_value_t *result; + JL_GC_PUSH1(&result); + result = expected; int success; while (1) { - success = jl_atomic_cmpswap((jl_value_t**)pp, &result[0], x); - if (success || !jl_egal(result[0], expected)) + success = jl_atomic_cmpswap((jl_value_t**)pp, &result, x); + if (success || !jl_egal(result, expected)) break; } - result[1] = success ? jl_true : jl_false; - result[0] = jl_f_tuple(NULL, result, 2); + result = jl_new_struct(rettyp, result, success ? jl_true : jl_false); JL_GC_POP(); - return result[0]; + return result; } else { if (!is_valid_intrinsic_elptr(ety)) @@ -216,7 +221,7 @@ JL_DLLEXPORT jl_value_t *jl_atomic_pointerreplace(jl_value_t *p, jl_value_t *exp size_t nb = jl_datatype_size(ety); if ((nb & (nb - 1)) != 0 || nb > MAX_POINTERATOMIC_SIZE) jl_error("atomic_pointerreplace: invalid pointer for atomic operation"); - return jl_atomic_cmpswap_bits((jl_datatype_t*)ety, pp, expected, x, nb); + return jl_atomic_cmpswap_bits((jl_datatype_t*)ety, rettyp, pp, expected, x, nb); } } diff --git a/src/staticdata.c b/src/staticdata.c index 8fa1613b075a8..f5892d4218e71 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 150 +#define NUM_TAGS 151 // 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. @@ -127,6 +127,7 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_floatingpoint_type); INSERT_TAG(jl_number_type); INSERT_TAG(jl_signed_type); + INSERT_TAG(jl_pair_type); // special typenames INSERT_TAG(jl_tuple_typename); diff --git a/test/atomics.jl b/test/atomics.jl index 4c32fc12d87ed..c53471ed0da26 100644 --- a/test/atomics.jl +++ b/test/atomics.jl @@ -4,6 +4,8 @@ using Test, Base.Threads using Core: ConcurrencyViolationError import Base: copy +const ReplaceType = ccall(:jl_apply_cmpswap_type, Any, (Any,), T) where T + mutable struct ARefxy{T} @atomic x::T y::T @@ -86,17 +88,18 @@ Base.show(io::IO, x::Int24) = print(io, "Int24(", Core.Intrinsics.zext_int(Int, @noinline function _test_field_operators(r) r = r[] + TT = fieldtype(typeof(r), :x) T = typeof(getfield(r, :x)) @test getfield(r, :x, :sequentially_consistent) === T(123_10) @test setfield!(r, :x, T(123_1), :sequentially_consistent) === T(123_1) @test getfield(r, :x, :sequentially_consistent) === T(123_1) - @test replacefield!(r, :x, 123_1 % UInt, T(123_30), :sequentially_consistent, :sequentially_consistent) === (T(123_1), false) - @test replacefield!(r, :x, T(123_1), T(123_30), :sequentially_consistent, :sequentially_consistent) === (T(123_1), true) + @test replacefield!(r, :x, 123_1 % UInt, T(123_30), :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((T(123_1), false)) + @test replacefield!(r, :x, T(123_1), T(123_30), :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((T(123_1), true)) @test getfield(r, :x, :sequentially_consistent) === T(123_30) - @test replacefield!(r, :x, T(123_1), T(123_1), :sequentially_consistent, :sequentially_consistent) === (T(123_30), false) + @test replacefield!(r, :x, T(123_1), T(123_1), :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((T(123_30), false)) @test getfield(r, :x, :sequentially_consistent) === T(123_30) - @test modifyfield!(r, :x, add, 1, :sequentially_consistent) === (T(123_30), T(123_31)) - @test modifyfield!(r, :x, add, 1, :sequentially_consistent) === (T(123_31), T(123_32)) + @test modifyfield!(r, :x, add, 1, :sequentially_consistent) === Pair{TT,TT}(T(123_30), T(123_31)) + @test modifyfield!(r, :x, add, 1, :sequentially_consistent) === Pair{TT,TT}(T(123_31), T(123_32)) @test getfield(r, :x, :sequentially_consistent) === T(123_32) @test swapfield!(r, :x, T(123_1), :sequentially_consistent) === T(123_32) @test getfield(r, :x, :sequentially_consistent) === T(123_1) @@ -120,6 +123,7 @@ test_field_operators(ARefxy{Float64}(123_10, 123_20)) @noinline function _test_field_orderings(r, x, y) @nospecialize x y r = r[] + TT = fieldtype(typeof(r), :x) @test getfield(r, :x) === x @test_throws ConcurrencyViolationError("invalid atomic ordering") getfield(r, :x, :u) @@ -199,7 +203,7 @@ test_field_operators(ARefxy{Float64}(123_10, 123_20)) @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 modifyfield!(r, :y, swap, x, :not_atomic) === Pair{TT,TT}(y, x) @test_throws ConcurrencyViolationError("invalid atomic ordering") replacefield!(r, :y, y, y, :u, :not_atomic) @test_throws ConcurrencyViolationError("invalid atomic ordering") replacefield!(r, :y, y, y, :unordered, :not_atomic) @@ -215,10 +219,10 @@ test_field_operators(ARefxy{Float64}(123_10, 123_20)) @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("invalid atomic ordering") 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 replacefield!(r, :y, x, y, :not_atomic, :not_atomic) === ReplaceType{TT}((x, true)) + @test replacefield!(r, :y, x, y, :not_atomic, :not_atomic) === ReplaceType{TT}((y, x === y)) + @test replacefield!(r, :y, y, y, :not_atomic) === ReplaceType{TT}((y, true)) + @test replacefield!(r, :y, y, y) === ReplaceType{TT}((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) @@ -234,11 +238,11 @@ test_field_operators(ARefxy{Float64}(123_10, 123_20)) @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_throws ConcurrencyViolationError("invalid atomic ordering") modifyfield!(r, :x, swap, x, :unordered) - @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 modifyfield!(r, :x, swap, x, :monotonic) === Pair{TT,TT}(x, x) + @test modifyfield!(r, :x, swap, x, :acquire) === Pair{TT,TT}(x, x) + @test modifyfield!(r, :x, swap, x, :release) === Pair{TT,TT}(x, x) + @test modifyfield!(r, :x, swap, x, :acquire_release) === Pair{TT,TT}(x, x) + @test modifyfield!(r, :x, swap, x, :sequentially_consistent) === Pair{TT,TT}(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) @@ -256,9 +260,9 @@ test_field_operators(ARefxy{Float64}(123_10, 123_20)) @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("invalid atomic ordering") 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) + @test replacefield!(r, :x, x, y, :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((x, true)) + @test replacefield!(r, :x, x, y, :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((y, x === y)) + @test replacefield!(r, :x, y, x, :sequentially_consistent) === ReplaceType{TT}((y, true)) nothing end @noinline function test_field_orderings(r, x, y) @@ -339,10 +343,10 @@ let a = ARefxy(1, -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 (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 @@ -352,17 +356,17 @@ let a = ARefxy(1, -1) @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 ReplaceType{Int}((2, true)) === @atomicreplace a.x 2 => 1 + @test ReplaceType{Int}((1, false)) === @atomicreplace :monotonic a.x 2 => 1 + @test ReplaceType{Int}((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 ReplaceType{Int}((1, true)) === @atomicreplace a.x xchg + @test ReplaceType{Int}((2, false)) === @atomicreplace :monotonic a.x xchg + @test ReplaceType{Int}((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/intrinsics.jl b/test/intrinsics.jl index 7fa8ecb0ebe27..7fb6bd651ebc0 100644 --- a/test/intrinsics.jl +++ b/test/intrinsics.jl @@ -100,6 +100,8 @@ let f = Core.Intrinsics.ashr_int @test f(Int32(2), -1) == 0 end +const ReplaceType = ccall(:jl_apply_cmpswap_type, Any, (Any,), T) where T + # issue #29929 let p = Ptr{Nothing}(0) @test unsafe_store!(p, nothing) === C_NULL @@ -107,9 +109,9 @@ let p = Ptr{Nothing}(0) @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) + @test Core.Intrinsics.atomic_pointermodify(p, (i, j) -> j, nothing, :sequentially_consistent) === Pair(nothing, nothing) + @test Core.Intrinsics.atomic_pointerreplace(p, nothing, nothing, :sequentially_consistent, :sequentially_consistent) === ReplaceType{Nothing}((nothing, true)) + @test Core.Intrinsics.atomic_pointerreplace(p, missing, nothing, :sequentially_consistent, :sequentially_consistent) === ReplaceType{Nothing}((nothing, false)) end struct GhostStruct end @@ -199,24 +201,24 @@ for TT in (Int8, Int16, Int32, Int64, Int128, Int256, Int512, Complex{Int32}, Co @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_pointerreplace(p, T(1), T(100), :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((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_pointerreplace(p, T(1), T(1), :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((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_pointermodify(p, add, T(1), :sequentially_consistent) === Pair{TT,TT}(T(100), T(101)) + @test Core.Intrinsics.atomic_pointermodify(p, add, T(1), :sequentially_consistent) === Pair{TT,TT}(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_pointerreplace(p, S(100), T(2), :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((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_pointermodify(p, swap, S(103), :sequentially_consistent) === Pair{TT,TT}(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_pointerreplace(p, T(100), S(2), :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((S(100), false)) + @test Core.Intrinsics.atomic_pointerreplace(p, S(100), T(2), :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((S(100), true)) @test Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) === T(2) end end)(TT,) diff --git a/test/show.jl b/test/show.jl index c00dcf523898c..1552214d2516e 100644 --- a/test/show.jl +++ b/test/show.jl @@ -1769,7 +1769,7 @@ end # spurious binding resolutions show(IOContext(b, :module => TestShowType), Base.Pair) @test !Base.isbindingresolved(TestShowType, :Pair) - @test String(take!(b)) == "Base.Pair" + @test String(take!(b)) == "Core.Pair" show(IOContext(b, :module => TestShowType), Base.Complex) @test Base.isbindingresolved(TestShowType, :Complex) @test String(take!(b)) == "Complex"