Index: llvm/include/llvm/BinaryFormat/ELFRelocs/PowerPC64.def =================================================================== --- llvm/include/llvm/BinaryFormat/ELFRelocs/PowerPC64.def +++ llvm/include/llvm/BinaryFormat/ELFRelocs/PowerPC64.def @@ -97,6 +97,7 @@ #undef R_PPC64_DTPREL16_HIGH #undef R_PPC64_DTPREL16_HIGHA #undef R_PPC64_REL24_NOTOC +#undef R_PPC64_PCREL_OPT #undef R_PPC64_PCREL34 #undef R_PPC64_GOT_PCREL34 #undef R_PPC64_IRELATIVE @@ -194,6 +195,7 @@ ELF_RELOC(R_PPC64_DTPREL16_HIGH, 114) ELF_RELOC(R_PPC64_DTPREL16_HIGHA, 115) ELF_RELOC(R_PPC64_REL24_NOTOC, 116) +ELF_RELOC(R_PPC64_PCREL_OPT, 123) ELF_RELOC(R_PPC64_PCREL34, 132) ELF_RELOC(R_PPC64_GOT_PCREL34, 133) ELF_RELOC(R_PPC64_IRELATIVE, 248) Index: llvm/lib/MC/MCObjectStreamer.cpp =================================================================== --- llvm/lib/MC/MCObjectStreamer.cpp +++ llvm/lib/MC/MCObjectStreamer.cpp @@ -665,6 +665,68 @@ DF->getContents().resize(DF->getContents().size() + 8, 0); } +static Optional> +getOffsetAndDataFragment(const MCSymbol &Symbol, uint32_t &RelocOffset, + MCDataFragment *&DF) { + if (Symbol.isVariable()) { + const MCExpr *SymbolExpr = Symbol.getVariableValue(); + MCValue OffsetVal; + if(!SymbolExpr->evaluateAsRelocatable(OffsetVal, nullptr, nullptr)) + return std::make_pair(false, + std::string("symbol in .reloc offset is not " + "relocatable")); + if (OffsetVal.isAbsolute()) { + RelocOffset = OffsetVal.getConstant(); + MCFragment *Fragment = Symbol.getFragment(); + // FIXME Support symbols with no DF. For example: + // .reloc .data, ENUM_VALUE, + if (!Fragment || Fragment->getKind() != MCFragment::FT_Data) + return std::make_pair(false, + std::string("symbol in offset has no data " + "fragment")); + DF = cast(Fragment); + return None; + } + + if (OffsetVal.getSymB()) + return std::make_pair(false, + std::string(".reloc symbol offset is not " + "representable")); + + const MCSymbolRefExpr &SRE = cast(*OffsetVal.getSymA()); + if (!SRE.getSymbol().isDefined()) + return std::make_pair(false, + std::string("symbol used in the .reloc offset is " + "not defined")); + + if (SRE.getSymbol().isVariable()) + return std::make_pair(false, + std::string("symbol used in the .reloc offset is " + "variable")); + + MCFragment *Fragment = SRE.getSymbol().getFragment(); + // FIXME Support symbols with no DF. For example: + // .reloc .data, ENUM_VALUE, + if (!Fragment || Fragment->getKind() != MCFragment::FT_Data) + return std::make_pair(false, + std::string("symbol in offset has no data " + "fragment")); + RelocOffset = SRE.getSymbol().getOffset() + OffsetVal.getConstant(); + DF = cast(Fragment); + } else { + RelocOffset = Symbol.getOffset(); + MCFragment *Fragment = Symbol.getFragment(); + // FIXME Support symbols with no DF. For example: + // .reloc .data, ENUM_VALUE, + if (!Fragment || Fragment->getKind() != MCFragment::FT_Data) + return std::make_pair(false, + std::string("symbol in offset has no data " + "fragment")); + DF = cast(Fragment); + } + return None; +} + Optional> MCObjectStreamer::emitRelocDirective(const MCExpr &Offset, StringRef Name, const MCExpr *Expr, SMLoc Loc, @@ -698,10 +760,17 @@ std::string(".reloc offset is not representable")); const MCSymbolRefExpr &SRE = cast(*OffsetVal.getSymA()); - if (SRE.getSymbol().isDefined()) { - // FIXME SRE.getSymbol() may not be relative to DF. + const MCSymbol &Symbol = SRE.getSymbol(); + if (Symbol.isDefined()) { + uint32_t SymbolOffset = 0; + Optional> Error; + Error = getOffsetAndDataFragment(Symbol, SymbolOffset, DF); + + if (Error != None) + return Error; + DF->getFixups().push_back( - MCFixup::create(SRE.getSymbol().getOffset() + OffsetVal.getConstant(), + MCFixup::create(SymbolOffset + OffsetVal.getConstant(), Expr, Kind, Loc)); return None; } Index: llvm/lib/Target/PowerPC/MCTargetDesc/PPCAsmBackend.cpp =================================================================== --- llvm/lib/Target/PowerPC/MCTargetDesc/PPCAsmBackend.cpp +++ llvm/lib/Target/PowerPC/MCTargetDesc/PPCAsmBackend.cpp @@ -33,6 +33,7 @@ case FK_Data_4: case FK_Data_8: case PPC::fixup_ppc_nofixup: + case PPC::fixup_ppc_linker_opt: return Value; case PPC::fixup_ppc_brcond14: case PPC::fixup_ppc_brcond14abs: @@ -71,6 +72,7 @@ case FK_Data_8: return 8; case PPC::fixup_ppc_nofixup: + case PPC::fixup_ppc_linker_opt: return 0; } } @@ -99,7 +101,8 @@ { "fixup_ppc_brcond14abs", 16, 14, 0 }, { "fixup_ppc_half16", 0, 16, 0 }, { "fixup_ppc_half16ds", 0, 14, 0 }, - { "fixup_ppc_pcrel34", 0, 34, MCFixupKindInfo::FKF_IsPCRel }, + { "fixup_ppc_pcrel34", 0, 34, MCFixupKindInfo::FKF_IsPCRel }, + { "fixup_ppc_linker_opt", 0, 0, 0 }, { "fixup_ppc_nofixup", 0, 0, 0 } }; const static MCFixupKindInfo InfosLE[PPC::NumTargetFixupKinds] = { @@ -112,6 +115,7 @@ { "fixup_ppc_half16", 0, 16, 0 }, { "fixup_ppc_half16ds", 2, 14, 0 }, { "fixup_ppc_pcrel34", 0, 34, MCFixupKindInfo::FKF_IsPCRel }, + { "fixup_ppc_linker_opt", 0, 0, 0 }, { "fixup_ppc_nofixup", 0, 0, 0 } }; @@ -158,6 +162,8 @@ switch ((unsigned)Kind) { default: return Kind >= FirstLiteralRelocationKind; + case PPC::fixup_ppc_linker_opt: + return true; case PPC::fixup_ppc_br24: case PPC::fixup_ppc_br24abs: case PPC::fixup_ppc_br24_notoc: Index: llvm/lib/Target/PowerPC/MCTargetDesc/PPCELFObjectWriter.cpp =================================================================== --- llvm/lib/Target/PowerPC/MCTargetDesc/PPCELFObjectWriter.cpp +++ llvm/lib/Target/PowerPC/MCTargetDesc/PPCELFObjectWriter.cpp @@ -386,6 +386,9 @@ break; } break; + case PPC::fixup_ppc_linker_opt: + Type = ELF::R_PPC64_PCREL_OPT; + break; case PPC::fixup_ppc_nofixup: switch (Modifier) { default: llvm_unreachable("Unsupported Modifier"); Index: llvm/lib/Target/PowerPC/MCTargetDesc/PPCFixupKinds.h =================================================================== --- llvm/lib/Target/PowerPC/MCTargetDesc/PPCFixupKinds.h +++ llvm/lib/Target/PowerPC/MCTargetDesc/PPCFixupKinds.h @@ -43,6 +43,11 @@ // A 34-bit fixup corresponding to PC-relative paddi. fixup_ppc_pcrel34, + /// Not a true fixup, but ties a pc-relative got access to an associated + /// memory operation to indicate to the linker that the sequence is safe to + /// optimize. + fixup_ppc_linker_opt, + /// Not a true fixup, but ties a symbol to a call to __tls_get_addr for the /// TLS general and local dynamic models, or inserts the thread-pointer /// register number. Index: llvm/test/MC/PowerPC/pcrel-reloc-with-expr.s =================================================================== --- /dev/null +++ llvm/test/MC/PowerPC/pcrel-reloc-with-expr.s @@ -0,0 +1,401 @@ +# RUN: llvm-mc -triple=powerpc64le-unknown-unknown -filetype=obj %s | \ +# RUN: llvm-objdump -dr --mcpu=pwr10 - | FileCheck %s +# RUN: llvm-mc -triple=powerpc64-unknown-unknown -filetype=obj %s | \ +# RUN: llvm-objdump -dr --mcpu=pwr10 - | FileCheck %s + + +## +# This section of tests contains the MCBinaryExpr as the first parameter of the +# .reloc relocation. +## + .text + .abiversion 2 + .globl Minimal + .p2align 4 + .type Minimal,@function +Minimal: +.LMinimal$local: + pld 3, vec@got@pcrel(0), 1 +.Lpcrel1: + .reloc .Lpcrel1-8,R_PPC64_PCREL_OPT,.-(.Lpcrel1-8) + lwa 3, 4(3) + blr + .long 0 + .quad 0 +# CHECK-LABEL: Minimal +# CHECK: pld 3, 0(0), 1 +# CHECK-NEXT: R_PPC64_GOT_PCREL34 vec +# CHECK-NEXT: R_PPC64_PCREL_OPT *ABS*+0x8 +# CHECK-NEXT: lwa 3, 4(3) +# CHECK-NEXT: blr + + .globl SingleInsnBetween + .p2align 4 + .type SingleInsnBetween,@function +SingleInsnBetween: +.LSingleInsnBetween$local: + pld 3, vec@got@pcrel(0), 1 +.Lpcrel2: + addi 3, 3, 42 + .reloc .Lpcrel2-8,R_PPC64_PCREL_OPT,.-(.Lpcrel2-8) + lwa 3, 4(3) + blr + .long 0 + .quad 0 +# CHECK_LABEL: SingleInsnBetween +# CHECK: pld 3, 0(0), 1 +# CHECK-NEXT: R_PPC64_GOT_PCREL34 vec +# CHECK-NEXT: R_PPC64_PCREL_OPT *ABS*+0xc +# CHECK-NEXT: addi 3, 3, 42 +# CHECK-NEXT: lwa 3, 4(3) +# CHECK-NEXT: blr + + + .globl MultiInsnBetween # -- Begin function + .p2align 4 + .type MultiInsnBetween,@function +MultiInsnBetween: +.LMultiInsnBetween$local: + pld 3, vec@got@pcrel(0), 1 +.Lpcrel3: + addi 3, 3, 42 + addi 3, 3, 42 + addi 3, 3, 42 + addi 3, 3, 42 + addi 3, 3, 42 + .reloc .Lpcrel3-8,R_PPC64_PCREL_OPT,.-(.Lpcrel3-8) + lwa 3, 4(3) + blr + .long 0 + .quad 0 +# CHECK_LABEL: MultiInsnBetween +# CHECK: pld 3, 0(0), 1 +# CHECK-NEXT: R_PPC64_GOT_PCREL34 vec +# CHECK-NEXT: R_PPC64_PCREL_OPT *ABS*+0x1c +# CHECK-NEXT: addi 3, 3, 42 +# CHECK-NEXT: addi 3, 3, 42 +# CHECK-NEXT: addi 3, 3, 42 +# CHECK-NEXT: addi 3, 3, 42 +# CHECK-NEXT: addi 3, 3, 42 +# CHECK-NEXT: lwa 3, 4(3) +# CHECK-NEXT: blr + + .globl PrefixInsnBetween + .p2align 6 + .type PrefixInsnBetween,@function + .space 48 # Add a space to force an alignment of a paddi. +PrefixInsnBetween: +.LPrefixInsnBetween$local: + pld 3, vec@got@pcrel(0), 1 +.Lpcrel4: + addi 3, 3, 42 + paddi 3, 3, 42, 0 + addi 3, 3, 42 + paddi 3, 3, 42, 0 + addi 3, 3, 42 + .reloc .Lpcrel4-8,R_PPC64_PCREL_OPT,.-(.Lpcrel4-8) + lwa 3, 4(3) + blr + .long 0 + .quad 0 +# CHECK_LABEL: PrefixInsnBetween +# CHECK: pld 3, 0(0), 1 +# CHECK-NEXT: R_PPC64_GOT_PCREL34 vec +# CHECK-NEXT: R_PPC64_PCREL_OPT *ABS*+0x28 +# CHECK-NEXT: addi 3, 3, 42 +# CHECK-NEXT: nop +# CHECK-NEXT: paddi 3, 3, 42, 0 +# CHECK-NEXT: addi 3, 3, 42 +# CHECK-NEXT: paddi 3, 3, 42, 0 +# CHECK-NEXT: addi 3, 3, 42 +# CHECK-NEXT: lwa 3, 4(3) +# CHECK-NEXT: blr + + + .globl SpaceBetween # -- Begin function + .p2align 4 + .type SpaceBetween,@function +SpaceBetween: +.LSpaceBetween$local: + pld 3, vec@got@pcrel(0), 1 +.Lpcrel5: + addi 3, 3, 42 + paddi 3, 3, 42, 0 + addi 3, 3, 42 + .space 40, 0 + paddi 3, 3, 42, 0 + addi 3, 3, 42 + .reloc .Lpcrel5-8,R_PPC64_PCREL_OPT,.-(.Lpcrel5-8) + lwa 3, 4(3) + blr + .long 0 + .quad 0 +# CHECK_LABEL: SpaceBetween +# CHECK: pld 3, 0(0), 1 +# CHECK-NEXT: R_PPC64_GOT_PCREL34 vec +# CHECK-NEXT: R_PPC64_PCREL_OPT *ABS*+0x50 +# CHECK-NEXT: addi 3, 3, 42 +# CHECK-NEXT: nop +# CHECK-NEXT: paddi 3, 3, 42, 0 +# CHECK-NEXT: addi 3, 3, 42 +# CHECK: paddi 3, 3, 42, 0 +# CHECK-NEXT: addi 3, 3, 42 +# CHECK-NEXT: lwa 3, 4(3) +# CHECK-NEXT: blr + + + .globl Plus + .p2align 4 + .type Plus,@function +Plus: +.LPlus$local: +.Lpcrel6: + addi 3, 3, 42 + addi 3, 3, 42 + pld 3, vec@got@pcrel(0), 1 + .reloc .Lpcrel6+8,R_PPC64_PCREL_OPT,.-(.Lpcrel6+8) + lwa 3, 4(3) + blr + .long 0 + .quad 0 +# CHECK-LABEL: Plus +# CHECK: pld 3, 0(0), 1 +# CHECK-NEXT: R_PPC64_PCREL_OPT *ABS*+0x8 +# CHECK-NEXT: R_PPC64_GOT_PCREL34 vec +# CHECK-NEXT: lwa 3, 4(3) +# CHECK-NEXT: blr + +## +# This section of tests contains the variable MCSymbol as part of the +# MCSymbolRefExpr for the first parameter of the .reloc relocation. +## + .globl VarLabelMinimal # -- Begin function + .p2align 4 + .type VarLabelMinimal,@function +VarLabelMinimal: +.LVarLabelMinimal$local: + pld 3, vec@got@pcrel(0), 1 +.Lpcrel101=.-8 + .reloc .Lpcrel101,R_PPC64_PCREL_OPT,.-.Lpcrel101 + lwa 3, 4(3) + blr + .long 0 + .quad 0 +# CHECK-LABEL: VarLabelMinimal +# CHECK: pld 3, 0(0), 1 +# CHECK-NEXT: R_PPC64_GOT_PCREL34 vec +# CHECK-NEXT: R_PPC64_PCREL_OPT *ABS*+0x8 +# CHECK-NEXT: lwa 3, 4(3) +# CHECK-NEXT: blr + + + .globl VarLabelSingleInsnBetween + .p2align 4 + .type VarLabelSingleInsnBetween,@function +VarLabelSingleInsnBetween: +.LVarLabelSingleInsnBetween$local: + pld 3, vec@got@pcrel(0), 1 +.Lpcrel102=.-8 + addi 3, 3, 42 + .reloc .Lpcrel102,R_PPC64_PCREL_OPT,.-.Lpcrel102 + lwa 3, 4(3) + blr + .long 0 + .quad 0 +# CHECK_LABEL: VarLabelSingleInsnBetween +# CHECK: pld 3, 0(0), 1 +# CHECK-NEXT: R_PPC64_GOT_PCREL34 vec +# CHECK-NEXT: R_PPC64_PCREL_OPT *ABS*+0xc +# CHECK-NEXT: addi 3, 3, 42 +# CHECK-NEXT: lwa 3, 4(3) +# CHECK-NEXT: blr + + .globl VarLabelMultiInsnBetween # -- Begin function + .p2align 4 + .type VarLabelMultiInsnBetween,@function +VarLabelMultiInsnBetween: +.LVarLabelMultiInsnBetween$local: + pld 3, vec@got@pcrel(0), 1 +.Lpcrel103=.-8 + addi 3, 3, 42 + addi 3, 3, 42 + addi 3, 3, 42 + addi 3, 3, 42 + addi 3, 3, 42 + .reloc .Lpcrel103,R_PPC64_PCREL_OPT,.-.Lpcrel103 + lwa 3, 4(3) + blr + .long 0 + .quad 0 +# CHECK_LABEL: VarLabelMultiInsnBetween +# CHECK: pld 3, 0(0), 1 +# CHECK-NEXT: R_PPC64_GOT_PCREL34 vec +# CHECK-NEXT: R_PPC64_PCREL_OPT *ABS*+0x1c +# CHECK-NEXT: addi 3, 3, 42 +# CHECK-NEXT: addi 3, 3, 42 +# CHECK-NEXT: addi 3, 3, 42 +# CHECK-NEXT: addi 3, 3, 42 +# CHECK-NEXT: addi 3, 3, 42 +# CHECK-NEXT: lwa 3, 4(3) +# CHECK-NEXT: blr + + + .globl VarLabelPrefixInsnBetween # -- Begin function + .p2align 4 + .type VarLabelPrefixInsnBetween,@function +VarLabelPrefixInsnBetween: +.LVarLabelPrefixInsnBetween$local: + pld 3, vec@got@pcrel(0), 1 +.Lpcrel104=.-8 + addi 3, 3, 42 + paddi 3, 3, 42, 0 + addi 3, 3, 42 + paddi 3, 3, 42, 0 + addi 3, 3, 42 + .reloc .Lpcrel104,R_PPC64_PCREL_OPT,.-.Lpcrel104 + lwa 3, 4(3) + blr + .long 0 + .quad 0 +# CHECK_LABEL: VarLabelPrefixInsnBetween +# CHECK: pld 3, 0(0), 1 +# CHECK-NEXT: R_PPC64_GOT_PCREL34 vec +# CHECK-NEXT: R_PPC64_PCREL_OPT *ABS*+0x24 +# CHECK-NEXT: addi 3, 3, 42 +# CHECK-NEXT: paddi 3, 3, 42, 0 +# CHECK-NEXT: addi 3, 3, 42 +# CHECK-NEXT: paddi 3, 3, 42, 0 +# CHECK-NEXT: addi 3, 3, 42 +# CHECK-NEXT: lwa 3, 4(3) +# CHECK-NEXT: blr + + + .globl VarLabelSpaceBetween # -- Begin function + .p2align 4 + .type VarLabelSpaceBetween,@function +VarLabelSpaceBetween: +.LVarLabelSpaceBetween$local: + pld 3, vec@got@pcrel(0), 1 +.Lpcrel105=.-8 + addi 3, 3, 42 + paddi 3, 3, 42, 0 + addi 3, 3, 42 + .space 40, 0 + paddi 3, 3, 42, 0 + addi 3, 3, 42 + .reloc .Lpcrel105,R_PPC64_PCREL_OPT,.-.Lpcrel105 + lwa 3, 4(3) + blr + .long 0 + .quad 0 +# CHECK_LABEL: VarLabelSpaceBetween +# CHECK: pld 3, 0(0), 1 +# CHECK-NEXT: R_PPC64_GOT_PCREL34 vec +# CHECK-NEXT: R_PPC64_PCREL_OPT *ABS*+0x4c +# CHECK-NEXT: addi 3, 3, 42 +# CHECK-NEXT: paddi 3, 3, 42, 0 +# CHECK-NEXT: addi 3, 3, 42 +# CHECK: paddi 3, 3, 42, 0 +# CHECK-NEXT: addi 3, 3, 42 +# CHECK-NEXT: lwa 3, 4(3) +# CHECK-NEXT: blr + + + .globl VarLabelPlus + .p2align 4 + .type VarLabelPlus,@function +VarLabelPlus: +.LVarLabelPlus$local: +.Lpcrel106: + addi 3, 3, 42 + addi 3, 3, 42 + pld 3, vec@got@pcrel(0), 1 + .reloc .Lpcrel106+8,R_PPC64_PCREL_OPT,.-(.Lpcrel106+8) + lwa 3, 4(3) + blr + .long 0 + .quad 0 +# CHECK-LABEL: VarLabelPlus +# CHECK: pld 3, 0(0), 1 +# CHECK-NEXT: R_PPC64_PCREL_OPT *ABS*+0x8 +# CHECK-NEXT: R_PPC64_GOT_PCREL34 vec +# CHECK-NEXT: lwa 3, 4(3) +# CHECK-NEXT: blr + +# Check the situation where the PLD requires an alignment nop. + .globl AlignPLD + .p2align 6 + .type AlignPLD,@function + .space 60 # Force the pld to require an alignment nop. +AlignPLD: +.LAlignPLD$local: + pld 3, vec@got@pcrel(0), 1 +.Lpcrel201: + .reloc .Lpcrel201-8,R_PPC64_PCREL_OPT,.-(.Lpcrel201-8) + lwa 3, 4(3) + blr +# CHECK-LABEL: AlignPLD +# CHECK: nop +# CHECK-NEXT: pld 3, 0(0), 1 +# CHECK-NEXT: R_PPC64_GOT_PCREL34 vec +# CHECK-NEXT: R_PPC64_PCREL_OPT *ABS*+0x8 +# CHECK-NEXT: lwa 3, 4(3) +# CHECK-NEXT: blr + +# The label and the pld are on the same line and so the nop is inserted before +# the label and the relocation should work. + .globl AlignPLDSameLine + .p2align 6 + .type AlignPLDSameLine,@function + .space 60 # Force the pld to require an alignment nop. +AlignPLDSameLine: +.LAlignPLDSameLine$local: +.Lpcrel202: pld 3, vec@got@pcrel(0), 1 + .reloc .Lpcrel202,R_PPC64_PCREL_OPT,.-.Lpcrel202 + lwa 3, 4(3) + blr +# CHECK-LABEL: AlignPLDSameLine +# CHECK: nop +# CHECK-NEXT: pld 3, 0(0), 1 +# CHECK-NEXT: R_PPC64_GOT_PCREL34 vec +# CHECK-NEXT: R_PPC64_PCREL_OPT *ABS*+0x8 +# CHECK-NEXT: lwa 3, 4(3) +# CHECK-NEXT: blr + + .globl AlignPLDLabelBefore + .p2align 6 + .type AlignPLDLabelBefore,@function + .space 60 # Force the pld to require an alignment nop. +AlignPLDLabelBefore: +.LAlignPLDLabelBefore$local: +.Label: + pld 3, vec@got@pcrel(0), 1 +.Lpcrel203: + .reloc .Lpcrel203-8,R_PPC64_PCREL_OPT,.-(.Lpcrel203-8) + lwa 3, 4(3) + blr +# CHECK-LABEL: AlignPLDLabelBefore +# CHECK: nop +# CHECK-NEXT: pld 3, 0(0), 1 +# CHECK-NEXT: R_PPC64_GOT_PCREL34 vec +# CHECK-NEXT: R_PPC64_PCREL_OPT *ABS*+0x8 +# CHECK-NEXT: lwa 3, 4(3) +# CHECK-NEXT: blr + + .globl AlignPLDLabelSameLine + .p2align 6 + .type AlignPLDLabelSameLine,@function + .space 60 # Force the pld to require an alignment nop. +AlignPLDLabelSameLine: +.Label2: pld 3, vec@got@pcrel(0), 1 +.Lpcrel204: + .reloc .Lpcrel204-8,R_PPC64_PCREL_OPT,.-(.Lpcrel204-8) + lwa 3, 4(3) + blr +# CHECK-LABEL: AlignPLDLabelSameLine +# CHECK: nop +# CHECK-NEXT: pld 3, 0(0), 1 +# CHECK-NEXT: R_PPC64_GOT_PCREL34 vec +# CHECK-NEXT: R_PPC64_PCREL_OPT *ABS*+0x8 +# CHECK-NEXT: lwa 3, 4(3) +# CHECK-NEXT: blr