Index: include/llvm/ADT/Triple.h =================================================================== --- include/llvm/ADT/Triple.h +++ include/llvm/ADT/Triple.h @@ -604,7 +604,10 @@ return getArch() == Triple::nvptx || getArch() == Triple::nvptx64; } - /// Tests wether the target supports comdat + /// Tests whether the environment is EABI + bool isEABI() const { return getEnvironment() == Triple::EABI; } + + /// Tests whether the target supports comdat bool supportsCOMDAT() const { return !isOSBinFormatMachO(); } /// @} Index: include/llvm/MC/MCExpr.h =================================================================== --- include/llvm/MC/MCExpr.h +++ include/llvm/MC/MCExpr.h @@ -250,6 +250,7 @@ VK_PPC_GOT_TLSLD_HA, // symbol@got@tlsld@ha VK_PPC_TLSLD, // symbol@tlsld VK_PPC_LOCAL, // symbol@local + VK_PPC_SDA, // symbol@sdarx VK_COFF_IMGREL32, // symbol@imgrel (image-relative) Index: include/llvm/MC/SectionKind.h =================================================================== --- include/llvm/MC/SectionKind.h +++ include/llvm/MC/SectionKind.h @@ -98,7 +98,7 @@ /// marked 'constant'. Common, - /// This is writeable data that has a non-zero initializer. + /// Data - This is writeable data that has a non-zero initializer. Data, /// ReadOnlyWithRel - These are global variables that are never @@ -107,15 +107,43 @@ /// can write to them. If it chooses to, the dynamic linker can /// mark the pages these globals end up on as read-only after it is /// done with its relocation phase. - ReadOnlyWithRel + ReadOnlyWithRel, + + /// SmallDataSections - Data sections where individual entries are capped + /// for size, typically for global-register-relative relocation. + /// These test as logical equivalents with respect to their unspecified + /// counterparts, but not vice-versa. + + /// SmallKindMask - Used to access the underlying Kind + SmallKindMask = 0x7f, + + /// SmallKind - Used to logically qualify a type as small + SmallKind = 0x80, + + /// SmallData - This is writeable data that has a non-zero initializer. + SmallData = (Data | SmallKind), + + /// SmallReadOnly - This is non-writeable data that has a non-zero + /// initializer. + SmallReadOnly = (ReadOnly | SmallKind), + + /// SmallBSS - This is writeable data that has a zero initializer. + SmallBSS = (BSS | SmallKind), + + /// SmallCommon - These represent tentative definitions, which always + /// have a zero initializer and are never marked 'constant'. + SmallCommon = (Common | SmallKind) } K : 8; + + int UnderlyingKind() const { return K & SmallKindMask; } + public: bool isMetadata() const { return K == Metadata; } bool isText() const { return K == Text; } bool isReadOnly() const { - return K == ReadOnly || isMergeableCString() || + return UnderlyingKind() == ReadOnly || isMergeableCString() || isMergeableConst(); } @@ -151,17 +179,26 @@ return isBSS() || isCommon() || isData() || isReadOnlyWithRel(); } - bool isBSS() const { return K == BSS || K == BSSLocal || K == BSSExtern; } - bool isBSSLocal() const { return K == BSSLocal; } - bool isBSSExtern() const { return K == BSSExtern; } + bool isBSS() const { + int UK = UnderlyingKind(); + return UK == BSS || UK == BSSLocal || UK == BSSExtern; + } + bool isBSSLocal() const { return UnderlyingKind() == BSSLocal; } + bool isBSSExtern() const { return UnderlyingKind() == BSSExtern; } - bool isCommon() const { return K == Common; } + bool isCommon() const { return UnderlyingKind() == Common; } - bool isData() const { return K == Data; } + bool isData() const { return UnderlyingKind() == Data; } bool isReadOnlyWithRel() const { return K == ReadOnlyWithRel; } + + bool isSmallData() const { return K == SmallData; } + bool isSmallReadOnly() const { return K == SmallReadOnly; } + bool isSmallBSS() const { return K == SmallBSS; } + bool isSmallCommon() const { return K == SmallCommon; } + bool isSmallKind() const { return (K & SmallKind) == SmallKind; } private: static SectionKind get(Kind K) { SectionKind Res; @@ -194,6 +231,10 @@ static SectionKind getCommon() { return get(Common); } static SectionKind getData() { return get(Data); } static SectionKind getReadOnlyWithRel() { return get(ReadOnlyWithRel); } + static SectionKind getSmallData() { return get(SmallData); } + static SectionKind getSmallReadOnly() { return get(SmallReadOnly); } + static SectionKind getSmallBSS() { return get(SmallBSS); } + static SectionKind getSmallCommon() { return get(SmallCommon); } }; } // end namespace llvm Index: include/llvm/Support/ELFRelocs/PowerPC.def =================================================================== --- include/llvm/Support/ELFRelocs/PowerPC.def +++ include/llvm/Support/ELFRelocs/PowerPC.def @@ -60,6 +60,22 @@ #undef R_PPC_GOT_DTPREL16_HA #undef R_PPC_TLSGD #undef R_PPC_TLSLD +#undef R_PPC_EMB_NADDR32 +#undef R_PPC_EMB_NADDR16 +#undef R_PPC_EMB_NADDR16_LO +#undef R_PPC_EMB_NADDR16_HI +#undef R_PPC_EMB_NADDR16_HA +#undef R_PPC_EMB_SDAI16 +#undef R_PPC_EMB_SDA2I16 +#undef R_PPC_EMB_SDA2REL +#undef R_PPC_EMB_SDA21 +#undef R_PPC_EMB_MRKREF +#undef R_PPC_EMB_RELSEC16 +#undef R_PPC_EMB_RELST_LO +#undef R_PPC_EMB_RELST_HI +#undef R_PPC_EMB_RELST_HA +#undef R_PPC_EMB_BIT_FLD +#undef R_PPC_EMB_RELSDA #undef R_PPC_REL16 #undef R_PPC_REL16_LO #undef R_PPC_REL16_HI @@ -117,6 +133,22 @@ ELF_RELOC(R_PPC_GOT_DTPREL16_HA, 94) ELF_RELOC(R_PPC_TLSGD, 95) ELF_RELOC(R_PPC_TLSLD, 96) +ELF_RELOC(R_PPC_EMB_NADDR32, 101) +ELF_RELOC(R_PPC_EMB_NADDR16, 102) +ELF_RELOC(R_PPC_EMB_NADDR16_LO, 103) +ELF_RELOC(R_PPC_EMB_NADDR16_HI, 104) +ELF_RELOC(R_PPC_EMB_NADDR16_HA, 105) +ELF_RELOC(R_PPC_EMB_SDAI16, 106) +ELF_RELOC(R_PPC_EMB_SDA2I16, 107) +ELF_RELOC(R_PPC_EMB_SDA2REL, 108) +ELF_RELOC(R_PPC_EMB_SDA21, 109) +ELF_RELOC(R_PPC_EMB_MRKREF, 110) +ELF_RELOC(R_PPC_EMB_RELSEC16, 111) +ELF_RELOC(R_PPC_EMB_RELST_LO, 112) +ELF_RELOC(R_PPC_EMB_RELST_HI, 113) +ELF_RELOC(R_PPC_EMB_RELST_HA, 114) +ELF_RELOC(R_PPC_EMB_BIT_FLD, 115) +ELF_RELOC(R_PPC_EMB_RELSDA, 116) ELF_RELOC(R_PPC_REL16, 249) ELF_RELOC(R_PPC_REL16_LO, 250) ELF_RELOC(R_PPC_REL16_HI, 251) Index: include/llvm/Target/TargetLoweringObjectFile.h =================================================================== --- include/llvm/Target/TargetLoweringObjectFile.h +++ include/llvm/Target/TargetLoweringObjectFile.h @@ -89,6 +89,11 @@ static SectionKind getKindForGlobal(const GlobalObject *GO, const TargetMachine &TM); + /// Determine if global variable is allocated in a subtarget small data + /// SectionKind. + static bool isGlobalInSmallSection(const GlobalObject *GO, + const TargetMachine &TM); + /// This method computes the appropriate section to emit the specified global /// variable or function definition. This should not be passed external (or /// available externally) globals. @@ -190,6 +195,10 @@ virtual MCSection *SelectSectionForGlobal(const GlobalObject *GO, SectionKind Kind, const TargetMachine &TM) const = 0; + virtual bool isGlobalInSmallSectionKind(const GlobalObject *GO, + const TargetMachine &TM) const { + return false; + } }; } // end namespace llvm Index: lib/MC/ELFObjectWriter.cpp =================================================================== --- lib/MC/ELFObjectWriter.cpp +++ lib/MC/ELFObjectWriter.cpp @@ -535,6 +535,7 @@ case MCSymbolRefExpr::VK_PPC_GOT_LO: case MCSymbolRefExpr::VK_PPC_GOT_HI: case MCSymbolRefExpr::VK_PPC_GOT_HA: + case MCSymbolRefExpr::VK_PPC_SDA: return true; } Index: lib/MC/MCExpr.cpp =================================================================== --- lib/MC/MCExpr.cpp +++ lib/MC/MCExpr.cpp @@ -263,6 +263,7 @@ case VK_PPC_GOT_TLSLD_HA: return "got@tlsld@ha"; case VK_PPC_TLSLD: return "tlsld"; case VK_PPC_LOCAL: return "local"; + case VK_PPC_SDA: return "sdarx"; case VK_COFF_IMGREL32: return "IMGREL"; case VK_Hexagon_PCREL: return "PCREL"; case VK_Hexagon_LO16: return "LO16"; @@ -325,6 +326,7 @@ .Case("got@h", VK_PPC_GOT_HI) .Case("got@ha", VK_PPC_GOT_HA) .Case("local", VK_PPC_LOCAL) + .Case("sdarx", VK_PPC_SDA) .Case("tocbase", VK_PPC_TOCBASE) .Case("toc", VK_PPC_TOC) .Case("toc@l", VK_PPC_TOC_LO) Index: lib/Target/Hexagon/HexagonISelLowering.cpp =================================================================== --- lib/Target/Hexagon/HexagonISelLowering.cpp +++ lib/Target/Hexagon/HexagonISelLowering.cpp @@ -1508,7 +1508,7 @@ if (RM == Reloc::Static) { SDValue GA = DAG.getTargetGlobalAddress(GV, dl, PtrVT, Offset); const GlobalObject *GO = GV->getBaseObject(); - if (GO && HLOF.isGlobalInSmallSection(GO, HTM)) + if (GO && HLOF.isGlobalInSmallSectionKind(GO, HTM)) return DAG.getNode(HexagonISD::CONST32_GP, dl, PtrVT, GA); return DAG.getNode(HexagonISD::CONST32, dl, PtrVT, GA); } Index: lib/Target/Hexagon/HexagonTargetObjectFile.h =================================================================== --- lib/Target/Hexagon/HexagonTargetObjectFile.h +++ lib/Target/Hexagon/HexagonTargetObjectFile.h @@ -26,8 +26,8 @@ SectionKind Kind, const TargetMachine &TM) const override; - bool isGlobalInSmallSection(const GlobalObject *GO, - const TargetMachine &TM) const; + bool isGlobalInSmallSectionKind(const GlobalObject *GO, + const TargetMachine &TM) const override; bool isSmallDataEnabled() const; Index: lib/Target/Hexagon/HexagonTargetObjectFile.cpp =================================================================== --- lib/Target/Hexagon/HexagonTargetObjectFile.cpp +++ lib/Target/Hexagon/HexagonTargetObjectFile.cpp @@ -115,7 +115,7 @@ << (Kind.isBSS() ? "kind_bss " : "" ) << (Kind.isBSSLocal() ? "kind_bss_local " : "" )); - if (isGlobalInSmallSection(GO, TM)) + if (Kind.isSmallKind()) return selectSmallSectionForGlobal(GO, Kind, TM); if (Kind.isCommon()) { @@ -155,7 +155,7 @@ ELF::SHF_WRITE | ELF::SHF_ALLOC); } - if (isGlobalInSmallSection(GO, TM)) + if (Kind.isSmallKind()) return selectSmallSectionForGlobal(GO, Kind, TM); // Otherwise, we work the same as ELF. @@ -166,7 +166,7 @@ /// Return true if this global value should be placed into small data/bss /// section. -bool HexagonTargetObjectFile::isGlobalInSmallSection(const GlobalObject *GO, +bool HexagonTargetObjectFile::isGlobalInSmallSectionKind(const GlobalObject *GO, const TargetMachine &TM) const { // Only global variables, not functions. DEBUG(dbgs() << "Checking if value is in small-data, -G" Index: lib/Target/Lanai/LanaiISelLowering.cpp =================================================================== --- lib/Target/Lanai/LanaiISelLowering.cpp +++ lib/Target/Lanai/LanaiISelLowering.cpp @@ -1161,15 +1161,12 @@ const GlobalValue *GV = cast(Op)->getGlobal(); int64_t Offset = cast(Op)->getOffset(); - const LanaiTargetObjectFile *TLOF = - static_cast( - getTargetMachine().getObjFileLowering()); - // If the code model is small or global variable will be placed in the small // section, then assume address will fit in 21-bits. const GlobalObject *GO = GV->getBaseObject(); if (getTargetMachine().getCodeModel() == CodeModel::Small || - (GO && TLOF->isGlobalInSmallSection(GO, getTargetMachine()))) { + (GO && TargetLoweringObjectFile::isGlobalInSmallSection( + GO, getTargetMachine()))) { SDValue Small = DAG.getTargetGlobalAddress( GV, DL, getPointerTy(DAG.getDataLayout()), Offset, LanaiII::MO_NO_FLAG); return DAG.getNode(ISD::OR, DL, MVT::i32, Index: lib/Target/Lanai/LanaiTargetObjectFile.h =================================================================== --- lib/Target/Lanai/LanaiTargetObjectFile.h +++ lib/Target/Lanai/LanaiTargetObjectFile.h @@ -18,19 +18,12 @@ MCSection *SmallDataSection; MCSection *SmallBSSSection; - bool isGlobalInSmallSection(const GlobalObject *GO, const TargetMachine &TM, - SectionKind Kind) const; - bool isGlobalInSmallSectionImpl(const GlobalObject *GO, - const TargetMachine &TM) const; + bool isGlobalInSmallSectionKind(const GlobalObject *GO, + const TargetMachine &TM) const override; public: void Initialize(MCContext &Ctx, const TargetMachine &TM) override; - /// Return true if this global address should be placed into small data/bss - /// section. - bool isGlobalInSmallSection(const GlobalObject *GO, - const TargetMachine &TM) const; - MCSection *SelectSectionForGlobal(const GlobalObject *GO, SectionKind Kind, const TargetMachine &TM) const override; Index: lib/Target/Lanai/LanaiTargetObjectFile.cpp =================================================================== --- lib/Target/Lanai/LanaiTargetObjectFile.cpp +++ lib/Target/Lanai/LanaiTargetObjectFile.cpp @@ -47,31 +47,9 @@ } // Return true if this global address should be placed into small data/bss -// section. -bool LanaiTargetObjectFile::isGlobalInSmallSection( - const GlobalObject *GO, const TargetMachine &TM) const { - // We first check the case where global is a declaration, because finding - // section kind using getKindForGlobal() is only allowed for global - // definitions. - if (GO->isDeclaration() || GO->hasAvailableExternallyLinkage()) - return isGlobalInSmallSectionImpl(GO, TM); - - return isGlobalInSmallSection(GO, TM, getKindForGlobal(GO, TM)); -} - -// Return true if this global address should be placed into small data/bss -// section. -bool LanaiTargetObjectFile::isGlobalInSmallSection(const GlobalObject *GO, - const TargetMachine &TM, - SectionKind Kind) const { - return (isGlobalInSmallSectionImpl(GO, TM) && - (Kind.isData() || Kind.isBSS() || Kind.isCommon())); -} - -// Return true if this global address should be placed into small data/bss -// section. This method does all the work, except for checking the section +// section. This method does all the work, directly influencing section // kind. -bool LanaiTargetObjectFile::isGlobalInSmallSectionImpl( +bool LanaiTargetObjectFile::isGlobalInSmallSectionKind( const GlobalObject *GO, const TargetMachine & /*TM*/) const { // Only global variables, not functions. const auto *GVA = dyn_cast(GO); @@ -93,9 +71,9 @@ MCSection *LanaiTargetObjectFile::SelectSectionForGlobal( const GlobalObject *GO, SectionKind Kind, const TargetMachine &TM) const { // Handle Small Section classification here. - if (Kind.isBSS() && isGlobalInSmallSection(GO, TM, Kind)) + if (Kind.isSmallBSS()) return SmallBSSSection; - if (Kind.isData() && isGlobalInSmallSection(GO, TM, Kind)) + if (Kind.isSmallData()) return SmallDataSection; // Otherwise, we work the same as ELF. Index: lib/Target/Mips/MipsISelLowering.cpp =================================================================== --- lib/Target/Mips/MipsISelLowering.cpp +++ lib/Target/Mips/MipsISelLowering.cpp @@ -1734,11 +1734,9 @@ const GlobalValue *GV = N->getGlobal(); if (!isPositionIndependent() && !ABI.IsN64()) { - const MipsTargetObjectFile *TLOF = - static_cast( - getTargetMachine().getObjFileLowering()); const GlobalObject *GO = GV->getBaseObject(); - if (GO && TLOF->IsGlobalInSmallSection(GO, getTargetMachine())) + if (GO && TargetLoweringObjectFile::isGlobalInSmallSection( + GO, getTargetMachine())) // %gp_rel relocation return getAddrGPRel(N, SDLoc(N), Ty, DAG); @@ -1887,7 +1885,7 @@ static_cast( getTargetMachine().getObjFileLowering()); - if (TLOF->IsConstantInSmallSection(DAG.getDataLayout(), N->getConstVal(), + if (TLOF->isConstantInSmallSection(DAG.getDataLayout(), N->getConstVal(), getTargetMachine())) // %gp_rel relocation return getAddrGPRel(N, SDLoc(N), Ty, DAG); Index: lib/Target/Mips/MipsTargetObjectFile.h =================================================================== --- lib/Target/Mips/MipsTargetObjectFile.h +++ lib/Target/Mips/MipsTargetObjectFile.h @@ -19,24 +19,17 @@ MCSection *SmallBSSSection; const MipsTargetMachine *TM; - bool IsGlobalInSmallSection(const GlobalObject *GO, const TargetMachine &TM, - SectionKind Kind) const; - bool IsGlobalInSmallSectionImpl(const GlobalObject *GO, - const TargetMachine &TM) const; + bool isGlobalInSmallSectionKind(const GlobalObject *GO, + const TargetMachine &TM) const override; public: void Initialize(MCContext &Ctx, const TargetMachine &TM) override; - /// Return true if this global address should be placed into small data/bss - /// section. - bool IsGlobalInSmallSection(const GlobalObject *GO, - const TargetMachine &TM) const; - MCSection *SelectSectionForGlobal(const GlobalObject *GO, SectionKind Kind, const TargetMachine &TM) const override; /// Return true if this constant should be placed into small data section. - bool IsConstantInSmallSection(const DataLayout &DL, const Constant *CN, + bool isConstantInSmallSection(const DataLayout &DL, const Constant *CN, const TargetMachine &TM) const; MCSection *getSectionForConstant(const DataLayout &DL, SectionKind Kind, Index: lib/Target/Mips/MipsTargetObjectFile.cpp =================================================================== --- lib/Target/Mips/MipsTargetObjectFile.cpp +++ lib/Target/Mips/MipsTargetObjectFile.cpp @@ -22,7 +22,7 @@ static cl::opt SSThreshold("mips-ssection-threshold", cl::Hidden, - cl::desc("Small data and bss section threshold size (default=8)"), + cl::desc("MIPS: Small data and bss section threshold size (default=8)"), cl::init(8)); static cl::opt @@ -60,32 +60,10 @@ } /// Return true if this global address should be placed into small data/bss -/// section. -bool MipsTargetObjectFile::IsGlobalInSmallSection( - const GlobalObject *GO, const TargetMachine &TM) const { - // We first check the case where global is a declaration, because finding - // section kind using getKindForGlobal() is only allowed for global - // definitions. - if (GO->isDeclaration() || GO->hasAvailableExternallyLinkage()) - return IsGlobalInSmallSectionImpl(GO, TM); - - return IsGlobalInSmallSection(GO, TM, getKindForGlobal(GO, TM)); -} - -/// Return true if this global address should be placed into small data/bss -/// section. -bool MipsTargetObjectFile:: -IsGlobalInSmallSection(const GlobalObject *GO, const TargetMachine &TM, - SectionKind Kind) const { - return (IsGlobalInSmallSectionImpl(GO, TM) && - (Kind.isData() || Kind.isBSS() || Kind.isCommon())); -} - -/// Return true if this global address should be placed into small data/bss -/// section. This method does all the work, except for checking the section +/// section. This method does all the work, directly influencing section /// kind. bool MipsTargetObjectFile:: -IsGlobalInSmallSectionImpl(const GlobalObject *GO, +isGlobalInSmallSectionKind(const GlobalObject *GO, const TargetMachine &TM) const { const MipsSubtarget &Subtarget = *static_cast(TM).getSubtargetImpl(); @@ -119,9 +97,9 @@ // sections? // Handle Small Section classification here. - if (Kind.isBSS() && IsGlobalInSmallSection(GO, TM, Kind)) + if (Kind.isSmallBSS()) return SmallBSSSection; - if (Kind.isData() && IsGlobalInSmallSection(GO, TM, Kind)) + if (Kind.isSmallData()) return SmallDataSection; // Otherwise, we work the same as ELF. @@ -129,7 +107,7 @@ } /// Return true if this constant should be placed into small data section. -bool MipsTargetObjectFile::IsConstantInSmallSection( +bool MipsTargetObjectFile::isConstantInSmallSection( const DataLayout &DL, const Constant *CN, const TargetMachine &TM) const { return (static_cast(TM) .getSubtargetImpl() @@ -142,7 +120,7 @@ SectionKind Kind, const Constant *C, unsigned &Align) const { - if (IsConstantInSmallSection(DL, C, *TM)) + if (isConstantInSmallSection(DL, C, *TM)) return SmallDataSection; // Otherwise, we work the same as ELF. Index: lib/Target/PowerPC/AsmParser/PPCAsmParser.cpp =================================================================== --- lib/Target/PowerPC/AsmParser/PPCAsmParser.cpp +++ lib/Target/PowerPC/AsmParser/PPCAsmParser.cpp @@ -22,9 +22,11 @@ #include "llvm/MC/MCParser/MCParsedAsmOperand.h" #include "llvm/MC/MCParser/MCTargetAsmParser.h" #include "llvm/MC/MCRegisterInfo.h" +#include "llvm/MC/MCSectionELF.h" #include "llvm/MC/MCStreamer.h" #include "llvm/MC/MCSubtargetInfo.h" #include "llvm/MC/MCSymbolELF.h" +#include "llvm/Support/ELF.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/TargetRegistry.h" #include "llvm/Support/raw_ostream.h" @@ -280,6 +282,8 @@ bool ParseDarwinDirectiveMachine(SMLoc L); bool ParseDirectiveAbiVersion(SMLoc L); bool ParseDirectiveLocalEntry(SMLoc L); + bool ParseElfSSectionDirective(SMLoc L, StringRef Section, + unsigned Type, unsigned Flags); bool MatchAndEmitInstruction(SMLoc IDLoc, unsigned &Opcode, OperandVector &Operands, MCStreamer &Out, @@ -1785,6 +1789,17 @@ return ParseDirectiveAbiVersion(DirectiveID.getLoc()); if (IDVal == ".localentry") return ParseDirectiveLocalEntry(DirectiveID.getLoc()); + if (IDVal == ".sdata") + return ParseElfSSectionDirective(DirectiveID.getLoc(), IDVal, + ELF::SHT_PROGBITS, + ELF::SHF_WRITE | ELF::SHF_ALLOC); + if (IDVal == ".sdata2") + return ParseElfSSectionDirective(DirectiveID.getLoc(), IDVal, + ELF::SHT_PROGBITS, ELF::SHF_ALLOC); + if (IDVal == ".sbss") + return ParseElfSSectionDirective(DirectiveID.getLoc(), IDVal, + ELF::SHT_NOBITS, + ELF::SHF_WRITE | ELF::SHF_ALLOC); } else { if (IDVal == ".machine") return ParseDarwinDirectiveMachine(DirectiveID.getLoc()); @@ -1976,6 +1991,25 @@ return false; } +/// parseSSectionDirective +/// ::= .sbss +/// ::= .sdata +/// ::= .sdata2 +bool PPCAsmParser::ParseElfSSectionDirective(SMLoc L, StringRef Section, + unsigned Type, unsigned Flags) { + // If this is not the end of the statement, report an error. + if (getLexer().isNot(AsmToken::EndOfStatement)) { + Error(L, "unexpected token, expected end of statement"); + return false; + } + + MCSection *ELFSection = getContext().getELFSection( + Section, Type, Flags); + getParser().getStreamer().SwitchSection(ELFSection); + + getParser().Lex(); // Eat EndOfStatement token. + return false; +} /// Force static initialization. Index: lib/Target/PowerPC/MCTargetDesc/PPCELFObjectWriter.cpp =================================================================== --- lib/Target/PowerPC/MCTargetDesc/PPCELFObjectWriter.cpp +++ lib/Target/PowerPC/MCTargetDesc/PPCELFObjectWriter.cpp @@ -296,6 +296,9 @@ case MCSymbolRefExpr::VK_PPC_GOT_DTPREL_HA: Type = ELF::R_PPC64_GOT_DTPREL16_HA; break; + case MCSymbolRefExpr::VK_PPC_SDA: + Type = ELF::R_PPC_EMB_SDA21; + break; } break; case PPC::fixup_ppc_half16ds: Index: lib/Target/PowerPC/MCTargetDesc/PPCMCCodeEmitter.cpp =================================================================== --- lib/Target/PowerPC/MCTargetDesc/PPCMCCodeEmitter.cpp +++ lib/Target/PowerPC/MCTargetDesc/PPCMCCodeEmitter.cpp @@ -97,7 +97,7 @@ unsigned getMachineOpValue(const MCInst &MI,const MCOperand &MO, SmallVectorImpl &Fixups, const MCSubtargetInfo &STI) const; - + // getBinaryCodeForInstr - TableGen'erated function for getting the // binary encoding for an instruction. uint64_t getBinaryCodeForInstr(const MCInst &MI, @@ -136,12 +136,12 @@ default: llvm_unreachable ("Invalid instruction size"); } - + ++MCNumEmitted; // Keep track of the # of mi's emitted. } - + }; - + } // end anonymous namespace MCCodeEmitter *llvm::createPPCMCCodeEmitter(const MCInstrInfo &MCII, @@ -156,7 +156,7 @@ const MCSubtargetInfo &STI) const { const MCOperand &MO = MI.getOperand(OpNo); if (MO.isReg() || MO.isImm()) return getMachineOpValue(MI, MO, Fixups, STI); - + // Add a fixup for the branch target. Fixups.push_back(MCFixup::create(0, MO.getExpr(), (MCFixupKind)PPC::fixup_ppc_br24)); @@ -206,7 +206,7 @@ const MCSubtargetInfo &STI) const { const MCOperand &MO = MI.getOperand(OpNo); if (MO.isReg() || MO.isImm()) return getMachineOpValue(MI, MO, Fixups, STI); - + // Add a fixup for the immediate field. Fixups.push_back(MCFixup::create(IsLittleEndian? 0 : 2, MO.getExpr(), (MCFixupKind)PPC::fixup_ppc_half16)); @@ -220,11 +220,21 @@ // displacement and the next 5 bits as the register #. assert(MI.getOperand(OpNo+1).isReg()); unsigned RegBits = getMachineOpValue(MI, MI.getOperand(OpNo+1), Fixups, STI) << 16; - + const MCOperand &MO = MI.getOperand(OpNo); if (MO.isImm()) return (getMachineOpValue(MI, MO, Fixups, STI) & 0xFFFF) | RegBits; - + else if (MO.isExpr()) { + if (const MCSymbolRefExpr *Expr = dyn_cast(MO.getExpr())) { + if (Expr->getKind() == MCSymbolRefExpr::VK_PPC_SDA) { + // Small data relocation must be one byte into instruction + Fixups.push_back(MCFixup::create(IsLittleEndian? 0 : 1, MO.getExpr(), + (MCFixupKind)PPC::fixup_ppc_half16)); + return 0; // RegBits added by SDA21 relocation + } + } + } + // Add a fixup for the displacement field. Fixups.push_back(MCFixup::create(IsLittleEndian? 0 : 2, MO.getExpr(), (MCFixupKind)PPC::fixup_ppc_half16)); @@ -239,11 +249,11 @@ // displacement and the next 5 bits as the register #. assert(MI.getOperand(OpNo+1).isReg()); unsigned RegBits = getMachineOpValue(MI, MI.getOperand(OpNo+1), Fixups, STI) << 14; - + const MCOperand &MO = MI.getOperand(OpNo); if (MO.isImm()) return ((getMachineOpValue(MI, MO, Fixups, STI) >> 2) & 0x3FFF) | RegBits; - + // Add a fixup for the displacement field. Fixups.push_back(MCFixup::create(IsLittleEndian? 0 : 2, MO.getExpr(), (MCFixupKind)PPC::fixup_ppc_half16ds)); @@ -317,7 +327,7 @@ const MCSubtargetInfo &STI) const { const MCOperand &MO = MI.getOperand(OpNo); if (MO.isReg()) return getMachineOpValue(MI, MO, Fixups, STI); - + // Add a fixup for the TLS register, which simply provides a relocation // hint to the linker that this statement is part of a relocation sequence. // Return the thread-pointer register's encoding. @@ -370,7 +380,7 @@ return Encode; } - + assert(MO.isImm() && "Relocation required in an instruction that we cannot encode!"); return MO.getImm(); Index: lib/Target/PowerPC/PPC.h =================================================================== --- lib/Target/PowerPC/PPC.h +++ lib/Target/PowerPC/PPC.h @@ -53,7 +53,7 @@ extern char &PPCVSXFMAMutateID; namespace PPCII { - + /// Target Operand Flag enum. enum TOF { //===------------------------------------------------------------------===// @@ -93,12 +93,13 @@ MO_DTPREL_LO = 5 << 4, MO_TLSLD_LO = 6 << 4, MO_TOC_LO = 7 << 4, + MO_SDA_LO = 9 << 4, // Symbol for VK_PPC_TLS fixup attached to an ADD instruction MO_TLS = 8 << 4 }; } // end namespace PPCII - + } // end namespace llvm; #endif Index: lib/Target/PowerPC/PPCAsmPrinter.cpp =================================================================== --- lib/Target/PowerPC/PPCAsmPrinter.cpp +++ lib/Target/PowerPC/PPCAsmPrinter.cpp @@ -186,7 +186,7 @@ // Linux assembler (Others?) does not take register mnemonics. // FIXME - What about special registers used in mfspr/mtspr? - if (!Subtarget->isDarwin()) + if (Subtarget->shouldStripRegisterPrefix()) RegName = stripRegisterPrefix(RegName); O << RegName; return; @@ -287,7 +287,7 @@ case 'y': // A memory reference for an X-form instruction { const char *RegName = "r0"; - if (!Subtarget->isDarwin()) + if (Subtarget->shouldStripRegisterPrefix()) RegName = stripRegisterPrefix(RegName); O << RegName << ", "; printOperand(MI, OpNo, O); Index: lib/Target/PowerPC/PPCISelLowering.cpp =================================================================== --- lib/Target/PowerPC/PPCISelLowering.cpp +++ lib/Target/PowerPC/PPCISelLowering.cpp @@ -1900,6 +1900,29 @@ Disp.getOpcode() == ISD::TargetConstantPool || Disp.getOpcode() == ISD::TargetJumpTable); Base = N.getOperand(0); + + // Apply EABI small data relocation if eligible + if (getTargetMachine().getTargetTriple().isEABI()) { + if (GlobalAddressSDNode *GSDN = + dyn_cast(Disp.getNode())) { + const GlobalValue *GV = GSDN->getGlobal(); + if (const GlobalVariable *GVar = dyn_cast(GV)) { + if (TargetLoweringObjectFile::isGlobalInSmallSection( + GVar, getTargetMachine())) { + // This register selection is only relevant for ASM printing. + // MO_SDA_LO will ensure encoded register is R0, and the linker + // selects the actual base register during relocation. + Base = DAG.getRegister(GVar->isConstant() ? PPC::R2 : PPC::R13, + MVT::i32); + Disp = DAG.getTargetGlobalAddress(GV, SDLoc(GSDN), + Disp.getValueType(), + GSDN->getOffset(), + PPCII::MO_SDA_LO); + } + } + } + } + return true; // [&g+r] } } else if (N.getOpcode() == ISD::OR) { Index: lib/Target/PowerPC/PPCMCInstLower.cpp =================================================================== --- lib/Target/PowerPC/PPCMCInstLower.cpp +++ lib/Target/PowerPC/PPCMCInstLower.cpp @@ -75,7 +75,7 @@ } return Sym; } - + return Sym; } @@ -105,6 +105,9 @@ case PPCII::MO_TLS: RefKind = MCSymbolRefExpr::VK_PPC_TLS; break; + case PPCII::MO_SDA_LO: + RefKind = MCSymbolRefExpr::VK_PPC_SDA; + break; } if (MO.getTargetFlags() == PPCII::MO_PLT) @@ -120,7 +123,7 @@ // Subtract off the PIC base if required. if (MO.getTargetFlags() & PPCII::MO_PIC_FLAG) { const MachineFunction *MF = MO.getParent()->getParent()->getParent(); - + const MCExpr *PB = MCSymbolRefExpr::create(MF->getPICBaseSymbol(), Ctx); Expr = MCBinaryExpr::createSub(Expr, PB, Ctx); } @@ -141,10 +144,10 @@ void llvm::LowerPPCMachineInstrToMCInst(const MachineInstr *MI, MCInst &OutMI, AsmPrinter &AP, bool isDarwin) { OutMI.setOpcode(MI->getOpcode()); - + for (unsigned i = 0, e = MI->getNumOperands(); i != e; ++i) { const MachineOperand &MO = MI->getOperand(i); - + MCOperand MCOp; switch (MO.getType()) { default: @@ -181,7 +184,7 @@ case MachineOperand::MO_RegisterMask: continue; } - + OutMI.addOperand(MCOp); } } Index: lib/Target/PowerPC/PPCSubtarget.h =================================================================== --- lib/Target/PowerPC/PPCSubtarget.h +++ lib/Target/PowerPC/PPCSubtarget.h @@ -289,6 +289,13 @@ bool isDarwin() const { return TargetTriple.isMacOSX(); } /// isBGQ - True if this is a BG/Q platform. bool isBGQ() const { return TargetTriple.getVendor() == Triple::BGQ; } + /// isEABI - True if this is an EABI platform. + bool isEABI() const { return TargetTriple.isEABI(); } + + /// shouldStripRegisterPrefix - True if platform uses PPC assembler that + /// needs register prefixes stripped + bool shouldStripRegisterPrefix() const { return !isDarwin() && + !isEABI(); } bool isTargetELF() const { return TargetTriple.isOSBinFormatELF(); } bool isTargetMachO() const { return TargetTriple.isOSBinFormatMachO(); } Index: lib/Target/PowerPC/PPCTargetMachine.h =================================================================== --- lib/Target/PowerPC/PPCTargetMachine.h +++ lib/Target/PowerPC/PPCTargetMachine.h @@ -43,6 +43,8 @@ const PPCSubtarget *getSubtargetImpl(const Function &F) const override; + const PPCSubtarget *getSubtargetImpl() const { return &Subtarget; } + // Pass Pipeline Configuration TargetPassConfig *createPassConfig(PassManagerBase &PM) override; Index: lib/Target/PowerPC/PPCTargetMachine.cpp =================================================================== --- lib/Target/PowerPC/PPCTargetMachine.cpp +++ lib/Target/PowerPC/PPCTargetMachine.cpp @@ -151,6 +151,9 @@ if (TT.isOSDarwin()) return make_unique(); + if (TT.isEABI()) + return make_unique(); + return make_unique(); } Index: lib/Target/PowerPC/PPCTargetObjectFile.h =================================================================== --- lib/Target/PowerPC/PPCTargetObjectFile.h +++ lib/Target/PowerPC/PPCTargetObjectFile.h @@ -29,6 +29,24 @@ const MCExpr *getDebugThreadLocalSymbol(const MCSymbol *Sym) const override; }; + /// PPCEmbeddedTargetObjectFile - This implementation is used for + /// 32-bit PPC-EABI. + class PPCEmbeddedTargetObjectFile : public TargetLoweringObjectFileELF { + MCSection *SmallDataSection = nullptr; + MCSection *SmallData2Section = nullptr; + MCSection *SmallBssSection = nullptr; + + bool isGlobalInSmallSectionKind(const GlobalObject *GO, + const TargetMachine &TM) const override; + + public: + + void Initialize(MCContext &Ctx, const TargetMachine &TM) override; + + MCSection *SelectSectionForGlobal(const GlobalObject *GO, SectionKind Kind, + const TargetMachine &TM) const override; + }; + } // end namespace llvm #endif Index: lib/Target/PowerPC/PPCTargetObjectFile.cpp =================================================================== --- lib/Target/PowerPC/PPCTargetObjectFile.cpp +++ lib/Target/PowerPC/PPCTargetObjectFile.cpp @@ -12,9 +12,16 @@ #include "llvm/MC/MCContext.h" #include "llvm/MC/MCExpr.h" #include "llvm/MC/MCSectionELF.h" +#include "PPCSubtarget.h" +#include "PPCTargetMachine.h" using namespace llvm; +static cl::opt +SSThreshold("ppc-ssection-threshold", cl::Hidden, + cl::desc("PPC: Small data and bss section threshold size (default=8)"), + cl::init(8)); + void PPC64LinuxTargetObjectFile:: Initialize(MCContext &Ctx, const TargetMachine &TM) { @@ -57,3 +64,55 @@ getContext()); } + +// A address must be loaded from a small section if its size is less than the +// small section size threshold. Data in this section must be addressed against +// non-volatile r13 for .sdata/.sbss and r2 for .sdata2. +static bool isInSmallSection(uint64_t Size) { + // gcc has traditionally not treated zero-sized objects as small data, so this + // is effectively part of the ABI. + return Size > 0 && Size <= SSThreshold; +} + +/// Return true if this global address should be placed into small data/bss +/// section. This method does all the work, directly influencing section +/// kind. +bool PPCEmbeddedTargetObjectFile:: +isGlobalInSmallSectionKind(const GlobalObject *GO, + const TargetMachine &TM) const { + // Only global variables, not functions. + const GlobalVariable *GVA = dyn_cast(GO); + if (!GVA) + return false; + + Type *Ty = GVA->getValueType(); + return isInSmallSection( + GVA->getParent()->getDataLayout().getTypeAllocSize(Ty)); +} + +void +PPCEmbeddedTargetObjectFile:: +Initialize(MCContext &Ctx, const TargetMachine &TM) { + TargetLoweringObjectFileELF::Initialize(Ctx, TM); + InitializeELF(TM.Options.UseInitArray); + + SmallDataSection = getContext().getELFSection( + ".sdata", ELF::SHT_PROGBITS, ELF::SHF_WRITE | ELF::SHF_ALLOC); + SmallData2Section = getContext().getELFSection( + ".sdata2", ELF::SHT_PROGBITS, ELF::SHF_ALLOC); + SmallBssSection = getContext().getELFSection( + ".sbss", ELF::SHT_NOBITS, ELF::SHF_WRITE | ELF::SHF_ALLOC); +} + +MCSection *PPCEmbeddedTargetObjectFile::SelectSectionForGlobal( + const GlobalObject *GO, SectionKind Kind, const TargetMachine &TM) const { + // Handle Small Section classification here. + if (Kind.isSmallBSS()) + return SmallBssSection; + if (Kind.isSmallReadOnly()) + return SmallData2Section; + if (Kind.isSmallKind()) + return SmallDataSection; + + return TargetLoweringObjectFileELF::SelectSectionForGlobal(GO, Kind, TM); +} Index: lib/Target/TargetLoweringObjectFile.cpp =================================================================== --- lib/Target/TargetLoweringObjectFile.cpp +++ lib/Target/TargetLoweringObjectFile.cpp @@ -86,10 +86,10 @@ if (const ConstantDataSequential *CDS = dyn_cast(C)) { unsigned NumElts = CDS->getNumElements(); assert(NumElts != 0 && "Can't have an empty CDS"); - + if (CDS->getElementAsInteger(NumElts-1) != 0) return false; // Not null terminated. - + // Verify that the null doesn't occur anywhere else in the string. for (unsigned i = 0; i != NumElts-1; ++i) if (CDS->getElementAsInteger(i) == 0) @@ -133,7 +133,7 @@ /// implementations simpler. The target implementation is free to ignore this /// extra info of course. SectionKind TargetLoweringObjectFile::getKindForGlobal(const GlobalObject *GO, - const TargetMachine &TM){ + const TargetMachine &TM) { assert(!GO->isDeclaration() && !GO->hasAvailableExternallyLinkage() && "Can only be used for global definitions"); @@ -151,17 +151,41 @@ return SectionKind::getThreadData(); } + // Provide target-dependent decision for data sections whether the global + // should be allocated in the small section. + const TargetLoweringObjectFile *TLOF = TM.getObjFileLowering(); + auto GetData = [=,&TM]() -> SectionKind { + return (!TLOF || !TLOF->isGlobalInSmallSectionKind(GO, TM)) ? + SectionKind::getData() : SectionKind::getSmallData(); + }; + auto GetReadOnly = [=,&TM]() -> SectionKind { + return (!TLOF || !TLOF->isGlobalInSmallSectionKind(GO, TM)) ? + SectionKind::getReadOnly() : SectionKind::getSmallReadOnly(); + }; + auto GetBSS = [=,&TM](SectionKind OrigBSS) -> SectionKind { + return (!TLOF || !TLOF->isGlobalInSmallSectionKind(GO, TM)) ? + OrigBSS : SectionKind::getSmallBSS(); + }; + auto GetCommon = [=,&TM]() -> SectionKind { + return (!TLOF || !TLOF->isGlobalInSmallSectionKind(GO, TM)) ? + SectionKind::getCommon() : SectionKind::getSmallCommon(); + }; + auto GetMergeableConst = [=,&TM](SectionKind OrigMerge) -> SectionKind { + return (!TLOF || !TLOF->isGlobalInSmallSectionKind(GO, TM)) ? + OrigMerge : SectionKind::getSmallReadOnly(); + }; + // Variables with common linkage always get classified as common. if (GVar->hasCommonLinkage()) - return SectionKind::getCommon(); + return GetCommon(); // Variable can be easily put to BSS section. if (isSuitableForBSS(GVar, TM.Options.NoZerosInBSS)) { if (GVar->hasLocalLinkage()) - return SectionKind::getBSSLocal(); + return GetBSS(SectionKind::getBSSLocal()); else if (GVar->hasExternalLinkage()) - return SectionKind::getBSSExtern(); - return SectionKind::getBSS(); + return GetBSS(SectionKind::getBSSExtern()); + return GetBSS(SectionKind::getBSS()); } const Constant *C = GVar->getInitializer(); @@ -177,7 +201,7 @@ // into a mergable section: just drop it into the general read-only // section instead. if (!GVar->hasGlobalUnnamedAddr()) - return SectionKind::getReadOnly(); + return GetReadOnly(); // If initializer is a null-terminated string, put it in a "cstring" // section of the right width. @@ -203,12 +227,12 @@ // mergable section. switch ( GVar->getParent()->getDataLayout().getTypeAllocSize(C->getType())) { - case 4: return SectionKind::getMergeableConst4(); - case 8: return SectionKind::getMergeableConst8(); - case 16: return SectionKind::getMergeableConst16(); - case 32: return SectionKind::getMergeableConst32(); + case 4: return GetMergeableConst(SectionKind::getMergeableConst4()); + case 8: return GetMergeableConst(SectionKind::getMergeableConst8()); + case 16: return GetMergeableConst(SectionKind::getMergeableConst16()); + case 32: return GetMergeableConst(SectionKind::getMergeableConst32()); default: - return SectionKind::getReadOnly(); + return GetReadOnly(); } } else { @@ -219,7 +243,7 @@ // consideration when it tries to merge entries in the section. if (ReloModel == Reloc::Static || ReloModel == Reloc::ROPI || ReloModel == Reloc::RWPI || ReloModel == Reloc::ROPI_RWPI) - return SectionKind::getReadOnly(); + return GetReadOnly(); // Otherwise, the dynamic linker needs to fix it up, put it in the // writable data.rel section. @@ -228,7 +252,24 @@ } // Okay, this isn't a constant. - return SectionKind::getData(); + return GetData(); +} + +/// Determine if global variable is allocated in a subtarget small data +/// SectionKind. +bool TargetLoweringObjectFile::isGlobalInSmallSection(const GlobalObject *GO, + const TargetMachine &TM) { + if (GO->isDeclaration() || GO->hasAvailableExternallyLinkage()) { + // getKindForGlobal only accepts defined objects. This failsafe bypasses + // classification steps not needed when handling declarations. + const TargetLoweringObjectFile *TLOF = TM.getObjFileLowering(); + assert(TLOF != nullptr && + "No TargetLoweringObjectFile for small section classification"); + return TLOF->isGlobalInSmallSectionKind(GO, TM); + } + + // Proceed with full SectionKind classification + return getKindForGlobal(GO, TM).isSmallKind(); } /// This method computes the appropriate section to emit the specified global Index: test/CodeGen/PowerPC/eabi-small-data.ll =================================================================== --- /dev/null +++ test/CodeGen/PowerPC/eabi-small-data.ll @@ -0,0 +1,40 @@ +; RUN: llc -mtriple=powerpc-unknown-unknown-eabi -ppc-ssection-threshold=8 < %s | FileCheck %s -check-prefix=SMALL8 +; RUN: llc -mtriple=powerpc-unknown-unknown-eabi -ppc-ssection-threshold=0 < %s | FileCheck %s -check-prefix=SMALL0 + +@s1 = internal unnamed_addr global i32 8, align 4 +@s2 = internal unnamed_addr global i32 4, align 4 +@g1 = external global i32 +@c1 = external constant i32 + +define void @foo() nounwind { +entry: + %0 = load i32, i32* @s1, align 4 +; SMALL8: lwz 3, s1@sdarx(13) +; SMALL0: lwz 3, s1@l(30) + tail call void @foo1(i32 %0) nounwind +; SMALL8: bl foo1 +; SMALL0: bl foo1 + %1 = load i32, i32* @g1, align 4 + %2 = load i32, i32* @c1, align 4 +; SMALL8: lwz 3, g1@sdarx(13) +; SMALL8: lwz 4, c1@sdarx(2) +; SMALL0: lis 3, g1@ha +; SMALL0: lis 4, c1@ha +; SMALL0: lwz 5, g1@l(3) +; SMALL0: lwz 4, c1@l(4) + %add = add nsw i32 %1, 2 +; SMALL8: stw 3, s1@sdarx(13) +; SMALL8: addi 3, 3, 2 +; SMALL0: stw 5, s1@l(30) +; SMALL0: addi 5, 5, 2 + store i32 %1, i32* @s1, align 4 + store i32 %2, i32* @s2, align 4 + store i32 %add, i32* @g1, align 4 +; SMALL8: stw 4, s2@sdarx(13) +; SMALL8: stw 3, g1@sdarx(13) +; SMALL0: stw 4, s2@l(6) +; SMALL0: stw 5, g1@l(3) + ret void +} + +declare void @foo1(i32) Index: test/MC/PowerPC/ppc-reloc.s =================================================================== --- test/MC/PowerPC/ppc-reloc.s +++ test/MC/PowerPC/ppc-reloc.s @@ -1,19 +1,37 @@ # RUN: llvm-mc -triple=powerpc-unknown-linux-gnu -filetype=obj %s | \ # RUN: llvm-readobj -r | FileCheck %s - .section .text + .text .globl foo .type foo,@function - .align 2 foo: bl printf@plt + bl printf bl _GLOBAL_OFFSET_TABLE_@local-4 + lwz 4, smallval@sdarx(13) + lis 5, .LC1@ha + addi 5, 5, .LC1@l .LC1: .size foo, . - foo + .sdata + .globl smallval +smallval: + .long 0xDEADBEEF + .long foo + .long .LC1 - . + # CHECK: Relocations [ # CHECK-NEXT: Section {{.*}} .rela.text { # CHECK-NEXT: 0x0 R_PPC_PLTREL24 printf 0x0 -# CHECK-NEXT: 0x4 R_PPC_LOCAL24PC _GLOBAL_OFFSET_TABLE_ 0xFFFFFFFC +# CHECK-NEXT: 0x4 R_PPC_REL24 printf 0x0 +# CHECK-NEXT: 0x8 R_PPC_LOCAL24PC _GLOBAL_OFFSET_TABLE_ 0xFFFFFFFC +# CHECK-NEXT: 0xD R_PPC_EMB_SDA21 smallval 0x0 +# CHECK-NEXT: 0x12 R_PPC_ADDR16_HA .text 0x18 +# CHECK-NEXT: 0x16 R_PPC_ADDR16_LO .text 0x18 +# CHECK-NEXT: } +# CHECK-NEXT: Section {{.*}} .rela.sdata { +# CHECK-NEXT: 0x4 R_PPC_ADDR32 foo 0x0 +# CHECK-NEXT: 0x8 R_PPC_REL32 .text 0x18 # CHECK-NEXT: } # CHECK-NEXT: ]