/
githubmirror
/
julia
Обзор
Документация
Войти
/
githubmirror
/
julia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/interpreter.c
1 016 строк
41 KB
Jameson Nash
clangsa: add thread-safety capability analysis of safepoint annotations (#62288)
15 июл 2026, 21:56
Не верифицирован
15 июл 2026, 21:56
9821f8a
Код
Авторство
О чём код?
// This file is a part of Julia. License is MIT: https://julialang.org/license #include <stdlib.h> #include <setjmp.h> #ifdef _OS_WINDOWS_ #include <malloc.h> #endif #include "julia.h" #include "julia_internal.h" #include "builtin_proto.h" #include "julia_assert.h" #ifdef __cplusplus extern "C" { #endif typedef struct { jl_code_info_t *src; // contains the names and number of slots jl_method_instance_t *mi; // MethodInstance we're executing, or NULL if toplevel jl_code_instance_t *ci; // CodeInstance we're executing (for generated functions) jl_module_t *module; // context for globals jl_value_t **locals; // slots for holding local slots and ssavalues jl_svec_t *sparam_vals; // method static parameters, if eval-ing a method body size_t ip; // Leak the currently-evaluating statement index to backtrace capture int preevaluation; // use special rules for pre-evaluating expressions (deprecated--only for ccall handling) int continue_at; // statement index to jump to after leaving exception handler (0 if none) } interpreter_state; // general alloca rules are incompatible on C and C++, so define a macro that deals with the difference #ifdef __cplusplus #define JL_CPPALLOCA(var,n) \ var = (decltype(var))alloca((n)) #else #define JL_CPPALLOCA(var,n) \ JL_CC_IGNORE_START("-Wc++-compat") \ var = alloca((n)); \ JL_CC_IGNORE_STOP #endif #ifdef __clang_gcanalyzer__ extern void JL_GC_ENABLEFRAME(interpreter_state*) JL_NOTSAFEPOINT; // This is necessary, because otherwise the analyzer considers this undefined // behavior and terminates the exploration #define JL_GC_PUSHFRAME(frame,locals,n) \ JL_CPPALLOCA(frame, sizeof(*frame)+((n) * sizeof(jl_value_t*))); \ memset(&frame[1], 0, sizeof(void*) * n); \ _JL_GC_PUSHARGS((jl_value_t**)&frame[1], n); \ locals = (jl_value_t**)&frame[1]; #else #define JL_GC_ENCODE_PUSHFRAME(n) ((((size_t)(n))<<2)|2) #define JL_GC_PUSHFRAME(frame,locals,n) \ JL_CPPALLOCA(frame, sizeof(*frame)+(((n)+3)*sizeof(jl_value_t*))); \ ((void**)&frame[1])[0] = NULL; \ ((void**)&frame[1])[1] = (void*)JL_GC_ENCODE_PUSHFRAME(n); \ ((void**)&frame[1])[2] = jl_pgcstack; \ memset(&((void**)&frame[1])[3], 0, (n)*sizeof(jl_value_t*)); \ jl_pgcstack = (jl_gcframe_t*)&(((void**)&frame[1])[1]); \ locals = &((jl_value_t**)&frame[1])[3]; // we define this separately so that we can populate the frame before we add it to the backtrace // it's recommended to mark the containing function with NOINLINE, though not essential #define JL_GC_ENABLEFRAME(frame) \ jl_signal_fence(); \ ((void**)&frame[1])[0] = __builtin_frame_address(0); #endif static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s) JL_CANSAFEPOINT; static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, int toplevel) JL_CANSAFEPOINT; // method definition form static jl_value_t *eval_methoddef(jl_expr_t *ex, interpreter_state *s) JL_CANSAFEPOINT { jl_value_t **args = jl_array_ptr_data(ex->args); // generic function definition if (jl_expr_nargs(ex) == 1) { jl_value_t **args = jl_array_ptr_data(ex->args); jl_sym_t *fname = (jl_sym_t*)args[0]; jl_module_t *modu = s->module; if (jl_is_globalref(fname)) { modu = jl_globalref_mod(fname); fname = jl_globalref_name(fname); } if (!jl_is_symbol(fname)) { jl_error("method: invalid declaration"); } return jl_declare_const_gf(modu, fname); } jl_value_t *atypes = NULL, *meth = NULL, *fname = NULL; JL_GC_PUSH3(&atypes, &meth, &fname); fname = eval_value(args[0], s); jl_methtable_t *mt = NULL; if (jl_is_mtable(fname)) mt = (jl_methtable_t*)fname; atypes = eval_value(args[1], s); meth = eval_value(args[2], s); jl_method_t *ret = jl_method_def((jl_svec_t*)atypes, mt, (jl_code_info_t*)meth, s->module); JL_GC_POP(); return (jl_value_t *)ret; } // expression evaluator static jl_value_t *do_call(jl_value_t **args, size_t nargs, interpreter_state *s) JL_CANSAFEPOINT { jl_value_t **argv; assert(nargs >= 1); JL_GC_PUSHARGS(argv, nargs); size_t i; for (i = 0; i < nargs; i++) argv[i] = eval_value(args[i], s); jl_value_t *result = jl_apply(argv, nargs); JL_GC_POP(); return result; } static jl_value_t *do_invoke(jl_value_t **args, size_t nargs, interpreter_state *s) JL_CANSAFEPOINT { jl_value_t **argv; assert(nargs >= 2); JL_GC_PUSHARGS(argv, nargs - 1); size_t i; for (i = 1; i < nargs; i++) argv[i-1] = eval_value(args[i], s); jl_value_t *c = args[0]; assert(jl_is_code_instance(c) || jl_is_method_instance(c)); jl_value_t *result = NULL; if (jl_is_code_instance(c)) { jl_code_instance_t *codeinst = (jl_code_instance_t*)c; assert(jl_atomic_load_relaxed(&codeinst->min_world) <= jl_current_task->world_age && jl_current_task->world_age <= jl_atomic_load_relaxed(&codeinst->max_world)); jl_callptr_t invoke = jl_atomic_load_acquire(&codeinst->invoke); if (!invoke) { jl_compile_codeinst(codeinst); invoke = jl_atomic_load_acquire(&codeinst->invoke); } if (invoke) { result = invoke(argv[0], nargs == 2 ? NULL : &argv[1], nargs - 2, codeinst); } else { if (codeinst->owner != jl_nothing) { jl_error("Failed to invoke or compile external codeinst"); } result = jl_invoke(argv[0], nargs == 2 ? NULL : &argv[1], nargs - 2, jl_get_ci_mi(codeinst)); } } else { result = jl_invoke(argv[0], nargs == 2 ? NULL : &argv[1], nargs - 2, (jl_method_instance_t*)c); } JL_GC_POP(); return result; } // get the global (throwing if null) in the current world jl_value_t *jl_eval_global_var(jl_module_t *m, jl_sym_t *e, size_t world) { jl_value_t *v = jl_get_global_value(m, e, world); if (v == NULL) jl_undefined_var_error(e, (jl_value_t*)m); return v; } // get the global (throwing if null) in the current world, optimized jl_value_t *jl_eval_globalref(jl_globalref_t *g, size_t world) { jl_value_t *v = jl_get_globalref_value(g, world); if (v == NULL) jl_undefined_var_error(g->name, (jl_value_t*)g->mod); return v; } static int jl_source_nslots(jl_code_info_t *src) JL_NOTSAFEPOINT { return jl_array_nrows(src->slotflags); } static int jl_source_nssavalues(jl_code_info_t *src) JL_NOTSAFEPOINT { return jl_is_long(src->ssavaluetypes) ? jl_unbox_long(src->ssavaluetypes) : jl_array_nrows(src->ssavaluetypes); } static void eval_stmt_value(jl_value_t *stmt, interpreter_state *s) JL_CANSAFEPOINT { jl_value_t *res = eval_value(stmt, s); s->locals[jl_source_nslots(s->src) + s->ip] = res; } static jl_value_t *eval_expr_tuple(jl_expr_t *ex, interpreter_state *s) JL_CANSAFEPOINT { // evaluate Expr(:tuple, ...) // only appears in post-lowered IR in foreignglobal / foreigncall assert(ex->head == jl_tuple_sym); jl_value_t **argv; jl_value_t **args = jl_array_ptr_data(ex->args); size_t nargs = jl_expr_nargs(ex); JL_GC_PUSHARGS(argv, nargs); for (size_t i = 0; i < nargs; i++) argv[i] = eval_value(args[i], s); jl_value_t *v = jl_f_tuple(NULL, argv, nargs); JL_GC_POP(); return v; } static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s) { jl_code_info_t *src = s->src; if (jl_is_ssavalue(e)) { ssize_t id = ((jl_ssavalue_t*)e)->id - 1; if (src == NULL || id >= jl_source_nssavalues(src) || id < 0 || s->locals == NULL) jl_error("access to invalid SSAValue"); else return s->locals[jl_source_nslots(src) + id]; } if (jl_is_slotnumber(e) || jl_is_argument(e)) { 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]; if (v == NULL) jl_undefined_var_error((jl_sym_t*)jl_array_ptr_ref(src->slotnames, n - 1), (jl_value_t*)jl_local_sym); return v; } if (jl_is_quotenode(e)) { return jl_quotenode_value(e); } if (jl_is_globalref(e)) { return jl_eval_globalref((jl_globalref_t*)e, jl_current_task->world_age); } if (jl_is_symbol(e)) { // bare symbols appear in toplevel exprs not wrapped in `thunk` return jl_eval_global_var(s->module, (jl_sym_t*)e, jl_current_task->world_age); } if (jl_is_pinode(e)) { jl_value_t *val = eval_value(jl_fieldref_noalloc(e, 0), s); #ifndef JL_NDEBUG JL_GC_PUSH1(&val); jl_typeassert(val, jl_fieldref_noalloc(e, 1)); JL_GC_POP(); #endif return val; } assert(!jl_is_phinode(e) && !jl_is_phicnode(e) && !jl_is_upsilonnode(e) && "malformed IR"); if (!jl_is_expr(e)) return e; jl_expr_t *ex = (jl_expr_t*)e; jl_value_t **args = jl_array_ptr_data(ex->args); size_t nargs = jl_array_nrows(ex->args); jl_sym_t *head = ex->head; if (head == jl_call_sym) { return do_call(args, nargs, s); } else if (head == jl_invoke_sym) { return do_invoke(args, nargs, s); } else if (head == jl_invoke_modify_sym) { return do_call(args + 1, nargs - 1, s); } else if (head == jl_isdefined_sym) { jl_value_t *sym = args[0]; int defined = 0; assert(nargs == 1 && "malformed IR"); if (jl_is_slotnumber(sym) || jl_is_argument(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) || jl_is_symbol(sym)) { jl_error("[Internal Error]: :isdefined on globalref should use `isdefinedglobal`"); } else if (jl_is_expr(sym) && ((jl_expr_t*)sym)->head == jl_static_parameter_sym) { ssize_t n = jl_unbox_long(jl_exprarg(sym, 0)); assert(n > 0); if (s->sparam_vals && n <= jl_svec_len(s->sparam_vals)) { jl_value_t *sp = jl_svecref(s->sparam_vals, n - 1); defined = jl_sparam_defined_value(sp) != NULL; } else { // static parameter val unknown needs to be an error for ccall jl_error("could not determine static parameter value"); } } else { assert(0 && "malformed isdefined expression"); } return defined ? jl_true : jl_false; } else if (head == jl_throw_undef_if_not_sym) { jl_value_t *cond = eval_value(args[1], s); assert(jl_is_bool(cond)); if (cond == jl_false) { jl_sym_t *var = (jl_sym_t*)args[0]; if (var == jl_getfield_undefref_sym) jl_throw(jl_undefref_exception); else jl_undefined_var_error(var, (jl_value_t*)jl_local_sym); } return jl_nothing; } else if (head == jl_new_sym) { jl_value_t **argv; JL_GC_PUSHARGS(argv, nargs); for (size_t i = 0; i < nargs; i++) argv[i] = eval_value(args[i], s); jl_value_t *v = jl_new_structv((jl_datatype_t*)argv[0], &argv[1], nargs - 1); JL_GC_POP(); return v; } else if (head == jl_splatnew_sym) { jl_value_t **argv; JL_GC_PUSHARGS(argv, 2); argv[0] = eval_value(args[0], s); argv[1] = eval_value(args[1], s); jl_value_t *v = jl_new_structt((jl_datatype_t*)argv[0], argv[1]); JL_GC_POP(); return v; } else if (head == jl_new_opaque_closure_sym) { jl_value_t **argv; JL_GC_PUSHARGS(argv, nargs); for (size_t i = 0; i < nargs; i++) argv[i] = eval_value(args[i], s); JL_NARGSV(new_opaque_closure, 4); jl_value_t *ret = (jl_value_t*)jl_new_opaque_closure((jl_tupletype_t*)argv[0], argv[1], argv[2], argv[4], argv+5, nargs-5, 1); JL_GC_POP(); return ret; } else if (head == jl_static_parameter_sym) { ssize_t n = jl_unbox_long(args[0]); assert(n > 0); if (s->sparam_vals && n <= jl_svec_len(s->sparam_vals)) { jl_value_t *sp = jl_svecref(s->sparam_vals, n - 1); jl_value_t *defval = jl_sparam_defined_value(sp); if (defval != NULL) return defval; if (!s->preevaluation) { // look up the parameter name from the method's signature jl_unionall_t *sig = (jl_unionall_t*)s->mi->def.method->sig; jl_tvar_t *var = NULL; for (ssize_t i = n; i > 0; i--) { if (jl_is_unionall(sig)) { var = sig->var; sig = (jl_unionall_t*)sig->body; } else { jl_error("malformed method signature"); } } jl_undefined_var_error(var->name, (jl_value_t*)jl_static_parameter_sym); } return sp; } // static parameter val unknown needs to be an error for ccall jl_error("could not determine static parameter value"); } else if (head == jl_copyast_sym) { return jl_copy_ast(eval_value(args[0], s)); } else if (head == jl_exc_sym) { return jl_current_exception(jl_current_task); } else if (head == jl_boundscheck_sym) { return jl_true; } else if (head == jl_meta_sym || head == jl_coverageeffect_sym || head == jl_inbounds_sym || head == jl_loopinfo_sym || head == jl_aliasscope_sym || head == jl_popaliasscope_sym || head == jl_inline_sym || head == jl_noinline_sym) { return jl_nothing; } else if (head == jl_gc_preserve_begin_sym || head == jl_gc_preserve_end_sym) { // The interpreter generally keeps values that were assigned in this scope // rooted. If the interpreter learns to be more aggressive here, we may // want to explicitly root these values. return jl_nothing; } else if (head == jl_method_sym && nargs == 1) { return eval_methoddef(ex, s); } else if (head == jl_foreigncall_sym) { jl_error("`ccall` requires the compiler"); } else if (head == jl_foreignglobal_sym) { assert(nargs == 1); jl_value_t *fptr = jl_exprarg(ex, 0); if (jl_is_quotenode(fptr)) { if (jl_is_string(jl_quotenode_value(fptr)) || jl_is_tuple(jl_quotenode_value(fptr))) fptr = jl_quotenode_value(fptr); } if (jl_is_expr(fptr)) { jl_expr_t *fptr_ex = (jl_expr_t*)fptr; if (fptr_ex->head == jl_tuple_sym) { jl_value_t *v = eval_expr_tuple(fptr_ex, s); JL_GC_PUSH1(&v); jl_value_t *r = jl_lookup_foreignsymbol(v); JL_GC_POP(); return r; } } else if (jl_is_tuple(fptr)) { return jl_lookup_foreignsymbol(fptr); } else if (jl_is_string(fptr)) { return jl_lookup_foreignsymbol(fptr); } else if (jl_is_quotenode(fptr) && jl_is_symbol(jl_quotenode_value(fptr))) { return jl_lookup_foreignsymbol(jl_quotenode_value(fptr)); } jl_value_t *v = eval_value(fptr, s); JL_TYPECHK(cglobal, pointer, v); JL_GC_PUSH1(&v); jl_value_t *r = jl_bitcast((jl_value_t*)jl_voidpointer_type, v); JL_GC_POP(); return r; } else if (head == jl_cfunction_sym) { jl_error("`cfunction` requires the compiler"); } jl_errorf("unsupported or misplaced expression %s", jl_symbol_name(head)); abort(); } // phi nodes don't behave like proper instructions, so we require a special interpreter to handle them static size_t eval_phi(jl_array_t *stmts, interpreter_state *s, size_t ns, size_t to) JL_CANSAFEPOINT { size_t from = s->ip; size_t ip = to; unsigned nphiblockstmts = 0; unsigned last_phi = 0; for (ip = to; ip < ns; ip++) { jl_value_t *e = jl_array_ptr_ref(stmts, ip); if (!jl_is_phinode(e)) { if (jl_is_expr(e) || jl_is_returnnode(e) || jl_is_gotoifnot(e) || jl_is_gotonode(e) || jl_is_phicnode(e) || jl_is_upsilonnode(e) || jl_is_ssavalue(e)) { break; } // Everything else is allowed in the phi-block for implementation // convenience - fall through. } else { last_phi = nphiblockstmts + 1; } nphiblockstmts += 1; } // Cut off the phi block at the last phi node. For global refs that are not // actually in the phi block, we want to evaluate them in the regular interpreter // loop instead to make sure exception state is set up properly in case they throw. nphiblockstmts = last_phi; ip = to + last_phi; if (nphiblockstmts) { jl_value_t **dest = &s->locals[jl_source_nslots(s->src) + to]; jl_value_t **phis; // = (jl_value_t**)alloca(sizeof(jl_value_t*) * nphiblockstmts); JL_GC_PUSHARGS(phis, nphiblockstmts); for (unsigned i = 0; i < nphiblockstmts; i++) { jl_value_t *e = jl_array_ptr_ref(stmts, to + i); if (!jl_is_phinode(e)) { // IR verification guarantees that the only thing that gets // evaluated here are constants, so it doesn't matter if we // update the locals or the phis, but let's be consistent // for simplicity. phis[i] = eval_value(e, s); continue; } jl_array_t *edges = (jl_array_t*)jl_fieldref_noalloc(e, 0); ssize_t edge = -1; size_t closest = to; // implicit edge has `to <= edge - 1 < to + i` // this is because we could see the following IR (all 1-indexed): // goto %3 unless %cond // %2 = phi ... // %3 = phi (1)[1 => %a], (2)[2 => %b] // from = 1, to = closest = 2, i = 1 --> edge = 2, edge_from = 2, from = 2 for (unsigned j = 0; j < jl_array_nrows(edges); ++j) { size_t edge_from = jl_array_data(edges, int32_t)[j]; // 1-indexed if (edge_from == from + 1) { if (edge == -1) edge = j; } else if (closest < edge_from && edge_from < (to + i + 1)) { // if we found a nearer implicit branch from fall-through, // that occurred since the last explicit branch, // we should use the value from that edge instead edge = j; closest = edge_from; } } jl_value_t *val = NULL; unsigned n_oldphi = closest - to; if (n_oldphi) { // promote this implicit branch to a basic block start // and move all phi values to their position in edges // note that we might have already processed some phi nodes // in this basic block, so we need to be extra careful here // to ignore those for (unsigned j = 0; j < n_oldphi; j++) { dest[j] = phis[j]; } for (unsigned j = n_oldphi; j < i; j++) { // move the rest to the start of phis phis[j - n_oldphi] = phis[j]; phis[j] = NULL; } from = closest - 1; i -= n_oldphi; dest += n_oldphi; to += n_oldphi; nphiblockstmts -= n_oldphi; } if (edge != -1) { // if edges list doesn't contain last branch, or the value is explicitly undefined // then this value should be unused. jl_array_t *values = (jl_array_t*)jl_fieldref_noalloc(e, 1); val = jl_array_ptr_ref(values, edge); if (val) val = eval_value(val, s); } phis[i] = val; } // now move all phi values to their position in edges for (unsigned j = 0; j < nphiblockstmts; j++) { dest[j] = phis[j]; } JL_GC_POP(); } return ip; } static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, int toplevel) { jl_handler_t __eh; size_t ns = jl_array_nrows(stmts); jl_task_t *ct = jl_current_task; while (1) { s->ip = ip; if (ip >= ns) jl_error("`body` expression must terminate in `return`. Use `block` instead."); jl_value_t *stmt = jl_array_ptr_ref(stmts, ip); assert(!jl_is_phinode(stmt)); size_t next_ip = ip + 1; assert(!jl_is_phinode(stmt) && !jl_is_phicnode(stmt) && "malformed IR"); if (jl_is_gotonode(stmt)) { next_ip = jl_gotonode_label(stmt) - 1; } else if (jl_is_gotoifnot(stmt)) { jl_value_t *cond = eval_value(jl_gotoifnot_cond(stmt), s); if (cond == jl_false) { next_ip = jl_gotoifnot_label(stmt) - 1; } else if (cond != jl_true) { jl_type_error("if", (jl_value_t*)jl_bool_type, cond); } } else if (jl_is_returnnode(stmt)) { return eval_value(jl_returnnode_value(stmt), s); } else if (jl_is_upsilonnode(stmt)) { jl_value_t *val = jl_fieldref_noalloc(stmt, 0); if (val) val = eval_value(val, s); jl_value_t *phic = s->locals[jl_source_nslots(s->src) + ip]; assert(jl_is_ssavalue(phic)); ssize_t id = ((jl_ssavalue_t*)phic)->id - 1; s->locals[jl_source_nslots(s->src) + id] = val; } else if (jl_is_enternode(stmt)) { jl_enter_handler(ct, &__eh); // This is a bit tricky, but supports the implementation of PhiC nodes. // They are conceptually slots, but the slot to store to doesn't get explicitly // mentioned in the store (aka the "UpsilonNode") (this makes them integrate more // nicely with the rest of the SSA representation). In a compiler, we would figure // out which slot to store to at compile time when we encounter the statement. We // can't quite do that here, but we do something similar: We scan the catch entry // block (the only place where PhiC nodes may occur) to find all the Upsilons we // can possibly encounter. Then, we remember which slot they store to (we abuse the // SSA value result array for this purpose). TODO: We could do this only the first // time we encounter a given enter. size_t catch_ip = jl_enternode_catch_dest(stmt); if (catch_ip) { catch_ip -= 1; while (catch_ip < ns) { jl_value_t *phicnode = jl_array_ptr_ref(stmts, catch_ip); if (!jl_is_phicnode(phicnode)) break; jl_array_t *values = (jl_array_t*)jl_fieldref_noalloc(phicnode, 0); for (size_t i = 0; i < jl_array_nrows(values); ++i) { jl_value_t *val = jl_array_ptr_ref(values, i); assert(jl_is_ssavalue(val)); size_t upsilon = ((jl_ssavalue_t*)val)->id - 1; assert(jl_is_upsilonnode(jl_array_ptr_ref(stmts, upsilon))); s->locals[jl_source_nslots(s->src) + upsilon] = jl_box_ssavalue(catch_ip + 1); } s->locals[jl_source_nslots(s->src) + catch_ip] = NULL; catch_ip += 1; } // store current top of exception stack for restore in pop_exception. } s->locals[jl_source_nslots(s->src) + ip] = jl_box_ulong(jl_excstack_state(ct)); if (jl_enternode_scope(stmt)) { jl_value_t *old_scope = ct->scope; // Identical to __eh.scope // GC preserve the old_scope, since it is not rooted in the `jl_handler_t *`, // the newly entered scope is preserved through the current_task. JL_GC_PUSH1(&old_scope); jl_value_t *new_scope = eval_value(jl_enternode_scope(stmt), s); jl_gc_wb_current_task(ct, new_scope); ct->scope = new_scope; if (!jl_setjmp(__eh.eh_ctx, 0)) { ct->eh = &__eh; eval_body(stmts, s, next_ip, toplevel); jl_unreachable(); } JL_GC_POP(); } else { if (!jl_setjmp(__eh.eh_ctx, 0)) { ct->eh = &__eh; eval_body(stmts, s, next_ip, toplevel); jl_unreachable(); } } if (s->continue_at) { // means we reached a :leave expression jl_eh_restore_state_noexcept(ct, &__eh); ip = s->continue_at; s->continue_at = 0; continue; } else { // a real exception jl_eh_restore_state(ct, &__eh); ip = catch_ip; assert(jl_enternode_catch_dest(stmt) != 0); continue; } } else if (jl_is_expr(stmt)) { // Most exprs are allowed to end a BB by fall through jl_sym_t *head = ((jl_expr_t*)stmt)->head; if (head == jl_assign_sym) { jl_value_t *lhs = jl_exprarg(stmt, 0); jl_value_t *rhs = eval_value(jl_exprarg(stmt, 1), s); if (jl_is_slotnumber(lhs)) { ssize_t n = jl_slot_number(lhs); assert(n <= jl_source_nslots(s->src) && n > 0); s->locals[n - 1] = rhs; } else { // This is an unmodeled error. Our frontend only generates // legal `=` expressions, but since GlobalRef used to be legal // here, give a loud error in case any package is modifying // internals. jl_error("Invalid IR: Assignment LHS not a Slot"); } } else if (head == jl_leave_sym) { int hand_n_leave = 0; for (int i = 0; i < jl_expr_nargs(stmt); ++i) { jl_value_t *arg = jl_exprarg(stmt, i); if (arg == jl_nothing) continue; assert(jl_is_ssavalue(arg)); jl_value_t *enter_stmt = jl_array_ptr_ref(stmts, ((jl_ssavalue_t*)arg)->id - 1); if (enter_stmt == jl_nothing) continue; hand_n_leave += 1; } if (hand_n_leave > 0) { assert(hand_n_leave > 0); // equivalent to jl_pop_handler(hand_n_leave), longjmping // to the :enter code above instead, which handles cleanup jl_handler_t *eh = ct->eh; while (--hand_n_leave > 0) { // pop GC frames for any skipped handlers ct->gcstack = eh->gcstack; eh = eh->prev; } // leave happens during normal control flow, but we must // longjmp to pop the eval_body call for each enter. s->continue_at = next_ip; asan_unpoison_task_stack(ct, &eh->eh_ctx); jl_longjmp(eh->eh_ctx, 1); } } else if (head == jl_pop_exception_sym) { size_t prev_state = jl_unbox_ulong(eval_value(jl_exprarg(stmt, 0), s)); jl_restore_excstack(ct, prev_state); } else if (toplevel) { if (head == jl_method_sym && jl_expr_nargs(stmt) > 1) { jl_value_t *res = eval_methoddef((jl_expr_t*)stmt, s); s->locals[jl_source_nslots(s->src) + s->ip] = res; } else if (head == jl_toplevel_sym) { jl_value_t *res = jl_toplevel_eval(s->module, stmt); s->locals[jl_source_nslots(s->src) + s->ip] = res; } else if (head == jl_latestworld_sym) { ct->world_age = jl_atomic_load_acquire(&jl_world_counter); } else if (jl_is_toplevel_only_expr(stmt)) { jl_toplevel_eval(s->module, stmt); } else if (head == jl_meta_sym) { if (jl_expr_nargs(stmt) == 1 && jl_exprarg(stmt, 0) == (jl_value_t*)jl_nospecialize_sym) { jl_set_module_nospecialize(s->module, 1); } if (jl_expr_nargs(stmt) == 1 && jl_exprarg(stmt, 0) == (jl_value_t*)jl_specialize_sym) { jl_set_module_nospecialize(s->module, 0); } if (jl_expr_nargs(stmt) == 2) { if (jl_exprarg(stmt, 0) == (jl_value_t*)jl_optlevel_sym) { if (jl_is_long(jl_exprarg(stmt, 1))) { int n = jl_unbox_long(jl_exprarg(stmt, 1)); jl_set_module_optlevel(s->module, n); } } else if (jl_exprarg(stmt, 0) == (jl_value_t*)jl_compile_sym) { if (jl_is_long(jl_exprarg(stmt, 1))) { jl_set_module_compile(s->module, jl_unbox_long(jl_exprarg(stmt, 1))); } } else if (jl_exprarg(stmt, 0) == (jl_value_t*)jl_infer_sym) { if (jl_is_long(jl_exprarg(stmt, 1))) { jl_set_module_infer(s->module, jl_unbox_long(jl_exprarg(stmt, 1))); } } else if (jl_exprarg(stmt, 0) == (jl_value_t*)jl_max_methods_sym) { if (jl_is_long(jl_exprarg(stmt, 1))) { jl_set_module_max_methods(s->module, jl_unbox_long(jl_exprarg(stmt, 1))); } } } } else { eval_stmt_value(stmt, s); } } else { eval_stmt_value(stmt, s); } } else if (jl_is_newvarnode(stmt)) { jl_value_t *var = jl_fieldref(stmt, 0); assert(jl_is_slotnumber(var)); ssize_t n = jl_slot_number(var); assert(n <= jl_source_nslots(s->src) && n > 0); s->locals[n - 1] = NULL; } else if (toplevel && jl_is_linenode(stmt)) { jl_atomic_store_relaxed(&jl_lineno, jl_linenode_line(stmt)); } else { eval_stmt_value(stmt, s); } ip = eval_phi(stmts, s, ns, next_ip); } abort(); } // preparing method IR for interpreter jl_value_t *jl_code_or_ci_for_interpreter(jl_method_instance_t *mi JL_PROPAGATES_ROOT, size_t world) { jl_value_t *ret = NULL; jl_code_info_t *src = NULL; if (jl_is_method(mi->def.value)) { if (mi->def.method->source) { jl_method_t *m = mi->def.method; src = (jl_code_info_t*)m->source; if (!jl_is_code_info(src)) { // Root the compressed blob across the (allocating) decode: // another thread interpreting the same method concurrently can // win the publish below first, at which point m->source no // longer keeps the blob alive while this thread is still // reading it. jl_value_t *compressed = (jl_value_t*)src; JL_GC_PUSH1(&compressed); src = jl_uncompress_ir(m, NULL, compressed); JL_GC_POP(); // Replace the method source by the uncompressed version, // under the assumption that the interpreter may need to // access it frequently. TODO: Have some sort of usage-based // cache here. (Concurrent publishes store equivalent copies; // last one wins.) jl_gc_write(m, m->source, jl_value_t, (jl_value_t*)src); } ret = (jl_value_t*)src; } else { jl_code_instance_t *cache = jl_atomic_load_relaxed(&mi->cache); jl_code_instance_t *uninferred = jl_cached_uninferred(cache, world); if (!uninferred) { assert(mi->def.method->generator); src = jl_code_for_staged(mi, world, &uninferred); } ret = (jl_value_t*)uninferred; src = (jl_code_info_t*)jl_atomic_load_relaxed(&uninferred->inferred); } } else { jl_code_instance_t *uninferred = jl_cached_uninferred(jl_atomic_load_relaxed(&mi->cache), world); ret = (jl_value_t*)uninferred; if (ret) { src = (jl_code_info_t*)jl_atomic_load_relaxed(&uninferred->inferred); } } if (!src || !jl_is_code_info(src)) { jl_throw(jl_new_struct(jl_missingcodeerror_type, (jl_value_t*)mi)); } return ret; } jl_code_info_t *jl_code_for_interpreter(jl_method_instance_t *mi, size_t world) { jl_value_t *code_or_ci = jl_code_or_ci_for_interpreter(mi, world); if (jl_is_code_instance(code_or_ci)) return (jl_code_info_t*)jl_atomic_load_relaxed(&((jl_code_instance_t*)code_or_ci)->inferred); return (jl_code_info_t*)code_or_ci; } // interpreter entry points jl_value_t *NOINLINE jl_fptr_interpret_call(jl_value_t *f, jl_value_t **args, uint32_t nargs, jl_code_instance_t *codeinst) { interpreter_state *s; jl_method_instance_t *mi = jl_get_ci_mi(codeinst); jl_task_t *ct = jl_current_task; size_t world = ct->world_age; jl_code_info_t *src = NULL; // NOTE: from here until `code`/`src` are stored into the GC frame below, // they may be reachable only through these locals (a concurrent thread // can republish m->source); this stretch must stay free of safepoints. jl_value_t *code = jl_code_or_ci_for_interpreter(mi, world); jl_code_instance_t *ci = NULL; if (jl_is_code_instance(code)) { ci = (jl_code_instance_t*)code; src = (jl_code_info_t*)jl_atomic_load_relaxed(&ci->inferred); } else { src = (jl_code_info_t*)code; } jl_array_t *stmts = src->code; assert(jl_typetagis(stmts, jl_array_any_type)); unsigned nroots = jl_source_nslots(src) + jl_source_nssavalues(src) + 2; jl_value_t **locals = NULL; JL_GC_PUSHFRAME(s, locals, nroots); locals[0] = (jl_value_t*)src; locals[1] = (jl_value_t*)stmts; s->locals = locals + 2; s->src = src; if (jl_is_module(mi->def.value)) { s->module = mi->def.module; } else { s->module = mi->def.method->module; size_t defargs = src->nargs; int isva = src->isva; size_t i; s->locals[0] = f; assert(isva ? nargs + 2 >= defargs : nargs + 1 == defargs); for (i = 1; i < defargs - isva; i++) s->locals[i] = args[i - 1]; if (isva) { assert(defargs >= 2); s->locals[defargs - 1] = jl_f_tuple(NULL, &args[defargs - 2], nargs + 2 - defargs); } } s->sparam_vals = mi->sparam_vals; s->preevaluation = 0; s->continue_at = 0; s->mi = mi; s->ci = ci; JL_GC_ENABLEFRAME(s); jl_value_t *r = eval_body(stmts, s, 0, 0); JL_GC_POP(); return r; } JL_DLLEXPORT const jl_callptr_t jl_fptr_interpret_call_addr = &jl_fptr_interpret_call; jl_value_t *jl_interpret_opaque_closure(jl_opaque_closure_t *oc, jl_value_t **args, size_t nargs) { jl_method_t *source = oc->source; jl_code_info_t *code = NULL; if (source->source) { code = jl_uncompress_ir(source, NULL, (jl_value_t*)source->source); } else { // OC constructed from optimized IR. It'll have a single specialization with optimized code // in it that we'll try to interpret. jl_svec_t *specializations = (jl_svec_t*)jl_atomic_load_relaxed(&source->specializations); assert(jl_is_method_instance(specializations)); jl_method_instance_t *mi = (jl_method_instance_t *)specializations; jl_code_instance_t *ci = jl_atomic_load_relaxed(&mi->cache); jl_value_t *src = jl_atomic_load_relaxed(&ci->inferred); if (!src) { // This can happen if somebody did :new_opaque_closure with broken IR. This is definitely bad // and UB, but let's try to be slightly nicer than segfaulting here for people debugging. jl_error("Internal Error: Opaque closure with no source at all"); } code = jl_uncompress_ir(source, ci, src); } interpreter_state *s; unsigned nroots = jl_source_nslots(code) + jl_source_nssavalues(code) + 2; jl_task_t *ct = jl_current_task; size_t last_age = ct->world_age; ct->world_age = oc->world; jl_value_t **locals = NULL; JL_GC_PUSHFRAME(s, locals, nroots); locals[0] = (jl_value_t*)oc; // The analyzer has some trouble with this locals[1] = (jl_value_t*)code; JL_GC_PROMISE_ROOTED(code); locals[2] = (jl_value_t*)oc->captures; s->locals = locals + 2; s->src = code; s->module = source->module; s->sparam_vals = NULL; s->preevaluation = 0; s->continue_at = 0; s->mi = NULL; s->ci = NULL; size_t defargs = source->nargs; int isva = source->isva; assert(isva ? nargs + 2 >= defargs : nargs + 1 == defargs); for (size_t i = 1; i < defargs - isva; i++) s->locals[i] = args[i - 1]; if (isva) { assert(defargs >= 2); s->locals[defargs - 1] = jl_f_tuple(NULL, &args[defargs - 2], nargs + 2 - defargs); } JL_GC_ENABLEFRAME(s); jl_value_t *r = eval_body(code->code, s, 0, 0); locals[0] = r; // GC root JL_GC_PROMISE_ROOTED(r); ct->world_age = last_age; jl_typeassert(r, jl_tparam1(jl_typeof(oc))); JL_GC_POP(); return r; } jl_value_t *NOINLINE jl_interpret_toplevel_thunk(jl_module_t *m, jl_code_info_t *src) { interpreter_state *s; unsigned nroots = jl_source_nslots(src) + jl_source_nssavalues(src); JL_GC_PUSHFRAME(s, s->locals, nroots); jl_array_t *stmts = src->code; assert(jl_typetagis(stmts, jl_array_any_type)); s->src = src; s->module = m; s->sparam_vals = jl_emptysvec; s->continue_at = 0; s->mi = NULL; s->ci = NULL; JL_GC_ENABLEFRAME(s); jl_value_t *r = eval_body(stmts, s, 0, 1); JL_GC_POP(); return r; } // deprecated: do not use this method in new code // it uses special scoping / evaluation / error rules // which should instead be handled in lowering jl_value_t *NOINLINE jl_interpret_toplevel_expr_in(jl_module_t *m, jl_value_t *e, jl_code_info_t *src, jl_svec_t *sparam_vals) { interpreter_state *s; jl_value_t **locals; JL_GC_PUSHFRAME(s, locals, 0); (void)locals; s->src = src; s->module = m; s->sparam_vals = sparam_vals; s->preevaluation = (sparam_vals != NULL); s->continue_at = 0; s->mi = NULL; s->ci = NULL; JL_GC_ENABLEFRAME(s); jl_value_t *v = eval_value(e, s); assert(v); JL_GC_POP(); return v; } JL_DLLEXPORT size_t jl_capture_interp_frame(jl_bt_element_t *bt_entry, void *stateend, size_t space_remaining) { interpreter_state *s = &((interpreter_state*)stateend)[-1]; int need_module = !s->mi; int required_space = need_module ? 4 : 3; if (space_remaining < required_space) return 0; // Should not happen size_t njlvalues = need_module ? 2 : 1; uintptr_t entry_tags = jl_bt_entry_descriptor(njlvalues, 0, JL_BT_INTERP_FRAME_TAG, s->ip); bt_entry[0].uintptr = JL_BT_NON_PTR_ENTRY; bt_entry[1].uintptr = entry_tags; bt_entry[2].jlvalue = s->ci ? (jl_value_t*)s->ci : s->mi ? (jl_value_t*)s->mi : s->src ? (jl_value_t*)s->src : (jl_value_t*)jl_nothing; if (need_module) { // If we only have a CodeInfo (s->src), we are in a top level thunk and // need to record the module separately. bt_entry[3].jlvalue = (jl_value_t*)s->module; } return required_space; } #ifdef __cplusplus } #endif