Skip to content

Commit 7b1e454

Browse files
support type annotations on global variables (#43671)
add type declarations to global bindings replaces #43455 closes #964 Co-authored-by: Miguel Raz Guzmán Macedo <[email protected]>
1 parent 942697f commit 7b1e454

25 files changed

+364
-67
lines changed

NEWS.md

+5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ New language features
2020
them after construction, providing for greater clarity and optimization
2121
ability of these objects ([#43305]).
2222
* Empty n-dimensional arrays can now be created using multiple semicolons inside square brackets, i.e. `[;;;]` creates a 0×0×0 `Array`. ([#41618])
23+
* Type annotations can now be added to global variables to make accessing the variable type stable. ([#43671])
2324

2425
Language changes
2526
----------------
@@ -35,6 +36,10 @@ Language changes
3536
to mitigate the ["trojan source"](https://www.trojansource.codes) vulnerability ([#42918]).
3637
* `Base.ifelse` is now defined as a generic function rather than a builtin one, allowing packages to
3738
extend its definition ([#37343]).
39+
* Every assignment to a global variable now first goes through a call to `convert(Any, x)` (or `convert(T, x)`
40+
respectively if a type `T` has already been declared for the global). This means great care should be taken
41+
to ensure the invariant `convert(Any, x) === x` always holds, as this change could otherwise lead to
42+
unexpected behavior. ([#43671])
3843

3944
Compiler/Runtime improvements
4045
-----------------------------

base/Base.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ modifyproperty!(x, f::Symbol, op, v, order::Symbol=:notatomic) =
5757
replaceproperty!(x, f::Symbol, expected, desired, success_order::Symbol=:notatomic, fail_order::Symbol=success_order) =
5858
(@inline; Core.replacefield!(x, f, expected, convert(fieldtype(typeof(x), f), desired), success_order, fail_order))
5959

60-
60+
convert(::Type{Any}, Core.@nospecialize x) = x
6161
include("coreio.jl")
6262

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

base/compiler/abstractinterpretation.jl

+3-1
Original file line numberDiff line numberDiff line change
@@ -1793,7 +1793,9 @@ function abstract_eval_global(M::Module, s::Symbol)
17931793
if isdefined(M,s) && isconst(M,s)
17941794
return Const(getfield(M,s))
17951795
end
1796-
return Any
1796+
ty = ccall(:jl_binding_type, Any, (Any, Any), M, s)
1797+
ty === nothing && return Any
1798+
return ty
17971799
end
17981800

17991801
function abstract_eval_ssavalue(s::SSAValue, src::CodeInfo)

base/compiler/compiler.jl

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ include(mod, x) = Core.include(mod, x)
2828
macro inline() Expr(:meta, :inline) end
2929
macro noinline() Expr(:meta, :noinline) end
3030

31+
convert(::Type{Any}, Core.@nospecialize x) = x
32+
3133
# essential files and libraries
3234
include("essentials.jl")
3335
include("ctypes.jl")

base/compiler/optimize.jl

+7-1
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ const _PURE_BUILTINS = Any[tuple, svec, ===, typeof, nfields]
156156
const _PURE_OR_ERROR_BUILTINS = [
157157
fieldtype, apply_type, isa, UnionAll,
158158
getfield, arrayref, const_arrayref, arraysize, isdefined, Core.sizeof,
159-
Core.kwfunc, Core.ifelse, Core._typevar, (<:)
159+
Core.kwfunc, Core.ifelse, Core._typevar, (<:),
160160
]
161161

162162
const TOP_TUPLE = GlobalRef(Core, :tuple)
@@ -219,6 +219,12 @@ function stmt_effect_free(@nospecialize(stmt), @nospecialize(rt), src::Union{IRC
219219
Any[argextype(args[i], src) for i = 2:length(args)])
220220
end
221221
contains_is(_PURE_BUILTINS, f) && return true
222+
# `get_binding_type` sets the type to Any if the binding doesn't exist yet
223+
if f === Core.get_binding_type
224+
length(args) == 3 || return false
225+
M, s = argextype(args[2], src), argextype(args[3], src)
226+
return get_binding_type_effect_free(M, s)
227+
end
222228
contains_is(_PURE_OR_ERROR_BUILTINS, f) || return false
223229
rt === Bottom && return false
224230
return _builtin_nothrow(f, Any[argextype(args[i], src) for i = 2:length(args)], rt)

base/compiler/ssair/inlining.jl

+5
Original file line numberDiff line numberDiff line change
@@ -1404,6 +1404,11 @@ function early_inline_special_case(
14041404
if _builtin_nothrow(f, argtypes[2:end], type)
14051405
return SomeCase(quoted(val))
14061406
end
1407+
elseif f === Core.get_binding_type
1408+
length(argtypes) == 3 || return nothing
1409+
if get_binding_type_effect_free(argtypes[2], argtypes[3])
1410+
return SomeCase(quoted(val))
1411+
end
14071412
end
14081413
end
14091414
return nothing

base/compiler/tfuncs.jl

+18
Original file line numberDiff line numberDiff line change
@@ -1695,6 +1695,8 @@ function _builtin_nothrow(@nospecialize(f), argtypes::Array{Any,1}, @nospecializ
16951695
return true
16961696
end
16971697
return false
1698+
elseif f === Core.get_binding_type
1699+
return length(argtypes) == 2
16981700
end
16991701
return false
17001702
end
@@ -1898,4 +1900,20 @@ function typename_static(@nospecialize(t))
18981900
return isType(t) ? _typename(t.parameters[1]) : Core.TypeName
18991901
end
19001902

1903+
function get_binding_type_effect_free(@nospecialize(M), @nospecialize(s))
1904+
if M isa Const && widenconst(M) === Module &&
1905+
s isa Const && widenconst(s) === Symbol
1906+
return ccall(:jl_binding_type, Any, (Any, Any), M.val, s.val) !== nothing
1907+
end
1908+
return false
1909+
end
1910+
function get_binding_type_tfunc(@nospecialize(M), @nospecialize(s))
1911+
if get_binding_type_effect_free(M, s)
1912+
@assert M isa Const && s isa Const
1913+
return Const(Core.get_binding_type(M.val, s.val))
1914+
end
1915+
return Type
1916+
end
1917+
add_tfunc(Core.get_binding_type, 2, 2, get_binding_type_tfunc, 0)
1918+
19011919
@specialize

base/essentials.jl

-1
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,6 @@ See also: [`round`](@ref), [`trunc`](@ref), [`oftype`](@ref), [`reinterpret`](@r
211211
function convert end
212212

213213
convert(::Type{Union{}}, @nospecialize x) = throw(MethodError(convert, (Union{}, x)))
214-
convert(::Type{Any}, @nospecialize x) = x
215214
convert(::Type{T}, x::T) where {T} = x
216215
convert(::Type{Type}, x::Type) = x # the ssair optimizer is strongly dependent on this method existing to avoid over-specialization
217216
# in the absence of inlining-enabled

doc/src/manual/performance-tips.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ The use of functions is not only important for performance: functions are more r
1111

1212
The functions should take arguments, instead of operating directly on global variables, see the next point.
1313

14-
## Avoid global variables
14+
## Avoid untyped global variables
1515

16-
A global variable might have its value, and therefore possibly its type, changed at any point. This makes
16+
An untyped global variable might have its value, and therefore possibly its type, changed at any point. This makes
1717
it difficult for the compiler to optimize code using global variables. This also applies to type-valued variables,
1818
i.e. type aliases on the global level. Variables should be local, or passed as arguments to functions, whenever possible.
1919

@@ -24,7 +24,9 @@ performance:
2424
const DEFAULT_VAL = 0
2525
```
2626

27-
Uses of non-constant globals can be optimized by annotating their types at the point of use:
27+
If a global is known to always be of the same type, [the type should be annotated](@ref man-typed-globals).
28+
29+
Uses of untyped globals can be optimized by annotating their types at the point of use:
2830

2931
```julia
3032
global x = rand(1000)

doc/src/manual/variables-and-scoping.md

+55
Original file line numberDiff line numberDiff line change
@@ -799,3 +799,58 @@ WARNING: redefinition of constant x. This may fail, cause incorrect answers, or
799799
julia> f()
800800
1
801801
```
802+
803+
## [Typed Globals](@id man-typed-globals)
804+
805+
!!! compat "Julia 1.8"
806+
Support for typed globals was added in Julia 1.8
807+
808+
Similar to being declared as constants, global bindings can also be declared to always be of a
809+
constant type. This can either be done without assigning an actual value using the syntax
810+
`global x::T` or upon assignment as `x::T = 123`.
811+
812+
```jldoctest
813+
julia> x::Float64 = 2.718
814+
2.718
815+
816+
julia> f() = x
817+
f (generic function with 1 method)
818+
819+
julia> Base.return_types(f)
820+
1-element Vector{Any}:
821+
Float64
822+
```
823+
824+
For any assignment to a global, Julia will first try to convert it to the appropriate type using
825+
[`convert`](@ref):
826+
827+
```jldoctest
828+
julia> global y::Int
829+
830+
julia> y = 1.0
831+
1.0
832+
833+
julia> y
834+
1
835+
836+
julia> y = 3.14
837+
ERROR: InexactError: Int64(3.14)
838+
Stacktrace:
839+
[...]
840+
```
841+
842+
The type does not need to be concrete, but annotations with abstract types typically have little
843+
performance benefit.
844+
845+
Once a global has either been assigned to or its type has been set, the binding type is not allowed
846+
to change:
847+
848+
```jldoctest
849+
julia> x = 1
850+
1
851+
852+
julia> global x::Int
853+
ERROR: cannot set type for global x. It already has a value or is already set to a different type.
854+
Stacktrace:
855+
[...]
856+
```

src/builtin_proto.h

+2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ JL_CALLABLE(jl_f__abstracttype);
6565
JL_CALLABLE(jl_f__primitivetype);
6666
JL_CALLABLE(jl_f__setsuper);
6767
JL_CALLABLE(jl_f__equiv_typedef);
68+
JL_CALLABLE(jl_f_get_binding_type);
69+
JL_CALLABLE(jl_f_set_binding_type);
6870

6971
#ifdef __cplusplus
7072
}

src/builtins.c

+40
Original file line numberDiff line numberDiff line change
@@ -1655,6 +1655,44 @@ JL_CALLABLE(jl_f__equiv_typedef)
16551655
return equiv_type(args[0], args[1]) ? jl_true : jl_false;
16561656
}
16571657

1658+
JL_CALLABLE(jl_f_get_binding_type)
1659+
{
1660+
JL_NARGS(get_binding_type, 2, 2);
1661+
JL_TYPECHK(get_binding_type, module, args[0]);
1662+
JL_TYPECHK(get_binding_type, symbol, args[1]);
1663+
jl_module_t *mod = (jl_module_t*)args[0];
1664+
jl_sym_t *sym = (jl_sym_t*)args[1];
1665+
jl_value_t *ty = jl_binding_type(mod, sym);
1666+
if (ty == (jl_value_t*)jl_nothing) {
1667+
jl_binding_t *b = jl_get_binding_wr(mod, sym, 0);
1668+
if (b) {
1669+
jl_value_t *old_ty = NULL;
1670+
jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type);
1671+
return jl_atomic_load_relaxed(&b->ty);
1672+
}
1673+
return (jl_value_t*)jl_any_type;
1674+
}
1675+
return ty;
1676+
}
1677+
1678+
JL_CALLABLE(jl_f_set_binding_type)
1679+
{
1680+
JL_NARGS(set_binding_type!, 2, 3);
1681+
JL_TYPECHK(set_binding_type!, module, args[0]);
1682+
JL_TYPECHK(set_binding_type!, symbol, args[1]);
1683+
jl_value_t *ty = nargs == 2 ? (jl_value_t*)jl_any_type : args[2];
1684+
JL_TYPECHK(set_binding_type!, type, ty);
1685+
jl_binding_t *b = jl_get_binding_wr((jl_module_t*)args[0], (jl_sym_t*)args[1], 1);
1686+
jl_value_t *old_ty = NULL;
1687+
if (!jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, ty) && ty != old_ty) {
1688+
if (nargs == 2)
1689+
return jl_nothing;
1690+
jl_errorf("cannot set type for global %s. It already has a value or is already set to a different type.",
1691+
jl_symbol_name(b->name));
1692+
}
1693+
return jl_nothing;
1694+
}
1695+
16581696
// IntrinsicFunctions ---------------------------------------------------------
16591697

16601698
static void (*runtime_fp[num_intrinsics])(void);
@@ -1834,6 +1872,8 @@ void jl_init_primitives(void) JL_GC_DISABLED
18341872
add_builtin_func("_setsuper!", jl_f__setsuper);
18351873
jl_builtin__typebody = add_builtin_func("_typebody!", jl_f__typebody);
18361874
add_builtin_func("_equiv_typedef", jl_f__equiv_typedef);
1875+
add_builtin_func("get_binding_type", jl_f_get_binding_type);
1876+
add_builtin_func("set_binding_type!", jl_f_set_binding_type);
18371877

18381878
// builtin types
18391879
add_builtin("Any", (jl_value_t*)jl_any_type);

src/codegen.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -2385,7 +2385,7 @@ static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *
23852385
LoadInst *v = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*)));
23862386
v->setOrdering(AtomicOrdering::Unordered);
23872387
tbaa_decorate(ctx.tbaa().tbaa_binding, v);
2388-
return mark_julia_type(ctx, v, true, (jl_value_t*)jl_any_type);
2388+
return mark_julia_type(ctx, v, true, bnd->ty);
23892389
}
23902390
// todo: use type info to avoid undef check
23912391
return emit_checked_var(ctx, bp, name, false, ctx.tbaa().tbaa_binding);

src/dump.c

+3
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ static void jl_serialize_module(jl_serializer_state *s, jl_module_t *m)
387387
jl_serialize_value(s, e);
388388
jl_serialize_value(s, jl_atomic_load_relaxed(&b->globalref));
389389
jl_serialize_value(s, b->owner);
390+
jl_serialize_value(s, jl_atomic_load_relaxed(&b->ty));
390391
write_int8(s->s, (b->deprecated<<3) | (b->constp<<2) | (b->exportp<<1) | (b->imported));
391392
}
392393
}
@@ -1708,6 +1709,8 @@ static jl_value_t *jl_deserialize_value_module(jl_serializer_state *s) JL_GC_DIS
17081709
if (bglobalref != NULL) jl_gc_wb(m, bglobalref);
17091710
b->owner = (jl_module_t*)jl_deserialize_value(s, (jl_value_t**)&b->owner);
17101711
if (b->owner != NULL) jl_gc_wb(m, b->owner);
1712+
jl_value_t *bty = jl_deserialize_value(s, (jl_value_t**)&b->ty);
1713+
*(jl_value_t**)&b->ty = bty;
17111714
int8_t flags = read_int8(s->s);
17121715
b->deprecated = (flags>>3) & 1;
17131716
b->constp = (flags>>2) & 1;

0 commit comments

Comments
 (0)