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:
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, ¶m);
- 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))