diff --git a/NEWS.md b/NEWS.md index 2fedf928137a7..6e0b00c92f041 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,10 +4,14 @@ Julia v1.9 Release Notes New language features --------------------- +* It is now possible to assign to bindings in another module using `setproperty!(::Module, ::Symbol, x)`. ([#44137]) Language changes ---------------- +* New builtins `getglobal(::Module, ::Symbol[, order])` and `setglobal!(::Module, ::Symbol, x[, order])` + for reading from and writing to globals. `getglobal` should now be preferred for accessing globals over + `getfield`. ([#44137]) Compiler/Runtime improvements ----------------------------- diff --git a/base/Base.jl b/base/Base.jl index cbfa5ede6aef9..533e09b02784d 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -28,8 +28,7 @@ macro noinline() 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) = (@inline; getfield(x, f)) -setproperty!(x::Module, f::Symbol, v) = setfield!(x, f, v) # to get a decent error +getproperty(x::Module, f::Symbol) = (@inline; getglobal(x, f)) getproperty(x::Type, f::Symbol) = (@inline; getfield(x, f)) setproperty!(x::Type, f::Symbol, v) = error("setfield! fields of Types should not be changed") getproperty(x::Tuple, f::Int) = (@inline; getfield(x, f)) @@ -40,8 +39,12 @@ setproperty!(x, f::Symbol, v) = setfield!(x, f, convert(fieldtype(typeof(x), f), dotgetproperty(x, f) = getproperty(x, f) -getproperty(x::Module, f::Symbol, order::Symbol) = (@inline; getfield(x, f, order)) -setproperty!(x::Module, f::Symbol, v, order::Symbol) = setfield!(x, f, v, order) # to get a decent error +getproperty(x::Module, f::Symbol, order::Symbol) = (@inline; getglobal(x, f, order)) +function setproperty!(x::Module, f::Symbol, v, order::Symbol=:monotonic) + @inline + val::Core.get_binding_type(x, f) = v + return setglobal!(x, f, val, order) +end getproperty(x::Type, f::Symbol, order::Symbol) = (@inline; 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; getfield(x, f, order)) diff --git a/base/boot.jl b/base/boot.jl index 90322b69a54d9..e41622721a6fe 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -193,6 +193,8 @@ export # object model functions fieldtype, getfield, setfield!, swapfield!, modifyfield!, replacefield!, nfields, throw, tuple, ===, isdefined, eval, + # access to globals + getglobal, setglobal!, # ifelse, sizeof # not exported, to avoid conflicting with Base # type reflection <:, typeof, isa, typeassert, @@ -201,7 +203,7 @@ export # constants nothing, Main -const getproperty = getfield +const getproperty = getfield # TODO: use `getglobal` for modules instead const setproperty! = setfield! abstract type Number end diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 62d92b766c6ca..3b5c78b7106b4 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1998,10 +1998,8 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), end function abstract_eval_global(M::Module, s::Symbol) - if isdefined(M,s) - if isconst(M,s) - return Const(getfield(M,s)) - end + if isdefined(M, s) && isconst(M, s) + return Const(getglobal(M, s)) end ty = ccall(:jl_binding_type, Any, (Any, Any), M, s) ty === nothing && return Any diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index 0616204dce748..04b3f515b46f4 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -675,7 +675,7 @@ function statement_cost(ex::Expr, line::Int, src::Union{CodeInfo, IRCode}, sptyp # The efficiency of operations like a[i] and s.b # depend strongly on whether the result can be # inferred, so check the type of ex - if f === Core.getfield || f === Core.tuple + if f === Core.getfield || f === Core.tuple || f === Core.getglobal # we might like to penalize non-inferrability, but # tuple iteration/destructuring makes that impossible # return plus_saturate(argcost, isknowntype(extyp) ? 1 : params.inline_nonleaf_penalty) diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index fdc50b3b481cd..7aeb303bc03a2 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -418,7 +418,7 @@ function lift_leaves(compact::IncrementalCompact, elseif isa(leaf, GlobalRef) mod, name = leaf.mod, leaf.name if isdefined(mod, name) && isconst(mod, name) - leaf = getfield(mod, name) + leaf = getglobal(mod, name) else return nothing end diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 54b89c9a04f43..452a2b554f307 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -480,6 +480,37 @@ function arraysize_nothrow(argtypes::Vector{Any}) return false end +struct MemoryOrder x::Cint end +const MEMORY_ORDER_UNSPECIFIED = MemoryOrder(-2) +const MEMORY_ORDER_INVALID = MemoryOrder(-1) +const MEMORY_ORDER_NOTATOMIC = MemoryOrder(0) +const MEMORY_ORDER_UNORDERED = MemoryOrder(1) +const MEMORY_ORDER_MONOTONIC = MemoryOrder(2) +const MEMORY_ORDER_CONSUME = MemoryOrder(3) +const MEMORY_ORDER_ACQUIRE = MemoryOrder(4) +const MEMORY_ORDER_RELEASE = MemoryOrder(5) +const MEMORY_ORDER_ACQ_REL = MemoryOrder(6) +const MEMORY_ORDER_SEQ_CST = MemoryOrder(7) + +function get_atomic_order(order::Symbol, loading::Bool, storing::Bool) + if order === :not_atomic + return MEMORY_ORDER_NOTATOMIC + elseif order === :unordered && (loading ⊻ storing) + return MEMORY_ORDER_UNORDERED + elseif order === :monotonic && (loading | storing) + return MEMORY_ORDER_MONOTONIC + elseif order === :acquire && loading + return MEMORY_ORDER_ACQUIRE + elseif order === :release && storing + return MEMORY_ORDER_RELEASE + elseif order === :acquire_release && (loading & storing) + return MEMORY_ORDER_ACQ_REL + elseif order === :sequentially_consistent + return MEMORY_ORDER_SEQ_CST + end + return MEMORY_ORDER_INVALID +end + function pointer_eltype(@nospecialize(ptr)) a = widenconst(ptr) if !has_free_typevars(a) @@ -1704,6 +1735,8 @@ function _builtin_nothrow(@nospecialize(f), argtypes::Array{Any,1}, @nospecializ return true end return false + elseif f === getglobal + return getglobal_nothrow(argtypes) elseif f === Core.get_binding_type length(argtypes) == 2 || return false return argtypes[1] ⊑ Module && argtypes[2] ⊑ Symbol @@ -1721,7 +1754,7 @@ const _EFFECT_FREE_BUILTINS = [ fieldtype, apply_type, isa, UnionAll, getfield, arrayref, const_arrayref, isdefined, Core.sizeof, Core.kwfunc, Core.ifelse, Core._typevar, (<:), - typeassert, throw, arraysize + typeassert, throw, arraysize, getglobal, ] const _CONSISTENT_BUILTINS = Any[ @@ -1774,16 +1807,20 @@ function builtin_effects(f::Builtin, argtypes::Vector{Any}, rt) # InferenceState. nothrow = getfield_nothrow(argtypes[2], argtypes[3], true) ipo_consistent &= nothrow - end + else + nothrow = isvarargtype(argtypes[end]) ? false : + builtin_nothrow(f, argtypes[2:end], rt) + end + effect_free = f === isdefined + elseif f === getglobal && length(argtypes) >= 3 + nothrow = effect_free = getglobal_nothrow(argtypes[2:end]) + ipo_consistent = nothrow && isconst((argtypes[2]::Const).val, (argtypes[3]::Const).val) + #effect_free = nothrow && isbindingresolved((argtypes[2]::Const).val, (argtypes[3]::Const).val) else ipo_consistent = contains_is(_CONSISTENT_BUILTINS, f) + effect_free = contains_is(_EFFECT_FREE_BUILTINS, f) || contains_is(_PURE_BUILTINS, f) + nothrow = isvarargtype(argtypes[end]) ? false : builtin_nothrow(f, argtypes[2:end], rt) end - # If we computed nothrow above for getfield, no need to repeat the procedure here - if !nothrow - nothrow = isvarargtype(argtypes[end]) ? false : - builtin_nothrow(f, argtypes[2:end], rt) - end - effect_free = contains_is(_EFFECT_FREE_BUILTINS, f) || contains_is(_PURE_BUILTINS, f) return Effects( ipo_consistent ? ALWAYS_TRUE : ALWAYS_FALSE, @@ -2030,17 +2067,63 @@ function typename_static(@nospecialize(t)) return isType(t) ? _typename(t.parameters[1]) : Core.TypeName end +function global_order_nothrow(@nospecialize(o), loading::Bool, storing::Bool) + o isa Const || return false + sym = o.val + if sym isa Symbol + order = get_atomic_order(sym, loading, storing) + return order !== MEMORY_ORDER_INVALID && order !== MEMORY_ORDER_NOTATOMIC + end + return false +end +function getglobal_nothrow(argtypes::Vector{Any}) + 2 ≤ length(argtypes) ≤ 3 || return false + if length(argtypes) == 3 + global_order_nothrow(o, true, false) || return false + end + M, s = argtypes + if M isa Const && s isa Const + M, s = M.val, s.val + if M isa Module && s isa Symbol + return isdefined(M, s) + end + end + return false +end +function getglobal_tfunc(@nospecialize(M), @nospecialize(s), @nospecialize(_=Symbol)) + if M isa Const && s isa Const + M, s = M.val, s.val + if M isa Module && s isa Symbol + return abstract_eval_global(M, s) + end + return Bottom + elseif !(hasintersect(widenconst(M), Module) && hasintersect(widenconst(s), Symbol)) + return Bottom + end + return Any +end +function setglobal!_tfunc(@nospecialize(M), @nospecialize(s), @nospecialize(v), + @nospecialize(_=Symbol)) + if !(hasintersect(widenconst(M), Module) && hasintersect(widenconst(s), Symbol)) + return Bottom + end + return v +end +add_tfunc(getglobal, 2, 3, getglobal_tfunc, 1) +add_tfunc(setglobal!, 3, 4, setglobal!_tfunc, 3) + function get_binding_type_effect_free(@nospecialize(M), @nospecialize(s)) - if M isa Const && widenconst(M) === Module && - s isa Const && widenconst(s) === Symbol - return ccall(:jl_binding_type, Any, (Any, Any), M.val, s.val) !== nothing + if M isa Const && s isa Const + M, s = M.val, s.val + if M isa Module && s isa Symbol + return ccall(:jl_binding_type, Any, (Any, Any), M, s) !== nothing + end end return false end function get_binding_type_tfunc(@nospecialize(M), @nospecialize(s)) if get_binding_type_effect_free(M, s) - @assert M isa Const && s isa Const - return Const(Core.get_binding_type(M.val, s.val)) + return Const(Core.get_binding_type((M::Const).val, (s::Const).val)) end return Type end diff --git a/base/compiler/utilities.jl b/base/compiler/utilities.jl index 9b1106e964919..e173fd69fbcf7 100644 --- a/base/compiler/utilities.jl +++ b/base/compiler/utilities.jl @@ -52,7 +52,7 @@ function istopfunction(@nospecialize(f), name::Symbol) tn = typeof(f).name if tn.mt.name === name top = _topmod(tn.module) - return isdefined(top, name) && isconst(top, name) && f === getfield(top, name) + return isdefined(top, name) && isconst(top, name) && f === getglobal(top, name) end return false end diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index f27bc19fe7c02..4356b1803d398 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -2731,6 +2731,9 @@ 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)`. +!!! compat "Julia 1.8" + `setproperty!` on modules requires at least Julia 1.8. + See also [`setfield!`](@ref Core.setfield!), [`propertynames`](@ref Base.propertynames) and [`getproperty`](@ref Base.getproperty). diff --git a/doc/src/manual/variables-and-scoping.md b/doc/src/manual/variables-and-scoping.md index 6e94037f3e564..f31e176038e2c 100644 --- a/doc/src/manual/variables-and-scoping.md +++ b/doc/src/manual/variables-and-scoping.md @@ -91,12 +91,6 @@ julia> module D b = a # errors as D's global scope is separate from A's end; ERROR: UndefVarError: a not defined - -julia> module E - import ..A # make module A available - A.a = 2 # throws below error - end; -ERROR: cannot assign variables in other modules ``` If a top-level expression contains a variable declaration with keyword `local`, diff --git a/doc/src/manual/variables.md b/doc/src/manual/variables.md index f61503d99a67c..0dfc4f508577f 100644 --- a/doc/src/manual/variables.md +++ b/doc/src/manual/variables.md @@ -81,13 +81,13 @@ julia> pi π = 3.1415926535897... julia> pi = 3 -ERROR: cannot assign a value to variable MathConstants.pi from module Main +ERROR: cannot assign a value to imported variable MathConstants.pi from module Main julia> sqrt(100) 10.0 julia> sqrt = 4 -ERROR: cannot assign a value to variable Base.sqrt from module Main +ERROR: cannot assign a value to imported variable Base.sqrt from module Main ``` ## [Allowed Variable Names](@id man-allowed-variable-names) diff --git a/src/builtin_proto.h b/src/builtin_proto.h index 7b11813e7a58b..46adef8444aa9 100644 --- a/src/builtin_proto.h +++ b/src/builtin_proto.h @@ -55,6 +55,7 @@ DECLARE_BUILTIN(_typebody); DECLARE_BUILTIN(typeof); DECLARE_BUILTIN(_typevar); DECLARE_BUILTIN(donotdelete); +DECLARE_BUILTIN(getglobal); JL_CALLABLE(jl_f_invoke_kwsorter); #ifdef DEFINE_BUILTIN_GLOBALS @@ -70,6 +71,7 @@ JL_CALLABLE(jl_f__equiv_typedef); JL_CALLABLE(jl_f_get_binding_type); JL_CALLABLE(jl_f_set_binding_type); JL_CALLABLE(jl_f_donotdelete); +JL_CALLABLE(jl_f_setglobal); #ifdef __cplusplus } diff --git a/src/builtins.c b/src/builtins.c index 11da13285e9ca..f81069424d784 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -916,22 +916,18 @@ JL_CALLABLE(jl_f_getfield) } jl_value_t *v = args[0]; jl_value_t *vt = jl_typeof(v); - if (vt == (jl_value_t*)jl_module_type) { - JL_TYPECHK(getfield, symbol, args[1]); - v = jl_eval_global_var((jl_module_t*)v, (jl_sym_t*)args[1]); // is seq_cst already - } - else { - 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 - } + if (vt == (jl_value_t*)jl_module_type) + return jl_f_getglobal(NULL, args, 2); // we just ignore the atomic order and boundschecks + 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 v; } @@ -940,7 +936,7 @@ JL_CALLABLE(jl_f_setfield) enum jl_memory_order order = jl_memory_order_notatomic; JL_NARGS(setfield!, 3, 4); if (nargs == 4) { - JL_TYPECHK(getfield, symbol, args[3]); + JL_TYPECHK(setfield!, symbol, args[3]); order = jl_get_atomic_order_checked((jl_sym_t*)args[3], 0, 1); } jl_value_t *v = args[0]; @@ -1175,6 +1171,82 @@ JL_CALLABLE(jl_f_isdefined) } +// module bindings + +JL_CALLABLE(jl_f_getglobal) +{ + enum jl_memory_order order = jl_memory_order_monotonic; + JL_NARGS(getglobal, 2, 3); + if (nargs == 3) { + JL_TYPECHK(getglobal, symbol, args[2]); + order = jl_get_atomic_order_checked((jl_sym_t*)args[2], 1, 0); + } + JL_TYPECHK(getglobal, module, args[0]); + JL_TYPECHK(getglobal, symbol, args[1]); + if (order == jl_memory_order_notatomic) + jl_atomic_error("getglobal: module binding cannot be read non-atomically"); + jl_value_t *v = jl_eval_global_var((jl_module_t*)args[0], (jl_sym_t*)args[1]); + // is seq_cst already, no fence needed + return v; +} + +JL_CALLABLE(jl_f_setglobal) +{ + enum jl_memory_order order = jl_memory_order_monotonic; + JL_NARGS(setglobal!, 3, 4); + if (nargs == 4) { + JL_TYPECHK(setglobal!, symbol, args[3]); + order = jl_get_atomic_order_checked((jl_sym_t*)args[3], 0, 1); + } + JL_TYPECHK(setglobal!, module, args[0]); + JL_TYPECHK(setglobal!, symbol, args[1]); + if (order == jl_memory_order_notatomic) + jl_atomic_error("setglobal!: module binding cannot be written non-atomically"); + // is seq_cst already, no fence needed + jl_binding_t *b = jl_get_binding_wr((jl_module_t*)args[0], (jl_sym_t*)args[1], 1); + jl_checked_assignment(b, args[2]); + return args[2]; +} + +JL_CALLABLE(jl_f_get_binding_type) +{ + JL_NARGS(get_binding_type, 2, 2); + JL_TYPECHK(get_binding_type, module, args[0]); + JL_TYPECHK(get_binding_type, symbol, args[1]); + jl_module_t *mod = (jl_module_t*)args[0]; + jl_sym_t *sym = (jl_sym_t*)args[1]; + jl_value_t *ty = jl_binding_type(mod, sym); + if (ty == (jl_value_t*)jl_nothing) { + jl_binding_t *b = jl_get_binding_wr(mod, sym, 0); + if (b) { + jl_value_t *old_ty = NULL; + jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type); + return jl_atomic_load_relaxed(&b->ty); + } + return (jl_value_t*)jl_any_type; + } + return ty; +} + +JL_CALLABLE(jl_f_set_binding_type) +{ + JL_NARGS(set_binding_type!, 2, 3); + JL_TYPECHK(set_binding_type!, module, args[0]); + JL_TYPECHK(set_binding_type!, symbol, args[1]); + jl_value_t *ty = nargs == 2 ? (jl_value_t*)jl_any_type : args[2]; + JL_TYPECHK(set_binding_type!, type, ty); + jl_binding_t *b = jl_get_binding_wr((jl_module_t*)args[0], (jl_sym_t*)args[1], 1); + jl_value_t *old_ty = NULL; + if (!jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, ty) && ty != old_ty) { + if (nargs == 2) + return jl_nothing; + jl_errorf("cannot set type for global %s. It already has a value or is already set to a different type.", + jl_symbol_name(b->name)); + } + return jl_nothing; +} + + // apply_type ----------------------------------------------------------------- int jl_valid_type_param(jl_value_t *v) @@ -1697,44 +1769,6 @@ JL_CALLABLE(jl_f__equiv_typedef) return equiv_type(args[0], args[1]) ? jl_true : jl_false; } -JL_CALLABLE(jl_f_get_binding_type) -{ - JL_NARGS(get_binding_type, 2, 2); - JL_TYPECHK(get_binding_type, module, args[0]); - JL_TYPECHK(get_binding_type, symbol, args[1]); - jl_module_t *mod = (jl_module_t*)args[0]; - jl_sym_t *sym = (jl_sym_t*)args[1]; - jl_value_t *ty = jl_binding_type(mod, sym); - if (ty == (jl_value_t*)jl_nothing) { - jl_binding_t *b = jl_get_binding_wr(mod, sym, 0); - if (b) { - jl_value_t *old_ty = NULL; - jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type); - return jl_atomic_load_relaxed(&b->ty); - } - return (jl_value_t*)jl_any_type; - } - return ty; -} - -JL_CALLABLE(jl_f_set_binding_type) -{ - JL_NARGS(set_binding_type!, 2, 3); - JL_TYPECHK(set_binding_type!, module, args[0]); - JL_TYPECHK(set_binding_type!, symbol, args[1]); - jl_value_t *ty = nargs == 2 ? (jl_value_t*)jl_any_type : args[2]; - JL_TYPECHK(set_binding_type!, type, ty); - jl_binding_t *b = jl_get_binding_wr((jl_module_t*)args[0], (jl_sym_t*)args[1], 1); - jl_value_t *old_ty = NULL; - if (!jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, ty) && ty != old_ty) { - if (nargs == 2) - return jl_nothing; - jl_errorf("cannot set type for global %s. It already has a value or is already set to a different type.", - jl_symbol_name(b->name)); - } - return jl_nothing; -} - // IntrinsicFunctions --------------------------------------------------------- static void (*runtime_fp[num_intrinsics])(void); @@ -1884,6 +1918,12 @@ void jl_init_primitives(void) JL_GC_DISABLED jl_builtin_nfields = add_builtin_func("nfields", jl_f_nfields); jl_builtin_isdefined = add_builtin_func("isdefined", jl_f_isdefined); + // module bindings + jl_builtin_getglobal = add_builtin_func("getglobal", jl_f_getglobal); + add_builtin_func("setglobal!", jl_f_setglobal); + add_builtin_func("get_binding_type", jl_f_get_binding_type); + add_builtin_func("set_binding_type!", jl_f_set_binding_type); + // array primitives jl_builtin_arrayref = add_builtin_func("arrayref", jl_f_arrayref); jl_builtin_const_arrayref = add_builtin_func("const_arrayref", jl_f_arrayref); @@ -1915,8 +1955,6 @@ void jl_init_primitives(void) JL_GC_DISABLED add_builtin_func("_setsuper!", jl_f__setsuper); jl_builtin__typebody = add_builtin_func("_typebody!", jl_f__typebody); add_builtin_func("_equiv_typedef", jl_f__equiv_typedef); - add_builtin_func("get_binding_type", jl_f_get_binding_type); - add_builtin_func("set_binding_type!", jl_f_set_binding_type); jl_builtin_donotdelete = add_builtin_func("donotdelete", jl_f_donotdelete); // builtin types diff --git a/src/codegen.cpp b/src/codegen.cpp index d35a34500b41b..4e83381b14221 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2144,7 +2144,7 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex) if (e->head == jl_call_sym) { jl_value_t *f = static_eval(ctx, jl_exprarg(e, 0)); if (f) { - if (jl_array_dim0(e->args) == 3 && f == jl_builtin_getfield) { + if (jl_array_dim0(e->args) == 3 && (f == jl_builtin_getfield || f == jl_builtin_getglobal)) { m = (jl_module_t*)static_eval(ctx, jl_exprarg(e, 1)); // Check the tag before evaluating `s` so that a value of random // type won't be corrupted. @@ -3241,6 +3241,38 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, return false; } + else if (f == jl_builtin_getglobal && (nargs == 2 || nargs == 3)) { + const jl_cgval_t &mod = argv[1]; + const jl_cgval_t &sym = argv[2]; + enum jl_memory_order order = jl_memory_order_unspecified; + + 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 + return false; + } + else + order = jl_memory_order_monotonic; + + if (order == jl_memory_order_invalid) { + emit_atomic_error(ctx, "invalid atomic ordering"); + *ret = jl_cgval_t(ctx.builder.getContext()); // unreachable + return true; + } + + if (sym.constant && sym.typ == (jl_value_t*)jl_symbol_type) { + jl_sym_t *name = (jl_sym_t*)sym.constant; + if (mod.constant && jl_is_module(mod.constant)) { + *ret = emit_globalref(ctx, (jl_module_t*)mod.constant, name); + return true; + } + } + + return false; + } + else if ((f == jl_builtin_setfield && (nargs == 3 || nargs == 4)) || (f == jl_builtin_swapfield && (nargs == 3 || nargs == 4)) || (f == jl_builtin_replacefield && (nargs == 4 || nargs == 5 || nargs == 6)) || @@ -3865,7 +3897,7 @@ static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t assert(b != NULL); if (b->owner != m) { char *msg; - (void)asprintf(&msg, "cannot assign a value to variable %s.%s from module %s", + (void)asprintf(&msg, "cannot assign a value to imported variable %s.%s from module %s", jl_symbol_name(b->owner->name), jl_symbol_name(s), jl_symbol_name(m->name)); emit_error(ctx, msg); free(msg); diff --git a/src/module.c b/src/module.c index 63dff3ae6deb7..249d0d548cd43 100644 --- a/src/module.c +++ b/src/module.c @@ -185,7 +185,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, } else if (error) { JL_UNLOCK(&m->lock); - jl_errorf("cannot assign a value to variable %s.%s from module %s", + jl_errorf("cannot assign a value to imported variable %s.%s from module %s", jl_symbol_name(b->owner->name), jl_symbol_name(var), jl_symbol_name(m->name)); } } diff --git a/src/staticdata.c b/src/staticdata.c index 28a21e9ea7c2b..e72f29257ce19 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -79,7 +79,7 @@ extern "C" { // TODO: put WeakRefs on the weak_refs list during deserialization // TODO: handle finalizers -#define NUM_TAGS 153 +#define NUM_TAGS 154 // 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. @@ -252,6 +252,7 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_builtin_ifelse); INSERT_TAG(jl_builtin__typebody); INSERT_TAG(jl_builtin_donotdelete); + INSERT_TAG(jl_builtin_getglobal); // All optional tags must be placed at the end, so that we // don't accidentally have a `NULL` in the middle @@ -310,6 +311,7 @@ static const jl_fptr_args_t id_to_fptrs[] = { &jl_f_ifelse, &jl_f__structtype, &jl_f__abstracttype, &jl_f__primitivetype, &jl_f__typebody, &jl_f__setsuper, &jl_f__equiv_typedef, &jl_f_get_binding_type, &jl_f_set_binding_type, &jl_f_opaque_closure_call, &jl_f_donotdelete, + &jl_f_getglobal, &jl_f_setglobal, NULL }; typedef struct { diff --git a/src/toplevel.c b/src/toplevel.c index 1f60a1b57c19c..b9b2bb7535707 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -283,7 +283,7 @@ static jl_value_t *jl_eval_dot_expr(jl_module_t *m, jl_value_t *x, jl_value_t *f args[1] = jl_toplevel_eval_flex(m, x, fast, 0); args[2] = jl_toplevel_eval_flex(m, f, fast, 0); if (jl_is_module(args[1])) { - JL_TYPECHK(getfield, symbol, args[2]); + JL_TYPECHK(getglobal, symbol, args[2]); args[0] = jl_eval_global_var((jl_module_t*)args[1], (jl_sym_t*)args[2]); } else { diff --git a/stdlib/Serialization/src/Serialization.jl b/stdlib/Serialization/src/Serialization.jl index 1ed98071d05e2..0f69e686bb473 100644 --- a/stdlib/Serialization/src/Serialization.jl +++ b/stdlib/Serialization/src/Serialization.jl @@ -481,7 +481,7 @@ function serialize(s::AbstractSerializer, g::GlobalRef) if (g.mod === __deserialized_types__ ) || (g.mod === Main && isdefined(g.mod, g.name) && isconst(g.mod, g.name)) - v = getfield(g.mod, g.name) + v = getglobal(g.mod, g.name) unw = unwrap_unionall(v) if isa(unw,DataType) && v === unw.name.wrapper && should_send_whole_type(s, unw) # handle references to types in Main by sending the whole type. @@ -541,7 +541,7 @@ function should_send_whole_type(s, t::DataType) isanonfunction = mod === Main && # only Main t.super === Function && # only Functions unsafe_load(unsafe_convert(Ptr{UInt8}, tn.name)) == UInt8('#') && # hidden type - (!isdefined(mod, name) || t != typeof(getfield(mod, name))) # XXX: 95% accurate test for this being an inner function + (!isdefined(mod, name) || t != typeof(getglobal(mod, name))) # XXX: 95% accurate test for this being an inner function # TODO: more accurate test? (tn.name !== "#" name) #TODO: iskw = startswith(tn.name, "#kw#") && ??? #TODO: iskw && return send-as-kwftype @@ -986,7 +986,7 @@ function deserialize_module(s::AbstractSerializer) end m = Base.root_module(mkey[1]) for i = 2:length(mkey) - m = getfield(m, mkey[i])::Module + m = getglobal(m, mkey[i])::Module end else name = String(deserialize(s)::Symbol) @@ -994,7 +994,7 @@ function deserialize_module(s::AbstractSerializer) m = Base.root_module(pkg) mname = deserialize(s) while mname !== () - m = getfield(m, mname)::Module + m = getglobal(m, mname)::Module mname = deserialize(s) end end @@ -1364,7 +1364,7 @@ function deserialize_datatype(s::AbstractSerializer, full::Bool) else name = deserialize(s)::Symbol mod = deserialize(s)::Module - ty = getfield(mod,name) + ty = getglobal(mod, name) end if isa(ty,DataType) && isempty(ty.parameters) t = ty diff --git a/test/core.jl b/test/core.jl index 93ba97df60420..270a1cb4964b1 100644 --- a/test/core.jl +++ b/test/core.jl @@ -7710,3 +7710,23 @@ end @test a == 1 @test b == Core.svec(2, 3) end + +@testset "setproperty! on modules" begin + m = Module() + @eval m global x::Int + + setglobal!(m, :x, 1) + @test m.x === 1 + setglobal!(m, :x, 2, :release) + @test m.x === 2 + @test_throws ConcurrencyViolationError setglobal!(m, :x, 3, :not_atomic) + @test_throws ErrorException setglobal!(m, :x, 4., :release) + + m.x = 1 + @test m.x === 1 + setproperty!(m, :x, 2, :release) + @test m.x === 2 + @test_throws ConcurrencyViolationError setproperty!(m, :x, 3, :not_atomic) + m.x = 4. + @test m.x === 4 +end diff --git a/test/misc.jl b/test/misc.jl index efc647667f4b6..6405c9ae5251d 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -399,16 +399,6 @@ let s = Set(1:100) @test summarysize([s]) > summarysize(s) end -# issue #13021 -let ex = try - Main.x13021 = 0 - nothing -catch ex - ex -end - @test isa(ex, ErrorException) && ex.msg == "cannot assign variables in other modules" -end - ## test conversion from UTF-8 to UTF-16 (for Windows APIs) # empty arrays