/* 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 backend/emit.cc /// @brief Target-independent assembly/data/debug section output tools. #include "rcc/all.h" #include #include #include namespace rcc { // Section types. namespace { enum Sec : int { kSecText = 0, kSecData = 1, kSecBss = 2 }; // Given (sec, thread), return the name/instruction string after .section. // When thread=true, SecText is unreachable. const char* section_name(int sec, bool thread) { if (!thread) { switch (sec) { case kSecText: return ".text"; case kSecData: return ".data"; case kSecBss: return ".bss"; } } else { switch (sec) { case kSecText: return ".abort \"unreachable\""; case kSecData: return ".section .tdata,\"awT\""; case kSecBss: return ".section .tbss,\"awT\""; } } return nullptr; // unreachable } // Write ".globl / label / section switch" header. Apple branch has TLV trampolines. void write_lnk(char* n, Lnk* l, int sec_kind, FILE* f) { const char* pfx = (n[0] == '"') ? "" : T.assym; const char* sfx = ""; if (T.apple && l->thread) { l->sec = const_cast("__DATA"); l->secf = const_cast("__thread_data,thread_local_regular"); sfx = "$tlv$init"; fputs(".section __DATA,__thread_vars,thread_local_variables\n", f); fprintf(f, "%s%s:\n", pfx, n); fprintf(f, "\t.quad __tlv_bootstrap\n" "\t.quad 0\n" "\t.quad %s%s%s\n\n", pfx, n, sfx); } if (l->sec) { fprintf(f, ".section %s", l->sec); if (l->secf) fprintf(f, ",%s", l->secf); } else { fputs(section_name(sec_kind, l->thread != 0), f); } fputc('\n', f); if (l->align) fprintf(f, ".balign %d\n", l->align); if (l->export_) fprintf(f, ".globl %s%s\n", pfx, n); fprintf(f, "%s%s%s:\n", pfx, n, sfx); } } // namespace void emitfnlnk(char* n, Lnk* l, FILE* f) { write_lnk(n, l, kSecText, f); } // Data sections. namespace { int64_t g_data_zero = 0; struct DataDecl { const char* decl; int64_t mask; }; constexpr DataDecl kDataDecl[] = { /* DStart */ {nullptr, 0}, /* DEnd */ {nullptr, 0}, /* DB */ {"\t.byte", 0xffLL}, /* DH */ {"\t.short", 0xffffLL}, /* DW */ {"\t.int", 0xffffffffLL}, /* DL */ {"\t.quad", -1LL}, /* DZ */ {nullptr, 0}, }; } // namespace void emitdat(Dat* d, FILE* f) { switch (d->type) { case Dat::DStart: g_data_zero = 0; return; case Dat::DEnd: if (d->lnk->common) { if (g_data_zero == -1) die("invalid common data definition"); const char* p = (d->name[0] == '"') ? "" : T.assym; fprintf(f, ".comm %s%s,%" PRId64, p, d->name, g_data_zero); if (d->lnk->align) fprintf(f, ",%d", d->lnk->align); fputc('\n', f); } else if (g_data_zero != -1) { write_lnk(d->name, d->lnk, kSecBss, f); fprintf(f, "\t.fill %" PRId64 ",1,0\n", g_data_zero); } return; case Dat::DZ: if (g_data_zero != -1) g_data_zero += d->u.num; else fprintf(f, "\t.fill %" PRId64 ",1,0\n", d->u.num); return; case Dat::DB: case Dat::DH: case Dat::DW: case Dat::DL: break; } // Switch to .data and flush accumulated zeros on first non-zero content. if (g_data_zero != -1) { write_lnk(d->name, d->lnk, kSecData, f); if (g_data_zero > 0) fprintf(f, "\t.fill %" PRId64 ",1,0\n", g_data_zero); g_data_zero = -1; } const DataDecl& dd = kDataDecl[d->type]; if (d->isstr) { if (d->type != Dat::DB) err(const_cast("strings only supported for 'b' currently")); fprintf(f, "\t.ascii %s\n", d->u.str); } else if (d->isref) { const char* p = (d->u.ref.name[0] == '"') ? "" : T.assym; fprintf(f, "%s %s%s%+" PRId64 "\n", dd.decl, p, d->u.ref.name, d->u.ref.off); } else { fprintf(f, "%s %" PRId64 "\n", dd.decl, d->u.num & dd.mask); } } // FP constant pool (stashbits + emitfin). struct Asmbits { bits n; int size; Asmbits* link; }; int stashbits(Ctx* ctx, bits n, int size) { assert(size == 4 || size == 8 || size == 16); Asmbits** pb; Asmbits* b; int i = 0; for (pb = &ctx->emit_stash; (b = *pb); pb = &b->link, ++i) { if (size <= b->size && b->n == n) return i; } b = static_cast(emalloc(sizeof(Asmbits))); b->n = n; b->size = size; b->link = nullptr; *pb = b; return i; } namespace { // Layout by .quad / .quad 0 (16B) / .quad (8B) / .int (4B). void emit_fp_pool(Ctx* ctx, FILE* f, const char* sec[3]) { if (!ctx->emit_stash) return; fprintf(f, "/* floating point constants */\n"); union { int32_t i; float f; } u; // Three passes from largest to smallest (16, 8, 4). for (int lg = 4; lg >= 2; --lg) { int width = 1 << lg; int i = 0; for (Asmbits* b = ctx->emit_stash; b; b = b->link, ++i) { if (b->size != width) continue; fprintf(f, ".section %s\n" ".p2align %d\n" "%sfp%d:", sec[lg - 2], lg, T.asloc, i); if (lg == 4) { fprintf(f, "\n\t.quad %" PRId64 "\n\t.quad 0\n\n", static_cast(b->n)); } else if (lg == 3) { double dv; memcpy(&dv, &b->n, sizeof(dv)); fprintf(f, "\n\t.quad %" PRId64 " /* %f */\n\n", static_cast(b->n), dv); } else /* lg == 2 */ { u.i = static_cast(b->n); fprintf(f, "\n\t.int %" PRId32 " /* %f */\n\n", u.i, static_cast(u.f)); } } } // Free linked list. while (Asmbits* b = ctx->emit_stash) { ctx->emit_stash = b->link; free(b); } } } // namespace void elf_emitfin(Ctx* ctx, FILE* f) { static const char* sec[3] = {".rodata", ".rodata", ".rodata"}; emit_fp_pool(ctx, f, sec); fprintf(f, ".section .note.GNU-stack,\"\",@progbits\n"); } void elf_emitfnfin(char* fn, FILE* f) { fprintf(f, ".type %s, @function\n", fn); fprintf(f, ".size %s, .-%s\n", fn, fn); } void macho_emitfin(Ctx* ctx, FILE* f) { static const char* sec[3] = { "__TEXT,__literal4,4byte_literals", "__TEXT,__literal8,8byte_literals", "__TEXT,__literal16,16byte_literals", }; emit_fp_pool(ctx, f, sec); } void pe_emitfin(Ctx* ctx, FILE* f) { static const char* sec[3] = {".rodata", ".rodata", ".rodata"}; emit_fp_pool(ctx, f, sec); } // DWARF .file / .loc. namespace { std::vector g_dbg_files; // intern id sequence. uint32_t g_dbg_curfile = 0; // 1-based (gas requires > 0). } // namespace void emitdbgfile(char* fn, FILE* f) { uint32_t id = intern(fn); for (size_t i = 0; i < g_dbg_files.size(); ++i) { if (g_dbg_files[i] == id) { g_dbg_curfile = static_cast(i + 1); return; } } g_dbg_files.push_back(id); g_dbg_curfile = static_cast(g_dbg_files.size()); fprintf(f, ".file %u %s\n", g_dbg_curfile, fn); } void emitdbgloc(uint line, uint col, FILE* f) { if (col != 0) fprintf(f, "\t.loc %u %u %u\n", g_dbg_curfile, line, col); else fprintf(f, "\t.loc %u %u\n", g_dbg_curfile, line); } } // namespace rcc