Index: lib/Target/AArch64/AArch64LoadStoreOptimizer.cpp =================================================================== --- lib/Target/AArch64/AArch64LoadStoreOptimizer.cpp +++ lib/Target/AArch64/AArch64LoadStoreOptimizer.cpp @@ -205,6 +205,66 @@ } } +// Given a scaled load/store opcode, return the equivalent unscaled opcode. +static unsigned getUnscaledFromScaled(unsigned Opc) { + switch (Opc) { + default: + llvm_unreachable("Unexpected scaled opcode!"); + case AArch64::STRSui: + return AArch64::STURSi; + case AArch64::STRDui: + return AArch64::STURDi; + case AArch64::STRQui: + return AArch64::STURQi; + case AArch64::STRWui: + return AArch64::STURWi; + case AArch64::STRXui: + return AArch64::STURXi; + case AArch64::LDRSui: + return AArch64::LDURSi; + case AArch64::LDRDui: + return AArch64::LDURDi; + case AArch64::LDRQui: + return AArch64::LDURQi; + case AArch64::LDRWui: + return AArch64::LDURWi; + case AArch64::LDRXui: + return AArch64::LDURXi; + case AArch64::LDRSWui: + return AArch64::LDURSWi; + } +} + +// Given an unscaled load/store opcode, return the equivalent scaled opcode. +static unsigned getScaledFromUnscaled(unsigned Opc) { + switch (Opc) { + default: + llvm_unreachable("Unexpected unscaled opcode!"); + case AArch64::STURSi: + return AArch64::STRSui; + case AArch64::STURDi: + return AArch64::STRDui; + case AArch64::STURQi: + return AArch64::STRQui; + case AArch64::STURWi: + return AArch64::STRWui; + case AArch64::STURXi: + return AArch64::STRXui; + case AArch64::LDURSi: + return AArch64::LDRSui; + case AArch64::LDURDi: + return AArch64::LDRDui; + case AArch64::LDURQi: + return AArch64::LDRQui; + case AArch64::LDURWi: + return AArch64::LDRWui; + case AArch64::LDURXi: + return AArch64::LDRXui; + case AArch64::LDURSWi: + return AArch64::LDRSWui; + } +} + static unsigned getMatchingNonSExtOpcode(unsigned Opc, bool *IsValidLdStrOpc = nullptr) { if (IsValidLdStrOpc) @@ -382,10 +442,22 @@ const MachineOperand &BaseRegOp = MergeForward ? getLdStBaseOp(Paired) : getLdStBaseOp(I); + int Offset = getLdStOffsetOp(I).getImm(); + int PairedOffset = getLdStOffsetOp(Paired).getImm(); + bool PairedIsUnscaled = isUnscaledLdSt(Paired->getOpcode()); + + // We're trying to pair instructions that differ in how they are scaled. + // If FirstMI is scaled then scale the offset of MI accordingly. + // Otherwise, do the opposite (i.e., make Paired's offset unscaled). + if (IsUnscaled != PairedIsUnscaled) { + int MemSize = getMemSize(Paired); + PairedOffset = + PairedIsUnscaled ? PairedOffset / MemSize : PairedOffset * MemSize; + } + // Which register is Rt and which is Rt2 depends on the offset order. MachineInstr *RtMI, *Rt2MI; - if (getLdStOffsetOp(I).getImm() == - getLdStOffsetOp(Paired).getImm() + OffsetStride) { + if (Offset == PairedOffset + OffsetStride) { RtMI = Paired; Rt2MI = I; // Here we swapped the assumption made for SExtIdx. @@ -397,10 +469,10 @@ RtMI = I; Rt2MI = Paired; } - // Handle Unscaled + // Scale the immediate offset, if necessary. int OffsetImm = getLdStOffsetOp(RtMI).getImm(); - if (IsUnscaled && EnableAArch64UnscaledMemOp) - OffsetImm /= OffsetStride; + if (isUnscaledLdSt(RtMI->getOpcode()) && EnableAArch64UnscaledMemOp) + OffsetImm /= getMemSize(RtMI); // Construct the new instruction. MachineInstrBuilder MIB = BuildMI(*I->getParent(), InsertionPoint, @@ -531,6 +603,36 @@ return false; } +static bool canMergeOpc(unsigned Opc, unsigned PairOpc, LdStPairFlags &Flags) { + bool CanMergeOpc = Opc == PairOpc; + // Opcodes match nothing more to check. + if (CanMergeOpc) + return true; + + // Try to match a signed-extended load/store with a zero-extended load/store. + Flags.setSExtIdx(-1); + bool IsValidLdStrOpc; + unsigned NonSExtOpc = getMatchingNonSExtOpcode(Opc, &IsValidLdStrOpc); + assert(IsValidLdStrOpc && + "Given Opc should be a Load or Store with an immediate"); + // Opc will be the first instruction in the pair. + CanMergeOpc = NonSExtOpc == getMatchingNonSExtOpcode(PairOpc); + if (CanMergeOpc) { + Flags.setSExtIdx(NonSExtOpc == (unsigned)Opc ? 1 : 0); + return true; + } + + // Try to match an unscaled load/store with a scaled load/store. + bool IsUnscaled = isUnscaledLdSt(Opc); + unsigned NewOpc = + IsUnscaled ? getScaledFromUnscaled(Opc) : getUnscaledFromScaled(Opc); + CanMergeOpc = NewOpc == PairOpc; + if (CanMergeOpc) + return true; + + return false; +} + /// findMatchingInsn - Scan the instructions looking for a load/store that can /// be combined with the current instruction into a load/store pair. MachineBasicBlock::iterator @@ -581,19 +683,8 @@ // Now that we know this is a real instruction, count it. ++Count; - bool CanMergeOpc = Opc == MI->getOpcode(); - Flags.setSExtIdx(-1); - if (!CanMergeOpc) { - bool IsValidLdStrOpc; - unsigned NonSExtOpc = getMatchingNonSExtOpcode(Opc, &IsValidLdStrOpc); - assert(IsValidLdStrOpc && - "Given Opc should be a Load or Store with an immediate"); - // Opc will be the first instruction in the pair. - Flags.setSExtIdx(NonSExtOpc == (unsigned)Opc ? 1 : 0); - CanMergeOpc = NonSExtOpc == getMatchingNonSExtOpcode(MI->getOpcode()); - } - - if (CanMergeOpc && getLdStOffsetOp(MI).isImm()) { + if (canMergeOpc(Opc, MI->getOpcode(), Flags) && + getLdStOffsetOp(MI).isImm()) { assert(MI->mayLoadOrStore() && "Expected memory operation."); // If we've found another instruction with the same opcode, check to see // if the base and offset are compatible with our starting instruction. @@ -607,6 +698,16 @@ // final offset must be in range. unsigned MIBaseReg = getLdStBaseOp(MI).getReg(); int MIOffset = getLdStOffsetOp(MI).getImm(); + + // We're trying to pair instructions that differ in how they are scaled. + // If FirstMI is scaled then scale the offset of MI accordingly. + // Otherwise, do the opposite (i.e., make MI's offset unscaled). + bool MIIsUnscaled = isUnscaledLdSt(MI); + if (IsUnscaled != MIIsUnscaled) { + int MemSize = getMemSize(MI); + MIOffset = MIIsUnscaled ? MIOffset / MemSize : MIOffset * MemSize; + } + if (BaseReg == MIBaseReg && ((Offset == MIOffset + OffsetStride) || (Offset + OffsetStride == MIOffset))) { int MinOffset = Offset < MIOffset ? Offset : MIOffset; @@ -617,8 +718,7 @@ return E; // If the resultant immediate offset of merging these instructions // is out of range for a pairwise instruction, bail and keep looking. - bool MIIsUnscaled = isUnscaledLdSt(MI); - if (!inBoundsForPair(MIIsUnscaled, MinOffset, OffsetStride)) { + if (!inBoundsForPair(IsUnscaled, MinOffset, OffsetStride)) { trackRegDefsUses(MI, ModifiedRegs, UsedRegs, TRI); MemInsns.push_back(MI); continue; Index: test/CodeGen/AArch64/ldp-stp-scaled-unscaled-pairs.ll =================================================================== --- /dev/null +++ test/CodeGen/AArch64/ldp-stp-scaled-unscaled-pairs.ll @@ -0,0 +1,95 @@ +; RUN: llc < %s -march=aarch64 -aarch64-stp-suppress=false -verify-machineinstrs | FileCheck %s + +; STRQ + STURQ +; CHECK-LABEL: @test1 +; CHECK: stp q0, q1, [x0, #-16] +; CHECK: ret +define void @test1(float* %ptr, <4 x float> %v1, <4 x float> %v2) { +entry: + %tmp1 = bitcast float* %ptr to <4 x float>* + store <4 x float> %v2, <4 x float>* %tmp1, align 16 + %add.ptr = getelementptr inbounds float, float* %ptr, i64 -4 + %tmp = bitcast float* %add.ptr to <4 x float>* + store <4 x float> %v1, <4 x float>* %tmp, align 16 + ret void +} + +; STURQ + STRQ +; CHECK-LABEL: @test2 +; CHECK: stp q0, q1, [x0, #-16] +; CHECK: ret +define void @test2(float* %ptr, <4 x float> %v1, <4 x float> %v2) { +entry: + %add.ptr = getelementptr inbounds float, float* %ptr, i64 -4 + %tmp = bitcast float* %add.ptr to <4 x float>* + store <4 x float> %v1, <4 x float>* %tmp, align 16 + %tmp1 = bitcast float* %ptr to <4 x float>* + store <4 x float> %v2, <4 x float>* %tmp1, align 16 + ret void +} + +; STRQ + STURQ +; CHECK-LABEL: @test3 +; CHECK: stp q0, q1, [x0, #-16] +; CHECK: ret +define void @test3(double* %ptr, <2 x double> %v1, <2 x double> %v2) { +entry: + %tmp1 = bitcast double* %ptr to <2 x double>* + store <2 x double> %v2, <2 x double>* %tmp1, align 16 + %add.ptr = getelementptr inbounds double, double* %ptr, i64 -2 + %tmp = bitcast double* %add.ptr to <2 x double>* + store <2 x double> %v1, <2 x double>* %tmp, align 16 + ret void +} + +; STURQ + STRQ +; CHECK-LABEL: @test4 +; CHECK: stp q0, q1, [x0, #-16] +; CHECK: ret +define void @test4(double* %ptr, <2 x double> %v1, <2 x double> %v2) { +entry: + %add.ptr = getelementptr inbounds double, double* %ptr, i64 -2 + %tmp = bitcast double* %add.ptr to <2 x double>* + store <2 x double> %v1, <2 x double>* %tmp, align 16 + %tmp1 = bitcast double* %ptr to <2 x double>* + store <2 x double> %v2, <2 x double>* %tmp1, align 16 + ret void +} + +; LDRX + LDURX +; CHECK-LABEL: @test5 +; CHECK: ldp x9, x8, [x0, #-8] +; CHECK: ret +define i64 @test5(i64* %p) nounwind { + %tmp = load i64, i64* %p, align 4 + %add.ptr = getelementptr inbounds i64, i64* %p, i64 -1 + %tmp1 = load i64, i64* %add.ptr, align 4 + %add = add nsw i64 %tmp1, %tmp + ret i64 %add +} + +; LDURX + LDRX +; CHECK-LABEL: @test6 +; CHECK: ldp x8, x9, [x0, #-8] +; CHECK: ret +define i64 @test6(i64* %p) nounwind { + %add.ptr = getelementptr inbounds i64, i64* %p, i64 -1 + %tmp1 = load i64, i64* %add.ptr, align 4 + %tmp = load i64, i64* %p, align 4 + %add = add nsw i64 %tmp1, %tmp + ret i64 %add +} + +; LDRSW + LDURSW +; CHECK-LABEL: @test7 +; CHECK: ldpsw x9, x8, [x0, #-4] +; CHECK: ret +define i64 @test7(i32* %p) nounwind { + %tmp = load i32, i32* %p, align 4 + %add.ptr = getelementptr inbounds i32, i32* %p, i64 -1 + %tmp1 = load i32, i32* %add.ptr, align 4 + %sexttmp = sext i32 %tmp to i64 + %sexttmp1 = sext i32 %tmp1 to i64 + %add = add nsw i64 %sexttmp1, %sexttmp + ret i64 %add +}