Skip to content

Commit e2acbaf

Browse files
committed
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)
1 parent 1e9fb81 commit e2acbaf

13 files changed

+160
-26
lines changed

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ Julia v0.7.0 Release Notes
44
New language features
55
---------------------
66

7+
* Local variables can be tested for being defined
8+
using the new `@isdefined variable` macro ([#TBD]).
9+
710
Language changes
811
----------------
912

base/inference.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ add_tfunc(===, 2, 2,
542542
end
543543
return Bool
544544
end)
545-
add_tfunc(isdefined, 1, IInf, (args...)->Bool)
545+
add_tfunc(isdefined, 1, 2, (args...)->Bool)
546546
add_tfunc(Core.sizeof, 1, 1, x->Int)
547547
add_tfunc(nfields, 1, 1,
548548
function (x::ANY)
@@ -2064,6 +2064,8 @@ function abstract_eval(e::ANY, vtypes::VarTable, sv::InferenceState)
20642064
return abstract_eval_constant(e.args[1])
20652065
elseif e.head === :invoke
20662066
error("type inference data-flow error: tried to double infer a function")
2067+
elseif e.head === :isdefined
2068+
return Bool
20672069
else
20682070
t = Any
20692071
end

base/reflection.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,10 @@ isconst(m::Module, s::Symbol) =
171171
"""
172172
@isdefined s -> Bool
173173
174-
Tests whether symbol `s` is defined in the current scope.
174+
Tests whether variable `s` is defined in the current scope.
175175
"""
176176
macro isdefined(s::Symbol)
177-
return :(isdefined($__module__, $(QuoteNode(s))))
177+
return Expr(:isdefined, esc(s))
178178
end
179179

180180
# return an integer such that object_id(x)==object_id(y) if x===y

src/ast.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ jl_sym_t *inert_sym; jl_sym_t *vararg_sym;
5656
jl_sym_t *unused_sym; jl_sym_t *static_parameter_sym;
5757
jl_sym_t *polly_sym; jl_sym_t *inline_sym;
5858
jl_sym_t *propagate_inbounds_sym;
59+
jl_sym_t *isdefined_sym;
5960

6061
static uint8_t flisp_system_image[] = {
6162
#include <julia_flisp.boot.inc>
@@ -431,6 +432,7 @@ void jl_init_frontend(void)
431432
polly_sym = jl_symbol("polly");
432433
inline_sym = jl_symbol("inline");
433434
propagate_inbounds_sym = jl_symbol("propagate_inbounds");
435+
isdefined_sym = jl_symbol("isdefined");
434436
}
435437

436438
JL_DLLEXPORT void jl_lisp_prompt(void)

src/ccall.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ static bool runtime_sym_gvs(const char *f_lib, const char *f_name, MT &&M,
9595
}
9696
}
9797
if (libsym == NULL) {
98-
libsym = *(void**)jl_get_global(libptrgv);
98+
libsym = *(void**)jl_get_globalvar(libptrgv);
9999
}
100100
assert(libsym != NULL);
101101

src/codegen.cpp

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ static Function *jlboundserrorv_func;
357357
static Function *jlcheckassign_func;
358358
static Function *jldeclareconst_func;
359359
static Function *jlgetbindingorerror_func;
360+
static Function *jlboundp_func;
360361
static Function *jltopeval_func;
361362
static Function *jlcopyast_func;
362363
static Function *jltuple_func;
@@ -2325,7 +2326,6 @@ static void simple_escape_analysis(jl_value_t *expr, bool esc, jl_codectx_t *ctx
23252326
}
23262327
}
23272328
else if (e->head == foreigncall_sym) {
2328-
esc = true;
23292329
simple_escape_analysis(jl_exprarg(e, 0), esc, ctx);
23302330
// 2nd and 3d arguments are static
23312331
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
23342334
}
23352335
}
23362336
else if (e->head == method_sym) {
2337-
simple_escape_analysis(jl_exprarg(e,0), esc, ctx);
2337+
simple_escape_analysis(jl_exprarg(e, 0), esc, ctx);
23382338
if (jl_expr_nargs(e) > 1) {
2339-
simple_escape_analysis(jl_exprarg(e,1), esc, ctx);
2340-
simple_escape_analysis(jl_exprarg(e,2), esc, ctx);
2339+
simple_escape_analysis(jl_exprarg(e, 1), esc, ctx);
2340+
simple_escape_analysis(jl_exprarg(e, 2), esc, ctx);
23412341
}
23422342
}
23432343
else if (e->head == assign_sym) {
23442344
// don't consider assignment LHS as a variable "use"
2345-
simple_escape_analysis(jl_exprarg(e,1), esc, ctx);
2345+
simple_escape_analysis(jl_exprarg(e, 1), esc, ctx);
23462346
}
23472347
else if (e->head != line_sym) {
2348+
if (e->head == isdefined_sym)
2349+
esc = false;
23482350
size_t elen = jl_array_dim0(e->args);
2349-
for(i=0; i < elen; i++) {
2350-
simple_escape_analysis(jl_exprarg(e,i), esc, ctx);
2351+
for (i = 0; i < elen; i++) {
2352+
simple_escape_analysis(jl_exprarg(e, i), esc, ctx);
23512353
}
23522354
}
23532355
return;
@@ -3603,6 +3605,67 @@ static jl_cgval_t emit_global(jl_sym_t *sym, jl_codectx_t *ctx)
36033605
return emit_checked_var(bp, sym, ctx, false, tbaa_binding);
36043606
}
36053607

