//===-- W65816ISelDAGToDAG.cpp - DAG to DAG inst selector for W65816 ------===// // // 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 // //===----------------------------------------------------------------------===// // // Skeleton DAG-to-DAG selector for the W65816. Falls back to the TableGen // selector for every node; real custom selection (frame-index materialisation, // addressing-mode matching, etc.) will be added once instructions exist. // //===----------------------------------------------------------------------===// #include "W65816.h" #include "W65816SelectionDAGInfo.h" #include "W65816TargetMachine.h" #include "llvm/CodeGen/SelectionDAG.h" #include "llvm/CodeGen/SelectionDAGISel.h" #include "llvm/Support/Debug.h" #include "llvm/Support/ErrorHandling.h" using namespace llvm; #define DEBUG_TYPE "w65816-isel" #define PASS_NAME "W65816 DAG->DAG Pattern Instruction Selection" namespace { class W65816DAGToDAGISel : public SelectionDAGISel { public: W65816DAGToDAGISel() = delete; W65816DAGToDAGISel(W65816TargetMachine &TM, CodeGenOptLevel OptLevel) : SelectionDAGISel(TM, OptLevel) {} private: #include "W65816GenDAGISel.inc" void Select(SDNode *N) override; // ComplexPattern selector: matches a FrameIndex SDValue and returns // it (plus a zero offset) so LDAfi / STAfi can fold the FI directly // into their operand list. bool SelectFrameIndex(SDValue N, SDValue &Base, SDValue &Offset); }; class W65816DAGToDAGISelLegacy : public SelectionDAGISelLegacy { public: static char ID; W65816DAGToDAGISelLegacy(W65816TargetMachine &TM, CodeGenOptLevel OptLevel) : SelectionDAGISelLegacy( ID, std::make_unique(TM, OptLevel)) {} }; } // end anonymous namespace char W65816DAGToDAGISelLegacy::ID; INITIALIZE_PASS(W65816DAGToDAGISelLegacy, DEBUG_TYPE, PASS_NAME, false, false) FunctionPass *llvm::createW65816ISelDag(W65816TargetMachine &TM, CodeGenOptLevel OptLevel) { return new W65816DAGToDAGISelLegacy(TM, OptLevel); } void W65816DAGToDAGISel::Select(SDNode *Node) { if (Node->isMachineOpcode()) { Node->setNodeId(-1); return; } // Custom selection: bare FrameIndex SDValue used as a pointer value // (e.g. `&arr[0]` for a stack-allocated array). The auto-generated // selector has no pattern for `(i16 frameindex)` because tablegen // doesn't expose FrameIndex as a leaf type — so ISel fails with // "Cannot select: FrameIndex" before ever reaching a load/store- // context fold. Convert to ADDframe (FI, 0); the frame-index // elimination pass turns ADDframe into TSC + CLC + ADC #(offset + // stackSize), producing SP+offset in A. // // ptr32 mode: a `(i32 frameindex)` is `&local` typed as a 32-bit // pointer (bank+addr). Lower as REG_SEQUENCE(ADDframe, sub_lo, 0, // sub_hi). Hi=0 reflects the program-bank assumption (stack lives // in bank 0 for our crt0 startup). Without this, ISel hits // "Cannot select: t# = FrameIndex" and the pass crashes — // observed for softDouble's __adddf3 calling dclass(a, &sa, &ea, // &ma) where the latter three become i32 frameindex SDValues. if (Node->getOpcode() == ISD::FrameIndex) { SDLoc DL(Node); int FI = cast(Node)->getIndex(); EVT VT = Node->getValueType(0); SDValue TFI = CurDAG->getTargetFrameIndex(FI, MVT::i16); SDValue Zero16 = CurDAG->getTargetConstant(0, DL, MVT::i16); if (VT == MVT::i16) { CurDAG->SelectNodeTo(Node, W65816::ADDframe, MVT::i16, TFI, Zero16); return; } if (VT == MVT::i32) { // Build (REG_SEQUENCE Wide32RC, ADDframe(FI,0), sub_lo, MOVi16(0), // sub_hi). ADDframe materialises lo as an i16 SDValue; the hi // half is the literal bank byte (0). SDNode *Lo = CurDAG->getMachineNode(W65816::ADDframe, DL, MVT::i16, TFI, Zero16); SDValue HiC = CurDAG->getTargetConstant(0, DL, MVT::i16); // For the high half, just materialise an i16 zero via MOVi16imm. SDNode *Hi = CurDAG->getMachineNode(W65816::LDAi16imm, DL, MVT::i16, HiC); SDValue RC = CurDAG->getTargetConstant(W65816::Wide32RegClassID, DL, MVT::i32); SDValue SubLo = CurDAG->getTargetConstant(llvm::sub_lo, DL, MVT::i32); SDValue SubHi = CurDAG->getTargetConstant(llvm::sub_hi, DL, MVT::i32); CurDAG->SelectNodeTo(Node, TargetOpcode::REG_SEQUENCE, MVT::i32, {RC, SDValue(Lo, 0), SubLo, SDValue(Hi, 0), SubHi}); return; } report_fatal_error("W65816: FrameIndex selection: unsupported VT"); } // Defer to the auto-generated selector for everything else. SelectCode(Node); } bool W65816DAGToDAGISel::SelectFrameIndex(SDValue N, SDValue &Base, SDValue &Offset) { // Bare FrameIndex: offset 0. if (auto *FIN = dyn_cast(N)) { Base = CurDAG->getTargetFrameIndex(FIN->getIndex(), MVT::i16); Offset = CurDAG->getTargetConstant(0, SDLoc(N), MVT::i16); return true; } // (add FrameIndex, const): fold the const into the memfi offset. // Type legalization emits this shape when splitting a multi-byte // load/store at a stack slot into multiple smaller loads (e.g. an // i32 spill becomes two i16 loads, with the high load at FI+2). // Without this, the bare FrameIndex inside the add is left as an // unmatched i16 leaf and ISel reports "Cannot select FrameIndex". if (N.getOpcode() == ISD::ADD) { SDValue LHS = N.getOperand(0); SDValue RHS = N.getOperand(1); if (auto *FIN = dyn_cast(LHS)) { if (auto *CN = dyn_cast(RHS)) { Base = CurDAG->getTargetFrameIndex(FIN->getIndex(), MVT::i16); Offset = CurDAG->getTargetConstant(CN->getSExtValue(), SDLoc(N), MVT::i16); return true; } } } return false; }