diff --git a/llvm/lib/Target/LoongArch/AsmParser/LoongArchAsmParser.cpp b/llvm/lib/Target/LoongArch/AsmParser/LoongArchAsmParser.cpp --- a/llvm/lib/Target/LoongArch/AsmParser/LoongArchAsmParser.cpp +++ b/llvm/lib/Target/LoongArch/AsmParser/LoongArchAsmParser.cpp @@ -78,6 +78,7 @@ OperandMatchResultTy parseImmediate(OperandVector &Operands); OperandMatchResultTy parseOperandWithModifier(OperandVector &Operands); OperandMatchResultTy parseSImm26Operand(OperandVector &Operands); + OperandMatchResultTy parseAtomicMemOp(OperandVector &Operands); bool parseOperand(OperandVector &Operands, StringRef Mnemonic); @@ -180,6 +181,11 @@ bool isImm() const override { return Kind == KindTy::Immediate; } bool isMem() const override { return false; } void setReg(MCRegister PhysReg) { Reg.RegNum = PhysReg; } + bool isGPR() const { + return Kind == KindTy::Register && + LoongArchMCRegisterClasses[LoongArch::GPRRegClassID].contains( + Reg.RegNum); + } static bool evaluateConstantImm(const MCExpr *Expr, int64_t &Imm, LoongArchMCExpr::VariantKind &VK) { @@ -674,6 +680,28 @@ return MatchOperand_Success; } +OperandMatchResultTy +LoongArchAsmParser::parseAtomicMemOp(OperandVector &Operands) { + // Parse "$r*". + if (parseRegister(Operands) != MatchOperand_Success) + return MatchOperand_NoMatch; + + // If there is a next operand and it is 0, ignore it. Otherwise print a + // diagnostic message. + if (getLexer().is(AsmToken::Comma)) { + getLexer().Lex(); // Consume comma token. + int64_t ImmVal; + SMLoc ImmStart = getLoc(); + if (getParser().parseIntToken(ImmVal, "expected optional integer offset")) + return MatchOperand_ParseFail; + if (ImmVal) { + Error(ImmStart, "optional integer offset must be 0"); + return MatchOperand_ParseFail; + } + } + + return MatchOperand_Success; +} /// Looks at a token type and creates the relevant operand from this /// information, adding to Operands. Return true upon an error. bool LoongArchAsmParser::parseOperand(OperandVector &Operands, @@ -1149,7 +1177,8 @@ unsigned Rk = Inst.getOperand(1).getReg(); unsigned Rj = Inst.getOperand(2).getReg(); if (Rd == Rk || Rd == Rj) - return Match_RequiresAMORdDifferRkRj; + return Rd == LoongArch::R0 ? Match_Success + : Match_RequiresAMORdDifferRkRj; } break; case LoongArch::PseudoLA_PCREL_LARGE: diff --git a/llvm/lib/Target/LoongArch/LoongArchAsmPrinter.cpp b/llvm/lib/Target/LoongArch/LoongArchAsmPrinter.cpp --- a/llvm/lib/Target/LoongArch/LoongArchAsmPrinter.cpp +++ b/llvm/lib/Target/LoongArch/LoongArchAsmPrinter.cpp @@ -90,22 +90,23 @@ if (ExtraCode) return true; + // We only support memory operands like "Base + Offset", where base must be a + // register, and offset can be a register or an immediate value. const MachineOperand &BaseMO = MI->getOperand(OpNo); // Base address must be a register. if (!BaseMO.isReg()) return true; // Print the base address register. OS << "$" << LoongArchInstPrinter::getRegisterName(BaseMO.getReg()); - // Print the offset register or immediate if has. - if (OpNo + 1 < MI->getNumOperands()) { - const MachineOperand &OffsetMO = MI->getOperand(OpNo + 1); - if (OffsetMO.isReg()) - OS << ", $" << LoongArchInstPrinter::getRegisterName(OffsetMO.getReg()); - else if (OffsetMO.isImm()) - OS << ", " << OffsetMO.getImm(); - else - return true; - } + // Print the offset operand. + const MachineOperand &OffsetMO = MI->getOperand(OpNo + 1); + if (OffsetMO.isReg()) + OS << ", $" << LoongArchInstPrinter::getRegisterName(OffsetMO.getReg()); + else if (OffsetMO.isImm()) + OS << ", " << OffsetMO.getImm(); + else + return true; + return false; } diff --git a/llvm/lib/Target/LoongArch/LoongArchISelDAGToDAG.cpp b/llvm/lib/Target/LoongArch/LoongArchISelDAGToDAG.cpp --- a/llvm/lib/Target/LoongArch/LoongArchISelDAGToDAG.cpp +++ b/llvm/lib/Target/LoongArch/LoongArchISelDAGToDAG.cpp @@ -79,19 +79,19 @@ bool LoongArchDAGToDAGISel::SelectInlineAsmMemoryOperand( const SDValue &Op, unsigned ConstraintID, std::vector &OutOps) { + SDValue Base = Op; + SDValue Offset = + CurDAG->getTargetConstant(0, SDLoc(Op), Subtarget->getGRLenVT()); switch (ConstraintID) { default: llvm_unreachable("unexpected asm memory constraint"); // Reg+Reg addressing. case InlineAsm::Constraint_k: - OutOps.push_back(Op.getOperand(0)); - OutOps.push_back(Op.getOperand(1)); - return false; + Base = Op.getOperand(0); + Offset = Op.getOperand(1); + break; // Reg+simm12 addressing. - case InlineAsm::Constraint_m: { - SDValue Base = Op; - SDValue Offset = - CurDAG->getTargetConstant(0, SDLoc(Op), Subtarget->getGRLenVT()); + case InlineAsm::Constraint_m: if (CurDAG->isBaseWithConstantOffset(Op)) { ConstantSDNode *CN = dyn_cast(Op.getOperand(1)); if (isIntN(12, CN->getSExtValue())) { @@ -100,19 +100,12 @@ Op.getValueType()); } } - OutOps.push_back(Base); - OutOps.push_back(Offset); - return false; - } + break; + // Reg+0 addressing. case InlineAsm::Constraint_ZB: - OutOps.push_back(Op); - // No offset. - return false; + break; // Reg+(simm14<<2) addressing. - case InlineAsm::Constraint_ZC: { - SDValue Base = Op; - SDValue Offset = - CurDAG->getTargetConstant(0, SDLoc(Op), Subtarget->getGRLenVT()); + case InlineAsm::Constraint_ZC: if (CurDAG->isBaseWithConstantOffset(Op)) { ConstantSDNode *CN = dyn_cast(Op.getOperand(1)); if (isIntN(16, CN->getSExtValue()) && @@ -122,12 +115,11 @@ Op.getValueType()); } } - OutOps.push_back(Base); - OutOps.push_back(Offset); - return false; - } + break; } - return true; + OutOps.push_back(Base); + OutOps.push_back(Offset); + return false; } bool LoongArchDAGToDAGISel::SelectBaseAddr(SDValue Addr, SDValue &Base) { diff --git a/llvm/lib/Target/LoongArch/LoongArchInstrInfo.td b/llvm/lib/Target/LoongArch/LoongArchInstrInfo.td --- a/llvm/lib/Target/LoongArch/LoongArchInstrInfo.td +++ b/llvm/lib/Target/LoongArch/LoongArchInstrInfo.td @@ -151,6 +151,20 @@ : ImmAsmOperand<"U", width, suffix> { } +// A parse method for "$r*" or "$r*, 0", where the 0 is be silently ignored. +// Only used for "AM*" instructions, in order to be compatible with GAS. +def AtomicMemAsmOperand : AsmOperandClass { + let Name = "AtomicMemAsmOperand"; + let RenderMethod = "addRegOperands"; + let PredicateMethod = "isGPR"; + let ParserMethod = "parseAtomicMemOp"; +} + +def GPRMemAtomic : RegisterOperand { + let ParserMatchClass = AtomicMemAsmOperand; + let PrintMethod = "printAtomicMemOp"; +} + // A parameterized register class alternative to i32imm/i64imm from Target.td. def grlenimm : Operand; def imm32 : Operand { @@ -434,7 +448,8 @@ let mayLoad = 1, mayStore = 1, Constraints = "@earlyclobber $rd" in class AM_3R op, string opstr> - : Fmt3R; + : Fmt3R; let mayLoad = 1 in class LLBase op, string opstr> @@ -1312,17 +1327,17 @@ defm : AtomicStPat, Requires<[IsLA32]>; -def PseudoAtomicStoreW : Pseudo<(outs GPR:$dst), (ins GPR:$rj, GPR:$rk)>, - PseudoInstExpansion<(AMSWAP_DB_W R0, - GPR:$rk, GPR:$rj)>; +def PseudoAtomicStoreW + : Pseudo<(outs GPR:$dst), (ins GPR:$rj, GPR:$rk)>, + PseudoInstExpansion<(AMSWAP_DB_W R0, GPR:$rk, GPRMemAtomic:$rj)>; def : Pat<(atomic_store_release_seqcst_32 GPR:$rj, GPR:$rk), (PseudoAtomicStoreW GPR:$rj, GPR:$rk)>; let Predicates = [IsLA64] in { -def PseudoAtomicStoreD : Pseudo<(outs GPR:$dst), (ins GPR:$rj, GPR:$rk)>, - PseudoInstExpansion<(AMSWAP_DB_D R0, - GPR:$rk, GPR:$rj)>; +def PseudoAtomicStoreD + : Pseudo<(outs GPR:$dst), (ins GPR:$rj, GPR:$rk)>, + PseudoInstExpansion<(AMSWAP_DB_D R0, GPR:$rk, GPRMemAtomic:$rj)>; def : Pat<(atomic_store_release_seqcst_64 GPR:$rj, GPR:$rk), (PseudoAtomicStoreD GPR:$rj, GPR:$rk)>; diff --git a/llvm/lib/Target/LoongArch/MCTargetDesc/LoongArchInstPrinter.h b/llvm/lib/Target/LoongArch/MCTargetDesc/LoongArchInstPrinter.h --- a/llvm/lib/Target/LoongArch/MCTargetDesc/LoongArchInstPrinter.h +++ b/llvm/lib/Target/LoongArch/MCTargetDesc/LoongArchInstPrinter.h @@ -27,6 +27,8 @@ void printInst(const MCInst *MI, uint64_t Address, StringRef Annot, const MCSubtargetInfo &STI, raw_ostream &O) override; void printRegName(raw_ostream &O, unsigned RegNo) const override; + void printAtomicMemOp(const MCInst *MI, unsigned OpNo, + const MCSubtargetInfo &STI, raw_ostream &O); // Autogenerated by tblgen. std::pair getMnemonic(const MCInst *MI) override; diff --git a/llvm/lib/Target/LoongArch/MCTargetDesc/LoongArchInstPrinter.cpp b/llvm/lib/Target/LoongArch/MCTargetDesc/LoongArchInstPrinter.cpp --- a/llvm/lib/Target/LoongArch/MCTargetDesc/LoongArchInstPrinter.cpp +++ b/llvm/lib/Target/LoongArch/MCTargetDesc/LoongArchInstPrinter.cpp @@ -57,6 +57,14 @@ MO.getExpr()->print(O, &MAI); } +void LoongArchInstPrinter::printAtomicMemOp(const MCInst *MI, unsigned OpNo, + const MCSubtargetInfo &STI, + raw_ostream &O) { + const MCOperand &MO = MI->getOperand(OpNo); + assert(MO.isReg() && "printAtomicMemOp can only print register operands"); + printRegName(O, MO.getReg()); +} + const char *LoongArchInstPrinter::getRegisterName(unsigned RegNo) { // Default print reg alias name return getRegisterName(RegNo, LoongArch::RegAliasName); diff --git a/llvm/test/CodeGen/LoongArch/inline-asm-constraint-ZB.ll b/llvm/test/CodeGen/LoongArch/inline-asm-constraint-ZB.ll --- a/llvm/test/CodeGen/LoongArch/inline-asm-constraint-ZB.ll +++ b/llvm/test/CodeGen/LoongArch/inline-asm-constraint-ZB.ll @@ -47,3 +47,16 @@ call void asm "amswap.w $$r12, $$r13, $0", "*^ZB"(ptr elementtype(i32) %1) ret void } + +define void @ZB_Input_Output(ptr %p) nounwind { +; ASM-LABEL: ZB_Input_Output: +; ASM: # %bb.0: +; ASM-NEXT: #APP +; ASM-NEXT: amadd_db.d $zero, $t1, $a0 +; ASM-NEXT: #NO_APP +; ASM-NEXT: ret +;; Make sure machine instr with this "ZB" constraint is printed correctly. +; MACHINE-INSTR: INLINEASM{{.*}}[mem:ZB], %0:gpr, 0 + call void asm "amadd_db.d $$zero, $$r13, $0", "=*^ZB,*^ZB,~{memory}"(ptr elementtype(i64) %p, ptr elementtype(i64) %p) + ret void +} diff --git a/llvm/test/MC/LoongArch/Basic/Integer/atomic.s b/llvm/test/MC/LoongArch/Basic/Integer/atomic.s --- a/llvm/test/MC/LoongArch/Basic/Integer/atomic.s +++ b/llvm/test/MC/LoongArch/Basic/Integer/atomic.s @@ -29,6 +29,18 @@ .ifdef LA64 +# CHECK64-ASM-AND-OBJ: amswap.w $a2, $t0, $s1 +# CHECK64-ASM: encoding: [0x06,0x33,0x60,0x38] +amswap.w $a2, $t0, $s1, 0 + +# CHECK64-ASM-AND-OBJ: amswap.w $zero, $t0, $zero +# CHECK64-ASM: encoding: [0x00,0x30,0x60,0x38] +amswap.w $zero, $t0, $zero + +# CHECK64-ASM-AND-OBJ: amadd_db.w $zero, $zero, $a1 +# CHECK64-ASM: encoding: [0xa0,0x00,0x6a,0x38] +amadd_db.w $zero, $zero, $a1 + # CHECK64-ASM-AND-OBJ: amswap.w $a2, $t0, $s1 # CHECK64-ASM: encoding: [0x06,0x33,0x60,0x38] amswap.w $a2, $t0, $s1 diff --git a/llvm/test/MC/LoongArch/Basic/Integer/invalid64.s b/llvm/test/MC/LoongArch/Basic/Integer/invalid64.s --- a/llvm/test/MC/LoongArch/Basic/Integer/invalid64.s +++ b/llvm/test/MC/LoongArch/Basic/Integer/invalid64.s @@ -80,16 +80,14 @@ bstrpick.d $a0, $a0, 32, 63 # CHECK: ^~~~~~ -# CHECK: :[[#@LINE+1]]:10: error: $rd must be different from both $rk and $rj -amadd.d $zero, $zero, $zero -# CHECK: :[[#@LINE+1]]:10: error: $rd must be different from both $rk and $rj -ammin.w $zero, $zero, $a0 -# CHECK: :[[#@LINE+1]]:10: error: $rd must be different from both $rk and $rj -amxor.w $zero, $a0, $zero - # CHECK: :[[#@LINE+1]]:10: error: $rd must be different from both $rk and $rj amadd.d $a0, $a0, $a0 # CHECK: :[[#@LINE+1]]:10: error: $rd must be different from both $rk and $rj ammin.w $a0, $a0, $a1 # CHECK: :[[#@LINE+1]]:10: error: $rd must be different from both $rk and $rj amxor.w $a0, $a1, $a0 + +# CHECK: :[[#@LINE+1]]:24: error: expected optional integer offset +amadd.d $a0, $a1, $a2, $a3 +# CHECK: :[[#@LINE+1]]:24: error: optional integer offset must be 0 +amadd.d $a0, $a1, $a2, 1