/* 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
. */
/// @file irgen.cc
/// @brief AST -> SSA IR: Main framework, Braun SSA, statement translation
#include "rcc/irgen.h"
#include
namespace rcc {
// BType -> K class
int IRGen::btype_to_cls(BType t) {
return (t == BType::Float) ? Ks : Kw;
}
// Constant / Ref Construction
Fn* IRGen::new_fn(const char* name) {
auto* fn = static_cast(alloc(ctx_, sizeof(Fn)));
memset(fn, 0, sizeof(Fn));
fn->name = strf(ctx_, PFn, "%s", name);
// Initialize tmp array
fn->ntmp = 0;
fn->tmp = static_cast(vnew(ctx_, 0, sizeof(Tmp), PFn));
for (int i = 0; i < Tmp0; i++) {
int cls = (T.fpr0 <= i && i < T.fpr0 + T.nfpr) ? Kd : Kl;
newtmp(ctx_, nullptr, cls, fn);
}
// Initialize con array: con[0] = CUndef (corresponds to R/UNDEF)
fn->ncon = 2;
fn->con = static_cast(vnew(ctx_, static_cast(fn->ncon), sizeof(Con), PFn));
memset(fn->con, 0, 2 * sizeof(Con));
fn->con[0].type = Con::CUndef;
// con[1] = CBits{0} (corresponds to CON_Z)
fn->con[1].type = Con::CBits;
fn->con[1].bits.i = 0;
fn->retty = -1;
fn->slot = 0;
fn->salign = 4;
// All user functions visible to linker
fn->lnk.export_ = 1;
return fn;
}
Blk* IRGen::new_blk(const char* name) {
auto* b = static_cast(alloc(ctx_, sizeof(Blk)));
memset(b, 0, sizeof(Blk));
b->id = cur_fn_->nblk++;
b->name = strf(ctx_, PFn, "%s.%u", name, b->id);
b->ins = static_cast(vnew(ctx_, 4, sizeof(Ins), PFn));
b->nins = 0;
b->visit = 0;
// Append to function block list tail
if (!cur_fn_->start) {
cur_fn_->start = b;
} else {
Blk* tail = cur_fn_->start;
while (tail->link)
tail = tail->link;
tail->link = b;
}
blk_vec_.push_back(b);
return b;
}
Blk* IRGen::get_blk(uint id) {
return blk_vec_[id];
}
bool IRGen::is_sealed(Blk* b) const {
auto it = sealed_.find(b->id);
return it != sealed_.end() && it->second;
}
// Constant / Ref Construction
Ref IRGen::make_int_ref(int32_t val) {
return getcon(ctx_, static_cast(val), cur_fn_);
}
Ref IRGen::make_float_ref(float val) {
Con c;
memset(&c, 0, sizeof(Con));
c.type = Con::CBits;
c.flt = 1;
c.bits.s = val;
return newcon(ctx_, &c, cur_fn_);
}
Ref IRGen::make_global_ref(const char* name) {
Con c;
memset(&c, 0, sizeof(Con));
c.type = Con::CAddr;
c.sym.type = Sym::SGlo;
c.sym.id = intern(name);
return newcon(ctx_, &c, cur_fn_);
}
Ref IRGen::make_global_addr_ref(const char* name) {
Ref sym = make_global_ref(name);
Ref dst = new_tmp_ref(Kl);
Ins i{};
i.op = Oaddr;
i.cls = Kl;
i.to = dst;
i.arg[0] = sym;
i.arg[1] = Ref::none();
push_ins(i);
return dst;
}
Ref IRGen::new_tmp_ref(int cls) {
return newtmp(ctx_, "t", cls, cur_fn_);
}
// Entry
IROutput IRGen::generate(CompUnitNode* root) {
output_ = {};
sym_.register_builtins();
sym_.enter_scope();
visit_comp_unit(root);
sym_.leave_scope();
return std::move(output_);
}
void IRGen::visit_comp_unit(CompUnitNode* node) {
for (auto& child : node->decls_and_funcs) {
if (child->kind == NodeKind::FuncDef)
visit_func_def(static_cast(child.get()));
else if (child->kind == NodeKind::VarDecl)
visit_global_decl(static_cast(child.get()));
}
}
// Function Definition
void IRGen::visit_func_def(FuncDefNode* node) {
cur_fn_ = new_fn(node->name.c_str());
cur_fn_->leaf = 1;
blk_vec_.clear();
current_def_.clear();
incomplete_phis_.clear();
sealed_.clear();
bool is_void = (node->ret_type == BType::Void);
cur_fn_ret_cls_ = is_void ? Kx : btype_to_cls(node->ret_type);
// Register function signature
FuncSig sig;
sig.name = node->name;
sig.ret_type = node->ret_type;
// Entry block
Blk* entry = new_blk("entry");
cur_blk_ = entry;
sealed_[entry->id] = true;
sym_.enter_scope();
// Process formal parameters
for (auto& p : node->params) {
auto* fp = static_cast(p.get());
int cls = fp->is_array ? Kl : btype_to_cls(fp->btype);
sig.params.push_back({fp->btype, fp->is_array});
Symbol sym;
sym.name = fp->name;
sym.type = fp->btype;
sym.is_param = true;
sym.is_array = fp->is_array;
sym.uid = sym_.allocate_uid();
if (fp->is_array) {
for (auto& d : fp->dims)
sym.dims.push_back(eval_const_int(d.get()));
}
// Parameter as SSA value: emit Opar instruction (one %t =cls par per parameter)
Ref param_ref = new_tmp_ref(cls);
Ins par_i;
par_i.op = Opar;
par_i.cls = static_cast(cls);
par_i.to = param_ref;
par_i.arg[0] = Ref::none();
par_i.arg[1] = Ref::none();
push_ins(par_i);
write_variable(sym.uid, cur_blk_, param_ref);
sym_.insert(fp->name, std::move(sym));
}
sym_.register_func(std::move(sig));
// Traverse function body
visit_block(static_cast(node->body.get()));
// Ensure last block is terminated
if (!cur_blk_terminated()) {
if (is_void) {
emit_ret_void();
} else {
Ref zero = (cur_fn_ret_cls_ == Ks) ? make_float_ref(0.0f) : make_int_ref(0);
emit_ret(zero, cur_fn_ret_cls_);
}
}
sym_.leave_scope();
output_.functions.push_back(cur_fn_);
cur_fn_ = nullptr;
}
// Global Declaration
void IRGen::visit_global_decl(VarDeclNode* node) {
for (auto& d : node->defs) {
auto* def = static_cast(d.get());
Symbol sym;
sym.name = def->name;
sym.type = node->btype;
sym.is_const = node->is_const;
sym.is_global = true;
for (auto& dim_expr : def->dims)
sym.dims.push_back(eval_const_int(dim_expr.get()));
sym.is_array = !sym.dims.empty();
GlobalData gd;
gd.name = def->name;
gd.elem_cls = btype_to_cls(node->btype);
gd.is_const = node->is_const;
gd.total_elems = 1;
for (int d2 : sym.dims)
gd.total_elems *= static_cast(d2);
if (!sym.is_array)
gd.total_elems = 1;
if (def->init_val) {
if (sym.is_array) {
std::vector flat;
flatten_init(sym.dims, def->init_val.get(), flat);
for (size_t i = 0; i < gd.total_elems; i++) {
if (i < flat.size() && flat[i]) {
if (node->btype == BType::Int)
gd.init_vals.push_back(eval_const_int(flat[i]));
else
gd.init_vals.push_back(eval_const_float(flat[i]));
} else {
if (node->btype == BType::Int)
gd.init_vals.push_back(static_cast(0));
else
gd.init_vals.push_back(0.0f);
}
}
} else {
if (node->btype == BType::Int)
gd.init_vals.push_back(eval_const_int(def->init_val.get()));
else
gd.init_vals.push_back(eval_const_float(def->init_val.get()));
}
} else {
gd.is_zeroinit = true;
}
if (node->is_const && !sym.is_array && def->init_val) {
if (node->btype == BType::Int)
sym.const_val = eval_const_int(def->init_val.get());
else
sym.const_val = eval_const_float(def->init_val.get());
}
sym_.insert(def->name, std::move(sym));
output_.globals.push_back(std::move(gd));
}
}
// Local Declaration
void IRGen::visit_local_decl(VarDeclNode* node) {
for (auto& d : node->defs) {
auto* def = static_cast(d.get());
Symbol sym;
sym.name = def->name;
sym.type = node->btype;
sym.is_const = node->is_const;
sym.uid = sym_.allocate_uid();
for (auto& dim_expr : def->dims)
sym.dims.push_back(eval_const_int(dim_expr.get()));
sym.is_array = !sym.dims.empty();
int cls = btype_to_cls(node->btype);
if (sym.is_array) {
int total = 1;
for (int d2 : sym.dims)
total *= d2;
Ref size_ref = make_int_ref(total * 4);
// Arrays are always 8B aligned
Ref addr = emit_ins(Oalloc8, Kl, size_ref, Ref::none());
sym.alloca_temp_id = static_cast(addr.val);
if (def->init_val) {
std::vector flat;
flatten_init(sym.dims, def->init_val.get(), flat);
// Count implicit zero tail
constexpr int kBulkZeroThreshold = 65536;
size_t nonzero_tail = flat.size();
while (nonzero_tail > 0 && flat[nonzero_tail - 1] == nullptr)
nonzero_tail--;
int zero_count = total - static_cast(nonzero_tail);
bool bulk = zero_count >= kBulkZeroThreshold;
if (bulk)
emit_bulk_zero(addr, total * 4);
size_t up_to = bulk ? nonzero_tail : static_cast(total);
for (size_t i = 0; i < up_to; i++) {
bool has_init = i < flat.size() && flat[i] != nullptr;
if (bulk && !has_init)
continue; // bulk-zero covered
Ref off = make_int_ref(static_cast(i * 4));
Ref elem_addr = emit_ins(Oadd, Kl, addr, off);
Ref val;
if (has_init) {
val = visit_expr(flat[i]);
val = coerce(val, cls);
} else {
val = (cls == Ks) ? make_float_ref(0.0f) : make_int_ref(0);
}
emit_store(val, elem_addr, cls);
}
}
sym_.insert(def->name, std::move(sym));
} else {
if (node->is_const && def->init_val) {
if (node->btype == BType::Int)
sym.const_val = eval_const_int(def->init_val.get());
else
sym.const_val = eval_const_float(def->init_val.get());
}
int uid = sym.uid;
sym_.insert(def->name, std::move(sym));
if (def->init_val) {
Ref val = visit_expr(def->init_val.get());
val = coerce(val, cls);
write_variable(uid, cur_blk_, val);
}
}
}
}
// Statements
void IRGen::visit_block(BlockNode* node) {
sym_.enter_scope();
for (auto& item : node->items) {
if (cur_blk_terminated())
break;
visit_stmt(item.get());
}
sym_.leave_scope();
}
void IRGen::visit_stmt(ASTNode* node) {
if (cur_blk_terminated())
return;
switch (node->kind) {
case NodeKind::VarDecl:
visit_local_decl(static_cast(node));
break;
case NodeKind::AssignStmt:
visit_assign(static_cast(node));
break;
case NodeKind::ExprStmt: {
auto* es = static_cast(node);
if (es->expr)
visit_expr(es->expr.get());
break;
}
case NodeKind::Block:
visit_block(static_cast(node));
break;
case NodeKind::IfStmt:
visit_if(static_cast(node));
break;
case NodeKind::WhileStmt:
visit_while(static_cast(node));
break;
case NodeKind::ReturnStmt:
visit_return(static_cast(node));
break;
case NodeKind::BreakStmt:
if (!loop_stack_.empty())
emit_jmp(loop_stack_.back().exit_block);
break;
case NodeKind::ContinueStmt:
if (!loop_stack_.empty())
emit_jmp(loop_stack_.back().cond_block);
break;
default:
break;
}
}
void IRGen::visit_assign(AssignStmtNode* node) {
Ref rhs = visit_expr(node->expr.get());
auto* lv = static_cast(node->lval.get());
Symbol* sym = sym_.lookup(lv->name);
int cls = btype_to_cls(sym->type);
// Cross-boundary type coordination
rhs = coerce(rhs, cls);
if (!lv->indices.empty() || sym->is_array || sym->is_global) {
Ref addr;
if (!lv->indices.empty())
addr = compute_array_addr(lv);
else
addr = get_var_addr_ref(sym);
emit_store(rhs, addr, cls);
} else {
write_variable(sym->uid, cur_blk_, rhs);
}
}
void IRGen::visit_if(IfStmtNode* node) {
Blk* then_bb = new_blk("if.then");
Blk* else_bb = node->else_stmt ? new_blk("if.else") : nullptr;
Blk* merge_bb = new_blk("if.merge");
Blk* false_target = else_bb ? else_bb : merge_bb;
visit_cond(node->cond.get(), then_bb, false_target);
seal_block(then_bb);
cur_blk_ = then_bb;
visit_stmt(node->then_stmt.get());
if (!cur_blk_terminated())
emit_jmp(merge_bb);
if (else_bb) {
seal_block(else_bb);
cur_blk_ = else_bb;
visit_stmt(node->else_stmt.get());
if (!cur_blk_terminated())
emit_jmp(merge_bb);
}
seal_block(merge_bb);
cur_blk_ = merge_bb;
}
void IRGen::visit_while(WhileStmtNode* node) {
Blk* cond_bb = new_blk("while.cond");
Blk* body_bb = new_blk("while.body");
Blk* exit_bb = new_blk("while.exit");
emit_jmp(cond_bb);
cur_blk_ = cond_bb;
visit_cond(node->cond.get(), body_bb, exit_bb);
seal_block(body_bb);
cur_blk_ = body_bb;
loop_stack_.push_back({cond_bb, exit_bb});
visit_stmt(node->body.get());
loop_stack_.pop_back();
if (!cur_blk_terminated())
emit_jmp(cond_bb);
seal_block(cond_bb);
seal_block(exit_bb);
cur_blk_ = exit_bb;
}
void IRGen::visit_return(ReturnStmtNode* node) {
if (node->expr) {
Ref val = visit_expr(node->expr.get());
// cur_fn_ret_cls_ is set at visit_func_def entry based on AST ret_type; void functions
// won't reach here.
assert(cur_fn_ret_cls_ != Kx && "non-void return in void function");
val = coerce(val, cur_fn_ret_cls_);
emit_ret(val, cur_fn_ret_cls_);
} else {
emit_ret_void();
}
}
// Braun SSA. Variable identity key is Symbol::uid(int), avoids name collision across scopes.
void IRGen::write_variable(int uid, Blk* blk, Ref val) {
current_def_[uid][blk->id] = val;
}
Ref IRGen::read_variable(int uid, Blk* blk, int cls) {
auto it = current_def_[uid].find(blk->id);
if (it != current_def_[uid].end())
return it->second;
return read_variable_recursive(uid, blk, cls);
}
Ref IRGen::read_variable_recursive(int uid, Blk* blk, int cls) {
Ref val;
uint npred = blk->npred;
if (!is_sealed(blk)) {
// Block not sealed: insert incomplete Phi
auto* phi = static_cast(alloc(ctx_, sizeof(Phi)));
memset(phi, 0, sizeof(Phi));
phi->cls = static_cast(cls);
phi->to = new_tmp_ref(cls);
phi->link = blk->phi;
blk->phi = phi;
val = phi->to;
incomplete_phis_.push_back({uid, phi, blk->id, cls});
} else if (npred == 1) {
val = read_variable(uid, blk->pred[0], cls);
} else if (npred == 0) {
// Entry block, variable undefined
val = (cls == Ks) ? make_float_ref(0.0f) : make_int_ref(0);
} else {
// Multiple predecessors: insert Phi
auto* phi = static_cast(alloc(ctx_, sizeof(Phi)));
memset(phi, 0, sizeof(Phi));
phi->cls = static_cast(cls);
phi->to = new_tmp_ref(cls);
phi->link = blk->phi;
blk->phi = phi;
val = phi->to;
write_variable(uid, blk, val); // Prevent infinite loop
// Populate phi arguments
phi->narg = npred;
phi->arg = static_cast[(alloc(ctx_, npred * sizeof(Ref)));
phi->blk = static_cast(alloc(ctx_, npred * sizeof(Blk*)));
for (uint i = 0; i < npred; i++) {
phi->blk[i] = blk->pred[i];
phi->arg[i] = read_variable(uid, blk->pred[i], cls);
}
val = try_remove_trivial_phi(phi);
}
write_variable(uid, blk, val);
return val;
}
void IRGen::seal_block(Blk* blk) {
// Snapshot to_process
std::vector to_process;
for (auto it = incomplete_phis_.begin(); it != incomplete_phis_.end();) {
if (it->block_id == blk->id) {
to_process.push_back(std::move(*it));
it = incomplete_phis_.erase(it);
} else {
++it;
}
}
sealed_[blk->id] = true;
for (auto& p : to_process)
add_phi_operands(p.var_uid, blk, p.phi, p.cls);
}
void IRGen::add_phi_operands(int uid, Blk* blk, Phi* phi, int cls) {
uint npred = blk->npred;
phi->narg = npred;
phi->arg = static_cast][(alloc(ctx_, npred * sizeof(Ref)));
phi->blk = static_cast(alloc(ctx_, npred * sizeof(Blk*)));
for (uint i = 0; i < npred; i++) {
phi->blk[i] = blk->pred[i];
phi->arg[i] = read_variable(uid, blk->pred[i], cls);
}
try_remove_trivial_phi(phi);
}
Ref IRGen::try_remove_trivial_phi(Phi* phi) {
Ref same = Ref::none();
bool same_set = false;
for (uint i = 0; i < phi->narg; i++) {
if (req(phi->arg[i], phi->to))
continue;
if (same_set && !req(phi->arg[i], same))
return phi->to;
same = phi->arg[i];
same_set = true;
}
return same_set ? same : phi->to;
}
// Emission helpers
void IRGen::push_ins(Ins i) {
vgrow(ctx_, &cur_blk_->ins, cur_blk_->nins + 1);
cur_blk_->ins[cur_blk_->nins++] = i;
}
Ref IRGen::emit_ins(int op, int cls, Ref a0, Ref a1) {
Ref dst = new_tmp_ref(cls);
Ins i;
i.op = static_cast(op);
i.cls = static_cast(cls);
i.to = dst;
i.arg[0] = a0;
i.arg[1] = a1;
push_ins(i);
return dst;
}
void IRGen::emit_store(Ref val, Ref addr, int cls) {
int op = (cls == Ks) ? Ostores : Ostorew;
Ins i;
i.op = static_cast(op);
i.cls = Kw;
i.to = Ref::none();
i.arg[0] = val;
i.arg[1] = addr;
push_ins(i);
}
Ref IRGen::emit_load(Ref addr, int cls) {
return emit_ins(Oload, cls, addr, Ref::none());
}
void IRGen::emit_bulk_zero(Ref addr, int size_bytes) {
// Rewrite as Ocall __rcc_memset
Ins arg_dst{};
arg_dst.op = Oarg;
arg_dst.cls = Kl;
arg_dst.to = Ref::none();
arg_dst.arg[0] = addr;
arg_dst.arg[1] = Ref::none();
push_ins(arg_dst);
Ins arg_val{};
arg_val.op = Oarg;
arg_val.cls = Kw;
arg_val.to = Ref::none();
arg_val.arg[0] = make_int_ref(0);
arg_val.arg[1] = Ref::none();
push_ins(arg_val);
Ins arg_sz{};
arg_sz.op = Oarg;
arg_sz.cls = Kl;
arg_sz.to = Ref::none();
arg_sz.arg[0] = make_int_ref(size_bytes);
arg_sz.arg[1] = Ref::none();
push_ins(arg_sz);
Ins call{};
call.op = Ocall;
call.cls = Kw;
call.to = Ref::none();
call.arg[0] = make_global_ref("__rcc_memset");
call.arg[1] = Ref::none();
push_ins(call);
cur_fn_->leaf = 0;
}
void IRGen::emit_jmp(Blk* target) {
if (cur_blk_terminated())
return;
cur_blk_->jmp.type = Jjmp;
cur_blk_->s1 = target;
add_edge(cur_blk_, target);
}
void IRGen::emit_jnz(Ref cond, Blk* true_bb, Blk* false_bb) {
if (cur_blk_terminated())
return;
cur_blk_->jmp.type = Jjnz;
cur_blk_->jmp.arg = cond;
cur_blk_->s1 = true_bb;
cur_blk_->s2 = false_bb;
add_edge(cur_blk_, true_bb);
add_edge(cur_blk_, false_bb);
}
void IRGen::emit_ret(Ref val, int cls) {
// Choose retw/rets based on cls
int jtype = (cls == Ks) ? Jrets : Jretw;
cur_blk_->jmp.type = static_cast(jtype);
cur_blk_->jmp.arg = val;
}
void IRGen::emit_ret_void() {
cur_blk_->jmp.type = Jret0;
}
bool IRGen::cur_blk_terminated() const {
return cur_blk_ && cur_blk_->jmp.type != Jxxx;
}
void IRGen::add_edge(Blk* from, Blk* to) {
// Add to 'to's pred list
uint n = to->npred;
if (n == 0) {
to->pred = static_cast(vnew(ctx_, 2, sizeof(Blk*), PFn));
} else {
vgrow(ctx_, &to->pred, n + 1);
}
to->pred[n] = from;
to->npred = n + 1;
}
Ref IRGen::get_var_addr_ref(Symbol* sym) {
if (sym->is_global)
return make_global_ref(sym->name.c_str());
if (sym->is_param && sym->is_array)
return read_variable(sym->uid, cur_blk_, Kl);
return Ref::tmp(static_cast(sym->alloca_temp_id));
}
} // namespace rcc
]