/* 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
. */
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
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(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(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(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(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(x) - static_cast(l) <= \
static_cast(u) - static_cast(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 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(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&);
// 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