[llvm] [GlobalISel] Add nomerge attribute support (PR #224332)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 17 08:03:56 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-backend-risc-v
Author: Milica Kovacevic (mkovacevic99)
<details>
<summary>Changes</summary>
GlobalISel currently ignores `nomerge`, so BranchFolding is free to tail-merge calls the source explicitly marked as needing distinct.
Added:
- CallLowering::lowerCall(MachineIRBuilder&, const CallBase&, ...) sets CallLoweringInfo::NoMerge from CB.cannotMerge(), then scans the instructions the target just built for the one flagged isCall() and tags it NoMerge. Generic across targets, unlike SelectionDAG which tags this per-target in each LowerCall.
- IRTranslator::translateTrap() lowers trap intrinsics outside that path, so it sets NoMerge explicitly in both of its branches.
Tests added for RISC-V, X86, and AArch64 (calls and tail-calls), plus X86 coverage for bare-opcode trap intrinsics. The named trap-func-name form isn't covered: it hits a pre-existing, unrelated GlobalISel miscompile when two calls share a named callee, independent of nomerge.
---
Full diff: https://github.com/llvm/llvm-project/pull/224332.diff
6 Files Affected:
- (modified) llvm/include/llvm/CodeGen/GlobalISel/CallLowering.h (+4)
- (modified) llvm/lib/CodeGen/GlobalISel/CallLowering.cpp (+21)
- (modified) llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp (+11-2)
- (added) llvm/test/CodeGen/AArch64/GlobalISel/nomerge.ll (+66)
- (added) llvm/test/CodeGen/RISCV/GlobalISel/nomerge.ll (+78)
- (added) llvm/test/CodeGen/X86/GlobalISel/nomerge.ll (+155)
``````````diff
diff --git a/llvm/include/llvm/CodeGen/GlobalISel/CallLowering.h b/llvm/include/llvm/CodeGen/GlobalISel/CallLowering.h
index 110f40a817770..db688408333c0 100644
--- a/llvm/include/llvm/CodeGen/GlobalISel/CallLowering.h
+++ b/llvm/include/llvm/CodeGen/GlobalISel/CallLowering.h
@@ -161,6 +161,10 @@ class LLVM_ABI CallLowering {
/// True if this call results in convergent operations.
bool IsConvergent = true;
+ /// True if this call must not be merged with another call site (the
+ /// `nomerge` attribute)
+ bool NoMerge = false;
+
GlobalValue *DeactivationSymbol = nullptr;
};
diff --git a/llvm/lib/CodeGen/GlobalISel/CallLowering.cpp b/llvm/lib/CodeGen/GlobalISel/CallLowering.cpp
index 90a0791a04e06..b630a501d2af2 100644
--- a/llvm/lib/CodeGen/GlobalISel/CallLowering.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/CallLowering.cpp
@@ -243,9 +243,30 @@ bool CallLowering::lowerCall(MachineIRBuilder &MIRBuilder, const CallBase &CB,
Info.IsMustTailCall = CB.isMustTailCall();
Info.IsTailCall = CanBeTailCalled;
Info.IsVarArg = IsVarArg;
+ Info.NoMerge = CB.cannotMerge();
+
+ // Remember the insertion point and containing block so that, once the
+ // target has built whatever instructions it needs for this call, we can
+ // find the actual call instruction among them and mark it NoMerge. This
+ // avoids requiring every target's lowerCall() to do so itself.
+ MachineBasicBlock &CallMBB = MIRBuilder.getMBB();
+ MachineBasicBlock::iterator InsertPtBefore = MIRBuilder.getInsertPt();
+ bool InsertAtBegin = InsertPtBefore == CallMBB.begin();
+ if (!InsertAtBegin)
+ --InsertPtBefore;
+
if (!lowerCall(MIRBuilder, Info))
return false;
+ if (Info.NoMerge && &MIRBuilder.getMBB() == &CallMBB) {
+ MachineBasicBlock::iterator Start =
+ InsertAtBegin ? CallMBB.begin() : std::next(InsertPtBefore);
+ for (MachineBasicBlock::iterator It = Start, End = MIRBuilder.getInsertPt();
+ It != End; ++It)
+ if (It->isCall())
+ It->setFlag(MachineInstr::MIFlag::NoMerge);
+ }
+
if (ReturnHintAlignReg && !Info.LoweredTailCall) {
MIRBuilder.buildAssertAlign(ResRegs[0], ReturnHintAlignReg,
ReturnHintAlign);
diff --git a/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp b/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
index 74a1ecf69655f..d8d8ae1267670 100644
--- a/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
@@ -2611,12 +2611,17 @@ bool IRTranslatorImpl::translateTrap(const CallInst &CI,
StringRef TrapFuncName =
CI.getAttributes().getFnAttr("trap-func-name").getValueAsString();
if (TrapFuncName.empty()) {
+ MachineInstrBuilder MIB;
if (Opcode == TargetOpcode::G_UBSANTRAP) {
uint64_t Code = cast<ConstantInt>(CI.getOperand(0))->getZExtValue();
- MIRBuilder.buildInstr(Opcode, {}, ArrayRef<llvm::SrcOp>{Code});
+ MIB = MIRBuilder.buildInstr(Opcode, {}, ArrayRef<llvm::SrcOp>{Code});
} else {
- MIRBuilder.buildInstr(Opcode);
+ MIB = MIRBuilder.buildInstr(Opcode);
}
+ // This doesn't go through the generic CallBase-based lowerCall(), so it
+ // never picks up CallLoweringInfo::NoMerge -- set it here directly.
+ if (CI.cannotMerge())
+ MIB.setMIFlag(MachineInstr::MIFlag::NoMerge);
return true;
}
@@ -2628,6 +2633,10 @@ bool IRTranslatorImpl::translateTrap(const CallInst &CI,
Info.Callee = MachineOperand::CreateES(TrapFuncName.data());
Info.CB = &CI;
Info.OrigRet = {Register(), Type::getVoidTy(CI.getContext()), 0};
+ // This calls the CallLoweringInfo-based lowerCall() overload directly,
+ // bypassing the CallBase-based one that normally populates NoMerge -- set
+ // it here explicitly.
+ Info.NoMerge = CI.cannotMerge();
return CLI->lowerCall(MIRBuilder, Info);
}
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/nomerge.ll b/llvm/test/CodeGen/AArch64/GlobalISel/nomerge.ll
new file mode 100644
index 0000000000000..db228be4ef417
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/nomerge.ll
@@ -0,0 +1,66 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 2
+; RUN: llc < %s -mtriple=aarch64 -global-isel -global-isel-abort=1 -o - | FileCheck %s
+
+define void @foo(i32 %i) nounwind {
+; CHECK-LABEL: foo:
+; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: str x30, [sp, #-16]! // 8-byte Folded Spill
+; CHECK-NEXT: cmp w0, #7
+; CHECK-NEXT: b.eq .LBB0_3
+; CHECK-NEXT: // %bb.1: // %entry
+; CHECK-NEXT: cmp w0, #5
+; CHECK-NEXT: b.ne .LBB0_4
+; CHECK-NEXT: // %bb.2: // %if.then
+; CHECK-NEXT: bl bar
+; CHECK-NEXT: ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-NEXT: b bar
+; CHECK-NEXT: .LBB0_3: // %if.then2
+; CHECK-NEXT: bl bar
+; CHECK-NEXT: .LBB0_4: // %if.end3
+; CHECK-NEXT: ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-NEXT: b bar
+entry:
+ switch i32 %i, label %if.end3 [
+ i32 5, label %if.then
+ i32 7, label %if.then2
+ ]
+
+if.then:
+ tail call void @bar() #0
+ br label %if.end3
+
+if.then2:
+ tail call void @bar() #0
+ br label %if.end3
+
+if.end3:
+ tail call void @bar() #0
+ ret void
+}
+
+define void @foo_tail(i1 %i) nounwind {
+; CHECK-LABEL: foo_tail:
+; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: tbz w0, #0, .LBB1_2
+; CHECK-NEXT: // %bb.1: // %if.then
+; CHECK-NEXT: b bar
+; CHECK-NEXT: .LBB1_2: // %if.else
+; CHECK-NEXT: b bar
+entry:
+ br i1 %i, label %if.then, label %if.else
+
+if.then:
+ tail call void @bar() #0
+ br label %if.end
+
+if.else:
+ tail call void @bar() #0
+ br label %if.end
+
+if.end:
+ ret void
+}
+
+declare void @bar()
+
+attributes #0 = { nomerge }
diff --git a/llvm/test/CodeGen/RISCV/GlobalISel/nomerge.ll b/llvm/test/CodeGen/RISCV/GlobalISel/nomerge.ll
new file mode 100644
index 0000000000000..d1df655db4115
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/GlobalISel/nomerge.ll
@@ -0,0 +1,78 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 2
+; RUN: llc < %s -mtriple=riscv64 -global-isel -global-isel-abort=1 -o - | FileCheck %s
+
+define void @foo(i32 %i) nounwind {
+; CHECK-LABEL: foo:
+; CHECK: # %bb.0: # %entry
+; CHECK-NEXT: addi sp, sp, -16
+; CHECK-NEXT: sd ra, 8(sp) # 8-byte Folded Spill
+; CHECK-NEXT: li a1, 7
+; CHECK-NEXT: sext.w a0, a0
+; CHECK-NEXT: beq a0, a1, .LBB0_3
+; CHECK-NEXT: # %bb.1: # %entry
+; CHECK-NEXT: li a1, 5
+; CHECK-NEXT: bne a0, a1, .LBB0_4
+; CHECK-NEXT: # %bb.2: # %if.then
+; CHECK-NEXT: call bar
+; CHECK-NEXT: j .LBB0_4
+; CHECK-NEXT: .LBB0_3: # %if.then2
+; CHECK-NEXT: call bar
+; CHECK-NEXT: .LBB0_4: # %if.end3
+; CHECK-NEXT: call bar
+; CHECK-NEXT: ld ra, 8(sp) # 8-byte Folded Reload
+; CHECK-NEXT: addi sp, sp, 16
+; CHECK-NEXT: ret
+entry:
+ switch i32 %i, label %if.end3 [
+ i32 5, label %if.then
+ i32 7, label %if.then2
+ ]
+
+if.then:
+ tail call void @bar() #0
+ br label %if.end3
+
+if.then2:
+ tail call void @bar() #0
+ br label %if.end3
+
+if.end3:
+ tail call void @bar() #0
+ ret void
+}
+
+define void @foo_tail(i1 %i) nounwind {
+; CHECK-LABEL: foo_tail:
+; CHECK: # %bb.0: # %entry
+; CHECK-NEXT: addi sp, sp, -16
+; CHECK-NEXT: sd ra, 8(sp) # 8-byte Folded Spill
+; CHECK-NEXT: xori a0, a0, 1
+; CHECK-NEXT: andi a0, a0, 1
+; CHECK-NEXT: bnez a0, .LBB1_2
+; CHECK-NEXT: # %bb.1: # %if.then
+; CHECK-NEXT: call bar
+; CHECK-NEXT: j .LBB1_3
+; CHECK-NEXT: .LBB1_2: # %if.else
+; CHECK-NEXT: call bar
+; CHECK-NEXT: .LBB1_3: # %if.then
+; CHECK-NEXT: ld ra, 8(sp) # 8-byte Folded Reload
+; CHECK-NEXT: addi sp, sp, 16
+; CHECK-NEXT: ret
+entry:
+ br i1 %i, label %if.then, label %if.else
+
+if.then:
+ tail call void @bar() #0
+ br label %if.end
+
+if.else:
+ tail call void @bar() #0
+ br label %if.end
+
+if.end:
+ ret void
+}
+
+declare void @bar()
+
+attributes #0 = { nomerge }
diff --git a/llvm/test/CodeGen/X86/GlobalISel/nomerge.ll b/llvm/test/CodeGen/X86/GlobalISel/nomerge.ll
new file mode 100644
index 0000000000000..0b4940df2e600
--- /dev/null
+++ b/llvm/test/CodeGen/X86/GlobalISel/nomerge.ll
@@ -0,0 +1,155 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 2
+; RUN: llc < %s -mtriple=x86_64-linux-gnu -global-isel -global-isel-abort=1 -o - | FileCheck %s
+
+define void @foo(i32 %i) nounwind {
+; CHECK-LABEL: foo:
+; CHECK: # %bb.0: # %entry
+; CHECK-NEXT: pushq %rax
+; CHECK-NEXT: cmpl $7, %edi
+; CHECK-NEXT: sete %al
+; CHECK-NEXT: testb $1, %al
+; CHECK-NEXT: jne .LBB0_3
+; CHECK-NEXT: # %bb.1: # %entry
+; CHECK-NEXT: cmpl $5, %edi
+; CHECK-NEXT: sete %al
+; CHECK-NEXT: testb $1, %al
+; CHECK-NEXT: je .LBB0_4
+; CHECK-NEXT: # %bb.2: # %if.then
+; CHECK-NEXT: callq bar
+; CHECK-NEXT: jmp .LBB0_4
+; CHECK-NEXT: .LBB0_3: # %if.then2
+; CHECK-NEXT: callq bar
+; CHECK-NEXT: .LBB0_4: # %if.end3
+; CHECK-NEXT: callq bar
+; CHECK-NEXT: popq %rax
+; CHECK-NEXT: retq
+entry:
+ switch i32 %i, label %if.end3 [
+ i32 5, label %if.then
+ i32 7, label %if.then2
+ ]
+
+if.then:
+ tail call void @bar() #0
+ br label %if.end3
+
+if.then2:
+ tail call void @bar() #0
+ br label %if.end3
+
+if.end3:
+ tail call void @bar() #0
+ ret void
+}
+
+define void @foo_tail(i1 %i) nounwind {
+; CHECK-LABEL: foo_tail:
+; CHECK: # %bb.0: # %entry
+; CHECK-NEXT: pushq %rax
+; CHECK-NEXT: testb $1, %dil
+; CHECK-NEXT: je .LBB1_2
+; CHECK-NEXT: # %bb.1: # %if.then
+; CHECK-NEXT: callq bar
+; CHECK-NEXT: popq %rax
+; CHECK-NEXT: retq
+; CHECK-NEXT: .LBB1_2: # %if.else
+; CHECK-NEXT: callq bar
+; CHECK-NEXT: popq %rax
+; CHECK-NEXT: retq
+entry:
+ br i1 %i, label %if.then, label %if.else
+
+if.then:
+ tail call void @bar() #0
+ br label %if.end
+
+if.else:
+ tail call void @bar() #0
+ br label %if.end
+
+if.end:
+ ret void
+}
+
+declare dso_local void @bar()
+
+define void @nomerge_trap(i32 %i) {
+; CHECK-LABEL: nomerge_trap:
+; CHECK: # %bb.0: # %entry
+; CHECK-NEXT: cmpl $7, %edi
+; CHECK-NEXT: sete %al
+; CHECK-NEXT: testb $1, %al
+; CHECK-NEXT: jne .LBB2_3
+; CHECK-NEXT: # %bb.1: # %entry
+; CHECK-NEXT: cmpl $5, %edi
+; CHECK-NEXT: sete %al
+; CHECK-NEXT: testb $1, %al
+; CHECK-NEXT: je .LBB2_4
+; CHECK-NEXT: # %bb.2: # %if.then
+; CHECK-NEXT: ud2
+; CHECK-NEXT: .LBB2_3: # %if.then2
+; CHECK-NEXT: ud2
+; CHECK-NEXT: .LBB2_4: # %if.end3
+; CHECK-NEXT: ud2
+entry:
+ switch i32 %i, label %if.end3 [
+ i32 5, label %if.then
+ i32 7, label %if.then2
+ ]
+
+if.then:
+ tail call void @llvm.trap() #0
+ unreachable
+
+if.then2:
+ tail call void @llvm.trap() #0
+ unreachable
+
+if.end3:
+ tail call void @llvm.trap() #0
+ unreachable
+}
+
+declare dso_local void @llvm.trap()
+
+define void @nomerge_debugtrap(i32 %i) {
+; CHECK-LABEL: nomerge_debugtrap:
+; CHECK: # %bb.0: # %entry
+; CHECK-NEXT: cmpl $7, %edi
+; CHECK-NEXT: sete %al
+; CHECK-NEXT: testb $1, %al
+; CHECK-NEXT: jne .LBB3_3
+; CHECK-NEXT: # %bb.1: # %entry
+; CHECK-NEXT: cmpl $5, %edi
+; CHECK-NEXT: sete %al
+; CHECK-NEXT: testb $1, %al
+; CHECK-NEXT: je .LBB3_4
+; CHECK-NEXT: # %bb.2: # %if.then
+; CHECK-NEXT: int3
+; CHECK-NEXT: .LBB3_3: # %if.then2
+; CHECK-NEXT: int3
+; CHECK-NEXT: .LBB3_4: # %if.end3
+; CHECK-NEXT: int3
+entry:
+ switch i32 %i, label %if.end3 [
+ i32 5, label %if.then
+ i32 7, label %if.then2
+ ]
+
+if.then:
+ tail call void @llvm.debugtrap() #0
+ unreachable
+
+if.then2:
+ tail call void @llvm.debugtrap() #0
+ unreachable
+
+if.end3:
+ tail call void @llvm.debugtrap() #0
+ unreachable
+}
+
+declare dso_local void @llvm.debugtrap()
+
+attributes #0 = { nomerge }
+
``````````
</details>
https://github.com/llvm/llvm-project/pull/224332
More information about the llvm-commits
mailing list