From a79f0dd6205a06c67684d3d7de4aeb6166f592b2 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Mon, 15 May 2017 20:51:24 -0400 Subject: [PATCH 1/7] Add runtime support for non-mutating setindex --- base/boot.jl | 3 ++- src/builtin_proto.h | 1 + src/builtins.c | 33 +++++++++++++++++++++++++++++++-- src/codegen.cpp | 3 ++- src/dump.c | 2 +- src/init.c | 3 ++- 6 files changed, 39 insertions(+), 6 deletions(-) diff --git a/base/boot.jl b/base/boot.jl index d0a686e761bf5..c9b69efdec14f 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, setfield!, nfields, throw, tuple, ===, + isdefined, eval, # sizeof # not exported, to avoid conflicting with Base.sizeof # type reflection issubtype, typeof, isa, typeassert, 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/codegen.cpp b/src/codegen.cpp index a3cd82040a16e..14d65fd3acc4d 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -3044,7 +3044,7 @@ static bool emit_builtin_call(jl_cgval_t *ret, jl_value_t *f, jl_value_t **args, } } - else if (f==jl_builtin_setfield && nargs==3) { + 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); @@ -6776,6 +6776,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/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..77b07905f15ca 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 = 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"); From 127251a7d17feb36f1faad6aeee74097dba3e071 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Mon, 15 May 2017 20:52:26 -0400 Subject: [PATCH 2/7] Add basic parsing and lowering support --- src/julia-parser.scm | 14 ++++++-- src/julia-syntax.scm | 85 ++++++++++++++++++++++++++++++++++++++++++++ test/parse.jl | 11 ++++++ 3 files changed, 108 insertions(+), 2 deletions(-) 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..59d676983a393 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -1836,6 +1836,56 @@ (extract (cdr params) (cons p newparams) whereparams))))) (extract (cddr e) '() '())) +(define (expand-at-assign expr base rr) + (let ((ssas `(,base)) + (code '()) + (idxs '())) + (define (emit c) + (set! code (cons c code))) + (define (add-level ssa sym) + (set! ssas (cons ssa ssas)) + (set! idxs (cons sym idxs))) + (define (visit expr) + (let* ((ssa (make-ssavalue)) + (primitive (or (symbol-like? expr) (eq? (car expr) 'vect))) + (prevval (if primitive base (visit (cadr expr)))) + (refidx (and (not (symbol-like? expr)) (eq? (car expr) 'ref))) + (indexing (and (not (symbol-like? expr)) + (or (eq? (car expr) 'vect) refidx))) + (sym (if (symbol-like? expr) `',expr + (if indexing (if refidx (cddr expr) (cdr expr)) (caddr expr))))) + (if indexing + (receive + (new-idxs stuff) (process-indexes prevval sym) + (add-level ssa new-idxs) + (emit `(block ,@stuff (= ,ssa (call getindex ,prevval ,@new-idxs))))) + (let ((ssym (if (or (symbol-like? sym) (quoted? sym)) sym + (let ((nssa (make-ssavalue))) + (emit `(= ,nssa ,sym)) + nssa)))) + (add-level ssa ssym) + (emit `(= ,ssa (call (core getfield) ,prevval ,ssym))))) + ssa)) + (define (build-assign expr ssas idxs v) + (let ((prevssa (if (symbol-like? ssas) ssas (car ssas))) + (sym (if (symbol-like? idxs) idxs (car idxs)))) + (let ((new-expr + (if (symbol-like? expr) + `(call (top setfield) ,prevssa ,sym ,v) + (case (car expr) + ((|.|) + `(call (top setfield) ,prevssa ,sym ,v)) + ((vect) + `(call (top setindex) ,prevssa ,v ,@sym)) + ((ref) + `(call (top setindex) ,prevssa ,v ,@sym)))))) + (if (null? (cdr idxs)) new-expr + (build-assign (cadr expr) (cdr ssas) (cdr idxs) new-expr))))) + (visit expr) + (let ((res (build-assign expr (cdr ssas) idxs rr))) + (cons (reverse code) res)))) + + ;; table mapping expression head to a function expanding that form (define expand-table (table @@ -1956,6 +2006,41 @@ (call (core fieldtype) (call (core typeof) ,aa) ,bb) ,rr)) (unnecessary ,rr))))) + ((@) + ;; x@c = + (let ((x (cadr lhs)) + (c (caddr lhs)) + (rhs (caddr e))) + (let* ((xx (if (or (symbol-like? x) (atom? x)) x (make-ssavalue))) + (rr (if (or (symbol-like? rhs) (atom? rhs)) rhs (make-ssavalue))) + (lowered (expand-at-assign c xx rr)) + (pre-assign (if (eq? rr rhs) '() `((= ,rr ,(expand-forms rhs)))))) + `(block ,.pre-assign + ,.(if (symbol-like? x) `(,@(car lowered) (= ,x ,(cdr lowered))) + (case (car x) + ;; a.b@c = + ((|.|) + (let* ((a (cadr x)) + (b (caddr x)) + (aa (if (symbol-like? a) a (make-ssavalue)))) + `( + ,.(if (eq? aa a) '() `((= ,aa ,(expand-forms a)))) + (= ,xx (call (core getfield) ,aa ,b)) + ,@(car lowered) + (call (core setfield!) ,aa ,b ,(cdr lowered)) + ))) + ;; a[b]@c = + ((ref) + (let* ((a (cadr x)) + (b (caddr x)) + (aa (if (symbol-like? a) a (make-ssavalue)))) + `( + ,.(if (eq? aa a) '() `((= ,aa ,(expand-forms a)))) + (= ,xx (call getindex ,aa ,b)) + ,@(car lowered) + (call (core setindex!) ,aa ,(cdr lowered) ,b) + ))) + )))))) ((tuple) ;; multiple assignment (let ((lhss (cdr lhs)) diff --git a/test/parse.jl b/test/parse.jl index 12377dbdbb01e..cbe3dc29ae58a 100644 --- a/test/parse.jl +++ b/test/parse.jl @@ -1195,3 +1195,14 @@ 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 for in place assign +@test parse("a@b = 1") == :(a@b = 1) +@test parse("a.b@c = 1") == :(a.b@c = 1) +@test parse("a.b@c.d = 1") == :(a.b@c.d = 1) +@test parse("a.b@c.d[e] = 1") == :(a.b@c.d[e] = 1) +@test parse("a.b@c[d] = 1") == :(a.b@c[d] = 1) +@test parse("a.b@c[d,e] = 1") == :(a.b@c[d,e] = 1) +@test parse("a.b@[c] = 1") == :(a.b@[c] = 1) +@test parse("a.b@[c,d] = 1") == :(a.b@[c,d] = 1) +@test parse("a.b[c]@[d] = 1") == :(a.b[c]@[d] = 1) From ce2204671ec02bb027550397f422894c95acc9dc Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Tue, 16 May 2017 14:06:58 -0400 Subject: [PATCH 3/7] Codegen and inference --- base/inference.jl | 1 + base/tuple.jl | 9 +-- src/cgutils.cpp | 149 ++++++++++++++++++++++++++++++++++++---------- src/codegen.cpp | 67 ++++++++++++++++----- 4 files changed, 171 insertions(+), 55 deletions(-) diff --git a/base/inference.jl b/base/inference.jl index 532b48917a26b..3dce73aaf8670 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -875,6 +875,7 @@ function getfield_tfunc(s00::ANY, name) return rewrap_unionall(limit_type_depth(R, 0), s00) end add_tfunc(getfield, 2, 2, (s::ANY, name::ANY) -> getfield_tfunc(s, name)) +add_tfunc(setfield, 3, 3, (o::ANY, f::ANY, v::ANY) -> o) add_tfunc(setfield!, 3, 3, (o::ANY, f::ANY, v::ANY) -> v) function fieldtype_tfunc(s0::ANY, name::ANY) if s0 === Any || s0 === Type || DataType ⊑ s0 || UnionAll ⊑ s0 diff --git a/base/tuple.jl b/base/tuple.jl index dc041a47937fb..4ac927cb033c0 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -23,13 +23,8 @@ 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)) -# 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 ## 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 14d65fd3acc4d..bbf0ee30079c9 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 @@ -3044,24 +3061,44 @@ static bool emit_builtin_call(jl_cgval_t *ret, jl_value_t *f, jl_value_t **args, } } - else if (f==jl_builtin_setfield_bang && nargs==3) { + else if (f==jl_builtin_setfield && 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) { - 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; } From ed060f1a73840f8f8cb75336acf7c321bb595b93 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sat, 20 May 2017 20:09:58 -0400 Subject: [PATCH 4/7] Unoptimized implementation of new semantics --- base/boot.jl | 2 +- base/exports.jl | 5 +++ base/inference.jl | 1 - base/reflection.jl | 2 + base/refpointer.jl | 105 +++++++++++++++++++++++++++++++++++++++++++ base/tuple.jl | 1 + src/datatype.c | 7 +++ src/init.c | 2 +- src/julia-syntax.scm | 103 +++++++++--------------------------------- test/choosetests.jl | 2 +- test/parse.jl | 32 ++++++++----- test/reffield.jl | 43 ++++++++++++++++++ 12 files changed, 209 insertions(+), 96 deletions(-) create mode 100644 test/reffield.jl diff --git a/base/boot.jl b/base/boot.jl index c9b69efdec14f..41e86a44f2c34 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -138,7 +138,7 @@ export Expr, GotoNode, LabelNode, LineNumberNode, QuoteNode, GlobalRef, NewvarNode, SSAValue, Slot, SlotNumber, TypedSlot, # object model functions - fieldtype, getfield, setfield, setfield!, nfields, throw, tuple, ===, + fieldtype, getfield, setfield!, nfields, throw, tuple, ===, isdefined, eval, # sizeof # not exported, to avoid conflicting with Base.sizeof # type reflection 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/inference.jl b/base/inference.jl index 3dce73aaf8670..532b48917a26b 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -875,7 +875,6 @@ function getfield_tfunc(s00::ANY, name) return rewrap_unionall(limit_type_depth(R, 0), s00) end add_tfunc(getfield, 2, 2, (s::ANY, name::ANY) -> getfield_tfunc(s, name)) -add_tfunc(setfield, 3, 3, (o::ANY, f::ANY, v::ANY) -> o) add_tfunc(setfield!, 3, 3, (o::ANY, f::ANY, v::ANY) -> v) function fieldtype_tfunc(s0::ANY, name::ANY) if s0 === Any || s0 === Type || DataType ⊑ s0 || UnionAll ⊑ s0 diff --git a/base/reflection.jl b/base/reflection.jl index 58d9b67e19177..4778f8c9ff7cc 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -379,6 +379,8 @@ function fieldindex(T::DataType, name::Symbol, err::Bool=true) return Int(ccall(:jl_field_index, Cint, (Any, Any, Cint), T, name, err)+1) end +fieldisptr(T::DataType, idx::Integer) = 1 == 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..c911d47a51b9b 100644 --- a/base/refpointer.jl +++ b/base/refpointer.jl @@ -51,6 +51,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 +114,110 @@ 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) + 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} + !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) + gepfield(x, Base.fieldindex(typeof(x), sym)) +end +function gepfield(x::RefField{T}, sym::Symbol) where T + gepfield(x, Base.fieldindex(T, sym)) +end +gepindex(x::Ref) = gepfield(x, 1) + +# Tuple is defined before us in bootstrap, so it can't refer to RefField +gepindex(x::RefField{<:Tuple}, idx) = gepfield(x, idx) + +function setindex!(x::RefField{T}, v::T) where T + unsafe_store!(Ptr{T}(pointer_from_objref(x.base)+x.offset), v) + v +end + +function getindex(x::RefField{T}, v::T) where T + unsafe_load(Ptr{T}(pointer_from_objref(x.base)+x.offset), v) +end + +function setfield(x, sym, v) + 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...) + 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 4ac927cb033c0..3b8e82c157dba 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -22,6 +22,7 @@ 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 setindex(x::Tuple, v, i::Integer) = setfield(x, i, v) 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/init.c b/src/init.c index 77b07905f15ca..0079ff1db448b 100644 --- a/src/init.c +++ b/src/init.c @@ -817,7 +817,7 @@ void jl_get_builtins(void) 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_bang = core("setfield!"); - jl_builtin_setfield = 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/julia-syntax.scm b/src/julia-syntax.scm index 59d676983a393..26c4a89175498 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -1836,55 +1836,23 @@ (extract (cdr params) (cons p newparams) whereparams))))) (extract (cddr e) '() '())) -(define (expand-at-assign expr base rr) - (let ((ssas `(,base)) - (code '()) - (idxs '())) - (define (emit c) - (set! code (cons c code))) - (define (add-level ssa sym) - (set! ssas (cons ssa ssas)) - (set! idxs (cons sym idxs))) - (define (visit expr) - (let* ((ssa (make-ssavalue)) - (primitive (or (symbol-like? expr) (eq? (car expr) 'vect))) - (prevval (if primitive base (visit (cadr expr)))) - (refidx (and (not (symbol-like? expr)) (eq? (car expr) 'ref))) - (indexing (and (not (symbol-like? expr)) - (or (eq? (car expr) 'vect) refidx))) - (sym (if (symbol-like? expr) `',expr - (if indexing (if refidx (cddr expr) (cdr expr)) (caddr expr))))) - (if indexing - (receive - (new-idxs stuff) (process-indexes prevval sym) - (add-level ssa new-idxs) - (emit `(block ,@stuff (= ,ssa (call getindex ,prevval ,@new-idxs))))) - (let ((ssym (if (or (symbol-like? sym) (quoted? sym)) sym - (let ((nssa (make-ssavalue))) - (emit `(= ,nssa ,sym)) - nssa)))) - (add-level ssa ssym) - (emit `(= ,ssa (call (core getfield) ,prevval ,ssym))))) - ssa)) - (define (build-assign expr ssas idxs v) - (let ((prevssa (if (symbol-like? ssas) ssas (car ssas))) - (sym (if (symbol-like? idxs) idxs (car idxs)))) - (let ((new-expr - (if (symbol-like? expr) - `(call (top setfield) ,prevssa ,sym ,v) - (case (car expr) - ((|.|) - `(call (top setfield) ,prevssa ,sym ,v)) - ((vect) - `(call (top setindex) ,prevssa ,v ,@sym)) - ((ref) - `(call (top setindex) ,prevssa ,v ,@sym)))))) - (if (null? (cdr idxs)) new-expr - (build-assign (cadr expr) (cdr ssas) (cdr idxs) new-expr))))) - (visit expr) - (let ((res (build-assign expr (cdr ssas) idxs rr))) - (cons (reverse code) res)))) - +(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 @@ -1935,6 +1903,8 @@ 'global expand-local-or-global-decl 'local-def expand-local-or-global-decl + '@ expand-at-ref + '= (lambda (e) (define lhs (cadr e)) @@ -2008,39 +1978,8 @@ (unnecessary ,rr))))) ((@) ;; x@c = - (let ((x (cadr lhs)) - (c (caddr lhs)) - (rhs (caddr e))) - (let* ((xx (if (or (symbol-like? x) (atom? x)) x (make-ssavalue))) - (rr (if (or (symbol-like? rhs) (atom? rhs)) rhs (make-ssavalue))) - (lowered (expand-at-assign c xx rr)) - (pre-assign (if (eq? rr rhs) '() `((= ,rr ,(expand-forms rhs)))))) - `(block ,.pre-assign - ,.(if (symbol-like? x) `(,@(car lowered) (= ,x ,(cdr lowered))) - (case (car x) - ;; a.b@c = - ((|.|) - (let* ((a (cadr x)) - (b (caddr x)) - (aa (if (symbol-like? a) a (make-ssavalue)))) - `( - ,.(if (eq? aa a) '() `((= ,aa ,(expand-forms a)))) - (= ,xx (call (core getfield) ,aa ,b)) - ,@(car lowered) - (call (core setfield!) ,aa ,b ,(cdr lowered)) - ))) - ;; a[b]@c = - ((ref) - (let* ((a (cadr x)) - (b (caddr x)) - (aa (if (symbol-like? a) a (make-ssavalue)))) - `( - ,.(if (eq? aa a) '() `((= ,aa ,(expand-forms a)))) - (= ,xx (call getindex ,aa ,b)) - ,@(car lowered) - (call (core setindex!) ,aa ,(cdr lowered) ,b) - ))) - )))))) + (let ((expanded-ref (expand-at-ref lhs))) + `(call setindex! ,expanded-ref ,(caddr e)))) ((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 cbe3dc29ae58a..b141de049a30b 100644 --- a/test/parse.jl +++ b/test/parse.jl @@ -1196,13 +1196,25 @@ end @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 for in place assign -@test parse("a@b = 1") == :(a@b = 1) -@test parse("a.b@c = 1") == :(a.b@c = 1) -@test parse("a.b@c.d = 1") == :(a.b@c.d = 1) -@test parse("a.b@c.d[e] = 1") == :(a.b@c.d[e] = 1) -@test parse("a.b@c[d] = 1") == :(a.b@c[d] = 1) -@test parse("a.b@c[d,e] = 1") == :(a.b@c[d,e] = 1) -@test parse("a.b@[c] = 1") == :(a.b@[c] = 1) -@test parse("a.b@[c,d] = 1") == :(a.b@[c,d] = 1) -@test parse("a.b[c]@[d] = 1") == :(a.b[c]@[d] = 1) +# 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)) + +expand(:(foo(1)@a)) 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 From e23361c1ca6de00a0544037f3dc99b3c7a6c337c Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Mon, 22 May 2017 21:48:10 -0400 Subject: [PATCH 5/7] WIP --- base/refpointer.jl | 28 ++++++++++++++++++++-------- base/tuple.jl | 38 +++++++++++++++++++++++++------------- src/julia-syntax.scm | 8 ++++++-- test/parse.jl | 9 ++++++++- 4 files changed, 59 insertions(+), 24 deletions(-) diff --git a/base/refpointer.jl b/base/refpointer.jl index c911d47a51b9b..e829994a7c760 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) = gepfield(let gepfield=_gepfield; r@x; end, sym) +gepfield(r::RefValue, idx::Integer) = gepfield(let gepfield=_gepfield; r@x; end, idx) +gepindex(r::RefValue, idxs...) = gepindex(r@x, 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) @@ -120,24 +127,28 @@ struct RefField{T} <: Ref{T} base offset::UInt # Basic constructors - global gepfield - function gepfield(x::ANY, idx::Integer) + global _gepfield + function _gepfield(x::ANY, idx::Integer) 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} + function _gepfield(x::RefField{T}, idx::Integer) where {T} !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) - gepfield(x, Base.fieldindex(typeof(x), sym)) +function _gepfield(x::ANY, sym::Symbol) + _gepfield(x, Base.fieldindex(typeof(x), sym)) end -function gepfield(x::RefField{T}, sym::Symbol) where T - gepfield(x, Base.fieldindex(T, sym)) +function _gepfield(x::RefField{T}, sym::Symbol) where T + _gepfield(x, Base.fieldindex(T, sym)) end -gepindex(x::Ref) = gepfield(x, 1) + +gepfield(x::ANY, idx::Integer) = _gepfield(x, idx) +gepfield(x::RefField{T}, idx::Integer) where {T} = _gepfield(x, idx) +gepfield(x::ANY, idx::Symbol) = _gepfield(x, idx) +gepfield(x::RefField{T}, idx::Symbol) where {T} = _gepfield(x, idx) # Tuple is defined before us in bootstrap, so it can't refer to RefField gepindex(x::RefField{<:Tuple}, idx) = gepfield(x, idx) @@ -146,6 +157,7 @@ function setindex!(x::RefField{T}, v::T) where T unsafe_store!(Ptr{T}(pointer_from_objref(x.base)+x.offset), v) v end +setindex!(x::RefField{T}, v::S) where {T,S} = setindex!(x, convert(T, v)::T) function getindex(x::RefField{T}, v::T) where T unsafe_load(Ptr{T}(pointer_from_objref(x.base)+x.offset), v) diff --git a/base/tuple.jl b/base/tuple.jl index 3b8e82c157dba..f5bf2eaa39604 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -101,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{RT, n} + 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) @@ -122,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),)) @@ -153,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]),) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 26c4a89175498..0ec766a38c0be 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -1978,8 +1978,12 @@ (unnecessary ,rr))))) ((@) ;; x@c = - (let ((expanded-ref (expand-at-ref lhs))) - `(call setindex! ,expanded-ref ,(caddr e)))) + (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/parse.jl b/test/parse.jl index b141de049a30b..5386bbd42bf43 100644 --- a/test/parse.jl +++ b/test/parse.jl @@ -1217,4 +1217,11 @@ end @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)) -expand(:(foo(1)@a)) +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 From 83a7dd55324c69b7c038f77dc665a885dbee8687 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Tue, 23 May 2017 17:48:04 -0400 Subject: [PATCH 6/7] Add extra early memcpyopt pass Under certain circumstances, we emit loads/stores of large LLVM structs. A lot of these can be trivially folded to memcpy and memcpyopt is capable of doing so, but we weren't running it until after SROA. SROA unfortunately, likes to take these apart, causing exponential compile-time blow up and reduced runtime peroformance. In one particular case (2000 element tuple), this change results in a 100x improvement in compile time. --- src/jitlayers.cpp | 1 + 1 file changed, 1 insertion(+) 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 From c52d1399e70e7f1edd30ac588aea8420fa793d85 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Wed, 24 May 2017 08:08:03 -0400 Subject: [PATCH 7/7] Some tweaks based on experiments --- base/reflection.jl | 11 +++++++++-- base/refpointer.jl | 30 +++++++++++++++++++----------- base/tuple.jl | 14 +++++++++++++- src/codegen.cpp | 11 +++++++---- src/julia-syntax.scm | 6 ++++++ 5 files changed, 54 insertions(+), 18 deletions(-) diff --git a/base/reflection.jl b/base/reflection.jl index 4778f8c9ff7cc..65785d6264380 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -376,10 +376,17 @@ 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 == ccall(:jl_get_field_isptr, Cint, (Any, Cint), T, idx) +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)) diff --git a/base/refpointer.jl b/base/refpointer.jl index e829994a7c760..a98dcd4c3a2e5 100644 --- a/base/refpointer.jl +++ b/base/refpointer.jl @@ -46,9 +46,9 @@ isassigned(x::RefValue) = isdefined(x, :x) global _gepvalue # Special for r::RefValue, r@a means r@x.a -gepfield(r::RefValue, sym::Symbol) = gepfield(let gepfield=_gepfield; r@x; end, sym) -gepfield(r::RefValue, idx::Integer) = gepfield(let gepfield=_gepfield; r@x; end, idx) -gepindex(r::RefValue, idxs...) = gepindex(r@x, idxs...) +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 @@ -129,41 +129,48 @@ struct RefField{T} <: Ref{T} # 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) = _gepfield(x, idx) -gepfield(x::RefField{T}, idx::Integer) where {T} = _gepfield(x, idx) -gepfield(x::ANY, idx::Symbol) = _gepfield(x, idx) -gepfield(x::RefField{T}, idx::Symbol) where {T} = _gepfield(x, idx) +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) = gepfield(x, idx) +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} = setindex!(x, convert(T, v)::T) +setindex!(x::RefField{T}, v::S) where {T,S} = (@_inline_meta; setindex!(x, convert(T, v)::T)) -function getindex(x::RefField{T}, v::T) where T - unsafe_load(Ptr{T}(pointer_from_objref(x.base)+x.offset), v) +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) @@ -176,6 +183,7 @@ function setfield(x, sym, v) end function setindex(x, v, idxs...) + @_inline_meta if typeof(x).mutable y = copy(x) setindex!(y, v, idxs...) diff --git a/base/tuple.jl b/base/tuple.jl index f5bf2eaa39604..35d24bc6884b3 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -102,7 +102,7 @@ julia> ntuple(i -> 2*i, 4) """ function ntuple(f::F, n::Integer) where F RT = Core.Inference.return_type(f, Tuple{Int}) - TT = NTuple{RT, n} + TT = NTuple{n, RT} if isbits(RT) r = Ref{TT}() for i = 1:n @@ -318,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/codegen.cpp b/src/codegen.cpp index bbf0ee30079c9..96baa52da975c 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -4721,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 { diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 0ec766a38c0be..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))))