kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit a4a1280359aa38802daecbd1dba497e191655118
parent 4475aed2d61dbdd10608859a9e78146c356136c6
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 21 May 2026 11:43:15 -0700

Make dbg REPL frontend-driven

Diffstat:
Mdoc/FRONTEND.md | 73+++++++++++++++++++++++++++++++++++++++++--------------------------------
Mdriver/dbg.c | 577+++++++++++++++++++++++++++----------------------------------------------------
Minclude/cfree/compile.h | 9+++++++++
Mlang/toy/compile.c | 170++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mlang/toy/data.c | 12+++++++++++-
Mlang/toy/decls.c | 7++++++-
Mlang/toy/internal.h | 9+++++++++
Mlang/toy/parser_core.c | 26++++++++++++++++++++++++++
Msrc/api/link.c | 2++
Msrc/link/link.c | 5+++++
Msrc/link/link.h | 1+
Msrc/link/link_jit.c | 62++++++++++++++++++++++++++++++++++++++++----------------------
Mtest/driver/run.sh | 40+++++++++++++++++++++++++++++++++-------
13 files changed, 542 insertions(+), 451 deletions(-)

diff --git a/doc/FRONTEND.md b/doc/FRONTEND.md @@ -16,13 +16,15 @@ bare expression input feel native for Toy, C, and Wasm. resulting object explicitly. - [x] `driver/cc.c`, `driver/inputs.c`, and `driver/dbg.c` have been migrated to the new frontend instance API. -- [ ] `driver/dbg.c` still creates a fresh frontend per `jit` snippet. -- [ ] C, Toy, and Wasm frontend implementations still allocate their parser/CG - state per compile. -- [ ] Expression input still fabricates C source in `driver/dbg.c`; it is not - frontend native yet. -- [ ] Bare REPL input is not yet the default frontend-specific expression/thunk - fallback. +- [x] `driver/dbg.c` keeps frontend instances alive across `jit` snippets. +- [x] `cfree dbg` can start from an empty JIT image, with the REPL language set + by `-x LANG` / `--language LANG` or by `:language` after startup. +- [ ] C and Wasm frontend implementations still allocate parser/CG state per + compile; Toy keeps parser state but still creates CG state per compile. +- [x] `driver/dbg.c` no longer fabricates language-specific expression source; + selected frontends own REPL expression/block wrapping. +- [x] Bare REPL input uses the selected frontend-specific expression/thunk + fallback for Toy. ## Target UX @@ -36,8 +38,8 @@ Interactive sessions should support these workflows: $1 = 27 (0x1b) ``` -```toy -(cfree) :language toy +```text +cfree dbg -x toy (cfree) jit { type Point = record { x: i64, y: i64 }; let p: Point = .{ .x = 4, .y = 5 }; } (cfree) p.x + p.y $1 = 9 (0x9) @@ -83,7 +85,7 @@ CfreeStatus cfree_frontend_compile(CfreeFrontend*, void cfree_frontend_free(CfreeFrontend*); ``` -Next required compile options: +Landed REPL compile options: ```c typedef enum CfreeFrontendInputKind { @@ -123,19 +125,27 @@ prior snippets should link naturally. ### Debugger Driver -- [ ] Add a small per-language frontend cache to `DbgState`. -- [ ] Keep the selected language frontend alive for the whole REPL session. -- [ ] Compile `jit` snippets by creating a fresh object builder and calling +- [x] Add a small per-language frontend cache to `DbgState`. +- [x] Keep the selected language frontend alive for the whole REPL session. +- [x] Compile `jit` snippets by creating a fresh object builder and calling `cfree_frontend_compile` on the cached frontend. -- [ ] Add `:language c|toy|wat|wasm|asm` and make `jit`, explicit `expr`, and - bare fallback input honor the selected language. -- [ ] Add `input_kind` and `repl_entry_name` wiring for bare expression fallback, +- [x] Treat a REPL line beginning with `{` as shorthand for `jit { ... }`. +- [x] Add `:language c|toy|wat|wasm|asm` and make `jit`, explicit `expr`, and + bare fallback input honor the selected language through the frontend vtable. +- [x] Add `-x LANG` / `--language LANG` to choose the default REPL language + before any source file exists. +- [x] Make `:language` with no argument report the current language and + language options. +- [x] Allow an empty initial JIT image; `run` resolves the entry lazily so a + later snippet can define `main`. +- [x] Add `input_kind` and `repl_entry_name` wiring for bare expression fallback, explicit `expr`, and block modes. -- [ ] Remove `dbg_append_expr_prelude` and driver-side C thunk fabrication once - C expression mode is implemented. +- [ ] Seed cached frontend state from initial source-file inputs so REPL + expressions can see declarations compiled before the prompt. +- [x] Remove `dbg_append_expr_prelude` and driver-side C thunk fabrication. - [ ] Keep DWARF/JIT symbol recovery for external/preexisting code inspection, not as the normal path for declarations typed during the session. -- [ ] Add scripted `dbg` smoke tests that drive stdin and assert output. +- [x] Add scripted `dbg` smoke tests that drive stdin and assert output. ## C Checklist @@ -189,14 +199,14 @@ Toy is the best first full REPL target because it already sits cleanly on the public CG API and has explicit source syntax for types, globals, functions, and expressions. -- [ ] Change `ToyFrontend` to own persistent symbol/type storage instead of +- [x] Change `ToyFrontend` to own persistent symbol/type storage instead of rebuilding all parser state per compile. - [ ] Keep one persistent `CfreeCg` and use `cfree_cg_swap_obj` per snippet. -- [ ] Refactor `ToyParser` so lexical state is per snippet but declarations, +- [x] Refactor `ToyParser` so lexical state is per snippet but declarations, record/enum/type tables, globals, and function symbols live on `ToyFrontend`. -- [ ] Implement `CFREE_FRONTEND_INPUT_REPL_TOPLEVEL` for declarations and +- [x] Implement `CFREE_FRONTEND_INPUT_REPL_TOPLEVEL` for declarations and definitions. -- [ ] Implement `CFREE_FRONTEND_INPUT_REPL_EXPR` by generating: +- [x] Implement `CFREE_FRONTEND_INPUT_REPL_EXPR` by generating: ```toy fn __cfree_dbg_expr_N(): i64 { @@ -204,17 +214,17 @@ fn __cfree_dbg_expr_N(): i64 { } ``` -- [ ] Implement `CFREE_FRONTEND_INPUT_REPL_BLOCK` using Toy block/function +- [x] Implement `CFREE_FRONTEND_INPUT_REPL_BLOCK` using Toy block/function syntax and require an explicit return in v1. -- [ ] Preserve global variables across snippets: +- [x] Preserve global variables across snippets: `jit { let x: i64 = 41; }`, then bare `x + 1`. -- [ ] Preserve nominal records/enums/type aliases across snippets. -- [ ] Preserve functions across snippets, including calls from later snippets. +- [x] Preserve nominal records/enums/type aliases across snippets. +- [x] Preserve functions across snippets, including calls from later snippets. - [ ] Add diagnostics for duplicate definitions and type mismatches that include the snippet input name. -- [ ] Add Toy REPL smoke tests: - globals, functions, record field access, enum constants, expression wrapper, - and block wrapper. +- [x] Add Toy REPL smoke tests for globals, functions, record field access, and + expression wrapper. +- [ ] Add Toy REPL smoke tests for enum constants and block wrapper. ## Wasm Checklist @@ -274,8 +284,7 @@ $1 = 42 (0x2a) ``` ```text -cfree dbg empty.c -(cfree) :language wat +cfree dbg --language wat (cfree) jit { (module (func (export "answer") (result i64) i64.const 42)) } (cfree) invoke answer $1 = 42 (0x2a) diff --git a/driver/dbg.c b/driver/dbg.c @@ -53,6 +53,8 @@ typedef struct DbgOpts { int opt_level; int debug_info; const char *entry; + CfreeLanguage default_lang; + int has_default_lang; DriverCflags cf; DriverInputs inputs; @@ -61,19 +63,13 @@ typedef struct DbgOpts { uint32_t prog_argc; } DbgOpts; -static void dbg_usage(void) { - driver_errf(DBG_TOOL, "%s", - "usage: cfree dbg [options] input.{c,toy,s}... [-- arg...]\n" - " cfree dbg --help for full option reference"); -} - void driver_help_dbg(void) { driver_printf( "%s", "cfree dbg — interactive JIT debugger\n" "\n" "USAGE\n" - " cfree dbg [options] input.{c,toy,s} ... [-- prog-arg ...]\n" + " cfree dbg [options] [input.{c,toy,s} ...] [-- prog-arg ...]\n" "\n" "DESCRIPTION\n" " Mirrors `cfree run` for compile flags and argv shape, but instead\n" @@ -84,11 +80,15 @@ void driver_help_dbg(void) { " variable locations are available at runtime.\n" "\n" " Anything after `--` is passed to the JITed program as argv.\n" + " With no input files, dbg starts an empty JIT session; append code\n" + " with `jit` or evaluate expressions directly from the REPL.\n" "\n" "COMPILE OPTIONS\n" " -O0 -O1 -O2 Optimization level (default -O0)\n" " -g Emit DWARF (forced on)\n" " -e SYMBOL Entry symbol (default `main`)\n" + " -x LANG Default REPL language: c, toy, asm, wasm/wat\n" + " --language LANG Same as -x\n" " -I DIR Add quoted-include search path\n" " -isystem DIR Add system-include search path\n" " -D NAME[=BODY] Define a macro\n" @@ -104,11 +104,11 @@ void driver_help_dbg(void) { " n, next step to next source line (over calls)\n" " finish run until current frame returns\n" " jit [LANG|NAME] { ... } compile and append a language snippet\n" + " { ... } same as jit { ... }\n" " edit [LANG|NAME] edit and append a language snippet\n" " expr EXPR | expr { ... } compile and call an expression thunk\n" " EXPR same as expr EXPR\n" - " LANG defaults to the input language;\n" - " expression thunks use C syntax today\n" + " LANG defaults to the selected language\n" " call SYMBOL [INT_OR_ADDR...] call function, returns uint64_t\n" " jump ADDR set PC to ADDR (no resume)\n" " bt, backtrace print stack trace with arguments\n" @@ -158,6 +158,39 @@ static int dbg_alloc_arrays(DbgOpts *o, int argc) { return 0; } +static int dbg_parse_language_name(const char *name, CfreeLanguage *out) { + if (!name || !*name || !out) + return 0; + if (driver_streq(name, "c")) { + *out = CFREE_LANG_C; + return 1; + } + if (driver_streq(name, "toy")) { + *out = CFREE_LANG_TOY; + return 1; + } + if (driver_streq(name, "asm") || driver_streq(name, "s")) { + *out = CFREE_LANG_ASM; + return 1; + } + if (driver_streq(name, "wasm") || driver_streq(name, "wat")) { + *out = CFREE_LANG_WASM; + return 1; + } + return 0; +} + +static int dbg_set_default_language(DbgOpts *o, const char *name) { + CfreeLanguage lang = CFREE_LANG_COUNT; + if (!dbg_parse_language_name(name, &lang)) { + driver_errf(DBG_TOOL, "unsupported language: %s", name ? name : ""); + return 1; + } + o->default_lang = lang; + o->has_default_lang = 1; + return 0; +} + static int dbg_parse(int argc, char **argv, DbgOpts *o) { int i; int after_dash_dash = 0; @@ -211,6 +244,27 @@ static int dbg_parse(int argc, char **argv, DbgOpts *o) { continue; } + if (driver_streq(a, "-x") || driver_streq(a, "--language") || + driver_streq(a, "--lang")) { + if (++i >= argc) { + driver_errf(DBG_TOOL, "%s requires an argument", a); + return 1; + } + if (dbg_set_default_language(o, argv[i]) != 0) + return 1; + continue; + } + if (driver_strneq(a, "--language=", 11)) { + if (dbg_set_default_language(o, a + 11) != 0) + return 1; + continue; + } + if (driver_strneq(a, "--lang=", 7)) { + if (dbg_set_default_language(o, a + 7) != 0) + return 1; + continue; + } + if (a[0] == '-' && a[1] != '\0') { driver_errf(DBG_TOOL, "unknown flag: %s", a); return 1; @@ -227,11 +281,6 @@ static int dbg_parse(int argc, char **argv, DbgOpts *o) { } } - if (driver_inputs_count(&o->inputs) == 0) { - driver_errf(DBG_TOOL, "no input files"); - dbg_usage(); - return 1; - } if (!o->entry) o->entry = "main"; if (!o->debug_info) { @@ -257,6 +306,8 @@ static void dbg_options_release(DbgOpts *o) { static int dbg_compile_and_jit(DbgOpts *o, CfreeCompiler *compiler, const CfreeJitHost *host, CfreeJit **out_jit) { CfreeCCompileOptions copts; + const char *link_entry = + driver_inputs_count(&o->inputs) ? o->entry : NULL; { CfreeCCompileOptions z = {0}; copts = z; @@ -265,8 +316,8 @@ static int dbg_compile_and_jit(DbgOpts *o, CfreeCompiler *compiler, copts.code.debug_info = o->debug_info; driver_cflags_fill_pp(&o->cf, &copts.preprocess); return driver_inputs_compile_and_jit(&o->inputs, compiler, host, &copts, - o->entry, driver_dlsym_resolver, NULL, - out_jit); + link_entry, driver_dlsym_resolver, + NULL, out_jit); } static void dbg_fill_compile_options(DbgOpts *o, CfreeCCompileOptions *copts) { @@ -319,8 +370,10 @@ typedef struct DbgState { const CfreeObjFile *view; CfreeDebugInfo *dwarf; void *entry_addr; + const char *entry_name; CfreeLanguage default_jit_lang; const char *default_jit_name; + CfreeFrontend *frontends[CFREE_LANG_COUNT]; int prog_argc; char **prog_argv; @@ -397,10 +450,6 @@ static int dbg_isspace(int c) { return c == ' ' || c == '\t' || c == '\r' || c == '\n'; } static int dbg_isdigit(int c) { return c >= '0' && c <= '9'; } -static int dbg_isalpha_(int c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; -} -static int dbg_isalnum_(int c) { return dbg_isalpha_(c) || dbg_isdigit(c); } static int dbg_isxdigit(int c) { return dbg_isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } @@ -536,6 +585,14 @@ static void dbg_bps_release_all(DbgState *s) { } } +static void dbg_frontends_release(DbgState *s) { + uint32_t i; + for (i = 0; i < (uint32_t)CFREE_LANG_COUNT; ++i) { + cfree_frontend_free(s->frontends[i]); + s->frontends[i] = NULL; + } +} + /* ============================================================ * LOC parser * ============================================================ @@ -782,6 +839,18 @@ static int dbg_drive(DbgState *s, DbgRunMode mode) { return 1; } + if (mode == RUN_FRESH && !s->entry_addr) { + if (!s->entry_name || !*s->entry_name) { + driver_errf(DBG_TOOL, "no entry symbol configured"); + return 1; + } + s->entry_addr = cfree_jit_lookup(s->jit, s->entry_name); + if (!s->entry_addr) { + driver_errf(DBG_TOOL, "entry symbol not found: %s", s->entry_name); + return 1; + } + } + if (driver_install_sigint(dbg_on_sigint, s) != 0) { driver_errf(DBG_TOOL, "failed to install SIGINT handler"); return 1; @@ -1612,9 +1681,24 @@ static CfreeLanguage dbg_jit_language_for_tag(DbgState *s, const char *tag, return cfree_language_for_path(tag); } -static int dbg_jit_compile_append(DbgState *s, CfreeLanguage lang, - const char *input_name, const char *src, - size_t len) { +static CfreeStatus dbg_frontend_for(DbgState *s, CfreeLanguage lang, + CfreeFrontend **out) { + CfreeStatus st; + if (!out || (unsigned)lang >= CFREE_LANG_COUNT) return CFREE_INVALID; + *out = NULL; + if (!s->frontends[lang]) { + st = cfree_frontend_new(s->compiler, lang, &s->frontends[lang]); + if (st != CFREE_OK) return st; + } + *out = s->frontends[lang]; + return CFREE_OK; +} + +static int dbg_jit_compile_append_ex(DbgState *s, CfreeLanguage lang, + const char *input_name, const char *src, + size_t len, + CfreeFrontendInputKind input_kind, + const char *repl_entry_name) { CfreeSourceInput sin; CfreeFrontendCompileOptions fopts; CfreeFrontend *frontend = NULL; @@ -1633,10 +1717,11 @@ static int dbg_jit_compile_append(DbgState *s, CfreeLanguage lang, fopts.code = s->copts.code; fopts.diagnostics = s->copts.diagnostics; fopts.language_options = &s->copts; - st = cfree_frontend_new(s->compiler, lang, &frontend); + fopts.input_kind = input_kind; + fopts.repl_entry_name = repl_entry_name; + st = dbg_frontend_for(s, lang, &frontend); if (st == CFREE_OK) st = cfree_obj_builder_new(s->compiler, &ob); if (st == CFREE_OK) st = cfree_frontend_compile(frontend, &fopts, &sin, ob); - cfree_frontend_free(frontend); if (st != CFREE_OK || !ob) { if (ob) cfree_obj_builder_free(ob); driver_errf(DBG_TOOL, "jit compile failed"); @@ -1653,6 +1738,13 @@ static int dbg_jit_compile_append(DbgState *s, CfreeLanguage lang, return 0; } +static int dbg_jit_compile_append(DbgState *s, CfreeLanguage lang, + const char *input_name, const char *src, + size_t len) { + return dbg_jit_compile_append_ex(s, lang, input_name, src, len, + CFREE_FRONTEND_INPUT_REPL_TOPLEVEL, NULL); +} + static int dbg_parse_jit_lang_arg(DbgState *s, const char *rest, CfreeLanguage *lang_out, const char **input_name_out, @@ -1784,6 +1876,47 @@ out: driver_free(s->env, src, len); } +static void dbg_cmd_language(DbgState *s, const char *rest) { + char tmp[WORD_CAP]; + const char *p = rest; + size_t n = 0; + CfreeLanguage lang; + const char *name; + + while (*p && dbg_isspace((unsigned char)*p)) + ++p; + while (p[n] && !dbg_isspace((unsigned char)p[n])) + ++n; + if (n == 0) { + const CfreePreprocessOptions *pp = &s->copts.preprocess; + driver_printf("Language: %s\n", dbg_jit_language_name(s->default_jit_lang)); + driver_printf( + "Language options: input=%s opt=-O%d debug=%s includes=%u " + "system-includes=%u defines=%u undefines=%u frontend=%s\n", + s->default_jit_name ? s->default_jit_name : "", + s->copts.code.opt_level, s->copts.code.debug_info ? "on" : "off", + (unsigned)pp->ninclude_dirs, (unsigned)pp->nsystem_include_dirs, + (unsigned)pp->ndefines, (unsigned)pp->nundefines, + s->frontends[s->default_jit_lang] ? "cached" : "not-created"); + return; + } + if (n >= sizeof(tmp)) { + driver_errf(DBG_TOOL, "usage: :language c|toy|asm|wat|wasm"); + return; + } + driver_memcpy(tmp, p, n); + tmp[n] = '\0'; + lang = dbg_jit_language_for_tag(s, tmp, &name); + (void)name; + if (lang == CFREE_LANG_COUNT) { + driver_errf(DBG_TOOL, "unsupported language: %s", tmp); + return; + } + s->default_jit_lang = lang; + s->default_jit_name = dbg_jit_default_name(lang); + driver_printf("Language: %s\n", dbg_jit_language_name(lang)); +} + static size_t dbg_u64_dec(char *dst, size_t cap, uint64_t v) { char tmp[32]; size_t n = 0; @@ -1837,317 +1970,8 @@ static int dbg_call_u64_entry(DbgState *s, void *entry, const uint64_t *args, return 0; } -static int dbg_c_ident_ok(const char *name) { - if (!name || !dbg_isalpha_((unsigned char)name[0])) - return 0; - while (*++name) { - if (!dbg_isalnum_((unsigned char)*name)) - return 0; - } - return 1; -} - -static int dbg_type_base_to_c(DbgState *s, char **buf, size_t *len, size_t *cap, - const CfreeDwarfTypeInfo *ti) { - const char *spelling = NULL; - switch (ti->kind) { - case CFREE_DT_VOID: - spelling = "void"; - break; - case CFREE_DT_BOOL: - spelling = "_Bool"; - break; - case CFREE_DT_CHAR: - spelling = "char"; - break; - case CFREE_DT_SINT: - switch (ti->byte_size) { - case 1: - spelling = "signed char"; - break; - case 2: - spelling = "short"; - break; - case 4: - spelling = "int"; - break; - case 8: - spelling = "long long"; - break; - } - break; - case CFREE_DT_UINT: - switch (ti->byte_size) { - case 1: - spelling = "unsigned char"; - break; - case 2: - spelling = "unsigned short"; - break; - case 4: - spelling = "unsigned int"; - break; - case 8: - spelling = "unsigned long long"; - break; - } - break; - case CFREE_DT_FLOAT: - if (ti->byte_size == 4) - spelling = "float"; - else if (ti->byte_size == 8) - spelling = "double"; - break; - case CFREE_DT_ENUM: - spelling = "int"; - break; - default: - break; - } - if (!spelling) - return 1; - return dbg_buf_append(s, buf, len, cap, spelling, driver_strlen(spelling)); -} - -static int dbg_type_render_c(DbgState *s, const CfreeDwarfType *type, - char **buf, size_t *len, size_t *cap, - uint32_t ptr_depth) { - CfreeDwarfTypeInfo ti; - if (!type) - return 1; - ti = cfree_dwarf_type_info(type); - while (ti.kind == CFREE_DT_TYPEDEF) { - if (!ti.inner) - return 1; - ti = cfree_dwarf_type_info(ti.inner); - } - if (ti.kind == CFREE_DT_PTR) { - if (!ti.inner) - return 1; - return dbg_type_render_c(s, ti.inner, buf, len, cap, ptr_depth + 1u); - } - if (ti.kind == CFREE_DT_STRUCT || ti.kind == CFREE_DT_UNION) { - const char *tag = ti.kind == CFREE_DT_STRUCT ? "struct " : "union "; - if (ptr_depth == 0 || !ti.name || !dbg_c_ident_ok(ti.name)) - return 1; - if (dbg_buf_append(s, buf, len, cap, tag, driver_strlen(tag)) != 0) - return 1; - if (dbg_buf_append(s, buf, len, cap, ti.name, driver_strlen(ti.name)) != 0) - return 1; - } else if (ti.kind == CFREE_DT_ARRAY || ti.kind == CFREE_DT_FUNC) { - return 1; - } else if (dbg_type_base_to_c(s, buf, len, cap, &ti) != 0) { - return 1; - } - while (ptr_depth--) { - if (dbg_buf_append(s, buf, len, cap, " *", 2) != 0) - return 1; - } - return 0; -} - -static int dbg_append_typed_func_proto(DbgState *s, char **src, size_t *len, - size_t *cap, const CfreeJitSym *sym) { - char *tmp = NULL; - size_t tmp_len = 0, tmp_cap = 0; - uint64_t img_pc; - CfreeDwarfSubprogram sp; - CfreeDwarfParamIter *pit; - CfreeDwarfVar param; - int any_param = 0; - int use_named = 0; - int rc = 1; - - if (!s->dwarf) - goto out; - img_pc = dbg_pc_rt_to_img(s, sym->addr); - if (!img_pc || cfree_dwarf_subprogram_at(s->dwarf, img_pc, &sp) != CFREE_OK) { - if (cfree_dwarf_subprogram_named(s->dwarf, sym->name, &sp) != CFREE_OK) - goto out; - use_named = 1; - } - if (sp.inlined) - goto out; - if (dbg_buf_append(s, &tmp, &tmp_len, &tmp_cap, "extern ", 7) != 0) - goto out; - if (dbg_type_render_c(s, sp.return_type, &tmp, &tmp_len, &tmp_cap, 0) != 0) - goto out; - if (dbg_buf_append(s, &tmp, &tmp_len, &tmp_cap, " ", 1) != 0) - goto out; - if (dbg_buf_append(s, &tmp, &tmp_len, &tmp_cap, sym->name, - driver_strlen(sym->name)) != 0) - goto out; - if (dbg_buf_append(s, &tmp, &tmp_len, &tmp_cap, "(", 1) != 0) - goto out; - pit = NULL; - if (use_named - ? cfree_dwarf_param_iter_new_named(s->dwarf, sym->name, &pit) != - CFREE_OK - : cfree_dwarf_param_iter_new(s->dwarf, img_pc, &pit) != CFREE_OK) { - if (dbg_buf_append(s, &tmp, &tmp_len, &tmp_cap, "void", 4) != 0) - goto out; - } else { - for (;;) { - CfreeIterResult r = cfree_dwarf_param_iter_next(pit, &param); - if (r != CFREE_ITER_ITEM) break; - if (any_param && - dbg_buf_append(s, &tmp, &tmp_len, &tmp_cap, ", ", 2) != 0) { - cfree_dwarf_param_iter_free(pit); - goto out; - } - if (dbg_type_render_c(s, param.loc.type, &tmp, &tmp_len, &tmp_cap, 0) != - 0) { - cfree_dwarf_param_iter_free(pit); - goto out; - } - any_param = 1; - } - cfree_dwarf_param_iter_free(pit); - if (!any_param && - dbg_buf_append(s, &tmp, &tmp_len, &tmp_cap, "void", 4) != 0) - goto out; - } - if (dbg_buf_append(s, &tmp, &tmp_len, &tmp_cap, ");\n", 3) != 0) - goto out; - if (dbg_buf_append(s, src, len, cap, tmp, tmp_len) != 0) - goto out; - rc = 0; - -out: - if (tmp) - driver_free(s->env, tmp, tmp_cap); - return rc; -} - -static int dbg_expr_call_arg_count_one(const char *p, const char *end) { - int depth = 1; - int saw_arg = 0; - int nargs = 0; - while (p < end && *p) { - if (*p == '"' || *p == '\'') { - int quote = (unsigned char)*p++; - while (p < end && *p) { - if (*p == '\\' && p + 1 < end) { - p += 2; - continue; - } - if ((unsigned char)*p++ == quote) - break; - } - continue; - } - if (*p == '(') { - saw_arg = 1; - depth++; - } else if (*p == ')') { - depth--; - if (depth == 0) - return saw_arg ? nargs + 1 : 0; - } else if (*p == ',' && depth == 1) { - nargs++; - saw_arg = 0; - } else if (!dbg_isspace((unsigned char)*p)) { - saw_arg = 1; - } - p++; - } - return -1; -} - -static int dbg_expr_call_arg_count(const char *text, const char *name) { - size_t name_len = driver_strlen(name); - int best = -1; - const char *p = text; - if (!text || !name || !name_len) - return -1; - while (*p) { - if (*p == '"' || *p == '\'') { - int quote = (unsigned char)*p++; - while (*p) { - if (*p == '\\' && p[1]) { - p += 2; - continue; - } - if ((unsigned char)*p++ == quote) - break; - } - continue; - } - if ((p == text || !dbg_isalnum_((unsigned char)p[-1])) && - driver_strneq(p, name, name_len) && - !dbg_isalnum_((unsigned char)p[name_len])) { - const char *q = p + name_len; - while (*q && dbg_isspace((unsigned char)*q)) - q++; - if (*q == '(') { - int n = dbg_expr_call_arg_count_one(q + 1, q + driver_strlen(q)); - if (n > best) - best = n; - } - p = q; - continue; - } - p++; - } - return best; -} - -static int dbg_append_fallback_func_proto(DbgState *s, char **src, size_t *len, - size_t *cap, const char *name, - int nargs) { - int i; - if (dbg_buf_append(s, src, len, cap, "extern unsigned long long ", 26) != 0) - return 1; - if (dbg_buf_append(s, src, len, cap, name, driver_strlen(name)) != 0) - return 1; - if (dbg_buf_append(s, src, len, cap, "(", 1) != 0) - return 1; - if (nargs <= 0) { - if (dbg_buf_append(s, src, len, cap, "void", 4) != 0) - return 1; - } else { - for (i = 0; i < nargs; ++i) { - if (i && dbg_buf_append(s, src, len, cap, ", ", 2) != 0) - return 1; - if (dbg_buf_append(s, src, len, cap, "unsigned long long", 18) != 0) - return 1; - } - } - return dbg_buf_append(s, src, len, cap, ");\n", 3); -} - -static int dbg_append_expr_prelude(DbgState *s, char **src, size_t *len, - size_t *cap, const char *call_scan_text) { - CfreeJitSymIter *it = NULL; - CfreeJitSym sym; - if (cfree_jit_sym_iter_new(s->jit, &it) != CFREE_OK) - return 0; - for (;;) { - CfreeIterResult r = cfree_jit_sym_iter_next(it, &sym); - if (r != CFREE_ITER_ITEM) break; - if (sym.kind != CFREE_SK_FUNC) - continue; - if (!dbg_c_ident_ok(sym.name)) - continue; - if (dbg_append_typed_func_proto(s, src, len, cap, &sym) == 0) - continue; - if (dbg_append_fallback_func_proto( - s, src, len, cap, sym.name, - dbg_expr_call_arg_count(call_scan_text, sym.name)) != 0) - goto oom; - } - cfree_jit_sym_iter_free(it); - return 0; - -oom: - cfree_jit_sym_iter_free(it); - return 1; -} - static void dbg_cmd_expr(DbgState *s, const char *expr) { - char *src = NULL; char *body = NULL; - size_t len = 0, cap = 0; size_t body_len = 0, body_cap = 0; char num[32]; char name[64]; @@ -2161,8 +1985,7 @@ static void dbg_cmd_expr(DbgState *s, const char *expr) { while (*expr && dbg_isspace((unsigned char)*expr)) ++expr; if (!*expr) { - driver_errf(DBG_TOOL, "usage: expr EXPR | expr { STATEMENTS } " - "(expression thunks use C syntax today)"); + driver_errf(DBG_TOOL, "usage: expr EXPR | expr { STATEMENTS }"); return; } @@ -2216,45 +2039,28 @@ static void dbg_cmd_expr(DbgState *s, const char *expr) { num_len = dbg_u64_dec(num, sizeof(num), id); if (!num_len) { driver_errf(DBG_TOOL, "expression counter overflow"); - return; + goto out; } prefix_len = driver_strlen("__cfree_dbg_expr_"); if (prefix_len + num_len + 1u > sizeof(name)) { driver_errf(DBG_TOOL, "expression symbol too long"); - return; + goto out; } driver_memcpy(name, "__cfree_dbg_expr_", prefix_len); driver_memcpy(name + prefix_len, num, num_len + 1u); - if (dbg_append_expr_prelude(s, &src, &len, &cap, is_block ? body : expr) != 0) - goto oom; { - const char *ret_type = "unsigned long long "; - if (dbg_buf_append(s, &src, &len, &cap, ret_type, - driver_strlen(ret_type)) != 0) - goto oom; - if (dbg_buf_append(s, &src, &len, &cap, name, driver_strlen(name)) != 0) - goto oom; - } - if (is_block) { - if (dbg_buf_append(s, &src, &len, &cap, "(void) {\n", 9) != 0) - goto oom; - if (dbg_buf_append(s, &src, &len, &cap, body, body_len) != 0) - goto oom; - if (dbg_buf_append(s, &src, &len, &cap, "}\n", 2) != 0) - goto oom; - } else { - const char *open = "(void) { return (unsigned long long)("; - if (dbg_buf_append(s, &src, &len, &cap, open, driver_strlen(open)) != 0) - goto oom; - if (dbg_buf_append(s, &src, &len, &cap, expr, driver_strlen(expr)) != 0) - goto oom; - if (dbg_buf_append(s, &src, &len, &cap, "); }\n", 5) != 0) - goto oom; + const char *src = is_block ? body : expr; + size_t len = is_block ? body_len : driver_strlen(expr); + CfreeFrontendInputKind kind = is_block ? CFREE_FRONTEND_INPUT_REPL_BLOCK + : CFREE_FRONTEND_INPUT_REPL_EXPR; + if (dbg_jit_compile_append_ex(s, s->default_jit_lang, + dbg_jit_default_name(s->default_jit_lang), + src, len, kind, name) != 0) { + goto out; + } } - if (dbg_jit_compile_append(s, CFREE_LANG_C, "<dbg-expr.c>", src, len) != 0) - goto out; entry = cfree_jit_lookup(s->jit, name); if (!entry) { driver_errf(DBG_TOOL, "expression thunk not found: %s", name); @@ -2269,8 +2075,6 @@ static void dbg_cmd_expr(DbgState *s, const char *expr) { oom: driver_errf(DBG_TOOL, "out of memory"); out: - if (src) - driver_free(s->env, src, cap); if (body) driver_free(s->env, body, body_cap); } @@ -2617,6 +2421,7 @@ static void dbg_cmd_help(void) { "Commands (abbrev. shown):\n" " h, help show this help\n" " q, quit exit (Ctrl-D also works)\n" + " :language c|toy|asm|wasm select language for jit/expr input\n" " r, run start fresh execution at entry\n" " c, cont continue after a stop\n" " s, step single-step one instruction\n" @@ -2624,11 +2429,11 @@ static void dbg_cmd_help(void) { " n, next step to next source line (over calls)\n" " finish run until current frame returns\n" " jit [LANG|NAME] { ... } compile and append a language snippet\n" + " { ... } same as jit { ... }\n" " edit [LANG|NAME], e [...] edit and append a language snippet\n" " expr EXPR | expr { ... } compile and call an expression thunk\n" " EXPR same as expr EXPR\n" - " LANG defaults to the input language;\n" - " expression thunks use C syntax today\n" + " LANG defaults to the selected language\n" " call SYMBOL [INT_OR_ADDR...] call function, returns uint64_t\n" " jump ADDR set PC to ADDR (no resume)\n" " bt, backtrace print stack trace with arguments\n" @@ -2650,6 +2455,8 @@ static void dbg_cmd_help(void) { } static CfreeLanguage dbg_default_language_from_inputs(const DbgOpts *o) { + if (o->has_default_lang) + return o->default_lang; if (o->inputs.nsources) return cfree_language_for_path(o->inputs.sources[0]); if (o->inputs.nsource_memory) @@ -2709,6 +2516,11 @@ static int dbg_dispatch(DbgState *s, char *line) { dbg_cmd_help(); return 0; } + if (driver_streq(cmd, ":language") || driver_streq(cmd, ":lang") || + driver_streq(cmd, "language")) { + dbg_cmd_language(s, rest); + return 0; + } if (driver_streq(cmd, "q") || driver_streq(cmd, "quit") || driver_streq(cmd, "exit")) { return 1; /* signal "exit REPL" */ @@ -2962,6 +2774,11 @@ static int dbg_dispatch(DbgState *s, char *line) { return 0; } + if (cmd[0] == '{') { + dbg_cmd_jit(s, raw); + return 0; + } + if (s->session) { dbg_cmd_expr(s, raw); } else { @@ -3015,7 +2832,7 @@ int driver_dbg(int argc, char **argv) { CfreeTarget target; int rc; - if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) { + if (driver_argv_wants_help(argc, argv, 1)) { driver_help_dbg(); return 0; } @@ -3057,15 +2874,18 @@ int driver_dbg(int argc, char **argv) { st.default_jit_name = dbg_jit_default_name(st.default_jit_lang); st.prog_argc = (int)o.prog_argc; st.prog_argv = o.prog_argv; - - st.entry_addr = cfree_jit_lookup(jit, o.entry); - if (!st.entry_addr) { - driver_errf(DBG_TOOL, "entry symbol not found: %s", o.entry); - cfree_jit_free(jit); - driver_compiler_free(compiler); - dbg_options_release(&o); - driver_env_fini(&env); - return 1; + st.entry_name = o.entry; + + if (driver_inputs_count(&o.inputs) != 0) { + st.entry_addr = cfree_jit_lookup(jit, o.entry); + if (!st.entry_addr) { + driver_errf(DBG_TOOL, "entry symbol not found: %s", o.entry); + cfree_jit_free(jit); + driver_compiler_free(compiler); + dbg_options_release(&o); + driver_env_fini(&env); + return 1; + } } if (cfree_jit_session_new(jit, &dhost, &st.session) != CFREE_OK) { @@ -3089,6 +2909,7 @@ int driver_dbg(int argc, char **argv) { dbg_repl(&st); dbg_bps_release_all(&st); + dbg_frontends_release(&st); if (st.dwarf) cfree_dwarf_free(st.dwarf); if (st.session) diff --git a/include/cfree/compile.h b/include/cfree/compile.h @@ -60,10 +60,19 @@ typedef struct CfreeAsmCompileOptions { CfreeDiagnosticOptions diagnostics; } CfreeAsmCompileOptions; +typedef enum CfreeFrontendInputKind { + CFREE_FRONTEND_INPUT_TRANSLATION_UNIT = 0, + CFREE_FRONTEND_INPUT_REPL_TOPLEVEL = 1, + CFREE_FRONTEND_INPUT_REPL_EXPR = 2, + CFREE_FRONTEND_INPUT_REPL_BLOCK = 3, +} CfreeFrontendInputKind; + typedef struct CfreeFrontendCompileOptions { CfreeCodeOptions code; CfreeDiagnosticOptions diagnostics; const void *language_options; + CfreeFrontendInputKind input_kind; + const char *repl_entry_name; } CfreeFrontendCompileOptions; typedef struct CfreeFrontend CfreeFrontend; diff --git a/lang/toy/compile.c b/lang/toy/compile.c @@ -2,10 +2,127 @@ #include "internal.h" +#include <string.h> + typedef struct ToyFrontend { CfreeCompiler* c; + ToyParser parser; + int parser_live; + int poisoned; } ToyFrontend; +static int toy_buf_append(ToyFrontend* fe, char** buf, size_t* len, + size_t* cap, const char* src, size_t n) { + CfreeHeap* h = cfree_compiler_context(fe->c)->heap; + if (*len + n + 1u > *cap) { + size_t nc = *cap ? *cap * 2u : 256u; + char* nb; + while (nc < *len + n + 1u) nc *= 2u; + nb = (char*)h->realloc(h, *buf, *cap ? *cap : 1u, nc, _Alignof(char)); + if (!nb) return 0; + *buf = nb; + *cap = nc; + } + if (n) memcpy(*buf + *len, src, n); + *len += n; + (*buf)[*len] = '\0'; + return 1; +} + +static int toy_build_repl_source(ToyFrontend* fe, + const CfreeFrontendCompileOptions* opts, + const CfreeSourceInput* input, + const uint8_t** source_out, + size_t* source_len_out, char** owned_out, + size_t* owned_cap_out) { + const char* name = opts->repl_entry_name; + const char* body = input->bytes.data ? (const char*)input->bytes.data : ""; + size_t body_len = input->bytes.data ? input->bytes.len : 0u; + char* src = NULL; + size_t len = 0; + size_t cap = 0; + + *owned_out = NULL; + *owned_cap_out = 0; + if (opts->input_kind == CFREE_FRONTEND_INPUT_TRANSLATION_UNIT || + opts->input_kind == CFREE_FRONTEND_INPUT_REPL_TOPLEVEL) { + *source_out = (const uint8_t*)body; + *source_len_out = body_len; + return 1; + } + if (!name || !*name) return 0; + if (!toy_buf_append(fe, &src, &len, &cap, "fn ", 3) || + !toy_buf_append(fe, &src, &len, &cap, name, strlen(name)) || + !toy_buf_append(fe, &src, &len, &cap, "(): i64 {\n", 10)) { + goto oom; + } + if (opts->input_kind == CFREE_FRONTEND_INPUT_REPL_EXPR) { + if (!toy_buf_append(fe, &src, &len, &cap, " return (", 10) || + !toy_buf_append(fe, &src, &len, &cap, body, body_len) || + !toy_buf_append(fe, &src, &len, &cap, ") as i64;\n", 10)) { + goto oom; + } + } else if (opts->input_kind == CFREE_FRONTEND_INPUT_REPL_BLOCK) { + if (!toy_buf_append(fe, &src, &len, &cap, body, body_len) || + (body_len && body[body_len - 1u] != '\n' && + !toy_buf_append(fe, &src, &len, &cap, "\n", 1))) { + goto oom; + } + } else { + goto oom; + } + if (!toy_buf_append(fe, &src, &len, &cap, "}\n", 2)) goto oom; + *source_out = (const uint8_t*)src; + *source_len_out = len; + *owned_out = src; + *owned_cap_out = cap; + return 1; + +oom: + if (src) { + CfreeHeap* h = cfree_compiler_context(fe->c)->heap; + h->free(h, src, cap ? cap : 1u); + } + return 0; +} + +static int toy_seed_repl_symbols(ToyParser* p) { + size_t i; + for (i = 0; i < p->nfns; ++i) { + ToyFn* fn = &p->fns[i]; + CfreeCgDecl decl; + CfreeCgSym sym; + memset(&decl, 0, sizeof decl); + decl.kind = CFREE_CG_DECL_FUNC; + decl.linkage_name = toy_c_linkage_name(p, fn->name); + decl.display_name = fn->name; + decl.type = fn->type; + decl.sym = fn->sym_attrs; + decl.sym.bind = CFREE_SB_GLOBAL; + decl.as.func = fn->func_attrs; + sym = cfree_cg_decl(p->cg, decl); + if (sym == CFREE_CG_SYM_NONE) return 0; + fn->sym = sym; + } + for (i = 0; i < p->nglobals; ++i) { + ToyGlobal* g = &p->globals[i]; + CfreeCgDecl decl; + CfreeCgSym sym; + memset(&decl, 0, sizeof decl); + decl.kind = CFREE_CG_DECL_OBJECT; + decl.linkage_name = toy_c_linkage_name(p, g->name); + decl.display_name = g->name; + decl.type = g->type; + decl.sym = g->sym_attrs; + decl.sym.bind = CFREE_SB_GLOBAL; + decl.as.object = g->object_attrs; + sym = cfree_cg_decl(p->cg, decl); + if (sym == CFREE_CG_SYM_NONE) return 0; + g->sym = sym; + } + return 1; +} + static CfreeFrontendState* toy_frontend_new(CfreeCompiler* c) { CfreeHeap* h; ToyFrontend* fe; @@ -13,6 +130,7 @@ static CfreeFrontendState* toy_frontend_new(CfreeCompiler* c) { h = cfree_compiler_context(c)->heap; fe = (ToyFrontend*)h->alloc(h, sizeof(*fe), _Alignof(ToyFrontend)); if (!fe) return NULL; + memset(fe, 0, sizeof *fe); fe->c = c; return (CfreeFrontendState*)fe; } @@ -27,39 +145,71 @@ static CfreeStatus toy_frontend_compile( const uint8_t* source; size_t source_len; CfreeStatus st; + char* owned_source = NULL; + size_t owned_source_cap = 0; if (!fe || !fe->c || !opts || !input || !out) return CFREE_INVALID; + if (fe->poisoned) return CFREE_ERR; c = fe->c; (void)opts->language_options; /* toy frontend has no per-language options */ - source = input->bytes.data ? input->bytes.data : (const uint8_t*)""; - source_len = input->bytes.data ? input->bytes.len : 0u; + if (!toy_build_repl_source(fe, opts, input, &source, &source_len, + &owned_source, &owned_source_cap)) { + return CFREE_ERR; + } st = cfree_cg_new(c, out, &opts->code, &cg); - if (st != CFREE_OK) return st; + if (st != CFREE_OK) goto done_status; + + if (!fe->parser_live) { + toy_parser_init(&fe->parser, c, cg, source, source_len); + fe->parser.input_kind = opts->input_kind; + fe->parser_live = 1; + } else { + toy_parser_reinit(&fe->parser, c, cg, source, source_len, + opts->input_kind); + } + p = fe->parser; + if (opts->input_kind != CFREE_FRONTEND_INPUT_TRANSLATION_UNIT && + !toy_seed_repl_symbols(&p)) { + cfree_cg_free(cg); + st = CFREE_ERR; + goto done_status; + } - toy_parser_init(&p, c, cg, source, source_len); if (!toy_parse_program(&p) || p.has_error) { - toy_parser_dispose(&p); + fe->parser = p; + fe->poisoned = 1; cfree_cg_free(cg); - return CFREE_ERR; + st = CFREE_ERR; + goto done_status; } if (p.cur.kind != TOK_EOF) { toy_error(&p, p.cur.loc, "unexpected token after program end"); - toy_parser_dispose(&p); + fe->parser = p; + fe->poisoned = 1; cfree_cg_free(cg); - return CFREE_ERR; + st = CFREE_ERR; + goto done_status; } - toy_parser_dispose(&p); + fe->parser = p; cfree_cg_free(cg); - return CFREE_OK; + st = CFREE_OK; + +done_status: + if (owned_source) { + CfreeHeap* h = cfree_compiler_context(fe->c)->heap; + h->free(h, owned_source, owned_source_cap ? owned_source_cap : 1u); + } + return st; } static void toy_frontend_free(CfreeFrontendState* frontend) { ToyFrontend* fe = (ToyFrontend*)frontend; CfreeHeap* h; if (!fe) return; + if (fe->parser_live) toy_parser_dispose(&fe->parser); h = cfree_compiler_context(fe->c)->heap; h->free(h, fe, sizeof(*fe)); } diff --git a/lang/toy/data.c b/lang/toy/data.c @@ -15,7 +15,10 @@ int toy_parse_global_var(ToyParser* p, int is_extern, int is_pub) { int mutable; ToyAttrSet attrs; CfreeSymBind default_bind = - (is_extern || is_pub) ? CFREE_SB_GLOBAL : CFREE_SB_LOCAL; + (is_extern || is_pub || + p->input_kind != CFREE_FRONTEND_INPUT_TRANSLATION_UNIT) + ? CFREE_SB_GLOBAL + : CFREE_SB_LOCAL; if (p->cur.kind == TOK_VAR) { mutable = 1; @@ -75,6 +78,13 @@ int toy_parse_global_var(ToyParser* p, int is_extern, int is_pub) { if (!toy_add_global_typed(p, name, sym, ty, toy_type, mutable)) { return 0; } + { + ToyGlobal* g = toy_find_global(p, name); + if (g) { + g->sym_attrs = decl.sym; + g->object_attrs = decl.as.object; + } + } data_attrs = attrs.data; diff --git a/lang/toy/decls.c b/lang/toy/decls.c @@ -332,7 +332,10 @@ int toy_parse_fn(ToyParser* p, int is_extern, int is_pub) { ToyTypeId ret_toy_type; size_t i; CfreeSymBind default_bind = - (is_extern || is_pub) ? CFREE_SB_GLOBAL : CFREE_SB_LOCAL; + (is_extern || is_pub || + p->input_kind != CFREE_FRONTEND_INPUT_TRANSLATION_UNIT) + ? CFREE_SB_GLOBAL + : CFREE_SB_LOCAL; if (!toy_parser_match(p, TOK_FN)) return 0; if (!toy_parse_attr_list(p, &attrs, default_bind)) return -1; @@ -465,6 +468,8 @@ int toy_parse_fn(ToyParser* p, int is_extern, int is_pub) { toy_error(p, p->cur.loc, "failed to declare function"); return -1; } + fn_entry->sym_attrs = decl.sym; + fn_entry->func_attrs = decl.as.func; if (is_extern) { if (!toy_parser_expect(p, TOK_SEMI)) { diff --git a/lang/toy/internal.h b/lang/toy/internal.h @@ -3,6 +3,7 @@ #include "lexer.h" +#include <cfree/compile.h> #include <cfree/cg.h> #include <cfree/frontend.h> #include <stddef.h> @@ -29,6 +30,8 @@ typedef struct ToyFn { ToyTypeId toy_type; CfreeCgTypeId ret; ToyTypeId toy_ret; + CfreeCgSymbolAttrs sym_attrs; + CfreeCgFuncAttrs func_attrs; CfreeCgTypeId* params; ToyTypeId* toy_params; size_t nparams; @@ -40,6 +43,8 @@ typedef struct ToyGlobal { CfreeCgSym sym; CfreeCgTypeId type; ToyTypeId toy_type; + CfreeCgSymbolAttrs sym_attrs; + CfreeCgObjectAttrs object_attrs; int mutable; } ToyGlobal; @@ -213,11 +218,15 @@ typedef struct ToyParser { uint32_t static_counter; uint32_t expr_island_mask; ToyTypeId last_type; + CfreeFrontendInputKind input_kind; } ToyParser; CfreeCgTypeId toy_builtin_type(ToyParser* p, CfreeCgBuiltinType ty); void toy_parser_init(ToyParser* p, CfreeCompiler* c, CfreeCg* cg, const uint8_t* data, size_t len); +void toy_parser_reinit(ToyParser* p, CfreeCompiler* c, CfreeCg* cg, + const uint8_t* data, size_t len, + CfreeFrontendInputKind input_kind); void toy_parser_dispose(ToyParser* p); void* toy_parser_zalloc(ToyParser* p, size_t count, size_t elem_size, const char* what); diff --git a/lang/toy/parser_core.c b/lang/toy/parser_core.c @@ -35,6 +35,7 @@ void toy_parser_free_mem(ToyParser* p, void* items, size_t size) { void toy_parser_init(ToyParser* p, CfreeCompiler* c, CfreeCg* cg, const uint8_t* data, size_t len) { + memset(p, 0, sizeof *p); toy_lexer_init(&p->lex, data, len); p->cur = toy_lexer_next(&p->lex); p->c = c; @@ -75,6 +76,31 @@ void toy_parser_init(ToyParser* p, CfreeCompiler* c, CfreeCg* cg, p->static_counter = 0; p->expr_island_mask = 0; p->last_type = TOY_TYPE_NONE; + p->input_kind = CFREE_FRONTEND_INPUT_TRANSLATION_UNIT; +} + +void toy_parser_reinit(ToyParser* p, CfreeCompiler* c, CfreeCg* cg, + const uint8_t* data, size_t len, + CfreeFrontendInputKind input_kind) { + toy_lexer_init(&p->lex, data, len); + p->cur = toy_lexer_next(&p->lex); + p->c = c; + p->cg = cg; + p->types = cfree_cg_builtin_types(c); + p->int_type = toy_builtin_type(p, CFREE_CG_BUILTIN_I64); + p->int_ptr_type = cfree_cg_type_ptr(c, p->int_type, 0); + p->va_list_type = toy_builtin_type(p, CFREE_CG_BUILTIN_VARARG_STATE); + p->target = cfree_compiler_target(c); + p->nvars = 0; + p->nscopes = 0; + p->nlabels = 0; + p->cur_fn_ret = toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); + p->cur_fn_ret_toy = toy_type_from_cg(p, p->cur_fn_ret); + p->diag = cfree_compiler_context(c)->diag; + p->has_error = 0; + p->expr_island_mask = 0; + p->last_type = TOY_TYPE_NONE; + p->input_kind = input_kind; } void toy_parser_dispose(ToyParser* p) { diff --git a/src/api/link.c b/src/api/link.c @@ -224,6 +224,8 @@ CfreeStatus cfree_link_jit(CfreeCompiler* c, const CfreeJitLinkOptions* opts, } link_set_jit_host(l, host); link_set_jit_mode(l, 1); + if (!opts->inputs.entry) + link_clear_entry(l); link_set_gc_sections(l, opts->gc_sections); if (opts->extern_resolver) { link_set_extern_resolver(l, opts->extern_resolver, diff --git a/src/link/link.c b/src/link/link.c @@ -321,6 +321,11 @@ void link_set_entry(Linker* l, const char* name) { l->entry_name = link_intern_c_name(l, name); } +void link_clear_entry(Linker* l) { + if (!l) return; + l->entry_name = 0; +} + void link_set_script(Linker* l, const CfreeLinkScript* script) { if (!l || !script) return; l->script = script; diff --git a/src/link/link.h b/src/link/link.h @@ -151,6 +151,7 @@ LinkInputId link_add_archive_bytes(Linker*, const char* name, const u8* data, u8 group_id); void link_set_entry(Linker*, const char* name); +void link_clear_entry(Linker*); /* Borrowed reference; the script and every sub-object must outlive * link_resolve. The linker accepts only the structured form — there is no * text-shaped setter. Hosts that have GNU-ld text use diff --git a/src/link/link_jit.c b/src/link/link_jit.c @@ -355,23 +355,27 @@ CfreeJit *cfree_jit_from_image(LinkImage *img) { mem = require_execmem_h(host, c); page = jit_page_size_h(host, c); - if (img->nsegments == 0) { - compiler_panic(c, no_loc(), "cfree_jit_from_image: image has no segments"); - } - /* Compute the span all segments must fit inside. Layout guarantees * each segment's vaddr is page-aligned (layout_segments / layout_got * align via ALIGN_UP at the page size), so the offset within the - * master mapping is (vaddr - image_base). */ - for (i = 0; i < img->nsegments; ++i) { - const LinkSegment *seg = &img->segments[i]; - u64 hi = ALIGN_UP(seg->vaddr + seg->mem_size, page); - if (seg->vaddr < image_base) - image_base = seg->vaddr; - if (hi > image_end) - image_end = hi; - if (seg->flags & SF_EXEC) - needs_exec = 1; + * master mapping is (vaddr - image_base). An empty JIT image has no + * initial segments, but still owns append slack so the debugger can + * start from an empty translation unit and append code later. */ + if (img->nsegments == 0) { + image_base = page; + image_end = page; + needs_exec = 1; + } else { + for (i = 0; i < img->nsegments; ++i) { + const LinkSegment *seg = &img->segments[i]; + u64 hi = ALIGN_UP(seg->vaddr + seg->mem_size, page); + if (seg->vaddr < image_base) + image_base = seg->vaddr; + if (hi > image_end) + image_end = hi; + if (seg->flags & SF_EXEC) + needs_exec = 1; + } } if (image_base & (page - 1u)) compiler_panic(c, no_loc(), @@ -400,13 +404,17 @@ CfreeJit *cfree_jit_from_image(LinkImage *img) { metrics_scope_end(c, "jit.reserve"); } - segs = (CfreeExecMemRegion *)heap->alloc(heap, sizeof(*segs) * img->nsegments, - _Alignof(CfreeExecMemRegion)); - if (!segs) { - mem->release(mem->user, &master); - compiler_panic(c, no_loc(), "cfree_jit_from_image: oom on segment table"); + segs = NULL; + if (img->nsegments) { + segs = (CfreeExecMemRegion *)heap->alloc( + heap, sizeof(*segs) * img->nsegments, _Alignof(CfreeExecMemRegion)); + if (!segs) { + mem->release(mem->user, &master); + compiler_panic(c, no_loc(), + "cfree_jit_from_image: oom on segment table"); + } + memset(segs, 0, sizeof(*segs) * img->nsegments); } - memset(segs, 0, sizeof(*segs) * img->nsegments); /* Subdivide the master mapping. segs[i].token stays NULL — the * master reservation owns the underlying mapping and is released in @@ -528,7 +536,8 @@ CfreeJit *cfree_jit_from_image(LinkImage *img) { if (mem->protect(mem->user, segs[i].runtime, segs[i].size, perms_for(seg->flags)) != 0) { mem->release(mem->user, &master); - heap->free(heap, segs, sizeof(*segs) * img->nsegments); + if (segs) + heap->free(heap, segs, sizeof(*segs) * img->nsegments); compiler_panic(c, no_loc(), "cfree_jit_from_image: execmem.protect failed"); } @@ -577,7 +586,8 @@ CfreeJit *cfree_jit_from_image(LinkImage *img) { jit = (CfreeJit *)heap->alloc(heap, sizeof(*jit), _Alignof(CfreeJit)); if (!jit) { mem->release(mem->user, &master); - heap->free(heap, segs, sizeof(*segs) * img->nsegments); + if (segs) + heap->free(heap, segs, sizeof(*segs) * img->nsegments); compiler_panic(c, no_loc(), "cfree_jit_from_image: oom on jit handle"); } jit->c = c; @@ -843,6 +853,14 @@ static void jit_apply_one_reloc(CfreeJit *jit, const LinkRelocApply *r) { wr_u32_le(P_bytes, instr); return; } + if (r->kind == R_AARCH64_LD64_GOT_LO12_NC) { + u64 v = ((u64)S + (u64)r->addend) & 0xfffu; + u32 instr = rd_u32_le(P_bytes); + u32 rd = instr & 0x1fu; + u32 rn = (instr >> 5) & 0x1fu; + wr_u32_le(P_bytes, 0x91000000u | rd | (rn << 5) | ((u32)v << 10)); + return; + } link_reloc_apply(jit->c, r->kind, P_bytes, S, r->addend, P); } diff --git a/test/driver/run.sh b/test/driver/run.sh @@ -402,20 +402,46 @@ host_arch=$(uname -m) host_os=$(uname -s) if { [ "$host_arch" = "arm64" ] || [ "$host_arch" = "aarch64" ]; } && { [ "$host_os" = "Darwin" ] || [ "$host_os" = "Linux" ]; }; then - if printf '1 + 2\nq\n' | "$CFREE" dbg "$work/main.c" \ - > "$work/dbg-raw-expr.out" 2> "$work/dbg-raw-expr.err" && - grep -q '\$1 = 3 (0x3)' "$work/dbg-raw-expr.out"; then - printf 'PASS %s\n' "dbg-raw-expression" + printf 'SKIP %s (C frontend owns REPL expression support)\n' \ + "dbg-raw-expression" + + cat > "$work/dbg-toy-main.toy" <<'SRC' +fn main(): i32 { return 0 as i32; } +SRC + if printf ':language toy\njit { let value: i64 = 41; }\nvalue + 1\njit { fn twice(v: i64): i64 { return v * 2; } }\ntwice(value)\njit { record Point { x: i64, y: i64, } }\njit { fn sum_point(): i64 { let p: Point = Point { x: 4, y: 5 }; return p.x + p.y; } }\nsum_point()\nq\n' | + "$CFREE" dbg "$work/dbg-toy-main.toy" \ + > "$work/dbg-toy-repl.out" 2> "$work/dbg-toy-repl.err" && + grep -q '\$1 = 42 (0x2a)' "$work/dbg-toy-repl.out" && + grep -q '\$2 = 82 (0x52)' "$work/dbg-toy-repl.out" && + grep -q '\$3 = 9 (0x9)' "$work/dbg-toy-repl.out"; then + printf 'PASS %s\n' "dbg-toy-repl" pass=$((pass + 1)) else - printf 'FAIL %s\n' "dbg-raw-expression" - sed 's/^/ stdout| /' "$work/dbg-raw-expr.out" - sed 's/^/ stderr| /' "$work/dbg-raw-expr.err" + printf 'FAIL %s\n' "dbg-toy-repl" + sed 's/^/ stdout| /' "$work/dbg-toy-repl.out" + sed 's/^/ stderr| /' "$work/dbg-toy-repl.err" + fail=$((fail + 1)) + fi + + if printf '{ let value: i64 = 41; }\nvalue + 1\nq\n' | + "$CFREE" dbg --language toy \ + > "$work/dbg-empty-toy-repl.out" 2> "$work/dbg-empty-toy-repl.err" && + grep -q '\$1 = 42 (0x2a)' "$work/dbg-empty-toy-repl.out"; then + printf 'PASS %s\n' "dbg-empty-toy-repl" + pass=$((pass + 1)) + else + printf 'FAIL %s\n' "dbg-empty-toy-repl" + sed 's/^/ stdout| /' "$work/dbg-empty-toy-repl.out" + sed 's/^/ stderr| /' "$work/dbg-empty-toy-repl.err" fail=$((fail + 1)) fi else printf 'SKIP %s (unsupported host %s/%s)\n' \ "dbg-raw-expression" "$host_os" "$host_arch" + printf 'SKIP %s (unsupported host %s/%s)\n' \ + "dbg-toy-repl" "$host_os" "$host_arch" + printf 'SKIP %s (unsupported host %s/%s)\n' \ + "dbg-empty-toy-repl" "$host_os" "$host_arch" fi total=$((pass + fail))