Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Make mutating immutables easier #21912

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ export
Expr, GotoNode, LabelNode, LineNumberNode, QuoteNode,
GlobalRef, NewvarNode, SSAValue, Slot, SlotNumber, TypedSlot,
# object model functions
fieldtype, getfield, setfield!, nfields, throw, tuple, ===, isdefined, eval,
fieldtype, getfield, setfield!, nfields, throw, tuple, ===,
isdefined, eval,
# sizeof # not exported, to avoid conflicting with Base.sizeof
# type reflection
issubtype, typeof, isa, typeassert,
Expand Down
5 changes: 5 additions & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,11 @@ export
catch_stacktrace,

# types
gepfield,
gepindex,
setfield,
setindex,
@setfield,
convert,
fieldoffset,
fieldname,
Expand Down
11 changes: 10 additions & 1 deletion base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -376,9 +376,18 @@ julia> Base.fieldindex(Foo, :z, false)
```
"""
function fieldindex(T::DataType, name::Symbol, err::Bool=true)
return Int(ccall(:jl_field_index, Cint, (Any, Any, Cint), T, name, err)+1)
@_pure_meta
i = 1
for fld in T.name.names
name == fld && return i
i += 1
end
err && error("type $T has no field $name")
return -1
end

fieldisptr(T::DataType, idx::Integer) = 1 == (@_pure_meta; ccall(:jl_get_field_isptr, Cint, (Any, Cint), T, idx))

type_alignment(x::DataType) = (@_pure_meta; ccall(:jl_get_alignment, Csize_t, (Any,), x))

# return all instances, for types that can be enumerated
Expand Down
125 changes: 125 additions & 0 deletions base/refpointer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,21 @@ end
RefValue(x::T) where {T} = RefValue{T}(x)
isassigned(x::RefValue) = isdefined(x, :x)

global _gepvalue
# Special for r::RefValue, r@a means r@x.a
gepfield(r::RefValue, sym::Symbol) = (@_inline_meta; gepfield(_gepfield(r, 1), sym))
gepfield(r::RefValue, idx::Integer) = (@_inline_meta; gepfield(_gepfield(r, 1), idx))
gepindex(r::RefValue, idxs...) = (@_inline_meta; gepindex(_gepfield(r, 1), idxs...))


Ref(x::Ref) = x
Ref(x::Any) = RefValue(x)
Ref(x::Ptr{T}, i::Integer=1) where {T} = x + (i-1)*Core.sizeof(T)
Ref(x, i::Integer) = (i != 1 && error("Object only has one element"); Ref(x))
Ref{T}() where {T} = RefValue{T}() # Ref{T}()
Ref{T}(x) where {T} = RefValue{T}(x) # Ref{T}(x)
convert(::Type{Ref{T}}, x) where {T} = RefValue{T}(x)
copy(x::RefValue{T}) where {T} = RefValue{T}(x.x)

function unsafe_convert(P::Type{Ptr{T}}, b::RefValue{T}) where T
if isbits(T)
Expand Down Expand Up @@ -113,6 +121,123 @@ cconvert(::Type{Ref{P}}, a::Array{<:Ptr}) where {P<:Ptr} = a
cconvert(::Type{Ptr{P}}, a::Array) where {P<:Union{Ptr,Cwstring,Cstring}} = Ref{P}(a)
cconvert(::Type{Ref{P}}, a::Array) where {P<:Union{Ptr,Cwstring,Cstring}} = Ref{P}(a)


## RefField
struct RefField{T} <: Ref{T}
base
offset::UInt
# Basic constructors
global _gepfield
function _gepfield(x::ANY, idx::Integer)
@_inline_meta
typeof(x).mutable || error("Tried to take reference to immutable type $(typeof(x))")
new{fieldtype(typeof(x), idx)}(x, fieldoffset(typeof(x), idx))
end
function _gepfield(x::RefField{T}, idx::Integer) where {T}
@_inline_meta
!fieldisptr(T, idx) || error("Can only take interior references that are inline (e.g. immutable). Tried to access field \"$(fieldname(T, idx))\" of type $T")
new{fieldtype(T, idx)}(x.base, x.offset + fieldoffset(T, idx))
end
end

function _gepfield(x::ANY, sym::Symbol)
@_inline_meta
_gepfield(x, Base.fieldindex(typeof(x), sym))
end
function _gepfield(x::RefField{T}, sym::Symbol) where T
@_inline_meta
_gepfield(x, Base.fieldindex(T, sym))
end

gepfield(x::ANY, idx::Integer) = (@_inline_meta; _gepfield(x, idx))
gepfield(x::RefField{T}, idx::Integer) where {T} = (@_inline_meta; _gepfield(x, idx))
gepfield(x::ANY, idx::Symbol) = (@_inline_meta; _gepfield(x, idx))
gepfield(x::RefField{T}, idx::Symbol) where {T} = (@_inline_meta; _gepfield(x, idx))

# Tuple is defined before us in bootstrap, so it can't refer to RefField
gepindex(x::RefField{<:Tuple}, idx) = (@_inline_meta; gepfield(x, idx))

function setindex!(x::RefField{T}, v::T) where T
@_inline_meta
unsafe_store!(Ptr{T}(pointer_from_objref(x.base)+x.offset), v)
v
end
setindex!(x::RefField{T}, v::S) where {T,S} = (@_inline_meta; setindex!(x, convert(T, v)::T))

function getindex(x::RefField{T}) where T
@_inline_meta
unsafe_load(Ptr{T}(pointer_from_objref(x.base)+x.offset))
end

function setfield(x, sym, v)
@_inline_meta
if typeof(x).mutable
y = copy(x)
setfield!(y, sym, v)
y
else
y = Ref{typeof(x)}(x)
(gepfield(y@[], sym))[] = v
y[]
end
end

function setindex(x, v, idxs...)
@_inline_meta
if typeof(x).mutable
y = copy(x)
setindex!(y, v, idxs...)
y
else
y = Ref{typeof(x)}(x)
(gepindex(y@[], idxs...))[] = v
y[]
end
end

macro setfield(base, idx)
if idx.head != :(=)
error("Expected assignment as second argument")
end
idx, rhs = idx.args
x, v = ntuple(i->gensym(), 2)
setupexprs = Expr[:($x = $base), :($v = $rhs)]
getfieldexprs, setfieldexprs = Expr[], Expr[]
res = nothing
while true
y, nv = ntuple(i->gensym(), 2)
if isa(idx, Symbol)
idx = Expr(:quote, idx)
(res !== nothing) && push!(getfieldexprs, :($res = getfield($x, $idx)))
push!(setfieldexprs, :($nv = setfield($x, $idx, $v)))
break
elseif idx.head == :vect
t = gensym()
(res !== nothing) && push!(getfieldexprs, :($res = getindex($x, $t...)))
push!(getfieldexprs, :($t = tuple($(idx.args...))))
push!(setfieldexprs, :($nv = setindex($x, $v, $t...)))
break
elseif idx.head == :(.)
(res !== nothing) && push!(getfieldexprs, :($res = getfield($y, $(idx.args[2]))))
push!(setfieldexprs, :($nv = setfield($y, $(idx.args[2]), $v)))
res, v, idx = y, nv, idx.args[1]
elseif idx.head == :ref
t = gensym()
(res !== nothing) && push!(getfieldexprs, :($res = getindex($y, $t...)))
push!(getfieldexprs, :($t = tuple($(idx.args[2:end]...))))
push!(setfieldexprs, :($nv = setindex($y, $v, $t...)))
res, v, idx = y, nv, idx.args[1]
else
error("Unknown field ref syntax")
end
end
push!(getfieldexprs, :($y = $x))
esc(Expr(:block,
setupexprs...,
reverse(getfieldexprs)...,
setfieldexprs...))
end

###

getindex(b::RefValue) = b.x
Expand Down
60 changes: 40 additions & 20 deletions base/tuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,10 @@ getindex(t::Tuple, i::Int) = getfield(t, i)
getindex(t::Tuple, i::Real) = getfield(t, convert(Int, i))
getindex(t::Tuple, r::AbstractArray{<:Any,1}) = ([t[ri] for ri in r]...)
getindex(t::Tuple, b::AbstractArray{Bool,1}) = length(b) == length(t) ? getindex(t,find(b)) : throw(BoundsError(t, b))
gepindex(x::Tuple, i) = gepfield(x, i)

# returns new tuple; N.B.: becomes no-op if i is out-of-bounds
setindex(x::Tuple, v, i::Integer) = _setindex((), x, v, i::Integer)
function _setindex(y::Tuple, r::Tuple, v, i::Integer)
@_inline_meta
_setindex((y..., ifelse(length(y) + 1 == i, v, first(r))), tail(r), v, i)
end
_setindex(y::Tuple, r::Tuple{}, v, i::Integer) = y
# returns new tuple
setindex(x::Tuple, v, i::Integer) = setfield(x, i, v)

## iterating ##

Expand Down Expand Up @@ -105,19 +101,29 @@ julia> ntuple(i -> 2*i, 4)
```
"""
function ntuple(f::F, n::Integer) where F
t = n == 0 ? () :
n == 1 ? (f(1),) :
n == 2 ? (f(1), f(2)) :
n == 3 ? (f(1), f(2), f(3)) :
n == 4 ? (f(1), f(2), f(3), f(4)) :
n == 5 ? (f(1), f(2), f(3), f(4), f(5)) :
n == 6 ? (f(1), f(2), f(3), f(4), f(5), f(6)) :
n == 7 ? (f(1), f(2), f(3), f(4), f(5), f(6), f(7)) :
n == 8 ? (f(1), f(2), f(3), f(4), f(5), f(6), f(7), f(8)) :
n == 9 ? (f(1), f(2), f(3), f(4), f(5), f(6), f(7), f(8), f(9)) :
n == 10 ? (f(1), f(2), f(3), f(4), f(5), f(6), f(7), f(8), f(9), f(10)) :
_ntuple(f, n)
return t
RT = Core.Inference.return_type(f, Tuple{Int})
TT = NTuple{n, RT}
if isbits(RT)
r = Ref{TT}()
for i = 1:n
r@[i] = f(i)
end
return r[]
else
t = n == 0 ? () :
n == 1 ? (f(1),) :
n == 2 ? (f(1), f(2)) :
n == 3 ? (f(1), f(2), f(3)) :
n == 4 ? (f(1), f(2), f(3), f(4)) :
n == 5 ? (f(1), f(2), f(3), f(4), f(5)) :
n == 6 ? (f(1), f(2), f(3), f(4), f(5), f(6)) :
n == 7 ? (f(1), f(2), f(3), f(4), f(5), f(6), f(7)) :
n == 8 ? (f(1), f(2), f(3), f(4), f(5), f(6), f(7), f(8)) :
n == 9 ? (f(1), f(2), f(3), f(4), f(5), f(6), f(7), f(8), f(9)) :
n == 10 ? (f(1), f(2), f(3), f(4), f(5), f(6), f(7), f(8), f(9), f(10)) :
_ntuple(f, n)
return t::TT
end
end

function _ntuple(f, n)
Expand All @@ -126,6 +132,7 @@ function _ntuple(f, n)
([f(i) for i = 1:n]...)
end


# inferrable ntuple
ntuple(f, ::Type{Val{0}}) = (@_inline_meta; ())
ntuple(f, ::Type{Val{1}}) = (@_inline_meta; (f(1),))
Expand Down Expand Up @@ -157,6 +164,7 @@ function _ntuple(out::NTuple{M,Any}, f::F, ::Type{Val{N}}) where {F,N,M}
_ntuple((out..., f(M+1)), f, Val{N})
end


# 1 argument function
map(f, t::Tuple{}) = ()
map(f, t::Tuple{Any,}) = (f(t[1]),)
Expand Down Expand Up @@ -310,6 +318,18 @@ function isless(t1::Tuple, t2::Tuple)
return n1 < n2
end

function convert(::Type{NTuple{N, S}}, x::NTuple{N, T} where T) where {N, S}
if isbits(S)
r = Ref{NTuple{N, S}}()
for i = 1:N
r@[i] = convert(S, x[i])
end
return r[]
else
return tuple([convert(S, x[i]) for i = 1:N]...)
end
end

## functions ##

isempty(x::Tuple{}) = true
Expand Down
1 change: 1 addition & 0 deletions src/builtin_proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ DECLARE_BUILTIN(_apply_latest);
DECLARE_BUILTIN(isdefined); DECLARE_BUILTIN(nfields);
DECLARE_BUILTIN(tuple); DECLARE_BUILTIN(svec);
DECLARE_BUILTIN(getfield); DECLARE_BUILTIN(setfield);
DECLARE_BUILTIN(setfield_bang);
DECLARE_BUILTIN(fieldtype); DECLARE_BUILTIN(arrayref);
DECLARE_BUILTIN(arrayset); DECLARE_BUILTIN(arraysize);
DECLARE_BUILTIN(apply_type); DECLARE_BUILTIN(applicable);
Expand Down
33 changes: 31 additions & 2 deletions src/builtins.c
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ JL_CALLABLE(jl_f_getfield)
return fval;
}

JL_CALLABLE(jl_f_setfield)
JL_CALLABLE(jl_f_setfield_bang)
{
JL_NARGS(setfield!, 3, 3);
jl_value_t *v = args[0];
Expand Down Expand Up @@ -650,6 +650,34 @@ JL_CALLABLE(jl_f_setfield)
return args[2];
}

JL_CALLABLE(jl_f_setfield)
{
JL_NARGS(setfield, 3, 3);
jl_value_t *v = args[0];
jl_value_t *vt = (jl_value_t*)jl_typeof(v);
if (vt == (jl_value_t*)jl_module_type || !jl_is_datatype(vt))
jl_type_error("setfield", (jl_value_t*)jl_datatype_type, v);
jl_datatype_t *st = (jl_datatype_t*)vt;
size_t idx;
if (jl_is_long(args[1])) {
idx = jl_unbox_long(args[1])-1;
if (idx >= jl_datatype_nfields(st))
jl_bounds_error(args[0], args[1]);
}
else {
JL_TYPECHK(setfield!, symbol, args[1]);
idx = jl_field_index(st, (jl_sym_t*)args[1], 1);
}
jl_value_t *ft = jl_field_type(st,idx);
if (!jl_isa(args[2], ft)) {
jl_type_error("setfield", ft, args[2]);
}
jl_value_t *newv = jl_new_struct_uninit(vt);
memcpy(newv, v, st->size);
jl_set_nth_field(newv, idx, args[2]);
return newv;
}

static jl_value_t *get_fieldtype(jl_value_t *t, jl_value_t *f)
{
if (jl_is_unionall(t)) {
Expand Down Expand Up @@ -1077,7 +1105,8 @@ void jl_init_primitives(void)

// field access
add_builtin_func("getfield", jl_f_getfield);
add_builtin_func("setfield!", jl_f_setfield);
add_builtin_func("setfield", jl_f_setfield);
add_builtin_func("setfield!", jl_f_setfield_bang);
add_builtin_func("fieldtype", jl_f_fieldtype);
add_builtin_func("nfields", jl_f_nfields);
add_builtin_func("isdefined", jl_f_isdefined);
Expand Down
Loading