3608+
static jl_cgval_t emit_isdefined(jl_value_t *sym, jl_codectx_t *ctx)
3609+
{
3610+
Value *isnull;
3611+
if (jl_is_slot(sym)) {
3612+
size_t sl = jl_slot_number(sym) - 1;
3613+
jl_varinfo_t &vi = ctx->slots[sl];
3614+
if (!vi.usedUndef)
3615+
return mark_julia_const(jl_true);
3616+
if (vi.boxroot == NULL || vi.pTIndex != NULL) {
3617+
assert(vi.defFlag);
3618+
isnull = builder.CreateLoad(vi.defFlag, vi.isVolatile);
3619+
}
3620+
if (vi.boxroot != NULL) {
3621+
Value *boxed = builder.CreateLoad(vi.boxroot, vi.isVolatile);
3622+
Value *box_isnull = builder.CreateICmpNE(boxed, V_null);
3623+
if (vi.pTIndex) {
3624+
// value is either boxed in the stack slot, or unboxed in value
3625+
// as indicated by testing (pTIndex & 0x80)
3626+
Value *tindex = builder.CreateLoad(vi.pTIndex, vi.isVolatile);
3627+
Value *load_unbox = builder.CreateICmpEQ(
3628+
builder.CreateAnd(tindex, ConstantInt::get(T_int8, 0x80)),
3629+
ConstantInt::get(T_int8, 0));
3630+
isnull = builder.CreateSelect(load_unbox, isnull, box_isnull);
3631+
}
3632+
else {
3633+
isnull = box_isnull;
3634+
}
3635+
}
3636+
}
3637+
else {
3638+
jl_module_t *modu;
3639+
jl_sym_t *name;
3640+
if (jl_is_globalref(sym)) {
3641+
modu = jl_globalref_mod(sym);
3642+
name = jl_globalref_name(sym);
3643+
}
3644+
else {
3645+
assert(jl_is_symbol(sym) && "malformed isdefined expression");
3646+
modu = ctx->module;
3647+
name = (jl_sym_t*)sym;
3648+
}
3649+
jl_binding_t *bnd = jl_get_binding(modu, name);
3650+
if (bnd) {
3651+
if (bnd->value != NULL)
3652+
return mark_julia_const(jl_true);
3653+
Value *bp = julia_binding_gv(bnd);
3654+
Instruction *v = builder.CreateLoad(bp);
3655+
tbaa_decorate(tbaa_binding, v);
3656+
isnull = builder.CreateICmpNE(v, V_null);
3657+
}
3658+
else {
3659+
Value *v = builder.CreateCall(prepare_call(jlboundp_func), {
3660+
literal_pointer_val((jl_value_t*)modu),
3661+
literal_pointer_val((jl_value_t*)name)
3662+
});
3663+
isnull = builder.CreateICmpNE(v, V_null);
3664+
}
3665+
}
3666+
return mark_julia_type(isnull, false, jl_bool_type, ctx);
3667+
}
3668+
36063669
static jl_cgval_t emit_local(jl_value_t *slotload, jl_codectx_t *ctx)
36073670
{
36083671
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)
41314194
// this is object-disoriented.
41324195
// however, this is a good way to do it because it should *not* be easy
41334196
// to add new node types.
4134-
if (head == invoke_sym) {
4197+
if (head == isdefined_sym) {
4198+
return emit_isdefined(args[0], ctx);
4199+
}
4200+
else if (head == invoke_sym) {
41354201
return emit_invoke(ex, ctx);
41364202
}
41374203
else if (head == call_sym) {
@@ -6774,6 +6840,12 @@ static void init_julia_llvm_env(Module *m)
67746840
"jl_get_binding_or_error", m);
67756841
add_named_global(jlgetbindingorerror_func, &jl_get_binding_or_error);
67766842

6843+
jlboundp_func =
6844+
Function::Create(FunctionType::get(T_pjlvalue, args_2ptrs, false),
6845+
Function::ExternalLinkage,
6846+
"jl_boundp", m);
6847+
add_named_global(jlboundp_func, &jl_boundp);
6848+
67776849
builtin_func_map[jl_f_is] = jlcall_func_to_llvm("jl_f_is", &jl_f_is, m);
67786850
builtin_func_map[jl_f_typeof] = jlcall_func_to_llvm("jl_f_typeof", &jl_f_typeof, m);
67796851
builtin_func_map[jl_f_sizeof] = jlcall_func_to_llvm("jl_f_sizeof", &jl_f_sizeof, m);

src/dump.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3347,7 +3347,7 @@ void jl_init_serializer(void)
33473347

33483348
jl_emptysvec, jl_emptytuple, jl_false, jl_true, jl_nothing, jl_any_type,
33493349
call_sym, invoke_sym, goto_ifnot_sym, return_sym, body_sym, line_sym,
3350-
lambda_sym, jl_symbol("tuple"), assign_sym,
3350+
lambda_sym, jl_symbol("tuple"), assign_sym, isdefined_sym,
33513351

33523352
// empirical list of very common symbols
33533353
#include "common_symbols1.inc"

src/interpreter.c

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -203,33 +203,46 @@ static jl_value_t *eval(jl_value_t *e, interpreter_state *s)
203203
ssize_t n = jl_slot_number(e);
204204
if (src == NULL || n > jl_source_nslots(src) || n < 1 || s->locals == NULL)
205205
jl_error("access to invalid slot number");
206-
jl_value_t *v = s->locals[n-1];
206+
jl_value_t *v = s->locals[n - 1];
207207
if (v == NULL)
208208
jl_undefined_var_error((jl_sym_t*)jl_array_ptr_ref(src->slotnames, n - 1));
209209
return v;
210210
}
211211
if (jl_is_globalref(e)) {
212-
jl_sym_t *s = jl_globalref_name(e);
213-
jl_value_t *v = jl_get_global(jl_globalref_mod(e), s);
214-
if (v == NULL)
215-
jl_undefined_var_error(s);
216-
return v;
212+
return jl_eval_global_var(jl_globalref_mod(e), jl_globalref_name(e));
217213
}
218214
if (jl_is_quotenode(e))
219215
return jl_fieldref(e,0);
220216
jl_module_t *modu = s->module;
221217
if (jl_is_symbol(e)) { // bare symbols appear in toplevel exprs not wrapped in `thunk`
222-
jl_value_t *v = jl_get_global(modu, (jl_sym_t*)e);
223-
if (v == NULL)
224-
jl_undefined_var_error((jl_sym_t*)e);
225-
return v;
218+
return jl_eval_global_var(modu, (jl_sym_t*)e);
226219
}
227220
if (!jl_is_expr(e))
228221
return e;
229222
jl_expr_t *ex = (jl_expr_t*)e;
230223
jl_value_t **args = (jl_value_t**)jl_array_data(ex->args);
231224
size_t nargs = jl_array_len(ex->args);
232-
if (ex->head == call_sym) {
225+
if (ex->head == isdefined_sym) {
226+
jl_value_t *sym = args[0];
227+
int defined = 0;
228+
if (jl_is_slot(sym)) {
229+
ssize_t n = jl_slot_number(sym);
230+
if (src == NULL || n > jl_source_nslots(src) || n < 1 || s->locals == NULL)
231+
jl_error("access to invalid slot number");
232+
defined = s->locals[n - 1] != NULL;
233+
}
234+
else if (jl_is_globalref(sym)) {
235+
defined = jl_boundp(jl_globalref_mod(sym), jl_globalref_name(sym));
236+
}
237+
else if (jl_is_symbol(sym)) {
238+
defined = jl_boundp(modu, (jl_sym_t*)sym);
239+
}
240+
else {
241+
assert(0 && "malformed isdefined expression");
242+
}
243+
return defined ? jl_true : jl_false;
244+
}
245+
else if (ex->head == call_sym) {
233246
return do_call(args, nargs, s);
234247
}
235248
else if (ex->head == invoke_sym) {

src/jitlayers.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1048,7 +1048,7 @@ GlobalVariable *jl_emit_sysimg_slot(Module *m, Type *typ, const char *name,
10481048
return gv;
10491049
}
10501050

1051-
void* jl_get_global(GlobalVariable *gv)
1051+
void* jl_get_globalvar(GlobalVariable *gv)
10521052
{
10531053
#if defined(USE_MCJIT) || defined(USE_ORCJIT)
10541054
void *p = (void*)(intptr_t)jl_ExecutionEngine->getPointerToGlobalIfAvailable(gv);

src/jitlayers.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ void addOptimizationPasses(PassManager *PM);
6868
void* jl_emit_and_add_to_shadow(GlobalVariable *gv, void *gvarinit = NULL);
6969
GlobalVariable *jl_emit_sysimg_slot(Module *m, Type *typ, const char *name,
7070
uintptr_t init, size_t &idx);
71-
void* jl_get_global(GlobalVariable *gv);
71+
void* jl_get_globalvar(GlobalVariable *gv);
7272
GlobalVariable *jl_get_global_for(const char *cname, void *addr, Module *M);
7373
void jl_add_to_shadow(Module *m);
7474
void jl_init_function(Function *f);

src/julia-syntax.scm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3597,6 +3597,7 @@ f(x) = yt(x)
35973597
((local) #f)
35983598
((implicit-global) #f)
35993599
((const) (emit e))
3600+
((isdefined) (if tail (emit-return e) e))
36003601

36013602
;; top level expressions returning values
36023603
((abstract_type bits_type composite_type thunk toplevel module)

src/julia_internal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,7 @@ extern jl_sym_t *meta_sym; extern jl_sym_t *list_sym;
975975
extern jl_sym_t *inert_sym; extern jl_sym_t *static_parameter_sym;
976976
extern jl_sym_t *polly_sym; extern jl_sym_t *inline_sym;
977977
extern jl_sym_t *propagate_inbounds_sym;
978+
extern jl_sym_t *isdefined_sym;
978979

979980
#ifdef __cplusplus
980981
}

test/core.jl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4997,3 +4997,43 @@ end
49974997
end
49984998
@test M22026.foofunction(Int16) === Int16
49994999
@test M22026.foofunction2(3) === 6.0f0
5000+
5001+
# tests for isdefined behavior and code generation
5002+
global undefined_variable
5003+
@test @isdefined Test
5004+
@test !@isdefined undefined_variable
5005+
@test !@isdefined undefined_variable2
5006+
@test let local_undef, local_def = 1
5007+
!@isdefined local_undef
5008+
@isdefined local_def
5009+
end
5010+
f_isdefined_latedef() = @isdefined f_isdefined_def
5011+
@test !f_isdefined_latedef()
5012+
f_isdefined(x) = @isdefined x
5013+
f_isdefined_undef() = @isdefined x_isundef
5014+
f_isdefined_def() = @isdefined f_isdefined_def
5015+
@test f_isdefined(1)
5016+
@test f_isdefined("")
5017+
@test !f_isdefined_undef()
5018+
@test f_isdefined_def()
5019+
@test f_isdefined_latedef()
5020+
f_isdefined_defvarI() = (x = rand(Int); @isdefined x)
5021+
f_isdefined_defvarS() = (x = randstring(1); @isdefined x)
5022+
@test f_isdefined_defvarI()
5023+
@test f_isdefined_defvarS()
5024+
f_isdefined_undefvar() = (local x; @isdefined x)
5025+
@test !f_isdefined_undefvar()
5026+
f_isdefined_unionvar(y, t) = (t > 0 && (x = (t == 1 ? 1 : y)); @isdefined x)
5027+
@test f_isdefined_unionvar(nothing, 1)
5028+
@test f_isdefined_unionvar("", 1)
5029+
@test f_isdefined_unionvar(1.0, 1)
5030+
@test f_isdefined_unionvar(1, 1)
5031+
@test !f_isdefined_unionvar(nothing, 0)
5032+
@test !f_isdefined_unionvar("", 0)
5033+
@test !f_isdefined_unionvar(1.0, 0)
5034+
@test !f_isdefined_unionvar(1, 0)
5035+
f_isdefined_splat(x...) = @isdefined x
5036+
@test f_isdefined_splat(1, 2, 3)
5037+
@test let err = @macroexpand @isdefined :x
5038+
isa(err, Expr) && err.head === :error && isa(err.args[1], MethodError)
5039+
end

0 commit comments

Comments
 (0)