From f4d6a3896b94ce7471dcc737684b60acf9e46f37 Mon Sep 17 00:00:00 2001 From: nella Date: Sun, 8 Nov 2026 14:18:00 +0100 Subject: [PATCH 1/3] Describe types. --- src/backend/ir/debug_info.cpp | 9 ++ src/backend/ir/debug_info.h | 47 +++++++++ src/backend/target/dwarf.cpp | 154 +++++++++++++++++++++++++++++- src/backend/target/dwarf.h | 17 +++- src/compiler/emit/emit.h | 6 ++ src/compiler/emit/emit_debug.cpp | 94 ++++++++++++++++++ src/compiler/emit/emit_global.cpp | 1 + src/compiler/emit/emit_state.h | 8 +- 8 files changed, 330 insertions(+), 6 deletions(-) diff --git a/src/backend/ir/debug_info.cpp b/src/backend/ir/debug_info.cpp index 1691078f..24833889 100644 --- a/src/backend/ir/debug_info.cpp +++ b/src/backend/ir/debug_info.cpp @@ -1,6 +1,15 @@ #include "ir/debug_info.h" namespace rat { + DebugType* DebugInfo::makeType(DebugType::Kind kind, String name, U32 size) { + types.push_back(std::make_unique()); + DebugType* t = types.back().get(); + t->kind = kind; + t->name = std::move(name); + t->size = size; + return t; + } + // consecutive nodes mostly share a location, reuse the last entry then U32 DebugBody::addLoc(SrcLoc src) { const DebugLoc& last = locs.back(); diff --git a/src/backend/ir/debug_info.h b/src/backend/ir/debug_info.h index f512fc27..ea0f1d28 100644 --- a/src/backend/ir/debug_info.h +++ b/src/backend/ir/debug_info.h @@ -11,10 +11,53 @@ namespace rat { U32 col = 0; }; + struct DebugType { + enum class Kind : U8 { + Signed, + Unsigned, + SignedChar, + UnsignedChar, + Bool, + Float, + ComplexFloat, + Pointer, + Struct, + Union, + Array, + Function + }; + struct Member { + String name; + DebugType* type; + U32 offset; // bytes + U32 bitSize = 0; // 0 = not a bitfield + U32 bitOffset = 0; + }; + + Kind kind; + String name; + U32 size = 0; + DebugType* elem = nullptr; // pointee, element or return type, null = void + U64 count = 0; // array length, 0 = unknown + List members; + List params; + B32 variadic = false; + B32 incomplete = false; // declared struct without a body + }; + + // a source variable + struct DebugVar { + String name; + DebugType* type; + SrcLoc decl; + String symbol; // static storage, else empty + }; + // a source function, outlives its IR function so inlined copies can still name it struct DebugFunc { String name; SrcLoc decl; + DebugType* ret = nullptr; B32 external = false; B32 variadic = false; }; @@ -35,7 +78,11 @@ namespace rat { String producer; String compDir; List files; // files[0] is the primary source + List globals; List> funcs; + List> types; + + DebugType* makeType(DebugType::Kind kind, String name, U32 size); }; } // namespace rat diff --git a/src/backend/target/dwarf.cpp b/src/backend/target/dwarf.cpp index 42a2d6bf..2cf5e599 100644 --- a/src/backend/target/dwarf.cpp +++ b/src/backend/target/dwarf.cpp @@ -5,11 +5,24 @@ namespace rat { namespace detail { + constexpr U32 DW_TAG_array_type = 0x01; + constexpr U32 DW_TAG_formal_parameter = 0x05; + constexpr U32 DW_TAG_member = 0x0d; + constexpr U32 DW_TAG_pointer_type = 0x0f; constexpr U32 DW_TAG_compile_unit = 0x11; + constexpr U32 DW_TAG_structure_type = 0x13; + constexpr U32 DW_TAG_subroutine_type = 0x15; + constexpr U32 DW_TAG_union_type = 0x17; constexpr U32 DW_TAG_unspecified_parameters = 0x18; + constexpr U32 DW_TAG_subrange_type = 0x21; + constexpr U32 DW_TAG_base_type = 0x24; constexpr U32 DW_TAG_subprogram = 0x2e; + constexpr U32 DW_TAG_variable = 0x34; + constexpr U32 DW_AT_location = 0x02; constexpr U32 DW_AT_name = 0x03; + constexpr U32 DW_AT_byte_size = 0x0b; + constexpr U32 DW_AT_bit_size = 0x0d; constexpr U32 DW_AT_stmt_list = 0x10; constexpr U32 DW_AT_low_pc = 0x11; constexpr U32 DW_AT_high_pc = 0x12; @@ -17,17 +30,25 @@ namespace rat { constexpr U32 DW_AT_comp_dir = 0x1b; constexpr U32 DW_AT_producer = 0x25; constexpr U32 DW_AT_prototyped = 0x27; + constexpr U32 DW_AT_count = 0x37; + constexpr U32 DW_AT_data_member_location = 0x38; constexpr U32 DW_AT_decl_file = 0x3a; + constexpr U32 DW_AT_declaration = 0x3c; + constexpr U32 DW_AT_encoding = 0x3e; constexpr U32 DW_AT_external = 0x3f; constexpr U32 DW_AT_frame_base = 0x40; + constexpr U32 DW_AT_type = 0x49; + constexpr U32 DW_AT_data_bit_offset = 0x6b; constexpr U32 DW_FORM_addr = 0x01; constexpr U32 DW_FORM_string = 0x08; constexpr U32 DW_FORM_udata = 0x0f; + constexpr U32 DW_FORM_ref4 = 0x13; constexpr U32 DW_FORM_sec_offset = 0x17; constexpr U32 DW_FORM_exprloc = 0x18; constexpr U32 DW_FORM_flag_present = 0x19; + constexpr U8 DW_OP_addr = 0x03; constexpr U8 DW_OP_call_frame_cfa = 0x9c; constexpr U8 DW_LNS_copy = 1; @@ -53,6 +74,41 @@ namespace rat { constexpr U8 DW_UT_compile = 1; + constexpr U8 DW_ATE_boolean = 0x02; + constexpr U8 DW_ATE_complex_float = 0x03; + constexpr U8 DW_ATE_float = 0x04; + constexpr U8 DW_ATE_signed = 0x05; + constexpr U8 DW_ATE_signed_char = 0x06; + constexpr U8 DW_ATE_unsigned = 0x07; + constexpr U8 DW_ATE_unsigned_char = 0x08; + + // per base DebugType kind + constexpr U8 kBaseEncoding[] = { + DW_ATE_signed, + DW_ATE_unsigned, + DW_ATE_signed_char, + DW_ATE_unsigned_char, + DW_ATE_boolean, + DW_ATE_float, + DW_ATE_complex_float, + }; + + // die tag per DebugType kind + constexpr U32 kTypeTag[] = { + DW_TAG_base_type, + DW_TAG_base_type, + DW_TAG_base_type, + DW_TAG_base_type, + DW_TAG_base_type, + DW_TAG_base_type, + DW_TAG_base_type, + DW_TAG_pointer_type, + DW_TAG_structure_type, + DW_TAG_union_type, + DW_TAG_array_type, + DW_TAG_subroutine_type, + }; + // x86-64, rsp and the return address column constexpr U8 kPtrBytes = 8; constexpr U32 kDwarfSp = 7; @@ -75,6 +131,8 @@ namespace rat { void Die::reloc(U32 name, U32 form, const String& symbol, I64 addend) { attr(name, form); + if(form == DW_FORM_exprloc) // the address of a static + body.insert(body.end(), {1 + kPtrBytes, DW_OP_addr}); RelocKind kind = form == DW_FORM_sec_offset ? RelocKind::Abs32 : RelocKind::Abs64; relocs.push_back({(U32)body.size(), symbol, kind, addend}); body.insert(body.end(), kind == RelocKind::Abs64 ? 8 : 4, 0); @@ -86,6 +144,17 @@ namespace rat { body.insert(body.end(), expr.begin(), expr.end()); } + void Die::ref(U32 name, const void* target) { + attr(name, DW_FORM_ref4); + refs.push_back({(U32)body.size(), target}); + le::put32(body, 0); + } + + void Die::type(U32 name, const DebugType* t) { + ref(name, t); + types.push_back(t); + } + // line follows the file attr and column precedes it void Die::srcLoc(U32 fileAttr, SrcLoc loc) { udata(fileAttr, loc.file); @@ -94,10 +163,18 @@ namespace rat { udata(fileAttr - 1, loc.col); } + void describeVar(Die& d, const DebugVar& v) { + d.string(DW_AT_name, v.name); + d.srcLoc(DW_AT_decl_file, v.decl); + d.type(DW_AT_type, v.type); + } + void describeFunc(Die& d, const String& name, const DebugFunc& f, B32 external) { d.string(DW_AT_name, name); d.srcLoc(DW_AT_decl_file, f.decl); d.attr(DW_AT_prototyped, DW_FORM_flag_present); + if(f.ret) + d.type(DW_AT_type, f.ret); if(external) d.attr(DW_AT_external, DW_FORM_flag_present); } @@ -119,6 +196,11 @@ namespace rat { U32 at = (U32)info.size(); le::uleb(info, it->second); U32 bodyAt = (U32)info.size(); + for(const auto& [off, target] : d.refs) + refs.push_back({bodyAt + off, target}); + for(const DebugType* t : d.types) + if(dieOff.emplace(t, 0).second) + pendingTypes.push_back(t); for(const AsmReloc& r : d.relocs) obj.addReloc(ObjectFile::DebugInfo, bodyAt + r.offset, r.symbol, r.kind, r.addend); info.insert(info.end(), d.body.begin(), d.body.end()); @@ -133,7 +215,58 @@ namespace rat { d.udata(DW_AT_high_pc, r[0].end - r[0].start); } - // ends the children of a function + void DieWriter::typeDie(const DebugType* t) { + using Kind = DebugType::Kind; + Kind k = t->kind; + Die d; + if(!t->name.empty()) + d.string(DW_AT_name, t->name); + if(k <= Kind::ComplexFloat) + d.udata(DW_AT_encoding, kBaseEncoding[(U32)k]); + if(t->incomplete) + d.attr(DW_AT_declaration, DW_FORM_flag_present); + else if(k <= Kind::Union) + d.udata(DW_AT_byte_size, t->size); + if(k == Kind::Function) + d.attr(DW_AT_prototyped, DW_FORM_flag_present); + if(t->elem) + d.type(DW_AT_type, t->elem); + B32 children = !t->members.empty() || k == Kind::Array || !t->params.empty() || t->variadic; + dieOff[t] = emit(kTypeTag[(U32)k], children, d); + if(children) + typeChildren(t); + } + + // members, the array subrange or params + void DieWriter::typeChildren(const DebugType* t) { + for(const DebugType::Member& m : t->members) { + Die member; + if(!m.name.empty()) + member.string(DW_AT_name, m.name); + member.type(DW_AT_type, m.type); + if(m.bitSize) { + member.udata(DW_AT_data_bit_offset, (U64)m.offset * 8 + m.bitOffset); + member.udata(DW_AT_bit_size, m.bitSize); + } else { + member.udata(DW_AT_data_member_location, m.offset); + } + emit(DW_TAG_member, false, member); + } + if(t->kind == DebugType::Kind::Array) { + Die range; + if(t->count) + range.udata(DW_AT_count, t->count); + emit(DW_TAG_subrange_type, false, range); + } + for(const DebugType* p : t->params) { + Die param; + param.type(DW_AT_type, p); + emit(DW_TAG_formal_parameter, false, param); + } + endChildren(t->variadic, true); + } + + // ends the children of a function or type void DieWriter::endChildren(B32 variadic, B32 children) { if(variadic) emit(DW_TAG_unspecified_parameters, false, {}); @@ -141,6 +274,18 @@ namespace rat { info.push_back(0); } + void DieWriter::globalDie(const DebugVar& v) { + const Global* g = mod.getGlobal(v.symbol); + if(!g) + return; + Die d; + describeVar(d, v); + if(!g->isInternal()) + d.attr(DW_AT_external, DW_FORM_flag_present); + d.reloc(DW_AT_location, DW_FORM_exprloc, v.symbol, 0); + emit(DW_TAG_variable, false, d); + } + void DieWriter::funcDie(const EncodedFunc& func) { const DebugFunc& f = *func.fn->getDebug()->func; Die d; @@ -167,12 +312,19 @@ namespace rat { cu.reloc(DW_AT_stmt_list, DW_FORM_sec_offset, obj.sectionSymbol(ObjectFile::DebugLine), 0); emit(DW_TAG_compile_unit, true, cu); + for(const DebugVar& g : di.globals) + globalDie(g); for(const EncodedFunc& func : funcs) if(func.fn->getDebug()) funcDie(func); + // types last, emitting one may queue more + for(U32 i = 0; i < pendingTypes.size(); ++i) + typeDie(pendingTypes[i]); info.push_back(0); abbrev.push_back(0); + for(const auto& [at, target] : refs) + le::patch32(info, at, dieOff[target]); le::patch32(info, 0, (U32)info.size() - 4); } diff --git a/src/backend/target/dwarf.h b/src/backend/target/dwarf.h index 522650f0..fd47bf98 100644 --- a/src/backend/target/dwarf.h +++ b/src/backend/target/dwarf.h @@ -48,17 +48,22 @@ namespace rat { struct Die { List decl; // attr and form pairs List body; - List relocs; // offsets in body + List> refs; // ref4 slots in body to the die of a type + List types; // referenced types, emitted later + List relocs; // offsets in body void attr(U32 name, U32 form); void string(U32 name, const String& s); void udata(U32 name, U64 v); - // an addr or a sec_offset + // an addr, a sec_offset or an exprloc naming a static void reloc(U32 name, U32 form, const String& symbol, I64 addend); void exprloc(U32 name, const List& expr); + void ref(U32 name, const void* target); + void type(U32 name, const DebugType* t); void srcLoc(U32 fileAttr, SrcLoc loc); }; + void describeVar(Die& d, const DebugVar& v); void describeFunc(Die& d, const String& name, const DebugFunc& f, B32 external); struct DieWriter { @@ -70,11 +75,17 @@ namespace rat { ObjectFile& obj; List info; List abbrev; - Map abbrevCode; // encoded declaration -> code + Map abbrevCode; // encoded declaration -> code + Map dieOff; // type -> its die, 0 = queued + List> refs; // ref4 slots in info to patch + List pendingTypes; U32 emit(U32 tag, B32 children, const Die& d); void pcRanges(Die& d, const List& r, U32 textOff); + void typeDie(const DebugType* t); + void typeChildren(const DebugType* t); void endChildren(B32 variadic, B32 children); + void globalDie(const DebugVar& v); void funcDie(const EncodedFunc& func); void unit(const List& funcs); }; diff --git a/src/compiler/emit/emit.h b/src/compiler/emit/emit.h index c815a5e0..f256ffb1 100644 --- a/src/compiler/emit/emit.h +++ b/src/compiler/emit/emit.h @@ -342,8 +342,14 @@ namespace rat::cc { // debug info, the hooks do nothing without -g SrcLoc debugLoc(U32 tok); + DebugType* debugType(CType t); + DebugType* debugObjectType(CType t, B32 isArray, U64 count); + DebugType* debugBase(CType t); + DebugType* debugRecord(const StructType* st); + DebugType* debugWrap(DebugType::Kind kind, DebugType* elem); void debugFunction(Function& fn, const FuncDef* def); void debugSetLoc(Function& fn, U32 tok); + void debugGlobal(const Declarator& d); // initializers static const Expr* peelAggregateCompound(const Expr* el); diff --git a/src/compiler/emit/emit_debug.cpp b/src/compiler/emit/emit_debug.cpp index a66f343a..c77bfc6f 100644 --- a/src/compiler/emit/emit_debug.cpp +++ b/src/compiler/emit/emit_debug.cpp @@ -30,6 +30,91 @@ namespace rat::cc { return {it->second, t.line, t.col}; } + DebugType* Emitter::debugType(CType t) { + using Kind = DebugType::Kind; + if(t.ptr > 0) + return debugWrap(Kind::Pointer, debugType(pointee(t))); + if(isArrayType(t)) + return debugObjectType(t.array->elem, true, t.array->count); + if(t.func) { + DebugType* f = dbg.info->makeType(Kind::Function, "", 0); + f->elem = debugType(t.func->ret); + for(const Param& p : t.func->params) + f->params.push_back(debugType(p.type)); + f->variadic = t.func->isVarArgs; + return f; + } + if(isVoidType(t)) + return nullptr; + if(isAggregate(t) && !t.isComplex()) + return debugRecord(t.strukt); + return debugBase(t); + } + + DebugType* Emitter::debugObjectType(CType t, B32 isArray, U64 count) { + DebugType* elem = debugType(t); + if(!isArray) + return elem; + DebugType* a = dbg.info->makeType(DebugType::Kind::Array, "", (U32)(count * byteSize(t))); + a->elem = elem; + a->count = count; + return a; + } + + // clang's spelling, typeName folds signed char and the long variants into others + DebugType* Emitter::debugBase(CType t) { + using Kind = DebugType::Kind; + String sign = t.isUnsigned() ? "unsigned " : ""; + CType plain = t; + plain.quals = plain.vquals = plain.rquals = 0; + String name = typeName(plain); + if(t.bits == 8 && !t.isFloat() && !t.isPlainChar()) + name = sign.empty() ? "signed char" : "unsigned char"; + else if(t.isLongLong()) + name = sign + "long long"; + else if(t.isLong() && !t.isFloat()) + name = sign + "long"; + DebugType*& b = dbg.bases[name]; + if(b) + return b; + Kind kind = t.isUnsigned() ? Kind::Unsigned : Kind::Signed; + if(t.bits == 8) + kind = t.isUnsigned() ? Kind::UnsignedChar : Kind::SignedChar; + if(t.bits == 1) + kind = Kind::Bool; + if(t.isFloat()) + kind = t.isComplex() ? Kind::ComplexFloat : Kind::Float; + b = dbg.info->makeType(kind, name, (U32)byteSize(t)); + return b; + } + + DebugType* Emitter::debugRecord(const StructType* st) { + DebugType::Kind kind = st->isUnion ? DebugType::Kind::Union : DebugType::Kind::Struct; + DebugType*& rec = dbg.types[{kind, st}]; + if(rec) + return rec; + rec = dbg.info->makeType(kind, st->tag, (U32)st->size); + rec->incomplete = !st->complete; + // anonymous struct members are already lifted into the parent at their final offsets + for(const Field& f : st->fields) { + if(!f.name) + continue; + DebugType* type = debugObjectType(f.type, f.isArray(), f.count); + U32 bits = f.isBitfield() ? f.bitWidth : 0; + rec->members.push_back({*f.name, type, (U32)f.offset, bits, f.bitOffset}); + } + return rec; + } + + DebugType* Emitter::debugWrap(DebugType::Kind kind, DebugType* elem) { + DebugType*& w = dbg.types[{kind, elem}]; + if(!w) { + w = dbg.info->makeType(kind, "", kind == DebugType::Kind::Pointer ? lay.ptrBytes : 0); + w->elem = elem; + } + return w; + } + void Emitter::debugFunction(Function& fn, const FuncDef* def) { if(!dbg.info) return; @@ -37,6 +122,7 @@ namespace rat::cc { DebugFunc& df = *dbg.info->funcs.back(); df.name = def->name; df.decl = debugLoc(def->offset); + df.ret = dbg.describeVars ? debugType(def->retType) : nullptr; df.external = !def->isStatic; df.variadic = def->isVarArgs; fn.setDebug(std::make_unique()); @@ -47,4 +133,12 @@ namespace rat::cc { if(dbg.info) fn.setLoc(fn.getDebug()->addLoc(debugLoc(tok))); } + + void Emitter::debugGlobal(const Declarator& d) { + if(!dbg.describeVars) + return; + const GlobalVariable& gv = syms.globals[*d.name]; + DebugType* type = debugObjectType(gv.type, gv.isArray, gv.count); + dbg.info->globals.push_back({*d.name, type, debugLoc(d.offset), *d.name}); + } } // namespace rat::cc diff --git a/src/compiler/emit/emit_global.cpp b/src/compiler/emit/emit_global.cpp index eeed23ac..7bd291f9 100644 --- a/src/compiler/emit/emit_global.cpp +++ b/src/compiler/emit/emit_global.cpp @@ -331,6 +331,7 @@ namespace rat::cc { } if(!registerGlobal(d, *d.name, nullptr)) return false; + debugGlobal(d); } return true; } diff --git a/src/compiler/emit/emit_state.h b/src/compiler/emit/emit_state.h index de59a411..a1266f82 100644 --- a/src/compiler/emit/emit_state.h +++ b/src/compiler/emit/emit_state.h @@ -6,6 +6,8 @@ #include "ir/function.h" #include "ir/module.h" +#include + namespace rat::cc { struct TokenStream; using Block = Function::Block; @@ -130,8 +132,10 @@ namespace rat::cc { struct DebugState { const TokenStream* toks = nullptr; DebugInfo* info = nullptr; - B32 describeVars = false; // types and variables, else lines only - Map files; // token file index to DebugInfo file + B32 describeVars = false; // types and variables, else lines only + Map files; // token file index to DebugInfo file + Map bases; // by name + std::map, DebugType*> types; // records and wrappers }; struct GlobalData { From a2f152229cce26d8e96616ba64c4464c2fb42a89 Mon Sep 17 00:00:00 2001 From: nella Date: Sun, 8 Nov 2026 16:03:00 +0100 Subject: [PATCH 2/3] Describe locals. --- src/backend/ir/debug_info.h | 13 +++++++- src/backend/pass/emit/x86/x86_encode.cpp | 10 +++++++ src/backend/pass/emit/x86/x86_encode.h | 1 + src/backend/pass/emit/x86/x86_lower.cpp | 4 ++- src/backend/pass/emit/x86/x86_lower.h | 1 - src/backend/pass/emit/x86/x86_op.h | 3 ++ src/backend/target/dwarf.cpp | 38 +++++++++++++++++++++--- src/backend/target/dwarf.h | 5 ++++ src/compiler/emit/emit.cpp | 2 +- src/compiler/emit/emit.h | 4 ++- src/compiler/emit/emit_debug.cpp | 26 +++++++++++++++- src/compiler/emit/emit_decl.cpp | 2 +- src/compiler/emit/emit_state.h | 1 + src/compiler/emit/emit_stmt.cpp | 4 ++- src/compiler/main.cpp | 4 +-- 15 files changed, 104 insertions(+), 14 deletions(-) diff --git a/src/backend/ir/debug_info.h b/src/backend/ir/debug_info.h index ea0f1d28..283b0eb0 100644 --- a/src/backend/ir/debug_info.h +++ b/src/backend/ir/debug_info.h @@ -5,6 +5,8 @@ #include "core.h" namespace rat { + struct AllocNode; + struct SrcLoc { U32 file = 0; // DebugInfo::files U32 line = 0; @@ -45,12 +47,13 @@ namespace rat { B32 incomplete = false; // declared struct without a body }; - // a source variable + // a source variable, shared by every copy of its function's code struct DebugVar { String name; DebugType* type; SrcLoc decl; String symbol; // static storage, else empty + B32 isParam = false; }; // a source function, outlives its IR function so inlined copies can still name it @@ -60,16 +63,24 @@ namespace rat { DebugType* ret = nullptr; B32 external = false; B32 variadic = false; + List> vars; // params first }; struct DebugLoc { SrcLoc src; }; + // one copy of a variable in a function's code + struct DebugVarInst { + const DebugVar* var; + AllocNode* slot = nullptr; // frame storage + }; + // debug state of one IR function struct DebugBody { DebugFunc* func; List locs = List(1); // 0 = no location + List vars; U32 addLoc(SrcLoc src); }; diff --git a/src/backend/pass/emit/x86/x86_encode.cpp b/src/backend/pass/emit/x86/x86_encode.cpp index dd7e7ceb..4e895486 100644 --- a/src/backend/pass/emit/x86/x86_encode.cpp +++ b/src/backend/pass/emit/x86/x86_encode.cpp @@ -564,6 +564,15 @@ namespace rat { encodedFunc->lines.push_back({a->here(), src}); } + void X86EncodePass::recordVars(const Function& f) { + if(!f.getDebug()) + return; + for(const DebugVarInst& v : f.getDebug()->vars) { + I32 off = v.slot ? fl->allocOff[v.slot->getId()] : kNoSlot; + encodedFunc->varFrameOff.push_back(off == kNoSlot ? kNoFrameSlot : off - detail::kCfaFromRbp); + } + } + U32 X86EncodePass::blockIdBound(const MachineFunc& f) { I32 maxId = -1; for(const MachineBlock& blk : f.blocks) @@ -702,6 +711,7 @@ namespace rat { if(encodedFunc) { encodedFunc->textOff = off; encodedFunc->size = (U32)code.size(); + recordVars(f); } placed.push_back({&f, off, (U32)relocs.size()}); return false; diff --git a/src/backend/pass/emit/x86/x86_encode.h b/src/backend/pass/emit/x86/x86_encode.h index 47c6ad53..bc70d9c5 100644 --- a/src/backend/pass/emit/x86/x86_encode.h +++ b/src/backend/pass/emit/x86/x86_encode.h @@ -102,6 +102,7 @@ namespace rat { void prologue(); void markCfi(CfiOp::Kind kind, PhysReg reg, I32 value); void markLine(U32 loc); + void recordVars(const Function& f); private: std::ostream* os; UniquePtr obj; diff --git a/src/backend/pass/emit/x86/x86_lower.cpp b/src/backend/pass/emit/x86/x86_lower.cpp index 5b970f95..40657769 100644 --- a/src/backend/pass/emit/x86/x86_lower.cpp +++ b/src/backend/pass/emit/x86/x86_lower.cpp @@ -551,7 +551,9 @@ namespace rat { curLoc = 0; allocOff.assign(f.idBound(), kNoSlot); lowerBlocks(); - mf.aux = std::make_unique(frame); // the layout rides along on mf.aux + if(f.getDebug()) + frame.allocOff = std::move(allocOff); // slot offsets for variable locations + mf.aux = std::make_unique(std::move(frame)); // the layout rides along on mf.aux } namespace detail { diff --git a/src/backend/pass/emit/x86/x86_lower.h b/src/backend/pass/emit/x86/x86_lower.h index 3e9c3c2b..8ff93e6f 100644 --- a/src/backend/pass/emit/x86/x86_lower.h +++ b/src/backend/pass/emit/x86/x86_lower.h @@ -300,7 +300,6 @@ namespace rat { void emitPhiCopies(I32 targetBlock, I32 predIdx); void emitTerminator(I32 b); private: - static constexpr I32 kNoSlot = INT32_MIN; // target const X86CallConv* conv = &abi::kSysV; const RegisterInfo* regs = nullptr; diff --git a/src/backend/pass/emit/x86/x86_op.h b/src/backend/pass/emit/x86/x86_op.h index 61b8ce57..6ae67dea 100644 --- a/src/backend/pass/emit/x86/x86_op.h +++ b/src/backend/pass/emit/x86/x86_op.h @@ -205,6 +205,8 @@ namespace rat { inline Slot slot(I32 s, U32 w = 8) { return {s, w}; } inline Xmm xmm(U32 n, U32 w = 8) { return {n, w}; } + constexpr I32 kNoSlot = INT32_MIN; + struct X86FrameLayout : MachineFuncAux { I32 ldScratch = 0; I32 vecScratch = 0; @@ -214,6 +216,7 @@ namespace rat { U32 namedGp = 0; U32 namedFp = 0; I32 sretSlot = 0; + List allocOff; }; } // namespace rat diff --git a/src/backend/target/dwarf.cpp b/src/backend/target/dwarf.cpp index 2cf5e599..2c826b4d 100644 --- a/src/backend/target/dwarf.cpp +++ b/src/backend/target/dwarf.cpp @@ -49,6 +49,7 @@ namespace rat { constexpr U32 DW_FORM_flag_present = 0x19; constexpr U8 DW_OP_addr = 0x03; + constexpr U8 DW_OP_fbreg = 0x91; constexpr U8 DW_OP_call_frame_cfa = 0x9c; constexpr U8 DW_LNS_copy = 1; @@ -274,6 +275,11 @@ namespace rat { info.push_back(0); } + // a static whose storage was never emitted gets no die + B32 DieWriter::hasStorage(const DebugVar& v) { + return v.symbol.empty() || mod.getGlobal(v.symbol); + } + void DieWriter::globalDie(const DebugVar& v) { const Global* g = mod.getGlobal(v.symbol); if(!g) @@ -286,14 +292,38 @@ namespace rat { emit(DW_TAG_variable, false, d); } + // a variable, without a location gdb reports it as optimized out + void DieWriter::instDie(const EncodedFunc& func, U32 inst) { + const DebugVar& v = *func.fn->getDebug()->vars[inst].var; + if(!hasStorage(v)) + return; + Die d; + describeVar(d, v); + I32 frameOff = func.varFrameOff[inst]; + if(!v.symbol.empty()) { + d.reloc(DW_AT_location, DW_FORM_exprloc, v.symbol, 0); + } else if(frameOff != kNoFrameSlot) { + List expr = {DW_OP_fbreg}; + le::sleb(expr, frameOff); + d.exprloc(DW_AT_location, expr); + } + emit(v.isParam ? DW_TAG_formal_parameter : DW_TAG_variable, false, d); + } + void DieWriter::funcDie(const EncodedFunc& func) { - const DebugFunc& f = *func.fn->getDebug()->func; + const DebugBody& body = *func.fn->getDebug(); Die d; - describeFunc(d, func.fn->getName(), f, !func.fn->getAttrs().isInternal()); + describeFunc(d, func.fn->getName(), *body.func, !func.fn->getAttrs().isInternal()); pcRanges(d, {{0, func.size}}, func.textOff); d.exprloc(DW_AT_frame_base, {DW_OP_call_frame_cfa}); - emit(DW_TAG_subprogram, f.variadic, d); - endChildren(f.variadic, f.variadic); + B32 variadic = body.func->variadic; + B32 children = variadic; + for(const DebugVarInst& v : body.vars) + children |= hasStorage(*v.var); + emit(DW_TAG_subprogram, children, d); + for(U32 i = 0; i < body.vars.size(); ++i) + instDie(func, i); + endChildren(variadic, children); } void DieWriter::unit(const List& funcs) { diff --git a/src/backend/target/dwarf.h b/src/backend/target/dwarf.h index fd47bf98..91cc3094 100644 --- a/src/backend/target/dwarf.h +++ b/src/backend/target/dwarf.h @@ -33,12 +33,15 @@ namespace rat { U32 end; }; + constexpr I32 kNoFrameSlot = INT32_MIN; + struct EncodedFunc { const Function* fn; U32 textOff; U32 size; List cfi; List lines; + List varFrameOff; // per DebugBody var, its frame slot from the cfa or kNoFrameSlot }; // .eh_frame always, the .debug_* sections when the module carries debug info @@ -85,7 +88,9 @@ namespace rat { void typeDie(const DebugType* t); void typeChildren(const DebugType* t); void endChildren(B32 variadic, B32 children); + B32 hasStorage(const DebugVar& v); void globalDie(const DebugVar& v); + void instDie(const EncodedFunc& func, U32 inst); void funcDie(const EncodedFunc& func); void unit(const List& funcs); }; diff --git a/src/compiler/emit/emit.cpp b/src/compiler/emit/emit.cpp index 9289d03f..dc1ce64d 100644 --- a/src/compiler/emit/emit.cpp +++ b/src/compiler/emit/emit.cpp @@ -485,7 +485,7 @@ namespace rat::cc { continue; if(isAggregate(p.type)) { func.scopes.declare(*p.name, Local::mem(arg, p.type)); - } else if(func.addrTaken.count(*p.name) || isTopVolatile(p.type)) { + } else if(func.addrTaken.count(*p.name) || isTopVolatile(p.type) || dbg.keepLocals) { Node* slot = fn.alloc(irType(p.type)); fn.store(slot, arg, isTopVolatile(p.type)); func.scopes.declare(*p.name, Local::mem(slot, p.type)); diff --git a/src/compiler/emit/emit.h b/src/compiler/emit/emit.h index f256ffb1..18db2774 100644 --- a/src/compiler/emit/emit.h +++ b/src/compiler/emit/emit.h @@ -47,7 +47,7 @@ namespace rat::cc { B32 emit(const TransUnit& unit); // describe the unit for debuggers, locations come from ts - void enableDebug(const TokenStream& ts, B32 describeVars); + void enableDebug(const TokenStream& ts, B32 keepLocalsInMemory, B32 describeVars); const String& error() const { return diag.message; } const List& warnings() const { return diag.warnings; } @@ -349,6 +349,8 @@ namespace rat::cc { DebugType* debugWrap(DebugType::Kind kind, DebugType* elem); void debugFunction(Function& fn, const FuncDef* def); void debugSetLoc(Function& fn, U32 tok); + void debugVar(Function& fn, DebugVar v, const Local& l); + void debugLocal(Function& fn, const Declarator& d); void debugGlobal(const Declarator& d); // initializers diff --git a/src/compiler/emit/emit_debug.cpp b/src/compiler/emit/emit_debug.cpp index c77bfc6f..269faf7c 100644 --- a/src/compiler/emit/emit_debug.cpp +++ b/src/compiler/emit/emit_debug.cpp @@ -6,7 +6,7 @@ #include namespace rat::cc { - void Emitter::enableDebug(const TokenStream& ts, B32 describeVars) { + void Emitter::enableDebug(const TokenStream& ts, B32 keepLocalsInMemory, B32 describeVars) { UniquePtr info = std::make_unique(); info->producer = String("ratcc ") + GIT_HASH; std::error_code ec; @@ -14,6 +14,7 @@ namespace rat::cc { info->files.push_back(ts.file()); dbg.toks = &ts; dbg.info = info.get(); + dbg.keepLocals = keepLocalsInMemory; dbg.describeVars = describeVars; mod.setDebugInfo(std::move(info)); } @@ -127,6 +128,12 @@ namespace rat::cc { df.variadic = def->isVarArgs; fn.setDebug(std::make_unique()); fn.getDebug()->func = &df; + for(const Param& p : def->params) { + Local l; + if(!dbg.describeVars || !p.name || !func.scopes.lookup(*p.name, l)) + continue; + debugVar(fn, {*p.name, debugType(p.type), debugLoc(p.offset), "", true}, l); + } } void Emitter::debugSetLoc(Function& fn, U32 tok) { @@ -134,6 +141,23 @@ namespace rat::cc { fn.setLoc(fn.getDebug()->addLoc(debugLoc(tok))); } + // records v with the frame slot l lives in, if any + void Emitter::debugVar(Function& fn, DebugVar v, const Local& l) { + DebugBody& body = *fn.getDebug(); + body.func->vars.push_back(std::make_unique(std::move(v))); + AllocNode* slot = !l.staticSymbol && l.inMem() ? dyn_cast(l.addr) : nullptr; + body.vars.push_back({body.func->vars.back().get(), slot}); + } + + void Emitter::debugLocal(Function& fn, const Declarator& d) { + Local l; + if(!dbg.describeVars || d.isExtern || !func.scopes.lookup(*d.name, l)) + return; + String symbol = l.staticSymbol ? *l.staticSymbol : ""; + DebugVar v{*d.name, debugObjectType(d.type, d.isArray, l.count), debugLoc(d.offset), symbol}; + debugVar(fn, std::move(v), l); + } + void Emitter::debugGlobal(const Declarator& d) { if(!dbg.describeVars) return; diff --git a/src/compiler/emit/emit_decl.cpp b/src/compiler/emit/emit_decl.cpp index 3de5b4e5..847515cd 100644 --- a/src/compiler/emit/emit_decl.cpp +++ b/src/compiler/emit/emit_decl.cpp @@ -300,7 +300,7 @@ namespace rat::cc { init = fn.constInt(irType(d.type), 0); } // a volatile local lives in memory, so every access stays - if(func.addrTaken.count(*d.name) || isTopVolatile(d.type)) { + if(func.addrTaken.count(*d.name) || isTopVolatile(d.type) || dbg.keepLocals) { Node* slot = declSlot(fn, d, irType(d.type), byteSize(d.type)); fn.store(slot, init, isTopVolatile(d.type)); func.scopes.declare(*d.name, Local::mem(slot, d.type)); diff --git a/src/compiler/emit/emit_state.h b/src/compiler/emit/emit_state.h index a1266f82..41b9d9c0 100644 --- a/src/compiler/emit/emit_state.h +++ b/src/compiler/emit/emit_state.h @@ -132,6 +132,7 @@ namespace rat::cc { struct DebugState { const TokenStream* toks = nullptr; DebugInfo* info = nullptr; + B32 keepLocals = false; // every named local in a frame slot, so -O0 code is inspectable B32 describeVars = false; // types and variables, else lines only Map files; // token file index to DebugInfo file Map bases; // by name diff --git a/src/compiler/emit/emit_stmt.cpp b/src/compiler/emit/emit_stmt.cpp index d82f8094..bcabbf8a 100644 --- a/src/compiler/emit/emit_stmt.cpp +++ b/src/compiler/emit/emit_stmt.cpp @@ -402,9 +402,11 @@ namespace rat::cc { case StmtKind::Compound: return emitCompound(fn, s); case StmtKind::Decl: - for(const Declarator& d : s->decls) + for(const Declarator& d : s->decls) { if(!emitOneDecl(fn, d)) return false; + debugLocal(fn, d); + } return true; case StmtKind::If: return emitIf(fn, s); diff --git a/src/compiler/main.cpp b/src/compiler/main.cpp index c0b9b7ad..0e66f0fb 100644 --- a/src/compiler/main.cpp +++ b/src/compiler/main.cpp @@ -97,7 +97,7 @@ namespace detail { " -E preprocess only\n" " -I -D -U preprocessor include dir, define, undefine\n" " -O0 / -O1 optimization level (default -O0)\n" - " -g[0..3] debug info (DWARF)\n" + " -g[0..3] debug info (DWARF), -g at -O0 keeps locals in memory,\n" " also -ggdb[0..3], -gline-tables-only, -gdwarf[-5]\n" " -nostdinc do not provide the builtin C standard headers\n" " -undef do not predefine the builtin target macros\n" @@ -231,7 +231,7 @@ namespace detail { Module& mod = *new Module; Emitter emitter(mod, TargetLayout::forTriple(hostTargetTriple())); if(opt.debugLevel) - emitter.enableDebug(ts, opt.debugLevel > 1); + emitter.enableDebug(ts, opt.debugLevel > 1 && opt.optLevel == 0, opt.debugLevel > 1); t0 = PhaseClock::now(); B32 emitOk = emitter.emit(*unit); F64 emitMs = msSince(t0); From 4701c5afff25a0f927391b67fecfc353c5fb5221 Mon Sep 17 00:00:00 2001 From: nella Date: Sun, 8 Nov 2026 18:44:00 +0100 Subject: [PATCH 3/3] Name typedefs and enums. --- src/backend/ir/debug_info.h | 10 ++++- src/backend/target/dwarf.cpp | 27 ++++++++++++-- src/compiler/emit/emit.h | 1 + src/compiler/emit/emit_debug.cpp | 26 +++++++++++++ src/compiler/emit/emit_state.h | 2 +- src/compiler/parse/ast.h | 24 +++++++++++- src/compiler/parse/parse_stmt.cpp | 4 +- src/compiler/parse/parse_struct.cpp | 35 ++++++++++-------- src/compiler/parse/parse_type.cpp | 4 +- src/compiler/parse/parser.h | 4 +- src/compiler/test/debug/debug.O0.expect | 34 +++++++++++++++++ src/compiler/test/debug/debug.O1.expect | 23 ++++++++++++ src/compiler/test/debug/debug.c | 49 +++++++++++++++++++++++++ src/compiler/test/debug/debug.gdb | 28 ++++++++++++++ src/compiler/test/debug/debug.h | 5 +++ 15 files changed, 248 insertions(+), 28 deletions(-) create mode 100644 src/compiler/test/debug/debug.O0.expect create mode 100644 src/compiler/test/debug/debug.O1.expect create mode 100644 src/compiler/test/debug/debug.c create mode 100644 src/compiler/test/debug/debug.gdb create mode 100644 src/compiler/test/debug/debug.h diff --git a/src/backend/ir/debug_info.h b/src/backend/ir/debug_info.h index 283b0eb0..920389cc 100644 --- a/src/backend/ir/debug_info.h +++ b/src/backend/ir/debug_info.h @@ -26,7 +26,11 @@ namespace rat { Struct, Union, Array, - Function + Function, + Typedef, + Enum, + Const, + Volatile }; struct Member { String name; @@ -39,12 +43,14 @@ namespace rat { Kind kind; String name; U32 size = 0; - DebugType* elem = nullptr; // pointee, element or return type, null = void + DebugType* elem = nullptr; // pointee, element, return, aliased or underlying type, null = void U64 count = 0; // array length, 0 = unknown List members; List params; B32 variadic = false; B32 incomplete = false; // declared struct without a body + List> enumerators; + SrcLoc decl; // typedef and enum }; // a source variable, shared by every copy of its function's code diff --git a/src/backend/target/dwarf.cpp b/src/backend/target/dwarf.cpp index 2c826b4d..1f37f2e3 100644 --- a/src/backend/target/dwarf.cpp +++ b/src/backend/target/dwarf.cpp @@ -6,18 +6,23 @@ namespace rat { namespace detail { constexpr U32 DW_TAG_array_type = 0x01; + constexpr U32 DW_TAG_enumeration_type = 0x04; constexpr U32 DW_TAG_formal_parameter = 0x05; constexpr U32 DW_TAG_member = 0x0d; constexpr U32 DW_TAG_pointer_type = 0x0f; constexpr U32 DW_TAG_compile_unit = 0x11; constexpr U32 DW_TAG_structure_type = 0x13; constexpr U32 DW_TAG_subroutine_type = 0x15; + constexpr U32 DW_TAG_typedef = 0x16; constexpr U32 DW_TAG_union_type = 0x17; constexpr U32 DW_TAG_unspecified_parameters = 0x18; constexpr U32 DW_TAG_subrange_type = 0x21; constexpr U32 DW_TAG_base_type = 0x24; + constexpr U32 DW_TAG_const_type = 0x26; + constexpr U32 DW_TAG_enumerator = 0x28; constexpr U32 DW_TAG_subprogram = 0x2e; constexpr U32 DW_TAG_variable = 0x34; + constexpr U32 DW_TAG_volatile_type = 0x35; constexpr U32 DW_AT_location = 0x02; constexpr U32 DW_AT_name = 0x03; @@ -28,6 +33,7 @@ namespace rat { constexpr U32 DW_AT_high_pc = 0x12; constexpr U32 DW_AT_language = 0x13; constexpr U32 DW_AT_comp_dir = 0x1b; + constexpr U32 DW_AT_const_value = 0x1c; constexpr U32 DW_AT_producer = 0x25; constexpr U32 DW_AT_prototyped = 0x27; constexpr U32 DW_AT_count = 0x37; @@ -42,6 +48,7 @@ namespace rat { constexpr U32 DW_FORM_addr = 0x01; constexpr U32 DW_FORM_string = 0x08; + constexpr U32 DW_FORM_sdata = 0x0d; constexpr U32 DW_FORM_udata = 0x0f; constexpr U32 DW_FORM_ref4 = 0x13; constexpr U32 DW_FORM_sec_offset = 0x17; @@ -108,6 +115,10 @@ namespace rat { DW_TAG_union_type, DW_TAG_array_type, DW_TAG_subroutine_type, + DW_TAG_typedef, + DW_TAG_enumeration_type, + DW_TAG_const_type, + DW_TAG_volatile_type, }; // x86-64, rsp and the return address column @@ -226,19 +237,22 @@ namespace rat { d.udata(DW_AT_encoding, kBaseEncoding[(U32)k]); if(t->incomplete) d.attr(DW_AT_declaration, DW_FORM_flag_present); - else if(k <= Kind::Union) + else if(k <= Kind::Union || k == Kind::Enum) d.udata(DW_AT_byte_size, t->size); if(k == Kind::Function) d.attr(DW_AT_prototyped, DW_FORM_flag_present); + if(k == Kind::Typedef || k == Kind::Enum) + d.srcLoc(DW_AT_decl_file, t->decl); if(t->elem) d.type(DW_AT_type, t->elem); - B32 children = !t->members.empty() || k == Kind::Array || !t->params.empty() || t->variadic; + B32 children = !t->members.empty() || k == Kind::Array || !t->params.empty() || t->variadic || + !t->enumerators.empty(); dieOff[t] = emit(kTypeTag[(U32)k], children, d); if(children) typeChildren(t); } - // members, the array subrange or params + // members, the array subrange, params or enumerators void DieWriter::typeChildren(const DebugType* t) { for(const DebugType::Member& m : t->members) { Die member; @@ -264,6 +278,13 @@ namespace rat { param.type(DW_AT_type, p); emit(DW_TAG_formal_parameter, false, param); } + for(const auto& [name, value] : t->enumerators) { + Die e; + e.string(DW_AT_name, name); + e.attr(DW_AT_const_value, DW_FORM_sdata); + le::sleb(e.body, value); + emit(DW_TAG_enumerator, false, e); + } endChildren(t->variadic, true); } diff --git a/src/compiler/emit/emit.h b/src/compiler/emit/emit.h index 18db2774..c4cb3955 100644 --- a/src/compiler/emit/emit.h +++ b/src/compiler/emit/emit.h @@ -346,6 +346,7 @@ namespace rat::cc { DebugType* debugObjectType(CType t, B32 isArray, U64 count); DebugType* debugBase(CType t); DebugType* debugRecord(const StructType* st); + DebugType* debugNamed(const NamedType* n); DebugType* debugWrap(DebugType::Kind kind, DebugType* elem); void debugFunction(Function& fn, const FuncDef* def); void debugSetLoc(Function& fn, U32 tok); diff --git a/src/compiler/emit/emit_debug.cpp b/src/compiler/emit/emit_debug.cpp index 269faf7c..ef1c38df 100644 --- a/src/compiler/emit/emit_debug.cpp +++ b/src/compiler/emit/emit_debug.cpp @@ -33,6 +33,20 @@ namespace rat::cc { DebugType* Emitter::debugType(CType t) { using Kind = DebugType::Kind; + const NamedType* named = outerNamedType(t); + // qualifiers a typedef carries belong to its own description + CType own = named ? named->type : CType{}; + U32 top = 1u << t.ptr; + if(t.vquals & ~own.vquals & top) { + clearTopVolatile(t); + return debugWrap(Kind::Volatile, debugType(t)); + } + if(t.quals & ~own.quals & top) { + clearTopConst(t); + return debugWrap(Kind::Const, debugType(t)); + } + if(named) + return debugNamed(named); if(t.ptr > 0) return debugWrap(Kind::Pointer, debugType(pointee(t))); if(isArrayType(t)) @@ -107,6 +121,18 @@ namespace rat::cc { return rec; } + DebugType* Emitter::debugNamed(const NamedType* n) { + DebugType::Kind kind = n->isEnum ? DebugType::Kind::Enum : DebugType::Kind::Typedef; + DebugType*& d = dbg.types[{kind, n}]; + if(d) + return d; + d = dbg.info->makeType(kind, n->name, n->isEnum ? (U32)byteSize(n->type) : 0); + d->decl = debugLoc(n->offset); + d->enumerators = n->enumerators; + d->elem = debugType(n->type); + return d; + } + DebugType* Emitter::debugWrap(DebugType::Kind kind, DebugType* elem) { DebugType*& w = dbg.types[{kind, elem}]; if(!w) { diff --git a/src/compiler/emit/emit_state.h b/src/compiler/emit/emit_state.h index 41b9d9c0..b2aaddce 100644 --- a/src/compiler/emit/emit_state.h +++ b/src/compiler/emit/emit_state.h @@ -136,7 +136,7 @@ namespace rat::cc { B32 describeVars = false; // types and variables, else lines only Map files; // token file index to DebugInfo file Map bases; // by name - std::map, DebugType*> types; // records and wrappers + std::map, DebugType*> types; // records, named and wrappers }; struct GlobalData { diff --git a/src/compiler/parse/ast.h b/src/compiler/parse/ast.h index 3d75dabd..99e80957 100644 --- a/src/compiler/parse/ast.h +++ b/src/compiler/parse/ast.h @@ -68,6 +68,7 @@ namespace rat::cc { struct StructType; struct ArrayType; struct FuncType; + struct NamedType; struct Expr; struct CType { @@ -85,17 +86,18 @@ namespace rat::cc { }; U32 bits = 32; - U32 bitPrec = 0; // bitfield width, 0 when the type is not a bitfield's U32 ptr = 0; U32 quals = 0; U32 vquals = 0; U32 rquals = 0; Base base = Base::Int; U8 mods = 0; + U8 bitPrec = 0; // bitfield width, 0 when the type is not a bitfield's const StructType* strukt = nullptr; const ArrayType* array = nullptr; const FuncType* func = nullptr; const Expr* typeofExpr = nullptr; + const NamedType* named = nullptr; // typedef or enum spelled, see outerNamedType constexpr B32 has(Mods m) const { return (mods & (U8)m) != 0; } constexpr void set(Mods m, B32 on = true) { @@ -136,6 +138,26 @@ namespace rat::cc { return t.ptr == 0 && t.base == CType::Base::Int && !t.isComplex() && t.strukt == nullptr; } + // a typedef name or an enum + struct NamedType { + String name; // empty for an anonymous enum + CType type; // aliased type, or the enum's underlying int + List> enumerators; + U32 offset = 0; // token of the name, or of the enum keyword + B32 isEnum = false; + }; + + // the typedef or enum t spells at its outer level, null once a derived type wraps it + constexpr const NamedType* outerNamedType(CType t) { + const NamedType* n = t.named; + if(!n) + return nullptr; + CType a = n->type; + B32 same = t.ptr == a.ptr && t.array == a.array && t.func == a.func && t.strukt == a.strukt && + t.base == a.base && t.bits == a.bits && t.mods == a.mods; + return same ? n : nullptr; + } + struct Field { enum Mods : U8 { Array = 1u << 0, // count is the element count diff --git a/src/compiler/parse/parse_stmt.cpp b/src/compiler/parse/parse_stmt.cpp index b9a186e3..114d85d5 100644 --- a/src/compiler/parse/parse_stmt.cpp +++ b/src/compiler/parse/parse_stmt.cpp @@ -346,7 +346,7 @@ namespace rat::cc { void Parser::pushScope() { typedefs.push(); enumConstants.push(); - enumSignedTags.push(); + enumTags.push(); structTypes.push(); ++scopeDepth; } @@ -355,7 +355,7 @@ namespace rat::cc { --scopeDepth; typedefs.pop(); enumConstants.pop(); - enumSignedTags.pop(); + enumTags.pop(); structTypes.pop(); } diff --git a/src/compiler/parse/parse_struct.cpp b/src/compiler/parse/parse_struct.cpp index 60a0fcc5..5aa72e21 100644 --- a/src/compiler/parse/parse_struct.cpp +++ b/src/compiler/parse/parse_struct.cpp @@ -85,7 +85,7 @@ namespace rat::cc { unitStart = l.bitPos / unitBits * unitBytes; } if(w > 0 && f.name) { - f.type.bitPrec = (U32)w; // the field's values wrap at this width + f.type.bitPrec = (U8)w; // the field's values wrap at this width f.set(Field::Bitfield); f.bitWidth = (U32)w; f.bitOffset = isUnion ? 0 : l.bitPos % unitBits; @@ -270,8 +270,9 @@ namespace rat::cc { // [ enumerator [, enumerator]... [,] ] } // enumerator: name [= const-expr] - B32 Parser::parseEnumerators(B32& anyNegative) { + B32 Parser::parseEnumerators(NamedType& e) { I64 next = 0; + B32 anyNegative = false; while(!check(TokKind::RBrace) && !check(TokKind::Eof)) { if(!check(TokKind::Identifier)) { fail(peek(), "expected enumerator name"); @@ -293,16 +294,18 @@ namespace rat::cc { if(value < 0) anyNegative = true; enumConstants.set(lex.text(name), value); + e.enumerators.push_back({lex.text(name), value}); next = value + 1; if(!accept(TokKind::Comma)) break; } + e.type.set(CType::Unsigned, !anyNegative); return expect(TokKind::RBrace, "'}'"); } // enum [alignas]... [tag] [ { enumerators ] B32 Parser::parseEnumSpec(CType& out) { - advance(); // enum + Token kw = advance(); // enum String tag; U32 ignored = 0; // an enum's alignment is its underlying type's if(!acceptTrailingAlignas(ignored)) @@ -310,20 +313,20 @@ namespace rat::cc { if(check(TokKind::Identifier)) tag = lex.text(advance()); - B32 anyNegative = false; - B32 haveList = accept(TokKind::LBrace); - if(haveList && !parseEnumerators(anyNegative)) - return false; - if(!tag.empty()) { - if(haveList) - enumSignedTags.set(tag, anyNegative); - else { - if(const B32* seen = enumSignedTags.get(tag)) - anyNegative = *seen; - } + const NamedType* e = nullptr; + if(accept(TokKind::LBrace)) { + NamedType* defined = arena.make(NamedType{tag, ctInt(), {}, kw.offset, true}); + if(!parseEnumerators(*defined)) + return false; + if(!tag.empty()) + enumTags.set(tag, defined); + e = defined; + } else if(const NamedType* const* seen = enumTags.get(tag)) { + e = *seen; } - out = ctInt(); - if(!anyNegative) + out = e ? e->type : ctInt(); + out.named = e; + if(!e) out.set(CType::Unsigned); return true; } diff --git a/src/compiler/parse/parse_type.cpp b/src/compiler/parse/parse_type.cpp index 86fb7fbd..414afc6e 100644 --- a/src/compiler/parse/parse_type.cpp +++ b/src/compiler/parse/parse_type.cpp @@ -84,7 +84,9 @@ namespace rat::cc { fail(peek(), "expected typedef name"); return false; } - typedefs.set(*r.name, r.type); + CType named = r.type; + named.named = arena.make(NamedType{*r.name, r.type, {}, r.offset}); + typedefs.set(*r.name, named); if(!accept(TokKind::Comma)) break; } diff --git a/src/compiler/parse/parser.h b/src/compiler/parse/parser.h index 8b2f99b9..6a3da2ff 100644 --- a/src/compiler/parse/parser.h +++ b/src/compiler/parse/parser.h @@ -104,7 +104,7 @@ namespace rat::cc { B32 acceptTrailingAlignas(U32& align); B32 parseTypeofSpec(CType& out); B32 parseEnumSpec(CType& out); - B32 parseEnumerators(B32& anyNegative); + B32 parseEnumerators(NamedType& e); B32 parseStructSpec(CType& out); struct StructLayout { U64 offset = 0; // bytes used so far; the union's widest member @@ -284,7 +284,7 @@ namespace rat::cc { U32 scopeDepth = 0; ScopeMap typedefs; ScopeMap enumConstants; - ScopeMap enumSignedTags; + ScopeMap enumTags; ScopeMap structTypes; // unit-wide tables diff --git a/src/compiler/test/debug/debug.O0.expect b/src/compiler/test/debug/debug.O0.expect new file mode 100644 index 00000000..969b9e4b --- /dev/null +++ b/src/compiler/test/debug/debug.O0.expect @@ -0,0 +1,34 @@ +Breakpoint 1, leaf (a=53) at +#1 0x + in deep (n=0, acc=53) at +debug.c:16 +#4 0x + in deep (n=3, acc=1) at +#5 0x + in main () at +debug.c:38 +$1 = 53 +Value returned is $2 = 160 +Breakpoint 2, scale (p=0x +k=5) at +debug.c:24 +$3 = {x = 1, y = 2.5, flag = 5, name = "hi\000\000\000\000\000"} +$4 = 5 +$5 = "hi\000\000\000\000\000" +$6 = 5 +$7 = 7 + in main () at +debug.c:39 +$8 = {4, 5, 6} +$9 = 97 'a' +Line 2 of +debug.h" starts at address +Breakpoint 3, span (q=..., bias=2) at +$12 = 2 +Value returned is $13 = 8 +type = Tagged +type = struct { + Color c; + const char *s; += Green += Blue diff --git a/src/compiler/test/debug/debug.O1.expect b/src/compiler/test/debug/debug.O1.expect new file mode 100644 index 00000000..3ec9d728 --- /dev/null +++ b/src/compiler/test/debug/debug.O1.expect @@ -0,0 +1,23 @@ +Breakpoint 1, leaf (a= +#1 0x + in deep ( +debug.c:16 +#4 0x + in deep ( +#5 0x + in main () at +debug.c:38 +Value returned is $2 = 160 +Breakpoint 2, scale (p= +debug.c:24 + in main () at +debug.c:39 += {4, 5, 6} +Breakpoint 3, span (q=..., bias= +Value returned is $10 = 8 +type = Tagged +type = struct { + Color c; + const char *s; += Green += Blue diff --git a/src/compiler/test/debug/debug.c b/src/compiler/test/debug/debug.c new file mode 100644 index 00000000..8a0eed8d --- /dev/null +++ b/src/compiler/test/debug/debug.c @@ -0,0 +1,49 @@ +#include "debug.h" + +struct Point { + int x; + double y; + unsigned flag : 3; + char name[8]; +}; + +int counter = 7; + +__attribute__((noinline)) int leaf(int a) { return a * 3 + 1; } + +__attribute__((noinline)) int deep(int n, int acc) { + if(n == 0) + return leaf(acc); + int t = square(n) + TWICE(acc); + return deep(n - 1, t) + t; +} + +__attribute__((noinline)) double scale(struct Point* p, int k) { + static int calls; + double r = p->y * k; + calls++; + return r; +} + +__attribute__((noinline)) int span(struct Point q, int bias) { + int w = q.x + bias; + counter += w; + return w + q.flag; +} + +int main(void) { + struct Point p = {1, 2.5, 5, "hi"}; + int arr[3] = {4, 5, 6}; + char ch = 'a'; + int r = deep(3, 1); + double d = scale(&p, arr[1]); + r += span(p, 2); + return (r + (int)d + ch + counter) & 1; +} + +typedef enum Color { Red, Green = 5, Blue = -2 } Color; +typedef struct { + Color c; + const char* s; +} Tagged; +Tagged tagged = {Green, "x"}; diff --git a/src/compiler/test/debug/debug.gdb b/src/compiler/test/debug/debug.gdb new file mode 100644 index 00000000..9922901b --- /dev/null +++ b/src/compiler/test/debug/debug.gdb @@ -0,0 +1,28 @@ +break leaf +run +bt +frame 2 +p t +frame 0 +finish +break debug.c:24 +continue +p *p +p p->flag +p p->name +p k +p counter +up +p arr +p ch +info line debug.h:2 +break span +continue +p q +p q.name +p bias +finish +whatis tagged +ptype tagged +p tagged.c +p (enum Color)-2 diff --git a/src/compiler/test/debug/debug.h b/src/compiler/test/debug/debug.h new file mode 100644 index 00000000..6acd00d7 --- /dev/null +++ b/src/compiler/test/debug/debug.h @@ -0,0 +1,5 @@ +static inline int square(int x) { + return x * x; +} +#define TWICE(v) \ + ((v) + (v))