Skip to content

Commit 04bd10f

Browse files
committed
Revert "Allow const declarations on mutable fields (JuliaLang#43305)"
1 parent 98b485e commit 04bd10f

19 files changed

+166
-295
lines changed

NEWS.md

-3
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@ 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]).
2219

2320
Language changes
2421
----------------

base/bitset.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const NO_OFFSET = Int === Int64 ? -one(Int) << 60 : -one(Int) << 29
1111
# a small optimization in the in(x, ::BitSet) method
1212

1313
mutable struct BitSet <: AbstractSet{Int}
14-
const bits::Vector{UInt64}
14+
bits::Vector{UInt64}
1515
# 1st stored Int equals 64*offset
1616
offset::Int
1717

base/compiler/ssair/passes.jl

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

base/compiler/tfuncs.jl

+72-18
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,19 @@ 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)
2729
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)
2840

2941
##########
3042
# tfuncs #
@@ -293,7 +305,7 @@ function isdefined_tfunc(@nospecialize(arg1), @nospecialize(sym))
293305
return Const(false)
294306
elseif isa(arg1, Const)
295307
arg1v = (arg1::Const).val
296-
if !ismutable(arg1v) || isdefined(arg1v, idx) || isconst(typeof(arg1v), idx)
308+
if !ismutable(arg1v) || isdefined(arg1v, idx) || (isa(arg1v, DataType) && is_dt_const_field(idx))
297309
return Const(isdefined(arg1v, idx))
298310
end
299311
elseif !isvatuple(a1)
@@ -635,6 +647,23 @@ function subtype_tfunc(@nospecialize(a), @nospecialize(b))
635647
end
636648
add_tfunc(<:, 2, 2, subtype_tfunc, 10)
637649

650+
is_dt_const_field(fld::Int) = (
651+
fld == DATATYPE_NAME_FIELDINDEX ||
652+
fld == DATATYPE_PARAMETERS_FIELDINDEX ||
653+
fld == DATATYPE_TYPES_FIELDINDEX ||
654+
fld == DATATYPE_SUPER_FIELDINDEX ||
655+
fld == DATATYPE_INSTANCE_FIELDINDEX ||
656+
fld == DATATYPE_HASH_FIELDINDEX
657+
)
658+
function const_datatype_getfield_tfunc(@nospecialize(sv), fld::Int)
659+
if fld == DATATYPE_INSTANCE_FIELDINDEX
660+
return isdefined(sv, fld) ? Const(getfield(sv, fld)) : Union{}
661+
elseif is_dt_const_field(fld) && isdefined(sv, fld)
662+
return Const(getfield(sv, fld))
663+
end
664+
return nothing
665+
end
666+
638667
function fieldcount_noerror(@nospecialize t)
639668
if t isa UnionAll || t isa Union
640669
t = argument_datatype(t)
@@ -773,27 +802,41 @@ function getfield_tfunc(@nospecialize(s00), @nospecialize(name))
773802
end
774803
if isa(name, Const)
775804
nv = name.val
776-
if isa(sv, Module)
777-
if isa(nv, Symbol)
778-
return abstract_eval_global(sv, nv)
779-
end
805+
if !(isa(nv,Symbol) || isa(nv,Int))
780806
return Bottom
781807
end
782-
if isa(nv, Symbol)
783-
nv = fieldindex(typeof(sv), nv, false)
808+
if isa(sv, UnionAll)
809+
if nv === :var || nv === 1
810+
return Const(sv.var)
811+
elseif nv === :body || nv === 2
812+
return Const(sv.body)
813+
end
814+
elseif isa(sv, DataType)
815+
idx = nv
816+
if isa(idx, Symbol)
817+
idx = fieldindex(DataType, idx, false)
818+
end
819+
if isa(idx, Int)
820+
t = const_datatype_getfield_tfunc(sv, idx)
821+
t === nothing || return t
822+
end
823+
elseif isa(sv, Core.TypeName)
824+
fld = isa(nv, Symbol) ? fieldindex(Core.TypeName, nv, false) : nv
825+
if (fld == TYPENAME_NAME_FIELDINDEX ||
826+
fld == TYPENAME_MODULE_FIELDINDEX ||
827+
fld == TYPENAME_WRAPPER_FIELDINDEX ||
828+
fld == TYPENAME_HASH_FIELDINDEX ||
829+
fld == TYPENAME_FLAGS_FIELDINDEX ||
830+
(fld == TYPENAME_NAMES_FIELDINDEX && isdefined(sv, fld)))
831+
return Const(getfield(sv, fld))
832+
end
784833
end
785-
if !isa(nv, Int)
786-
return Bottom
834+
if isa(sv, Module) && isa(nv, Symbol)
835+
return abstract_eval_global(sv, nv)
787836
end
788-
if isa(sv, DataType) && nv == DATATYPE_TYPES_FIELDINDEX && isdefined(sv, nv)
837+
if (isa(sv, SimpleVector) || !ismutable(sv)) && isdefined(sv, nv)
789838
return Const(getfield(sv, nv))
790839
end
791-
if isconst(typeof(sv), nv)
792-
if isdefined(sv, nv)
793-
return Const(getfield(sv, nv))
794-
end
795-
return Union{}
796-
end
797840
end
798841
s = typeof(sv)
799842
elseif isa(s00, PartialStruct)
@@ -813,11 +856,11 @@ function getfield_tfunc(@nospecialize(s00), @nospecialize(name))
813856
return Any
814857
end
815858
s = s::DataType
816-
if s <: Tuple && !(Int <: widenconst(name))
859+
if s <: Tuple && name Symbol
817860
return Bottom
818861
end
819862
if s <: Module
820-
if !(Symbol <: widenconst(name))
863+
if name Int
821864
return Bottom
822865
end
823866
return Any
@@ -878,6 +921,17 @@ function getfield_tfunc(@nospecialize(s00), @nospecialize(name))
878921
if fld < 1 || fld > nf
879922
return Bottom
880923
end
924+
if isconstType(s00)
925+
sp = s00.parameters[1]
926+
elseif isa(s00, Const)
927+
sp = s00.val
928+
else
929+
sp = nothing
930+
end
931+
if isa(sp, DataType)
932+
t = const_datatype_getfield_tfunc(sp, fld)
933+
t !== nothing && return t
934+
end
881935
R = ftypes[fld]
882936
if isempty(s.parameters)
883937
return R

