aboutsummaryrefslogtreecommitdiff
path: root/rcc-legacy/include/rcc/all.h
diff options
context:
space:
mode:
Diffstat (limited to 'rcc-legacy/include/rcc/all.h')
-rw-r--r--rcc-legacy/include/rcc/all.h826
1 files changed, 826 insertions, 0 deletions
diff --git a/rcc-legacy/include/rcc/all.h b/rcc-legacy/include/rcc/all.h
new file mode 100644
index 0000000..f81acd1
--- /dev/null
+++ b/rcc-legacy/include/rcc/all.h
@@ -0,0 +1,826 @@
+/* Source code for RCC-Legacy.
+ Copyright (C) 2026 Richard Qin
+
+This file is part of RCC.
+
+RCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+RCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with RCC; see the file LICENSE. If not see
+<http://www.gnu.org/licenses/>. */
+
+#pragma once
+
+#include <array>
+#include <cassert>
+#include <climits>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <vector>
+
+namespace rcc {
+
+// Basic type aliases
+
+typedef unsigned char uchar;
+typedef unsigned int uint;
+typedef unsigned long ulong;
+typedef unsigned long long bits;
+
+// Forward declaration
+struct BSet;
+struct Ref;
+struct Op;
+struct Ins;
+struct Phi;
+struct Blk;
+struct Use;
+struct Sym;
+struct Num;
+struct Alias;
+struct Tmp;
+struct Con;
+struct Fn;
+struct Typ;
+struct Field;
+struct Dat;
+struct Lnk;
+struct Target;
+struct SubPool;
+struct Ctx;
+struct Insert; // load.cc
+struct Name; // ssa.cc
+struct Asmbits; // emit.cc
+typedef struct Addr Mem;
+
+enum {
+ NIns = 1 << 20,
+ NAlign = 3,
+ NField = 32,
+ NBit = CHAR_BIT * sizeof(bits),
+ NPm = 64, // Max copies for parallel move (rega)
+};
+
+struct Target {
+ char name[16];
+ char apple;
+ char windows;
+ int gpr0;
+ int ngpr;
+ int fpr0;
+ int nfpr;
+ bits rglob;
+ int nrglob;
+ int* rsave;
+ int nrsave[2];
+ bits (*retregs)(Ref, int[2]);
+ bits (*argregs)(Ref, int[2]);
+ int (*memargs)(int);
+ void (*abi0)(Fn*);
+ void (*abi1)(Ctx*, Fn*);
+ void (*isel)(Ctx*, Fn*);
+ void (*emitfn)(Fn*, FILE*);
+ void (*emitfin)(Ctx*, FILE*);
+ char asloc[4];
+ char assym[4];
+ uint cansel : 1;
+};
+
+#define BIT(n) (static_cast<bits>(1) << (n))
+
+enum {
+ RXX = 0,
+ Tmp0 = NBit,
+};
+
+struct BSet {
+ uint nt;
+ bits* t;
+};
+
+enum {
+ RTmp,
+ RCon,
+ RInt,
+ RType, /* last kind to come out of the parser */
+ RSlot,
+ RCall,
+ RMem,
+};
+
+struct Ref {
+ uint type : 3;
+ uint val : 29;
+
+ // Factory methods
+ static Ref none() { return {RTmp, 0}; }
+ static Ref undef() { return {RCon, 0}; }
+ static Ref con_z() { return {RCon, 1}; }
+ static Ref tmp(uint v) { return {RTmp, v}; }
+ static Ref con(uint v) { return {RCon, v}; }
+ static Ref slot(int v) { return {RSlot, static_cast<uint>(v) & 0x1fffffffu}; }
+ static Ref typ(uint v) { return {RType, v}; }
+ static Ref call(uint v) { return {RCall, v}; }
+ static Ref mem(uint v) { return {RMem, v}; }
+ static Ref integer(int v) { return {RInt, static_cast<uint>(v) & 0x1fffffffu}; }
+
+ bool operator==(Ref o) const { return type == o.type && val == o.val; }
+ bool operator!=(Ref o) const { return !(*this == o); }
+};
+
+static inline int req(Ref a, Ref b) {
+ return a.type == b.type && a.val == b.val;
+}
+
+static inline int rtype(Ref r) {
+ if (req(r, Ref::none()))
+ return -1;
+ return r.type;
+}
+
+static inline int rsval(Ref r) {
+ return (static_cast<int>(r.val) ^ 0x10000000) - 0x10000000;
+}
+
+static inline int isreg(Ref r) {
+ return rtype(r) == RTmp && r.val < Tmp0;
+}
+
+enum CmpI {
+ Cieq,
+ Cine,
+ Cisge,
+ Cisgt,
+ Cisle,
+ Cislt,
+ Ciuge,
+ Ciugt,
+ Ciule,
+ Ciult,
+ NCmpI,
+};
+
+enum CmpF {
+ Cfeq,
+ Cfge,
+ Cfgt,
+ Cfle,
+ Cflt,
+ Cfne,
+ Cfo,
+ Cfuo,
+ NCmpF,
+ NCmp = NCmpI + NCmpF,
+};
+
+enum { Kx = -1, Kw, Kl, Ks, Kd };
+
+#define KWIDE(k) ((k) & 1)
+#define KBASE(k) ((k) >> 1)
+
+#include "ops.h" // enum O { Oxxx, ..., NOp } + OpInfo / kops[] single source attribute table
+
+enum J {
+ Jxxx,
+#define JMPS(X) \
+ X(retw) \
+ X(retl) \
+ X(rets) \
+ X(retd) \
+ X(retsb) \
+ X(retub) \
+ X(retsh) \
+ X(retuh) \
+ X(retc) \
+ X(ret0) \
+ X(jmp) \
+ X(jnz) \
+ X(jfieq) \
+ X(jfine) \
+ X(jfisge) \
+ X(jfisgt) X(jfisle) X(jfislt) X(jfiuge) X(jfiugt) X(jfiule) X(jfiult) X(jffeq) X(jffge) \
+ X(jffgt) X(jffle) X(jfflt) X(jffne) X(jffo) X(jffuo) X(hlt)
+#define X(j) J##j,
+ JMPS(X)
+#undef X
+ NJmp
+};
+
+enum {
+ Ocmpw = Oceqw,
+ Ocmpw1 = Ocultw,
+ Ocmpl = Oceql,
+ Ocmpl1 = Ocultl,
+ Ocmps = Oceqs,
+ Ocmps1 = Ocuos,
+ Ocmpd = Oceqd,
+ Ocmpd1 = Ocuod,
+ Oalloc = Oalloc4,
+ Oalloc1 = Oalloc16,
+ Oflag = Oflagieq,
+ Oflag1 = Oflagfuo,
+ Oxsel = Oxselieq,
+ Oxsel1 = Oxselfuo,
+ NPubOp = Onop,
+ Jjf = Jjfieq,
+ Jjf1 = Jjffuo,
+};
+
+#define INRANGE(x, l, u) \
+ (static_cast<unsigned>(x) - static_cast<unsigned>(l) <= \
+ static_cast<unsigned>(u) - static_cast<unsigned>(l))
+#define isstore(o) INRANGE(o, Ostoreb, Ostored)
+#define isload(o) INRANGE(o, Oloadsb, Oload)
+#define isalloc(o) INRANGE(o, Oalloc4, Oalloc16)
+#define isext(o) INRANGE(o, Oextsb, Oextuw)
+#define ispar(o) INRANGE(o, Opar, Opare)
+#define isarg(o) INRANGE(o, Oarg, Oargv)
+#define isret(j) INRANGE(j, Jretw, Jret0)
+#define isparbh(o) INRANGE(o, Oparsb, Oparuh)
+#define isargbh(o) INRANGE(o, Oargsb, Oarguh)
+#define isretbh(j) INRANGE(j, Jretsb, Jretuh)
+#define isxsel(o) INRANGE(o, Oxsel, Oxsel1)
+
+struct Op {
+ char* name;
+ short argcls[2][4];
+ uint canfold : 1;
+ uint hasid : 1;
+ uint idval : 1;
+ uint commutes : 1;
+ uint assoc : 1;
+ uint idemp : 1;
+ uint cmpeqwl : 1;
+ uint cmplgtewl : 1;
+ uint eqval : 1;
+ uint pinned : 1;
+};
+
+struct Ins {
+ uint op : 30;
+ uint cls : 2;
+ Ref to;
+ Ref arg[2];
+};
+
+struct Phi {
+ Ref to;
+ short cls;
+ int visit;
+ uint narg;
+ Ref* arg;
+ Blk** blk;
+ Phi* link;
+};
+
+struct Blk {
+ Phi* phi;
+ Ins* ins;
+ uint nins;
+ struct {
+ short type;
+ short cls; // Jjfi* / Jjff* carried cmp operand cls; other jmp types are 0
+ Ref arg;
+ Ref arg2; // Used only by Jjfi* / Jjff* (carries 2nd cmp operand when fusing cmp+jnz)
+ } jmp;
+ Blk* s1;
+ Blk* s2;
+ Blk* link;
+
+ uint id;
+ uint visit;
+
+ Blk* idom;
+ Blk *dom, *dlink;
+ Blk** fron;
+ uint nfron;
+ int depth;
+
+ Blk** pred;
+ uint npred;
+ BSet in[1], out[1], gen[1];
+ int nlive[2];
+ int loop;
+ char* name;
+};
+
+struct Use {
+ enum {
+ UXXX,
+ UPhi,
+ UIns,
+ UJmp,
+ } type;
+ uint bid;
+ union {
+ Ins* ins;
+ Phi* phi;
+ } u;
+};
+
+struct Sym {
+ enum {
+ SGlo = 0,
+ SThr = 1,
+ SExt = 2,
+ SExtThr = SExt | SThr,
+ } type;
+ uint32_t id;
+};
+
+struct Num {
+ uchar n;
+ uchar nl, nr;
+ Ref l, r;
+};
+
+enum { NoAlias, MayAlias, MustAlias };
+
+struct Alias {
+ enum {
+ ABot = 0,
+ ALoc = 1,
+ ACon = 2,
+ AEsc = 3,
+ ASym = 4,
+ AUnk = 6,
+#define astack(t) ((t) & 1)
+ } type;
+ int base;
+ int64_t offset;
+ // True only for ASym path: Oadd(ASym, AUnk) preserves symbol identity, but offset is
+ // unreliable. Makes alias() degrade to MayAlias on "same symbol / same base" branch, while
+ // "different ASym" can still NoAlias. Not propagated to ALoc/AEsc: stack slot offset is
+ // controlled by IRGen and always known.
+ bool offset_unknown;
+ union {
+ Sym sym;
+ struct {
+ int sz;
+ bits m;
+ } loc;
+ } u;
+ Alias* slot;
+};
+
+struct Tmp {
+ char* name;
+ Ins* def;
+ Use* use;
+ uint ndef, nuse;
+ uint bid;
+ uint cost;
+ int slot;
+ short cls;
+ struct {
+ int r;
+ int w;
+ bits m;
+ } hint;
+ int phi;
+ Alias alias;
+ enum { WFull, Wsb, Wub, Wsh, Wuh, Wsw, Wuw } width;
+ int visit;
+ uint gcmbid;
+};
+
+struct Con {
+ enum {
+ CUndef,
+ CBits,
+ CAddr,
+ } type;
+ Sym sym;
+ union {
+ int64_t i;
+ double d;
+ float s;
+ } bits;
+ char flt;
+};
+
+struct Addr {
+ Con offset;
+ Ref base;
+ Ref index;
+ int scale;
+};
+
+struct Lnk {
+ char export_; // 'export' is C++ reserved word (C++20 module)
+ char thread;
+ char common;
+ char align;
+ char* sec;
+ char* secf;
+};
+
+struct Fn {
+ Blk* start;
+ Tmp* tmp;
+ Con* con;
+ Mem* mem;
+ int ntmp;
+ int ncon;
+ int nmem;
+ uint nblk;
+ int retty;
+ Ref retr;
+ Blk** rpo;
+ bits reg;
+ int slot;
+ int salign;
+ char vararg;
+ char dynalloc;
+ char leaf;
+ char* name;
+ Lnk lnk;
+ SubPool* _subpool; // Memory pool ownership for this fn; parse attaches it when generating fn,
+ // worker calls subpool_free after processing
+};
+
+struct Field {
+ enum {
+ FEnd,
+ Fb,
+ Fh,
+ Fw,
+ Fl,
+ Fs,
+ Fd,
+ FPad,
+ FTyp,
+ } type;
+ uint len;
+};
+
+struct Typ {
+ char* name;
+ char isdark;
+ char isunion;
+ int align;
+ uint64_t size;
+ uint nunion;
+ Field (*fields)[NField + 1];
+};
+
+struct Dat {
+ enum { DStart, DEnd, DB, DH, DW, DL, DZ } type;
+ char* name;
+ Lnk* lnk;
+ union {
+ int64_t num;
+ double fltd;
+ float flts;
+ char* str;
+ struct {
+ char* name;
+ int64_t off;
+ } ref;
+ } u;
+ char isref;
+ char isstr;
+};
+
+extern Target T;
+extern char debug['Z' + 1];
+
+typedef enum {
+ PHeap,
+ PFn,
+} Pool;
+
+// SubPool / Ctx
+
+struct SubPool {
+ std::vector<void*> chunks; // Each chunk is a pointer from emalloc
+};
+
+void* subpool_alloc(SubPool*, size_t);
+void subpool_free(SubPool*);
+
+struct Ctx {
+ SubPool* subpool = nullptr;
+
+ Ins* insb = nullptr; // 8MB, emalloc during init(); freed during ~Ctx
+ Ins* curi = nullptr; // Reverse fill pointer, reset to insb + NIns during bind()
+
+ Fn* load_curf = nullptr;
+ uint load_inum = 0;
+ Insert* load_ilog = nullptr;
+ uint load_nlog = 0;
+
+ Name* ssa_namel = nullptr;
+
+ Ref gvn_con01[2]{};
+ Ins** gvn_tbl = nullptr;
+ uint gvn_tbln = 0;
+
+ bits rega_regu = 0;
+ Tmp* rega_tmp = nullptr;
+ Mem* rega_mem = nullptr;
+ struct {
+ Ref src, dst;
+ int cls;
+ } rega_pm[NPm]{};
+ int rega_npm = 0;
+ int rega_loop = 0;
+ uint rega_stmov = 0, rega_stblk = 0;
+
+ Asmbits* emit_stash = nullptr;
+
+ // die context: during spill, record "peak FPR live set when limit_one"; dumped during emit die
+ int spill_fp_live_peak = 0;
+
+ FILE* out_buf = nullptr;
+ FILE* dbg_buf = nullptr;
+
+ Fn* fn = nullptr;
+
+ void init(); // Allocate insb (8MB) and initial SubPool; other fields zero-initialized
+ void bind(Fn*); // Take over fn's SubPool, reset curi and per-Fn pass states
+ void release(); // Release current SubPool, clear states; do not release insb
+ ~Ctx(); // Release insb and remaining SubPool
+};
+
+extern Typ* typ;
+extern Op optab[NOp];
+
+// util.cc
+
+uint32_t hash(const char*);
+[[noreturn]] void die_(const char*, const char*, ...) __attribute__((format(printf, 2, 3)));
+
+// die-context utilities
+struct Ins;
+void set_die_ctx(Ctx*);
+const char* die_ctx_str(Ctx*, Ins* cur); // cur can be nullptr
+void* emalloc(size_t);
+void* alloc(Ctx*, size_t);
+void freeall(Ctx*);
+void* vnew(Ctx*, ulong, size_t, Pool);
+void vfree(void*);
+void vgrow(Ctx*, void*, ulong);
+
+/// Lazy-allocating vgrow
+inline void vensure(Ctx* ctx, void* vp, ulong len, size_t esz, Pool pl) {
+ void** pp = static_cast<void**>(vp);
+ if (*pp == nullptr)
+ *pp = vnew(ctx, len, esz, pl);
+ else
+ vgrow(ctx, vp, len);
+}
+void addins(Ctx*, Ins**, uint*, Ins*);
+void addbins(Ctx*, Ins**, uint*, Blk*);
+char* strf(Ctx*, Pool, const char*, ...) __attribute__((format(printf, 3, 4)));
+uint32_t intern(const char*);
+char* str(uint32_t);
+int argcls(Ins*, int);
+int iscmp(int, int*, int*);
+void igroup(Blk*, Ins*, Ins**, Ins**);
+void emit(Ctx*, int, int, Ref, Ref, Ref);
+void emiti(Ctx*, Ins);
+void idup(Ctx*, Blk*, Ins*, ulong);
+Ins* icpy(Ins*, Ins*, ulong);
+int cmpop(int);
+int cmpwlneg(int);
+int clsmerge(short*, short);
+int phicls(int, Tmp*);
+uint phiargn(Phi*, Blk*);
+Ref phiarg(Phi*, Blk*);
+Ref newtmp(Ctx*, const char*, int, Fn*);
+void chuse(Ctx*, Ref, int, Fn*);
+int symeq(Sym, Sym);
+Ref newcon(Ctx*, Con*, Fn*);
+Ref getcon(Ctx*, int64_t, Fn*);
+int addcon(Con*, Con*, int);
+int isconbits(Fn* fn, Ref r, int64_t* v);
+void salloc(Ctx*, Ref, Ref, Fn*);
+int lowest_bit(bits);
+void dumpts(BSet*, Tmp*, FILE*);
+void runmatch(uchar*, Num*, Ref, Ref*);
+
+void bsinit(Ctx*, BSet*, uint);
+void bszero(BSet*);
+uint bscount(BSet*);
+void bsset(BSet*, uint);
+void bsclr(BSet*, uint);
+void bscopy(BSet*, BSet*);
+void bsunion(BSet*, BSet*);
+void bsinter(BSet*, BSet*);
+void bsdiff(BSet*, BSet*);
+int bsequal(BSet*, BSet*);
+int bsiter(BSet*, int*);
+
+static inline int bshas(BSet* bs, uint elt) {
+ assert(elt < bs->nt * NBit);
+ return (bs->t[elt / NBit] & BIT(elt % NBit)) != 0;
+}
+
+// parse.cc
+
+void parse(FILE*, char*, void (*)(char*), void (*)(Dat*), void (*)(Fn*));
+void printfn(Fn*, FILE*);
+void printref(Ref, Fn*, FILE*);
+[[noreturn]] void err(char*, ...) __attribute__((format(printf, 1, 2)));
+
+// abi.cc
+
+void elimsb(Fn*);
+
+// cfg.cc - CFG analysis and simplification (Ctx*, Fn*) conventions
+
+Blk* newblk(Ctx*);
+void fillpreds(Ctx*, Fn*);
+void fillcfg(Ctx*, Fn*);
+void filldom(Fn*);
+int sdom(Blk*, Blk*);
+int dom(Blk*, Blk*);
+void fillfron(Ctx*, Fn*);
+void loopiter(Fn*, void (*)(Blk*, Blk*));
+void filldepth(Fn*);
+Blk* lca(Blk*, Blk*);
+void fillloop(Fn*);
+void simpljmp(Ctx*, Fn*);
+int reaches(Fn*, Blk*, Blk*);
+int reachesnotvia(Fn*, Blk*, Blk*, Blk*);
+int ifgraph(Blk*, Blk**, Blk**, Blk**);
+void simplcfg(Ctx*, Fn*);
+
+// mem.cc
+
+void promote(Ctx*, Fn*);
+void coalesce(Ctx*, Fn*);
+
+// alias.cc
+
+void fillalias(Fn*);
+void getalias(Alias*, Ref, Fn*);
+int alias(Ref, int, int, Ref, int, int*, Fn*);
+int escapes(Ref, Fn*);
+
+// load.cc
+
+int loadsz(Ins*);
+int storesz(Ins*);
+void loadopt(Ctx*, Fn*);
+
+// ssa.cc - Use chain population / SSA construction / SSA invariant verification
+
+void adduse_ins(Ctx*, Tmp*, Blk*, Ins*);
+void adduse_phi(Ctx*, Tmp*, Blk*, Phi*);
+void adduse_jmp(Ctx*, Tmp*, Blk*);
+void filluse(Ctx*, Fn*);
+void ssa(Ctx*, Fn*);
+void ssacheck(Fn*);
+void ssa_drain_namel(Ctx*); // Called by ~Ctx to drain emalloc nodes from ssa_namel free list
+
+// copy.cc
+
+void narrowpars(Ctx*, Fn*);
+Ref copyref(Ctx*, Fn*, Blk*, Ins*);
+Ref phicopyref(Ctx*, Fn*, Blk*, Phi*);
+
+// fold.cc
+
+int foldint(Con*, int, int, Con*, Con*);
+Ref foldref(Ctx*, Fn*, Ins*);
+
+// gvn.cc
+//
+
+int zeroval(Fn*, Blk*, Ref, int, int*);
+void gvn(Ctx*, Fn*);
+
+// gcm.cc
+
+int pinned(Ins*);
+void gcm(Ctx*, Fn*);
+
+// ifopt.cc
+
+void ifconvert(Ctx*, Fn*);
+
+// divopt.cc
+
+void divopt(Ctx*, Fn*);
+
+// mulopt.cc - Constant multiplication strength reduction (x * c -> <=3 shift/add/sub/neg).
+
+void mulopt(Ctx*, Fn*);
+
+// storemrg.cc - Merge adjacent same-base 4B-zero stores into 8B storel.
+
+void storemrg(Ctx*, Fn*);
+
+// licm.cc
+
+void licm(Ctx*, Fn*);
+
+// scalar_repl.cc - Hoist intra-loop same-address load+store accumulators to reg + phi.
+
+void scalar_repl(Ctx*, Fn*);
+
+// unroll.cc - Loop unrolling (Phase 1: Full unroll for constant trips).
+
+void unroll(Ctx*, Fn*);
+
+// tre.cc - Tail recursion elimination (Phase 1: Self-recursion + normalized tail call form).
+
+void tre(Ctx*, Fn*);
+
+// lsr.cc - Loop strength reduction (Phase 1: Constant basic induction variable * constant).
+
+void lsr(Ctx*, Fn*);
+
+// loop_idiom.cc - memset / memcpy / memmove loop idiom recognition -> Rewrite as Ocall to builtin
+// runtime.
+
+void loop_idiom(Ctx*, Fn*);
+
+// reduction_unroll.cc - Integer reduction loop K-way parallel accumulator unrolling, breaking
+// serial dependency chains.
+
+void reduction_unroll(Ctx*, Fn*);
+
+// loopinterchange.cc - Integer perfect 2-level nested loop interchange (row-major column access ->
+// row access).
+
+void loopinterchange(Ctx*, Fn*);
+
+// unswitch.cc - Loop unswitching (hoisting if (loop_invariant_cond) out of loop).
+
+void unswitch(Ctx*, Fn*);
+
+// sccp.cc — Sparse Conditional Constant Propagation(Wegman-Zadeck)。
+
+void sccp(Ctx*, Fn*);
+
+// reassoc.cc - Expression reassociation (rank-based, constant gathering + normalization + common
+// term elimination).
+
+void reassoc(Ctx*, Fn*);
+
+// dce.cc - Global dead code elimination (mark-and-sweep, roots = side-effect Ins + jmp.arg).
+
+void dce(Ctx*, Fn*);
+
+// dead_loop.cc - Dead loop elimination (no side-effects + provably terminating + no live-out loop).
+
+void dead_loop(Ctx*, Fn*);
+
+// inline.cc - Function inlining (leaf-only + fixpoint), whole-module perspective, called once after
+// IRGen and before run_midend.
+
+void inline_module(Ctx*, std::vector<Fn*>&);
+
+// simpl.cc
+
+void simpl(Ctx*, Fn*);
+
+// live.cc - Backward dataflow liveness analysis
+
+void liveon(BSet*, Blk*, Blk*);
+void filllive(Ctx*, Fn*);
+
+// spill.cc
+
+void fillcost(Ctx*, Fn*);
+void spill(Ctx*, Fn*);
+
+// rega.cc
+
+void rega(Ctx*, Fn*);
+
+// peephole.cc
+
+void peephole(Ctx*, Fn*);
+
+// emit.cc
+
+void emitfnlnk(char*, Lnk*, FILE*);
+void emitdat(Dat*, FILE*);
+void emitdbgfile(char*, FILE*);
+void emitdbgloc(uint, uint, FILE*);
+int stashbits(Ctx*, bits, int);
+void elf_emitfnfin(char*, FILE*);
+void elf_emitfin(Ctx*, FILE*);
+void macho_emitfin(Ctx*, FILE*);
+void pe_emitfin(Ctx*, FILE*);
+void rv64_emitfin(Ctx*, FILE*);
+
+// macro
+#define die(...) die_(__FILE__, __VA_ARGS__)
+#define MAKESURE(what, x) static_assert((x), #what)
+
+} // namespace rcc