Skip to content

Commit 35cb45c

Browse files
committed
[ImplicitNullChecks] Support complex addressing mode
The pass is updated to handle loads through complex addressing mode, specifically, when we have a scaled register and a scale. It requires two API updates in TII which have been implemented for X86. See added IR and MIR testcases. Tests-Run: make check Reviewed-By: reames, danstrushin Differential Revision: https://reviews.llvm.org/D87148
1 parent 68e1a8d commit 35cb45c

File tree

9 files changed

+273
-14
lines changed

9 files changed

+273
-14
lines changed

llvm/include/llvm/CodeGen/TargetInstrInfo.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,15 @@ struct RegImmPair {
8080
RegImmPair(Register Reg, int64_t Imm) : Reg(Reg), Imm(Imm) {}
8181
};
8282

83+
/// Used to describe addressing mode similar to ExtAddrMode in CodeGenPrepare.
84+
/// It holds the register values, the scale value and the displacement.
85+
struct ExtAddrMode {
86+
Register BaseReg;
87+
Register ScaledReg;
88+
int64_t Scale;
89+
int64_t Displacement;
90+
};
91+
8392
//---------------------------------------------------------------------------
8493
///
8594
/// TargetInstrInfo - Interface to description of machine instruction set
@@ -968,6 +977,15 @@ class TargetInstrInfo : public MCInstrInfo {
968977
return None;
969978
}
970979

980+
/// Returns true if MI is an instruction that defines Reg to have a constant
981+
/// value and the value is recorded in ImmVal. The ImmVal is a result that
982+
/// should be interpreted as modulo size of Reg.
983+
virtual bool getConstValDefinedInReg(const MachineInstr &MI,
984+
const Register Reg,
985+
int64_t &ImmVal) const {
986+
return false;
987+
}
988+
971989
/// Store the specified register of the given register class to the specified
972990
/// stack frame index. The store instruction is to be added to the given
973991
/// machine basic block before the specified machine instruction. If isKill
@@ -1270,6 +1288,16 @@ class TargetInstrInfo : public MCInstrInfo {
12701288
return false;
12711289
}
12721290

1291+
/// Target dependent implementation to get the values constituting the address
1292+
/// MachineInstr that is accessing memory. These values are returned as a
1293+
/// struct ExtAddrMode which contains all relevant information to make up the
1294+
/// address.
1295+
virtual Optional<ExtAddrMode>
1296+
getAddrModeFromMemoryOp(const MachineInstr &MemI,
1297+
const TargetRegisterInfo *TRI) const {
1298+
return None;
1299+
}
1300+
12731301
/// Returns true if MI's Def is NullValueReg, and the MI
12741302
/// does not change the Zero value. i.e. cases such as rax = shr rax, X where
12751303
/// NullValueReg = rax. Note that if the NullValueReg is non-zero, this

llvm/lib/CodeGen/ImplicitNullChecks.cpp

