aboutsummaryrefslogtreecommitdiff
path: root/rcc-legacy
diff options
context:
space:
mode:
authorRichard-Qin-X <richard.qin.001@gmail.com>2026-06-08 17:00:52 +0800
committerRichard-Qin-X <richard.qin.001@gmail.com>2026-06-08 17:00:52 +0800
commit71564eb6b92d3e0a99d4061980afd8b51227906c (patch)
treeaaa38eb0c58dc25467975dc224590d6541a77faf /rcc-legacy
parentc2995a5b95a1e04620ad1fc94ed19186234388ca (diff)
downloadrcc-project-71564eb6b92d3e0a99d4061980afd8b51227906c.tar.gz
rcc-project-71564eb6b92d3e0a99d4061980afd8b51227906c.tar.xz
[legacy/ir] Add SSA IR generation
Diffstat (limited to 'rcc-legacy')
-rw-r--r--rcc-legacy/src/ir/irgen.cc728
-rw-r--r--rcc-legacy/src/ir/irgen_expr.cc328
-rw-r--r--rcc-legacy/src/ir/irgen_util.cc244
-rw-r--r--rcc-legacy/src/ir/print.cc388
4 files changed, 1688 insertions, 0 deletions
diff --git a/rcc-legacy/src/ir/irgen.cc b/rcc-legacy/src/ir/irgen.cc
new file mode 100644
index 0000000..1332109
--- /dev/null
+++ b/rcc-legacy/src/ir/irgen.cc
@@ -0,0 +1,728 @@
+/* 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/>. */
+
+/// @file irgen.cc
+/// @brief AST -> SSA IR: Main framework, Braun SSA, statement translation
+
+#include "rcc/irgen.h"
+
+#include <cstddef>
+
+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<Fn*>(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<Tmp*>(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<Con*>(vnew(ctx_, static_cast<ulong>(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<Blk*>(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<Ins*>(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<int64_t>(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<FuncDefNode*>(child.get()));
+ else if (child->kind == NodeKind::VarDecl)
+ visit_global_decl(static_cast<VarDeclNode*>(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<FuncFParamNode*>(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<uint>(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<BlockNode*>(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<VarDefNode*>(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<size_t>(d2);
+ if (!sym.is_array)
+ gd.total_elems = 1;
+
+ if (def->init_val) {
+ if (sym.is_array) {
+ std::vector<ASTNode*> 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<int32_t>(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<VarDefNode*>(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<int>(addr.val);
+
+ if (def->init_val) {
+ std::vector<ASTNode*> 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<int>(nonzero_tail);
+ bool bulk = zero_count >= kBulkZeroThreshold;
+ if (bulk)
+ emit_bulk_zero(addr, total * 4);
+ size_t up_to = bulk ? nonzero_tail : static_cast<size_t>(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<int>(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<VarDeclNode*>(node));
+ break;
+ case NodeKind::AssignStmt:
+ visit_assign(static_cast<AssignStmtNode*>(node));
+ break;
+ case NodeKind::ExprStmt: {
+ auto* es = static_cast<ExprStmtNode*>(node);
+ if (es->expr)
+ visit_expr(es->expr.get());
+ break;
+ }
+ case NodeKind::Block:
+ visit_block(static_cast<BlockNode*>(node));
+ break;
+ case NodeKind::IfStmt:
+ visit_if(static_cast<IfStmtNode*>(node));
+ break;
+ case NodeKind::WhileStmt:
+ visit_while(static_cast<WhileStmtNode*>(node));
+ break;
+ case NodeKind::ReturnStmt:
+ visit_return(static_cast<ReturnStmtNode*>(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<LValNode*>(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<Phi*>(alloc(ctx_, sizeof(Phi)));
+ memset(phi, 0, sizeof(Phi));
+ phi->cls = static_cast<short>(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<Phi*>(alloc(ctx_, sizeof(Phi)));
+ memset(phi, 0, sizeof(Phi));
+ phi->cls = static_cast<short>(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<Ref*>(alloc(ctx_, npred * sizeof(Ref)));
+ phi->blk = static_cast<Blk**>(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<IncompletePhi> 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<Ref*>(alloc(ctx_, npred * sizeof(Ref)));
+ phi->blk = static_cast<Blk**>(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<uint>(op);
+ i.cls = static_cast<uint>(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<uint>(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<short>(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<Blk**>(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<uint>(sym->alloca_temp_id));
+}
+
+} // namespace rcc
diff --git a/rcc-legacy/src/ir/irgen_expr.cc b/rcc-legacy/src/ir/irgen_expr.cc
new file mode 100644
index 0000000..e18895c
--- /dev/null
+++ b/rcc-legacy/src/ir/irgen_expr.cc
@@ -0,0 +1,328 @@
+/* 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/>. */
+
+/// @file irgen_expr.cc
+/// @brief Expression evaluation + short-circuit conditional jump
+
+#include "rcc/irgen.h"
+
+namespace rcc {
+
+int IRGen::ref_cls_of(Ref r, Fn* fn) {
+ if (rtype(r) == RTmp && r.val >= Tmp0)
+ return fn->tmp[r.val].cls;
+ if (rtype(r) == RCon) {
+ Con* c = &fn->con[r.val];
+ if (c->type == Con::CBits && c->flt)
+ return Ks;
+ }
+ return Kw;
+}
+
+Ref IRGen::coerce(Ref v, int target_cls) {
+ int cur = ref_cls_of(v, cur_fn_);
+ if (cur == target_cls)
+ return v;
+ if (target_cls == Ks && cur == Kw)
+ return emit_ins(Oswtof, Ks, v, Ref::none());
+ if (target_cls == Kw && cur == Ks)
+ return emit_ins(Ostosi, Kw, v, Ref::none());
+ return v;
+}
+
+// Expression evaluation
+
+Ref IRGen::visit_expr(ASTNode* node) {
+ switch (node->kind) {
+ case NodeKind::IntLiteral:
+ return make_int_ref(static_cast<IntLiteralNode*>(node)->value);
+
+ case NodeKind::FloatLiteral:
+ return make_float_ref(static_cast<FloatLiteralNode*>(node)->value);
+
+ case NodeKind::LVal: {
+ auto* lv = static_cast<LValNode*>(node);
+ Symbol* sym = sym_.lookup(lv->name);
+ if (!sym)
+ die("undefined variable");
+
+ // Constant scalar: return constant value directly
+ if (sym->is_const && !sym->is_array && lv->indices.empty()) {
+ if (auto* iv = std::get_if<int32_t>(&sym->const_val))
+ return make_int_ref(*iv);
+ if (auto* fv = std::get_if<float>(&sym->const_val))
+ return make_float_ref(*fv);
+ }
+
+ // Array without index -> return address
+ if (sym->is_array && lv->indices.empty())
+ return get_var_addr_ref(sym);
+
+ if (sym->is_array && !lv->indices.empty()) {
+ if (lv->indices.size() < sym->dims.size() + (sym->is_param ? 1 : 0))
+ return compute_array_addr(lv);
+ Ref addr = compute_array_addr(lv);
+ return emit_load(addr, btype_to_cls(sym->type));
+ }
+
+ // Global variable
+ if (sym->is_global) {
+ Ref addr = make_global_ref(sym->name.c_str());
+ return emit_load(addr, btype_to_cls(sym->type));
+ }
+
+ // Alloca variable
+ if (sym->alloca_temp_id >= 0) {
+ Ref addr = Ref::tmp(static_cast<uint>(sym->alloca_temp_id));
+ return emit_load(addr, btype_to_cls(sym->type));
+ }
+
+ // Normal local scalar: Braun
+ int cls = btype_to_cls(sym->type);
+ return read_variable(sym->uid, cur_blk_, cls);
+ }
+
+ case NodeKind::BinaryExpr: {
+ auto* bin = static_cast<BinaryExprNode*>(node);
+
+ // Logical operation -> short-circuit evaluation -> phi(1,0)
+ if (bin->op == BinOp::And || bin->op == BinOp::Or) {
+ Blk* true_bb = new_blk("cond.true");
+ Blk* false_bb = new_blk("cond.false");
+ Blk* merge_bb = new_blk("cond.merge");
+
+ visit_cond(node, true_bb, false_bb);
+
+ seal_block(true_bb);
+ cur_blk_ = true_bb;
+ emit_jmp(merge_bb);
+
+ seal_block(false_bb);
+ cur_blk_ = false_bb;
+ emit_jmp(merge_bb);
+
+ seal_block(merge_bb);
+ cur_blk_ = merge_bb;
+
+ // Phi: true→1, false→0
+ auto* phi = static_cast<Phi*>(alloc(ctx_, sizeof(Phi)));
+ memset(phi, 0, sizeof(Phi));
+ phi->cls = Kw;
+ phi->to = new_tmp_ref(Kw);
+ phi->narg = 2;
+ phi->arg = static_cast<Ref*>(alloc(ctx_, 2 * sizeof(Ref)));
+ phi->blk = static_cast<Blk**>(alloc(ctx_, 2 * sizeof(Blk*)));
+ phi->arg[0] = make_int_ref(1);
+ phi->blk[0] = true_bb;
+ phi->arg[1] = make_int_ref(0);
+ phi->blk[1] = false_bb;
+ phi->link = merge_bb->phi;
+ merge_bb->phi = phi;
+ return phi->to;
+ }
+
+ Ref lhs = visit_expr(bin->lhs.get());
+ Ref rhs = visit_expr(bin->rhs.get());
+
+ int lk = ref_cls_of(lhs, cur_fn_);
+ int rk = ref_cls_of(rhs, cur_fn_);
+
+ bool is_float = (lk == Ks || rk == Ks);
+ if (is_float && lk == Kw)
+ lhs = emit_ins(Oswtof, Ks, lhs, Ref::none());
+ if (is_float && rk == Kw)
+ rhs = emit_ins(Oswtof, Ks, rhs, Ref::none());
+
+ int rt = is_float ? Ks : Kw;
+ switch (bin->op) {
+ case BinOp::Add:
+ return emit_ins(Oadd, rt, lhs, rhs);
+ case BinOp::Sub:
+ return emit_ins(Osub, rt, lhs, rhs);
+ case BinOp::Mul:
+ return emit_ins(Omul, rt, lhs, rhs);
+ case BinOp::Div:
+ return emit_ins(Odiv, rt, lhs, rhs);
+ case BinOp::Mod:
+ return emit_ins(Orem, rt, lhs, rhs);
+ case BinOp::Lt:
+ return emit_ins(is_float ? Oclts : Ocsltw, Kw, lhs, rhs);
+ case BinOp::Gt:
+ return emit_ins(is_float ? Ocgts : Ocsgtw, Kw, lhs, rhs);
+ case BinOp::Le:
+ return emit_ins(is_float ? Ocles : Ocslew, Kw, lhs, rhs);
+ case BinOp::Ge:
+ return emit_ins(is_float ? Ocges : Ocsgew, Kw, lhs, rhs);
+ case BinOp::Eq:
+ return emit_ins(is_float ? Oceqs : Oceqw, Kw, lhs, rhs);
+ case BinOp::Ne:
+ return emit_ins(is_float ? Ocnes : Ocnew, Kw, lhs, rhs);
+ default:
+ return make_int_ref(0);
+ }
+ }
+
+ case NodeKind::UnaryExpr: {
+ auto* u = static_cast<UnaryExprNode*>(node);
+ Ref operand = visit_expr(u->operand.get());
+ switch (u->op) {
+ case UnaryOp::Pos:
+ return operand;
+ case UnaryOp::Neg: {
+ int k = ref_cls_of(operand, cur_fn_);
+ if (k == Ks)
+ return emit_ins(Oneg, Ks, operand, Ref::none());
+ return emit_ins(Osub, Kw, make_int_ref(0), operand);
+ }
+ case UnaryOp::Not:
+ return emit_ins(Oceqw, Kw, operand, make_int_ref(0));
+ }
+ return make_int_ref(0);
+ }
+
+ case NodeKind::FuncCall: {
+ auto* call_node = static_cast<FuncCallNode*>(node);
+
+ // starttime/stoptime special handling
+ if (call_node->name == "starttime" || call_node->name == "stoptime") {
+ std::string real_name = "_sysy_" + call_node->name;
+ Ref fn_ref = make_global_ref(real_name.c_str());
+ Ref line_ref = make_int_ref(call_node->lineno);
+
+ // arg w, line
+ Ins arg_i;
+ arg_i.op = Oarg;
+ arg_i.cls = Kw;
+ arg_i.to = Ref::none();
+ arg_i.arg[0] = line_ref;
+ arg_i.arg[1] = Ref::none();
+ push_ins(arg_i);
+
+ // call
+ Ins call_i;
+ call_i.op = Ocall;
+ call_i.cls = Kw;
+ call_i.to = Ref::none();
+ call_i.arg[0] = fn_ref;
+ call_i.arg[1] = Ref::none();
+ push_ins(call_i);
+ return make_int_ref(0);
+ }
+
+ FuncSig* fsig = sym_.lookup_func(call_node->name);
+ bool is_void = fsig && fsig->ret_type == BType::Void;
+ int ret_cls = fsig ? btype_to_cls(fsig->ret_type) : Kw;
+
+ std::vector<Ref> arg_refs;
+ std::vector<int> arg_clses;
+ arg_refs.reserve(call_node->args.size());
+ arg_clses.reserve(call_node->args.size());
+ for (size_t i = 0; i < call_node->args.size(); i++) {
+ Ref v = visit_expr(call_node->args[i].get());
+ int acls = Kw;
+ if (fsig && i < fsig->params.size())
+ acls = fsig->params[i].second ? Kl : btype_to_cls(fsig->params[i].first);
+
+ if (acls == Kw || acls == Ks)
+ v = coerce(v, acls);
+ arg_refs.push_back(v);
+ arg_clses.push_back(acls);
+ }
+ for (size_t i = 0; i < arg_refs.size(); i++) {
+ Ins arg_i;
+ arg_i.op = Oarg;
+ arg_i.cls = static_cast<uint>(arg_clses[i]);
+ arg_i.to = Ref::none();
+ arg_i.arg[0] = arg_refs[i];
+ arg_i.arg[1] = Ref::none();
+ push_ins(arg_i);
+ }
+
+ // call instruction
+ Ref fn_ref = make_global_ref(call_node->name.c_str());
+ if (is_void) {
+ Ins call_i;
+ call_i.op = Ocall;
+ call_i.cls = Kw;
+ call_i.to = Ref::none();
+ call_i.arg[0] = fn_ref;
+ call_i.arg[1] = Ref::none();
+ push_ins(call_i);
+ cur_fn_->leaf = 0;
+ return make_int_ref(0);
+ }
+
+ Ref dst = new_tmp_ref(ret_cls);
+ Ins call_i;
+ call_i.op = Ocall;
+ call_i.cls = static_cast<uint>(ret_cls);
+ call_i.to = dst;
+ call_i.arg[0] = fn_ref;
+ call_i.arg[1] = Ref::none();
+ push_ins(call_i);
+ cur_fn_->leaf = 0;
+ return dst;
+ }
+
+ case NodeKind::ImplicitCast: {
+ auto* c = static_cast<ImplicitCastNode*>(node);
+ Ref v = visit_expr(c->operand.get());
+ int k = ref_cls_of(v, cur_fn_);
+ if (c->target_type == BType::Float && k == Kw)
+ return emit_ins(Oswtof, Ks, v, Ref::none());
+ if (c->target_type == BType::Int && k == Ks)
+ return emit_ins(Ostosi, Kw, v, Ref::none());
+ return v;
+ }
+
+ default:
+ return make_int_ref(0);
+ }
+}
+
+// Short-circuit conditional jump
+
+void IRGen::visit_cond(ASTNode* cond, Blk* true_bb, Blk* false_bb) {
+ if (auto* bin = dynamic_cast<BinaryExprNode*>(cond)) {
+ if (bin->op == BinOp::And)
+ return visit_cond_and(bin, true_bb, false_bb);
+ if (bin->op == BinOp::Or)
+ return visit_cond_or(bin, true_bb, false_bb);
+ }
+ Ref val = visit_expr(cond);
+ emit_jnz(val, true_bb, false_bb);
+}
+
+void IRGen::visit_cond_and(BinaryExprNode* node, Blk* true_bb, Blk* false_bb) {
+ Blk* rhs_bb = new_blk("and.rhs");
+ visit_cond(node->lhs.get(), rhs_bb, false_bb);
+ seal_block(rhs_bb);
+ cur_blk_ = rhs_bb;
+ visit_cond(node->rhs.get(), true_bb, false_bb);
+}
+
+void IRGen::visit_cond_or(BinaryExprNode* node, Blk* true_bb, Blk* false_bb) {
+ Blk* rhs_bb = new_blk("or.rhs");
+ visit_cond(node->lhs.get(), true_bb, rhs_bb);
+ seal_block(rhs_bb);
+ cur_blk_ = rhs_bb;
+ visit_cond(node->rhs.get(), true_bb, false_bb);
+}
+
+} // namespace rcc
diff --git a/rcc-legacy/src/ir/irgen_util.cc b/rcc-legacy/src/ir/irgen_util.cc
new file mode 100644
index 0000000..e7e89f5
--- /dev/null
+++ b/rcc-legacy/src/ir/irgen_util.cc
@@ -0,0 +1,244 @@
+/* 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/>. */
+/// @file irgen_util.cc
+/// @brief Constant evaluation, array init flattening, array address computation
+
+#include "rcc/irgen.h"
+
+#include <cstddef>
+
+namespace rcc {
+
+// Compile-time constant evaluation
+
+int32_t IRGen::eval_const_int(ASTNode* node) {
+ switch (node->kind) {
+ case NodeKind::IntLiteral:
+ return static_cast<IntLiteralNode*>(node)->value;
+ case NodeKind::FloatLiteral:
+ return static_cast<int32_t>(static_cast<FloatLiteralNode*>(node)->value);
+ case NodeKind::LVal: {
+ auto* lv = static_cast<LValNode*>(node);
+ Symbol* sym = sym_.lookup(lv->name);
+ if (sym && sym->is_const && !sym->is_array) {
+ if (auto* iv = std::get_if<int32_t>(&sym->const_val))
+ return *iv;
+ if (auto* fv = std::get_if<float>(&sym->const_val))
+ return static_cast<int32_t>(*fv);
+ }
+ return 0;
+ }
+ case NodeKind::UnaryExpr: {
+ auto* u = static_cast<UnaryExprNode*>(node);
+ int32_t v = eval_const_int(u->operand.get());
+ if (u->op == UnaryOp::Neg)
+ return -v;
+ if (u->op == UnaryOp::Not)
+ return !v;
+ return v;
+ }
+ case NodeKind::BinaryExpr: {
+ auto* b = static_cast<BinaryExprNode*>(node);
+ int32_t l = eval_const_int(b->lhs.get());
+ int32_t r = eval_const_int(b->rhs.get());
+ switch (b->op) {
+ case BinOp::Add:
+ return l + r;
+ case BinOp::Sub:
+ return l - r;
+ case BinOp::Mul:
+ return l * r;
+ case BinOp::Div:
+ return r ? l / r : 0;
+ case BinOp::Mod:
+ return r ? l % r : 0;
+ case BinOp::Lt:
+ return l < r;
+ case BinOp::Gt:
+ return l > r;
+ case BinOp::Le:
+ return l <= r;
+ case BinOp::Ge:
+ return l >= r;
+ case BinOp::Eq:
+ return l == r;
+ case BinOp::Ne:
+ return l != r;
+ case BinOp::And:
+ return l && r;
+ case BinOp::Or:
+ return l || r;
+ }
+ return 0;
+ }
+ default:
+ return 0;
+ }
+}
+
+float IRGen::eval_const_float(ASTNode* node) {
+ switch (node->kind) {
+ case NodeKind::FloatLiteral:
+ return static_cast<FloatLiteralNode*>(node)->value;
+ case NodeKind::IntLiteral:
+ return static_cast<float>(static_cast<IntLiteralNode*>(node)->value);
+ case NodeKind::LVal: {
+ auto* lv = static_cast<LValNode*>(node);
+ Symbol* sym = sym_.lookup(lv->name);
+ if (sym && sym->is_const && !sym->is_array) {
+ if (auto* fv = std::get_if<float>(&sym->const_val))
+ return *fv;
+ if (auto* iv = std::get_if<int32_t>(&sym->const_val))
+ return static_cast<float>(*iv);
+ }
+ return 0.0f;
+ }
+ case NodeKind::UnaryExpr: {
+ auto* u = static_cast<UnaryExprNode*>(node);
+ float v = eval_const_float(u->operand.get());
+ if (u->op == UnaryOp::Neg)
+ return -v;
+ return v;
+ }
+ case NodeKind::BinaryExpr: {
+ auto* b = static_cast<BinaryExprNode*>(node);
+ float l = eval_const_float(b->lhs.get());
+ float r = eval_const_float(b->rhs.get());
+ switch (b->op) {
+ case BinOp::Add:
+ return l + r;
+ case BinOp::Sub:
+ return l - r;
+ case BinOp::Mul:
+ return l * r;
+ case BinOp::Div:
+ return r != 0.0f ? l / r : 0.0f;
+ default:
+ return 0.0f;
+ }
+ }
+ default:
+ return 0.0f;
+ }
+}
+
+// Array initialization flattening
+
+void IRGen::flatten_init(const std::vector<int>& dims, ASTNode* init, std::vector<ASTNode*>& out) {
+ if (!init)
+ return;
+
+ if (init->kind != NodeKind::InitList) {
+ out.push_back(init);
+ return;
+ }
+
+ auto* list = static_cast<InitListNode*>(init);
+ if (dims.empty())
+ return;
+
+ int sub_size = 1;
+ for (size_t i = 1; i < dims.size(); i++)
+ sub_size *= dims[i];
+
+ int total = dims[0] * sub_size;
+ int pos = 0;
+
+ for (auto& item : list->items) {
+ if (pos >= total)
+ break;
+
+ if (item->kind == NodeKind::InitList) {
+ if (dims.size() > 1) {
+ std::vector<int> sub_dims(dims.begin() + 1, dims.end());
+ int before = static_cast<int>(out.size());
+ flatten_init(sub_dims, item.get(), out);
+ int filled = static_cast<int>(out.size()) - before;
+ while (filled < sub_size) {
+ out.push_back(nullptr);
+ filled++;
+ }
+ pos += sub_size;
+ } else {
+ auto* inner = static_cast<InitListNode*>(item.get());
+ for (auto& elem : inner->items) {
+ if (pos < total) {
+ out.push_back(elem.get());
+ pos++;
+ }
+ }
+ }
+ } else {
+ out.push_back(item.get());
+ pos++;
+ }
+ }
+
+ while (pos < total) {
+ out.push_back(nullptr);
+ pos++;
+ }
+}
+
+// Array address calculation
+
+Ref IRGen::compute_array_addr(LValNode* node) {
+ Symbol* sym = sym_.lookup(node->name);
+ Ref base = get_var_addr_ref(sym);
+
+ if (sym->is_param && sym->is_array)
+ base = read_variable(sym->uid, cur_blk_, Kl);
+
+ const auto& dims = sym->dims;
+ size_t ndims = dims.size();
+
+ Ref offset = Ref::none();
+ bool offset_valid = false;
+
+ for (size_t i = 0; i < node->indices.size(); i++) {
+ Ref idx = visit_expr(node->indices[i].get());
+
+ int stride = 1;
+ size_t dim_idx = sym->is_param ? i : (i + 1);
+ for (size_t j = dim_idx; j < ndims; j++)
+ stride *= dims[j];
+
+ // Extend index to 'l' type for pointer arithmetic
+ int idx_cls = Kw;
+ if (rtype(idx) == RTmp && idx.val >= Tmp0)
+ idx_cls = cur_fn_->tmp[idx.val].cls;
+ if (idx_cls == Kw)
+ idx = emit_ins(Oextsw, Kl, idx, Ref::none());
+
+ Ref byte_off = emit_ins(Omul, Kl, idx, make_int_ref(stride * 4));
+
+ if (offset_valid) {
+ offset = emit_ins(Oadd, Kl, offset, byte_off);
+ } else {
+ offset = byte_off;
+ offset_valid = true;
+ }
+ }
+
+ if (offset_valid)
+ return emit_ins(Oadd, Kl, base, offset);
+ return base;
+}
+
+} // namespace rcc
diff --git a/rcc-legacy/src/ir/print.cc b/rcc-legacy/src/ir/print.cc
new file mode 100644
index 0000000..d452a6b
--- /dev/null
+++ b/rcc-legacy/src/ir/print.cc
@@ -0,0 +1,388 @@
+/* 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/>. */
+
+/// @file print.cc
+/// @brief IR text output: Fn / Blk / Phi / Ins / Jmp / Con / Ref
+
+#include "rcc/all.h"
+#include "rcc/irgen.h"
+
+#include <cinttypes>
+
+namespace rcc {
+
+namespace {
+
+/// K class characters: Kw='w' Kl='l' Ks='s' Kd='d'. Others are treated as errors.
+char cls_char(int k) {
+ switch (k) {
+ case Kw:
+ return 'w';
+ case Kl:
+ return 'l';
+ case Ks:
+ return 's';
+ case Kd:
+ return 'd';
+ default:
+ return '?';
+ }
+}
+
+const char* jmp_name(int j) {
+ static constexpr auto tab = [] {
+ std::array<const char*, NJmp> a{};
+#define X(n) a[J##n] = #n;
+ JMPS(X)
+#undef X
+ return a;
+ }();
+ if (j <= Jxxx || j >= NJmp)
+ return "??";
+ return tab[static_cast<size_t>(j)];
+}
+
+/// Mem (= Addr) internal addressing component output: [ offset + base + scale*index ]
+void print_mem(Mem* m, Fn* fn, FILE* f);
+
+bool op_needs_cls_suffix(int op) {
+ switch (op) {
+ case Oarg:
+ case Oswap:
+ case Oxcmp:
+ case Oacmp:
+ case Oacmn:
+ case Oafcmp:
+ case Oxtest:
+ case Oxdiv:
+ case Oxidiv:
+ return true;
+ default:
+ return false;
+ }
+}
+
+} // namespace
+
+// printcon
+
+static void printcon(Con* c, FILE* f) {
+ switch (c->type) {
+ case Con::CUndef:
+ return;
+ case Con::CAddr:
+ if (c->sym.type & Sym::SExt)
+ fputs("extern ", f);
+ if (c->sym.type & Sym::SThr)
+ fputs("thread ", f);
+ fprintf(f, "$%s", str(c->sym.id));
+ if (c->bits.i)
+ fprintf(f, "%+" PRId64, c->bits.i);
+ return;
+ case Con::CBits:
+ if (c->flt == 1)
+ fprintf(f, "s_%f", static_cast<double>(c->bits.s));
+ else if (c->flt == 2)
+ fprintf(f, "d_%lf", c->bits.d);
+ else
+ fprintf(f, "%" PRId64, c->bits.i);
+ return;
+ }
+}
+
+// printref
+
+void printref(Ref r, Fn* fn, FILE* f) {
+ switch (rtype(r)) {
+ case RTmp:
+ if (r.val < Tmp0)
+ fprintf(f, "R%u", r.val);
+ else
+ fprintf(f, "%%%s", fn->tmp[r.val].name);
+ return;
+
+ case RCon:
+ if (req(r, Ref::undef()))
+ fputs("UNDEF", f);
+ else
+ printcon(&fn->con[r.val], f);
+ return;
+
+ case RSlot:
+ fprintf(f, "S%d", rsval(r));
+ return;
+
+ case RCall:
+ fprintf(f, "%04x", r.val);
+ return;
+
+ case RType:
+ fprintf(f, ":%s", typ[r.val].name);
+ return;
+
+ case RMem:
+ print_mem(&fn->mem[r.val], fn, f);
+ return;
+
+ case RInt:
+ fprintf(f, "%d", rsval(r));
+ return;
+
+ case -1:
+ fputc('R', f);
+ return;
+ }
+}
+
+namespace {
+
+void print_mem(Mem* m, Fn* fn, FILE* f) {
+ fputc('[', f);
+ bool need_plus = false;
+ if (m->offset.type != Con::CUndef) {
+ printcon(&m->offset, f);
+ need_plus = true;
+ }
+ if (!req(m->base, Ref::none())) {
+ if (need_plus)
+ fputs(" + ", f);
+ printref(m->base, fn, f);
+ need_plus = true;
+ }
+ if (!req(m->index, Ref::none())) {
+ if (need_plus)
+ fputs(" + ", f);
+ fprintf(f, "%d * ", m->scale);
+ printref(m->index, fn, f);
+ }
+ fputc(']', f);
+}
+
+// Phi / Ins / Jmp single-line output
+
+void print_phi_line(Phi* p, Fn* fn, FILE* f) {
+ fputc('\t', f);
+ printref(p->to, fn, f);
+ fprintf(f, " =%c phi ", cls_char(p->cls));
+ assert(p->narg && "phi has no arg");
+ for (uint n = 0; n < p->narg; n++) {
+ fprintf(f, "@%s ", p->blk[n]->name);
+ printref(p->arg[n], fn, f);
+ if (n + 1 < p->narg)
+ fputs(", ", f);
+ }
+ fputc('\n', f);
+}
+
+void print_ins_line(Ins* i, Fn* fn, FILE* f) {
+ fputc('\t', f);
+ if (!req(i->to, Ref::none())) {
+ printref(i->to, fn, f);
+ fprintf(f, " =%c ", cls_char(static_cast<int>(i->cls)));
+ }
+ assert(optab[i->op].name && "missing op name in optab");
+ fputs(optab[i->op].name, f);
+
+ if (req(i->to, Ref::none()) && op_needs_cls_suffix(i->op))
+ fputc(cls_char(static_cast<int>(i->cls)), f);
+
+ if (!req(i->arg[0], Ref::none())) {
+ fputc(' ', f);
+ printref(i->arg[0], fn, f);
+ }
+ if (!req(i->arg[1], Ref::none())) {
+ fputs(", ", f);
+ printref(i->arg[1], fn, f);
+ }
+ fputc('\n', f);
+}
+
+/// Terminating instructions: ret / jmp / jnz / floating-point conditional jump / hlt
+void print_jmp_line(Blk* b, Fn* fn, FILE* f) {
+ int jt = b->jmp.type;
+
+ if (isret(jt)) {
+ fputs("\tret", f);
+ if (jt != Jret0 || !req(b->jmp.arg, Ref::none())) {
+ fputc(' ', f);
+ printref(b->jmp.arg, fn, f);
+ }
+ fputc('\n', f);
+ return;
+ }
+
+ if (jt == Jhlt) {
+ fputs("\thlt\n", f);
+ return;
+ }
+
+ if (jt == Jjmp) {
+ // If fall-through to next block, jmp can be omitted (QBE output style)
+ if (b->s1 != b->link)
+ fprintf(f, "\tjmp @%s\n", b->s1->name);
+ return;
+ }
+
+ // jnz or jf* series conditional jumps: two successors
+ fprintf(f, "\t%s ", jmp_name(jt));
+ if (jt == Jjnz) {
+ printref(b->jmp.arg, fn, f);
+ fputs(", ", f);
+ }
+ assert(b->s1 && b->s2 && "branch missing successor");
+ fprintf(f, "@%s, @%s\n", b->s1->name, b->s2->name);
+}
+
+void print_call(Ins* call, Ins** args, size_t nargs, Fn* fn, FILE* f) {
+ fputc('\t', f);
+ if (!req(call->to, Ref::none())) {
+ printref(call->to, fn, f);
+ fprintf(f, " =%c ", cls_char(static_cast<int>(call->cls)));
+ }
+ fputs("call ", f);
+ printref(call->arg[0], fn, f);
+ fputc('(', f);
+ for (size_t k = 0; k < nargs; k++) {
+ if (k)
+ fputs(", ", f);
+ Ins* a = args[k];
+ fprintf(f, "%c ", cls_char(static_cast<int>(a->cls)));
+ printref(a->arg[0], fn, f);
+ }
+ fputs(")\n", f);
+}
+
+void print_block(Blk* b, Fn* fn, FILE* f) {
+ fprintf(f, "@%s\n", b->name);
+ for (Phi* p = b->phi; p; p = p->link)
+ print_phi_line(p, fn, f);
+
+ // Scan: skip Opar at entry block start (already in signature); Oarg sequence is merged and
+ // printed when encountering Ocall, and call itself is output at the merge point.
+ Ins* pending_args[NIns];
+ size_t npending = 0;
+ for (uint k = 0; k < b->nins; k++) {
+ Ins* i = &b->ins[k];
+ if (i->op == Onop)
+ continue; // promote/coalesce marks instructions as nop to "delete" them, QBE text
+ // doesn't accept nop keyword
+ if (i->op == Opar)
+ continue;
+ if (i->op == Oarg) {
+ assert(npending < NIns && "too many call args");
+ pending_args[npending++] = i;
+ continue;
+ }
+ if (i->op == Ocall) {
+ print_call(i, pending_args, npending, fn, f);
+ npending = 0;
+ continue;
+ }
+ // There should be no dangling Oarg before normal instructions
+ assert(npending == 0 && "orphan arg before non-call op");
+ print_ins_line(i, fn, f);
+ }
+ assert(npending == 0 && "orphan arg at end of block");
+
+ print_jmp_line(b, fn, f);
+}
+
+void print_signature(Fn* fn, FILE* f) {
+ fputs("export\nfunction ", f);
+
+ int ret_cls = Kx;
+ for (Blk* b = fn->start; b; b = b->link) {
+ int jt = b->jmp.type;
+ if (!isret(jt))
+ continue;
+ if (jt >= Jretw && jt <= Jretd)
+ ret_cls = jt - Jretw; // Jretw..Jretd align with Kw..Kd
+ break;
+ }
+ if (ret_cls >= 0)
+ fprintf(f, "%c ", cls_char(ret_cls));
+ fprintf(f, "$%s(", fn->name);
+
+ // Scan Opar in entry to collect parameters
+ Blk* entry = fn->start;
+ bool first = true;
+ if (entry) {
+ for (uint k = 0; k < entry->nins; k++) {
+ Ins* i = &entry->ins[k];
+ if (i->op != Opar) // Opar are contiguous at entry head, break on first non-Opar
+ break;
+ if (!first)
+ fputs(", ", f);
+ fprintf(f, "%c ", cls_char(static_cast<int>(i->cls)));
+ printref(i->to, fn, f);
+ first = false;
+ }
+ }
+ fputs(") {\n", f);
+}
+
+} // namespace
+
+// printfn - Top-level assembly
+
+void printfn(Fn* fn, FILE* f) {
+ print_signature(fn, f);
+ for (Blk* b = fn->start; b; b = b->link)
+ print_block(b, fn, f);
+ fputs("}\n", f);
+}
+
+// print_globals - Data segment
+
+namespace {
+
+constexpr size_t ELEM_BYTES = 4; // Kw / Ks are both 4 bytes
+
+void print_global_cell(int elem_cls, const std::variant<int32_t, float>& v, FILE* f) {
+ if (elem_cls == Ks) {
+ // QBE floating-point constant literal s_<float>
+ float fv = std::holds_alternative<float>(v) ? std::get<float>(v)
+ : static_cast<float>(std::get<int32_t>(v));
+ fprintf(f, "s s_%f", static_cast<double>(fv));
+ } else {
+ int32_t iv = std::holds_alternative<int32_t>(v) ? std::get<int32_t>(v)
+ : static_cast<int32_t>(std::get<float>(v));
+ fprintf(f, "w %d", iv);
+ }
+}
+
+} // namespace
+
+void print_globals(const std::vector<GlobalData>& globals, FILE* f) {
+ for (const auto& g : globals) {
+ fprintf(f, "data $%s = align 4 { ", g.name.c_str());
+ if (g.is_zeroinit) {
+ fprintf(f, "z %zu", g.total_elems * ELEM_BYTES);
+ } else {
+ for (size_t i = 0; i < g.init_vals.size(); i++) {
+ if (i)
+ fputs(", ", f);
+ print_global_cell(g.elem_cls, g.init_vals[i], f);
+ }
+ }
+ fputs(" }\n", f);
+ }
+}
+
+} // namespace rcc