Skip to content

Commit 5ceefe4

Browse files
vtjnashmbauman
andcommitted
Allow const declarations on mutable fields
Mark some builtin types also, although Serialization relies upon being able to mutilate the Method objects, so we do not yet mark those. Replaces #11430 Co-authored-by: Matt Bauman <mbauman@gmail.com>
1 parent 5d41a76 commit 5ceefe4

File tree

18 files changed

+294
-165
lines changed

18 files changed

+294
-165
lines changed

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ New language features
1515
* ``, ``, and `` are now allowed as identifier characters ([#42314]).
1616
* `try`-blocks can now optionally have an `else`-block which is executed right after the main body only if
1717
no errors were thrown. ([#42211])
18+
* Mutable struct fields may now be annotated as `const` to prevent changing
19+
them after construction, providing for greater clarity and optimization
20+
ability of these objects ([#43305]).
1821

1922
Language changes
2023
----------------

base/compiler/ssair/passes.jl

Lines changed: 2 additions & 1 deletion
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
@@ -867,6 +867,7 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse
867867
stmt = ir[SSAValue(def)]::Expr # == `setfield!` call
868868
field = try_compute_fieldidx_stmt(ir, stmt, typ)
869869
field === nothing && @goto skip
870+
isconst(typ, field) && @goto skip # we discovered an attempt to mutate a const field, which must error
870871
push!(fielddefuse[field].defs, def)
871872
end
872873
# Check that the defexpr has defined values for all the fields

base/compiler/tfuncs.jl

Lines changed: 18 additions & 72 deletions
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

Lines changed: 24 additions & 1 deletion
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

Lines changed: 5 additions & 5 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 8 additions & 0 deletions
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

Lines changed: 4 additions & 12 deletions
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

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2496,6 +2496,7 @@ static bool emit_f_opfield(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f,
24962496
bool isboxed = jl_field_isptr(uty, idx);
24972497
bool isatomic = jl_field_isatomic(uty, idx);
24982498
bool needlock = isatomic && !isboxed && jl_datatype_size(jl_field_type(uty, idx)) > MAX_ATOMIC_SIZE;
2499+
*ret = jl_cgval_t();
24992500
if (isatomic == (order == jl_memory_order_notatomic)) {
25002501
emit_atomic_error(ctx,
25012502
issetfield ?
@@ -2509,25 +2510,37 @@ static bool emit_f_opfield(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f,
25092510
: "swapfield!: non-atomic field cannot be written atomically") :
25102511
(isatomic ? "modifyfield!: atomic field cannot be written non-atomically"
25112512
: "modifyfield!: non-atomic field cannot be written atomically"));
2512-
*ret = jl_cgval_t();
2513-
return true;
25142513
}
2515-
if (isatomic == (fail_order == jl_memory_order_notatomic)) {
2514+
else if (isatomic == (fail_order == jl_memory_order_notatomic)) {
25162515
emit_atomic_error(ctx,
25172516
(isatomic ? "replacefield!: atomic field cannot be accessed non-atomically"
25182517
: "replacefield!: non-atomic field cannot be accessed atomically"));
2519-
*ret = jl_cgval_t();
2520-
return true;
25212518
}
2522-
*ret = emit_setfield(ctx, uty, obj, idx, val, cmp, true, true,
2523-
(needlock || order <= jl_memory_order_notatomic)
2524-
? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0
2525-
: get_llvm_atomic_order(order),
2526-
(needlock || fail_order <= jl_memory_order_notatomic)
2527-
? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0
2528-
: get_llvm_atomic_order(fail_order),
2529-
needlock, issetfield, isreplacefield, isswapfield, ismodifyfield,
2530-
modifyop, fname);
2519+
else if (!uty->name->mutabl) {
2520+
std::string msg = fname + ": immutable struct of type "
2521+
+ std::string(jl_symbol_name(uty->name->name))
2522+
+ " cannot be changed";
2523+
emit_error(ctx, msg);
2524+
}
2525+
else if (jl_field_isconst(uty, idx)) {
2526+
std::string msg = fname + ": const field ."
2527+
+ std::string(jl_symbol_name((jl_sym_t*)jl_svec_ref(jl_field_names(uty), idx)))
2528+
+ " of type "
2529+
+ std::string(jl_symbol_name(uty->name->name))
2530+
+ " cannot be changed";
2531+
emit_error(ctx, msg);
2532+
}
2533+
else {
2534+
*ret = emit_setfield(ctx, uty, obj, idx, val, cmp, true,
2535+
(needlock || order <= jl_memory_order_notatomic)
2536+
? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0
2537+
: get_llvm_atomic_order(order),
2538+
(needlock || fail_order <= jl_memory_order_notatomic)
2539+
? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0
2540+
: get_llvm_atomic_order(fail_order),
2541+
needlock, issetfield, isreplacefield, isswapfield, ismodifyfield,
2542+
modifyop, fname);
2543+
}
25312544
return true;
25322545
}
25332546
}

0 commit comments

Comments
 (0)