Lines changed: 83 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -378,26 +378,100 @@ ImplicitNullChecks::isSuitableMemoryOp(const MachineInstr &MI,
378378
if (MI.getDesc().getNumDefs() > 1)
379379
return SR_Unsuitable;
380380

381-
// FIXME: This handles only simple addressing mode.
382-
if (!TII->getMemOperandWithOffset(MI, BaseOp, Offset, OffsetIsScalable, TRI))
381+
if (!MI.mayLoadOrStore() || MI.isPredicable())
382+
return SR_Unsuitable;
383+
auto AM = TII->getAddrModeFromMemoryOp(MI, TRI);
384+
if (!AM)
383385
return SR_Unsuitable;
386+
auto AddrMode = *AM;
387+
const Register BaseReg = AddrMode.BaseReg, ScaledReg = AddrMode.ScaledReg;
388+
int64_t Displacement = AddrMode.Displacement;
384389

385390
// We need the base of the memory instruction to be same as the register
386391
// where the null check is performed (i.e. PointerReg).
387-
if (!BaseOp->isReg() || BaseOp->getReg() != PointerReg)
392+
if (BaseReg != PointerReg && ScaledReg != PointerReg)
388393
return SR_Unsuitable;
389-
390-
// Scalable offsets are a part of scalable vectors (SVE for AArch64). That
391-
// target is in-practice unsupported for ImplicitNullChecks.
392-
if (OffsetIsScalable)
394+
const MachineRegisterInfo &MRI = MI.getMF()->getRegInfo();
395+
unsigned PointerRegSizeInBits = TRI->getRegSizeInBits(PointerReg, MRI);
396+
// Bail out of the sizes of BaseReg, ScaledReg and PointerReg are not the
397+
// same.
398+
if ((BaseReg &&
399+
TRI->getRegSizeInBits(BaseReg, MRI) != PointerRegSizeInBits) ||
400+
(ScaledReg &&
401+
TRI->getRegSizeInBits(ScaledReg, MRI) != PointerRegSizeInBits))
393402
return SR_Unsuitable;
394403

395-
if (!MI.mayLoadOrStore() || MI.isPredicable())
404+
// Returns true if RegUsedInAddr is used for calculating the displacement
405+
// depending on addressing mode. Also calculates the Displacement.
406+
auto CalculateDisplacementFromAddrMode = [&](Register RegUsedInAddr,
407+
int64_t Multiplier) {
408+
// The register can be NoRegister, which is defined as zero for all targets.
409+
// Consider instruction of interest as `movq 8(,%rdi,8), %rax`. Here the
410+
// ScaledReg is %rdi, while there is no BaseReg.
411+
if (!RegUsedInAddr)
412+
return false;
413+
assert(Multiplier && "expected to be non-zero!");
414+
MachineInstr *ModifyingMI = nullptr;
415+
for (auto It = std::next(MachineBasicBlock::const_reverse_iterator(&MI));
416+
It != MI.getParent()->rend(); It++) {
417+
const MachineInstr *CurrMI = &*It;
418+
if (CurrMI->modifiesRegister(RegUsedInAddr, TRI)) {
419+
ModifyingMI = const_cast<MachineInstr *>(CurrMI);
420+
break;
421+
}
422+
}
423+
if (!ModifyingMI)
424+
return false;
425+
// Check for the const value defined in register by ModifyingMI. This means
426+
// all other previous values for that register has been invalidated.
427+
int64_t ImmVal;
428+
if (!TII->getConstValDefinedInReg(*ModifyingMI, RegUsedInAddr, ImmVal))
429+
return false;
430+
// Calculate the reg size in bits, since this is needed for bailing out in
431+
// case of overflow.
432+
int32_t RegSizeInBits = TRI->getRegSizeInBits(RegUsedInAddr, MRI);
433+
APInt ImmValC(RegSizeInBits, ImmVal, true /*IsSigned*/);
434+
APInt MultiplierC(RegSizeInBits, Multiplier);
435+
assert(MultiplierC.isStrictlyPositive() &&
436+
"expected to be a positive value!");
437+
bool IsOverflow;
438+
// Sign of the product depends on the sign of the ImmVal, since Multiplier
439+
// is always positive.
440+
APInt Product = ImmValC.smul_ov(MultiplierC, IsOverflow);
441+
if (IsOverflow)
442+
return false;
443+
APInt DisplacementC(64, Displacement, true /*isSigned*/);
444+
DisplacementC = Product.sadd_ov(DisplacementC, IsOverflow);
445+
if (IsOverflow)
446+
return false;
447+
448+
// We only handle diplacements upto 64 bits wide.
449+
if (DisplacementC.getActiveBits() > 64)
450+
return false;
451+
Displacement = DisplacementC.getSExtValue();
452+
return true;
453+
};
454+
455+
// If a register used in the address is constant, fold it's effect into the
456+
// displacement for ease of analysis.
457+
bool BaseRegIsConstVal = false, ScaledRegIsConstVal = false;
458+
if (CalculateDisplacementFromAddrMode(BaseReg, 1))
459+
BaseRegIsConstVal = true;
460+
if (CalculateDisplacementFromAddrMode(ScaledReg, AddrMode.Scale))
461+
ScaledRegIsConstVal = true;
462+
463+
// The register which is not null checked should be part of the Displacement
464+
// calculation, otherwise we do not know whether the Displacement is made up
465+
// by some symbolic values.
466+
// This matters because we do not want to incorrectly assume that load from
467+
// falls in the zeroth faulting page in the "sane offset check" below.
468+
if ((BaseReg && BaseReg != PointerReg && !BaseRegIsConstVal) ||
469+
(ScaledReg && ScaledReg != PointerReg && !ScaledRegIsConstVal))
396470
return SR_Unsuitable;
397471

398472
// We want the mem access to be issued at a sane offset from PointerReg,
399473
// so that if PointerReg is null then the access reliably page faults.
400-
if (!(-PageSize < Offset && Offset < PageSize))
474+
if (!(-PageSize < Displacement && Displacement < PageSize))
401475
return SR_Unsuitable;
402476

403477
// Finally, check whether the current memory access aliases with previous one.

llvm/lib/Target/AArch64/AArch64InstrInfo.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2144,6 +2144,24 @@ bool AArch64InstrInfo::getMemOperandsWithOffsetWidth(
21442144
return true;
21452145
}
21462146

2147+
Optional<ExtAddrMode>
2148+
AArch64InstrInfo::getAddrModeFromMemoryOp(const MachineInstr &MemI,
2149+
const TargetRegisterInfo *TRI) const {
2150+
const MachineOperand *Base; // Filled with the base operand of MI.
2151+
int64_t Offset; // Filled with the offset of MI.
2152+
bool OffsetIsScalable;
2153+
if (!getMemOperandWithOffset(MemI, Base, Offset, OffsetIsScalable, TRI))
2154+
return None;
2155+
2156+
if (!Base->isReg())
2157+
return None;
2158+
ExtAddrMode AM;
2159+
AM.BaseReg = Base->getReg();
2160+
AM.Displacement = Offset;
2161+
AM.ScaledReg = 0;
2162+
return AM;
2163+
}
2164+
21472165
bool AArch64InstrInfo::getMemOperandWithOffsetWidth(
21482166
const MachineInstr &LdSt, const MachineOperand *&BaseOp, int64_t &Offset,
21492167
bool &OffsetIsScalable, unsigned &Width,

llvm/lib/Target/AArch64/AArch64InstrInfo.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ class AArch64InstrInfo final : public AArch64GenInstrInfo {
113113
/// Hint that pairing the given load or store is unprofitable.
114114
static void suppressLdStPair(MachineInstr &MI);
115115

116+
Optional<ExtAddrMode>
117+
getAddrModeFromMemoryOp(const MachineInstr &MemI,
118+
const TargetRegisterInfo *TRI) const override;
119+
116120
bool getMemOperandsWithOffsetWidth(
117121
const MachineInstr &MI, SmallVectorImpl<const MachineOperand *> &BaseOps,
118122
int64_t &Offset, bool &OffsetIsScalable, unsigned &Width,

llvm/lib/Target/X86/X86InstrInfo.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3663,6 +3663,45 @@ static unsigned getLoadStoreRegOpcode(unsigned Reg,
36633663
}
36643664
}
36653665

3666+
Optional<ExtAddrMode>
3667+
X86InstrInfo::getAddrModeFromMemoryOp(const MachineInstr &MemI,
3668+
const TargetRegisterInfo *TRI) const {
3669+
const MCInstrDesc &Desc = MemI.getDesc();
3670+
int MemRefBegin = X86II::getMemoryOperandNo(Desc.TSFlags);
3671+
if (MemRefBegin < 0)
3672+
return None;
3673+
3674+
MemRefBegin += X86II::getOperandBias(Desc);
3675+
3676+
auto &BaseOp = MemI.getOperand(MemRefBegin + X86::AddrBaseReg);
3677+
if (!BaseOp.isReg()) // Can be an MO_FrameIndex
3678+
return None;
3679+
3680+
const MachineOperand &DispMO = MemI.getOperand(MemRefBegin + X86::AddrDisp);
3681+
// Displacement can be symbolic
3682+
if (!DispMO.isImm())
3683+
return None;
3684+
3685+
ExtAddrMode AM;
3686+
AM.BaseReg = BaseOp.getReg();
3687+
AM.ScaledReg = MemI.getOperand(MemRefBegin + X86::AddrIndexReg).getReg();
3688+
AM.Scale = MemI.getOperand(MemRefBegin + X86::AddrScaleAmt).getImm();
3689+
AM.Displacement = DispMO.getImm();
3690+
return AM;
3691+
}
3692+
3693+
bool X86InstrInfo::getConstValDefinedInReg(const MachineInstr &MI,
3694+
const Register Reg,
3695+
int64_t &ImmVal) const {
3696+
if (MI.getOpcode() != X86::MOV32ri && MI.getOpcode() != X86::MOV64ri)
3697+
return false;
3698+
// Mov Src can be a global address.
3699+
if (!MI.getOperand(1).isImm() || MI.getOperand(0).getReg() != Reg)
3700+
return false;
3701+
ImmVal = MI.getOperand(1).getImm();
3702+
return true;
3703+
}
3704+
36663705
bool X86InstrInfo::preservesZeroValueInReg(
36673706
const MachineInstr *MI, const Register NullValueReg,
36683707
const TargetRegisterInfo *TRI) const {

llvm/lib/Target/X86/X86InstrInfo.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,13 @@ class X86InstrInfo final : public X86GenInstrInfo {
317317
SmallVectorImpl<MachineOperand> &Cond,
318318
bool AllowModify) const override;
319319

320+
Optional<ExtAddrMode>
321+
getAddrModeFromMemoryOp(const MachineInstr &MemI,
322+
const TargetRegisterInfo *TRI) const override;
323+
324+
bool getConstValDefinedInReg(const MachineInstr &MI, const Register Reg,
325+
int64_t &ImmVal) const override;
326+
320327
bool preservesZeroValueInReg(const MachineInstr *MI,
321328
const Register NullValueReg,
322329
const TargetRegisterInfo *TRI) const override;

llvm/test/CodeGen/X86/implicit-null-check-negative.ll

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,24 @@ define i64 @imp_null_check_load_shift_add_addr(i64* %x, i64 %r) {
129129
%t = load i64, i64* %x.loc
130130
ret i64 %t
131131
}
132+
133+
; the memory op is not within faulting page.
134+
define i64 @imp_null_check_load_addr_outside_faulting_page(i64* %x) {
135+
entry:
136+
%c = icmp eq i64* %x, null
137+
br i1 %c, label %is_null, label %not_null, !make.implicit !0
138+
139+
is_null:
140+
ret i64 42
141+
142+
not_null:
143+
%y = ptrtoint i64* %x to i64
144+
%shry = shl i64 %y, 3
145+
%shry.add = add i64 %shry, 68719472640
146+
%y.ptr = inttoptr i64 %shry.add to i64*
147+
%x.loc = getelementptr i64, i64* %y.ptr, i64 1
148+
%t = load i64, i64* %x.loc
149+
ret i64 %t
150+
}
151+
132152
!0 = !{}

llvm/test/CodeGen/X86/implicit-null-check.ll

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
21
; RUN: llc -verify-machineinstrs -O3 -mtriple=x86_64-apple-macosx -enable-implicit-null-checks < %s | FileCheck %s
32

43
define i32 @imp_null_check_load(i32* %x) {
@@ -593,14 +592,12 @@ define i64 @imp_null_check_load_shift_addr(i64* %x) {
593592

594593
; Same as imp_null_check_load_shift_addr but shift is by 3 and this is now
595594
; converted into complex addressing.
596-
; TODO: Can be converted into implicit null check
597595
define i64 @imp_null_check_load_shift_by_3_addr(i64* %x) {
598596
; CHECK-LABEL: imp_null_check_load_shift_by_3_addr:
599597
; CHECK: ## %bb.0: ## %entry
600-
; CHECK-NEXT: testq %rdi, %rdi
601-
; CHECK-NEXT: je LBB22_1
598+
; CHECK-NEXT: Ltmp18:
599+
; CHECK-NEXT: movq 8(,%rdi,8), %rax ## on-fault: LBB22_1
602600
; CHECK-NEXT: ## %bb.2: ## %not_null
603-
; CHECK-NEXT: movq 8(,%rdi,8), %rax
604601
; CHECK-NEXT: retq
605602
; CHECK-NEXT: LBB22_1: ## %is_null
606603
; CHECK-NEXT: movl $42, %eax
@@ -621,4 +618,31 @@ define i64 @imp_null_check_load_shift_by_3_addr(i64* %x) {
621618
%t = load i64, i64* %x.loc
622619
ret i64 %t
623620
}
621+
622+
define i64 @imp_null_check_load_shift_add_addr(i64* %x) {
623+
; CHECK-LABEL: imp_null_check_load_shift_add_addr:
624+
; CHECK: ## %bb.0: ## %entry
625+
; CHECK: movq 3526(,%rdi,8), %rax ## on-fault: LBB23_1
626+
; CHECK-NEXT: ## %bb.2: ## %not_null
627+
; CHECK-NEXT: retq
628+
; CHECK-NEXT: LBB23_1: ## %is_null
629+
; CHECK-NEXT: movl $42, %eax
630+
; CHECK-NEXT: retq
631+
632+
entry:
633+
%c = icmp eq i64* %x, null
634+
br i1 %c, label %is_null, label %not_null, !make.implicit !0
635+
636+
is_null:
637+
ret i64 42
638+
639+
not_null:
640+
%y = ptrtoint i64* %x to i64
641+
%shry = shl i64 %y, 3
642+
%shry.add = add i64 %shry, 3518
643+
%y.ptr = inttoptr i64 %shry.add to i64*
644+
%x.loc = getelementptr i64, i64* %y.ptr, i64 1
645+
%t = load i64, i64* %x.loc
646+
ret i64 %t
647+
}
624648
!0 = !{}

llvm/test/CodeGen/X86/implicit-null-checks.mir

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,22 @@
377377
ret i32 undef
378378
}
379379

380+
define i32 @imp_null_check_address_mul_overflow(i32* %x, i32 %a) {
381+
entry:
382+
%c = icmp eq i32* %x, null
383+
br i1 %c, label %is_null, label %not_null, !make.implicit !0
384+
385+
is_null: ; preds = %entry
386+
ret i32 42
387+
388+
not_null: ; preds = %entry
389+
%y = ptrtoint i32* %x to i32
390+
%y64 = zext i32 %y to i64
391+
%b = mul i64 %y64, 9223372036854775807 ; 0X0FFFF.. i.e. 2^63 - 1
392+
%z = trunc i64 %b to i32
393+
ret i32 %z
394+
}
395+
380396
attributes #0 = { "target-features"="+bmi,+bmi2" }
381397

382398
!0 = !{}
@@ -1316,3 +1332,32 @@ body: |
13161332
RETQ $eax
13171333
13181334
...
1335+
---
1336+
name: imp_null_check_address_mul_overflow
1337+
# CHECK-LABEL: name: imp_null_check_address_mul_overflow
1338+
# CHECK: bb.0.entry:
1339+
# CHECK-NOT: FAULTING_OP
1340+
alignment: 16
1341+
tracksRegLiveness: true
1342+
liveins:
1343+
- { reg: '$rdi' }
1344+
- { reg: '$rsi' }
1345+
body: |
1346+
bb.0.entry:
1347+
liveins: $rsi, $rdi
1348+
1349+
TEST64rr $rdi, $rdi, implicit-def $eflags
1350+
JCC_1 %bb.1, 4, implicit $eflags
1351+
1352+
bb.2.not_null:
1353+
liveins: $rdi, $rsi
1354+
1355+
$rcx = MOV64ri -9223372036854775808
1356+
$eax = MOV32rm killed $rdi, 2, $rcx, 0, $noreg, implicit-def $rax
1357+
RETQ $eax
1358+
1359+
bb.1.is_null:
1360+
$eax = MOV32ri 42
1361+
RETQ $eax
1362+
1363+
...

0 commit comments

Comments
 (0)