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:
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 {