Skip to content

Commit 63f6294

Browse files
vtjnashmbauman
andauthored
Allow const declarations on mutable fields (JuliaLang#43305)
Mark some builtin types also, although Serialization relies upon being able to mutilate the Method objects, so we do not yet mark those. Replaces JuliaLang#11430 Co-authored-by: Matt Bauman <[email protected]>
1 parent 08aa0ac commit 63f6294

18 files changed

+294
-165
lines changed

NEWS.md

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ New language features
1616
* Support for Unicode 14.0.0 ([#43443]).
1717
* `try`-blocks can now optionally have an `else`-block which is executed right after the main body only if
1818
no errors were thrown. ([#42211])
19+
* Mutable struct fields may now be annotated as `const` to prevent changing
20+
them after construction, providing for greater clarity and optimization
21+
ability of these objects ([#43305]).
1922

2023
Language changes
2124
----------------

base/compiler/ssair/passes.jl

+2-1
Original file line numberDiff line numberDiff line change
@@ -846,7 +846,7 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse
846846
if isa(typ, UnionAll)
847847
typ = unwrap_unionall(typ)
848848
end
849-
# Could still end up here if we tried to setfield! and immutable, which would
849+
# Could still end up here if we tried to setfield! on an immutable, which would
850850
# error at runtime, but is not illegal to have in the IR.
851851
ismutabletype(typ) || continue
852852
typ = typ::DataType
@@ -871,6 +871,7 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse
871871
stmt = ir[SSAValue(def)]::Expr # == `setfield!` call
872872
field = try_compute_fieldidx_stmt(ir, stmt, typ)
873873
field === nothing && @goto skip
874+
isconst(typ, field) && @goto skip # we discovered an attempt to mutate a const field, which must error
874875
push!(fielddefuse[field].defs, def)
875876
end
876877
# Check that the defexpr has defined values for all the fields

base/compiler/tfuncs.jl

+18-72
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,7 @@ function find_tfunc(@nospecialize f)
2424
end
2525
end
2626

27-
const DATATYPE_NAME_FIELDINDEX = fieldindex(DataType, :name)
28-
const DATATYPE_PARAMETERS_FIELDINDEX = fieldindex(DataType, :parameters)
2927
const DATATYPE_TYPES_FIELDINDEX = fieldindex(DataType, :types)
30-
const DATATYPE_SUPER_FIELDINDEX = fieldindex(DataType, :super)
31-
const DATATYPE_INSTANCE_FIELDINDEX = fieldindex(DataType, :instance)
32-
const DATATYPE_HASH_FIELDINDEX = fieldindex(DataType, :hash)
33-
34-
const TYPENAME_NAME_FIELDINDEX = fieldindex(Core.TypeName, :name)
35-
const TYPENAME_MODULE_FIELDINDEX = fieldindex(Core.TypeName, :module)
36-
const TYPENAME_NAMES_FIELDINDEX = fieldindex(Core.TypeName, :names)
37-
const TYPENAME_WRAPPER_FIELDINDEX = fieldindex(Core.TypeName, :wrapper)
38-
const TYPENAME_HASH_FIELDINDEX = fieldindex(Core.TypeName, :hash)
39-
const TYPENAME_FLAGS_FIELDINDEX = fieldindex(Core.TypeName, :flags)
4028

4129
##########
4230
# tfuncs #
@@ -305,7 +293,7 @@ function isdefined_tfunc(@nospecialize(arg1), @nospecialize(sym))
305293
return Const(false)
306294
elseif isa(arg1, Const)
307295
arg1v = (arg1::Const).val
308-
if !ismutable(arg1v) || isdefined(arg1v, idx) || (isa(arg1v, DataType) && is_dt_const_field(idx))
296+
if !ismutable(arg1v) || isdefined(arg1v, idx) || isconst(typeof(arg1v), idx)
309297
return Const(isdefined(arg1v, idx))
310298
end
311299
elseif !isvatuple(a1)
@@ -646,23 +634,6 @@ function subtype_tfunc(@nospecialize(a), @nospecialize(b))
646634
end
647635
add_tfunc(<:, 2, 2, subtype_tfunc, 10)
648636

649-
is_dt_const_field(fld::Int) = (
650-
fld == DATATYPE_NAME_FIELDINDEX ||
651-
fld == DATATYPE_PARAMETERS_FIELDINDEX ||
652-
fld == DATATYPE_TYPES_FIELDINDEX ||
653-
fld == DATATYPE_SUPER_FIELDINDEX ||
654-
fld == DATATYPE_INSTANCE_FIELDINDEX ||
655-
fld == DATATYPE_HASH_FIELDINDEX
656-
)
657-
function const_datatype_getfield_tfunc(@nospecialize(sv), fld::Int)
658-
if fld == DATATYPE_INSTANCE_FIELDINDEX
659-
return isdefined(sv, fld) ? Const(getfield(sv, fld)) : Union{}
660-
elseif is_dt_const_field(fld) && isdefined(sv, fld)
661-
return Const(getfield(sv, fld))
662-
end
663-
return nothing
664-
end
665-
666637
function fieldcount_noerror(@nospecialize t)
667638
if t isa UnionAll || t isa Union
668639
t = argument_datatype(t)
@@ -801,41 +772,27 @@ function getfield_tfunc(@nospecialize(s00), @nospecialize(name))
801772
end
802773
if isa(name, Const)
803774
nv = name.val
804-
if !(isa(nv,Symbol) || isa(nv,Int))
775+
if isa(sv, Module)
776+
if isa(nv, Symbol)
777+
return abstract_eval_global(sv, nv)
778+
end
805779
return Bottom
806780
end
807-
if isa(sv, UnionAll)
808-
if nv === :var || nv === 1
809-
return Const(sv.var)
810-
elseif nv === :body || nv === 2
811-
return Const(sv.body)
812-
end
813-
elseif isa(sv, DataType)
814-
idx = nv
815-
if isa(idx, Symbol)
816-
idx = fieldindex(DataType, idx, false)
817-
end
818-
if isa(idx, Int)
819-
t = const_datatype_getfield_tfunc(sv, idx)
820-
t === nothing || return t
821-
end
822-
elseif isa(sv, Core.TypeName)
823-
fld = isa(nv, Symbol) ? fieldindex(Core.TypeName, nv, false) : nv
824-
if (fld == TYPENAME_NAME_FIELDINDEX ||
825-
fld == TYPENAME_MODULE_FIELDINDEX ||
826-
fld == TYPENAME_WRAPPER_FIELDINDEX ||
827-
fld == TYPENAME_HASH_FIELDINDEX ||
828-
fld == TYPENAME_FLAGS_FIELDINDEX ||
829-
(fld == TYPENAME_NAMES_FIELDINDEX && isdefined(sv, fld)))
830-
return Const(getfield(sv, fld))
831-
end
781+
if isa(nv, Symbol)
782+
nv = fieldindex(typeof(sv), nv, false)
832783
end
833-
if isa(sv, Module) && isa(nv, Symbol)
834-
return abstract_eval_global(sv, nv)
784+
if !isa(nv, Int)
785+
return Bottom
835786
end
836-
if (isa(sv, SimpleVector) || !ismutable(sv)) && isdefined(sv, nv)
787+
if isa(sv, DataType) && nv == DATATYPE_TYPES_FIELDINDEX && isdefined(sv, nv)
837788
return Const(getfield(sv, nv))
838789
end
790+
if isconst(typeof(sv), nv)
791+
if isdefined(sv, nv)
792+
return Const(getfield(sv, nv))
793+
end
794+
return Union{}
795+
end
839796
end
840797
s = typeof(sv)
841798
elseif isa(s00, PartialStruct)
@@ -855,11 +812,11 @@ function getfield_tfunc(@nospecialize(s00), @nospecialize(name))
855812
return Any
856813
end
857814
s = s::DataType
858-
if s <: Tuple && name Symbol
815+
if s <: Tuple && !(Int <: widenconst(name))
859816
return Bottom
860817
end
861818
if s <: Module
862-
if name Int
819+
if !(Symbol <: widenconst(name))
863820
return Bottom
864821
end
865822
return Any
@@ -920,17 +877,6 @@ function getfield_tfunc(@nospecialize(s00), @nospecialize(name))
920877
if fld < 1 || fld > nf
921878
return Bottom
922879
end
923-
if isconstType(s00)
924-
sp = s00.parameters[1]
925-
elseif isa(s00, Const)
926-
sp = s00.val
927-
else
928-
sp = nothing
929-
end
930-
if isa(sp, DataType)
931-
t = const_datatype_getfield_tfunc(sp, fld)
932-
t !== nothing && return t
933-
end
934880
R = ftypes[fld]
935881
if isempty(s.parameters)
936882
return R

base/reflection.jl

+24-1
Original file line numberDiff line numberDiff line change
@@ -248,11 +248,34 @@ parentmodule(t::UnionAll) = parentmodule(unwrap_unionall(t))
248248
"""
249249
isconst(m::Module, s::Symbol) -> Bool
250250
251-
Determine whether a global is declared `const` in a given `Module`.
251+
Determine whether a global is declared `const` in a given module `m`.
252252
"""
253253
isconst(m::Module, s::Symbol) =
254254
ccall(:jl_is_const, Cint, (Any, Any), m, s) != 0
255255

256+
"""
257+
isconst(t::DataType, s::Union{Int,Symbol}) -> Bool
258+
259+
Determine whether a field `s` is declared `const` in a given type `t`.
260+
"""
261+
function isconst(@nospecialize(t::Type), s::Symbol)
262+
t = unwrap_unionall(t)
263+
isa(t, DataType) || return false
264+
return isconst(t, fieldindex(t, s, false))
265+
end
266+
function isconst(@nospecialize(t::Type), s::Int)
267+
t = unwrap_unionall(t)
268+
# TODO: what to do for `Union`?
269+
isa(t, DataType) || return false # uncertain
270+
ismutabletype(t) || return true # immutable structs are always const
271+
1 <= s <= length(t.name.names) || return true # OOB reads are "const" since they always throw
272+
constfields = t.name.constfields
273+
constfields === C_NULL && return false
274+
s -= 1
275+
return unsafe_load(Ptr{UInt32}(constfields), 1 + s÷32) & (1 << (s%32)) != 0
276+
end
277+
278+
256279
"""
257280
@locals()
258281

doc/src/base/base.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,12 @@ Base.isstructtype
180180
Base.nameof(::DataType)
181181
Base.fieldnames
182182
Base.fieldname
183+
Core.fieldtype
184+
Base.fieldtypes
185+
Base.fieldcount
183186
Base.hasfield
187+
Core.nfields
188+
Base.isconst
184189
```
185190

186191
### Memory layout
@@ -190,9 +195,6 @@ Base.sizeof(::Type)
190195
Base.isconcretetype
191196
Base.isbits
192197
Base.isbitstype
193-
Core.fieldtype
194-
Base.fieldtypes
195-
Base.fieldcount
196198
Base.fieldoffset
197199
Base.datatype_alignment
198200
Base.datatype_haspadding
@@ -418,8 +420,6 @@ Base.@__DIR__
418420
Base.@__LINE__
419421
Base.fullname
420422
Base.names
421-
Core.nfields
422-
Base.isconst
423423
Base.nameof(::Function)
424424
Base.functionloc(::Any, ::Any)
425425
Base.functionloc(::Method)

src/ast.scm

+1-1
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@
378378
(or (symbol? e) (decl? e)))
379379

380380
(define (eventually-decl? e)
381-
(or (decl? e) (and (pair? e) (eq? (car e) 'atomic) (symdecl? (cadr e)))))
381+
(or (symbol? e) (and (pair? e) (memq (car e) '(|::| atomic const)) (eventually-decl? (cadr e)))))
382382

383383
(define (make-decl n t) `(|::| ,n ,t))
384384

src/builtins.c

+8
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,10 @@ static inline size_t get_checked_fieldindex(const char *name, jl_datatype_t *st,
855855
JL_TYPECHKS(name, symbol, arg);
856856
idx = jl_field_index(st, (jl_sym_t*)arg, 1);
857857
}
858+
if (mutabl && jl_field_isconst(st, idx)) {
859+
jl_errorf("%s: const field .%s of type %s cannot be changed", name,
860+
jl_symbol_name((jl_sym_t*)jl_svec_ref(jl_field_names(st), idx)), jl_symbol_name(st->name->name));
861+
}
858862
return idx;
859863
}
860864

@@ -1604,6 +1608,10 @@ static int equiv_type(jl_value_t *ta, jl_value_t *tb)
16041608
? dtb->name->atomicfields == NULL
16051609
: (dtb->name->atomicfields != NULL &&
16061610
memcmp(dta->name->atomicfields, dtb->name->atomicfields, (jl_svec_len(dta->name->names) + 31) / 32 * sizeof(uint32_t)) == 0)) &&
1611+
(dta->name->constfields == NULL
1612+
? dtb->name->constfields == NULL
1613+
: (dtb->name->constfields != NULL &&
1614+
memcmp(dta->name->constfields, dtb->name->constfields, (jl_svec_len(dta->name->names) + 31) / 32 * sizeof(uint32_t)) == 0)) &&
16071615
jl_egal((jl_value_t*)jl_field_names(dta), (jl_value_t*)jl_field_names(dtb)) &&
16081616
jl_nparams(dta) == jl_nparams(dtb)))
16091617
return 0;

src/cgutils.cpp

+4-12
Original file line numberDiff line numberDiff line change
@@ -2249,10 +2249,10 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st
22492249
else {
22502250
ptindex = emit_struct_gep(ctx, cast<StructType>(lt), staddr, byte_offset + fsz);
22512251
}
2252-
return emit_unionload(ctx, addr, ptindex, jfty, fsz, al, tbaa, jt->name->mutabl, union_max, tbaa_unionselbyte);
2252+
return emit_unionload(ctx, addr, ptindex, jfty, fsz, al, tbaa, !jl_field_isconst(jt, idx), union_max, tbaa_unionselbyte);
22532253
}
22542254
assert(jl_is_concrete_type(jfty));
2255-
if (!jt->name->mutabl && !(maybe_null && (jfty == (jl_value_t*)jl_bool_type ||
2255+
if (jl_field_isconst(jt, idx) && !(maybe_null && (jfty == (jl_value_t*)jl_bool_type ||
22562256
((jl_datatype_t*)jfty)->layout->npointers))) {
22572257
// just compute the pointer and let user load it when necessary
22582258
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
32833283
emit_write_barrier(ctx, parent, ptrs);
32843284
}
32853285

3286-
32873286
static jl_cgval_t emit_setfield(jl_codectx_t &ctx,
32883287
jl_datatype_t *sty, const jl_cgval_t &strct, size_t idx0,
32893288
jl_cgval_t rhs, jl_cgval_t cmp,
3290-
bool checked, bool wb, AtomicOrdering Order, AtomicOrdering FailOrder,
3289+
bool wb, AtomicOrdering Order, AtomicOrdering FailOrder,
32913290
bool needlock, bool issetfield, bool isreplacefield, bool isswapfield, bool ismodifyfield,
32923291
const jl_cgval_t *modifyop, const std::string &fname)
32933292
{
3294-
if (!sty->name->mutabl && checked) {
3295-
std::string msg = fname + ": immutable struct of type "
3296-
+ std::string(jl_symbol_name(sty->name->name))
3297-
+ " cannot be changed";
3298-
emit_error(ctx, msg);
3299-
return jl_cgval_t();
3300-
}
33013293
assert(strct.ispointer());
33023294
size_t byte_offset = jl_field_offset(sty, idx0);
33033295
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
35743566
else
35753567
need_wb = false;
35763568
emit_typecheck(ctx, rhs, jl_svecref(sty->types, i), "new"); // n.b. ty argument must be concrete
3577-
emit_setfield(ctx, sty, strctinfo, i, rhs, jl_cgval_t(), false, need_wb, AtomicOrdering::NotAtomic, AtomicOrdering::NotAtomic, false, true, false, false, false, nullptr, "");
3569+
emit_setfield(ctx, sty, strctinfo, i, rhs, jl_cgval_t(), need_wb, AtomicOrdering::NotAtomic, AtomicOrdering::NotAtomic, false, true, false, false, false, nullptr, "");
35783570
}
35793571
return strctinfo;
35803572
}

src/codegen.cpp

+27-14
Original file line numberDiff line numberDiff line change
@@ -2506,6 +2506,7 @@ static bool emit_f_opfield(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f,
25062506
bool isboxed = jl_field_isptr(uty, idx);
25072507
bool isatomic = jl_field_isatomic(uty, idx);
25082508
bool needlock = isatomic && !isboxed && jl_datatype_size(jl_field_type(uty, idx)) > MAX_ATOMIC_SIZE;
2509+
*ret = jl_cgval_t();
25092510
if (isatomic == (order == jl_memory_order_notatomic)) {
25102511
emit_atomic_error(ctx,
25112512
issetfield ?
@@ -2519,25 +2520,37 @@ static bool emit_f_opfield(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f,
25192520
: "swapfield!: non-atomic field cannot be written atomically") :
25202521
(isatomic ? "modifyfield!: atomic field cannot be written non-atomically"
25212522
: "modifyfield!: non-atomic field cannot be written atomically"));
2522-
*ret = jl_cgval_t();
2523-
return true;
25242523
}
2525-
if (isatomic == (fail_order == jl_memory_order_notatomic)) {
2524+
else if (isatomic == (fail_order == jl_memory_order_notatomic)) {
25262525
emit_atomic_error(ctx,
25272526
(isatomic ? "replacefield!: atomic field cannot be accessed non-atomically"
25282527
: "replacefield!: non-atomic field cannot be accessed atomically"));
2529-
*ret = jl_cgval_t();
2530-
return true;
25312528
}
2532-
*ret = emit_setfield(ctx, uty, obj, idx, val, cmp, true, true,
2533-
(needlock || order <= jl_memory_order_notatomic)
2534-
? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0
2535-
: get_llvm_atomic_order(order),
2536-
(needlock || fail_order <= jl_memory_order_notatomic)
2537-
? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0
2538-
: get_llvm_atomic_order(fail_order),
2539-
needlock, issetfield, isreplacefield, isswapfield, ismodifyfield,
2540-
modifyop, fname);
2529+
else if (!uty->name->mutabl) {
2530+
std::string msg = fname + ": immutable struct of type "
2531+
+ std::string(jl_symbol_name(uty->name->name))
2532+
+ " cannot be changed";
2533+
emit_error(ctx, msg);
2534+
}
2535+
else if (jl_field_isconst(uty, idx)) {
2536+
std::string msg = fname + ": const field ."
2537+
+ std::string(jl_symbol_name((jl_sym_t*)jl_svec_ref(jl_field_names(uty), idx)))
2538+
+ " of type "
2539+
+ std::string(jl_symbol_name(uty->name->name))
2540+
+ " cannot be changed";
2541+
emit_error(ctx, msg);
2542+
}
2543+
else {
2544+
*ret = emit_setfield(ctx, uty, obj, idx, val, cmp, true,
2545+
(needlock || order <= jl_memory_order_notatomic)
2546+
? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0
2547+
: get_llvm_atomic_order(order),
2548+
(needlock || fail_order <= jl_memory_order_notatomic)
2549+
? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0
2550+
: get_llvm_atomic_order(fail_order),
2551+
needlock, issetfield, isreplacefield, isswapfield, ismodifyfield,
2552+
modifyop, fname);
2553+
}
25412554
return true;
25422555
}
25432556
}

0 commit comments

Comments
 (0)