Skip to content

Commit

Permalink
Merge pull request #22281 from JuliaLang/jn/isdefined-local
Browse files Browse the repository at this point in the history
handle scope correctly for `isdefined` macro
  • Loading branch information
vtjnash authored Jun 9, 2017
2 parents 70915de + ef030bd commit 69f3284
Show file tree
Hide file tree
Showing 13 changed files with 223 additions and 29 deletions.
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------------

Expand Down
38 changes: 34 additions & 4 deletions base/inference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,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)
Expand Down Expand Up @@ -2071,6 +2071,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
Expand Down Expand Up @@ -4353,6 +4372,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
Expand Down Expand Up @@ -4665,15 +4688,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
Expand Down
4 changes: 2 additions & 2 deletions base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 <julia_flisp.boot.inc>
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/ccall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
88 changes: 80 additions & 8 deletions src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/dump.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
35 changes: 24 additions & 11 deletions src/interpreter.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion src/jitlayers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/jitlayers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
19 changes: 19 additions & 0 deletions src/julia-syntax.scm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions src/julia_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Loading

0 comments on commit 69f3284

Please sign in to comment.