base/reflection.jl

+1-24
Original file line numberDiff line numberDiff line change
@@ -248,34 +248,11 @@ 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 `m`.
251+
Determine whether a global is declared `const` in a given `Module`.
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-
279256
"""
280257
@locals()
281258

doc/src/base/base.md

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

191186
### Memory layout
@@ -195,6 +190,9 @@ Base.sizeof(::Type)
195190
Base.isconcretetype
196191
Base.isbits
197192
Base.isbitstype
193+
Core.fieldtype
194+
Base.fieldtypes
195+
Base.fieldcount
198196
Base.fieldoffset
199197
Base.datatype_alignment
200198
Base.datatype_haspadding
@@ -420,6 +418,8 @@ Base.@__DIR__
420418
Base.@__LINE__
421419
Base.fullname
422420
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 (symbol? e) (and (pair? e) (memq (car e) '(|::| atomic const)) (eventually-decl? (cadr e)))))
381+
(or (decl? e) (and (pair? e) (eq? (car e) 'atomic) (symdecl? (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,10 +855,6 @@ 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-
}
862858
return idx;
863859
}
864860

@@ -1608,10 +1604,6 @@ static int equiv_type(jl_value_t *ta, jl_value_t *tb)
16081604
? dtb->name->atomicfields == NULL
16091605
: (dtb->name->atomicfields != NULL &&
16101606
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)) &&
16151607
jl_egal((jl_value_t*)jl_field_names(dta), (jl_value_t*)jl_field_names(dtb)) &&
16161608
jl_nparams(dta) == jl_nparams(dtb)))
16171609
return 0;

src/cgutils.cpp

+12-4
Original file line numberDiff line numberDiff line change
@@ -2251,10 +2251,10 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st
22512251
else {
22522252
ptindex = emit_struct_gep(ctx, cast<StructType>(lt), staddr, byte_offset + fsz);
22532253
}
2254-
return emit_unionload(ctx, addr, ptindex, jfty, fsz, al, tbaa, !jl_field_isconst(jt, idx), union_max, tbaa_unionselbyte);
2254+
return emit_unionload(ctx, addr, ptindex, jfty, fsz, al, tbaa, jt->name->mutabl, union_max, tbaa_unionselbyte);
22552255
}
22562256
assert(jl_is_concrete_type(jfty));
2257-
if (jl_field_isconst(jt, idx) && !(maybe_null && (jfty == (jl_value_t*)jl_bool_type ||
2257+
if (!jt->name->mutabl && !(maybe_null && (jfty == (jl_value_t*)jl_bool_type ||
22582258
((jl_datatype_t*)jfty)->layout->npointers))) {
22592259
// just compute the pointer and let user load it when necessary
22602260
return mark_julia_slot(addr, jfty, NULL, tbaa);
@@ -3295,13 +3295,21 @@ static void emit_write_multibarrier(jl_codectx_t &ctx, Value *parent, Value *agg
32953295
emit_write_barrier(ctx, parent, ptrs);
32963296
}
32973297

3298+
32983299
static jl_cgval_t emit_setfield(jl_codectx_t &ctx,
32993300
jl_datatype_t *sty, const jl_cgval_t &strct, size_t idx0,
33003301
jl_cgval_t rhs, jl_cgval_t cmp,
3301-
bool wb, AtomicOrdering Order, AtomicOrdering FailOrder,
3302+
bool checked, bool wb, AtomicOrdering Order, AtomicOrdering FailOrder,
33023303
bool needlock, bool issetfield, bool isreplacefield, bool isswapfield, bool ismodifyfield,
33033304
const jl_cgval_t *modifyop, const std::string &fname)
33043305
{
3306+
if (!sty->name->mutabl && checked) {
3307+
std::string msg = fname + ": immutable struct of type "
3308+
+ std::string(jl_symbol_name(sty->name->name))
3309+
+ " cannot be changed";
3310+
emit_error(ctx, msg);
3311+
return jl_cgval_t();
3312+
}
33053313
assert(strct.ispointer());
33063314
size_t byte_offset = jl_field_offset(sty, idx0);
33073315
Value *addr = data_pointer(ctx, strct);
@@ -3578,7 +3586,7 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg
35783586
else
35793587
need_wb = false;
35803588
emit_typecheck(ctx, rhs, jl_svecref(sty->types, i), "new"); // n.b. ty argument must be concrete
3581-
emit_setfield(ctx, sty, strctinfo, i, rhs, jl_cgval_t(), need_wb, AtomicOrdering::NotAtomic, AtomicOrdering::NotAtomic, false, true, false, false, false, nullptr, "");
3589+
emit_setfield(ctx, sty, strctinfo, i, rhs, jl_cgval_t(), false, need_wb, AtomicOrdering::NotAtomic, AtomicOrdering::NotAtomic, false, true, false, false, false, nullptr, "");
35823590
}
35833591
return strctinfo;
35843592
}

src/codegen.cpp

+14-27
Original file line numberDiff line numberDiff line change
@@ -2503,7 +2503,6 @@ static bool emit_f_opfield(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f,
25032503
bool isboxed = jl_field_isptr(uty, idx);
25042504
bool isatomic = jl_field_isatomic(uty, idx);
25052505
bool needlock = isatomic && !isboxed && jl_datatype_size(jl_field_type(uty, idx)) > MAX_ATOMIC_SIZE;
2506-
*ret = jl_cgval_t();
25072506
if (isatomic == (order == jl_memory_order_notatomic)) {
25082507
emit_atomic_error(ctx,
25092508
issetfield ?
@@ -2517,37 +2516,25 @@ static bool emit_f_opfield(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f,
25172516
: "swapfield!: non-atomic field cannot be written atomically") :
25182517
(isatomic ? "modifyfield!: atomic field cannot be written non-atomically"
25192518
: "modifyfield!: non-atomic field cannot be written atomically"));
2519+
*ret = jl_cgval_t();
2520+
return true;
25202521
}
2521-
else if (isatomic == (fail_order == jl_memory_order_notatomic)) {
2522+
if (isatomic == (fail_order == jl_memory_order_notatomic)) {
25222523
emit_atomic_error(ctx,
25232524
(isatomic ? "replacefield!: atomic field cannot be accessed non-atomically"
25242525
: "replacefield!: non-atomic field cannot be accessed atomically"));
2526+
*ret = jl_cgval_t();
2527+
return true;
25252528
}
2526-
else if (!uty->name->mutabl) {
2527-
std::string msg = fname + ": immutable struct of type "
2528-
+ std::string(jl_symbol_name(uty->name->name))
2529-
+ " cannot be changed";
2530-
emit_error(ctx, msg);
2531-
}
2532-
else if (jl_field_isconst(uty, idx)) {
2533-
std::string msg = fname + ": const field ."
2534-
+ std::string(jl_symbol_name((jl_sym_t*)jl_svec_ref(jl_field_names(uty), idx)))
2535-
+ " of type "
2536-
+ std::string(jl_symbol_name(uty->name->name))
2537-
+ " cannot be changed";
2538-
emit_error(ctx, msg);
2539-
}
2540-
else {
2541-
*ret = emit_setfield(ctx, uty, obj, idx, val, cmp, true,
2542-
(needlock || order <= jl_memory_order_notatomic)
2543-
? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0
2544-
: get_llvm_atomic_order(order),
2545-
(needlock || fail_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(fail_order),
2548-
needlock, issetfield, isreplacefield, isswapfield, ismodifyfield,
2549-
modifyop, fname);
2550-
}
2529+
*ret = emit_setfield(ctx, uty, obj, idx, val, cmp, true, true,
2530+
(needlock || order <= jl_memory_order_notatomic)
2531+
? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0
2532+
: get_llvm_atomic_order(order),
2533+
(needlock || fail_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(fail_order),
2536+
needlock, issetfield, isreplacefield, isswapfield, ismodifyfield,
2537+
modifyop, fname);
25512538
return true;
25522539
}
25532540
}

0 commit comments

Comments
 (0)