Skip to content

Commit

Permalink
implement setproperty! for modules
Browse files Browse the repository at this point in the history
This replaces #44137. As discussed on triage, instead of supporting
modules in `setfield!`, this adds two new builtins `getglobal` and
`setglobal!` explicitly for reading and modifying module bindings. We
should probably consider `getfield(::Module, ::Symbol)` to be
soft-deprecated, but I don't think we want to add any warnings since
that will likely just annoy people.
  • Loading branch information
simeonschaub committed Feb 17, 2022
1 parent 1ad2396 commit 2370334
Show file tree
Hide file tree
Showing 13 changed files with 199 additions and 81 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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
----------------
Expand Down
11 changes: 7 additions & 4 deletions base/Base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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; Core.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))
Expand All @@ -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; Core.getglobal(x, f, order))
function setproperty!(x::Module, f::Symbol, v, order::Symbol=:monotonic)
@inline
val::Core.get_binding_type(x, f) = v
return Core.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))
Expand Down
65 changes: 65 additions & 0 deletions base/compiler/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -1704,6 +1735,8 @@ function _builtin_nothrow(@nospecialize(f), argtypes::Array{Any,1}, @nospecializ
return true
end
return false
elseif f === Core.getglobal
return getglobal_nothrow(argtypes)
elseif f === Core.get_binding_type
return length(argtypes) == 2
elseif f === donotdelete
Expand Down Expand Up @@ -2029,6 +2062,38 @@ 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(_=:monotonic))
if get_binding_type_effect_free(M, s)
@assert M isa Const && s isa Const
return Core.get_binding_type(M.val, s.val)
end
return Any
end
add_tfunc(Core.getglobal, 2, 3, getglobal_tfunc, 1)

function get_binding_type_effect_free(@nospecialize(M), @nospecialize(s))
if M isa Const && widenconst(M) === Module &&
s isa Const && widenconst(s) === Symbol
Expand Down
3 changes: 3 additions & 0 deletions base/docs/basedocs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
6 changes: 0 additions & 6 deletions doc/src/manual/variables-and-scoping.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down
4 changes: 2 additions & 2 deletions doc/src/manual/variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions src/builtin_proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ 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_getglobal);
JL_CALLABLE(jl_f_setglobal);

#ifdef __cplusplus
}
Expand Down
154 changes: 97 additions & 57 deletions src/builtins.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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];
Expand Down Expand Up @@ -1175,6 +1171,84 @@ 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]);
jl_value_t *v = jl_eval_global_var((jl_module_t*)args[0], (jl_sym_t*)args[1]);
if (order == jl_memory_order_notatomic)
jl_atomic_error("getglobal: module binding cannot be read non-atomically");
if (order >= jl_memory_order_acq_rel || order == jl_memory_order_acquire)
jl_fence(); // `v` already had at least consume ordering
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");
if (order >= jl_memory_order_acq_rel || order == jl_memory_order_release)
jl_fence();
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)
Expand Down Expand Up @@ -1697,44 +1771,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);
Expand Down Expand Up @@ -1884,6 +1920,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
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);
Expand Down Expand Up @@ -1915,8 +1957,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
Expand Down
2 changes: 1 addition & 1 deletion src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3872,7 +3872,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);
Expand Down
2 changes: 1 addition & 1 deletion src/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
Expand Down
1 change: 1 addition & 0 deletions src/staticdata.c
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,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 {
Expand Down
Loading

0 comments on commit 2370334

Please sign in to comment.