From ef030bd32a06b8c92c1bfe4079ec5f32887cc34e Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 7 Jun 2017 19:12:25 -0400 Subject: [PATCH] handle scope correctly for @isdefined this allows testing whether a variable is defined, and may be especially useful for emitting from a macro (which may want to conditionally initialize a variable) --- NEWS.md | 3 ++ base/inference.jl | 38 +++++++++++++++++-- base/reflection.jl | 4 +- src/ast.c | 2 + src/ccall.cpp | 2 +- src/codegen.cpp | 88 ++++++++++++++++++++++++++++++++++++++++---- src/dump.c | 2 +- src/interpreter.c | 35 ++++++++++++------ src/jitlayers.cpp | 2 +- src/jitlayers.h | 2 +- src/julia-syntax.scm | 19 ++++++++++ src/julia_internal.h | 1 + test/core.jl | 54 +++++++++++++++++++++++++++ 13 files changed, 223 insertions(+), 29 deletions(-) diff --git a/NEWS.md b/NEWS.md index 0f62292db74c1..08dc34e3e8d72 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,9 @@ Julia v0.7.0 Release Notes New language features --------------------- + * Local variables can be tested for being defined + using the new `@isdefined variable` macro ([#TBD]). + Language changes ---------------- diff --git a/base/inference.jl b/base/inference.jl index 5ef16364df4b5..73fc121884c44 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -542,7 +542,7 @@ add_tfunc(===, 2, 2, end return Bool end) -add_tfunc(isdefined, 1, IInf, (args...)->Bool) +add_tfunc(isdefined, 1, 2, (args...)->Bool) add_tfunc(Core.sizeof, 1, 1, x->Int) add_tfunc(nfields, 1, 1, function (x::ANY) @@ -2064,6 +2064,25 @@ function abstract_eval(e::ANY, vtypes::VarTable, sv::InferenceState) return abstract_eval_constant(e.args[1]) elseif e.head === :invoke error("type inference data-flow error: tried to double infer a function") + elseif e.head === :isdefined + sym = e.args[1] + t = Bool + if isa(sym, Slot) + vtyp = vtypes[slot_id(sym)] + if vtyp.typ === Bottom + t = Const(false) # never assigned previously + elseif !vtyp.undef + t = Const(true) # definitely assigned previously + end + elseif isa(sym, Symbol) + if isdefined(sv.mod, sym.name) + t = Const(true) + end + elseif isa(sym, GlobalRef) + if isdefined(sym.mod, sym.name) + t = Const(true) + end + end else t = Any end @@ -4342,6 +4361,10 @@ const corenumtype = Union{Int32, Int64, Float32, Float64} # return inlined replacement for `e`, inserting new needed statements # at index `ins` in `stmts`. function inlining_pass(e::Expr, sv::InferenceState, stmts, ins) + if e.head === :isdefined + isa(e.typ, Const) && return e.typ.val + return e + end if e.head === :method # avoid running the inlining pass on function definitions return e @@ -4654,15 +4677,22 @@ function replace_vars!(src::CodeInfo, r::ObjectIdDict) end function _replace_vars!(e::ANY, r::ObjectIdDict) - if isa(e,SSAValue) || isa(e,Slot) + if isa(e, SSAValue) || isa(e, Slot) v = normvar(e) if haskey(r, v) return r[v] end end - if isa(e,Expr) + if isa(e, Expr) for i = 1:length(e.args) - e.args[i] = _replace_vars!(e.args[i], r) + a = e.args[i] + if e.head === :isdefined + if e.args[i] !== _replace_vars!(a, r) + return true + end + else + e.args[i] = _replace_vars!(a, r) + end end end return e diff --git a/base/reflection.jl b/base/reflection.jl index 63eb16d12ecf0..a13fc7e9bdb4a 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -171,10 +171,10 @@ isconst(m::Module, s::Symbol) = """ @isdefined s -> Bool -Tests whether symbol `s` is defined in the current scope. +Tests whether variable `s` is defined in the current scope. """ macro isdefined(s::Symbol) - return :(isdefined($__module__, $(QuoteNode(s)))) + return Expr(:isdefined, esc(s)) end # return an integer such that object_id(x)==object_id(y) if x===y diff --git a/src/ast.c b/src/ast.c index 0b286de277d5b..aa1e5f3eee975 100644 --- a/src/ast.c +++ b/src/ast.c @@ -56,6 +56,7 @@ jl_sym_t *inert_sym; jl_sym_t *vararg_sym; jl_sym_t *unused_sym; jl_sym_t *static_parameter_sym; jl_sym_t *polly_sym; jl_sym_t *inline_sym; jl_sym_t *propagate_inbounds_sym; +jl_sym_t *isdefined_sym; static uint8_t flisp_system_image[] = { #include @@ -431,6 +432,7 @@ void jl_init_frontend(void) polly_sym = jl_symbol("polly"); inline_sym = jl_symbol("inline"); propagate_inbounds_sym = jl_symbol("propagate_inbounds"); + isdefined_sym = jl_symbol("isdefined"); } JL_DLLEXPORT void jl_lisp_prompt(void) diff --git a/src/ccall.cpp b/src/ccall.cpp index 32058d0a47731..c50f0bb20e8e8 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -95,7 +95,7 @@ static bool runtime_sym_gvs(const char *f_lib, const char *f_name, MT &&M, } } if (libsym == NULL) { - libsym = *(void**)jl_get_global(libptrgv); + libsym = *(void**)jl_get_globalvar(libptrgv); } assert(libsym != NULL); diff --git a/src/codegen.cpp b/src/codegen.cpp index d701a6181a66c..b7d16107a67e2 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -357,6 +357,7 @@ static Function *jlboundserrorv_func; static Function *jlcheckassign_func; static Function *jldeclareconst_func; static Function *jlgetbindingorerror_func; +static Function *jlboundp_func; static Function *jltopeval_func; static Function *jlcopyast_func; static Function *jltuple_func; @@ -2325,7 +2326,6 @@ static void simple_escape_analysis(jl_value_t *expr, bool esc, jl_codectx_t *ctx } } else if (e->head == foreigncall_sym) { - esc = true; simple_escape_analysis(jl_exprarg(e, 0), esc, ctx); // 2nd and 3d arguments are static size_t alen = jl_array_dim0(e->args); @@ -2334,20 +2334,22 @@ static void simple_escape_analysis(jl_value_t *expr, bool esc, jl_codectx_t *ctx } } else if (e->head == method_sym) { - simple_escape_analysis(jl_exprarg(e,0), esc, ctx); + simple_escape_analysis(jl_exprarg(e, 0), esc, ctx); if (jl_expr_nargs(e) > 1) { - simple_escape_analysis(jl_exprarg(e,1), esc, ctx); - simple_escape_analysis(jl_exprarg(e,2), esc, ctx); + simple_escape_analysis(jl_exprarg(e, 1), esc, ctx); + simple_escape_analysis(jl_exprarg(e, 2), esc, ctx); } } else if (e->head == assign_sym) { // don't consider assignment LHS as a variable "use" - simple_escape_analysis(jl_exprarg(e,1), esc, ctx); + simple_escape_analysis(jl_exprarg(e, 1), esc, ctx); } else if (e->head != line_sym) { + if (e->head == isdefined_sym) + esc = false; size_t elen = jl_array_dim0(e->args); - for(i=0; i < elen; i++) { - simple_escape_analysis(jl_exprarg(e,i), esc, ctx); + for (i = 0; i < elen; i++) { + simple_escape_analysis(jl_exprarg(e, i), esc, ctx); } } return; @@ -3603,6 +3605,67 @@ static jl_cgval_t emit_global(jl_sym_t *sym, jl_codectx_t *ctx) return emit_checked_var(bp, sym, ctx, false, tbaa_binding); } +static jl_cgval_t emit_isdefined(jl_value_t *sym, jl_codectx_t *ctx) +{ + Value *isnull; + if (jl_is_slot(sym)) { + size_t sl = jl_slot_number(sym) - 1; + jl_varinfo_t &vi = ctx->slots[sl]; + if (!vi.usedUndef) + return mark_julia_const(jl_true); + if (vi.boxroot == NULL || vi.pTIndex != NULL) { + assert(vi.defFlag); + isnull = builder.CreateLoad(vi.defFlag, vi.isVolatile); + } + if (vi.boxroot != NULL) { + Value *boxed = builder.CreateLoad(vi.boxroot, vi.isVolatile); + Value *box_isnull = builder.CreateICmpNE(boxed, V_null); + if (vi.pTIndex) { + // value is either boxed in the stack slot, or unboxed in value + // as indicated by testing (pTIndex & 0x80) + Value *tindex = builder.CreateLoad(vi.pTIndex, vi.isVolatile); + Value *load_unbox = builder.CreateICmpEQ( + builder.CreateAnd(tindex, ConstantInt::get(T_int8, 0x80)), + ConstantInt::get(T_int8, 0)); + isnull = builder.CreateSelect(load_unbox, isnull, box_isnull); + } + else { + isnull = box_isnull; + } + } + } + else { + jl_module_t *modu; + jl_sym_t *name; + if (jl_is_globalref(sym)) { + modu = jl_globalref_mod(sym); + name = jl_globalref_name(sym); + } + else { + assert(jl_is_symbol(sym) && "malformed isdefined expression"); + modu = ctx->module; + name = (jl_sym_t*)sym; + } + jl_binding_t *bnd = jl_get_binding(modu, name); + if (bnd) { + if (bnd->value != NULL) + return mark_julia_const(jl_true); + Value *bp = julia_binding_gv(bnd); + Instruction *v = builder.CreateLoad(bp); + tbaa_decorate(tbaa_binding, v); + isnull = builder.CreateICmpNE(v, V_null); + } + else { + Value *v = builder.CreateCall(prepare_call(jlboundp_func), { + literal_pointer_val((jl_value_t*)modu), + literal_pointer_val((jl_value_t*)name) + }); + isnull = builder.CreateICmpNE(v, V_null); + } + } + return mark_julia_type(isnull, false, jl_bool_type, ctx); +} + static jl_cgval_t emit_local(jl_value_t *slotload, jl_codectx_t *ctx) { size_t sl = jl_slot_number(slotload) - 1; @@ -4131,7 +4194,10 @@ static jl_cgval_t emit_expr(jl_value_t *expr, jl_codectx_t *ctx) // this is object-disoriented. // however, this is a good way to do it because it should *not* be easy // to add new node types. - if (head == invoke_sym) { + if (head == isdefined_sym) { + return emit_isdefined(args[0], ctx); + } + else if (head == invoke_sym) { return emit_invoke(ex, ctx); } else if (head == call_sym) { @@ -6774,6 +6840,12 @@ static void init_julia_llvm_env(Module *m) "jl_get_binding_or_error", m); add_named_global(jlgetbindingorerror_func, &jl_get_binding_or_error); + jlboundp_func = + Function::Create(FunctionType::get(T_pjlvalue, args_2ptrs, false), + Function::ExternalLinkage, + "jl_boundp", m); + add_named_global(jlboundp_func, &jl_boundp); + builtin_func_map[jl_f_is] = jlcall_func_to_llvm("jl_f_is", &jl_f_is, m); builtin_func_map[jl_f_typeof] = jlcall_func_to_llvm("jl_f_typeof", &jl_f_typeof, m); builtin_func_map[jl_f_sizeof] = jlcall_func_to_llvm("jl_f_sizeof", &jl_f_sizeof, m); diff --git a/src/dump.c b/src/dump.c index c020c554b29e6..b5a46e0d843f2 100644 --- a/src/dump.c +++ b/src/dump.c @@ -3347,7 +3347,7 @@ void jl_init_serializer(void) jl_emptysvec, jl_emptytuple, jl_false, jl_true, jl_nothing, jl_any_type, call_sym, invoke_sym, goto_ifnot_sym, return_sym, body_sym, line_sym, - lambda_sym, jl_symbol("tuple"), assign_sym, + lambda_sym, jl_symbol("tuple"), assign_sym, isdefined_sym, // empirical list of very common symbols #include "common_symbols1.inc" diff --git a/src/interpreter.c b/src/interpreter.c index b78fd15686fd3..55ac7b7b819a3 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -203,33 +203,46 @@ static jl_value_t *eval(jl_value_t *e, interpreter_state *s) ssize_t n = jl_slot_number(e); if (src == NULL || n > jl_source_nslots(src) || n < 1 || s->locals == NULL) jl_error("access to invalid slot number"); - jl_value_t *v = s->locals[n-1]; + jl_value_t *v = s->locals[n - 1]; if (v == NULL) jl_undefined_var_error((jl_sym_t*)jl_array_ptr_ref(src->slotnames, n - 1)); return v; } if (jl_is_globalref(e)) { - jl_sym_t *s = jl_globalref_name(e); - jl_value_t *v = jl_get_global(jl_globalref_mod(e), s); - if (v == NULL) - jl_undefined_var_error(s); - return v; + return jl_eval_global_var(jl_globalref_mod(e), jl_globalref_name(e)); } if (jl_is_quotenode(e)) return jl_fieldref(e,0); jl_module_t *modu = s->module; if (jl_is_symbol(e)) { // bare symbols appear in toplevel exprs not wrapped in `thunk` - jl_value_t *v = jl_get_global(modu, (jl_sym_t*)e); - if (v == NULL) - jl_undefined_var_error((jl_sym_t*)e); - return v; + return jl_eval_global_var(modu, (jl_sym_t*)e); } if (!jl_is_expr(e)) return e; jl_expr_t *ex = (jl_expr_t*)e; jl_value_t **args = (jl_value_t**)jl_array_data(ex->args); size_t nargs = jl_array_len(ex->args); - if (ex->head == call_sym) { + if (ex->head == isdefined_sym) { + jl_value_t *sym = args[0]; + int defined = 0; + if (jl_is_slot(sym)) { + ssize_t n = jl_slot_number(sym); + if (src == NULL || n > jl_source_nslots(src) || n < 1 || s->locals == NULL) + jl_error("access to invalid slot number"); + defined = s->locals[n - 1] != NULL; + } + else if (jl_is_globalref(sym)) { + defined = jl_boundp(jl_globalref_mod(sym), jl_globalref_name(sym)); + } + else if (jl_is_symbol(sym)) { + defined = jl_boundp(modu, (jl_sym_t*)sym); + } + else { + assert(0 && "malformed isdefined expression"); + } + return defined ? jl_true : jl_false; + } + else if (ex->head == call_sym) { return do_call(args, nargs, s); } else if (ex->head == invoke_sym) { diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index 54a950a4ec99d..e6ae9474d9c9c 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -1048,7 +1048,7 @@ GlobalVariable *jl_emit_sysimg_slot(Module *m, Type *typ, const char *name, return gv; } -void* jl_get_global(GlobalVariable *gv) +void* jl_get_globalvar(GlobalVariable *gv) { #if defined(USE_MCJIT) || defined(USE_ORCJIT) void *p = (void*)(intptr_t)jl_ExecutionEngine->getPointerToGlobalIfAvailable(gv); diff --git a/src/jitlayers.h b/src/jitlayers.h index 92a318c2e3fbf..972867f0ebda6 100644 --- a/src/jitlayers.h +++ b/src/jitlayers.h @@ -68,7 +68,7 @@ void addOptimizationPasses(PassManager *PM); void* jl_emit_and_add_to_shadow(GlobalVariable *gv, void *gvarinit = NULL); GlobalVariable *jl_emit_sysimg_slot(Module *m, Type *typ, const char *name, uintptr_t init, size_t &idx); -void* jl_get_global(GlobalVariable *gv); +void* jl_get_globalvar(GlobalVariable *gv); GlobalVariable *jl_get_global_for(const char *cname, void *addr, Module *M); void jl_add_to_shadow(Module *m); void jl_init_function(Function *f); diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 123e44621140a..bb4226f56b4c4 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -3073,6 +3073,24 @@ f(x) = yt(x) (assq (cadr e) (cadr (lam:vinfo lam)))) '(null) e)) + ((isdefined) ;; convert isdefined expr to function for closure converted variables + (let* ((sym (cadr e)) + (vi (and (symbol? sym) (assq sym (car (lam:vinfo lam))))) + (cv (and (symbol? sym) (assq sym (cadr (lam:vinfo lam)))))) + (cond ((eq? sym fname) e) + ((memq sym (lam:sp lam)) e) + (cv + (if (and (vinfo:asgn cv) (vinfo:capt cv)) + (let ((access (if interp + `($ (call (core QuoteNode) ,sym)) + `(call (core getfield) ,fname (inert ,sym))))) + `(call (core isdefined) ,access (inert contents))) + 'true)) + (vi + (if (and (vinfo:asgn vi) (vinfo:capt vi)) + `(call (core isdefined) ,sym (inert contents)) + e)) + (else e)))) ((method) (let* ((name (method-expr-name e)) (short (length= e 2)) ;; function f end @@ -3597,6 +3615,7 @@ f(x) = yt(x) ((local) #f) ((implicit-global) #f) ((const) (emit e)) + ((isdefined) (if tail (emit-return e) e)) ;; top level expressions returning values ((abstract_type bits_type composite_type thunk toplevel module) diff --git a/src/julia_internal.h b/src/julia_internal.h index 9337c784fcff6..877ddb4400d58 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -975,6 +975,7 @@ extern jl_sym_t *meta_sym; extern jl_sym_t *list_sym; extern jl_sym_t *inert_sym; extern jl_sym_t *static_parameter_sym; extern jl_sym_t *polly_sym; extern jl_sym_t *inline_sym; extern jl_sym_t *propagate_inbounds_sym; +extern jl_sym_t *isdefined_sym; #ifdef __cplusplus } diff --git a/test/core.jl b/test/core.jl index 32d0f73a1d23d..e83a4fb885897 100644 --- a/test/core.jl +++ b/test/core.jl @@ -4997,3 +4997,57 @@ end end @test M22026.foofunction(Int16) === Int16 @test M22026.foofunction2(3) === 6.0f0 + +# tests for isdefined behavior and code generation +global undefined_variable +@test @isdefined Test +@test !@isdefined undefined_variable +@test !@isdefined undefined_variable2 +@test let local_undef, local_def = 1 + !@isdefined local_undef + @isdefined local_def +end +f_isdefined_latedef() = @isdefined f_isdefined_def +@test !f_isdefined_latedef() +f_isdefined(x) = @isdefined x +f_isdefined_undef() = @isdefined x_isundef +f_isdefined_def() = @isdefined f_isdefined_def +@test f_isdefined(1) +@test f_isdefined("") +@test !f_isdefined_undef() +@test f_isdefined_def() +@test f_isdefined_latedef() +f_isdefined_defvarI() = (x = rand(Int); @isdefined x) +f_isdefined_defvarS() = (x = randstring(1); @isdefined x) +@test f_isdefined_defvarI() +@test f_isdefined_defvarS() +f_isdefined_undefvar() = (local x; @isdefined x) +@test !f_isdefined_undefvar() +f_isdefined_unionvar(y, t) = (t > 0 && (x = (t == 1 ? 1 : y)); @isdefined x) +@test f_isdefined_unionvar(nothing, 1) +@test f_isdefined_unionvar("", 1) +@test f_isdefined_unionvar(1.0, 1) +@test f_isdefined_unionvar(1, 1) +@test !f_isdefined_unionvar(nothing, 0) +@test !f_isdefined_unionvar("", 0) +@test !f_isdefined_unionvar(1.0, 0) +@test !f_isdefined_unionvar(1, 0) +f_isdefined_splat(x...) = @isdefined x +@test f_isdefined_splat(1, 2, 3) +@test let err = @macroexpand @isdefined :x + isa(err, Expr) && err.head === :error && isa(err.args[1], MethodError) +end +f_isdefined_cl_1(y) = (local x; for i = 1:y; x = 2; end; () -> x; @isdefined x) +f_isdefined_cl_2(y) = (local x; for i = 1:y; x = 2; end; () -> @isdefined x) +f_isdefined_cl_3() = (x = 2; () -> x; @isdefined x) +f_isdefined_cl_4() = (local x; () -> x; @isdefined x) +f_isdefined_cl_5() = (x = 2; () -> @isdefined x) +f_isdefined_cl_6() = (local x; () -> @isdefined x) +@test f_isdefined_cl_1(1) +@test !f_isdefined_cl_1(0) +@test f_isdefined_cl_2(1)() +@test !f_isdefined_cl_2(0)() +@test f_isdefined_cl_3() +@test !f_isdefined_cl_4() +@test f_isdefined_cl_5()() +@test !f_isdefined_cl_6()()