| 1 | //===-- WebAssemblyFrameLowering.cpp - WebAssembly Frame Lowering ----------==// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | /// |
| 9 | /// \file |
| 10 | /// This file contains the WebAssembly implementation of |
| 11 | /// TargetFrameLowering class. |
| 12 | /// |
| 13 | /// On WebAssembly, there aren't a lot of things to do here. There are no |
| 14 | /// callee-saved registers to save, and no spill slots. |
| 15 | /// |
| 16 | /// The stack grows downward. |
| 17 | /// |
| 18 | //===----------------------------------------------------------------------===// |
| 19 | |
| 20 | #include "WebAssemblyFrameLowering.h" |
| 21 | #include "MCTargetDesc/WebAssemblyMCTargetDesc.h" |
| 22 | #include "Utils/WebAssemblyTypeUtilities.h" |
| 23 | #include "WebAssembly.h" |
| 24 | #include "WebAssemblyInstrInfo.h" |
| 25 | #include "WebAssemblyMachineFunctionInfo.h" |
| 26 | #include "WebAssemblySubtarget.h" |
| 27 | #include "WebAssemblyTargetMachine.h" |
| 28 | #include "llvm/CodeGen/Analysis.h" |
| 29 | #include "llvm/CodeGen/MachineFrameInfo.h" |
| 30 | #include "llvm/CodeGen/MachineFunction.h" |
| 31 | #include "llvm/CodeGen/MachineInstrBuilder.h" |
| 32 | #include "llvm/CodeGen/MachineRegisterInfo.h" |
| 33 | #include "llvm/IR/Instructions.h" |
| 34 | #include "llvm/MC/MCAsmInfo.h" |
| 35 | using namespace llvm; |
| 36 | |
| 37 | #define DEBUG_TYPE "wasm-frame-info" |
| 38 | |
| 39 | // TODO: wasm64 |
| 40 | // TODO: Emit TargetOpcode::CFI_INSTRUCTION instructions |
| 41 | |
| 42 | // In an ideal world, when objects are added to the MachineFrameInfo by |
| 43 | // FunctionLoweringInfo::set, we could somehow hook into target-specific code to |
| 44 | // ensure they are assigned the right stack ID. However there isn't a hook that |
| 45 | // runs between then and DAG building time, though, so instead we hoist stack |
| 46 | // objects lazily when they are first used, and comprehensively after the DAG is |
| 47 | // built via the PreprocessISelDAG hook, called by the |
| 48 | // SelectionDAGISel::runOnMachineFunction. We have to do it in two places |
| 49 | // because we want to do it while building the selection DAG for uses of alloca, |
| 50 | // but not all alloca instructions are used so we have to follow up afterwards. |
| 51 | std::optional<unsigned> |
| 52 | WebAssemblyFrameLowering::getLocalForStackObject(MachineFunction &MF, |
| 53 | int FrameIndex) { |
| 54 | MachineFrameInfo &MFI = MF.getFrameInfo(); |
| 55 | |
| 56 | // If already hoisted to a local, done. |
| 57 | if (MFI.getStackID(ObjectIdx: FrameIndex) == TargetStackID::WasmLocal) |
| 58 | return static_cast<unsigned>(MFI.getObjectOffset(ObjectIdx: FrameIndex)); |
| 59 | |
| 60 | // If not allocated in the object address space, this object will be in |
| 61 | // linear memory. |
| 62 | const AllocaInst *AI = MFI.getObjectAllocation(ObjectIdx: FrameIndex); |
| 63 | if (!AI || !WebAssembly::isWasmVarAddressSpace(AS: AI->getAddressSpace())) |
| 64 | return std::nullopt; |
| 65 | |
| 66 | // Otherwise, allocate this object in the named value stack, outside of linear |
| 67 | // memory. |
| 68 | SmallVector<EVT, 4> ValueVTs; |
| 69 | const WebAssemblyTargetLowering &TLI = |
| 70 | *MF.getSubtarget<WebAssemblySubtarget>().getTargetLowering(); |
| 71 | WebAssemblyFunctionInfo *FuncInfo = MF.getInfo<WebAssemblyFunctionInfo>(); |
| 72 | ComputeValueVTs(TLI, DL: MF.getDataLayout(), Ty: AI->getAllocatedType(), ValueVTs); |
| 73 | MFI.setStackID(ObjectIdx: FrameIndex, ID: TargetStackID::WasmLocal); |
| 74 | // Abuse SP offset to record the index of the first local in the object. |
| 75 | unsigned Local = FuncInfo->getParams().size() + FuncInfo->getLocals().size(); |
| 76 | MFI.setObjectOffset(ObjectIdx: FrameIndex, SPOffset: Local); |
| 77 | // Allocate WebAssembly locals for each non-aggregate component of the |
| 78 | // allocation. |
| 79 | for (EVT ValueVT : ValueVTs) |
| 80 | FuncInfo->addLocal(VT: ValueVT.getSimpleVT()); |
| 81 | // Abuse object size to record number of WebAssembly locals allocated to |
| 82 | // this object. |
| 83 | MFI.setObjectSize(ObjectIdx: FrameIndex, Size: ValueVTs.size()); |
| 84 | return static_cast<unsigned>(Local); |
| 85 | } |
| 86 | |
| 87 | /// We need a base pointer in the case of having items on the stack that |
| 88 | /// require stricter alignment than the stack pointer itself. Because we need |
| 89 | /// to shift the stack pointer by some unknown amount to force the alignment, |
| 90 | /// we need to record the value of the stack pointer on entry to the function. |
| 91 | bool WebAssemblyFrameLowering::hasBP(const MachineFunction &MF) const { |
| 92 | const auto *RegInfo = |
| 93 | MF.getSubtarget<WebAssemblySubtarget>().getRegisterInfo(); |
| 94 | return RegInfo->hasStackRealignment(MF); |
| 95 | } |
| 96 | |
| 97 | /// Return true if the specified function should have a dedicated frame pointer |
| 98 | /// register. |
| 99 | bool WebAssemblyFrameLowering::hasFPImpl(const MachineFunction &MF) const { |
| 100 | const MachineFrameInfo &MFI = MF.getFrameInfo(); |
| 101 | |
| 102 | // When we have var-sized objects, we move the stack pointer by an unknown |
| 103 | // amount, and need to emit a frame pointer to restore the stack to where we |
| 104 | // were on function entry. |
| 105 | // If we already need a base pointer, we use that to fix up the stack pointer. |
| 106 | // If there are no fixed-size objects, we would have no use of a frame |
| 107 | // pointer, and thus should not emit one. |
| 108 | bool HasFixedSizedObjects = MFI.getStackSize() > 0; |
| 109 | bool NeedsFixedReference = !hasBP(MF) || HasFixedSizedObjects; |
| 110 | |
| 111 | return MFI.isFrameAddressTaken() || |
| 112 | (MFI.hasVarSizedObjects() && NeedsFixedReference) || |
| 113 | MFI.hasStackMap() || MFI.hasPatchPoint(); |
| 114 | } |
| 115 | |
| 116 | /// Under normal circumstances, when a frame pointer is not required, we reserve |
| 117 | /// argument space for call sites in the function immediately on entry to the |
| 118 | /// current function. This eliminates the need for add/sub sp brackets around |
| 119 | /// call sites. Returns true if the call frame is included as part of the stack |
| 120 | /// frame. |
| 121 | bool WebAssemblyFrameLowering::hasReservedCallFrame( |
| 122 | const MachineFunction &MF) const { |
| 123 | return !MF.getFrameInfo().hasVarSizedObjects(); |
| 124 | } |
| 125 | |
| 126 | // Returns true if this function needs a local user-space stack pointer for its |
| 127 | // local frame (not for exception handling). |
| 128 | bool WebAssemblyFrameLowering::needsSPForLocalFrame( |
| 129 | const MachineFunction &MF) const { |
| 130 | auto &MFI = MF.getFrameInfo(); |
| 131 | auto &MRI = MF.getRegInfo(); |
| 132 | // llvm.stacksave can explicitly read SP register and it can appear without |
| 133 | // dynamic alloca. |
| 134 | bool HasExplicitSPUse = |
| 135 | any_of(Range: MRI.use_operands(Reg: getSPReg(MF)), |
| 136 | P: [](MachineOperand &MO) { return !MO.isImplicit(); }); |
| 137 | |
| 138 | return MFI.getStackSize() || MFI.adjustsStack() || hasFP(MF) || |
| 139 | HasExplicitSPUse; |
| 140 | } |
| 141 | |
| 142 | // In function with EH pads, we need to make a copy of the value of |
| 143 | // __stack_pointer global in SP32/64 register, in order to use it when |
| 144 | // restoring __stack_pointer after an exception is caught. |
| 145 | bool WebAssemblyFrameLowering::needsPrologForEH( |
| 146 | const MachineFunction &MF) const { |
| 147 | auto EHType = MF.getTarget().getMCAsmInfo()->getExceptionHandlingType(); |
| 148 | return EHType == ExceptionHandling::Wasm && |
| 149 | MF.getFunction().hasPersonalityFn() && MF.getFrameInfo().hasCalls(); |
| 150 | } |
| 151 | |
| 152 | /// Returns true if this function needs a local user-space stack pointer. |
| 153 | /// Unlike a machine stack pointer, the wasm user stack pointer is a global |
| 154 | /// variable, so it is loaded into a register in the prolog. |
| 155 | bool WebAssemblyFrameLowering::needsSP(const MachineFunction &MF) const { |
| 156 | return needsSPForLocalFrame(MF) || needsPrologForEH(MF); |
| 157 | } |
| 158 | |
| 159 | /// Returns true if the local user-space stack pointer needs to be written back |
| 160 | /// to __stack_pointer global by this function (this is not meaningful if |
| 161 | /// needsSP is false). If false, the stack red zone can be used and only a local |
| 162 | /// SP is needed. |
| 163 | bool WebAssemblyFrameLowering::needsSPWriteback( |
| 164 | const MachineFunction &MF) const { |
| 165 | auto &MFI = MF.getFrameInfo(); |
| 166 | assert(needsSP(MF)); |
| 167 | // When we don't need a local stack pointer for its local frame but only to |
| 168 | // support EH, we don't need to write SP back in the epilog, because we don't |
| 169 | // bump down the stack pointer in the prolog. We need to write SP back in the |
| 170 | // epilog only if |
| 171 | // 1. We need SP not only for EH support but also because we actually use |
| 172 | // stack or we have a frame address taken. |
| 173 | // 2. We cannot use the red zone. |
| 174 | bool CanUseRedZone = MFI.getStackSize() <= RedZoneSize && !MFI.hasCalls() && |
| 175 | !MF.getFunction().hasFnAttribute(Kind: Attribute::NoRedZone); |
| 176 | return needsSPForLocalFrame(MF) && !CanUseRedZone; |
| 177 | } |
| 178 | |
| 179 | unsigned WebAssemblyFrameLowering::getSPReg(const MachineFunction &MF) { |
| 180 | return MF.getSubtarget<WebAssemblySubtarget>().hasAddr64() |
| 181 | ? WebAssembly::SP64 |
| 182 | : WebAssembly::SP32; |
| 183 | } |
| 184 | |
| 185 | unsigned WebAssemblyFrameLowering::getFPReg(const MachineFunction &MF) { |
| 186 | return MF.getSubtarget<WebAssemblySubtarget>().hasAddr64() |
| 187 | ? WebAssembly::FP64 |
| 188 | : WebAssembly::FP32; |
| 189 | } |
| 190 | |
| 191 | unsigned |
| 192 | WebAssemblyFrameLowering::getOpcConst(const MachineFunction &MF) { |
| 193 | return MF.getSubtarget<WebAssemblySubtarget>().hasAddr64() |
| 194 | ? WebAssembly::CONST_I64 |
| 195 | : WebAssembly::CONST_I32; |
| 196 | } |
| 197 | |
| 198 | unsigned WebAssemblyFrameLowering::getOpcAdd(const MachineFunction &MF) { |
| 199 | return MF.getSubtarget<WebAssemblySubtarget>().hasAddr64() |
| 200 | ? WebAssembly::ADD_I64 |
| 201 | : WebAssembly::ADD_I32; |
| 202 | } |
| 203 | |
| 204 | unsigned WebAssemblyFrameLowering::getOpcSub(const MachineFunction &MF) { |
| 205 | return MF.getSubtarget<WebAssemblySubtarget>().hasAddr64() |
| 206 | ? WebAssembly::SUB_I64 |
| 207 | : WebAssembly::SUB_I32; |
| 208 | } |
| 209 | |
| 210 | unsigned WebAssemblyFrameLowering::getOpcAnd(const MachineFunction &MF) { |
| 211 | return MF.getSubtarget<WebAssemblySubtarget>().hasAddr64() |
| 212 | ? WebAssembly::AND_I64 |
| 213 | : WebAssembly::AND_I32; |
| 214 | } |
| 215 | |
| 216 | unsigned |
| 217 | WebAssemblyFrameLowering::getOpcGlobGet(const MachineFunction &MF) { |
| 218 | return MF.getSubtarget<WebAssemblySubtarget>().hasAddr64() |
| 219 | ? WebAssembly::GLOBAL_GET_I64 |
| 220 | : WebAssembly::GLOBAL_GET_I32; |
| 221 | } |
| 222 | |
| 223 | unsigned |
| 224 | WebAssemblyFrameLowering::getOpcGlobSet(const MachineFunction &MF) { |
| 225 | return MF.getSubtarget<WebAssemblySubtarget>().hasAddr64() |
| 226 | ? WebAssembly::GLOBAL_SET_I64 |
| 227 | : WebAssembly::GLOBAL_SET_I32; |
| 228 | } |
| 229 | |
| 230 | void WebAssemblyFrameLowering::writeSPToGlobal( |
| 231 | unsigned SrcReg, MachineFunction &MF, MachineBasicBlock &MBB, |
| 232 | MachineBasicBlock::iterator &InsertStore, const DebugLoc &DL) const { |
| 233 | const auto *TII = MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo(); |
| 234 | |
| 235 | const char *ES = "__stack_pointer" ; |
| 236 | auto *SPSymbol = MF.createExternalSymbolName(Name: ES); |
| 237 | |
| 238 | BuildMI(BB&: MBB, I: InsertStore, MIMD: DL, MCID: TII->get(Opcode: getOpcGlobSet(MF))) |
| 239 | .addExternalSymbol(FnName: SPSymbol) |
| 240 | .addReg(RegNo: SrcReg); |
| 241 | } |
| 242 | |
| 243 | MachineBasicBlock::iterator |
| 244 | WebAssemblyFrameLowering::eliminateCallFramePseudoInstr( |
| 245 | MachineFunction &MF, MachineBasicBlock &MBB, |
| 246 | MachineBasicBlock::iterator I) const { |
| 247 | assert(!I->getOperand(0).getImm() && (hasFP(MF) || hasBP(MF)) && |
| 248 | "Call frame pseudos should only be used for dynamic stack adjustment" ); |
| 249 | auto &ST = MF.getSubtarget<WebAssemblySubtarget>(); |
| 250 | const auto *TII = ST.getInstrInfo(); |
| 251 | if (I->getOpcode() == TII->getCallFrameDestroyOpcode() && |
| 252 | needsSPWriteback(MF)) { |
| 253 | DebugLoc DL = I->getDebugLoc(); |
| 254 | writeSPToGlobal(SrcReg: getSPReg(MF), MF, MBB, InsertStore&: I, DL); |
| 255 | } |
| 256 | return MBB.erase(I); |
| 257 | } |
| 258 | |
| 259 | void WebAssemblyFrameLowering::emitPrologue(MachineFunction &MF, |
| 260 | MachineBasicBlock &MBB) const { |
| 261 | // TODO: Do ".setMIFlag(MachineInstr::FrameSetup)" on emitted instructions |
| 262 | auto &MFI = MF.getFrameInfo(); |
| 263 | assert(MFI.getCalleeSavedInfo().empty() && |
| 264 | "WebAssembly should not have callee-saved registers" ); |
| 265 | |
| 266 | if (!needsSP(MF)) |
| 267 | return; |
| 268 | uint64_t StackSize = MFI.getStackSize(); |
| 269 | |
| 270 | auto &ST = MF.getSubtarget<WebAssemblySubtarget>(); |
| 271 | const auto *TII = ST.getInstrInfo(); |
| 272 | auto &MRI = MF.getRegInfo(); |
| 273 | |
| 274 | auto InsertPt = MBB.begin(); |
| 275 | while (InsertPt != MBB.end() && |
| 276 | WebAssembly::isArgument(Opc: InsertPt->getOpcode())) |
| 277 | ++InsertPt; |
| 278 | DebugLoc DL; |
| 279 | |
| 280 | const TargetRegisterClass *PtrRC = |
| 281 | MRI.getTargetRegisterInfo()->getPointerRegClass(MF); |
| 282 | unsigned SPReg = getSPReg(MF); |
| 283 | if (StackSize) |
| 284 | SPReg = MRI.createVirtualRegister(RegClass: PtrRC); |
| 285 | |
| 286 | const char *ES = "__stack_pointer" ; |
| 287 | auto *SPSymbol = MF.createExternalSymbolName(Name: ES); |
| 288 | BuildMI(BB&: MBB, I: InsertPt, MIMD: DL, MCID: TII->get(Opcode: getOpcGlobGet(MF)), DestReg: SPReg) |
| 289 | .addExternalSymbol(FnName: SPSymbol); |
| 290 | |
| 291 | bool HasBP = hasBP(MF); |
| 292 | if (HasBP) { |
| 293 | auto FI = MF.getInfo<WebAssemblyFunctionInfo>(); |
| 294 | Register BasePtr = MRI.createVirtualRegister(RegClass: PtrRC); |
| 295 | FI->setBasePointerVreg(BasePtr); |
| 296 | BuildMI(BB&: MBB, I: InsertPt, MIMD: DL, MCID: TII->get(Opcode: WebAssembly::COPY), DestReg: BasePtr) |
| 297 | .addReg(RegNo: SPReg); |
| 298 | } |
| 299 | if (StackSize) { |
| 300 | // Subtract the frame size |
| 301 | Register OffsetReg = MRI.createVirtualRegister(RegClass: PtrRC); |
| 302 | BuildMI(BB&: MBB, I: InsertPt, MIMD: DL, MCID: TII->get(Opcode: getOpcConst(MF)), DestReg: OffsetReg) |
| 303 | .addImm(Val: StackSize); |
| 304 | BuildMI(BB&: MBB, I: InsertPt, MIMD: DL, MCID: TII->get(Opcode: getOpcSub(MF)), DestReg: getSPReg(MF)) |
| 305 | .addReg(RegNo: SPReg) |
| 306 | .addReg(RegNo: OffsetReg); |
| 307 | } |
| 308 | if (HasBP) { |
| 309 | Register BitmaskReg = MRI.createVirtualRegister(RegClass: PtrRC); |
| 310 | Align Alignment = MFI.getMaxAlign(); |
| 311 | BuildMI(BB&: MBB, I: InsertPt, MIMD: DL, MCID: TII->get(Opcode: getOpcConst(MF)), DestReg: BitmaskReg) |
| 312 | .addImm(Val: (int64_t) ~(Alignment.value() - 1)); |
| 313 | BuildMI(BB&: MBB, I: InsertPt, MIMD: DL, MCID: TII->get(Opcode: getOpcAnd(MF)), DestReg: getSPReg(MF)) |
| 314 | .addReg(RegNo: getSPReg(MF)) |
| 315 | .addReg(RegNo: BitmaskReg); |
| 316 | } |
| 317 | if (hasFP(MF)) { |
| 318 | // Unlike most conventional targets (where FP points to the saved FP), |
| 319 | // FP points to the bottom of the fixed-size locals, so we can use positive |
| 320 | // offsets in load/store instructions. |
| 321 | BuildMI(BB&: MBB, I: InsertPt, MIMD: DL, MCID: TII->get(Opcode: WebAssembly::COPY), DestReg: getFPReg(MF)) |
| 322 | .addReg(RegNo: getSPReg(MF)); |
| 323 | } |
| 324 | if (StackSize && needsSPWriteback(MF)) { |
| 325 | writeSPToGlobal(SrcReg: getSPReg(MF), MF, MBB, InsertStore&: InsertPt, DL); |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | void WebAssemblyFrameLowering::emitEpilogue(MachineFunction &MF, |
| 330 | MachineBasicBlock &MBB) const { |
| 331 | uint64_t StackSize = MF.getFrameInfo().getStackSize(); |
| 332 | if (!needsSP(MF) || !needsSPWriteback(MF)) |
| 333 | return; |
| 334 | auto &ST = MF.getSubtarget<WebAssemblySubtarget>(); |
| 335 | const auto *TII = ST.getInstrInfo(); |
| 336 | auto &MRI = MF.getRegInfo(); |
| 337 | auto InsertPt = MBB.getFirstTerminator(); |
| 338 | DebugLoc DL; |
| 339 | |
| 340 | if (InsertPt != MBB.end()) |
| 341 | DL = InsertPt->getDebugLoc(); |
| 342 | |
| 343 | // Restore the stack pointer. If we had fixed-size locals, add the offset |
| 344 | // subtracted in the prolog. |
| 345 | unsigned SPReg = 0; |
| 346 | unsigned SPFPReg = hasFP(MF) ? getFPReg(MF) : getSPReg(MF); |
| 347 | if (hasBP(MF)) { |
| 348 | auto FI = MF.getInfo<WebAssemblyFunctionInfo>(); |
| 349 | SPReg = FI->getBasePointerVreg(); |
| 350 | } else if (StackSize) { |
| 351 | const TargetRegisterClass *PtrRC = |
| 352 | MRI.getTargetRegisterInfo()->getPointerRegClass(MF); |
| 353 | Register OffsetReg = MRI.createVirtualRegister(RegClass: PtrRC); |
| 354 | BuildMI(BB&: MBB, I: InsertPt, MIMD: DL, MCID: TII->get(Opcode: getOpcConst(MF)), DestReg: OffsetReg) |
| 355 | .addImm(Val: StackSize); |
| 356 | // In the epilog we don't need to write the result back to the SP32/64 |
| 357 | // physreg because it won't be used again. We can use a stackified register |
| 358 | // instead. |
| 359 | SPReg = MRI.createVirtualRegister(RegClass: PtrRC); |
| 360 | BuildMI(BB&: MBB, I: InsertPt, MIMD: DL, MCID: TII->get(Opcode: getOpcAdd(MF)), DestReg: SPReg) |
| 361 | .addReg(RegNo: SPFPReg) |
| 362 | .addReg(RegNo: OffsetReg); |
| 363 | } else { |
| 364 | SPReg = SPFPReg; |
| 365 | } |
| 366 | |
| 367 | writeSPToGlobal(SrcReg: SPReg, MF, MBB, InsertStore&: InsertPt, DL); |
| 368 | } |
| 369 | |
| 370 | bool WebAssemblyFrameLowering::isSupportedStackID( |
| 371 | TargetStackID::Value ID) const { |
| 372 | // Use the Object stack for WebAssembly locals which can only be accessed |
| 373 | // by name, not via an address in linear memory. |
| 374 | if (ID == TargetStackID::WasmLocal) |
| 375 | return true; |
| 376 | |
| 377 | return TargetFrameLowering::isSupportedStackID(ID); |
| 378 | } |
| 379 | |
| 380 | TargetFrameLowering::DwarfFrameBase |
| 381 | WebAssemblyFrameLowering::getDwarfFrameBase(const MachineFunction &MF) const { |
| 382 | DwarfFrameBase Loc; |
| 383 | Loc.Kind = DwarfFrameBase::WasmFrameBase; |
| 384 | const WebAssemblyFunctionInfo &MFI = *MF.getInfo<WebAssemblyFunctionInfo>(); |
| 385 | if (needsSP(MF) && MFI.isFrameBaseVirtual()) { |
| 386 | unsigned LocalNum = MFI.getFrameBaseLocal(); |
| 387 | Loc.Location.WasmLoc = {.Kind: WebAssembly::TI_LOCAL, .Index: LocalNum}; |
| 388 | } else { |
| 389 | // TODO: This should work on a breakpoint at a function with no frame, |
| 390 | // but probably won't work for traversing up the stack. |
| 391 | Loc.Location.WasmLoc = {.Kind: WebAssembly::TI_GLOBAL_RELOC, .Index: 0}; |
| 392 | } |
| 393 | return Loc; |
| 394 | } |
| 395 | |