diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyRegColoring.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyRegColoring.cpp index 5252db4858b97..4a6d37d7052e5 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyRegColoring.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyRegColoring.cpp @@ -72,6 +72,152 @@ static float computeWeight(const MachineRegisterInfo *MRI, return Weight; } +// Create a map of "Register -> vector of ". +// The SlotIndex is the slot index of the next non-debug instruction or the end +// of a BB, because DBG_VALUE's don't have slot index themselves. +// Adapted from RegisterCoalescer::buildVRegToDbgValueMap. +static DenseMap>> +buildVRegToDbgValueMap(MachineFunction &MF, const LiveIntervals *Liveness) { + DenseMap>> + DbgVRegToValues; + const SlotIndexes *Slots = Liveness->getSlotIndexes(); + SmallVector ToInsert; + + // After collecting a block of DBG_VALUEs into ToInsert, enter them into the + // map. + auto CloseNewDVRange = [&DbgVRegToValues, &ToInsert](SlotIndex Slot) { + for (auto *X : ToInsert) { + for (const auto &Op : X->debug_operands()) { + if (Op.isReg() && Op.getReg().isVirtual()) + DbgVRegToValues[Op.getReg()].push_back({Slot, X}); + } + } + + ToInsert.clear(); + }; + + // Iterate over all instructions, collecting them into the ToInsert vector. + // Once a non-debug instruction is found, record the slot index of the + // collected DBG_VALUEs. + for (auto &MBB : MF) { + SlotIndex CurrentSlot = Slots->getMBBStartIdx(&MBB); + + for (auto &MI : MBB) { + if (MI.isDebugValue()) { + if (any_of(MI.debug_operands(), [](const MachineOperand &MO) { + return MO.isReg() && MO.getReg().isVirtual(); + })) + ToInsert.push_back(&MI); + } else if (!MI.isDebugOrPseudoInstr()) { + CurrentSlot = Slots->getInstructionIndex(MI); + CloseNewDVRange(CurrentSlot); + } + } + + // Close range of DBG_VALUEs at the end of blocks. + CloseNewDVRange(Slots->getMBBEndIdx(&MBB)); + } + + // Sort all DBG_VALUEs we've seen by slot number. + for (auto &Pair : DbgVRegToValues) + llvm::sort(Pair.second); + return DbgVRegToValues; +} + +// After register coalescing, some DBG_VALUEs will be invalid. Set them undef. +// This function has to run before the actual coalescing, i.e., the register +// changes. +static void undefInvalidDbgValues( + const LiveIntervals *Liveness, + const ArrayRef> &Assignments, + DenseMap>> + &DbgVRegToValues) { +#ifndef NDEBUG + DenseSet SeenRegs; +#endif + for (size_t I = 0, E = Assignments.size(); I < E; ++I) { + const auto &CoalescedIntervals = Assignments[I]; + if (CoalescedIntervals.empty()) + continue; + for (LiveInterval *LI : CoalescedIntervals) { + Register Reg = LI->reg(); +#ifndef NDEBUG + // Ensure we don't process the same register twice + assert(SeenRegs.insert(Reg).second); +#endif + auto RegMapIt = DbgVRegToValues.find(Reg); + if (RegMapIt == DbgVRegToValues.end()) + continue; + SlotIndex LastSlot; + bool LastUndefResult = false; + for (auto [Slot, DbgValue] : RegMapIt->second) { + // All consecutive DBG_VALUEs have the same slot because the slot + // indices they have is the one for the first non-debug instruction + // after it, because DBG_VALUEs don't have slot index themselves. Before + // doing live range queries, quickly check if the current DBG_VALUE has + // the same slot index as the previous one, in which case we should do + // the same. Note that RegMapIt->second, the vector of {SlotIndex, + // DBG_VALUE}, is sorted by SlotIndex, which is necessary for this + // check. + if (Slot == LastSlot) { + if (LastUndefResult) { + LLVM_DEBUG(dbgs() << "Undefed: " << *DbgValue << "\n"); + DbgValue->setDebugValueUndef(); + } + continue; + } + LastSlot = Slot; + LastUndefResult = false; + for (LiveInterval *OtherLI : CoalescedIntervals) { + if (LI == OtherLI) + continue; + + // This DBG_VALUE has 'Reg' (the current LiveInterval's register) as + // its operand. If this DBG_VALUE's slot index is within other + // registers' live ranges, this DBG_VALUE should be undefed. For + // example, suppose %0 and %1 are to be coalesced into %0. + // ; %0's live range starts + // %0 = value_0 + // DBG_VALUE %0, !"a", ... (a) + // DBG_VALUE %1, !"b", ... (b) + // use %0 + // ; %0's live range ends + // ... + // ; %1's live range starts + // %1 = value_1 + // DBG_VALUE %0, !"c", ... (c) + // DBG_VALUE %1, !"d", ... (d) + // use %1 + // ; %1's live range ends + // + // In this code, (b) and (c) should be set to undef. After the two + // registers are coalesced, (b) will incorrectly say the variable + // "b"'s value is 'value_0', and (c) will also incorrectly say the + // variable "c"'s value is value_1. Note it doesn't actually matter + // which register they are coalesced into (%0 or %1); (b) and (c) + // should be set to undef as well if they are coalesced into %1. + // + // This happens DBG_VALUEs are not included when computing live + // ranges. + // + // Note that it is not possible for this DBG_VALUE to be + // simultaneously within 'Reg''s live range and one of other coalesced + // registers' live ranges because if their live ranges overlapped they + // would have not been selected as a coalescing candidate in the first + // place. + auto *SegmentIt = OtherLI->find(Slot); + if (SegmentIt != OtherLI->end() && SegmentIt->contains(Slot)) { + LLVM_DEBUG(dbgs() << "Undefed: " << *DbgValue << "\n"); + DbgValue->setDebugValueUndef(); + LastUndefResult = true; + break; + } + } + } + } + } +} + bool WebAssemblyRegColoring::runOnMachineFunction(MachineFunction &MF) { LLVM_DEBUG({ dbgs() << "********** Register Coloring **********\n" @@ -91,11 +237,17 @@ bool WebAssemblyRegColoring::runOnMachineFunction(MachineFunction &MF) { &getAnalysis(); WebAssemblyFunctionInfo &MFI = *MF.getInfo(); + // We don't preserve SSA form. + MRI->leaveSSA(); + // Gather all register intervals into a list and sort them. unsigned NumVRegs = MRI->getNumVirtRegs(); SmallVector SortedIntervals; SortedIntervals.reserve(NumVRegs); + // Record DBG_VALUEs and their SlotIndexes. + auto DbgVRegToValues = buildVRegToDbgValueMap(MF, Liveness); + LLVM_DEBUG(dbgs() << "Interesting register intervals:\n"); for (unsigned I = 0; I < NumVRegs; ++I) { Register VReg = Register::index2VirtReg(I); @@ -166,6 +318,9 @@ bool WebAssemblyRegColoring::runOnMachineFunction(MachineFunction &MF) { if (!Changed) return false; + // Set DBG_VALUEs that will be invalid after coalescing to undef. + undefInvalidDbgValues(Liveness, Assignments, DbgVRegToValues); + // Rewrite register operands. for (size_t I = 0, E = SortedIntervals.size(); I < E; ++I) { Register Old = SortedIntervals[I]->reg(); diff --git a/llvm/test/DebugInfo/WebAssembly/dbg-value-reg-coloring.mir b/llvm/test/DebugInfo/WebAssembly/dbg-value-reg-coloring.mir new file mode 100644 index 0000000000000..6c0c0e2861ef7 --- /dev/null +++ b/llvm/test/DebugInfo/WebAssembly/dbg-value-reg-coloring.mir @@ -0,0 +1,210 @@ +# RUN: llc -run-pass wasm-reg-coloring %s -o - | FileCheck %s + +# Tests for invalid DBG_VALUE set to undef after in RegColoring + +--- | + target triple = "wasm32-unknown-unknown" + + declare void @use(i32) + + define void @coalesce_test_0() { + call void @llvm.dbg.value(metadata i32 0, metadata !5, metadata !DIExpression()), !dbg !10 + ret void + } + define void @coalesce_test_1() { + unreachable + } + define void @coalesce_test_2() { + unreachable + } + define void @coalesce_test_3() { + unreachable + } + declare void @llvm.dbg.value(metadata, metadata, metadata) + + !llvm.dbg.cu = !{!0} + !llvm.module.flags = !{!2, !3, !4} + + !0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, emissionKind: FullDebug) + !1 = !DIFile(filename: "test.c", directory: "") + !2 = !{i32 7, !"Dwarf Version", i32 5} + !3 = !{i32 2, !"Debug Info Version", i32 3} + !4 = !{i32 1, !"wchar_size", i32 4} + !5 = !DILocalVariable(name: "var_a", scope: !6, file: !1, line: 2, type: !9) + !6 = distinct !DISubprogram(name: "coalesce_test_0", scope: !1, file: !1, line: 1, type: !7, scopeLine: 1, unit: !0) + !7 = !DISubroutineType(types: !8) + !8 = !{null} + !9 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) + !10 = !DILocation(line: 0, scope: !6) + !11 = !DILocalVariable(name: "var_b", scope: !6, file: !1, line: 2, type: !9) + !12 = !DILocalVariable(name: "var_c", scope: !6, file: !1, line: 2, type: !9) +... + +--- +# %0 and %1 are coalesced in this test +# CHECK-LABEL: name: coalesce_test_0 +name: coalesce_test_0 +liveins: + - { reg: '$arguments' } +tracksRegLiveness: true +body: | + bb.0: + liveins: $arguments + %0:i32 = CONST_I32 0, implicit-def $arguments + ; This should remain the same + DBG_VALUE %0, $noreg, !5, !DIExpression(), debug-location !10 + ; This should be set to undef, because this is within %0's live range + DBG_VALUE %1, $noreg, !10, !DIExpression(), debug-location !10 + CALL @use, %0, implicit-def $arguments + NOP implicit-def $arguments + %1:i32 = CONST_I32 1, implicit-def $arguments + ; This should be set to undef, because this is within %1's live range + DBG_VALUE %0, $noreg, !5, !DIExpression(), debug-location !10 + ; This should remain the same + DBG_VALUE %1, $noreg, !10, !DIExpression(), debug-location !10 + CALL @use, %1, implicit-def $arguments + RETURN implicit-def $arguments + + ; CHECK: %0:i32 = CONST_I32 0 + ; CHECK-NEXT: DBG_VALUE %0, $noreg + ; CHECK-NEXT: DBG_VALUE $noreg, $noreg + ; CHECK-NEXT: CALL @use, %0 + ; CHECK-NEXT: NOP + ; CHECK-NEXT: %0:i32 = CONST_I32 1 + ; CHECK-NEXT: DBG_VALUE $noreg, $noreg + ; CHECK-NEXT: DBG_VALUE %0, $noreg + ; CHECK-NEXT: CALL @use, %0 + ; CHECK-NEXT: RETURN implicit-def $arguments +... + +--- +# A similar test with above, only with more consecutive DBG_VALUEs. These +# consecutive DBG_VALUEs will be handled with a quick last result check. +# CHECK-LABEL: name: coalesce_test_1 +name: coalesce_test_1 +liveins: + - { reg: '$arguments' } +tracksRegLiveness: true +body: | + bb.0: + liveins: $arguments + %0:i32 = CONST_I32 0, implicit-def $arguments + ; All DBG_VALUE %1s in %0's live range will be set to undef + DBG_VALUE %1, $noreg, !10, !DIExpression(), debug-location !10 + DBG_VALUE %1, $noreg, !10, !DIExpression(), debug-location !10 + DBG_VALUE %0, $noreg, !5, !DIExpression(), debug-location !10 + DBG_VALUE %1, $noreg, !10, !DIExpression(), debug-location !10 + CALL @use, %0, implicit-def $arguments + NOP implicit-def $arguments + %1:i32 = CONST_I32 1, implicit-def $arguments + ; All DBG_VALUE %0s in %1's live range will be set to undef + DBG_VALUE %0, $noreg, !5, !DIExpression(), debug-location !10 + DBG_VALUE %0, $noreg, !5, !DIExpression(), debug-location !10 + DBG_VALUE %1, $noreg, !10, !DIExpression(), debug-location !10 + DBG_VALUE %0, $noreg, !5, !DIExpression(), debug-location !10 + CALL @use, %1, implicit-def $arguments + RETURN implicit-def $arguments + + ; CHECK: %0:i32 = CONST_I32 0 + ; CHECK-NEXT: DBG_VALUE $noreg, $noreg + ; CHECK-NEXT: DBG_VALUE $noreg, $noreg + ; CHECK-NEXT: DBG_VALUE %0, $noreg + ; CHECK-NEXT: DBG_VALUE $noreg, $noreg + ; CHECK-NEXT: CALL @use, %0 + ; CHECK-NEXT: NOP + ; CHECK-NEXT: %0:i32 = CONST_I32 1 + ; CHECK-NEXT: DBG_VALUE $noreg, $noreg + ; CHECK-NEXT: DBG_VALUE $noreg, $noreg + ; CHECK-NEXT: DBG_VALUE %0, $noreg + ; CHECK-NEXT: DBG_VALUE $noreg, $noreg + ; CHECK-NEXT: CALL @use, %0 + ; CHECK-NEXT: RETURN implicit-def $arguments +... + +--- +# The same with coalesce_test_0, but the two registers' live ranges are in +# different BBs. +# CHECK-LABEL: name: coalesce_test_2 +name: coalesce_test_2 +liveins: + - { reg: '$arguments' } +tracksRegLiveness: true +body: | + bb.0: + liveins: $arguments + %0:i32 = CONST_I32 0, implicit-def $arguments + DBG_VALUE %0, $noreg, !5, !DIExpression(), debug-location !10 + DBG_VALUE %1, $noreg, !10, !DIExpression(), debug-location !10 + CALL @use, %0, implicit-def $arguments + BR %bb.1, implicit-def $arguments + + ; CHECK: bb.0: + ; CHECK: %0:i32 = CONST_I32 0 + ; CHECK-NEXT: DBG_VALUE %0, $noreg + ; CHECK-NEXT: DBG_VALUE $noreg, $noreg + ; CHECK-NEXT: CALL @use, %0 + ; CHECK-NEXT: BR %bb.1 + + bb.1: + %1:i32 = CONST_I32 1, implicit-def $arguments + DBG_VALUE %0, $noreg, !5, !DIExpression(), debug-location !10 + DBG_VALUE %1, $noreg, !10, !DIExpression(), debug-location !10 + CALL @use, %1, implicit-def $arguments + RETURN implicit-def $arguments + + ; CHECK: bb.1: + ; CHECK-NEXT: %0:i32 = CONST_I32 1 + ; CHECK-NEXT: DBG_VALUE $noreg, $noreg + ; CHECK-NEXT: DBG_VALUE %0, $noreg + ; CHECK-NEXT: CALL @use, %0 + ; CHECK-NEXT: RETURN implicit-def $arguments +... + +--- +# Same test with three registers. +# CHECK-LABEL: name: coalesce_test_3 +name: coalesce_test_3 +liveins: + - { reg: '$arguments' } +tracksRegLiveness: true +body: | + bb.0: + liveins: $arguments + %0:i32 = CONST_I32 0, implicit-def $arguments + DBG_VALUE %0, $noreg, !5, !DIExpression(), debug-location !10 + DBG_VALUE %1, $noreg, !10, !DIExpression(), debug-location !10 + DBG_VALUE %2, $noreg, !11, !DIExpression(), debug-location !10 + CALL @use, %0, implicit-def $arguments + NOP implicit-def $arguments + %1:i32 = CONST_I32 1, implicit-def $arguments + DBG_VALUE %0, $noreg, !5, !DIExpression(), debug-location !10 + DBG_VALUE %1, $noreg, !10, !DIExpression(), debug-location !10 + DBG_VALUE %2, $noreg, !11, !DIExpression(), debug-location !10 + CALL @use, %1, implicit-def $arguments + NOP implicit-def $arguments + %2:i32 = CONST_I32 2, implicit-def $arguments + DBG_VALUE %0, $noreg, !5, !DIExpression(), debug-location !10 + DBG_VALUE %1, $noreg, !10, !DIExpression(), debug-location !10 + DBG_VALUE %2, $noreg, !11, !DIExpression(), debug-location !10 + CALL @use, %2, implicit-def $arguments + RETURN implicit-def $arguments + + ; CHECK: %0:i32 = CONST_I32 0 + ; CHECK-NEXT: DBG_VALUE %0, $noreg + ; CHECK-NEXT: DBG_VALUE $noreg, $noreg + ; CHECK-NEXT: DBG_VALUE $noreg, $noreg + ; CHECK-NEXT: CALL @use, %0 + ; CHECK-NEXT: NOP + ; CHECK-NEXT: %0:i32 = CONST_I32 1 + ; CHECK-NEXT: DBG_VALUE $noreg, $noreg + ; CHECK-NEXT: DBG_VALUE %0, $noreg + ; CHECK-NEXT: DBG_VALUE $noreg, $noreg + ; CHECK-NEXT: CALL @use, %0 + ; CHECK-NEXT: NOP + ; CHECK-NEXT: %0:i32 = CONST_I32 2 + ; CHECK-NEXT: DBG_VALUE $noreg, $noreg + ; CHECK-NEXT: DBG_VALUE $noreg, $noreg + ; CHECK-NEXT: DBG_VALUE %0, $noreg + ; CHECK-NEXT: CALL @use, %0 + ; CHECK-NEXT: RETURN implicit-def $arguments +---