xref: /freebsd-src/contrib/llvm-project/llvm/lib/Target/RISCV/MCA/RISCVCustomBehaviour.cpp (revision 1db9f3b21e39176dd5b67cf8ac378633b172463e)
1bdd1243dSDimitry Andric //===------------------- RISCVCustomBehaviour.cpp ---------------*-C++ -* -===//
2bdd1243dSDimitry Andric //
3bdd1243dSDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4bdd1243dSDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
5bdd1243dSDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6bdd1243dSDimitry Andric //
7bdd1243dSDimitry Andric //===----------------------------------------------------------------------===//
8bdd1243dSDimitry Andric /// \file
9bdd1243dSDimitry Andric ///
10bdd1243dSDimitry Andric /// This file implements methods from the RISCVCustomBehaviour class.
11bdd1243dSDimitry Andric ///
12bdd1243dSDimitry Andric //===----------------------------------------------------------------------===//
13bdd1243dSDimitry Andric 
14bdd1243dSDimitry Andric #include "RISCVCustomBehaviour.h"
15bdd1243dSDimitry Andric #include "MCTargetDesc/RISCVMCTargetDesc.h"
1606c3fb27SDimitry Andric #include "RISCV.h"
17bdd1243dSDimitry Andric #include "TargetInfo/RISCVTargetInfo.h"
18bdd1243dSDimitry Andric #include "llvm/MC/TargetRegistry.h"
19bdd1243dSDimitry Andric #include "llvm/Support/Debug.h"
20bdd1243dSDimitry Andric 
21bdd1243dSDimitry Andric #define DEBUG_TYPE "llvm-mca-riscv-custombehaviour"
22bdd1243dSDimitry Andric 
23bdd1243dSDimitry Andric // This brings in a table with primary key of
24bdd1243dSDimitry Andric // base instruction opcode and lmul and maps
25bdd1243dSDimitry Andric // to the opcode of the pseudo instruction.
26bdd1243dSDimitry Andric namespace RISCVVInversePseudosTable {
27bdd1243dSDimitry Andric using namespace llvm;
28bdd1243dSDimitry Andric using namespace llvm::RISCV;
29bdd1243dSDimitry Andric 
30bdd1243dSDimitry Andric struct PseudoInfo {
31bdd1243dSDimitry Andric   uint16_t Pseudo;
32bdd1243dSDimitry Andric   uint16_t BaseInstr;
33bdd1243dSDimitry Andric   uint8_t VLMul;
3406c3fb27SDimitry Andric   uint8_t SEW;
35bdd1243dSDimitry Andric };
36bdd1243dSDimitry Andric 
37bdd1243dSDimitry Andric #define GET_RISCVVInversePseudosTable_IMPL
38bdd1243dSDimitry Andric #define GET_RISCVVInversePseudosTable_DECL
39bdd1243dSDimitry Andric #include "RISCVGenSearchableTables.inc"
40bdd1243dSDimitry Andric 
41bdd1243dSDimitry Andric } // end namespace RISCVVInversePseudosTable
42bdd1243dSDimitry Andric 
43bdd1243dSDimitry Andric namespace llvm {
44bdd1243dSDimitry Andric namespace mca {
45bdd1243dSDimitry Andric 
46bdd1243dSDimitry Andric const llvm::StringRef RISCVLMULInstrument::DESC_NAME = "RISCV-LMUL";
47bdd1243dSDimitry Andric 
48bdd1243dSDimitry Andric bool RISCVLMULInstrument::isDataValid(llvm::StringRef Data) {
49bdd1243dSDimitry Andric   // Return true if not one of the valid LMUL strings
50bdd1243dSDimitry Andric   return StringSwitch<bool>(Data)
51bdd1243dSDimitry Andric       .Cases("M1", "M2", "M4", "M8", "MF2", "MF4", "MF8", true)
52bdd1243dSDimitry Andric       .Default(false);
53bdd1243dSDimitry Andric }
54bdd1243dSDimitry Andric 
55bdd1243dSDimitry Andric uint8_t RISCVLMULInstrument::getLMUL() const {
56bdd1243dSDimitry Andric   // assertion prevents us from needing llvm_unreachable in the StringSwitch
57bdd1243dSDimitry Andric   // below
58bdd1243dSDimitry Andric   assert(isDataValid(getData()) &&
59bdd1243dSDimitry Andric          "Cannot get LMUL because invalid Data value");
6006c3fb27SDimitry Andric   // These are the LMUL values that are used in RISC-V tablegen
61bdd1243dSDimitry Andric   return StringSwitch<uint8_t>(getData())
62bdd1243dSDimitry Andric       .Case("M1", 0b000)
63bdd1243dSDimitry Andric       .Case("M2", 0b001)
64bdd1243dSDimitry Andric       .Case("M4", 0b010)
65bdd1243dSDimitry Andric       .Case("M8", 0b011)
665f757f3fSDimitry Andric       .Case("MF2", 0b111)
67bdd1243dSDimitry Andric       .Case("MF4", 0b110)
685f757f3fSDimitry Andric       .Case("MF8", 0b101);
69bdd1243dSDimitry Andric }
70bdd1243dSDimitry Andric 
7106c3fb27SDimitry Andric const llvm::StringRef RISCVSEWInstrument::DESC_NAME = "RISCV-SEW";
7206c3fb27SDimitry Andric 
7306c3fb27SDimitry Andric bool RISCVSEWInstrument::isDataValid(llvm::StringRef Data) {
7406c3fb27SDimitry Andric   // Return true if not one of the valid SEW strings
7506c3fb27SDimitry Andric   return StringSwitch<bool>(Data)
7606c3fb27SDimitry Andric       .Cases("E8", "E16", "E32", "E64", true)
7706c3fb27SDimitry Andric       .Default(false);
78bdd1243dSDimitry Andric }
79bdd1243dSDimitry Andric 
8006c3fb27SDimitry Andric uint8_t RISCVSEWInstrument::getSEW() const {
8106c3fb27SDimitry Andric   // assertion prevents us from needing llvm_unreachable in the StringSwitch
8206c3fb27SDimitry Andric   // below
8306c3fb27SDimitry Andric   assert(isDataValid(getData()) && "Cannot get SEW because invalid Data value");
8406c3fb27SDimitry Andric   // These are the LMUL values that are used in RISC-V tablegen
8506c3fb27SDimitry Andric   return StringSwitch<uint8_t>(getData())
8606c3fb27SDimitry Andric       .Case("E8", 8)
8706c3fb27SDimitry Andric       .Case("E16", 16)
8806c3fb27SDimitry Andric       .Case("E32", 32)
8906c3fb27SDimitry Andric       .Case("E64", 64);
9006c3fb27SDimitry Andric }
9106c3fb27SDimitry Andric 
9206c3fb27SDimitry Andric bool RISCVInstrumentManager::supportsInstrumentType(
9306c3fb27SDimitry Andric     llvm::StringRef Type) const {
9406c3fb27SDimitry Andric   return Type == RISCVLMULInstrument::DESC_NAME ||
9506c3fb27SDimitry Andric          Type == RISCVSEWInstrument::DESC_NAME;
9606c3fb27SDimitry Andric }
9706c3fb27SDimitry Andric 
9806c3fb27SDimitry Andric UniqueInstrument
99bdd1243dSDimitry Andric RISCVInstrumentManager::createInstrument(llvm::StringRef Desc,
100bdd1243dSDimitry Andric                                          llvm::StringRef Data) {
10106c3fb27SDimitry Andric   if (Desc == RISCVLMULInstrument::DESC_NAME) {
10206c3fb27SDimitry Andric     if (!RISCVLMULInstrument::isDataValid(Data)) {
103bdd1243dSDimitry Andric       LLVM_DEBUG(dbgs() << "RVCB: Bad data for instrument kind " << Desc << ": "
104bdd1243dSDimitry Andric                         << Data << '\n');
105bdd1243dSDimitry Andric       return nullptr;
106bdd1243dSDimitry Andric     }
10706c3fb27SDimitry Andric     return std::make_unique<RISCVLMULInstrument>(Data);
10806c3fb27SDimitry Andric   }
10906c3fb27SDimitry Andric 
11006c3fb27SDimitry Andric   if (Desc == RISCVSEWInstrument::DESC_NAME) {
11106c3fb27SDimitry Andric     if (!RISCVSEWInstrument::isDataValid(Data)) {
11206c3fb27SDimitry Andric       LLVM_DEBUG(dbgs() << "RVCB: Bad data for instrument kind " << Desc << ": "
11306c3fb27SDimitry Andric                         << Data << '\n');
11406c3fb27SDimitry Andric       return nullptr;
11506c3fb27SDimitry Andric     }
11606c3fb27SDimitry Andric     return std::make_unique<RISCVSEWInstrument>(Data);
11706c3fb27SDimitry Andric   }
11806c3fb27SDimitry Andric 
11906c3fb27SDimitry Andric   LLVM_DEBUG(dbgs() << "RVCB: Unknown instrumentation Desc: " << Desc << '\n');
12006c3fb27SDimitry Andric   return nullptr;
12106c3fb27SDimitry Andric }
12206c3fb27SDimitry Andric 
12306c3fb27SDimitry Andric SmallVector<UniqueInstrument>
12406c3fb27SDimitry Andric RISCVInstrumentManager::createInstruments(const MCInst &Inst) {
12506c3fb27SDimitry Andric   if (Inst.getOpcode() == RISCV::VSETVLI ||
12606c3fb27SDimitry Andric       Inst.getOpcode() == RISCV::VSETIVLI) {
12706c3fb27SDimitry Andric     LLVM_DEBUG(dbgs() << "RVCB: Found VSETVLI and creating instrument for it: "
12806c3fb27SDimitry Andric                       << Inst << "\n");
12906c3fb27SDimitry Andric     unsigned VTypeI = Inst.getOperand(2).getImm();
13006c3fb27SDimitry Andric     RISCVII::VLMUL VLMUL = RISCVVType::getVLMUL(VTypeI);
13106c3fb27SDimitry Andric 
13206c3fb27SDimitry Andric     StringRef LMUL;
13306c3fb27SDimitry Andric     switch (VLMUL) {
13406c3fb27SDimitry Andric     case RISCVII::LMUL_1:
13506c3fb27SDimitry Andric       LMUL = "M1";
13606c3fb27SDimitry Andric       break;
13706c3fb27SDimitry Andric     case RISCVII::LMUL_2:
13806c3fb27SDimitry Andric       LMUL = "M2";
13906c3fb27SDimitry Andric       break;
14006c3fb27SDimitry Andric     case RISCVII::LMUL_4:
14106c3fb27SDimitry Andric       LMUL = "M4";
14206c3fb27SDimitry Andric       break;
14306c3fb27SDimitry Andric     case RISCVII::LMUL_8:
14406c3fb27SDimitry Andric       LMUL = "M8";
14506c3fb27SDimitry Andric       break;
14606c3fb27SDimitry Andric     case RISCVII::LMUL_F2:
14706c3fb27SDimitry Andric       LMUL = "MF2";
14806c3fb27SDimitry Andric       break;
14906c3fb27SDimitry Andric     case RISCVII::LMUL_F4:
15006c3fb27SDimitry Andric       LMUL = "MF4";
15106c3fb27SDimitry Andric       break;
15206c3fb27SDimitry Andric     case RISCVII::LMUL_F8:
15306c3fb27SDimitry Andric       LMUL = "MF8";
15406c3fb27SDimitry Andric       break;
15506c3fb27SDimitry Andric     case RISCVII::LMUL_RESERVED:
15606c3fb27SDimitry Andric       llvm_unreachable("Cannot create instrument for LMUL_RESERVED");
15706c3fb27SDimitry Andric     }
15806c3fb27SDimitry Andric     SmallVector<UniqueInstrument> Instruments;
15906c3fb27SDimitry Andric     Instruments.emplace_back(
16006c3fb27SDimitry Andric         createInstrument(RISCVLMULInstrument::DESC_NAME, LMUL));
16106c3fb27SDimitry Andric 
16206c3fb27SDimitry Andric     unsigned SEW = RISCVVType::getSEW(VTypeI);
16306c3fb27SDimitry Andric     StringRef SEWStr;
16406c3fb27SDimitry Andric     switch (SEW) {
16506c3fb27SDimitry Andric     case 8:
16606c3fb27SDimitry Andric       SEWStr = "E8";
16706c3fb27SDimitry Andric       break;
16806c3fb27SDimitry Andric     case 16:
16906c3fb27SDimitry Andric       SEWStr = "E16";
17006c3fb27SDimitry Andric       break;
17106c3fb27SDimitry Andric     case 32:
17206c3fb27SDimitry Andric       SEWStr = "E32";
17306c3fb27SDimitry Andric       break;
17406c3fb27SDimitry Andric     case 64:
17506c3fb27SDimitry Andric       SEWStr = "E64";
17606c3fb27SDimitry Andric       break;
17706c3fb27SDimitry Andric     default:
17806c3fb27SDimitry Andric       llvm_unreachable("Cannot create instrument for SEW");
17906c3fb27SDimitry Andric     }
18006c3fb27SDimitry Andric     Instruments.emplace_back(
18106c3fb27SDimitry Andric         createInstrument(RISCVSEWInstrument::DESC_NAME, SEWStr));
18206c3fb27SDimitry Andric 
18306c3fb27SDimitry Andric     return Instruments;
18406c3fb27SDimitry Andric   }
18506c3fb27SDimitry Andric   return SmallVector<UniqueInstrument>();
186bdd1243dSDimitry Andric }
187bdd1243dSDimitry Andric 
1885f757f3fSDimitry Andric static std::pair<uint8_t, uint8_t>
189*1db9f3b2SDimitry Andric getEEWAndEMUL(unsigned Opcode, RISCVII::VLMUL LMUL, uint8_t SEW) {
1905f757f3fSDimitry Andric   uint8_t EEW;
1915f757f3fSDimitry Andric   switch (Opcode) {
1925f757f3fSDimitry Andric   case RISCV::VLM_V:
1935f757f3fSDimitry Andric   case RISCV::VSM_V:
1945f757f3fSDimitry Andric   case RISCV::VLE8_V:
1955f757f3fSDimitry Andric   case RISCV::VSE8_V:
196*1db9f3b2SDimitry Andric   case RISCV::VLSE8_V:
197*1db9f3b2SDimitry Andric   case RISCV::VSSE8_V:
1985f757f3fSDimitry Andric     EEW = 8;
1995f757f3fSDimitry Andric     break;
2005f757f3fSDimitry Andric   case RISCV::VLE16_V:
2015f757f3fSDimitry Andric   case RISCV::VSE16_V:
202*1db9f3b2SDimitry Andric   case RISCV::VLSE16_V:
203*1db9f3b2SDimitry Andric   case RISCV::VSSE16_V:
2045f757f3fSDimitry Andric     EEW = 16;
2055f757f3fSDimitry Andric     break;
2065f757f3fSDimitry Andric   case RISCV::VLE32_V:
2075f757f3fSDimitry Andric   case RISCV::VSE32_V:
208*1db9f3b2SDimitry Andric   case RISCV::VLSE32_V:
209*1db9f3b2SDimitry Andric   case RISCV::VSSE32_V:
2105f757f3fSDimitry Andric     EEW = 32;
2115f757f3fSDimitry Andric     break;
2125f757f3fSDimitry Andric   case RISCV::VLE64_V:
2135f757f3fSDimitry Andric   case RISCV::VSE64_V:
214*1db9f3b2SDimitry Andric   case RISCV::VLSE64_V:
215*1db9f3b2SDimitry Andric   case RISCV::VSSE64_V:
2165f757f3fSDimitry Andric     EEW = 64;
2175f757f3fSDimitry Andric     break;
2185f757f3fSDimitry Andric   default:
219*1db9f3b2SDimitry Andric     llvm_unreachable("Could not determine EEW from Opcode");
2205f757f3fSDimitry Andric   }
2215f757f3fSDimitry Andric 
2225f757f3fSDimitry Andric   auto EMUL = RISCVVType::getSameRatioLMUL(SEW, LMUL, EEW);
2235f757f3fSDimitry Andric   if (!EEW)
2245f757f3fSDimitry Andric     llvm_unreachable("Invalid SEW or LMUL for new ratio");
2255f757f3fSDimitry Andric   return std::make_pair(EEW, *EMUL);
2265f757f3fSDimitry Andric }
2275f757f3fSDimitry Andric 
228*1db9f3b2SDimitry Andric bool opcodeHasEEWAndEMULInfo(unsigned short Opcode) {
229*1db9f3b2SDimitry Andric   return Opcode == RISCV::VLM_V || Opcode == RISCV::VSM_V ||
230*1db9f3b2SDimitry Andric          Opcode == RISCV::VLE8_V || Opcode == RISCV::VSE8_V ||
231*1db9f3b2SDimitry Andric          Opcode == RISCV::VLE16_V || Opcode == RISCV::VSE16_V ||
232*1db9f3b2SDimitry Andric          Opcode == RISCV::VLE32_V || Opcode == RISCV::VSE32_V ||
233*1db9f3b2SDimitry Andric          Opcode == RISCV::VLE64_V || Opcode == RISCV::VSE64_V ||
234*1db9f3b2SDimitry Andric          Opcode == RISCV::VLSE8_V || Opcode == RISCV::VSSE8_V ||
235*1db9f3b2SDimitry Andric          Opcode == RISCV::VLSE16_V || Opcode == RISCV::VSSE16_V ||
236*1db9f3b2SDimitry Andric          Opcode == RISCV::VLSE32_V || Opcode == RISCV::VSSE32_V ||
237*1db9f3b2SDimitry Andric          Opcode == RISCV::VLSE64_V || Opcode == RISCV::VSSE64_V;
238*1db9f3b2SDimitry Andric }
239*1db9f3b2SDimitry Andric 
240bdd1243dSDimitry Andric unsigned RISCVInstrumentManager::getSchedClassID(
241bdd1243dSDimitry Andric     const MCInstrInfo &MCII, const MCInst &MCI,
24206c3fb27SDimitry Andric     const llvm::SmallVector<Instrument *> &IVec) const {
243bdd1243dSDimitry Andric   unsigned short Opcode = MCI.getOpcode();
244bdd1243dSDimitry Andric   unsigned SchedClassID = MCII.get(Opcode).getSchedClass();
245bdd1243dSDimitry Andric 
2465f757f3fSDimitry Andric   // Unpack all possible RISC-V instruments from IVec.
24706c3fb27SDimitry Andric   RISCVLMULInstrument *LI = nullptr;
24806c3fb27SDimitry Andric   RISCVSEWInstrument *SI = nullptr;
24906c3fb27SDimitry Andric   for (auto &I : IVec) {
25006c3fb27SDimitry Andric     if (I->getDesc() == RISCVLMULInstrument::DESC_NAME)
25106c3fb27SDimitry Andric       LI = static_cast<RISCVLMULInstrument *>(I);
25206c3fb27SDimitry Andric     else if (I->getDesc() == RISCVSEWInstrument::DESC_NAME)
25306c3fb27SDimitry Andric       SI = static_cast<RISCVSEWInstrument *>(I);
25406c3fb27SDimitry Andric   }
25506c3fb27SDimitry Andric 
25606c3fb27SDimitry Andric   // Need LMUL or LMUL, SEW in order to override opcode. If no LMUL is provided,
25706c3fb27SDimitry Andric   // then no option to override.
25806c3fb27SDimitry Andric   if (!LI) {
25906c3fb27SDimitry Andric     LLVM_DEBUG(
26006c3fb27SDimitry Andric         dbgs() << "RVCB: Did not use instrumentation to override Opcode.\n");
26106c3fb27SDimitry Andric     return SchedClassID;
26206c3fb27SDimitry Andric   }
26306c3fb27SDimitry Andric   uint8_t LMUL = LI->getLMUL();
26406c3fb27SDimitry Andric 
26506c3fb27SDimitry Andric   // getBaseInfo works with (Opcode, LMUL, 0) if no SEW instrument,
26606c3fb27SDimitry Andric   // or (Opcode, LMUL, SEW) if SEW instrument is active, and depends on LMUL
26706c3fb27SDimitry Andric   // and SEW, or (Opcode, LMUL, 0) if does not depend on SEW.
26806c3fb27SDimitry Andric   uint8_t SEW = SI ? SI->getSEW() : 0;
2695f757f3fSDimitry Andric 
2705f757f3fSDimitry Andric   const RISCVVInversePseudosTable::PseudoInfo *RVV = nullptr;
271*1db9f3b2SDimitry Andric   if (opcodeHasEEWAndEMULInfo(Opcode)) {
2725f757f3fSDimitry Andric     RISCVII::VLMUL VLMUL = static_cast<RISCVII::VLMUL>(LMUL);
273*1db9f3b2SDimitry Andric     auto [EEW, EMUL] = getEEWAndEMUL(Opcode, VLMUL, SEW);
2745f757f3fSDimitry Andric     RVV = RISCVVInversePseudosTable::getBaseInfo(Opcode, EMUL, EEW);
2755f757f3fSDimitry Andric   } else {
27606c3fb27SDimitry Andric     // Check if it depends on LMUL and SEW
2775f757f3fSDimitry Andric     RVV = RISCVVInversePseudosTable::getBaseInfo(Opcode, LMUL, SEW);
27806c3fb27SDimitry Andric     // Check if it depends only on LMUL
27906c3fb27SDimitry Andric     if (!RVV)
28006c3fb27SDimitry Andric       RVV = RISCVVInversePseudosTable::getBaseInfo(Opcode, LMUL, 0);
2815f757f3fSDimitry Andric   }
28206c3fb27SDimitry Andric 
283bdd1243dSDimitry Andric   // Not a RVV instr
284bdd1243dSDimitry Andric   if (!RVV) {
285bdd1243dSDimitry Andric     LLVM_DEBUG(
28606c3fb27SDimitry Andric         dbgs() << "RVCB: Could not find PseudoInstruction for Opcode "
28706c3fb27SDimitry Andric                << MCII.getName(Opcode)
28806c3fb27SDimitry Andric                << ", LMUL=" << (LI ? LI->getData() : "Unspecified")
28906c3fb27SDimitry Andric                << ", SEW=" << (SI ? SI->getData() : "Unspecified")
290bdd1243dSDimitry Andric                << ". Ignoring instrumentation and using original SchedClassID="
291bdd1243dSDimitry Andric                << SchedClassID << '\n');
292bdd1243dSDimitry Andric     return SchedClassID;
293bdd1243dSDimitry Andric   }
294bdd1243dSDimitry Andric 
295bdd1243dSDimitry Andric   // Override using pseudo
296bdd1243dSDimitry Andric   LLVM_DEBUG(dbgs() << "RVCB: Found Pseudo Instruction for Opcode "
29706c3fb27SDimitry Andric                     << MCII.getName(Opcode) << ", LMUL=" << LI->getData()
29806c3fb27SDimitry Andric                     << ", SEW=" << (SI ? SI->getData() : "Unspecified")
299bdd1243dSDimitry Andric                     << ". Overriding original SchedClassID=" << SchedClassID
300bdd1243dSDimitry Andric                     << " with " << MCII.getName(RVV->Pseudo) << '\n');
301bdd1243dSDimitry Andric   return MCII.get(RVV->Pseudo).getSchedClass();
302bdd1243dSDimitry Andric }
303bdd1243dSDimitry Andric 
304bdd1243dSDimitry Andric } // namespace mca
305bdd1243dSDimitry Andric } // namespace llvm
306bdd1243dSDimitry Andric 
307bdd1243dSDimitry Andric using namespace llvm;
308bdd1243dSDimitry Andric using namespace mca;
309bdd1243dSDimitry Andric 
310bdd1243dSDimitry Andric static InstrumentManager *
311bdd1243dSDimitry Andric createRISCVInstrumentManager(const MCSubtargetInfo &STI,
312bdd1243dSDimitry Andric                              const MCInstrInfo &MCII) {
313bdd1243dSDimitry Andric   return new RISCVInstrumentManager(STI, MCII);
314bdd1243dSDimitry Andric }
315bdd1243dSDimitry Andric 
31606c3fb27SDimitry Andric /// Extern function to initialize the targets for the RISC-V backend
317bdd1243dSDimitry Andric extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeRISCVTargetMCA() {
318bdd1243dSDimitry Andric   TargetRegistry::RegisterInstrumentManager(getTheRISCV32Target(),
319bdd1243dSDimitry Andric                                             createRISCVInstrumentManager);
320bdd1243dSDimitry Andric   TargetRegistry::RegisterInstrumentManager(getTheRISCV64Target(),
321bdd1243dSDimitry Andric                                             createRISCVInstrumentManager);
322bdd1243dSDimitry Andric }
323