kit

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

commit 30e75d19b7e18b8ef01b44fe1ea16eee9233e8ee
parent 106346286e80596ca1f647a9ec69c2120494d34a
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri,  8 May 2026 16:59:16 -0700

src: JIT session, debug info, target pic/code-model plumbing

Diffstat:
Msrc/api/pipeline.c | 518+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Msrc/arch/arch.h | 130+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/core/core.h | 6++++++
Asrc/debug/c_debug.h | 23+++++++++++++++++++++++
Msrc/debug/debug.h | 93++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Msrc/decl/decl.h | 8++++++++
Msrc/lex/lex.h | 4++--
Msrc/link/link.h | 28+++++++++++++++++++++++++---
Msrc/obj/obj.h | 4++++
Msrc/opt/ir.h | 1+
10 files changed, 716 insertions(+), 99 deletions(-)

diff --git a/src/api/pipeline.c b/src/api/pipeline.c @@ -268,12 +268,13 @@ static Linker* build_linker(Compiler* c, const CfreeLinkOptions* opts) opts->obj_bytes[i].data, opts->obj_bytes[i].len); } for (i = 0; i < opts->narchives; ++i) { - link_add_archive_bytes(linker, opts->archives[i].name, - opts->archives[i].data, opts->archives[i].len); + const CfreeBytesInputArchive* a = &opts->archives[i]; + link_add_archive_bytes(linker, a->input.name, + a->input.data, a->input.len, + a->flags, a->group_id); } - if (opts->linker_script_text) { - link_set_script_text(linker, opts->linker_script_text, - opts->linker_script_len); + if (opts->linker_script) { + link_set_script(linker, opts->linker_script); } if (opts->entry) { link_set_entry(linker, opts->entry); @@ -496,8 +497,9 @@ int cfree_run(const CfreeOptions* opts) LoadedBytes* obj_bytes = NULL; LoadedBytes* arch_bytes = NULL; LoadedBytes script; - CfreeBytesInput* obj_in = NULL; - CfreeBytesInput* arch_in = NULL; + const CfreeLinkScript* parsed_script = NULL; + CfreeBytesInput* obj_in = NULL; + CfreeBytesInputArchive* arch_in = NULL; CfreeLinkOptions link_opts; CfreeCompileOptions co; Writer* out_writer = NULL; @@ -525,9 +527,11 @@ int cfree_run(const CfreeOptions* opts) validate_run_options(c, opts); nsrc = total_sources(opts); - co.opt_level = opts->opt_level; - co.debug_info = opts->debug_info; - co.pp = opts->pp; + co.opt_level = opts->opt_level; + co.debug_info = opts->debug_info; + co.pp = opts->pp; + co.warnings_are_errors = opts->warnings_are_errors; + co.max_errors = opts->max_errors; /* Load source paths (if any) up front so OBJ and EXE/JIT share one path. */ if (opts->nsource_files) { @@ -599,14 +603,33 @@ int cfree_run(const CfreeOptions* opts) load_path_bytes(c, opts->linker_script, &script); } + /* The linker takes the structured form only; parse the loaded text into + * CfreeLinkScript via the helper. The parsed script is arena-owned by + * the Compiler and freed when c->tu is released by compiler_fini, so no + * explicit cfree_link_script_free is needed here. */ + if (script.loaded && + cfree_link_script_parse(c, (const char*)script.file.data, + script.file.size, &parsed_script)) { + release_loaded_array(c, src_loaded, opts->nsource_files); + release_loaded_array(c, obj_bytes, opts->nobject_files); + release_loaded_array(c, arch_bytes, opts->narchives); + release_loaded_bytes(c, &script); + compiler_fini(c); + return 1; + } + /* Stage parallel CfreeBytesInput arrays for the linker. */ if (opts->nobject_files) { obj_in = arena_array(c->tu, CfreeBytesInput, opts->nobject_files); for (i = 0; i < opts->nobject_files; ++i) obj_in[i] = obj_bytes[i].in; } if (opts->narchives) { - arch_in = arena_array(c->tu, CfreeBytesInput, opts->narchives); - for (i = 0; i < opts->narchives; ++i) arch_in[i] = arch_bytes[i].in; + arch_in = arena_array(c->tu, CfreeBytesInputArchive, opts->narchives); + for (i = 0; i < opts->narchives; ++i) { + arch_in[i].input = arch_bytes[i].in; + arch_in[i].flags = CFREE_LAF_NONE; + arch_in[i].group_id = 0; + } } link_opts.objs = (ObjBuilder* const*)objs; @@ -615,8 +638,7 @@ int cfree_run(const CfreeOptions* opts) link_opts.nobj_bytes = opts->nobject_files; link_opts.archives = arch_in; link_opts.narchives = opts->narchives; - link_opts.linker_script_text = script.loaded ? (const char*)script.file.data : NULL; - link_opts.linker_script_len = script.loaded ? script.file.size : 0; + link_opts.linker_script = parsed_script; link_opts.entry = opts->entry; link_opts.extern_resolver = opts->extern_resolver; link_opts.extern_resolver_user = opts->extern_resolver_user; @@ -756,6 +778,12 @@ struct CfreeObjFile { Compiler compiler; ObjBuilder* ob; ObjFmt fmt; + /* Lazily-flattened section bytes, indexed by 0-based section idx. NULL + * entries mean "not yet flattened" or "BSS (no bytes)"; size_cache holds + * the flattened length. Allocated from compiler.global on first use. */ + const u8** sec_data_cache; + u32* sec_data_size; + u32 sec_data_n; }; struct CfreeObjSymIter { @@ -795,6 +823,9 @@ CfreeObjFile* cfree_obj_open(const CfreeEnv* env, CfreeTarget target, return NULL; } f->fmt = ofmt; + f->sec_data_cache = NULL; + f->sec_data_size = NULL; + f->sec_data_n = 0; f->ob = obj_read_bytes(&f->compiler, input->name, input->data, input->len, ofmt); return f; } @@ -804,6 +835,16 @@ void cfree_obj_close(CfreeObjFile* f) Heap* h; if (!f) return; h = (Heap*)f->compiler.env->heap; + if (f->sec_data_cache) { + u32 i; + for (i = 0; i < f->sec_data_n; ++i) { + if (f->sec_data_cache[i]) { + h->free(h, (void*)f->sec_data_cache[i], f->sec_data_size[i]); + } + } + h->free(h, f->sec_data_cache, sizeof(*f->sec_data_cache) * f->sec_data_n); + h->free(h, f->sec_data_size, sizeof(*f->sec_data_size) * f->sec_data_n); + } obj_free(f->ob); compiler_fini(&f->compiler); h->free(h, f, sizeof(*f)); @@ -873,6 +914,180 @@ void cfree_obj_symiter_free(CfreeObjSymIter* it) h->free(h, it, sizeof(*it)); } +CfreeObjBuilder* cfree_obj_builder(const CfreeObjFile* f) +{ + return f ? f->ob : NULL; +} + +const uint8_t* cfree_obj_section_data(const CfreeObjFile* cf, uint32_t idx, + size_t* len_out) +{ + /* Cast away const: lazy flatten cache is the only mutation, and it's + * idempotent under the externally-observable API. */ + CfreeObjFile* f = (CfreeObjFile*)cf; + const Section* sec; + Heap* h; + u32 n; + u8* buf; + size_t dummy; + + if (!len_out) len_out = &dummy; + *len_out = 0; + if (!f) return NULL; + + n = obj_section_count(f->ob); + if (idx >= n) return NULL; + + sec = obj_section_get(f->ob, (ObjSecId)(idx + 1)); + if (!sec) return NULL; + /* BSS: no in-file bytes. */ + if (sec->bss_size || sec->bytes.total == 0) return NULL; + + h = (Heap*)f->compiler.env->heap; + + /* Allocate per-section cache arrays on first call. */ + if (!f->sec_data_cache) { + f->sec_data_cache = (const u8**)h->alloc( + h, sizeof(*f->sec_data_cache) * n, _Alignof(const u8*)); + if (!f->sec_data_cache) return NULL; + f->sec_data_size = (u32*)h->alloc( + h, sizeof(*f->sec_data_size) * n, _Alignof(u32)); + if (!f->sec_data_size) { + h->free(h, f->sec_data_cache, sizeof(*f->sec_data_cache) * n); + f->sec_data_cache = NULL; + return NULL; + } + { + u32 i; + for (i = 0; i < n; ++i) { + f->sec_data_cache[i] = NULL; + f->sec_data_size[i] = 0; + } + } + f->sec_data_n = n; + } + + if (f->sec_data_cache[idx]) { + *len_out = f->sec_data_size[idx]; + return f->sec_data_cache[idx]; + } + + buf = (u8*)h->alloc(h, sec->bytes.total, 1); + if (!buf) return NULL; + buf_flatten(&sec->bytes, buf); + f->sec_data_cache[idx] = buf; + f->sec_data_size[idx] = sec->bytes.total; + *len_out = sec->bytes.total; + return buf; +} + +/* Generic GNU-ish names for RelocKind. Per-arch ELF-style names ("R_X86_64_*", + * "R_AARCH64_*", "R_RISCV_*") could be added later — this gives objdump -r a + * stable label without lying about the arch. Returned pointers are static + * string literals (lifetime is forever). */ +static const char* reloc_kind_name(u16 kind) +{ + switch ((RelocKind)kind) { + case R_NONE: return "R_NONE"; + case R_ABS32: return "R_ABS32"; + case R_ABS64: return "R_ABS64"; + case R_REL32: return "R_REL32"; + case R_REL64: return "R_REL64"; + case R_PC32: return "R_PC32"; + case R_PC64: return "R_PC64"; + case R_GOT32: return "R_GOT32"; + case R_PLT32: return "R_PLT32"; + case R_ARM_CALL: return "R_ARM_CALL"; + case R_ARM_MOVW: return "R_ARM_MOVW"; + case R_ARM_MOVT: return "R_ARM_MOVT"; + case R_ARM_B26: return "R_ARM_B26"; + case R_AARCH64_CALL26: return "R_AARCH64_CALL26"; + case R_AARCH64_ADR_PREL_PG_HI21: return "R_AARCH64_ADR_PREL_PG_HI21"; + case R_AARCH64_ADD_ABS_LO12_NC: return "R_AARCH64_ADD_ABS_LO12_NC"; + case R_RV_HI20: return "R_RISCV_HI20"; + case R_RV_LO12_I: return "R_RISCV_LO12_I"; + case R_RV_LO12_S: return "R_RISCV_LO12_S"; + case R_RV_BRANCH: return "R_RISCV_BRANCH"; + case R_RV_JAL: return "R_RISCV_JAL"; + case R_RV_CALL: return "R_RISCV_CALL"; + case R_WASM_FUNCIDX: return "R_WASM_FUNCTION_INDEX_LEB"; + case R_WASM_TABLEIDX: return "R_WASM_TABLE_INDEX_SLEB"; + case R_WASM_MEMOFS: return "R_WASM_MEMORY_ADDR_LEB"; + case R_WASM_TYPEIDX: return "R_WASM_TYPE_INDEX_LEB"; + } + return "R_UNKNOWN"; +} + +struct CfreeObjRelocIter { + CfreeObjFile* file; + u32 nsec; /* obj_section_count snapshot */ + u32 sec_idx; /* 0-based; current section being walked */ + u32 rel_idx; /* index into current section's relocs */ + u32 rel_n; /* count for the current section */ + const Reloc* rels; /* relocs for the current section */ +}; + +CfreeObjRelocIter* cfree_obj_reliter_new(CfreeObjFile* f) +{ + Heap* h; + CfreeObjRelocIter* it; + if (!f) return NULL; + h = (Heap*)f->compiler.env->heap; + it = (CfreeObjRelocIter*)h->alloc(h, sizeof(*it), _Alignof(CfreeObjRelocIter)); + if (!it) return NULL; + it->file = f; + it->nsec = obj_section_count(f->ob); + it->sec_idx = 0; + it->rel_idx = 0; + it->rel_n = 0; + it->rels = NULL; + return it; +} + +int cfree_obj_reliter_next(CfreeObjRelocIter* it, CfreeObjReloc* out) +{ + const Reloc* r; + const ObjSym* sym; + + if (!it || !out) return 0; + + /* Advance to a section that has relocs. */ + while (it->rel_idx >= it->rel_n) { + if (it->sec_idx >= it->nsec) return 0; + ++it->sec_idx; /* convert to 1-based ObjSecId for the call below */ + it->rel_n = obj_reloc_count(it->file->ob, (ObjSecId)it->sec_idx); + it->rels = it->rel_n ? obj_relocs(it->file->ob, (ObjSecId)it->sec_idx) : NULL; + it->rel_idx = 0; + } + + r = &it->rels[it->rel_idx++]; + out->section = (uint32_t)(it->sec_idx - 1); + out->offset = r->offset; + out->addend = r->addend; + out->kind = r->kind; + out->kind_name = reloc_kind_name(r->kind); + + if (r->sym == OBJ_SYM_NONE) { + out->sym = CFREE_SECTION_NONE; + out->sym_name = ""; + } else { + out->sym = (uint32_t)r->sym; + sym = obj_symbol_get(it->file->ob, r->sym); + out->sym_name = (sym && sym->name) + ? pool_str(it->file->compiler.global, sym->name, NULL) + : ""; + } + return 1; +} + +void cfree_obj_reliter_free(CfreeObjRelocIter* it) +{ + Heap* h; + if (!it) return; + h = (Heap*)it->file->compiler.env->heap; + h->free(h, it, sizeof(*it)); +} + /* ============================================================ * POSIX ar archive: write and list * ============================================================ @@ -880,70 +1095,158 @@ void cfree_obj_symiter_free(CfreeObjSymIter* it) * Each member has a 60-byte fixed-width ASCII header followed by data * bytes (plus one '\n' pad byte when data length is odd). */ +/* Compute the basename and length of a member path for ar_name encoding. */ +static void ar_name_basename(const char* in, const char** name_out, size_t* len_out) +{ + const char* name = in; + const char* p; + size_t namelen = 0; + for (p = in; *p; ++p) { + if (*p == '/') name = p + 1; + } + for (p = name; *p; ++p) ++namelen; + *name_out = name; + *len_out = namelen; +} + +/* Determine whether a member name needs the '//' long-name table. + * GNU ar uses the table when the basename exceeds 15 chars or contains + * '/' (since '/' is the in-header terminator). */ +static int ar_name_needs_longtable(const char* name, size_t len) +{ + size_t i; + if (len > 15) return 1; + for (i = 0; i < len; ++i) if (name[i] == '/') return 1; + return 0; +} + +/* Fill a 60-byte member header. `name_field` is the 16-byte ar_name encoding + * to write (already terminated with '/' and space-padded). */ +static void ar_fill_header(char hdr[60], const char name_field[16], + uint64_t epoch, uint64_t size) +{ + size_t j; + for (j = 0; j < 16; ++j) hdr[j] = name_field[j]; + /* ar_date[12] */ + for (j = 16; j < 28; ++j) hdr[j] = ' '; + if (epoch) wh_ar_num(hdr + 16, 12, epoch); + else hdr[16] = '0'; + /* ar_uid[6]: 0 */ + for (j = 28; j < 34; ++j) hdr[j] = ' '; + hdr[28] = '0'; + /* ar_gid[6]: 0 */ + for (j = 34; j < 40; ++j) hdr[j] = ' '; + hdr[34] = '0'; + /* ar_mode[8]: 644 */ + for (j = 40; j < 48; ++j) hdr[j] = ' '; + hdr[40] = '6'; hdr[41] = '4'; hdr[42] = '4'; + /* ar_size[10] */ + wh_ar_num(hdr + 48, 10, size); + /* ar_fmag[2] */ + hdr[58] = '`'; hdr[59] = '\n'; +} + int cfree_ar_write(CfreeWriter* out, - const CfreeBytesInput* members, uint32_t nmembers) + const CfreeBytesInput* members, uint32_t nmembers, + const CfreeArWriteOptions* opts) { static const char magic[] = "!<arch>\n"; + static const CfreeArWriteOptions default_opts = {0, 0, 0}; uint32_t i; + uint64_t epoch; + int long_names; + uint64_t longtab_size = 0; + char pad = '\n'; if (!out) return 1; if (!members && nmembers) return 1; - wh_bytes(out, magic, 8); - - for (i = 0; i < nmembers; ++i) { - const CfreeBytesInput* m = &members[i]; - const char* name; - const char* p; - size_t namelen; - char hdr[60]; - char pad = '\n'; - size_t j; - - if (!m->name) return 1; - - /* Use basename of the member path. */ - name = m->name; - for (p = m->name; *p; ++p) { - if (*p == '/') name = p + 1; + if (!opts) opts = &default_opts; + epoch = opts->epoch; + long_names = opts->long_names; + + /* Two passes (heap-free, recomputing names): pass 1 sizes the '//' + * long-name table; pass 2 emits magic, '//' member if any, and each + * member with its 16-byte ar_name field. The long-name running offset + * is tracked inline during the second pass. */ + if (long_names) { + for (i = 0; i < nmembers; ++i) { + const char* name; + size_t namelen; + if (!members[i].name) return 1; + ar_name_basename(members[i].name, &name, &namelen); + if (ar_name_needs_longtable(name, namelen)) { + longtab_size += (uint64_t)namelen + 2; /* name + "/\n" */ + } } - namelen = 0; - for (p = name; *p; ++p) ++namelen; - if (namelen > 15) namelen = 15; /* truncate; long-name table not yet supported */ - - /* ar_name[16]: name + '/' + spaces */ - for (j = 0; j < 16; ++j) hdr[j] = ' '; - for (j = 0; j < namelen; ++j) hdr[j] = name[j]; - hdr[namelen] = '/'; - - /* ar_date[12]: 0 */ - for (j = 16; j < 28; ++j) hdr[j] = ' '; - hdr[16] = '0'; - - /* ar_uid[6]: 0 */ - for (j = 28; j < 34; ++j) hdr[j] = ' '; - hdr[28] = '0'; - - /* ar_gid[6]: 0 */ - for (j = 34; j < 40; ++j) hdr[j] = ' '; - hdr[34] = '0'; - - /* ar_mode[8]: 644 */ - for (j = 40; j < 48; ++j) hdr[j] = ' '; - hdr[40] = '6'; hdr[41] = '4'; hdr[42] = '4'; - - /* ar_size[10]: member data size, decimal */ - wh_ar_num(hdr + 48, 10, (uint64_t)m->len); + } else { + /* Validate names. */ + for (i = 0; i < nmembers; ++i) { + if (!members[i].name) return 1; + } + } - /* ar_fmag[2]: "`\n" */ - hdr[58] = '`'; - hdr[59] = '\n'; + wh_bytes(out, magic, 8); + /* Emit '//' long-name table member, if any. */ + if (longtab_size) { + char hdr[60]; + char name_field[16]; + size_t j; + for (j = 0; j < 16; ++j) name_field[j] = ' '; + name_field[0] = '/'; + name_field[1] = '/'; + ar_fill_header(hdr, name_field, 0, longtab_size); wh_bytes(out, hdr, 60); - if (m->data && m->len) wh_bytes(out, m->data, m->len); - if (m->len & 1) wh_bytes(out, &pad, 1); + for (i = 0; i < nmembers; ++i) { + const char* name; + size_t namelen; + ar_name_basename(members[i].name, &name, &namelen); + if (ar_name_needs_longtable(name, namelen)) { + wh_bytes(out, name, namelen); + wh_bytes(out, "/\n", 2); + } + } + if (longtab_size & 1) wh_bytes(out, &pad, 1); + } + + /* Re-walk members; emit headers + payloads. Track the running offset + * within the // table so each long-name member's name field encodes + * `/<offset>`. */ + { + uint64_t longtab_off = 0; + for (i = 0; i < nmembers; ++i) { + const CfreeBytesInput* m = &members[i]; + const char* name; + size_t namelen; + char hdr[60]; + char name_field[16]; + size_t j; + + ar_name_basename(m->name, &name, &namelen); + + for (j = 0; j < 16; ++j) name_field[j] = ' '; + if (long_names && ar_name_needs_longtable(name, namelen)) { + /* Encode `/<decimal-offset>` in the 16-byte name field. */ + name_field[0] = '/'; + wh_ar_num(name_field + 1, 15, longtab_off); + longtab_off += (uint64_t)namelen + 2; + } else { + size_t emit = namelen > 15 ? 15 : namelen; + for (j = 0; j < emit; ++j) name_field[j] = name[j]; + name_field[emit] = '/'; + } + + ar_fill_header(hdr, name_field, epoch, (uint64_t)m->len); + wh_bytes(out, hdr, 60); + if (m->data && m->len) wh_bytes(out, m->data, m->len); + if (m->len & 1) wh_bytes(out, &pad, 1); + } } + /* TODO(symbol_index): emit System V `/` symbol-index entry when + * opts->symbol_index is nonzero. Currently silently ignored. */ + return 0; } @@ -952,28 +1255,46 @@ int cfree_ar_iter_init(CfreeArIter* it, const CfreeBytesInput* archive) if (!it || !archive) return 0; if (!archive->data && archive->len) return 0; if (cfree_detect_fmt(archive->data, archive->len) != CFREE_BIN_AR) return 0; - it->_p = archive->data + 8; - it->_end = archive->data + archive->len; + it->_p = archive->data + 8; + it->_end = archive->data + archive->len; + it->_longnames = NULL; + it->_longnames_len = 0; + it->_namebuf[0] = '\0'; return 1; } +/* Resolve a `/<decimal-offset>` reference into the iterator's `//` table. + * Names in the table are terminated by '/' or '\n'. Returns the resolved + * name length, or 0 on failure. Writes the name into it->_namebuf. */ +static size_t ar_resolve_longname(CfreeArIter* it, uint64_t off) +{ + size_t i; + if (!it->_longnames) return 0; + if (off >= it->_longnames_len) return 0; + for (i = 0; i + 1 < sizeof(it->_namebuf); ++i) { + size_t k = (size_t)off + i; + char ch; + if (k >= it->_longnames_len) break; + ch = (char)it->_longnames[k]; + if (ch == '/' || ch == '\n') break; + it->_namebuf[i] = ch; + } + it->_namebuf[i] = '\0'; + return i; +} + int cfree_ar_iter_next(CfreeArIter* it, CfreeArMember* out) { for (;;) { - int namelen; uint64_t size; size_t avail; int j; + int namelen; + char name_field[16]; if (it->_p + 60 > it->_end) return 0; - namelen = 0; - for (j = 0; j < 16; ++j) { - char ch = (char)it->_p[j]; - if (ch == '/' || ch == ' ' || ch == '\0') break; - out->name[namelen++] = ch; - } - out->name[namelen] = '\0'; + for (j = 0; j < 16; ++j) name_field[j] = (char)it->_p[j]; size = 0; for (j = 48; j < 58; ++j) { @@ -986,14 +1307,59 @@ int cfree_ar_iter_next(CfreeArIter* it, CfreeArMember* out) avail = (size_t)(it->_end - it->_p); if ((uint64_t)avail < size) return 0; /* truncated */ + /* Special members (handled before user-visible naming): + * "//" extended-name (long-name) table + * "/" alone System V symbol index + * "__.SYMDEF" BSD symbol index */ + if (name_field[0] == '/' && name_field[1] == '/') { + it->_longnames = it->_p; + it->_longnames_len = (size_t)size; + goto advance; + } + if (name_field[0] == '/' && name_field[1] == ' ') { + /* System V symbol index "/ ". */ + goto advance; + } + + /* Decode name. */ + if (name_field[0] == '/' && + name_field[1] >= '0' && name_field[1] <= '9') { + /* `/<offset>` long-name reference. */ + uint64_t off = 0; + for (j = 1; j < 16; ++j) { + char ch = name_field[j]; + if (ch < '0' || ch > '9') break; + off = off * 10 + (uint64_t)(unsigned char)(ch - '0'); + } + namelen = (int)ar_resolve_longname(it, off); + } else { + namelen = 0; + for (j = 0; j < 16; ++j) { + char ch = name_field[j]; + if (ch == '/' || ch == ' ' || ch == '\0') break; + it->_namebuf[namelen++] = ch; + } + it->_namebuf[namelen] = '\0'; + } + + out->name = it->_namebuf; out->data = it->_p; out->size = (size_t)size; it->_p += (size_t)size; if ((size & 1) && it->_p < it->_end) it->_p++; - if (namelen > 0 && out->name[0] != '/') return 1; - /* Skip special members (symbol table '/', extended name table '//') */ + /* Skip special-but-named members (BSD symbol index). */ + if (it->_namebuf[0] == '_' && + it->_namebuf[1] == '_' && it->_namebuf[2] == '.') { + continue; + } + if (namelen > 0) return 1; + continue; + + advance: + it->_p += (size_t)size; + if ((size & 1) && it->_p < it->_end) it->_p++; } } diff --git a/src/arch/arch.h b/src/arch/arch.h @@ -11,6 +11,14 @@ typedef u32 Reg; #define REG_NONE 0xffffffffu +/* Vector / SIMD forward compat: vector ops will arrive as new variants in + * the BinOp, UnOp, CmpOp, ConvKind families. Backend switches over these + * enums must use `default:` (unreachable / panic) rather than exhaustive + * case lists, so adding a new variant later does not silently mis-handle on + * backends that haven't been taught about it. Vector loads/stores reuse the + * existing load/store methods with vector-typed Operands and appropriate + * MemAccess. RegClass may grow mask/predicate subclasses (e.g. AVX-512 + * k-regs, SVE predicate regs) — Operand.cls is u8 and has room. */ typedef enum RegClass { RC_INT, RC_FP, @@ -60,6 +68,41 @@ typedef enum MemOrder { MO_SEQ_CST, } MemOrder; +/* Compiler-intrinsic kinds dispatched through CGTarget.intrinsic and carried + * on IR_INTRINSIC (extra.imm = IntrinKind). The set is bounded: a backend + * must know each one to choose inline-vs-libcall. Hint intrinsics + * (EXPECT/UNREACHABLE/TRAP/PREFETCH/ASSUME_ALIGNED) ride the same dispatch: + * the backend decides whether they emit an instruction or a no-op. + * + * Not every C builtin lives here. Parser-evaluated builtins + * (__builtin_offsetof, __builtin_constant_p, __builtin_choose_expr, + * __builtin_types_compatible_p) fold at parse and never reach IR. Builtins + * that already have dedicated CGTarget methods (alloca, va_*, setjmp/longjmp, + * atomics) keep them. Returns-twice routines like __cfree_setjmp on real + * arches stay magic external symbol names so opt's call-site analysis sees a + * normal IR_CALL. */ +typedef enum IntrinKind { + INTRIN_NONE = 0, + + /* bit ops */ + INTRIN_POPCOUNT, + INTRIN_CTZ, INTRIN_CLZ, + INTRIN_BSWAP16, INTRIN_BSWAP32, INTRIN_BSWAP64, + + /* memory */ + INTRIN_MEMCPY, INTRIN_MEMMOVE, INTRIN_MEMSET, + INTRIN_PREFETCH, + INTRIN_ASSUME_ALIGNED, + + /* hints */ + INTRIN_EXPECT, + INTRIN_UNREACHABLE, + INTRIN_TRAP, + + /* checked arith — multi-result (value, overflow_flag) */ + INTRIN_ADD_OVERFLOW, INTRIN_SUB_OVERFLOW, INTRIN_MUL_OVERFLOW, +} IntrinKind; + typedef enum OpKind { OPK_IMM, OPK_REG, @@ -208,13 +251,21 @@ typedef struct CGParamDesc { SrcLoc loc; } CGParamDesc; +/* text_section_id and group_id are per-function so that -ffunction-sections, + * __attribute__((section)) on functions, and COMDAT for C11 inline-with- + * external-definition all work with no extra plumbing. Decl.section_id already + * carries the user's request; CG/decl decides the section name policy + * (default .text, vs .text.<sym> under -ffunction-sections, vs explicit + * attribute). The backend just writes to the named section. */ typedef struct CGFuncDesc { - ObjSymId sym; - const Type* fn_type; + ObjSymId sym; + ObjSecId text_section_id; + ObjGroupId group_id; /* OBJ_GROUP_NONE if none */ + const Type* fn_type; const ABIFuncInfo* abi; const CGParamDesc* params; - u32 nparams; - SrcLoc loc; + u32 nparams; + SrcLoc loc; } CGFuncDesc; typedef struct CGCallDesc { @@ -279,6 +330,24 @@ struct MCEmitter { ObjSymId, i64 addend, int explicit_addend, int pair); void (*emit_label_ref)(MCEmitter*, MCLabel, RelocKind, u32 width, i64 addend); void (*set_loc) (MCEmitter*, SrcLoc); + + /* ---- CFI / unwind ---- + * Buffered per-function and emitted into .debug_frame / .eh_frame by Debug + * at TU finalize. CFI directives are byte-position-bound — they describe + * the register-save state starting at the current pos() in the current + * section — so they live on MCEmitter (the only common point that already + * tracks (section_id, offset)). If the CG was constructed with Debug=NULL, + * records are discarded. Register numbering is the per-arch DWARF reg + * number; offsets are byte deltas from the CFA. */ + void (*cfi_startproc) (MCEmitter*); + void (*cfi_endproc) (MCEmitter*); + void (*cfi_def_cfa) (MCEmitter*, u32 reg, i32 ofs); + void (*cfi_def_cfa_offset) (MCEmitter*, i32 ofs); + void (*cfi_def_cfa_register)(MCEmitter*, u32 reg); + void (*cfi_offset) (MCEmitter*, u32 reg, i32 ofs); + void (*cfi_rel_offset) (MCEmitter*, u32 reg, i32 ofs); + void (*cfi_restore) (MCEmitter*, u32 reg); + void (*destroy) (MCEmitter*); }; @@ -288,7 +357,6 @@ struct CGTarget { Compiler* c; ObjBuilder* obj; MCEmitter* mc; - u32 text_section_id; /* ---- function lifecycle ---- */ void (*func_begin)(CGTarget*, const CGFuncDesc*); @@ -301,7 +369,6 @@ struct CGTarget { * mechanics; opt_cgtarget returns fresh virtual regs and ignores spills. */ Reg (*alloc_reg) (CGTarget*, RegClass, const Type*); void (*free_reg) (CGTarget*, Reg); /* hint; opt_cgtarget ignores */ - i32 (*alloc_local)(CGTarget*, u32 size, u32 align); FrameSlot (*frame_slot)(CGTarget*, const FrameSlotDesc*); void (*param) (CGTarget*, const CGParamDesc*); const Reg* (*clobbers)(CGTarget*, RegClass, u32* nregs); @@ -340,6 +407,13 @@ struct CGTarget { void (*load) (CGTarget*, Operand dst /*REG*/, Operand addr /*LOCAL|GLOBAL|INDIRECT*/, MemAccess); void (*store) (CGTarget*, Operand addr /*LOCAL|GLOBAL|INDIRECT*/, Operand src /*REG|IMM*/, MemAccess); void (*addr_of) (CGTarget*, Operand dst /*REG*/, Operand lv /*LOCAL|GLOBAL|INDIRECT*/); + /* Materializes the address of a thread-local symbol into `dst`. Distinct + * from addr_of because TLS resolution can be a multi-instruction sequence + * or a runtime call (e.g. GD model), not a cheap addressing mode. The + * backend chooses the TLS model (LE/IE/LD/GD) from c->target and the + * symbol's visibility. Subsequent accesses go through OPK_INDIRECT on the + * resulting pointer; this lets opt hoist the materialization via LICM. */ + void (*tls_addr_of)(CGTarget*, Operand dst /*REG*/, ObjSymId sym, i64 addend); void (*copy_bytes)(CGTarget*, Operand dst_addr, Operand src_addr, AggregateAccess); void (*set_bytes) (CGTarget*, Operand dst_addr, Operand byte_value, AggregateAccess); void (*bitfield_load) (CGTarget*, Operand dst /*REG*/, Operand record_addr, BitFieldAccess); @@ -398,6 +472,32 @@ struct CGTarget { MemAccess, MemOrder success, MemOrder failure); void (*fence) (CGTarget*, MemOrder); + /* ---- compiler intrinsics ---- + * Typed dispatch for builtins whose lowering is backend-relevant + * (inline-vs-libcall, inline sequence selection) or whose semantics opt + * cares about (hint pattern matching, exhaustiveness). The IR carries + * IR_INTRINSIC + extra.imm = IntrinKind; the wrapped target receives the + * same call at lowering time with materialized operands. + * + * Operand shapes by IntrinKind: + * POPCOUNT/CTZ/CLZ/BSWAP* : dsts[0] REG result; args[0] REG input + * MEMCPY/MEMMOVE : dsts none; args = (dst_addr, src_addr, n) + * MEMSET : dsts none; args = (dst_addr, byte, n) + * PREFETCH : dsts none; args = (addr [, rw [, locality]]) + * ASSUME_ALIGNED : dsts[0] REG; args = (ptr, align [, offset]) + * EXPECT : dsts[0] REG; args = (val, expected) + * UNREACHABLE / TRAP : dsts none; args none + * ADD/SUB/MUL_OVERFLOW : dsts[0] REG result, dsts[1] REG i1 overflow; + * args = (a, b) + * + * Backends that lack an inline sequence for a given kind may emit a + * normal IR_CALL-shaped sequence to a runtime entry (e.g. memcpy) — the + * IR records intent, the backend chooses mechanism. Hint kinds may be + * lowered as no-ops where the arch has nothing to emit. */ + void (*intrinsic)(CGTarget*, IntrinKind, + Operand* dsts, u32 ndst, + const Operand* args, u32 narg); + /* ---- inline asm ---- * Per-arch constraint binding + template assembly, packaged as one block. * ins[i] are pre-evaluated input operands. @@ -436,4 +536,22 @@ CGTarget* cgtarget_new(Compiler*, ObjBuilder*, MCEmitter*); void cgtarget_finalize(CGTarget*); void cgtarget_free(CGTarget*); +/* ---- Disassembler hook ---- + * Bytes -> records, not frontend-driven lowering, so this is a separate + * hook from CGTarget/MCEmitter. The internal implementation may share + * encoding tables with the per-arch backend (sequencing concern, not an + * interface concern). Constructed for c->target. + * + * arch_disasm_decode returns the number of bytes consumed, or 0 if input + * is too short or undecodable (in which case the public iterator advances + * by the arch's minimum unit). ArchDisasm owns the mnemonic / operands / + * annotation string buffers placed into *out; they are valid until the + * next decode or arch_disasm_free, whichever comes first. */ +typedef struct ArchDisasm ArchDisasm; + +ArchDisasm* arch_disasm_new (Compiler*); +u32 arch_disasm_decode(ArchDisasm*, const u8* bytes, size_t len, + u64 vaddr, CfreeInsn* out); +void arch_disasm_free (ArchDisasm*); + #endif diff --git a/src/core/core.h b/src/core/core.h @@ -42,6 +42,12 @@ typedef struct SourceManager SourceManager; * intentionally not intern-pool concepts; see obj/obj.h and link/link.h. */ typedef u32 Sym; +/* Binary-blob handle into the same Pool. Shares the numeric value space with + * Sym but is a distinct typedef so callers don't accidentally mix interned + * strings with decoded literal bytes. */ +typedef u32 BytesId; +#define BYTES_NONE 0u + /* SrcLoc is the public CfreeSrcLoc; the alias keeps internal call sites * terse. */ typedef CfreeSrcLoc SrcLoc; diff --git a/src/debug/c_debug.h b/src/debug/c_debug.h @@ -0,0 +1,23 @@ +#ifndef CFREE_C_DEBUG_H +#define CFREE_C_DEBUG_H + +#include "core/core.h" +#include "type/type.h" +#include "debug/debug.h" + +/* C-specific adapter over the language-neutral Debug type DIE API. + * + * Walks a `const Type*` and emits the corresponding tree of debug_type_* + * calls, returning a DebugTypeId that the C frontend (parse / cg) can + * pass to debug_param, debug_local, debug_func_begin, etc. Used only by + * the C frontend; the core Debug module does not depend on `Type`. + * + * Identity: this adapter interns by Type* pointer. CG owns a per-TU + * cache so repeated lookups for the same Type* yield the same id without + * re-walking the chain. The cache lives for the duration of the TU (i.e. + * until debug_emit / debug_free) and is invalidated automatically when + * Debug is freed. */ + +DebugTypeId c_debug_type(Debug*, TargetABI*, const Type*); + +#endif diff --git a/src/debug/debug.h b/src/debug/debug.h @@ -2,19 +2,28 @@ #define CFREE_DEBUG_H #include "core/core.h" -#include "type/type.h" #include "arch/arch.h" -/* DWARF debug info. The producer side (CG, CGTarget/MCEmitter, opt) feeds events here as - * compilation runs; the consumer side writes .debug_* sections into the same - * ObjBuilder when debug_emit is called. +/* DWARF debug info. The producer side (CG, CGTarget/MCEmitter, opt) feeds + * events here as compilation runs; the consumer side writes .debug_* + * sections into the same ObjBuilder when debug_emit is called. + * + * Type DIEs are addressed through opaque DebugTypeId handles. The core + * Debug module is language-neutral: it knows about DWARF type kinds (base, + * pointer, array, qualified, typedef, function, record, enum) but not + * about the C `Type` representation. C-specific walking — turning a + * `const Type*` chain into a tree of debug_type_* calls — lives in the + * c_debug adapter (src/debug/c_debug.h) and is the only consumer that + * needs to see `type/type.h`. * * Producer responsibilities: - * - Parser: nothing directly; types are looked up on demand from those that - * reach debug_local / debug_param. + * - Parser: nothing directly; types are looked up on demand from those + * that reach debug_local / debug_param. * - CG: function and scope lifecycle, parameter and local declarations. - * - MCEmitter (or the lowering pass inside opt at -O2): the line program, and - * pc-range bounds for functions. + * Resolves a `const Type*` to a DebugTypeId once via c_debug_type and + * passes the id thereafter. + * - MCEmitter (or the lowering pass inside opt at -O2): the line program + * and pc-range bounds for functions. * - opt at -O2: location-list entries when a variable's location changes * across the optimized function. */ @@ -26,8 +35,68 @@ void debug_free(Debug*); /* file table — SourceManager owns paths; returns DWARF file index */ u32 debug_file(Debug*, u32 source_file_id); -/* function lifecycle */ -void debug_func_begin (Debug*, ObjSymId, const Type* fn_type, SrcLoc decl); +/* ============================================================ + * Type DIE handles + * ============================================================ + * Construction is one-shot per type: callers describe the type to Debug, + * which records the DIE and returns an id. Ids are stable for the + * lifetime of the Debug object. DEBUG_TYPE_NONE is the sentinel used for + * "no type" (e.g. a void return without an explicit void DIE — callers + * normally supply debug_type_void()). + * + * The core Debug module does not intern by source-language identity; that + * is the adapter's job (c_debug.h keeps a Type* → DebugTypeId cache). + * Constructing the same shape twice yields two distinct ids. */ + +typedef u32 DebugTypeId; +#define DEBUG_TYPE_NONE 0u + +typedef enum DebugBaseEncoding { + DEBUG_BE_BOOL, + DEBUG_BE_SIGNED, DEBUG_BE_UNSIGNED, + DEBUG_BE_SIGNED_CHAR, DEBUG_BE_UNSIGNED_CHAR, + DEBUG_BE_FLOAT, + DEBUG_BE_UTF, + DEBUG_BE_ADDRESS, +} DebugBaseEncoding; + +DebugTypeId debug_type_base (Debug*, Sym name, DebugBaseEncoding, u32 byte_size); +DebugTypeId debug_type_void (Debug*); +DebugTypeId debug_type_ptr (Debug*, DebugTypeId pointee); +/* count == 0 means incomplete/unknown bound */ +DebugTypeId debug_type_array (Debug*, DebugTypeId elem, u32 count); +DebugTypeId debug_type_const (Debug*, DebugTypeId base); +DebugTypeId debug_type_volatile(Debug*, DebugTypeId base); +DebugTypeId debug_type_restrict(Debug*, DebugTypeId base); +DebugTypeId debug_type_typedef (Debug*, Sym name, DebugTypeId base); +DebugTypeId debug_type_func (Debug*, DebugTypeId ret, + const DebugTypeId* params, u32 nparams, + int variadic); + +/* Records (struct/union) and enums are built incrementally so that + * recursive shapes (a struct containing a pointer to itself) can be + * expressed: open the builder, get an id back from _end, and pass that id + * back through debug_type_ptr earlier if needed via the adapter's cache. */ +typedef struct DebugTypeBuilder DebugTypeBuilder; +DebugTypeBuilder* debug_type_record_begin (Debug*, Sym tag, int is_union, + u32 byte_size, u32 align); +void debug_type_record_field (DebugTypeBuilder*, Sym name, + DebugTypeId type, u32 byte_offset); +void debug_type_record_bitfield(DebugTypeBuilder*, Sym name, + DebugTypeId type, u32 byte_offset, + u16 bit_offset, u16 bit_width); +DebugTypeId debug_type_record_end (DebugTypeBuilder*); + +typedef struct DebugEnumBuilder DebugEnumBuilder; +DebugEnumBuilder* debug_type_enum_begin(Debug*, Sym tag, DebugTypeId base); +void debug_type_enum_value(DebugEnumBuilder*, Sym name, i64 value); +DebugTypeId debug_type_enum_end (DebugEnumBuilder*); + +/* ============================================================ + * Function and variable lifecycle + * ============================================================ */ + +void debug_func_begin (Debug*, ObjSymId, DebugTypeId fn_type, SrcLoc decl); void debug_func_pc_range(Debug*, ObjSecId text_section_id, u32 begin_ofs, u32 end_ofs); void debug_func_end (Debug*); @@ -54,8 +123,8 @@ typedef struct DebugVarLoc { } v; } DebugVarLoc; -void debug_param(Debug*, Sym name, const Type*, SrcLoc, u32 idx, DebugVarLoc); -void debug_local(Debug*, Sym name, const Type*, SrcLoc, DebugVarLoc); +void debug_param(Debug*, Sym name, DebugTypeId, SrcLoc, u32 idx, DebugVarLoc); +void debug_local(Debug*, Sym name, DebugTypeId, SrcLoc, DebugVarLoc); /* line program */ void debug_line(Debug*, ObjSecId text_section_id, u32 text_offset, SrcLoc, int is_stmt); diff --git a/src/decl/decl.h b/src/decl/decl.h @@ -88,4 +88,12 @@ void decl_define_object (DeclTable*, DeclId, u64 size, u32 align, const InitItem* init, u32 ninit); void decl_define_tentative(DeclTable*, DeclId, u64 size, u32 align); +/* Defines `self` as an alias of `target`: self's ObjSym shares + * (section_id, value, size) with target's symbol. Bind/visibility come from + * self's Decl; DF_WEAK on self produces a weak alias (weakref-equivalent). + * Aliasing an undefined or still-tentative target at finalize time is a fatal + * diagnostic. Object-format mapping: ELF same (st_shndx, st_value); COFF + * redirect; Mach-O indirect symbol. */ +void decl_define_alias(DeclTable*, DeclId self, DeclId target); + #endif diff --git a/src/lex/lex.h b/src/lex/lex.h @@ -70,8 +70,8 @@ typedef struct LitInfo { u8 kind; /* LitKind */ u8 enc; /* LitEnc for strings/chars */ u16 flags; /* TokFlag suffix/encoding bits */ - Sym spelling; /* exact source spelling */ - Sym bytes; /* decoded bytes/code units, if already decoded */ + Sym spelling; /* exact source spelling */ + BytesId bytes; /* decoded bytes/code units, if already decoded */ } LitInfo; typedef struct Tok { diff --git a/src/link/link.h b/src/link/link.h @@ -102,11 +102,19 @@ void link_free(Linker*); LinkInputId link_add_obj(Linker*, ObjBuilder*); LinkInputId link_add_obj_bytes(Linker*, const char* name, const u8* data, size_t len); +/* `flags` is a bitmask of CfreeLinkArchFlag. `group_id == 0` means linear + * single-pass; archives sharing a nonzero `group_id` are scanned cyclically + * (equivalent to GNU ld --start-group ... --end-group). */ LinkInputId link_add_archive_bytes(Linker*, const char* name, - const u8* data, size_t len); + const u8* data, size_t len, + u8 flags, u8 group_id); void link_set_entry(Linker*, const char* name); -void link_set_script_text(Linker*, const char* text, size_t len); +/* 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 + * cfree_link_script_parse first. */ +void link_set_script(Linker*, const CfreeLinkScript*); void link_set_extern_resolver(Linker*, LinkExternResolver, void* user); /* Symbol resolution and layout are explicit so file linking and JIT share the @@ -116,7 +124,21 @@ void link_set_extern_resolver(Linker*, LinkExternResolver, void* user); * panic between resolve and consumer (emit_writer / jit_from_image) reaps * it. Successful consumers either call link_image_free (which undefers and * frees) or transfer ownership via cfree_jit_from_image (which undefers and - * keeps the image alive for the JIT's lifetime). */ + * keeps the image alive for the JIT's lifetime). + * + * ---- Incremental-linking invariant (forward compat) ---- + * The single-shot link_resolve implementation must not destroy or consume + * input-side state that a future incremental re-resolve would need. + * Specifically: + * - LinkRelocApply records stay as data: do not burn them into segment + * bytes destructively without preserving the originals. + * - LinkInputId -> ObjBuilder* mappings stay stable for the lifetime of + * the Linker — adding an input never invalidates an existing handle. + * - Resolution is structured as a function from inputs to a fresh + * LinkImage, not as in-place mutation of the Linker. + * Incremental linking is the single most likely future addition; this + * comment locks in the implementation discipline that keeps the existing + * surface amenable, with no speculative API. */ LinkImage* link_resolve(Linker*); void link_image_free(LinkImage*); const LinkSymbol* link_symbol(LinkImage*, LinkSymId); diff --git a/src/obj/obj.h b/src/obj/obj.h @@ -183,6 +183,10 @@ ObjSymId obj_symbol(ObjBuilder*, Sym name, SymBind, SymKind, ObjSecId section_id, u64 value, u64 size); ObjSymId obj_symbol_ex(ObjBuilder*, Sym name, SymBind, SymVis, SymKind, ObjSecId section_id, u64 value, u64 size, u64 common_align); +/* obj_symbol_ex creates a symbol; obj_symbol_define fills in the + * (section_id, value, size) fields of an already-created symbol. The pair + * supports forward references: an undefined ObjSymId is created when first + * needed for a relocation, and defined later when its definition is emitted. */ void obj_symbol_define(ObjBuilder*, ObjSymId, ObjSecId section_id, u64 value, u64 size); void obj_reloc(ObjBuilder*, ObjSecId section_id, u32 offset, diff --git a/src/opt/ir.h b/src/opt/ir.h @@ -40,6 +40,7 @@ typedef enum IROp { IR_SETJMP, /* returns-twice; opt treats as control barrier */ IR_LONGJMP, /* terminator-like; control does not return */ IR_ASM_BLOCK, /* opaque to most passes; preserves order, defines outs, clobbers */ + IR_INTRINSIC, /* extra.imm = IntrinKind; multi-result for *_OVERFLOW */ } IROp; typedef struct IRCallAux {