diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 675a5262..7b1f83a6 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -2766,6 +2766,22 @@ (put nextenv :fiber fiber) (put nextenv :debug-level level) (put nextenv :signal (fiber/last-value fiber)) + + # define variables available at breakpoint + (def frame ((debug/stack fiber) 0)) + (def slotsyms ((disasm (frame :function)) :slotsyms)) + (def pc (frame :pc)) + + (loop [[start stop syms] :in slotsyms] + (when (and (or (= start :top) + (<= start pc)) + (or (= stop :top) + (< pc stop))) + (loop [[sym instances] :pairs syms + [def-index slot] :in instances + :when (<= def-index pc)] + (put nextenv (symbol sym) @{:value (get-in frame [:slots slot])})))) + (merge-into nextenv debugger-env) (defn debugger-chunks [buf p] (def status (:state p :delimiters)) diff --git a/src/core/asm.c b/src/core/asm.c index 1cf2c57a..a17c45f0 100644 --- a/src/core/asm.c +++ b/src/core/asm.c @@ -882,6 +882,37 @@ static Janet janet_disasm_slotcount(JanetFuncDef *def) { return janet_wrap_integer(def->slotcount); } +/* +example structure: +:slotsyms @[(:top :top @{"o1" @[(-1 0)] "outer" @[(0 1)])]) + (2 8 +# ^ beginning of scope +# ^ end of scope + @{"inner" @[(6 6)] "odo4" @[(2 4) (10 7)]})] +# ^ ^ bytecode index of def +# ^ ^ slot index + ^ redefinition + +beginning of scope is the bytecode index of when a scope begins. +if the program counter is >= beginning of scope and < end of scope, it is +currently in that scope. + +bytecode index of def is the bytecode index from where the slot / sym is actually defined. before this point the slot might have a value, but it's just residual data from earlier slot usage. + +the redefinition is an example of what happens if the same symbol is used twice, e.g. due to calling `def` twice with the same symbol in the same scope. in that case the first slot (4) is valid from the first bytecode index until the bytecode index in the redefinition, after which the second slot (4) is valid. + +:top means the function's scope, e.g. parameters and `def` calls outside of e.g. a `do`-block +*/ +static Janet janet_disasm_slotsyms(JanetFuncDef *def) { + JanetArray *slotsyms = janet_array(def->slotsyms_length); + for (int32_t i = 0; i < def->slotsyms_length; i++) { + slotsyms->data[i] = def->slotsyms[i]; + } + slotsyms->count = def->slotsyms_length; + return janet_wrap_array(slotsyms); +} + + static Janet janet_disasm_bytecode(JanetFuncDef *def) { JanetArray *bcode = janet_array(def->bytecode_length); for (int32_t i = 0; i < def->bytecode_length; i++) { @@ -961,6 +992,7 @@ Janet janet_disasm(JanetFuncDef *def) { janet_table_put(ret, janet_ckeywordv("structarg"), janet_disasm_structarg(def)); janet_table_put(ret, janet_ckeywordv("name"), janet_disasm_name(def)); janet_table_put(ret, janet_ckeywordv("slotcount"), janet_disasm_slotcount(def)); + janet_table_put(ret, janet_ckeywordv("slotsyms"), janet_disasm_slotsyms(def)); janet_table_put(ret, janet_ckeywordv("constants"), janet_disasm_constants(def)); janet_table_put(ret, janet_ckeywordv("sourcemap"), janet_disasm_sourcemap(def)); janet_table_put(ret, janet_ckeywordv("environments"), janet_disasm_environments(def)); @@ -997,6 +1029,7 @@ JANET_CORE_FN(cfun_disasm, "* :source - name of source file that this function was compiled from.\n" "* :name - name of function.\n" "* :slotcount - how many virtual registers, or slots, this function uses. Corresponds to stack space used by function.\n" + "* :slotsyms - all slots and their symbols.\n" "* :constants - an array of constants referenced by this function.\n" "* :sourcemap - a mapping of each bytecode instruction to a line and column in the source file.\n" "* :environments - an internal mapping of which enclosing functions are referenced for bindings.\n" diff --git a/src/core/bytecode.c b/src/core/bytecode.c index 2da4418b..5208c47a 100644 --- a/src/core/bytecode.c +++ b/src/core/bytecode.c @@ -218,6 +218,7 @@ JanetFuncDef *janet_funcdef_alloc(void) { def->closure_bitset = NULL; def->flags = 0; def->slotcount = 0; + def->slotsyms = 0; def->arity = 0; def->min_arity = 0; def->max_arity = INT32_MAX; diff --git a/src/core/compile.c b/src/core/compile.c index 2ad46313..d8feab59 100644 --- a/src/core/compile.c +++ b/src/core/compile.c @@ -97,6 +97,8 @@ void janetc_nameslot(JanetCompiler *c, const uint8_t *sym, JanetSlot s) { sp.slot = s; sp.keep = 0; sp.slot.flags |= JANET_SLOT_NAMED; + // -1 because c->buffer has already passed the `def`/`var` + sp.bytecode_pos = janet_v_count(c->buffer) - 1; janet_v_push(c->scope->syms, sp); } @@ -172,8 +174,52 @@ void janetc_popscope(JanetCompiler *c) { janetc_regalloc_touch(&newscope->ra, pair.slot.index); } } - } + + if (janet_truthy(janet_dyn("debug"))) { + /* push symbols */ + if (true || (!(oldscope->flags & (JANET_SCOPE_FUNCTION | JANET_SCOPE_UNUSED)) && newscope)) { + Janet *t = janet_tuple_begin(3); + t[0] = janet_wrap_integer(oldscope->bytecode_start); + t[1] = janet_wrap_integer(janet_v_count(c->buffer)); + + JanetTable *sym_table = janet_table(janet_v_count(oldscope->syms)); + + for (int32_t i = 0; i < janet_v_count(oldscope->syms); i++) { + SymPair pair = oldscope->syms[i]; + + if (pair.sym != NULL) { + Janet *symbol_tuple = janet_tuple_begin(2); + symbol_tuple[0] = janet_wrap_integer(pair.bytecode_pos); + symbol_tuple[1] = janet_wrap_integer(pair.slot.index); + + Janet k = janet_cstringv((const char *) pair.sym); + + Janet arr = janet_table_get(sym_table, k); + + if (janet_checktype(arr, JANET_NIL)) { + JanetArray *a = janet_array(1); + janet_array_push(a, janet_wrap_tuple(janet_tuple_end(symbol_tuple))); + janet_table_put(sym_table, k, janet_wrap_array(a)); + } else { + JanetArray *a = janet_unwrap_array(arr); + janet_array_push(a, janet_wrap_tuple(janet_tuple_end(symbol_tuple))); + } + } + } + + t[2] = janet_wrap_table(sym_table); + if (c->local_binds->count > 0) { + janet_array_push(janet_unwrap_array(c->local_binds->data[c->local_binds->count - 1]), janet_wrap_tuple(janet_tuple_end(t))); + } else { + // this shouldn't occur -- local_binds should always have at least + // 1 element when a scope is popped + fprintf(stderr, "no local_binds pushed\n"); + } + } + /* end push symbols */ + } + /* Free the old scope */ janet_v_free(oldscope->consts); janet_v_free(oldscope->syms); @@ -926,6 +972,24 @@ JanetFuncDef *janetc_pop_funcdef(JanetCompiler *c) { /* Pop the scope */ janetc_popscope(c); + + if (janet_truthy(janet_dyn("debug"))) { + JanetArray *last_binds = janet_unwrap_array(c->local_binds->data[c->local_binds->count - 1]); + + def->slotsyms_length = last_binds->count; + def->slotsyms = janet_malloc(sizeof(Janet) * (size_t)def->slotsyms_length); + + // just gotta modify some tuples + Janet *top_level_tuple = (Janet *)janet_unwrap_tuple(last_binds->data[last_binds->count - 1]); + top_level_tuple[0] = janet_ckeywordv("top"); + top_level_tuple[1] = janet_ckeywordv("top"); + for (int i = 0; i < last_binds->count; i++) { + def->slotsyms[def->slotsyms_length - i - 1] = last_binds->data[i]; + } + } + + janet_array_pop(c->local_binds); + return def; } @@ -936,6 +1000,7 @@ static void janetc_init(JanetCompiler *c, JanetTable *env, const uint8_t *where, c->mapbuffer = NULL; c->recursion_guard = JANET_RECURSION_GUARD; c->env = env; + c->local_binds = janet_array(0); c->source = where; c->current_mapping.line = -1; c->current_mapping.column = -1; @@ -954,6 +1019,7 @@ static void janetc_deinit(JanetCompiler *c) { janet_v_free(c->buffer); janet_v_free(c->mapbuffer); c->env = NULL; + c->local_binds = NULL; } /* Compile a form. */ @@ -966,6 +1032,9 @@ JanetCompileResult janet_compile_lint(Janet source, janetc_init(&c, env, where, lints); /* Push a function scope */ + if (janet_truthy(janet_dyn("debug"))) { + janet_array_push(c.local_binds, janet_wrap_array(janet_array(0))); + } janetc_scope(&rootscope, &c, JANET_SCOPE_FUNCTION | JANET_SCOPE_TOP, "root"); /* Set initial form options */ @@ -984,6 +1053,9 @@ JanetCompileResult janet_compile_lint(Janet source, } else { c.result.error_mapping = c.current_mapping; janetc_popscope(&c); + if (janet_truthy(janet_dyn("debug"))) { + janet_array_pop(c.local_binds); + } } janetc_deinit(&c); diff --git a/src/core/compile.h b/src/core/compile.h index 90f81732..30b58064 100644 --- a/src/core/compile.h +++ b/src/core/compile.h @@ -112,6 +112,7 @@ typedef struct SymPair { JanetSlot slot; const uint8_t *sym; int keep; + int32_t bytecode_pos; } SymPair; /* A lexical scope during compilation */ @@ -160,6 +161,8 @@ struct JanetCompiler { /* Hold the environment */ JanetTable *env; + JanetArray *local_binds; + /* Name of source to attach to generated functions */ const uint8_t *source; diff --git a/src/core/specials.c b/src/core/specials.c index 4b81ea37..cb077894 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -753,6 +753,10 @@ static JanetSlot janetc_while(JanetFopts opts, int32_t argn, const Janet *argv) if (c->buffer) janet_v__cnt(c->buffer) = labelwt; if (c->mapbuffer) janet_v__cnt(c->mapbuffer) = labelwt; + + if (janet_truthy(janet_dyn("debug"))) { + janet_array_push(c->local_binds, janet_wrap_array(janet_array(0))); + } janetc_scope(&tempscope, c, JANET_SCOPE_FUNCTION, "while-iife"); /* Recompile in the function scope */ @@ -829,6 +833,9 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) { /* Begin function */ c->scope->flags |= JANET_SCOPE_CLOSURE; + if (janet_truthy(janet_dyn("debug"))) { + janet_array_push(c->local_binds, janet_wrap_array(janet_array(0))); + } janetc_scope(&fnscope, c, JANET_SCOPE_FUNCTION, "function"); if (argn == 0) { diff --git a/src/include/janet.h b/src/include/janet.h index b24d52f2..1168c14f 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -1031,6 +1031,7 @@ struct JanetFuncDef { JanetSourceMapping *sourcemap; JanetString source; JanetString name; + Janet *slotsyms; int32_t flags; int32_t slotcount; /* The amount of stack space required for the function */ @@ -1041,6 +1042,7 @@ struct JanetFuncDef { int32_t bytecode_length; int32_t environments_length; int32_t defs_length; + int32_t slotsyms_length; }; /* A function environment */ diff --git a/test/suite0015.janet b/test/suite0015.janet new file mode 100644 index 00000000..11883e26 --- /dev/null +++ b/test/suite0015.janet @@ -0,0 +1,27 @@ +# test *debug* flags + +(import ./helper :prefix "" :exit true) +(start-suite 15) + +(assert (deep= (in (disasm (defn a [] (def x 10) x)) :slotsyms) + @[]) + "no slotsyms when *debug* is false") + +(setdyn *debug* true) +(assert (deep= (in (disasm (defn a [] (def x 10) x)) :slotsyms) + @[[:top :top @{"a" @[[0 0]] "x" @[[1 1]]}]]) + "slotsyms when *debug* is true") +(setdyn *debug* false) + +(setdyn *debug* true) +(assert (deep= (in (disasm (defn a [] + (def x 10) + (do + (def y 20) + (+ x y)))) :slotsyms) + @[[:top :top @{"a" @[[0 0]] "x" @[[1 1]]}] + [2 5 @{"y" @[[2 2]]}]]) + "inner slotsyms") +(setdyn *debug* false) + +(end-suite)