diff --git a/NEWS.md b/NEWS.md index 995f0ab5823e4..d5406a8661875 100644 --- a/NEWS.md +++ b/NEWS.md @@ -20,6 +20,7 @@ New language features them after construction, providing for greater clarity and optimization ability of these objects ([#43305]). * Empty n-dimensional arrays can now be created using multiple semicolons inside square brackets, i.e. `[;;;]` creates a 0×0×0 `Array`. ([#41618]) +* Type annotations can now be added to global variables to make accessing the variable type stable. ([#43671]) Language changes ---------------- @@ -35,6 +36,10 @@ Language changes to mitigate the ["trojan source"](https://www.trojansource.codes) vulnerability ([#42918]). * `Base.ifelse` is now defined as a generic function rather than a builtin one, allowing packages to extend its definition ([#37343]). +* Every assignment to a global variable now first goes through a call to `convert(Any, x)` (or `convert(T, x)` + respectively if a type `T` has already been declared for the global). This means great care should be taken + to ensure the invariant `convert(Any, x) === x` always holds, as this change could otherwise lead to + unexpected behavior. ([#43671]) Compiler/Runtime improvements ----------------------------- diff --git a/base/Base.jl b/base/Base.jl index 8fd0780133818..ece7cbe96df53 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -57,7 +57,7 @@ modifyproperty!(x, f::Symbol, op, v, order::Symbol=:notatomic) = replaceproperty!(x, f::Symbol, expected, desired, success_order::Symbol=:notatomic, fail_order::Symbol=success_order) = (@inline; Core.replacefield!(x, f, expected, convert(fieldtype(typeof(x), f), desired), success_order, fail_order)) - +convert(::Type{Any}, Core.@nospecialize x) = x include("coreio.jl") eval(x) = Core.eval(Base, x) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index eb0ff4be030ca..9a01f5aa80d59 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1793,7 +1793,9 @@ function abstract_eval_global(M::Module, s::Symbol) if isdefined(M,s) && isconst(M,s) return Const(getfield(M,s)) end - return Any + ty = ccall(:jl_binding_type, Any, (Any, Any), M, s) + ty === nothing && return Any + return ty end function abstract_eval_ssavalue(s::SSAValue, src::CodeInfo) diff --git a/base/compiler/compiler.jl b/base/compiler/compiler.jl index 5f2f5614ba209..41e045773fb06 100644 --- a/base/compiler/compiler.jl +++ b/base/compiler/compiler.jl @@ -28,6 +28,8 @@ include(mod, x) = Core.include(mod, x) macro inline() Expr(:meta, :inline) end macro noinline() Expr(:meta, :noinline) end +convert(::Type{Any}, Core.@nospecialize x) = x + # essential files and libraries include("essentials.jl") include("ctypes.jl") diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index b136a857d507a..3c00b9faa6d0a 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -156,7 +156,7 @@ const _PURE_BUILTINS = Any[tuple, svec, ===, typeof, nfields] const _PURE_OR_ERROR_BUILTINS = [ fieldtype, apply_type, isa, UnionAll, getfield, arrayref, const_arrayref, arraysize, isdefined, Core.sizeof, - Core.kwfunc, Core.ifelse, Core._typevar, (<:) + Core.kwfunc, Core.ifelse, Core._typevar, (<:), ] const TOP_TUPLE = GlobalRef(Core, :tuple) @@ -219,6 +219,12 @@ function stmt_effect_free(@nospecialize(stmt), @nospecialize(rt), src::Union{IRC Any[argextype(args[i], src) for i = 2:length(args)]) end contains_is(_PURE_BUILTINS, f) && return true + # `get_binding_type` sets the type to Any if the binding doesn't exist yet + if f === Core.get_binding_type + length(args) == 3 || return false + M, s = argextype(args[2], src), argextype(args[3], src) + return get_binding_type_effect_free(M, s) + end contains_is(_PURE_OR_ERROR_BUILTINS, f) || return false rt === Bottom && return false return _builtin_nothrow(f, Any[argextype(args[i], src) for i = 2:length(args)], rt) diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 9b2214b769045..7bb583ffd8bbc 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -1404,6 +1404,11 @@ function early_inline_special_case( if _builtin_nothrow(f, argtypes[2:end], type) return SomeCase(quoted(val)) end + elseif f === Core.get_binding_type + length(argtypes) == 3 || return nothing + if get_binding_type_effect_free(argtypes[2], argtypes[3]) + return SomeCase(quoted(val)) + end end end return nothing diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 177f33bd227f8..d1df40f0471b0 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -1695,6 +1695,8 @@ function _builtin_nothrow(@nospecialize(f), argtypes::Array{Any,1}, @nospecializ return true end return false + elseif f === Core.get_binding_type + return length(argtypes) == 2 end return false end @@ -1898,4 +1900,20 @@ function typename_static(@nospecialize(t)) return isType(t) ? _typename(t.parameters[1]) : Core.TypeName end +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 + 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)) + end + return Type +end +add_tfunc(Core.get_binding_type, 2, 2, get_binding_type_tfunc, 0) + @specialize diff --git a/base/essentials.jl b/base/essentials.jl index dd410b06cc8d9..1e4fea20bb4ae 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -211,7 +211,6 @@ See also: [`round`](@ref), [`trunc`](@ref), [`oftype`](@ref), [`reinterpret`](@r function convert end convert(::Type{Union{}}, @nospecialize x) = throw(MethodError(convert, (Union{}, x))) -convert(::Type{Any}, @nospecialize x) = x convert(::Type{T}, x::T) where {T} = x convert(::Type{Type}, x::Type) = x # the ssair optimizer is strongly dependent on this method existing to avoid over-specialization # in the absence of inlining-enabled diff --git a/doc/src/manual/performance-tips.md b/doc/src/manual/performance-tips.md index 287e8247b6921..3dd09b207ddda 100644 --- a/doc/src/manual/performance-tips.md +++ b/doc/src/manual/performance-tips.md @@ -11,9 +11,9 @@ The use of functions is not only important for performance: functions are more r The functions should take arguments, instead of operating directly on global variables, see the next point. -## Avoid global variables +## Avoid untyped global variables -A global variable might have its value, and therefore possibly its type, changed at any point. This makes +An untyped global variable might have its value, and therefore possibly its type, changed at any point. This makes it difficult for the compiler to optimize code using global variables. This also applies to type-valued variables, i.e. type aliases on the global level. Variables should be local, or passed as arguments to functions, whenever possible. @@ -24,7 +24,9 @@ performance: const DEFAULT_VAL = 0 ``` -Uses of non-constant globals can be optimized by annotating their types at the point of use: +If a global is known to always be of the same type, [the type should be annotated](@ref man-typed-globals). + +Uses of untyped globals can be optimized by annotating their types at the point of use: ```julia global x = rand(1000) diff --git a/doc/src/manual/variables-and-scoping.md b/doc/src/manual/variables-and-scoping.md index eee509f207525..6e94037f3e564 100644 --- a/doc/src/manual/variables-and-scoping.md +++ b/doc/src/manual/variables-and-scoping.md @@ -799,3 +799,58 @@ WARNING: redefinition of constant x. This may fail, cause incorrect answers, or julia> f() 1 ``` + +## [Typed Globals](@id man-typed-globals) + +!!! compat "Julia 1.8" + Support for typed globals was added in Julia 1.8 + +Similar to being declared as constants, global bindings can also be declared to always be of a +constant type. This can either be done without assigning an actual value using the syntax +`global x::T` or upon assignment as `x::T = 123`. + +```jldoctest +julia> x::Float64 = 2.718 +2.718 + +julia> f() = x +f (generic function with 1 method) + +julia> Base.return_types(f) +1-element Vector{Any}: + Float64 +``` + +For any assignment to a global, Julia will first try to convert it to the appropriate type using +[`convert`](@ref): + +```jldoctest +julia> global y::Int + +julia> y = 1.0 +1.0 + +julia> y +1 + +julia> y = 3.14 +ERROR: InexactError: Int64(3.14) +Stacktrace: +[...] +``` + +The type does not need to be concrete, but annotations with abstract types typically have little +performance benefit. + +Once a global has either been assigned to or its type has been set, the binding type is not allowed +to change: + +```jldoctest +julia> x = 1 +1 + +julia> global x::Int +ERROR: cannot set type for global x. It already has a value or is already set to a different type. +Stacktrace: +[...] +``` diff --git a/src/builtin_proto.h b/src/builtin_proto.h index e0b328e664d6c..bc01c078de602 100644 --- a/src/builtin_proto.h +++ b/src/builtin_proto.h @@ -65,6 +65,8 @@ JL_CALLABLE(jl_f__abstracttype); JL_CALLABLE(jl_f__primitivetype); JL_CALLABLE(jl_f__setsuper); JL_CALLABLE(jl_f__equiv_typedef); +JL_CALLABLE(jl_f_get_binding_type); +JL_CALLABLE(jl_f_set_binding_type); #ifdef __cplusplus } diff --git a/src/builtins.c b/src/builtins.c index b5368ad36a164..1fd26fd8ffbc4 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1655,6 +1655,44 @@ 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); @@ -1834,6 +1872,8 @@ 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); // builtin types add_builtin("Any", (jl_value_t*)jl_any_type); diff --git a/src/codegen.cpp b/src/codegen.cpp index 2580f693356ea..596693f3830f5 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2385,7 +2385,7 @@ static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t * LoadInst *v = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*))); v->setOrdering(AtomicOrdering::Unordered); tbaa_decorate(ctx.tbaa().tbaa_binding, v); - return mark_julia_type(ctx, v, true, (jl_value_t*)jl_any_type); + return mark_julia_type(ctx, v, true, bnd->ty); } // todo: use type info to avoid undef check return emit_checked_var(ctx, bp, name, false, ctx.tbaa().tbaa_binding); diff --git a/src/dump.c b/src/dump.c index dc551580a59aa..4a448bcb23376 100644 --- a/src/dump.c +++ b/src/dump.c @@ -387,6 +387,7 @@ static void jl_serialize_module(jl_serializer_state *s, jl_module_t *m) jl_serialize_value(s, e); jl_serialize_value(s, jl_atomic_load_relaxed(&b->globalref)); jl_serialize_value(s, b->owner); + jl_serialize_value(s, jl_atomic_load_relaxed(&b->ty)); write_int8(s->s, (b->deprecated<<3) | (b->constp<<2) | (b->exportp<<1) | (b->imported)); } } @@ -1708,6 +1709,8 @@ static jl_value_t *jl_deserialize_value_module(jl_serializer_state *s) JL_GC_DIS if (bglobalref != NULL) jl_gc_wb(m, bglobalref); b->owner = (jl_module_t*)jl_deserialize_value(s, (jl_value_t**)&b->owner); if (b->owner != NULL) jl_gc_wb(m, b->owner); + jl_value_t *bty = jl_deserialize_value(s, (jl_value_t**)&b->ty); + *(jl_value_t**)&b->ty = bty; int8_t flags = read_int8(s->s); b->deprecated = (flags>>3) & 1; b->constp = (flags>>2) & 1; diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 4a0a871c93e1f..07753233e204e 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -258,6 +258,12 @@ (or (overlay? e) (sym-ref? e))) +(define (binding-to-globalref e) + (and (nodot-sym-ref? e) + (let ((mod (if (globalref? e) (cadr e) '(thismodule))) + (sym (if (symbol? e) e (last e)))) + `(globalref ,mod ,sym)))) + ;; convert final (... x) to (curly Vararg x) (define (dots->vararg a) (if (null? a) a @@ -3380,12 +3386,29 @@ f(x) = yt(x) `(call (core getfield) ,fname ,(get opaq var)) `(call (core getfield) ,fname (inert ,var)))) +(define (convert-global-assignment var rhs0 globals) + (let* ((rhs1 (if (or (simple-atom? rhs0) + (equal? rhs0 '(the_exception))) + rhs0 + (make-ssavalue))) + (ref (binding-to-globalref var)) + (ty `(call (core get_binding_type) ,(cadr ref) (inert ,(caddr ref)))) + (rhs (if (get globals ref #t) ;; no type declaration for constants + (convert-for-type-decl rhs1 ty) + rhs1)) + (ex `(= ,var ,rhs))) + (if (eq? rhs1 rhs0) + `(block ,ex ,rhs0) + `(block (= ,rhs1 ,rhs0) + ,ex + ,rhs1)))) + ;; convert assignment to a closed variable to a setfield! call. ;; while we're at it, generate `convert` calls for variables with ;; declared types. ;; when doing this, the original value needs to be preserved, to ;; ensure the expression `a=b` always returns exactly `b`. -(define (convert-assignment var rhs0 fname lam interp opaq) +(define (convert-assignment var rhs0 fname lam interp opaq globals) (cond ((symbol? var) (let* ((vi (assq var (car (lam:vinfo lam)))) @@ -3396,7 +3419,9 @@ f(x) = yt(x) (closed (and cv (vinfo:asgn cv) (vinfo:capt cv))) (capt (and vi (vinfo:asgn vi) (vinfo:capt vi)))) (if (and (not closed) (not capt) (equal? vt '(core Any))) - `(= ,var ,rhs0) + (if (or (local-in? var lam) (underscore-symbol? var)) + `(= ,var ,rhs0) + (convert-global-assignment var rhs0 globals)) (let* ((rhs1 (if (or (simple-atom? rhs0) (equal? rhs0 '(the_exception))) rhs0 @@ -3417,10 +3442,8 @@ f(x) = yt(x) `(block (= ,rhs1 ,rhs0) ,ex ,rhs1)))))) - ((and (pair? var) (or (eq? (car var) 'outerref) - (eq? (car var) 'globalref))) - - `(= ,var ,rhs0)) + ((or (outerref? var) (globalref? var)) + (convert-global-assignment var rhs0 globals)) ((ssavalue? var) `(= ,var ,rhs0)) (else @@ -3680,17 +3703,17 @@ f(x) = yt(x) (define (toplevel-preserving? e) (and (pair? e) (memq (car e) '(if elseif block trycatch tryfinally trycatchelse)))) -(define (map-cl-convert exprs fname lam namemap defined toplevel interp opaq) +(define (map-cl-convert exprs fname lam namemap defined toplevel interp opaq (globals (table))) (if toplevel (map (lambda (x) (let ((tl (lift-toplevel (cl-convert x fname lam namemap defined (and toplevel (toplevel-preserving? x)) - interp opaq)))) + interp opaq globals)))) (if (null? (cdr tl)) (car tl) `(block ,@(cdr tl) ,(car tl))))) exprs) - (map (lambda (x) (cl-convert x fname lam namemap defined #f interp opaq)) exprs))) + (map (lambda (x) (cl-convert x fname lam namemap defined #f interp opaq globals)) exprs))) (define (prepare-lambda! lam) ;; mark all non-arguments as assigned, since locals that are never assigned @@ -3699,11 +3722,11 @@ f(x) = yt(x) (list-tail (car (lam:vinfo lam)) (length (lam:args lam)))) (lambda-optimize-vars! lam)) -(define (cl-convert e fname lam namemap defined toplevel interp opaq) +(define (cl-convert e fname lam namemap defined toplevel interp opaq (globals (table))) (if (and (not lam) (not (and (pair? e) (memq (car e) '(lambda method macro opaque_closure))))) (if (atom? e) e - (cons (car e) (map-cl-convert (cdr e) fname lam namemap defined toplevel interp opaq))) + (cons (car e) (map-cl-convert (cdr e) fname lam namemap defined toplevel interp opaq globals))) (cond ((symbol? e) (define (new-undef-var name) @@ -3722,7 +3745,7 @@ f(x) = yt(x) (val (if (equal? typ '(core Any)) val `(call (core typeassert) ,val - ,(cl-convert typ fname lam namemap defined toplevel interp opaq))))) + ,(cl-convert typ fname lam namemap defined toplevel interp opaq globals))))) `(block ,@(if (eq? box access) '() `((= ,access ,box))) ,undefcheck @@ -3754,8 +3777,8 @@ f(x) = yt(x) e) ((=) (let ((var (cadr e)) - (rhs (cl-convert (caddr e) fname lam namemap defined toplevel interp opaq))) - (convert-assignment var rhs fname lam interp opaq))) + (rhs (cl-convert (caddr e) fname lam namemap defined toplevel interp opaq globals))) + (convert-assignment var rhs fname lam interp opaq globals))) ((local-def) ;; make new Box for local declaration of defined variable (let ((vi (assq (cadr e) (car (lam:vinfo lam))))) (if (and vi (vinfo:asgn vi) (vinfo:capt vi)) @@ -3768,7 +3791,9 @@ f(x) = yt(x) (if (vinfo:never-undef vi) '(null) `(newvar ,(cadr e)))))) - ((const) e) + ((const) + (put! globals (binding-to-globalref (cadr e)) #f) + e) ((atomic) e) ((const-if-global) (if (local-in? (cadr e) lam) @@ -3821,7 +3846,7 @@ f(x) = yt(x) (sp-inits (if (or short (not (eq? (car sig) 'block))) '() (map-cl-convert (butlast (cdr sig)) - fname lam namemap defined toplevel interp opaq))) + fname lam namemap defined toplevel interp opaq globals))) (sig (and sig (if (eq? (car sig) 'block) (last sig) sig)))) @@ -3848,7 +3873,7 @@ f(x) = yt(x) ;; anonymous functions with keyword args generate global ;; functions that refer to the type of a local function (rename-sig-types sig namemap) - fname lam namemap defined toplevel interp opaq) + fname lam namemap defined toplevel interp opaq globals) ,(let ((body (add-box-inits-to-body lam2 (cl-convert (cadddr lam2) 'anon lam2 (table) (table) #f interp opaq)))) @@ -3862,7 +3887,7 @@ f(x) = yt(x) (newlam (compact-and-renumber (linearize (car exprs)) 'none 0))) `(toplevel-butfirst (block ,@sp-inits - (method ,name ,(cl-convert sig fname lam namemap defined toplevel interp opaq) + (method ,name ,(cl-convert sig fname lam namemap defined toplevel interp opaq globals) ,(julia-bq-macro newlam))) ,@top-stmts)))) @@ -3965,7 +3990,7 @@ f(x) = yt(x) (append (map (lambda (gs tvar) (make-assignment gs `(call (core TypeVar) ',tvar (core Any)))) closure-param-syms closure-param-names) - `((method #f ,(cl-convert arg-defs fname lam namemap defined toplevel interp opaq) + `((method #f ,(cl-convert arg-defs fname lam namemap defined toplevel interp opaq globals) ,(convert-lambda lam2 (if iskw (caddr (lam:args lam2)) @@ -4004,7 +4029,7 @@ f(x) = yt(x) (begin (put! defined name #t) `(toplevel-butfirst - ,(convert-assignment name mk-closure fname lam interp opaq) + ,(convert-assignment name mk-closure fname lam interp opaq globals) ,@typedef ,@(map (lambda (v) `(moved-local ,v)) moved-vars) ,@sp-inits @@ -4017,14 +4042,14 @@ f(x) = yt(x) (table) (table) (null? (cadr e)) ;; only toplevel thunks have 0 args - interp opaq))) + interp opaq globals))) `(lambda ,(cadr e) (,(clear-capture-bits (car (lam:vinfo e))) () ,@(cddr (lam:vinfo e))) (block ,@body)))) ;; remaining `::` expressions are type assertions ((|::|) - (cl-convert `(call (core typeassert) ,@(cdr e)) fname lam namemap defined toplevel interp opaq)) + (cl-convert `(call (core typeassert) ,@(cdr e)) fname lam namemap defined toplevel interp opaq globals)) ;; remaining `decl` expressions are only type assertions if the ;; argument is global or a non-symbol. ((decl) @@ -4032,15 +4057,22 @@ f(x) = yt(x) (local-in? (cadr e) lam)) '(null)) (else - (if (or (symbol? (cadr e)) (and (pair? (cadr e)) (eq? (caadr e) 'outerref))) - (error "type declarations on global variables are not yet supported")) - (cl-convert `(call (core typeassert) ,@(cdr e)) fname lam namemap defined toplevel interp opaq)))) + (cl-convert + (let ((ref (binding-to-globalref (cadr e)))) + (if ref + (begin + (put! globals ref #t) + `(block + (toplevel-only set_binding_type! ,(cadr e)) + (call (core set_binding_type!) ,(cadr ref) (inert ,(caddr ref)) ,(caddr e)))) + `(call (core typeassert) ,@(cdr e)))) + fname lam namemap defined toplevel interp opaq globals)))) ;; `with-static-parameters` expressions can be removed now; used only by analyze-vars ((with-static-parameters) - (cl-convert (cadr e) fname lam namemap defined toplevel interp opaq)) + (cl-convert (cadr e) fname lam namemap defined toplevel interp opaq globals)) (else (cons (car e) - (map-cl-convert (cdr e) fname lam namemap defined toplevel interp opaq)))))))) + (map-cl-convert (cdr e) fname lam namemap defined toplevel interp opaq globals)))))))) (define (closure-convert e) (cl-convert e #f #f #f #f #f #f #f)) @@ -4184,13 +4216,14 @@ f(x) = yt(x) (define (check-top-level e) (define (head-to-text h) (case h - ((abstract_type) "\"abstract type\"") - ((primitive_type) "\"primitive type\"") - ((struct_type) "\"struct\"") - ((method) "method definition") - (else (string "\"" h "\"")))) + ((abstract_type) "\"abstract type\" expression") + ((primitive_type) "\"primitive type\" expression") + ((struct_type) "\"struct\" expression") + ((method) "method definition") + ((set_binding_type!) (string "type declaration for global \"" (deparse (cadr e)) "\"")) + (else (string "\"" h "\" expression")))) (if (not (null? (cadr lam))) - (error (string (head-to-text (car e)) " expression not at top level")))) + (error (string (head-to-text (car e)) " not at top level")))) ;; evaluate the arguments of a call, creating temporary locations as needed (define (compile-args lst break-labels) (if (null? lst) '() diff --git a/src/julia.h b/src/julia.h index d60d3c20f9e60..ff0542307c1cf 100644 --- a/src/julia.h +++ b/src/julia.h @@ -509,6 +509,7 @@ typedef struct { _Atomic(jl_value_t*) value; _Atomic(jl_value_t*) globalref; // cached GlobalRef for this binding struct _jl_module_t* owner; // for individual imported bindings -- TODO: make _Atomic + _Atomic(jl_value_t*) ty; // binding type uint8_t constp:1; uint8_t exportp:1; uint8_t imported:1; @@ -1552,6 +1553,7 @@ JL_DLLEXPORT int jl_get_module_max_methods(jl_module_t *m); JL_DLLEXPORT jl_binding_t *jl_get_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); JL_DLLEXPORT jl_binding_t *jl_get_binding_or_error(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT jl_value_t *jl_module_globalref(jl_module_t *m, jl_sym_t *var); +JL_DLLEXPORT jl_value_t *jl_binding_type(jl_module_t *m, jl_sym_t *var); // get binding for assignment JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int error); JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); @@ -1562,7 +1564,7 @@ JL_DLLEXPORT int jl_is_const(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT jl_value_t *jl_get_global(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); JL_DLLEXPORT void jl_set_global(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT); JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT); -JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b JL_ROOTING_ARGUMENT, jl_value_t *rhs JL_ROOTED_ARGUMENT) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b JL_ROOTING_ARGUMENT, jl_value_t *rhs JL_ROOTED_ARGUMENT); JL_DLLEXPORT void jl_declare_constant(jl_binding_t *b); JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from); JL_DLLEXPORT void jl_module_use(jl_module_t *to, jl_module_t *from, jl_sym_t *s); diff --git a/src/julia_internal.h b/src/julia_internal.h index 67166cd635165..223e0210c1261 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -618,6 +618,7 @@ JL_DLLEXPORT int jl_is_submodule(jl_module_t *child, jl_module_t *parent) JL_NOT jl_array_t *jl_get_loaded_modules(void); JL_DLLEXPORT int jl_datatype_isinlinealloc(jl_datatype_t *ty, int pointerfree); +void jl_eval_global_expr(jl_module_t *m, jl_expr_t *ex, int set_type); jl_value_t *jl_toplevel_eval_flex(jl_module_t *m, jl_value_t *e, int fast, int expanded); jl_value_t *jl_eval_global_var(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *e); diff --git a/src/method.c b/src/method.c index d7de06c01592e..0b615e7e46dd5 100644 --- a/src/method.c +++ b/src/method.c @@ -76,7 +76,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve if (e->head == jl_global_sym && binding_effects) { // execute the side-effects of "global x" decl immediately: // creates uninitialized mutable binding in module for each global - jl_toplevel_eval_flex(module, expr, 0, 1); + jl_eval_global_expr(module, e, 1); expr = jl_nothing; } if (jl_is_toplevel_only_expr(expr) || e->head == jl_const_sym || diff --git a/src/module.c b/src/module.c index 8057727c00395..3de4cb91d5b3b 100644 --- a/src/module.c +++ b/src/module.c @@ -162,6 +162,7 @@ static jl_binding_t *new_binding(jl_sym_t *name) b->name = name; b->value = NULL; b->owner = NULL; + b->ty = NULL; b->globalref = NULL; b->constp = 0; b->exportp = 0; @@ -375,6 +376,20 @@ JL_DLLEXPORT jl_value_t *jl_binding_owner(jl_module_t *m, jl_sym_t *var) return (jl_value_t*)b->owner; } +// get type of binding m.var, without resolving the binding +JL_DLLEXPORT jl_value_t *jl_binding_type(jl_module_t *m, jl_sym_t *var) +{ + JL_LOCK(&m->lock); + jl_binding_t *b = (jl_binding_t*)ptrhash_get(&m->bindings, var); + if (b == HT_NOTFOUND || b->owner == NULL) + b = using_resolve_binding(m, var, NULL, 0); + JL_UNLOCK(&m->lock); + if (b == NULL) + return jl_nothing; + jl_value_t *ty = jl_atomic_load_relaxed(&b->ty); + return ty ? ty : jl_nothing; +} + JL_DLLEXPORT jl_binding_t *jl_get_binding(jl_module_t *m, jl_sym_t *var) { return jl_get_binding_(m, var, NULL); @@ -671,6 +686,8 @@ JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var return; } } + jl_value_t *old_ty = NULL; + jl_atomic_cmpswap_relaxed(&bp->ty, &old_ty, (jl_value_t*)jl_any_type); } jl_errorf("invalid redefinition of constant %s", jl_symbol_name(bp->name)); @@ -787,8 +804,13 @@ void jl_binding_deprecation_warning(jl_module_t *m, jl_binding_t *b) } } -JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_value_t *rhs) JL_NOTSAFEPOINT +JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_value_t *rhs) { + jl_value_t *old_ty = NULL; + if (!jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type) && !jl_isa(rhs, old_ty)) { + jl_errorf("cannot set type for global %s. It already has a value of a different type.", + jl_symbol_name(b->name)); + } if (b->constp) { jl_value_t *old = NULL; if (jl_atomic_cmpswap(&b->value, &old, rhs)) { diff --git a/src/staticdata.c b/src/staticdata.c index 7427e23d391aa..28fbdce09d2d3 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -251,7 +251,8 @@ static const jl_fptr_args_t id_to_fptrs[] = { &jl_f_arrayref, &jl_f_const_arrayref, &jl_f_arrayset, &jl_f_arraysize, &jl_f_apply_type, &jl_f_applicable, &jl_f_invoke, &jl_f_sizeof, &jl_f__expr, &jl_f__typevar, &jl_f_ifelse, &jl_f__structtype, &jl_f__abstracttype, &jl_f__primitivetype, - &jl_f__typebody, &jl_f__setsuper, &jl_f__equiv_typedef, &jl_f_opaque_closure_call, + &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, NULL }; typedef struct { @@ -412,6 +413,7 @@ static void jl_serialize_module(jl_serializer_state *s, jl_module_t *m) jl_serialize_value(s, jl_atomic_load_relaxed(&b->value)); jl_serialize_value(s, jl_atomic_load_relaxed(&b->globalref)); jl_serialize_value(s, b->owner); + jl_serialize_value(s, jl_atomic_load_relaxed(&b->ty)); } } @@ -701,7 +703,8 @@ static void jl_write_module(jl_serializer_state *s, uintptr_t item, jl_module_t write_pointerfield(s, jl_atomic_load_relaxed(&b->value)); write_pointerfield(s, jl_atomic_load_relaxed(&b->globalref)); write_pointerfield(s, (jl_value_t*)b->owner); - size_t flag_offset = offsetof(jl_binding_t, owner) + sizeof(b->owner); + write_pointerfield(s, jl_atomic_load_relaxed(&b->ty)); + size_t flag_offset = offsetof(jl_binding_t, ty) + sizeof(b->ty); ios_write(s->s, (char*)b + flag_offset, sizeof(*b) - flag_offset); tot += sizeof(jl_binding_t); count += 1; diff --git a/src/toplevel.c b/src/toplevel.c index 23216bb750399..1f60a1b57c19c 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -297,6 +297,30 @@ static jl_value_t *jl_eval_dot_expr(jl_module_t *m, jl_value_t *x, jl_value_t *f return args[0]; } +void jl_eval_global_expr(jl_module_t *m, jl_expr_t *ex, int set_type) { + // create uninitialized mutable binding for "global x" decl + size_t i, l = jl_array_len(ex->args); + for (i = 0; i < l; i++) { + jl_value_t *arg = jl_exprarg(ex, i); + jl_module_t *gm; + jl_sym_t *gs; + if (jl_is_globalref(arg)) { + gm = jl_globalref_mod(arg); + gs = jl_globalref_name(arg); + } + else { + assert(jl_is_symbol(arg)); + gm = m; + gs = (jl_sym_t*)arg; + } + jl_binding_t *b = jl_get_binding_wr(gm, gs, 0); + if (set_type && b) { + jl_value_t *old_ty = NULL; + jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type); + } + } +} + // module referenced by (top ...) from within m // this is only needed because of the bootstrapping process: // - initially Base doesn't exist and top === Core @@ -797,23 +821,7 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int return jl_nothing; } else if (head == jl_global_sym) { - // create uninitialized mutable binding for "global x" decl - size_t i, l = jl_array_len(ex->args); - for (i = 0; i < l; i++) { - jl_value_t *arg = jl_exprarg(ex, i); - jl_module_t *gm; - jl_sym_t *gs; - if (jl_is_globalref(arg)) { - gm = jl_globalref_mod(arg); - gs = jl_globalref_name(arg); - } - else { - assert(jl_is_symbol(arg)); - gm = m; - gs = (jl_sym_t*)arg; - } - jl_get_binding_wr(gm, gs, 0); - } + jl_eval_global_expr(m, ex, 0); JL_GC_POP(); return jl_nothing; } diff --git a/test/compiler/inline.jl b/test/compiler/inline.jl index 327eba160b8ff..f6bd13a0ad154 100644 --- a/test/compiler/inline.jl +++ b/test/compiler/inline.jl @@ -914,3 +914,17 @@ end f(a) = only(strides(a)); @test fully_eliminated(f, Tuple{typeof(a)}) && f(a) == 1 end + +@testset "elimination of `get_binding_type`" begin + m = Module() + @eval m begin + global x::Int + f() = Core.get_binding_type($m, :x) + g() = Core.get_binding_type($m, :y) + end + + @test fully_eliminated(m.f, Tuple{}, Int) + src = code_typed(m.g, ())[][1] + @test count(iscall((src, Core.get_binding_type)), src.code) == 1 + @test m.g() === Any +end diff --git a/test/core.jl b/test/core.jl index 391a13e3784f2..43d6da062560b 100644 --- a/test/core.jl +++ b/test/core.jl @@ -405,9 +405,6 @@ function typeassert_instead_of_decl() end @test_throws TypeError typeassert_instead_of_decl() -# type declarations on globals not implemented yet -@test_throws ErrorException eval(Meta.parse("global x20327::Int")) - y20327 = 1 @test_throws TypeError y20327::Float64 diff --git a/test/staged.jl b/test/staged.jl index 26e3dace1994b..b99ef46a2bc1e 100644 --- a/test/staged.jl +++ b/test/staged.jl @@ -303,4 +303,5 @@ end @generated function f33243() :(global x33243 = 2) end -@test_throws ErrorException f33243() +@test f33243() === 2 +@test x33243 === 2 diff --git a/test/syntax.jl b/test/syntax.jl index fc15d1f61b47a..69d3e8c7fe591 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -3171,3 +3171,80 @@ end @test x == 1 @test f == 2 end + +@testset "typed globals" begin + m = Module() + @eval m begin + x::Int = 1 + f(y) = x + y + end + @test Base.return_types(m.f, (Int,)) == [Int] + + m = Module() + @eval m begin + global x::Int + f(y) = x + y + end + @test Base.return_types(m.f, (Int,)) == [Int] + + m = Module() + @test_throws ErrorException @eval m begin + function f() + global x + x::Int = 1 + x = 2. + end + g() = x + end + + m = Module() + @test_throws ErrorException @eval m function f() + global x + x::Int = 1 + x::Float64 = 2. + end + + m = Module() + @test_throws ErrorException @eval m begin + x::Int = 1 + x::Float64 = 2 + end + + m = Module() + @test_throws ErrorException @eval m begin + x::Int = 1 + const x = 2 + end + + m = Module() + @test_throws ErrorException @eval m begin + const x = 1 + x::Int = 2 + end + + m = Module() + @test_throws ErrorException @eval m begin + x = 1 + global x::Float64 + end + + m = Module() + @test_throws ErrorException @eval m begin + x = 1 + global x::Int + end + + m = Module() + @eval m module Foo + export bar + bar = 1 + end + @eval m begin + using .Foo + bar::Float64 = 2 + end + @test m.bar === 2.0 + @test Core.get_binding_type(m, :bar) == Float64 + @test m.Foo.bar === 1 + @test Core.get_binding_type(m.Foo, :bar) == Any +end