diff --git a/src/backend/ir/debug_info.cpp b/src/backend/ir/debug_info.cpp index 2483388..5c706f0 100644 --- a/src/backend/ir/debug_info.cpp +++ b/src/backend/ir/debug_info.cpp @@ -11,12 +11,12 @@ namespace rat { } // consecutive nodes mostly share a location, reuse the last entry then - U32 DebugBody::addLoc(SrcLoc src) { + U32 DebugBody::addLoc(SrcLoc src, U32 scope) { const DebugLoc& last = locs.back(); - if(locs.size() > 1 && last.src.file == src.file && last.src.line == src.line && - last.src.col == src.col) + if(locs.size() > 1 && last.scope == scope && last.src.file == src.file && + last.src.line == src.line && last.src.col == src.col) return (U32)locs.size() - 1; - locs.push_back({src}); + locs.push_back({src, scope}); return (U32)locs.size() - 1; } } // namespace rat diff --git a/src/backend/ir/debug_info.h b/src/backend/ir/debug_info.h index 920389c..298502d 100644 --- a/src/backend/ir/debug_info.h +++ b/src/backend/ir/debug_info.h @@ -72,23 +72,33 @@ namespace rat { List> vars; // params first }; + // a lexical block, or an inlined call when inlined is set, scope 0 is the function body + struct DebugScope { + U32 parent = 0; + const DebugFunc* inlined = nullptr; + U32 callLoc = 0; + }; + struct DebugLoc { SrcLoc src; + U32 scope = 0; }; // one copy of a variable in a function's code struct DebugVarInst { const DebugVar* var; + U32 scope = 0; AllocNode* slot = nullptr; // frame storage }; - // debug state of one IR function + // debug state of one IR function, inlining copies it into the caller struct DebugBody { DebugFunc* func; - List locs = List(1); // 0 = no location + List locs = List(1); // 0 = no location + List scopes = List(1); // 0 = the body List vars; - U32 addLoc(SrcLoc src); + U32 addLoc(SrcLoc src, U32 scope); }; struct DebugInfo { diff --git a/src/backend/pass/emit/x86/x86_encode.cpp b/src/backend/pass/emit/x86/x86_encode.cpp index 4e89548..76e7c2e 100644 --- a/src/backend/pass/emit/x86/x86_encode.cpp +++ b/src/backend/pass/emit/x86/x86_encode.cpp @@ -564,6 +564,22 @@ namespace rat { encodedFunc->lines.push_back({a->here(), src}); } + // [start, end) belongs to the instr's scope and its ancestors, located instrs carry the scope on + void X86EncodePass::markScope(U32 loc, U32 start, U32 end) { + if(!encodedFunc || encodedFunc->scopes.empty() || start == end) + return; + const DebugBody& body = *encodedFunc->fn->getDebug(); + if(loc) + scope = body.locs[loc].scope; + for(U32 s = scope; s != 0; s = body.scopes[s].parent) { + List& r = encodedFunc->scopes[s]; + if(!r.empty() && r.back().end == start) + r.back().end = end; + else + r.push_back({start, end}); + } + } + void X86EncodePass::recordVars(const Function& f) { if(!f.getDebug()) return; @@ -642,8 +658,10 @@ namespace rat { blockOffset[blk.id] = a->here(); I32 fallthrough = (bi + 1 < f.blocks.size()) ? (I32)f.blocks[bi + 1].id : -1; for(const MachineInstr& in : blk.insts) { + U32 at = a->here(); markLine(in.loc); emitInst(in, fallthrough); + markScope(in.loc, at, a->here()); } } // jump tables: 4-byte offsets from the table base, emitted after the code @@ -696,8 +714,11 @@ namespace rat { if(objectFormatFor(target.getTriple().os) == ObjectFormat::Elf) { encodedFunc = &encoded.emplace_back(); encodedFunc->fn = &f; - if(f.getDebug()) + if(f.getDebug()) { encodedFunc->lines.push_back({0, f.getDebug()->func->decl}); + encodedFunc->scopes.resize(f.getDebug()->scopes.size()); + } + scope = 0; } code.clear(); Asm a(code, relocs); diff --git a/src/backend/pass/emit/x86/x86_encode.h b/src/backend/pass/emit/x86/x86_encode.h index bc70d9c..ad18fac 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 markScope(U32 loc, U32 start, U32 end); void recordVars(const Function& f); private: std::ostream* os; @@ -111,6 +112,7 @@ namespace rat { List placed; List encoded; // elf only, one per placed function EncodedFunc* encodedFunc = nullptr; // the function being encoded, null on coff + U32 scope = 0; // of the last located instr const X86CallConv* conv = &abi::kSysV; const MachineFunc* fn = nullptr; const X86FrameLayout* fl = nullptr; diff --git a/src/backend/pass/opt/inline.cpp b/src/backend/pass/opt/inline.cpp index b249654..f040734 100644 --- a/src/backend/pass/opt/inline.cpp +++ b/src/backend/pass/opt/inline.cpp @@ -33,6 +33,8 @@ namespace rat { } Node* InlinePass::mapped(Node* key) const { + if(!key) + return nullptr; U32 id = key->getId(); return id < cloneMap.size() ? cloneMap[id] : nullptr; } @@ -128,9 +130,46 @@ namespace rat { caller.removeNode(call); } + // the callee scopes and locs move under a new inlined scope of the caller, callee scope k lands at + // scopeBase + k and loc l at locBase + l, the clones take the moved locs + void InlinePass::inlineScopes(Function& caller, CallNode* call, const Function& callee) { + DebugBody& to = *caller.getDebug(); + const DebugBody& from = *callee.getDebug(); + U32 scopeBase = (U32)to.scopes.size(); + U32 locBase = (U32)to.locs.size() - 1; + to.scopes.push_back({to.locs[call->getLoc()].scope, from.func, call->getLoc()}); + for(U32 k = 1; k < from.scopes.size(); ++k) { + DebugScope s = from.scopes[k]; + s.parent += scopeBase; + s.callLoc = s.callLoc ? locBase + s.callLoc : 0; + to.scopes.push_back(s); + } + for(U32 l = 1; l < from.locs.size(); ++l) + to.locs.push_back({from.locs[l].src, scopeBase + from.locs[l].scope}); + for(Node* n : callee) + if(Node* c = mapped(n); c && n->getLoc() && !isStartProj(callee, n)) + c->setLoc(locBase + n->getLoc()); + } + + // after inlineScopes, the callee scopes end the caller list + void InlinePass::inlineVars(Function& caller, const Function& callee) { + DebugBody& to = *caller.getDebug(); + U32 scopeBase = (U32)(to.scopes.size() - callee.getDebug()->scopes.size()); + for(const DebugVarInst& v : callee.getDebug()->vars) { + DebugVarInst copy = v; + copy.scope = scopeBase + v.scope; + copy.slot = dyn_cast(mapped(v.slot)); + to.vars.push_back(copy); + } + } + B32 InlinePass::inlineCallSite(Function& caller, CallNode* call, Function& callee) { if(!cloneBody(caller, call, callee)) return false; + if(caller.getDebug() && callee.getDebug()) { + inlineScopes(caller, call, callee); + inlineVars(caller, callee); + } wireClones(callee); Merged m = mergeReturns(caller, callee); if(!m.ctrl) diff --git a/src/backend/pass/opt/inline.h b/src/backend/pass/opt/inline.h index a73fffb..7840000 100644 --- a/src/backend/pass/opt/inline.h +++ b/src/backend/pass/opt/inline.h @@ -46,6 +46,8 @@ namespace rat { void wireClones(Function& callee); Merged mergeReturns(Function& caller, const Function& callee); static void replaceCall(Function& caller, CallNode* call, const Merged& m); + void inlineScopes(Function& caller, CallNode* call, const Function& callee); + void inlineVars(Function& caller, const Function& callee); B32 inlineCallSite(Function& caller, CallNode* call, Function& callee); private: const CallGraph* graph = nullptr; // of the current module run diff --git a/src/backend/target/dwarf.cpp b/src/backend/target/dwarf.cpp index 1f37f2e..85a8d63 100644 --- a/src/backend/target/dwarf.cpp +++ b/src/backend/target/dwarf.cpp @@ -8,6 +8,7 @@ namespace rat { 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_lexical_block = 0x0b; constexpr U32 DW_TAG_member = 0x0d; constexpr U32 DW_TAG_pointer_type = 0x0f; constexpr U32 DW_TAG_compile_unit = 0x11; @@ -16,6 +17,7 @@ namespace rat { constexpr U32 DW_TAG_typedef = 0x16; constexpr U32 DW_TAG_union_type = 0x17; constexpr U32 DW_TAG_unspecified_parameters = 0x18; + constexpr U32 DW_TAG_inlined_subroutine = 0x1d; constexpr U32 DW_TAG_subrange_type = 0x21; constexpr U32 DW_TAG_base_type = 0x24; constexpr U32 DW_TAG_const_type = 0x26; @@ -34,8 +36,10 @@ namespace rat { constexpr U32 DW_AT_language = 0x13; constexpr U32 DW_AT_comp_dir = 0x1b; constexpr U32 DW_AT_const_value = 0x1c; + constexpr U32 DW_AT_inline = 0x20; constexpr U32 DW_AT_producer = 0x25; constexpr U32 DW_AT_prototyped = 0x27; + constexpr U32 DW_AT_abstract_origin = 0x31; constexpr U32 DW_AT_count = 0x37; constexpr U32 DW_AT_data_member_location = 0x38; constexpr U32 DW_AT_decl_file = 0x3a; @@ -44,6 +48,8 @@ namespace rat { constexpr U32 DW_AT_external = 0x3f; constexpr U32 DW_AT_frame_base = 0x40; constexpr U32 DW_AT_type = 0x49; + constexpr U32 DW_AT_ranges = 0x55; + constexpr U32 DW_AT_call_file = 0x58; constexpr U32 DW_AT_data_bit_offset = 0x6b; constexpr U32 DW_FORM_addr = 0x01; @@ -59,6 +65,9 @@ namespace rat { constexpr U8 DW_OP_fbreg = 0x91; constexpr U8 DW_OP_call_frame_cfa = 0x9c; + constexpr U8 DW_LE_end_of_list = 0; + constexpr U8 DW_LE_offset_pair = 4; // same in rnglists and loclists + constexpr U8 DW_LNS_copy = 1; constexpr U8 DW_LNS_advance_pc = 2; constexpr U8 DW_LNS_advance_line = 3; @@ -82,6 +91,8 @@ namespace rat { constexpr U8 DW_UT_compile = 1; + constexpr U8 DW_INL_inlined = 1; + constexpr U8 DW_ATE_boolean = 0x02; constexpr U8 DW_ATE_complex_float = 0x03; constexpr U8 DW_ATE_float = 0x04; @@ -167,7 +178,7 @@ namespace rat { types.push_back(t); } - // line follows the file attr and column precedes it + // line follows the file attr and column precedes it, for decl_* and call_* alike void Die::srcLoc(U32 fileAttr, SrcLoc loc) { udata(fileAttr, loc.file); udata(fileAttr + 1, loc.line); @@ -219,12 +230,22 @@ namespace rat { return at; } - // one range as low and high pc + // one range as low and high pc, several as a .debug_rnglists list. list offsets are from the + // unit base, .text void DieWriter::pcRanges(Die& d, const List& r, U32 textOff) { - assert(r.size() == 1 && "split ranges need .debug_rnglists"); - d.reloc( - DW_AT_low_pc, DW_FORM_addr, obj.sectionSymbol(ObjectFile::Text), textOff + r[0].start); - d.udata(DW_AT_high_pc, r[0].end - r[0].start); + if(r.size() == 1) { + d.reloc( + DW_AT_low_pc, DW_FORM_addr, obj.sectionSymbol(ObjectFile::Text), textOff + r[0].start); + return d.udata(DW_AT_high_pc, r[0].end - r[0].start); + } + String listSym = obj.sectionSymbol(ObjectFile::DebugRnglists); + d.reloc(DW_AT_ranges, DW_FORM_sec_offset, listSym, (I64)rnglists.size()); + for(const PcRange& p : r) { + rnglists.push_back(DW_LE_offset_pair); + le::uleb(rnglists, textOff + p.start); + le::uleb(rnglists, textOff + p.end); + } + rnglists.push_back(DW_LE_end_of_list); } void DieWriter::typeDie(const DebugType* t) { @@ -313,14 +334,19 @@ 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; + // a variable copy, named by its abstract die when it sits in inlined code. without a location + // gdb reports it as optimized out + void DieWriter::instDie(const ScopeTree& tree, U32 inst) { + const DebugVarInst& vi = tree.body.vars[inst]; + const DebugVar& v = *vi.var; if(!hasStorage(v)) return; Die d; - describeVar(d, v); - I32 frameOff = func.varFrameOff[inst]; + if(tree.inlinedIn[vi.scope]) + d.ref(DW_AT_abstract_origin, &v); + else + describeVar(d, v); + I32 frameOff = tree.func.varFrameOff[inst]; if(!v.symbol.empty()) { d.reloc(DW_AT_location, DW_FORM_exprloc, v.symbol, 0); } else if(frameOff != kNoFrameSlot) { @@ -331,22 +357,95 @@ namespace rat { emit(v.isParam ? DW_TAG_formal_parameter : DW_TAG_variable, false, d); } - void DieWriter::funcDie(const EncodedFunc& func) { + void DieWriter::scopeDie(const ScopeTree& tree, U32 scope) { + const DebugScope& s = tree.body.scopes[scope]; + Die d; + if(s.inlined) + d.ref(DW_AT_abstract_origin, s.inlined); + pcRanges(d, tree.func.scopes[scope], tree.func.textOff); + if(s.inlined) + d.srcLoc(DW_AT_call_file, tree.body.locs[s.callLoc].src); + B32 children = hasDies(tree, scope); + emit(s.inlined ? DW_TAG_inlined_subroutine : DW_TAG_lexical_block, children, d); + scopeChildren(tree, scope); + if(children) + info.push_back(0); + } + + // lexical blocks without code hand their vars and children to the parent, an inlined call + // without code shows nothing + void DieWriter::scopeChildren(const ScopeTree& tree, U32 scope) { + for(U32 inst : tree.vars[scope]) + instDie(tree, inst); + for(U32 kid : tree.kids[scope]) { + B32 hasCode = !tree.func.scopes[kid].empty(); + if(tree.body.scopes[kid].inlined) { + if(hasCode) + scopeDie(tree, kid); + } else if(hasCode && hasDies(tree, kid)) { + scopeDie(tree, kid); + } else { + scopeChildren(tree, kid); + } + } + } + + // whether scopeChildren emits anything + B32 DieWriter::hasDies(const ScopeTree& tree, U32 scope) { + for(U32 inst : tree.vars[scope]) + if(hasStorage(*tree.body.vars[inst].var)) + return true; + for(U32 kid : tree.kids[scope]) + if(tree.body.scopes[kid].inlined ? !tree.func.scopes[kid].empty() : hasDies(tree, kid)) + return true; + return false; + } + + ScopeTree DieWriter::indexScopes(const EncodedFunc& func) { const DebugBody& body = *func.fn->getDebug(); + U32 n = (U32)body.scopes.size(); + ScopeTree tree{func, body, List>(n), List>(n), List(n, 0)}; + for(U32 s = 1; s < n; ++s) { + const DebugScope& sc = body.scopes[s]; + tree.kids[sc.parent].push_back(s); + tree.inlinedIn[s] = sc.inlined ? s : tree.inlinedIn[sc.parent]; // parents come first + // every inlined func gets its abstract die + if(sc.inlined && dieOff.emplace(sc.inlined, 0).second) + pendingFuncs.push_back(sc.inlined); + } + for(U32 i = 0; i < body.vars.size(); ++i) + tree.vars[body.vars[i].scope].push_back(i); + return tree; + } + + void DieWriter::funcDie(const EncodedFunc& func) { + ScopeTree tree = indexScopes(func); Die d; - describeFunc(d, func.fn->getName(), *body.func, !func.fn->getAttrs().isInternal()); + describeFunc(d, func.fn->getName(), *tree.body.func, !func.fn->getAttrs().isInternal()); pcRanges(d, {{0, func.size}}, func.textOff); d.exprloc(DW_AT_frame_base, {DW_OP_call_frame_cfa}); - B32 variadic = body.func->variadic; - B32 children = variadic; - for(const DebugVarInst& v : body.vars) - children |= hasStorage(*v.var); + B32 variadic = tree.body.func->variadic; + B32 children = variadic || hasDies(tree, 0); emit(DW_TAG_subprogram, children, d); - for(U32 i = 0; i < body.vars.size(); ++i) - instDie(func, i); + scopeChildren(tree, 0); endChildren(variadic, children); } + // what inlined copies point at, names and types without code + void DieWriter::abstractDie(const DebugFunc* f) { + Die d; + describeFunc(d, f->name, *f, f->external); + d.udata(DW_AT_inline, DW_INL_inlined); + B32 children = !f->vars.empty() || f->variadic; + dieOff[f] = emit(DW_TAG_subprogram, children, d); + for(const UniquePtr& v : f->vars) { + Die var; + describeVar(var, *v); + dieOff[v.get()] = emit(v->isParam ? DW_TAG_formal_parameter : DW_TAG_variable, false, var); + } + endChildren(f->variadic, children); + } + void DieWriter::unit(const List& funcs) { const DebugInfo& di = *mod.getDebugInfo(); le::put32(info, 0); // unit_length @@ -363,11 +462,14 @@ namespace rat { cu.reloc(DW_AT_stmt_list, DW_FORM_sec_offset, obj.sectionSymbol(ObjectFile::DebugLine), 0); emit(DW_TAG_compile_unit, true, cu); + unitHeader(rnglists); for(const DebugVar& g : di.globals) globalDie(g); for(const EncodedFunc& func : funcs) if(func.fn->getDebug()) funcDie(func); + for(U32 i = 0; i < pendingFuncs.size(); ++i) + abstractDie(pendingFuncs[i]); // types last, emitting one may queue more for(U32 i = 0; i < pendingTypes.size(); ++i) typeDie(pendingTypes[i]); @@ -377,6 +479,7 @@ namespace rat { for(const auto& [at, target] : refs) le::patch32(info, at, dieOff[target]); le::patch32(info, 0, (U32)info.size() - 4); + le::patch32(rnglists, 0, (U32)rnglists.size() - 4); } // unit_length, version 5, address_size, segment_selector_size, then the line table's @@ -499,6 +602,7 @@ namespace rat { w.unit(funcs); obj.append(ObjectFile::DebugInfo, w.info.data(), (U32)w.info.size()); obj.append(ObjectFile::DebugAbbrev, w.abbrev.data(), (U32)w.abbrev.size()); + obj.append(ObjectFile::DebugRnglists, w.rnglists.data(), (U32)w.rnglists.size()); detail::writeLineTable(*di, funcs, obj); } } // namespace rat diff --git a/src/backend/target/dwarf.h b/src/backend/target/dwarf.h index 91cc309..9def548 100644 --- a/src/backend/target/dwarf.h +++ b/src/backend/target/dwarf.h @@ -41,7 +41,8 @@ namespace rat { U32 size; List cfi; List lines; - List varFrameOff; // per DebugBody var, its frame slot from the cfa or kNoFrameSlot + List> scopes; // per DebugBody scope, 0 unused + 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 @@ -51,7 +52,7 @@ namespace rat { struct Die { List decl; // attr and form pairs List body; - List> refs; // ref4 slots in body to the die of a type + List> refs; // ref4 slots in body to the die of a type, func or var List types; // referenced types, emitted later List relocs; // offsets in body @@ -69,6 +70,14 @@ namespace rat { void describeVar(Die& d, const DebugVar& v); void describeFunc(Die& d, const String& name, const DebugFunc& f, B32 external); + struct ScopeTree { + const EncodedFunc& func; + const DebugBody& body; + List> kids; // scope -> child scopes + List> vars; // scope -> var insts + List inlinedIn; // scope -> its nearest inlined scope, 0 = none + }; + struct DieWriter { DieWriter(const Module& m, ObjectFile& o) : mod(m), @@ -79,9 +88,11 @@ namespace rat { List info; List abbrev; Map abbrevCode; // encoded declaration -> code - Map dieOff; // type -> its die, 0 = queued + Map dieOff; // type, func or var -> its die, 0 = queued List> refs; // ref4 slots in info to patch List pendingTypes; + List pendingFuncs; // abstract instances of inlined funcs + List rnglists; U32 emit(U32 tag, B32 children, const Die& d); void pcRanges(Die& d, const List& r, U32 textOff); @@ -90,8 +101,13 @@ namespace rat { void endChildren(B32 variadic, B32 children); B32 hasStorage(const DebugVar& v); void globalDie(const DebugVar& v); - void instDie(const EncodedFunc& func, U32 inst); + void instDie(const ScopeTree& tree, U32 inst); + void scopeDie(const ScopeTree& tree, U32 scope); + void scopeChildren(const ScopeTree& tree, U32 scope); + B32 hasDies(const ScopeTree& tree, U32 scope); + ScopeTree indexScopes(const EncodedFunc& func); void funcDie(const EncodedFunc& func); + void abstractDie(const DebugFunc* f); void unit(const List& funcs); }; diff --git a/src/compiler/emit/emit.h b/src/compiler/emit/emit.h index c4cb395..17b9a01 100644 --- a/src/compiler/emit/emit.h +++ b/src/compiler/emit/emit.h @@ -350,6 +350,7 @@ namespace rat::cc { DebugType* debugWrap(DebugType::Kind kind, DebugType* elem); void debugFunction(Function& fn, const FuncDef* def); void debugSetLoc(Function& fn, U32 tok); + U32 debugEnterScope(Function& fn, const Stmt* s); void debugVar(Function& fn, DebugVar v, const Local& l); void debugLocal(Function& fn, const Declarator& d); void debugGlobal(const Declarator& d); diff --git a/src/compiler/emit/emit_debug.cpp b/src/compiler/emit/emit_debug.cpp index ef1c38d..c679d8b 100644 --- a/src/compiler/emit/emit_debug.cpp +++ b/src/compiler/emit/emit_debug.cpp @@ -154,6 +154,8 @@ namespace rat::cc { df.variadic = def->isVarArgs; fn.setDebug(std::make_unique()); fn.getDebug()->func = &df; + dbg.scope = 0; + dbg.funcBody = def->body; for(const Param& p : def->params) { Local l; if(!dbg.describeVars || !p.name || !func.scopes.lookup(*p.name, l)) @@ -164,15 +166,32 @@ namespace rat::cc { void Emitter::debugSetLoc(Function& fn, U32 tok) { if(dbg.info) - fn.setLoc(fn.getDebug()->addLoc(debugLoc(tok))); + fn.setLoc(fn.getDebug()->addLoc(debugLoc(tok), dbg.scope)); } - // records v with the frame slot l lives in, if any + // a nested block gets a scope when it declares something, so shadowed names resolve right + U32 Emitter::debugEnterScope(Function& fn, const Stmt* s) { + U32 outer = dbg.scope; + B32 declares = false; + if(dbg.info && s->kind == StmtKind::For) + declares = s->forInit && s->forInit->kind == StmtKind::Decl; + else if(dbg.info && s != dbg.funcBody) + for(const Stmt* child : s->body) + declares |= child->kind == StmtKind::Decl; + if(declares) { + List& scopes = fn.getDebug()->scopes; + scopes.push_back({outer}); + dbg.scope = (U32)scopes.size() - 1; + } + return outer; + } + + // records v in the current scope 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}); + body.vars.push_back({body.func->vars.back().get(), dbg.scope, slot}); } void Emitter::debugLocal(Function& fn, const Declarator& d) { diff --git a/src/compiler/emit/emit_state.h b/src/compiler/emit/emit_state.h index b2aaddc..5c2a807 100644 --- a/src/compiler/emit/emit_state.h +++ b/src/compiler/emit/emit_state.h @@ -132,10 +132,12 @@ 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 + B32 keepLocals = false; // every named local in a frame slot, so -O0 code is inspectable + B32 describeVars = false; // types and variables, else lines only + U32 scope = 0; // current DebugBody scope + const Stmt* funcBody = nullptr; // its scope is 0 + Map files; // token file index to DebugInfo file + Map bases; // by name std::map, DebugType*> types; // records, named and wrappers }; diff --git a/src/compiler/emit/emit_stmt.cpp b/src/compiler/emit/emit_stmt.cpp index bcabbf8..4c0dfb2 100644 --- a/src/compiler/emit/emit_stmt.cpp +++ b/src/compiler/emit/emit_stmt.cpp @@ -105,7 +105,9 @@ namespace rat::cc { B32 Emitter::emitFor(Function& fn, const Stmt* s) { func.scopes.push(); + U32 outerScope = debugEnterScope(fn, s); B32 ok = emitForScoped(fn, s); + dbg.scope = outerScope; func.scopes.pop(); return ok; } @@ -346,6 +348,7 @@ namespace rat::cc { B32 Emitter::emitCompound(Function& fn, const Stmt* s) { func.scopes.push(); + U32 outerScope = debugEnterScope(fn, s); Node* mark = nullptr; Node* outerSp = func.sp; if(blockDeclaresVla(s)) { @@ -375,6 +378,7 @@ namespace rat::cc { restoreStack(fn, mark); func.sp = mark ? mark : outerSp; func.scopes.pop(); + dbg.scope = outerScope; return true; } diff --git a/src/compiler/test/debug/scopes.O0.expect b/src/compiler/test/debug/scopes.O0.expect new file mode 100644 index 0000000..07087a2 --- /dev/null +++ b/src/compiler/test/debug/scopes.O0.expect @@ -0,0 +1,10 @@ +Breakpoint 1, square (x=0) at +#1 0x + in work (n=3) at +scopes.c:10 + in main () at +$1 = 1 +acc = 1 +i = 0 +acc = 0 +$2 = 99 diff --git a/src/compiler/test/debug/scopes.O1.expect b/src/compiler/test/debug/scopes.O1.expect new file mode 100644 index 0000000..12feb90 --- /dev/null +++ b/src/compiler/test/debug/scopes.O1.expect @@ -0,0 +1,5 @@ +Breakpoint 1, square (x= +#1 work (n= +scopes.c:17 +#2 0x + in main () at diff --git a/src/compiler/test/debug/scopes.c b/src/compiler/test/debug/scopes.c new file mode 100644 index 0000000..ecda502 --- /dev/null +++ b/src/compiler/test/debug/scopes.c @@ -0,0 +1,20 @@ +static int square(int x) { + int y = x * x; + return y + 1; +} + +__attribute__((noinline)) int work(int n) { + int acc = 0; + for(int i = 0; i < n; i++) { + int acc = i * 2; + acc += square(i); + n -= acc & 0; + } + { + int acc = 99; + n += acc - 99; + } + return acc + square(n); +} + +int main(void) { return work(3) & 1; } diff --git a/src/compiler/test/debug/scopes.gdb b/src/compiler/test/debug/scopes.gdb new file mode 100644 index 0000000..9d86068 --- /dev/null +++ b/src/compiler/test/debug/scopes.gdb @@ -0,0 +1,12 @@ +break square +run +bt +delete +break scopes.c:11 +continue +p acc +info locals +delete +break scopes.c:15 +continue +p acc