diff options
| author | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-06-08 17:00:52 +0800 |
|---|---|---|
| committer | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-06-08 17:00:52 +0800 |
| commit | 71564eb6b92d3e0a99d4061980afd8b51227906c (patch) | |
| tree | aaa38eb0c58dc25467975dc224590d6541a77faf /rcc-legacy | |
| parent | c2995a5b95a1e04620ad1fc94ed19186234388ca (diff) | |
| download | rcc-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.cc | 728 | ||||
| -rw-r--r-- | rcc-legacy/src/ir/irgen_expr.cc | 328 | ||||
| -rw-r--r-- | rcc-legacy/src/ir/irgen_util.cc | 244 | ||||
| -rw-r--r-- | rcc-legacy/src/ir/print.cc | 388 |
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 |
