diff --git a/base/boot.jl b/base/boot.jl index d0a686e761bf5..41e86a44f2c34 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -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, diff --git a/base/exports.jl b/base/exports.jl index 039c5328f9bb1..632e54df44020 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -978,6 +978,11 @@ export catch_stacktrace, # types + gepfield, + gepindex, + setfield, + setindex, + @setfield, convert, fieldoffset, fieldname, diff --git a/base/reflection.jl b/base/reflection.jl index 58d9b67e19177..65785d6264380 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -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 diff --git a/base/refpointer.jl b/base/refpointer.jl index 087a6e0786490..a98dcd4c3a2e5 100644 --- a/base/refpointer.jl +++ b/base/refpointer.jl @@ -44,6 +44,13 @@ 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) @@ -51,6 +58,7 @@ 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) @@ -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 diff --git a/base/tuple.jl b/base/tuple.jl index dc041a47937fb..35d24bc6884b3 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -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 ## @@ -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) @@ -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),)) @@ -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]),) @@ -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 diff --git a/src/builtin_proto.h b/src/builtin_proto.h index 754e9824ac4b0..d964ac561e3db 100644 --- a/src/builtin_proto.h +++ b/src/builtin_proto.h @@ -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); diff --git a/src/builtins.c b/src/builtins.c index e7f29e0051e73..6ec0abe203efe 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -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]; @@ -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)) { @@ -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); diff --git a/src/cgutils.cpp b/src/cgutils.cpp index 06c06215dfad1..47b7a9964b584 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -1215,7 +1215,7 @@ static jl_cgval_t typed_load(Value *ptr, Value *idx_0based, jl_value_t *jltype, static void typed_store(Value *ptr, Value *idx_0based, const jl_cgval_t &rhs, jl_value_t *jltype, jl_codectx_t *ctx, MDNode *tbaa, Value *parent, // for the write barrier, NULL if no barrier needed - unsigned alignment = 0, bool root_box = true) // if the value to store needs a box, should we root it ? + unsigned alignment = 0, bool root_box = true, bool inbounds = false) // if the value to store needs a box, should we root it ? { bool isboxed; Type *elty = julia_type_to_llvm(jltype, &isboxed); @@ -1234,8 +1234,9 @@ static void typed_store(Value *ptr, Value *idx_0based, const jl_cgval_t &rhs, data = emit_bitcast(ptr, PointerType::get(elty, 0)); else data = ptr; - Instruction *store = builder.CreateAlignedStore(r, builder.CreateGEP(data, - idx_0based), isboxed ? alignment : julia_alignment(r, jltype, alignment)); + Instruction *store = builder.CreateAlignedStore(r, + inbounds ? builder.CreateInBoundsGEP(data, idx_0based) : builder.CreateGEP(data, idx_0based), + isboxed ? alignment : julia_alignment(r, jltype, alignment)); if (tbaa) tbaa_decorate(tbaa, store); } @@ -1340,6 +1341,40 @@ static Value *data_pointer(const jl_cgval_t &x, jl_codectx_t *ctx, Type *astype return data; } +static bool emit_setfield_unknownidx(jl_cgval_t &strct, Value *idx, jl_datatype_t *stt, const jl_cgval_t &rhs, jl_codectx_t *ctx) +{ + size_t nfields = jl_datatype_nfields(stt); + if (strct.ispointer()) { // boxed or stack + if (is_datatype_all_pointers(stt)) { + idx = emit_bounds_check(strct, (jl_value_t*)stt, idx, ConstantInt::get(T_size, nfields), ctx); + typed_store(data_pointer(strct, ctx), idx, rhs, (jl_value_t*)jl_any_type, ctx, + strct.tbaa, data_pointer(strct, ctx, T_pjlvalue), 8, true, true); + } + else if (is_tupletype_homogeneous(stt->types)) { + assert(nfields > 0); // nf == 0 trapped by all_pointers case + jl_value_t *jt = jl_field_type(stt, 0); + idx = emit_bounds_check(strct, (jl_value_t*)stt, idx, ConstantInt::get(T_size, nfields), ctx); + typed_store(data_pointer(strct, ctx), idx, rhs, jt, ctx, + strct.tbaa, data_pointer(strct, ctx, T_pjlvalue), + jl_datatype_size(jt), true, true); + return true; + } + } + else if (is_tupletype_homogeneous(stt->types)) { + assert(jl_isbits(stt)); + if (nfields == 0 || strct.isghost) { + return false; + } + assert(!jl_field_isptr(stt, 0)); + jl_value_t *jt = jl_field_type(stt, 0); + Value *idx0 = emit_bounds_check(strct, (jl_value_t*)stt, idx, ConstantInt::get(T_size, nfields), ctx); + Value *vrhs = emit_unbox(cast(strct.V->getType())->getElementType(), rhs, jt); + strct.V = builder.CreateInsertElement(strct.V, vrhs, idx0); + return true; + } + return false; +} + static bool emit_getfield_unknownidx(jl_cgval_t *ret, const jl_cgval_t &strct, Value *idx, jl_datatype_t *stt, jl_codectx_t *ctx) { @@ -1418,6 +1453,36 @@ static bool emit_getfield_unknownidx(jl_cgval_t *ret, const jl_cgval_t &strct, return false; } +static void emit_checked_write_barrier(jl_codectx_t *ctx, Value *parent, Value *ptr); +static void emit_setfield_knownindex(jl_datatype_t *sty, const jl_cgval_t &strct, size_t idx0, + const jl_cgval_t &rhs, jl_codectx_t *ctx, bool checked, + bool wb) +{ + if (sty->mutabl || !checked) { + assert(strct.ispointer()); + Value *addr = builder.CreateGEP(data_pointer(strct, ctx, T_pint8), + ConstantInt::get(T_size, jl_field_offset(sty, idx0))); + jl_value_t *jfty = jl_svecref(sty->types, idx0); + if (jl_field_isptr(sty, idx0)) { + Value *r = boxed(rhs, ctx, false); // don't need a temporary gcroot since it'll be rooted by strct (but should ensure strct is rooted via mark_gc_use) + tbaa_decorate(strct.tbaa, builder.CreateStore(r, emit_bitcast(addr, T_ppjlvalue))); + if (wb && strct.isboxed) emit_checked_write_barrier(ctx, boxed(strct, ctx), r); + mark_gc_use(strct); + } + else { + int align = jl_field_offset(sty, idx0); + align |= 16; + align &= -align; + typed_store(addr, ConstantInt::get(T_size, 0), rhs, jfty, ctx, + strct.tbaa, data_pointer(strct, ctx, T_pjlvalue), align); + } + } + else { + // TODO: better error + emit_error("type is immutable", ctx); + } +} + static jl_cgval_t emit_getfield_knownidx(const jl_cgval_t &strct, unsigned idx, jl_datatype_t *jt, jl_codectx_t *ctx) { jl_value_t *jfty = jl_field_type(jt, idx); @@ -2227,34 +2292,6 @@ static void emit_checked_write_barrier(jl_codectx_t *ctx, Value *parent, Value * builder.SetInsertPoint(cont); } -static void emit_setfield(jl_datatype_t *sty, const jl_cgval_t &strct, size_t idx0, - const jl_cgval_t &rhs, jl_codectx_t *ctx, bool checked, bool wb) -{ - if (sty->mutabl || !checked) { - assert(strct.ispointer()); - Value *addr = builder.CreateGEP(data_pointer(strct, ctx, T_pint8), - ConstantInt::get(T_size, jl_field_offset(sty, idx0))); - jl_value_t *jfty = jl_svecref(sty->types, idx0); - if (jl_field_isptr(sty, idx0)) { - Value *r = boxed(rhs, ctx, false); // don't need a temporary gcroot since it'll be rooted by strct (but should ensure strct is rooted via mark_gc_use) - tbaa_decorate(strct.tbaa, builder.CreateStore(r, emit_bitcast(addr, T_ppjlvalue))); - if (wb && strct.isboxed) emit_checked_write_barrier(ctx, boxed(strct, ctx), r); - mark_gc_use(strct); - } - else { - int align = jl_field_offset(sty, idx0); - align |= 16; - align &= -align; - typed_store(addr, ConstantInt::get(T_size, 0), rhs, jfty, ctx, - strct.tbaa, data_pointer(strct, ctx, T_pjlvalue), align); - } - } - else { - // TODO: better error - emit_error("type is immutable", ctx); - } -} - static bool might_need_root(jl_value_t *ex) { return (!jl_is_symbol(ex) && !jl_is_slot(ex) && !jl_is_ssavalue(ex) && @@ -2266,7 +2303,6 @@ static jl_cgval_t emit_new_struct(jl_value_t *ty, size_t nargs, jl_value_t **arg { assert(jl_is_datatype(ty)); assert(jl_is_leaf_type(ty)); - assert(nargs>0); jl_datatype_t *sty = (jl_datatype_t*)ty; size_t nf = jl_datatype_nfields(sty); if (nf > 0) { @@ -2345,7 +2381,7 @@ static jl_cgval_t emit_new_struct(jl_value_t *ty, size_t nargs, jl_value_t **arg } if (might_need_root(args[i])) // TODO: how to remove this? need_wb = true; - emit_setfield(sty, strctinfo, i - 1, rhs, ctx, false, need_wb); + emit_setfield_knownindex(sty, strctinfo, i - 1, rhs, ctx, false, need_wb); } return strctinfo; } @@ -2367,6 +2403,53 @@ static jl_cgval_t emit_new_struct(jl_value_t *ty, size_t nargs, jl_value_t **arg } } +static jl_cgval_t emit_object_copy(jl_cgval_t tocopy, jl_codectx_t *ctx) +{ + jl_datatype_t *sty = (jl_datatype_t*)tocopy.typ; + size_t nf = jl_datatype_nfields(sty); + if (nf > 0) { + Value *strct; + bool init_as_value = false; + if (jl_isbits(sty)) { + Type *lt = julia_type_to_llvm((jl_value_t*)sty); + // whether we should perform the initialization with the struct as a IR value + // or instead initialize the stack buffer with stores + bool init_as_value = false; + if (lt->isVectorTy() || + jl_is_vecelement_type((jl_value_t*)sty) || + type_is_ghost(lt)) // maybe also check the size ? + init_as_value = true; + + if (init_as_value) + strct = lt == T_void ? UndefValue::get(NoopType) : + tocopy.ispointer() ? builder.CreateLoad(tocopy.V) : + tocopy.V; + else { + strct = emit_static_alloca(lt); + } + } else { + strct = emit_allocobj(ctx, jl_datatype_size(sty), + literal_pointer_val((jl_value_t*)sty)); + } + if (!init_as_value) { + if (tocopy.ispointer()) { + unsigned julia_alignment = 1; + if (sty->layout) + julia_alignment = sty->layout->alignment; + builder.CreateMemCpy(strct, tocopy.V, jl_datatype_size(sty), julia_alignment); + } else { + builder.CreateStore(tocopy.V, strct); + } + } + if (init_as_value) + return mark_julia_type(strct, !jl_isbits(sty), (jl_value_t*)sty, ctx); + else + return mark_julia_slot(strct, (jl_value_t*)sty, NULL, tbaa_stack);; + } else { + return emit_new_struct(tocopy.typ, 0, NULL, ctx); + } +} + static Value *emit_exc_in_transit(jl_codectx_t *ctx) { Value *pexc_in_transit = emit_bitcast(ctx->ptlsStates, T_ppjlvalue); diff --git a/src/codegen.cpp b/src/codegen.cpp index a3cd82040a16e..96baa52da975c 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2638,6 +2638,23 @@ static Value *emit_f_is(const jl_cgval_t &arg1, const jl_cgval_t &arg2, jl_codec #endif } +static size_t compute_setfield_index(jl_datatype_t *uty, jl_value_t **args, jl_codectx_t *ctx) { + size_t idx = (size_t)-1; + if (jl_is_quotenode(args[2]) && jl_is_symbol(jl_fieldref(args[2],0))) { + idx = jl_field_index(uty, (jl_sym_t*)jl_fieldref(args[2],0), 0); + } + else if (jl_is_long(args[2]) || (jl_is_quotenode(args[2]) && jl_is_long(jl_fieldref(args[2],0)))) { + ssize_t i; + if (jl_is_long(args[2])) + i = jl_unbox_long(args[2]); + else + i = jl_unbox_long(jl_fieldref(args[2],0)); + if (i > 0 && i <= jl_datatype_nfields(uty)) + idx = i-1; + } + return idx; +} + static bool emit_builtin_call(jl_cgval_t *ret, jl_value_t *f, jl_value_t **args, size_t nargs, jl_codectx_t *ctx, jl_value_t *expr) // returns true if the call has been handled @@ -3048,20 +3065,40 @@ static bool emit_builtin_call(jl_cgval_t *ret, jl_value_t *f, jl_value_t **args, jl_datatype_t *sty = (jl_datatype_t*)expr_type(args[1], ctx); rt1 = (jl_value_t*)sty; jl_datatype_t *uty = (jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)sty); - if (jl_is_structtype(uty) && uty != jl_module_type) { - size_t idx = (size_t)-1; - if (jl_is_quotenode(args[2]) && jl_is_symbol(jl_fieldref(args[2],0))) { - idx = jl_field_index(uty, (jl_sym_t*)jl_fieldref(args[2],0), 0); - } - else if (jl_is_long(args[2]) || (jl_is_quotenode(args[2]) && jl_is_long(jl_fieldref(args[2],0)))) { - ssize_t i; - if (jl_is_long(args[2])) - i = jl_unbox_long(args[2]); - else - i = jl_unbox_long(jl_fieldref(args[2],0)); - if (i > 0 && i <= jl_datatype_nfields(uty)) - idx = i-1; + if (jl_is_structtype(uty) && uty != jl_module_type && uty->layout) { + size_t idx = compute_setfield_index(uty, args, ctx); + if (idx != (size_t)-1) { + jl_value_t *ft = jl_svecref(uty->types, idx); + jl_value_t *rhst = expr_type(args[3], ctx); + rt2 = rhst; + if (uty->layout && jl_subtype(rhst, ft)) { + // TODO: attempt better codegen for approximate types + jl_cgval_t strct = emit_expr(args[1], ctx); // emit lhs + jl_cgval_t rhs = emit_expr(args[3], ctx); // emit rhs + *ret = emit_object_copy(strct, ctx); + emit_setfield_knownindex(sty, *ret, idx, rhs, ctx, false, true); + JL_GC_POP(); + return true; + } + } else if (expr_type(args[2], ctx) == (jl_value_t*)jl_long_type) { + Value *vidx = emit_unbox(T_size, emit_expr(args[2], ctx), (jl_value_t*)jl_long_type); + jl_cgval_t strct = emit_expr(args[1], ctx); // emit lhs + jl_cgval_t rhs = emit_expr(args[3], ctx); // emit rhs + *ret = emit_object_copy(strct, ctx); + if (emit_setfield_unknownidx(*ret, vidx, sty, rhs, ctx)) { + JL_GC_POP(); + return true; + } } + } + } + + else if (f==jl_builtin_setfield_bang && nargs==3) { + jl_datatype_t *sty = (jl_datatype_t*)expr_type(args[1], ctx); + rt1 = (jl_value_t*)sty; + jl_datatype_t *uty = (jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)sty); + if (jl_is_structtype(uty) && uty != jl_module_type && uty->layout) { + size_t idx = compute_setfield_index(uty, args, ctx); if (idx != (size_t)-1) { jl_value_t *ft = jl_svecref(uty->types, idx); jl_value_t *rhst = expr_type(args[3], ctx); @@ -3070,7 +3107,7 @@ static bool emit_builtin_call(jl_cgval_t *ret, jl_value_t *f, jl_value_t **args, // TODO: attempt better codegen for approximate types jl_cgval_t strct = emit_expr(args[1], ctx); // emit lhs *ret = emit_expr(args[3], ctx); - emit_setfield(sty, strct, idx, *ret, ctx, true, true); + emit_setfield_knownindex(sty, strct, idx, *ret, ctx, true, true); JL_GC_POP(); return true; } @@ -4684,12 +4721,15 @@ static Function *gen_cfun_wrapper(jl_function_t *ff, jl_value_t *jlrettype, jl_t if (sig.sret) prt = sig.fargt_sig[0]->getContainedType(0); // sret is a PointerType bool issigned = jl_signed_type && jl_subtype(declrt, (jl_value_t*)jl_signed_type); - Value *v = julia_to_native(sig.lrt, toboxed, declrt, NULL, retval, - false, 0, &ctx, NULL); - r = llvm_type_rewrite(v, prt, issigned, &ctx); + + typeassert_input(retval, declrt, NULL, 0, false, &ctx); + + Value *dest = sig.sret ? sretPtr : NULL; + r = emit_unbox(sig.lrt, retval, declrt, dest); if (sig.sret) { - builder.CreateStore(r, sretPtr); r = NULL; + } else { + r = llvm_type_rewrite(r, prt, issigned, &ctx); } } else { @@ -6776,6 +6816,7 @@ static void init_julia_llvm_env(Module *m) builtin_func_map[jl_f_isdefined] = jlcall_func_to_llvm("jl_f_isdefined", &jl_f_isdefined, m); builtin_func_map[jl_f_getfield] = jlcall_func_to_llvm("jl_f_getfield", &jl_f_getfield, m); builtin_func_map[jl_f_setfield] = jlcall_func_to_llvm("jl_f_setfield", &jl_f_setfield, m); + builtin_func_map[jl_f_setfield_bang] = jlcall_func_to_llvm("jl_f_setfield_bang", &jl_f_setfield_bang, m); builtin_func_map[jl_f_fieldtype] = jlcall_func_to_llvm("jl_f_fieldtype", &jl_f_fieldtype, m); builtin_func_map[jl_f_nfields] = jlcall_func_to_llvm("jl_f_nfields", &jl_f_nfields, m); builtin_func_map[jl_f__expr] = jlcall_func_to_llvm("jl_f__expr", &jl_f__expr, m); diff --git a/src/datatype.c b/src/datatype.c index 0bf85c8af246a..1e55fb219fc4a 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -755,6 +755,13 @@ JL_DLLEXPORT size_t jl_get_field_offset(jl_datatype_t *ty, int field) return jl_field_offset(ty, field - 1); } +JL_DLLEXPORT int jl_get_field_isptr(jl_datatype_t *ty, int field) +{ + if (ty->layout == NULL || field > jl_datatype_nfields(ty) || field < 1) + jl_bounds_error_int((jl_value_t*)ty, field); + return jl_field_isptr(ty, field - 1); +} + JL_DLLEXPORT size_t jl_get_alignment(jl_datatype_t *ty) { if (ty->layout == NULL) diff --git a/src/dump.c b/src/dump.c index 7e4ca4a9ad75a..349ce80698295 100644 --- a/src/dump.c +++ b/src/dump.c @@ -79,7 +79,7 @@ static const jl_fptr_t id_to_fptrs[] = { jl_f_throw, jl_f_is, jl_f_typeof, jl_f_issubtype, jl_f_isa, jl_f_typeassert, jl_f__apply, jl_f__apply_pure, jl_f__apply_latest, jl_f_isdefined, jl_f_tuple, jl_f_svec, jl_f_intrinsic_call, jl_f_invoke_kwsorter, - jl_f_getfield, jl_f_setfield, jl_f_fieldtype, jl_f_nfields, + jl_f_getfield, jl_f_setfield, jl_f_setfield_bang, jl_f_fieldtype, jl_f_nfields, jl_f_arrayref, jl_f_arrayset, jl_f_arraysize, jl_f_apply_type, jl_f_applicable, jl_f_invoke, jl_f_sizeof, jl_f__expr, NULL }; diff --git a/src/init.c b/src/init.c index 29edd9b93f1ca..0079ff1db448b 100644 --- a/src/init.c +++ b/src/init.c @@ -816,7 +816,8 @@ void jl_get_builtins(void) jl_builtin_typeassert = core("typeassert"); jl_builtin__apply = core("_apply"); jl_builtin_isdefined = core("isdefined"); jl_builtin_nfields = core("nfields"); jl_builtin_tuple = core("tuple"); jl_builtin_svec = core("svec"); - jl_builtin_getfield = core("getfield"); jl_builtin_setfield = core("setfield!"); + jl_builtin_getfield = core("getfield"); jl_builtin_setfield_bang = core("setfield!"); + jl_builtin_setfield = NULL; //core("setfield"); jl_builtin_fieldtype = core("fieldtype"); jl_builtin_arrayref = core("arrayref"); jl_builtin_arrayset = core("arrayset"); jl_builtin_arraysize = core("arraysize"); jl_builtin_apply_type = core("apply_type"); jl_builtin_applicable = core("applicable"); diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index 336bde973fbb1..9e32bbeffb82f 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -158,6 +158,7 @@ void addOptimizationPasses(PassManager *PM) // list of passes from vmkit PM->add(createCFGSimplificationPass()); // Clean up disgusting code PM->add(createPromoteMemoryToRegisterPass()); // Kill useless allocas + PM->add(createMemCpyOptPass()); // hopefully these functions (from llvmcall) don't try to interact with the Julia runtime // or have anything that might corrupt the createLowerPTLSPass pass diff --git a/src/julia-parser.scm b/src/julia-parser.scm index 6626739da4af7..9c27c0ea48935 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -30,6 +30,7 @@ (define prec-power (add-dots '(^ ↑ ↓ ⇵ ⟰ ⟱ ⤈ ⤉ ⤊ ⤋ ⤒ ⤓ ⥉ ⥌ ⥍ ⥏ ⥑ ⥔ ⥕ ⥘ ⥙ ⥜ ⥝ ⥠ ⥡ ⥣ ⥥ ⥮ ⥯ ↑ ↓))) (define prec-decl '(|::|)) ;; `where` occurring after `::` +;; '@' after here if used in `a.b@c = ` syntax (define prec-dot '(|.|)) (define prec-names '(prec-assignment @@ -972,10 +973,19 @@ (list 'call (take-token s) ex (parse-factor-h s parse-unary ops)))))) -;; -2^3 is parsed as -(2^3), so call parse-decl for the first argument, +;; -2^3 is parsed as -(2^3), so call parse-at for the first argument, ;; and parse-unary from then on (to handle 2^-3) (define (parse-factor s) - (parse-factor-h s parse-decl is-prec-power?)) + (parse-factor-h s parse-at is-prec-power?)) + +(define (parse-at s) + (let loop ((ex (parse-decl s))) + (let ((t (peek-token s))) + (case t + ((#\@) (take-token s) + (loop (list '@ ex (parse-decl s)))) + (else + ex))))) (define (parse-decl s) (let loop ((ex (parse-call s))) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 86ed90c300ac6..5bdea9f8f675b 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -1580,6 +1580,12 @@ (T (caddr lhs))) `(block ,@(cdr e) ,(expand-update-operator op op= (car e) rhs T)))) + ((and (pair? lhs) (eq? (car lhs) '@)) + (let* ((ref (make-ssavalue)) + (nlhs `(ref ,ref))) + `(block + (= ,ref ,(expand-forms lhs)) + ,(expand-update-operator- op op= nlhs rhs declT)))) (else (if (and (pair? lhs) (not (memq (car lhs) '(|.| tuple vcat typed_hcat typed_vcat)))) @@ -1836,6 +1842,24 @@ (extract (cdr params) (cons p newparams) whereparams))))) (extract (cddr e) '() '())) +(define (expand-at-ref-b base b) + (if (or (symbol-like? b) (quoted? b)) + `(call gepfield ,base ,`',b) + (case (car b) + ((|.|) `(call gepfield ,(expand-at-ref-b base (cadr b)) ,(caddr b))) + ((ref) `(call gepindex ,(expand-at-ref-b base (cadr b)) ,@(cddr b))) + ((vect) `(call gepindex ,base ,@(cdr b))) + (else `(call gepfield ,base ,b))))) + +(define (expand-at-ref e) + ;; a@b + (let ((a (cadr e)) + (b (caddr e))) + (let ((aa (if (symbol-like? a) a (make-ssavalue)))) + (if (eq? aa a) (expand-at-ref-b aa b) + `(block (= ,aa ,(expand-forms a)) + ,(expand-at-ref-b aa b)))))) + ;; table mapping expression head to a function expanding that form (define expand-table (table @@ -1885,6 +1909,8 @@ 'global expand-local-or-global-decl 'local-def expand-local-or-global-decl + '@ expand-at-ref + '= (lambda (e) (define lhs (cadr e)) @@ -1956,6 +1982,14 @@ (call (core fieldtype) (call (core typeof) ,aa) ,bb) ,rr)) (unnecessary ,rr))))) + ((@) + ;; x@c = + (let ((rhs (caddr e))) + (let ((expanded-ref (expand-at-ref lhs)) + (rr (if (or (symbol-like? rhs) (atom? rhs)) rhs (make-ssavalue)))) + `(block + ,.(if (eq? rr rhs) '() `((= ,rr ,(expand-forms rhs)))) + (call setindex! ,expanded-ref ,rr))))) ((tuple) ;; multiple assignment (let ((lhss (cdr lhs)) diff --git a/test/choosetests.jl b/test/choosetests.jl index 7974eee36f60e..a2ea71e7773b5 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -35,7 +35,7 @@ function choosetests(choices = []) "enums", "cmdlineargs", "i18n", "workspace", "libdl", "int", "checked", "intset", "floatfuncs", "compile", "distributed", "inline", "boundscheck", "error", "ambiguous", "cartesian", "asmvariant", "osutils", - "channels", "iostream" + "channels", "iostream", "reffield" ] profile_skipped = false if startswith(string(Sys.ARCH), "arm") diff --git a/test/parse.jl b/test/parse.jl index 12377dbdbb01e..5386bbd42bf43 100644 --- a/test/parse.jl +++ b/test/parse.jl @@ -1195,3 +1195,33 @@ end # issue #16937 @test expand(:(f(2, a=1, w=3, c=3, w=4, b=2))) == Expr(:error, "keyword argument \"w\" repeated in call to \"f\"") + +# Basic parsing of reference syntax +@test expand(parse("a@b")) == :(gepfield(a, :b)) +@test expand(parse("a@c")) == :(gepfield(a, :c)) +@test expand(parse("a@c.d")) == :(gepfield(gepfield(a, :c), :d)) +@test expand(parse("a@c.d[e]")) == :(gepindex(gepfield(gepfield(a, :c), :d), e)) +@test expand(parse("a@c[d]")) == :(gepindex(gepfield(a, :c), d)) +@test expand(parse("a@c[d,e]")) == :(gepindex(gepfield(a, :c), d, e)) +@test expand(parse("a@[c]")) == :(gepindex(a, c)) +@test expand(parse("a@[c,d]")) == :(gepindex(a, c, d)) +@test expand(parse("a@[c].d")) == :(gepfield(gepindex(a, c),:d)) + +@test expand(parse("a@b = 1")) == :(setindex!(gepfield(a, :b), 1)) +@test expand(parse("a@c = 1")) == :(setindex!(gepfield(a, :c), 1)) +@test expand(parse("a@c.d = 1")) == :(setindex!(gepfield(gepfield(a, :c), :d), 1)) +@test expand(parse("a@c.d[e] = 1")) == :(setindex!(gepindex(gepfield(gepfield(a, :c), :d), e), 1)) +@test expand(parse("a@c[d] = 1")) == :(setindex!(gepindex(gepfield(a, :c), d), 1)) +@test expand(parse("a@c[d,e] = 1")) == :(setindex!(gepindex(gepfield(a, :c), d, e), 1)) +@test expand(parse("a@[c] = 1")) == :(setindex!(gepindex(a, c), 1)) +@test expand(parse("a@[c,d] = 1")) == :(setindex!(gepindex(a, c, d), 1)) +@test expand(parse("a@[c].d = 1")) == :(setindex!(gepfield(gepindex(a, c),:d), 1)) + +struct reftuplefoo + t::NTuple{2, Int} +end +let x = Ref{reftuplefoo}() + # Check that the RHS gets expanded + x@t = ntuple(identity, 2) + @test x[] == (1,2) +end diff --git a/test/reffield.jl b/test/reffield.jl new file mode 100644 index 0000000000000..dd0d7dd3e8d48 --- /dev/null +++ b/test/reffield.jl @@ -0,0 +1,43 @@ +struct reffoo3 + c::Int +end + +struct reffoo2 + b::reffoo3 +end + +mutable struct reffoo + a::reffoo2 +end + +let x = reffoo(reffoo2(reffoo3(1))) + x@a.b.c = 2 + @test x.a.b.c == 2 + (x@a.b.c)[] = 3 + @test x.a.b.c == 3 + let x = x@a.b + x@c = 4 + end + @test x.a.b.c == 4 + ya = @setfield(x.a, b.c = 5) + @test ya.b.c == 5 + @test x.a.b.c == 4 + @test_throws ErrorException x.a@b.c = 2 + @test @setfield(x.a, b.c = 5).b.c == 5 +end + +let t = ntuple(identity, 4) + r = Ref{typeof(t)}(t) + r@[][2] = 1 + @test r[] == (1,1,3,4) + @test @setfield(r, [][3] = 1)[] == (1,1,1,4) + @test r[] == (1,1,3,4) + @test_throws ErrorException t@[1] + @test @setfield(t, [3] = 1) == (1,2,1,4) +end + +let t = ntuple(i->reffoo2(reffoo3(i)), 3) + r = Ref{typeof(t)}(t) + r@[][2].b.c = 6 + @test r[][2].b.c == 6 +end