Index: llvm/lib/Target/WebAssembly/CMakeLists.txt =================================================================== --- llvm/lib/Target/WebAssembly/CMakeLists.txt +++ llvm/lib/Target/WebAssembly/CMakeLists.txt @@ -18,6 +18,7 @@ WebAssemblyAsmPrinter.cpp WebAssemblyCFGStackify.cpp WebAssemblyCFGSort.cpp + WebAssemblyDebugFixup.cpp WebAssemblyDebugValueManager.cpp WebAssemblyLateEHPrepare.cpp WebAssemblyExceptionInfo.cpp Index: llvm/lib/Target/WebAssembly/WebAssembly.h =================================================================== --- llvm/lib/Target/WebAssembly/WebAssembly.h +++ llvm/lib/Target/WebAssembly/WebAssembly.h @@ -51,6 +51,7 @@ FunctionPass *createWebAssemblyExplicitLocals(); FunctionPass *createWebAssemblyLowerBrUnless(); FunctionPass *createWebAssemblyRegNumbering(); +FunctionPass *createWebAssemblyDebugFixup(); FunctionPass *createWebAssemblyPeephole(); // PassRegistry initialization declarations. @@ -75,6 +76,7 @@ void initializeWebAssemblyExplicitLocalsPass(PassRegistry &); void initializeWebAssemblyLowerBrUnlessPass(PassRegistry &); void initializeWebAssemblyRegNumberingPass(PassRegistry &); +void initializeWebAssemblyDebugFixupPass(PassRegistry &); void initializeWebAssemblyPeepholePass(PassRegistry &); namespace WebAssembly { Index: llvm/lib/Target/WebAssembly/WebAssemblyDebugFixup.cpp =================================================================== --- /dev/null +++ llvm/lib/Target/WebAssembly/WebAssemblyDebugFixup.cpp @@ -0,0 +1,139 @@ +//===-- WebAssemblyDebugFixup.cpp - Debug Fixup ------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// Several prior passes may "stackify" registers, here we ensure any references +/// in such registers in debug_value instructions become stack relative also. +/// This is done in a separate pass such that not all previous passes need to +/// track stack depth when values get stackified. +/// +//===----------------------------------------------------------------------===// + +#include "MCTargetDesc/WebAssemblyMCTargetDesc.h" +#include "WebAssembly.h" +#include "WebAssemblyMachineFunctionInfo.h" +#include "WebAssemblySubtarget.h" +#include "WebAssemblyUtilities.h" +#include "llvm/ADT/SCCIterator.h" +#include "llvm/CodeGen/MachineFrameInfo.h" +#include "llvm/CodeGen/MachineFunction.h" +#include "llvm/CodeGen/MachineInstrBuilder.h" +#include "llvm/CodeGen/MachineLoopInfo.h" +#include "llvm/CodeGen/MachineRegisterInfo.h" +#include "llvm/CodeGen/Passes.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/raw_ostream.h" +using namespace llvm; + +#define DEBUG_TYPE "wasm-debug-fixup" + +namespace { +class WebAssemblyDebugFixup final : public MachineFunctionPass { + StringRef getPassName() const override { return "WebAssembly Debug Fixup"; } + + void getAnalysisUsage(AnalysisUsage &AU) const override { + AU.setPreservesCFG(); + MachineFunctionPass::getAnalysisUsage(AU); + } + + bool runOnMachineFunction(MachineFunction &MF) override; + +public: + static char ID; // Pass identification, replacement for typeid + WebAssemblyDebugFixup() : MachineFunctionPass(ID) {} +}; +} // end anonymous namespace + +char WebAssemblyDebugFixup::ID = 0; +INITIALIZE_PASS( + WebAssemblyDebugFixup, DEBUG_TYPE, + "Ensures debug_value's that have been stackified become stack relative", + false, false) + +FunctionPass *llvm::createWebAssemblyDebugFixup() { + return new WebAssemblyDebugFixup(); +} + +bool WebAssemblyDebugFixup::runOnMachineFunction(MachineFunction &MF) { + LLVM_DEBUG(dbgs() << "********** Debug Fixup **********\n" + "********** Function: " + << MF.getName() << '\n'); + + WebAssemblyFunctionInfo &MFI = *MF.getInfo(); + const auto *TII = MF.getSubtarget().getInstrInfo(); + + struct StackElem { + unsigned Reg; + MachineInstr *DebugValue; + }; + std::vector Stack; + for (MachineBasicBlock &MBB : MF) { + // We may insert into this list. + for (auto MII = MBB.begin(); MII != MBB.end(); ++MII) { + MachineInstr &MI = *MII; + if (MI.isDebugValue()) { + auto &MO = MI.getOperand(0); + // Also check if not a $noreg: likely a DBG_VALUE we just inserted. + if (MO.isReg() && MO.getReg().isValid() && + MFI.isVRegStackified(MO.getReg())) { + // Found a DBG_VALUE with a stackified register we will + // change into a stack operand. + // Search for register rather than assume it is on top (which it + // typically is if it appears right after the def), since + // DBG_VALUE's may shift under some circumstances. + size_t Depth = 0; + for (auto &Elem : Stack) { + if (MO.getReg() == Elem.Reg) { + LLVM_DEBUG(dbgs() << "Debug Value VReg " << MO.getReg() + << " -> Stack Relative " << Depth << "\n"); + MO.ChangeToTargetIndex(WebAssembly::TI_OPERAND_STACK, Depth); + // Save the DBG_VALUE instruction that defined this stackified + // variable since later we need it to construct another one on + // pop. + Elem.DebugValue = &MI; + break; + } + Depth++; + } + // If the Reg was not found, we have a DBG_VALUE outside of its + // def-use range, and we leave it unmodified as reg, which means + // it will be culled later. + } + } else { + // Track stack depth. + for (MachineOperand &MO : reverse(MI.explicit_uses())) { + if (MO.isReg() && MFI.isVRegStackified(MO.getReg())) { + auto Prev = Stack.back(); + Stack.pop_back(); + assert(Prev.Reg == MO.getReg() && + "WebAssemblyDebugFixup: Pop: Register not matched!"); + if (Prev.DebugValue) { + // This stackified reg is a variable that started life at + // Prev.DebugValue, so now that we're popping it we must insert + // a $noreg DBG_VALUE for the variable to end it, right after + // the current instruction. + BuildMI(*Prev.DebugValue->getParent(), std::next(MII), + MI.getDebugLoc(), TII->get(WebAssembly::DBG_VALUE), false, + Register(), Prev.DebugValue->getOperand(2).getMetadata(), + Prev.DebugValue->getOperand(3).getMetadata()); + } + } + } + for (MachineOperand &MO : MI.defs()) { + if (MO.isReg() && MFI.isVRegStackified(MO.getReg())) { + Stack.push_back({MO.getReg(), nullptr}); + } + } + } + } + assert(Stack.empty() && + "WebAssemblyDebugFixup: Stack not empty at end of basic block!"); + } + + return true; +} Index: llvm/lib/Target/WebAssembly/WebAssemblyExplicitLocals.cpp =================================================================== --- llvm/lib/Target/WebAssembly/WebAssemblyExplicitLocals.cpp +++ llvm/lib/Target/WebAssembly/WebAssemblyExplicitLocals.cpp @@ -31,16 +31,6 @@ #define DEBUG_TYPE "wasm-explicit-locals" -// A command-line option to disable this pass, and keep implicit locals -// for the purpose of testing with lit/llc ONLY. -// This produces output which is not valid WebAssembly, and is not supported -// by assemblers/disassemblers and other MC based tools. -static cl::opt WasmDisableExplicitLocals( - "wasm-disable-explicit-locals", cl::Hidden, - cl::desc("WebAssembly: output implicit locals in" - " instruction output for test purposes only."), - cl::init(false)); - namespace { class WebAssemblyExplicitLocals final : public MachineFunctionPass { StringRef getPassName() const override { @@ -211,10 +201,6 @@ "********** Function: " << MF.getName() << '\n'); - // Disable this pass if directed to do so. - if (WasmDisableExplicitLocals) - return false; - bool Changed = false; MachineRegisterInfo &MRI = MF.getRegInfo(); WebAssemblyFunctionInfo &MFI = *MF.getInfo(); Index: llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp =================================================================== --- llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp +++ llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp @@ -45,6 +45,16 @@ cl::desc("WebAssembly Emscripten-style setjmp/longjmp handling"), cl::init(false)); +// A command-line option to keep implicit locals +// for the purpose of testing with lit/llc ONLY. +// This produces output which is not valid WebAssembly, and is not supported +// by assemblers/disassemblers and other MC based tools. +static cl::opt WasmDisableExplicitLocals( + "wasm-disable-explicit-locals", cl::Hidden, + cl::desc("WebAssembly: output implicit locals in" + " instruction output for test purposes only."), + cl::init(false)); + extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeWebAssemblyTarget() { // Register the target. RegisterTargetMachine X( @@ -75,6 +85,7 @@ initializeWebAssemblyExplicitLocalsPass(PR); initializeWebAssemblyLowerBrUnlessPass(PR); initializeWebAssemblyRegNumberingPass(PR); + initializeWebAssemblyDebugFixupPass(PR); initializeWebAssemblyPeepholePass(PR); } @@ -466,7 +477,8 @@ addPass(createWebAssemblyCFGStackify()); // Insert explicit local.get and local.set operators. - addPass(createWebAssemblyExplicitLocals()); + if (!WasmDisableExplicitLocals) + addPass(createWebAssemblyExplicitLocals()); // Lower br_unless into br_if. addPass(createWebAssemblyLowerBrUnless()); @@ -477,6 +489,10 @@ // Create a mapping from LLVM CodeGen virtual registers to wasm registers. addPass(createWebAssemblyRegNumbering()); + + // Fix debug_values whose defs have been stackified. + if (!WasmDisableExplicitLocals) + addPass(createWebAssemblyDebugFixup()); } yaml::MachineFunctionInfo * Index: llvm/test/CodeGen/WebAssembly/stackified-debug.ll =================================================================== --- /dev/null +++ llvm/test/CodeGen/WebAssembly/stackified-debug.ll @@ -0,0 +1,92 @@ +; RUN: llc < %s | FileCheck %s + +; Input C code: + +; int i = input(); // Nested case +; int j = input(); // Trivial def-use. +; output(i, j); + +; The ll below generates 330 lines of .S, so relevant parts that the +; WebAssemblyDebugFixup pass affects: + +; CHECK: call input +; CHECK: .Ltmp0: +; CHECK: call input +; CHECK: .Ltmp1: +; CHECK: call output +; CHECK: .Ltmp2: + +; This defines variable "i" which is live on the stack between Ltmp0 and Ltmp2, +; 2 = TI_OPERAND_STACK and 0 = stack offset. + +; CHECK: .section .debug_loc,"",@ +; CHECK: .Ldebug_loc0: +; CHECK: .int32 .Ltmp0-.Lfunc_begin0 +; CHECK: .int32 .Ltmp2-.Lfunc_begin0 +; CHECK: .int16 4 # Loc expr size +; CHECK: .int8 237 # DW_OP_WASM_location +; CHECK: .int8 2 # 2 +; CHECK: .int8 0 # 0 +; CHECK: .int8 159 # DW_OP_stack_value + +; This defines variable "j" which is live on the stack between Ltmp1 and Ltmp2, +; 2 = TI_OPERAND_STACK and 1 = stack offset. + +; CHECK: .Ldebug_loc1: +; CHECK: .int32 .Ltmp1-.Lfunc_begin0 +; CHECK: .int32 .Ltmp2-.Lfunc_begin0 +; CHECK: .int16 4 # Loc expr size +; CHECK: .int8 237 # DW_OP_WASM_location +; CHECK: .int8 2 # 2 +; CHECK: .int8 1 # 1 +; CHECK: .int8 159 # DW_OP_stack_value + + + + +source_filename = "stackified.c" +target triple = "wasm32-unknown-unknown" + +define void @foo() !dbg !12 { +entry: + %call = call i32 @input(), !dbg !18 + call void @llvm.dbg.value(metadata i32 %call, metadata !16, metadata !DIExpression()), !dbg !19 + %call1 = call i32 @input(), !dbg !20 + call void @llvm.dbg.value(metadata i32 %call1, metadata !17, metadata !DIExpression()), !dbg !19 + call void @output(i32 %call, i32 %call1), !dbg !21 + ret void, !dbg !22 +} + +declare i32 @input() + +declare !dbg !4 void @output(i32, i32) + +declare void @llvm.dbg.value(metadata, metadata, metadata) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!8, !9, !10} +!llvm.ident = !{!11} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 11.0.0 (https://github.com/llvm/llvm-project.git ed7aaf832444411ce93aa0443425ce401f5c7a8e)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, retainedTypes: !3, nameTableKind: None) +!1 = !DIFile(filename: "stackified.c", directory: "C:\\stuff\\llvm-project") +!2 = !{} +!3 = !{!4} +!4 = !DISubprogram(name: "output", scope: !1, file: !1, line: 2, type: !5, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !2) +!5 = !DISubroutineType(types: !6) +!6 = !{null, !7, !7} +!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!8 = !{i32 7, !"Dwarf Version", i32 4} +!9 = !{i32 2, !"Debug Info Version", i32 3} +!10 = !{i32 1, !"wchar_size", i32 4} +!11 = !{!"clang version 11.0.0 (https://github.com/llvm/llvm-project.git ed7aaf832444411ce93aa0443425ce401f5c7a8e)"} +!12 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 3, type: !13, scopeLine: 3, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !15) +!13 = !DISubroutineType(types: !14) +!14 = !{null} +!15 = !{!16, !17} +!16 = !DILocalVariable(name: "i", scope: !12, file: !1, line: 4, type: !7) +!17 = !DILocalVariable(name: "j", scope: !12, file: !1, line: 5, type: !7) +!18 = !DILocation(line: 4, column: 11, scope: !12) +!19 = !DILocation(line: 0, scope: !12) +!20 = !DILocation(line: 5, column: 11, scope: !12) +!21 = !DILocation(line: 6, column: 3, scope: !12) +!22 = !DILocation(line: 7, column: 1, scope: !12)