diff --git a/NEWS.md b/NEWS.md index 9e1a043406339..f0310f4cf37e3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -16,6 +16,9 @@ New language features * Support for Unicode 14.0.0 ([#43443]). * `try`-blocks can now optionally have an `else`-block which is executed right after the main body only if no errors were thrown. ([#42211]) +* Mutable struct fields may now be annotated as `const` to prevent changing + them after construction, providing for greater clarity and optimization + ability of these objects ([#43305]). Language changes ---------------- diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index 0a1badff94baf..7d33b37141fef 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -846,7 +846,7 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse if isa(typ, UnionAll) typ = unwrap_unionall(typ) end - # Could still end up here if we tried to setfield! and immutable, which would + # Could still end up here if we tried to setfield! on an immutable, which would # error at runtime, but is not illegal to have in the IR. ismutabletype(typ) || continue typ = typ::DataType @@ -871,6 +871,7 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse stmt = ir[SSAValue(def)]::Expr # == `setfield!` call field = try_compute_fieldidx_stmt(ir, stmt, typ) field === nothing && @goto skip + isconst(typ, field) && @goto skip # we discovered an attempt to mutate a const field, which must error push!(fielddefuse[field].defs, def) end # Check that the defexpr has defined values for all the fields diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 4444242bafc9c..583ed053a39d7 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -24,19 +24,7 @@ function find_tfunc(@nospecialize f) end end -const DATATYPE_NAME_FIELDINDEX = fieldindex(DataType, :name) -const DATATYPE_PARAMETERS_FIELDINDEX = fieldindex(DataType, :parameters) const DATATYPE_TYPES_FIELDINDEX = fieldindex(DataType, :types) -const DATATYPE_SUPER_FIELDINDEX = fieldindex(DataType, :super) -const DATATYPE_INSTANCE_FIELDINDEX = fieldindex(DataType, :instance) -const DATATYPE_HASH_FIELDINDEX = fieldindex(DataType, :hash) - -const TYPENAME_NAME_FIELDINDEX = fieldindex(Core.TypeName, :name) -const TYPENAME_MODULE_FIELDINDEX = fieldindex(Core.TypeName, :module) -const TYPENAME_NAMES_FIELDINDEX = fieldindex(Core.TypeName, :names) -const TYPENAME_WRAPPER_FIELDINDEX = fieldindex(Core.TypeName, :wrapper) -const TYPENAME_HASH_FIELDINDEX = fieldindex(Core.TypeName, :hash) -const TYPENAME_FLAGS_FIELDINDEX = fieldindex(Core.TypeName, :flags) ########## # tfuncs # @@ -305,7 +293,7 @@ function isdefined_tfunc(@nospecialize(arg1), @nospecialize(sym)) return Const(false) elseif isa(arg1, Const) arg1v = (arg1::Const).val - if !ismutable(arg1v) || isdefined(arg1v, idx) || (isa(arg1v, DataType) && is_dt_const_field(idx)) + if !ismutable(arg1v) || isdefined(arg1v, idx) || isconst(typeof(arg1v), idx) return Const(isdefined(arg1v, idx)) end elseif !isvatuple(a1) @@ -646,23 +634,6 @@ function subtype_tfunc(@nospecialize(a), @nospecialize(b)) end add_tfunc(<:, 2, 2, subtype_tfunc, 10) -is_dt_const_field(fld::Int) = ( - fld == DATATYPE_NAME_FIELDINDEX || - fld == DATATYPE_PARAMETERS_FIELDINDEX || - fld == DATATYPE_TYPES_FIELDINDEX || - fld == DATATYPE_SUPER_FIELDINDEX || - fld == DATATYPE_INSTANCE_FIELDINDEX || - fld == DATATYPE_HASH_FIELDINDEX - ) -function const_datatype_getfield_tfunc(@nospecialize(sv), fld::Int) - if fld == DATATYPE_INSTANCE_FIELDINDEX - return isdefined(sv, fld) ? Const(getfield(sv, fld)) : Union{} - elseif is_dt_const_field(fld) && isdefined(sv, fld) - return Const(getfield(sv, fld)) - end - return nothing -end - function fieldcount_noerror(@nospecialize t) if t isa UnionAll || t isa Union t = argument_datatype(t) @@ -801,41 +772,27 @@ function getfield_tfunc(@nospecialize(s00), @nospecialize(name)) end if isa(name, Const) nv = name.val - if !(isa(nv,Symbol) || isa(nv,Int)) + if isa(sv, Module) + if isa(nv, Symbol) + return abstract_eval_global(sv, nv) + end return Bottom end - if isa(sv, UnionAll) - if nv === :var || nv === 1 - return Const(sv.var) - elseif nv === :body || nv === 2 - return Const(sv.body) - end - elseif isa(sv, DataType) - idx = nv - if isa(idx, Symbol) - idx = fieldindex(DataType, idx, false) - end - if isa(idx, Int) - t = const_datatype_getfield_tfunc(sv, idx) - t === nothing || return t - end - elseif isa(sv, Core.TypeName) - fld = isa(nv, Symbol) ? fieldindex(Core.TypeName, nv, false) : nv - if (fld == TYPENAME_NAME_FIELDINDEX || - fld == TYPENAME_MODULE_FIELDINDEX || - fld == TYPENAME_WRAPPER_FIELDINDEX || - fld == TYPENAME_HASH_FIELDINDEX || - fld == TYPENAME_FLAGS_FIELDINDEX || - (fld == TYPENAME_NAMES_FIELDINDEX && isdefined(sv, fld))) - return Const(getfield(sv, fld)) - end + if isa(nv, Symbol) + nv = fieldindex(typeof(sv), nv, false) end - if isa(sv, Module) && isa(nv, Symbol) - return abstract_eval_global(sv, nv) + if !isa(nv, Int) + return Bottom end - if (isa(sv, SimpleVector) || !ismutable(sv)) && isdefined(sv, nv) + if isa(sv, DataType) && nv == DATATYPE_TYPES_FIELDINDEX && isdefined(sv, nv) return Const(getfield(sv, nv)) end + if isconst(typeof(sv), nv) + if isdefined(sv, nv) + return Const(getfield(sv, nv)) + end + return Union{} + end end s = typeof(sv) elseif isa(s00, PartialStruct) @@ -855,11 +812,11 @@ function getfield_tfunc(@nospecialize(s00), @nospecialize(name)) return Any end s = s::DataType - if s <: Tuple && name ⊑ Symbol + if s <: Tuple && !(Int <: widenconst(name)) return Bottom end if s <: Module - if name ⊑ Int + if !(Symbol <: widenconst(name)) return Bottom end return Any @@ -920,17 +877,6 @@ function getfield_tfunc(@nospecialize(s00), @nospecialize(name)) if fld < 1 || fld > nf return Bottom end - if isconstType(s00) - sp = s00.parameters[1] - elseif isa(s00, Const) - sp = s00.val - else - sp = nothing - end - if isa(sp, DataType) - t = const_datatype_getfield_tfunc(sp, fld) - t !== nothing && return t - end R = ftypes[fld] if isempty(s.parameters) return R diff --git a/base/reflection.jl b/base/reflection.jl index 1565eb15bcda7..6f6675f382a46 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -248,11 +248,34 @@ parentmodule(t::UnionAll) = parentmodule(unwrap_unionall(t)) """ isconst(m::Module, s::Symbol) -> Bool -Determine whether a global is declared `const` in a given `Module`. +Determine whether a global is declared `const` in a given module `m`. """ isconst(m::Module, s::Symbol) = ccall(:jl_is_const, Cint, (Any, Any), m, s) != 0 +""" + isconst(t::DataType, s::Union{Int,Symbol}) -> Bool + +Determine whether a field `s` is declared `const` in a given type `t`. +""" +function isconst(@nospecialize(t::Type), s::Symbol) + t = unwrap_unionall(t) + isa(t, DataType) || return false + return isconst(t, fieldindex(t, s, false)) +end +function isconst(@nospecialize(t::Type), s::Int) + t = unwrap_unionall(t) + # TODO: what to do for `Union`? + isa(t, DataType) || return false # uncertain + ismutabletype(t) || return true # immutable structs are always const + 1 <= s <= length(t.name.names) || return true # OOB reads are "const" since they always throw + constfields = t.name.constfields + constfields === C_NULL && return false + s -= 1 + return unsafe_load(Ptr{UInt32}(constfields), 1 + s÷32) & (1 << (s%32)) != 0 +end + + """ @locals() diff --git a/doc/src/base/base.md b/doc/src/base/base.md index f35e49bffcfaf..101669f0588a5 100644 --- a/doc/src/base/base.md +++ b/doc/src/base/base.md @@ -180,7 +180,12 @@ Base.isstructtype Base.nameof(::DataType) Base.fieldnames Base.fieldname +Core.fieldtype +Base.fieldtypes +Base.fieldcount Base.hasfield +Core.nfields +Base.isconst ``` ### Memory layout @@ -190,9 +195,6 @@ Base.sizeof(::Type) Base.isconcretetype Base.isbits Base.isbitstype -Core.fieldtype -Base.fieldtypes -Base.fieldcount Base.fieldoffset Base.datatype_alignment Base.datatype_haspadding @@ -418,8 +420,6 @@ Base.@__DIR__ Base.@__LINE__ Base.fullname Base.names -Core.nfields -Base.isconst Base.nameof(::Function) Base.functionloc(::Any, ::Any) Base.functionloc(::Method) diff --git a/src/ast.scm b/src/ast.scm index a1615cc01e2fe..7056189764876 100644 --- a/src/ast.scm +++ b/src/ast.scm @@ -378,7 +378,7 @@ (or (symbol? e) (decl? e))) (define (eventually-decl? e) - (or (decl? e) (and (pair? e) (eq? (car e) 'atomic) (symdecl? (cadr e))))) + (or (symbol? e) (and (pair? e) (memq (car e) '(|::| atomic const)) (eventually-decl? (cadr e))))) (define (make-decl n t) `(|::| ,n ,t)) diff --git a/src/builtins.c b/src/builtins.c index cae89319f4052..54df354f7172b 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -855,6 +855,10 @@ static inline size_t get_checked_fieldindex(const char *name, jl_datatype_t *st, JL_TYPECHKS(name, symbol, arg); idx = jl_field_index(st, (jl_sym_t*)arg, 1); } + if (mutabl && jl_field_isconst(st, idx)) { + jl_errorf("%s: const field .%s of type %s cannot be changed", name, + jl_symbol_name((jl_sym_t*)jl_svec_ref(jl_field_names(st), idx)), jl_symbol_name(st->name->name)); + } return idx; } @@ -1604,6 +1608,10 @@ static int equiv_type(jl_value_t *ta, jl_value_t *tb) ? dtb->name->atomicfields == NULL : (dtb->name->atomicfields != NULL && memcmp(dta->name->atomicfields, dtb->name->atomicfields, (jl_svec_len(dta->name->names) + 31) / 32 * sizeof(uint32_t)) == 0)) && + (dta->name->constfields == NULL + ? dtb->name->constfields == NULL + : (dtb->name->constfields != NULL && + memcmp(dta->name->constfields, dtb->name->constfields, (jl_svec_len(dta->name->names) + 31) / 32 * sizeof(uint32_t)) == 0)) && jl_egal((jl_value_t*)jl_field_names(dta), (jl_value_t*)jl_field_names(dtb)) && jl_nparams(dta) == jl_nparams(dtb))) return 0; diff --git a/src/cgutils.cpp b/src/cgutils.cpp index c3519fc553d6b..f2c654e85275a 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -2249,10 +2249,10 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st else { ptindex = emit_struct_gep(ctx, cast(lt), staddr, byte_offset + fsz); } - return emit_unionload(ctx, addr, ptindex, jfty, fsz, al, tbaa, jt->name->mutabl, union_max, tbaa_unionselbyte); + return emit_unionload(ctx, addr, ptindex, jfty, fsz, al, tbaa, !jl_field_isconst(jt, idx), union_max, tbaa_unionselbyte); } assert(jl_is_concrete_type(jfty)); - if (!jt->name->mutabl && !(maybe_null && (jfty == (jl_value_t*)jl_bool_type || + if (jl_field_isconst(jt, idx) && !(maybe_null && (jfty == (jl_value_t*)jl_bool_type || ((jl_datatype_t*)jfty)->layout->npointers))) { // just compute the pointer and let user load it when necessary return mark_julia_slot(addr, jfty, NULL, tbaa); @@ -3283,21 +3283,13 @@ static void emit_write_multibarrier(jl_codectx_t &ctx, Value *parent, Value *agg emit_write_barrier(ctx, parent, ptrs); } - static jl_cgval_t emit_setfield(jl_codectx_t &ctx, jl_datatype_t *sty, const jl_cgval_t &strct, size_t idx0, jl_cgval_t rhs, jl_cgval_t cmp, - bool checked, bool wb, AtomicOrdering Order, AtomicOrdering FailOrder, + bool wb, AtomicOrdering Order, AtomicOrdering FailOrder, bool needlock, bool issetfield, bool isreplacefield, bool isswapfield, bool ismodifyfield, const jl_cgval_t *modifyop, const std::string &fname) { - if (!sty->name->mutabl && checked) { - std::string msg = fname + ": immutable struct of type " - + std::string(jl_symbol_name(sty->name->name)) - + " cannot be changed"; - emit_error(ctx, msg); - return jl_cgval_t(); - } assert(strct.ispointer()); size_t byte_offset = jl_field_offset(sty, idx0); Value *addr = data_pointer(ctx, strct); @@ -3574,7 +3566,7 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg else need_wb = false; emit_typecheck(ctx, rhs, jl_svecref(sty->types, i), "new"); // n.b. ty argument must be concrete - emit_setfield(ctx, sty, strctinfo, i, rhs, jl_cgval_t(), false, need_wb, AtomicOrdering::NotAtomic, AtomicOrdering::NotAtomic, false, true, false, false, false, nullptr, ""); + emit_setfield(ctx, sty, strctinfo, i, rhs, jl_cgval_t(), need_wb, AtomicOrdering::NotAtomic, AtomicOrdering::NotAtomic, false, true, false, false, false, nullptr, ""); } return strctinfo; } diff --git a/src/codegen.cpp b/src/codegen.cpp index 249a761d1ef0c..588d992dca9a7 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2506,6 +2506,7 @@ static bool emit_f_opfield(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, bool isboxed = jl_field_isptr(uty, idx); bool isatomic = jl_field_isatomic(uty, idx); bool needlock = isatomic && !isboxed && jl_datatype_size(jl_field_type(uty, idx)) > MAX_ATOMIC_SIZE; + *ret = jl_cgval_t(); if (isatomic == (order == jl_memory_order_notatomic)) { emit_atomic_error(ctx, issetfield ? @@ -2519,25 +2520,37 @@ static bool emit_f_opfield(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, : "swapfield!: non-atomic field cannot be written atomically") : (isatomic ? "modifyfield!: atomic field cannot be written non-atomically" : "modifyfield!: non-atomic field cannot be written atomically")); - *ret = jl_cgval_t(); - return true; } - if (isatomic == (fail_order == jl_memory_order_notatomic)) { + else if (isatomic == (fail_order == jl_memory_order_notatomic)) { emit_atomic_error(ctx, (isatomic ? "replacefield!: atomic field cannot be accessed non-atomically" : "replacefield!: non-atomic field cannot be accessed atomically")); - *ret = jl_cgval_t(); - return true; } - *ret = emit_setfield(ctx, uty, obj, idx, val, cmp, true, true, - (needlock || order <= jl_memory_order_notatomic) - ? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0 - : get_llvm_atomic_order(order), - (needlock || fail_order <= jl_memory_order_notatomic) - ? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0 - : get_llvm_atomic_order(fail_order), - needlock, issetfield, isreplacefield, isswapfield, ismodifyfield, - modifyop, fname); + else if (!uty->name->mutabl) { + std::string msg = fname + ": immutable struct of type " + + std::string(jl_symbol_name(uty->name->name)) + + " cannot be changed"; + emit_error(ctx, msg); + } + else if (jl_field_isconst(uty, idx)) { + std::string msg = fname + ": const field ." + + std::string(jl_symbol_name((jl_sym_t*)jl_svec_ref(jl_field_names(uty), idx))) + + " of type " + + std::string(jl_symbol_name(uty->name->name)) + + " cannot be changed"; + emit_error(ctx, msg); + } + else { + *ret = emit_setfield(ctx, uty, obj, idx, val, cmp, true, + (needlock || order <= jl_memory_order_notatomic) + ? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0 + : get_llvm_atomic_order(order), + (needlock || fail_order <= jl_memory_order_notatomic) + ? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0 + : get_llvm_atomic_order(fail_order), + needlock, issetfield, isreplacefield, isswapfield, ismodifyfield, + modifyop, fname); + } return true; } } diff --git a/src/datatype.c b/src/datatype.c index 3825641b96623..e7f1ab22365b8 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -79,6 +79,7 @@ JL_DLLEXPORT jl_typename_t *jl_new_typename_in(jl_sym_t *name, jl_module_t *modu tn->mt = NULL; tn->partial = NULL; tn->atomicfields = NULL; + tn->constfields = NULL; return tn; } @@ -595,7 +596,7 @@ JL_DLLEXPORT jl_datatype_t *jl_new_datatype( t->name = NULL; if (jl_is_typename(name)) { - // This code-path is used by the Serialization module to by-pass normal expectations + // This code-path is used by the Serialization module to bypass normal expectations tn = (jl_typename_t*)name; tn->abstract = abstract; tn->mutabl = mutabl; @@ -622,6 +623,7 @@ JL_DLLEXPORT jl_datatype_t *jl_new_datatype( tn->n_uninitialized = jl_svec_len(fnames) - ninitialized; uint32_t *volatile atomicfields = NULL; + uint32_t *volatile constfields = NULL; int i; JL_TRY { for (i = 0; i + 1 < jl_svec_len(fattrs); i += 2) { @@ -643,17 +645,28 @@ JL_DLLEXPORT jl_datatype_t *jl_new_datatype( } atomicfields[fldn / 32] |= 1 << (fldn % 32); } + else if (attr == jl_const_sym) { + if (!mutabl) + jl_errorf("invalid field attribute const for immutable struct"); + if (constfields == NULL) { + size_t nb = (jl_svec_len(fnames) + 31) / 32 * sizeof(uint32_t); + constfields = (uint32_t*)malloc_s(nb); + memset(constfields, 0, nb); + } + constfields[fldn / 32] |= 1 << (fldn % 32); + } else { jl_errorf("invalid field attribute %s", jl_symbol_name(attr)); } } } JL_CATCH { - if (atomicfields) - free(atomicfields); + free(atomicfields); + free(constfields); jl_rethrow(); } tn->atomicfields = atomicfields; + tn->constfields = constfields; if (t->name->wrapper == NULL) { t->name->wrapper = (jl_value_t*)t; diff --git a/src/dump.c b/src/dump.c index 59f5112b86a88..f6e467bcce78b 100644 --- a/src/dump.c +++ b/src/dump.c @@ -838,6 +838,10 @@ static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v, int as_li write_int32(s->s, nb); if (nb) ios_write(s->s, (char*)tn->atomicfields, nb); + nb = tn->constfields ? (jl_svec_len(tn->names) + 31) / 32 * sizeof(uint32_t) : 0; + write_int32(s->s, nb); + if (nb) + ios_write(s->s, (char*)tn->constfields, nb); } return; } @@ -1786,6 +1790,11 @@ static jl_value_t *jl_deserialize_value_any(jl_serializer_state *s, uint8_t tag, tn->atomicfields = (uint32_t*)malloc(nfields); ios_read(s->s, (char*)tn->atomicfields, nfields); } + nfields = read_int32(s->s); + if (nfields) { + tn->constfields = (uint32_t*)malloc(nfields); + ios_read(s->s, (char*)tn->constfields, nfields); + } } else { jl_datatype_t *dt = (jl_datatype_t*)jl_unwrap_unionall(jl_get_global(m, sym)); diff --git a/src/jltypes.c b/src/jltypes.c index f14b364bda9ff..cfed8982cbc7c 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -2003,6 +2003,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_any_type /*jl_int32_type*/, jl_any_type /*jl_int32_type*/, jl_any_type /*jl_uint8_type*/); + const static uint32_t datatype_constfields[1] = { 0x00000097 }; // (1<<0)|(1<<1)|(1<<2)|(1<<4)|(1<<7) + jl_datatype_type->name->constfields = datatype_constfields; jl_precompute_memoized_dt(jl_datatype_type, 1); jl_typename_type->name = jl_new_typename_in(jl_symbol("TypeName"), core, 0, 1); @@ -2010,19 +2012,21 @@ void jl_init_types(void) JL_GC_DISABLED jl_typename_type->name->mt = jl_nonfunction_mt; jl_typename_type->super = jl_any_type; jl_typename_type->parameters = jl_emptysvec; - jl_typename_type->name->n_uninitialized = 12 - 2; - jl_typename_type->name->names = jl_perm_symsvec(12, "name", "module", - "names", "atomicfields", + jl_typename_type->name->n_uninitialized = 13 - 2; + jl_typename_type->name->names = jl_perm_symsvec(13, "name", "module", + "names", "atomicfields", "constfields", "wrapper", "cache", "linearcache", "mt", "partial", "hash", "n_uninitialized", "flags"); // "abstract", "mutable", "mayinlinealloc", - jl_typename_type->types = jl_svec(12, jl_symbol_type, jl_any_type /*jl_module_type*/, - jl_simplevector_type, jl_any_type/*jl_voidpointer_type*/, + jl_typename_type->types = jl_svec(13, jl_symbol_type, jl_any_type /*jl_module_type*/, + jl_simplevector_type, jl_any_type/*jl_voidpointer_type*/, jl_any_type/*jl_voidpointer_type*/, jl_type_type, jl_simplevector_type, jl_simplevector_type, jl_methtable_type, jl_any_type, jl_any_type /*jl_long_type*/, jl_any_type /*jl_int32_type*/, jl_any_type /*jl_uint8_type*/); + const static uint32_t typename_constfields[1] = { 0x00001d3f }; // (1<<0)|(1<<1)|(1<<2)|(1<<3)|(1<<4)|(1<<5)|(1<<8)|(1<<10)|(1<<11)|(1<<12) + jl_typename_type->name->constfields = typename_constfields; jl_precompute_memoized_dt(jl_typename_type, 1); jl_methtable_type->name = jl_new_typename_in(jl_symbol("MethodTable"), core, 0, 1); @@ -2040,6 +2044,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_any_type, jl_any_type/*module*/, jl_any_type/*any vector*/, jl_any_type/*voidpointer*/, jl_any_type/*int32*/, jl_any_type/*uint8*/, jl_any_type/*uint8*/); + const static uint32_t methtable_constfields[1] = { 0x00000040 }; // (1<<6); + jl_methtable_type->name->constfields = methtable_constfields; jl_precompute_memoized_dt(jl_methtable_type, 1); jl_symbol_type->name = jl_new_typename_in(jl_symbol("Symbol"), core, 0, 1); @@ -2216,6 +2222,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_bool_type), jl_emptysvec, 0, 1, 4); + const static uint32_t typemap_entry_constfields[1] = { 0x000003fe }; // (1<<1)|(1<<2)|(1<<3)|(1<<4)|(1<<5)|(1<<6)|(1<<7)|(1<<8)|(1<<9); + jl_typemap_entry_type->name->constfields = typemap_entry_constfields; jl_function_type = jl_new_abstracttype((jl_value_t*)jl_symbol("Function"), core, jl_any_type, jl_emptysvec); jl_builtin_type = jl_new_abstracttype((jl_value_t*)jl_symbol("Builtin"), core, jl_function_type, jl_emptysvec); @@ -2390,19 +2398,19 @@ void jl_init_types(void) JL_GC_DISABLED "file", "line", "primary_world", - "deleted_world", + "deleted_world", // !const "sig", - "specializations", - "speckeyset", + "specializations", // !const + "speckeyset", // !const "slot_syms", "external_mt", - "source", - "unspecialized", - "generator", - "roots", - "ccallable", - "invokes", - "recursion_relation", + "source", // !const + "unspecialized", // !const + "generator", // !const + "roots", // !const + "ccallable", // !const + "invokes", // !const + "recursion_relation", // !const "nargs", "called", "nospecialize", @@ -2440,6 +2448,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_uint8_type), jl_emptysvec, 0, 1, 10); + //const static uint32_t method_constfields[1] = { 0x03fc065f }; // (1<<0)|(1<<1)|(1<<2)|(1<<3)|(1<<4)|(1<<6)|(1<<9)|(1<<10)|(1<<18)|(1<<19)|(1<<20)|(1<<21)|(1<<22)|(1<<23)|(1<<24)|(1<<25); + //jl_method_type->name->constfields = method_constfields; jl_method_instance_type = jl_new_datatype(jl_symbol("MethodInstance"), core, @@ -2464,6 +2474,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_bool_type), jl_emptysvec, 0, 1, 3); + //const static uint32_t method_instance_constfields[1] = { 0x00000007 }; // (1<<0)|(1<<1)|(1<<2); + //jl_method_instance_type->name->constfields = method_instance_constfields; jl_code_instance_type = jl_new_datatype(jl_symbol("CodeInstance"), core, @@ -2495,6 +2507,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_emptysvec, 0, 1, 1); jl_svecset(jl_code_instance_type->types, 1, jl_code_instance_type); + const static uint32_t code_instance_constfields[1] = { 0x00000001 }; // (1<<1); + jl_code_instance_type->name->constfields = code_instance_constfields; jl_const_type = jl_new_datatype(jl_symbol("Const"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(1, "val"), @@ -2610,7 +2624,6 @@ void jl_init_types(void) JL_GC_DISABLED jl_opaque_closure_typename = ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_opaque_closure_type))->name; jl_compute_field_offsets((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_opaque_closure_type)); - jl_partial_opaque_type = jl_new_datatype(jl_symbol("PartialOpaque"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(5, "typ", "env", "isva", "parent", "source"), jl_svec(5, jl_type_type, jl_any_type, jl_bool_type, jl_method_instance_type, jl_method_type), @@ -2625,10 +2638,11 @@ void jl_init_types(void) JL_GC_DISABLED jl_svecset(jl_datatype_type->types, 8, jl_uint8_type); jl_svecset(jl_typename_type->types, 1, jl_module_type); jl_svecset(jl_typename_type->types, 3, jl_voidpointer_type); - jl_svecset(jl_typename_type->types, 4, jl_type_type); - jl_svecset(jl_typename_type->types, 9, jl_long_type); - jl_svecset(jl_typename_type->types, 10, jl_int32_type); - jl_svecset(jl_typename_type->types, 11, jl_uint8_type); + jl_svecset(jl_typename_type->types, 4, jl_voidpointer_type); + jl_svecset(jl_typename_type->types, 5, jl_type_type); + jl_svecset(jl_typename_type->types, 10, jl_long_type); + jl_svecset(jl_typename_type->types, 11, jl_int32_type); + jl_svecset(jl_typename_type->types, 12, jl_uint8_type); jl_svecset(jl_methtable_type->types, 4, jl_long_type); jl_svecset(jl_methtable_type->types, 6, jl_module_type); jl_svecset(jl_methtable_type->types, 7, jl_array_any_type); diff --git a/src/julia-parser.scm b/src/julia-parser.scm index 9006fd4c2b380..ef8019722f50d 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -1452,17 +1452,11 @@ (expr (if (and (pair? assgn) (eq? (car assgn) 'tuple)) (cons word (cdr assgn)) (list word assgn)))) - (if const + (if const ;; normalize `global const` and `const global` `(const ,expr) expr))) ((const) - (let ((assgn (parse-eq s))) - (if (not (and (pair? assgn) - (or (eq? (car assgn) '=) - (eq? (car assgn) 'global) - (eq? (car assgn) 'local)))) - (error "expected assignment after \"const\"") - `(const ,assgn)))) + `(const ,(parse-eq s))) ((function macro) (let* ((loc (line-number-node s)) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index e71cda3f9afee..1b11b48765951 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -891,17 +891,17 @@ (define (struct-def-expr- name params bounds super fields0 mut) (receive - (fields defs) (separate (lambda (x) (or (symbol? x) (eventually-decl? x))) - fields0) + (fields defs) (separate eventually-decl? fields0) (let* ((attrs ()) (fields (let ((n 0)) (map (lambda (x) (set! n (+ n 1)) - (if (and (pair? x) (not (decl? x))) - (begin - (set! attrs (cons (quotify (car x)) (cons n attrs))) - (cadr x)) - x)) + (let loop ((x x)) + (if (and (pair? x) (not (decl? x))) + (begin + (set! attrs (cons (quotify (car x)) (cons n attrs))) + (loop (cadr x))) + x))) fields))) (attrs (reverse attrs)) (defs (filter (lambda (x) (not (or (effect-free? x) (eq? (car x) 'string)))) defs)) @@ -1295,9 +1295,9 @@ (if (null? f) '() (let ((x (car f))) - (cond ((or (symdecl? x) (linenum? x)) + (cond ((or (eventually-decl? x) (linenum? x)) (loop (cdr f))) - ((and (assignment? x) (symdecl? (cadr x))) + ((and (assignment? x) (eventually-decl? (cadr x))) (error (string "\"" (deparse x) "\" inside type definition is reserved"))) (else '()))))) (expand-forms @@ -1385,7 +1385,7 @@ (expand-forms (expand-decls (car arg) (cdr arg) #t))) ((= |::|) (expand-forms (expand-decls 'const (cdr e) #f))) - (else e))))) + (else (error "expected assignment after \"const\"")))))) (define (expand-atomic-decl e) (error "unimplemented or unsupported atomic declaration")) @@ -1708,9 +1708,9 @@ ,@(if outer `((require-existing-local ,lhs)) '()) (if (call (top not_int) (call (core ===) ,next (null))) (_do_while - (block ,body - (= ,next (call (top iterate) ,coll ,state))) - (call (top not_int) (call (core ===) ,next (null)))))))))))) + (block ,body + (= ,next (call (top iterate) ,coll ,state))) + (call (top not_int) (call (core ===) ,next (null)))))))))))) ;; wrap `expr` in a function appropriate for consuming values from given ranges (define (func-for-generator-ranges expr range-exprs flat outervars) diff --git a/src/julia.h b/src/julia.h index 5663a6c310052..f626f3026e67e 100644 --- a/src/julia.h +++ b/src/julia.h @@ -415,7 +415,7 @@ typedef struct { struct _jl_module_t *module; jl_svec_t *names; // field names const uint32_t *atomicfields; // if any fields are atomic, we record them here - //const uint32_t *constfields; // if any fields are const, we record them here + const uint32_t *constfields; // if any fields are const, we record them here // `wrapper` is either the only instantiation of the type (if no parameters) // or a UnionAll accepting parameters to make an instantiation. jl_value_t *wrapper; @@ -1114,7 +1114,6 @@ static inline uint32_t jl_ptr_offset(jl_datatype_t *st, int i) JL_NOTSAFEPOINT static inline int jl_field_isatomic(jl_datatype_t *st, int i) JL_NOTSAFEPOINT { - // if (!st->mutable) return 0; // TODO: is this fast-path helpful? const uint32_t *atomicfields = st->name->atomicfields; if (atomicfields != NULL) { if (atomicfields[i / 32] & (1 << (i % 32))) @@ -1123,6 +1122,20 @@ static inline int jl_field_isatomic(jl_datatype_t *st, int i) JL_NOTSAFEPOINT return 0; } +static inline int jl_field_isconst(jl_datatype_t *st, int i) JL_NOTSAFEPOINT +{ + jl_typename_t *tn = st->name; + if (!tn->mutabl) + return 1; + const uint32_t *constfields = tn->constfields; + if (constfields != NULL) { + if (constfields[i / 32] & (1 << (i % 32))) + return 1; + } + return 0; +} + + static inline int jl_is_layout_opaque(const jl_datatype_layout_t *l) JL_NOTSAFEPOINT { return l->nfields == 0 && l->npointers > 0; diff --git a/src/staticdata.c b/src/staticdata.c index 223d7b8e63427..634728991a358 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -1064,6 +1064,16 @@ static void jl_write_values(jl_serializer_state *s) arraylist_push(&s->relocs_list, (void*)(((uintptr_t)ConstDataRef << RELOC_TAG_OFFSET) + layout)); // relocation target ios_write(s->const_data, (char*)tn->atomicfields, nb); } + if (tn->constfields != NULL) { + size_t nb = (jl_svec_len(tn->names) + 31) / 32 * sizeof(uint32_t); + uintptr_t layout = LLT_ALIGN(ios_pos(s->const_data), sizeof(void*)); + write_padding(s->const_data, layout - ios_pos(s->const_data)); // realign stream + newtn->constfields = NULL; // relocation offset + layout /= sizeof(void*); + arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_typename_t, constfields))); // relocation location + arraylist_push(&s->relocs_list, (void*)(((uintptr_t)ConstDataRef << RELOC_TAG_OFFSET) + layout)); // relocation target + ios_write(s->const_data, (char*)tn->constfields, nb); + } } else if (((jl_datatype_t*)(jl_typeof(v)))->name == jl_idtable_typename) { // will need to rehash this, later (after types are fully constructed) diff --git a/stdlib/Serialization/src/Serialization.jl b/stdlib/Serialization/src/Serialization.jl index 2889072bbdc8b..9a4ebc7af8c2a 100644 --- a/stdlib/Serialization/src/Serialization.jl +++ b/stdlib/Serialization/src/Serialization.jl @@ -1273,10 +1273,11 @@ function deserialize_typename(s::AbstractSerializer, number) else # reuse the same name for the type, if possible, for nicer debugging tn_name = isdefined(__deserialized_types__, name) ? gensym() : name - tn = ccall(:jl_new_typename_in, Ref{Core.TypeName}, (Any, Any, Cint, Cint), + tn = ccall(:jl_new_typename_in, Any, (Any, Any, Cint, Cint), tn_name, __deserialized_types__, false, false) makenew = true end + tn = tn::Core.TypeName remember_object(s, tn, number) deserialize_cycle(s, tn) @@ -1291,19 +1292,22 @@ function deserialize_typename(s::AbstractSerializer, number) ninitialized = deserialize(s)::Int32 if makenew - Core.setfield!(tn, :names, names) # TODO: there's an unhanded cycle in the dependency graph at this point: # while deserializing super and/or types, we may have encountered # tn.wrapper and throw UndefRefException before we get to this point ndt = ccall(:jl_new_datatype, Any, (Any, Any, Any, Any, Any, Any, Any, Cint, Cint, Cint), tn, tn.module, super, parameters, names, types, attrs, abstr, mutabl, ninitialized) - tn.wrapper = ndt.name.wrapper + @assert tn == ndt.name ccall(:jl_set_const, Cvoid, (Any, Any, Any), tn.module, tn.name, tn.wrapper) ty = tn.wrapper - if has_instance && !isdefined(ty, :instance) - # use setfield! directly to avoid `fieldtype` lowering expecting to see a Singleton object already on ty - Core.setfield!(ty, :instance, ccall(:jl_new_struct, Any, (Any, Any...), ty)) + if has_instance + ty = ty::DataType + if !isdefined(ty, :instance) + singleton = ccall(:jl_new_struct, Any, (Any, Any...), ty) + # use setfield! directly to avoid `fieldtype` lowering expecting to see a Singleton object already on ty + ccall(:jl_set_nth_field, Cvoid, (Any, Csize_t, Any), ty, Base.fieldindex(DataType, :instance)-1, singleton) + end end end @@ -1313,15 +1317,16 @@ function deserialize_typename(s::AbstractSerializer, number) defs = deserialize(s) maxa = deserialize(s)::Int if makenew - tn.mt = ccall(:jl_new_method_table, Any, (Any, Any), name, tn.module) + mt = ccall(:jl_new_method_table, Any, (Any, Any), name, tn.module) if !isempty(parameters) - tn.mt.offs = 0 + mt.offs = 0 end - tn.mt.name = mtname - tn.mt.max_args = maxa + mt.name = mtname + mt.max_args = maxa + ccall(:jl_set_nth_field, Cvoid, (Any, Csize_t, Any), tn, Base.fieldindex(Core.TypeName, :mt)-1, mt) for def in defs if isdefined(def, :sig) - ccall(:jl_method_table_insert, Cvoid, (Any, Any, Ptr{Cvoid}), tn.mt, def, C_NULL) + ccall(:jl_method_table_insert, Cvoid, (Any, Any, Ptr{Cvoid}), mt, def, C_NULL) end end end @@ -1333,9 +1338,10 @@ function deserialize_typename(s::AbstractSerializer, number) end end elseif makenew - tn.mt = Symbol.name.mt + mt = Symbol.name.mt + ccall(:jl_set_nth_field, Cvoid, (Any, Csize_t, Any), tn, Base.fieldindex(Core.TypeName, :mt)-1, mt) end - return tn::Core.TypeName + return tn end function deserialize_datatype(s::AbstractSerializer, full::Bool) diff --git a/test/core.jl b/test/core.jl index 3727d3ff8f069..6c36827d42afb 100644 --- a/test/core.jl +++ b/test/core.jl @@ -9,6 +9,90 @@ const Bottom = Union{} # For curmod_* include("testenv.jl") +## tests that `const` field declarations + +# sanity tests that our built-in types are marked correctly for const fields +for (T, c) in ( + (Core.CodeInfo, []), + (Core.CodeInstance, [:def]), + (Core.Method, [#=:name, :module, :file, :line, :primary_world, :sig, :slot_syms, :external_mt, :nargs, :called, :nospecialize, :nkw, :isva, :pure, :is_for_opaque_closure, :constprop=#]), + (Core.MethodInstance, [#=:def, :specTypes, :sparam_vals]=#]), + (Core.MethodTable, [:module]), + (Core.TypeMapEntry, [:sig, :simplesig, :guardsigs, :min_world, :max_world, :func, :isleafsig, :issimplesig, :va]), + (Core.TypeMapLevel, []), + (Core.TypeName, [:name, :module, :names, :atomicfields, :constfields, :wrapper, :mt, :hash, :n_uninitialized, :flags]), + (DataType, [:name, :super, :parameters, :instance, :hash]), + ) + @test Set((fieldname(T, i) for i in 1:fieldcount(T) if isconst(T, i))) == Set(c) +end + +@test_throws(ErrorException("setfield!: const field .name of type DataType cannot be changed"), + setfield!(Int, :name, Int.name)) +@test_throws(ErrorException("setfield!: const field .name of type DataType cannot be changed"), + (Base.Experimental.@force_compile; setfield!(Int, :name, Int.name))) + +@test_throws(ErrorException("invalid field attribute const for immutable struct"), + @eval struct ABCDconst + const abcd + end) +mutable struct ABCDconst + const a + const b::Int + c + const d::Union{Int,Nothing} +end +@test_throws(ErrorException("invalid redefinition of constant ABCDconst"), + mutable struct ABCDconst + const a + const b::Int + c + d::Union{Int,Nothing} + end) +@test_throws(ErrorException("invalid redefinition of constant ABCDconst"), + mutable struct ABCDconst + a + b::Int + c + d::Union{Int,Nothing} + end) +let abcd = ABCDconst(1, 2, 3, 4) + @test (1, 2, 3, 4) === (abcd.a, abcd.b, abcd.c, abcd.d) + @test_throws(ErrorException("setfield!: const field .a of type ABCDconst cannot be changed"), + abcd.a = 0) + @test_throws(ErrorException("replacefield!: const field .a of type ABCDconst cannot be changed"), + replacefield!(abcd, :a, 1, 0)) + @test_throws(ErrorException("modifyfield!: const field .a of type ABCDconst cannot be changed"), + modifyfield!(abcd, :a, +, 1)) + @test_throws(ErrorException("swapfield!: const field .a of type ABCDconst cannot be changed"), + swapfield!(abcd, :a, 0)) + @test_throws(ErrorException("setfield!: const field .b of type ABCDconst cannot be changed"), + abcd.b = 0) + abcd.c = "not constant" + @test_throws(ErrorException("setfield!: const field .d of type ABCDconst cannot be changed"), + abcd.d = nothing) + @test (1, 2, "not constant", 4) === (abcd.a, abcd.b, abcd.c, abcd.d) +end +# repeat with the compiler +let abcd = ABCDconst(1, 2, 3, 4) + Base.Experimental.@force_compile + @test (1, 2, 3, 4) === (abcd.a, abcd.b, abcd.c, abcd.d) + @test_throws(ErrorException("setfield!: const field .a of type ABCDconst cannot be changed"), + abcd.a = 0) + @test_throws(ErrorException("replacefield!: const field .a of type ABCDconst cannot be changed"), + replacefield!(abcd, :a, 1, 0)) + @test_throws(ErrorException("modifyfield!: const field .a of type ABCDconst cannot be changed"), + modifyfield!(abcd, :a, +, 1)) + @test_throws(ErrorException("swapfield!: const field .a of type ABCDconst cannot be changed"), + swapfield!(abcd, :a, 0)) + @test_throws(ErrorException("setfield!: const field .b of type ABCDconst cannot be changed"), + abcd.b = 0) + abcd.c = "not constant" + @test_throws(ErrorException("setfield!: const field .d of type ABCDconst cannot be changed"), + abcd.d = nothing) + @test (1, 2, "not constant", 4) === (abcd.a, abcd.b, abcd.c, abcd.d) +end + + f47(x::Vector{Vector{T}}) where {T} = 0 @test_throws MethodError f47(Vector{Vector}()) @test f47(Vector{Vector{Int}}()) == 0