Skip to content

Commit 3b8adcf

Browse files
authored
[GlobalISel] Add computeNumSignBits for ASHR (#139503)
1 parent 5a9c201 commit 3b8adcf

File tree

12 files changed

+215
-95
lines changed

12 files changed

+215
-95
lines changed

llvm/include/llvm/CodeGen/GlobalISel/GISelValueTracking.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,20 @@ class LLVM_ABI GISelValueTracking : public GISelChangeObserver {
103103
/// \return The known alignment for the pointer-like value \p R.
104104
Align computeKnownAlignment(Register R, unsigned Depth = 0);
105105

106+
/// If a G_SHL/G_ASHR/G_LSHR node with shift operand \p R has shift amounts
107+
/// that are all less than the element bit-width of the shift node, return the
108+
/// valid constant range.
109+
std::optional<ConstantRange>
110+
getValidShiftAmountRange(Register R, const APInt &DemandedElts,
111+
unsigned Depth);
112+
113+
/// If a G_SHL/G_ASHR/G_LSHR node with shift operand \p R has shift amounts
114+
/// that are all less than the element bit-width of the shift node, return the
115+
/// minimum possible value.
116+
std::optional<uint64_t> getValidMinimumShiftAmount(Register R,
117+
const APInt &DemandedElts,
118+
unsigned Depth = 0);
119+
106120
/// Determine which floating-point classes are valid for \p V, and return them
107121
/// in KnownFPClass bit sets.
108122
///

llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1884,6 +1884,14 @@ unsigned GISelValueTracking::computeNumSignBits(Register R,
18841884
}
18851885
break;
18861886
}
1887+
case TargetOpcode::G_ASHR: {
1888+
Register Src1 = MI.getOperand(1).getReg();
1889+
Register Src2 = MI.getOperand(2).getReg();
1890+
FirstAnswer = computeNumSignBits(Src1, DemandedElts, Depth + 1);
1891+
if (auto C = getValidMinimumShiftAmount(Src2, DemandedElts, Depth + 1))
1892+
FirstAnswer = std::min<uint64_t>(FirstAnswer + *C, TyBits);
1893+
break;
1894+
}
18871895
case TargetOpcode::G_TRUNC: {
18881896
Register Src = MI.getOperand(1).getReg();
18891897
LLT SrcTy = MRI.getType(Src);
@@ -2053,6 +2061,64 @@ unsigned GISelValueTracking::computeNumSignBits(Register R, unsigned Depth) {
20532061
return computeNumSignBits(R, DemandedElts, Depth);
20542062
}
20552063

2064+
std::optional<ConstantRange> GISelValueTracking::getValidShiftAmountRange(
2065+
Register R, const APInt &DemandedElts, unsigned Depth) {
2066+
// Shifting more than the bitwidth is not valid.
2067+
MachineInstr &MI = *MRI.getVRegDef(R);
2068+
unsigned Opcode = MI.getOpcode();
2069+
2070+
LLT Ty = MRI.getType(R);
2071+
unsigned BitWidth = Ty.getScalarSizeInBits();
2072+
2073+
if (Opcode == TargetOpcode::G_CONSTANT) {
2074+
const APInt &ShAmt = MI.getOperand(1).getCImm()->getValue();
2075+
if (ShAmt.uge(BitWidth))
2076+
return std::nullopt;
2077+
return ConstantRange(ShAmt);
2078+
}
2079+
2080+
if (Opcode == TargetOpcode::G_BUILD_VECTOR) {
2081+
const APInt *MinAmt = nullptr, *MaxAmt = nullptr;
2082+
for (unsigned I = 0, E = MI.getNumOperands() - 1; I != E; ++I) {
2083+
if (!DemandedElts[I])
2084+
continue;
2085+
MachineInstr *Op = MRI.getVRegDef(MI.getOperand(I + 1).getReg());
2086+
if (Op->getOpcode() != TargetOpcode::G_CONSTANT) {
2087+
MinAmt = MaxAmt = nullptr;
2088+
break;
2089+
}
2090+
2091+
const APInt &ShAmt = Op->getOperand(1).getCImm()->getValue();
2092+
if (ShAmt.uge(BitWidth))
2093+
return std::nullopt;
2094+
if (!MinAmt || MinAmt->ugt(ShAmt))
2095+
MinAmt = &ShAmt;
2096+
if (!MaxAmt || MaxAmt->ult(ShAmt))
2097+
MaxAmt = &ShAmt;
2098+
}
2099+
assert(((!MinAmt && !MaxAmt) || (MinAmt && MaxAmt)) &&
2100+
"Failed to find matching min/max shift amounts");
2101+
if (MinAmt && MaxAmt)
2102+
return ConstantRange(*MinAmt, *MaxAmt + 1);
2103+
}
2104+
2105+
// Use computeKnownBits to find a hidden constant/knownbits (usually type
2106+
// legalized). e.g. Hidden behind multiple bitcasts/build_vector/casts etc.
2107+
KnownBits KnownAmt = getKnownBits(R, DemandedElts, Depth);
2108+
if (KnownAmt.getMaxValue().ult(BitWidth))
2109+
return ConstantRange::fromKnownBits(KnownAmt, /*IsSigned=*/false);
2110+
2111+
return std::nullopt;
2112+
}
2113+
2114+
std::optional<uint64_t> GISelValueTracking::getValidMinimumShiftAmount(
2115+
Register R, const APInt &DemandedElts, unsigned Depth) {
2116+
if (std::optional<ConstantRange> AmtRange =
2117+
getValidShiftAmountRange(R, DemandedElts, Depth))
2118+
return AmtRange->getUnsignedMin().getZExtValue();
2119+
return std::nullopt;
2120+
}
2121+
20562122
void GISelValueTrackingAnalysisLegacy::getAnalysisUsage(
20572123
AnalysisUsage &AU) const {
20582124
AU.setPreservesAll();
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# NOTE: Assertions have been autogenerated by utils/update_givaluetracking_test_checks.py UTC_ARGS: --version 5
2+
# RUN: llc -mtriple aarch64 -passes="print<gisel-value-tracking>" %s -o - 2>&1 | FileCheck %s
3+
4+
---
5+
name: Cst
6+
body: |
7+
bb.1:
8+
; CHECK-LABEL: name: @Cst
9+
; CHECK-NEXT: %0:_ KnownBits:10000000 SignBits:1
10+
; CHECK-NEXT: %1:_ KnownBits:00000011 SignBits:6
11+
; CHECK-NEXT: %2:_ KnownBits:11110000 SignBits:4
12+
%0:_(s8) = G_CONSTANT i8 128
13+
%1:_(s8) = G_CONSTANT i8 3
14+
%2:_(s8) = G_ASHR %0, %1
15+
...
16+
---
17+
name: CstBig
18+
body: |
19+
bb.1:
20+
; CHECK-LABEL: name: @CstBig
21+
; CHECK-NEXT: %0:_ KnownBits:11111000 SignBits:5
22+
; CHECK-NEXT: %1:_ KnownBits:00000110 SignBits:5
23+
; CHECK-NEXT: %2:_ KnownBits:11111111 SignBits:8
24+
%0:_(s8) = G_CONSTANT i8 248
25+
%1:_(s8) = G_CONSTANT i8 6
26+
%2:_(s8) = G_ASHR %0, %1
27+
...
28+
---
29+
name: ScalarVar
30+
body: |
31+
bb.1:
32+
; CHECK-LABEL: name: @ScalarVar
33+
; CHECK-NEXT: %0:_ KnownBits:???????? SignBits:1
34+
; CHECK-NEXT: %1:_ KnownBits:???????? SignBits:1
35+
; CHECK-NEXT: %2:_ KnownBits:???????? SignBits:1
36+
%0:_(s8) = COPY $b0
37+
%1:_(s8) = COPY $b1
38+
%2:_(s8) = G_ASHR %0, %1
39+
...
40+
---
41+
name: ScalarCst
42+
body: |
43+
bb.1:
44+
; CHECK-LABEL: name: @ScalarCst
45+
; CHECK-NEXT: %0:_ KnownBits:???????? SignBits:1
46+
; CHECK-NEXT: %1:_ KnownBits:00000011 SignBits:6
47+
; CHECK-NEXT: %2:_ KnownBits:???????? SignBits:4
48+
%0:_(s8) = COPY $b0
49+
%1:_(s8) = G_CONSTANT i8 3
50+
%2:_(s8) = G_ASHR %0, %1
51+
...
52+
---
53+
name: VectorVar
54+
body: |
55+
bb.1:
56+
; CHECK-LABEL: name: @VectorVar
57+
; CHECK-NEXT: %0:_ KnownBits:???????????????? SignBits:1
58+
; CHECK-NEXT: %1:_ KnownBits:???????????????? SignBits:1
59+
; CHECK-NEXT: %2:_ KnownBits:???????????????? SignBits:1
60+
%0:_(<4 x s16>) = COPY $d0
61+
%1:_(<4 x s16>) = COPY $d1
62+
%2:_(<4 x s16>) = G_ASHR %0, %1
63+
...
64+
---
65+
name: VectorCst
66+
body: |
67+
bb.1:
68+
; CHECK-LABEL: name: @VectorCst
69+
; CHECK-NEXT: %0:_ KnownBits:???????????????? SignBits:1
70+
; CHECK-NEXT: %1:_ KnownBits:0000000000000011 SignBits:14
71+
; CHECK-NEXT: %2:_ KnownBits:0000000000000011 SignBits:14
72+
; CHECK-NEXT: %3:_ KnownBits:???????????????? SignBits:4
73+
%0:_(<4 x s16>) = COPY $d0
74+
%1:_(s16) = G_CONSTANT i16 3
75+
%2:_(<4 x s16>) = G_BUILD_VECTOR %1, %1, %1, %1
76+
%3:_(<4 x s16>) = G_ASHR %0, %2
77+
...
78+
---
79+
name: VectorCst36
80+
body: |
81+
bb.1:
82+
; CHECK-LABEL: name: @VectorCst36
83+
; CHECK-NEXT: %0:_ KnownBits:???????????????? SignBits:1
84+
; CHECK-NEXT: %1:_ KnownBits:0000000000000011 SignBits:14
85+
; CHECK-NEXT: %2:_ KnownBits:0000000000000110 SignBits:13
86+
; CHECK-NEXT: %3:_ KnownBits:0000000000000?1? SignBits:13
87+
; CHECK-NEXT: %4:_ KnownBits:???????????????? SignBits:4
88+
%0:_(<4 x s16>) = COPY $d0
89+
%1:_(s16) = G_CONSTANT i16 3
90+
%2:_(s16) = G_CONSTANT i16 6
91+
%3:_(<4 x s16>) = G_BUILD_VECTOR %1, %2, %2, %1
92+
%4:_(<4 x s16>) = G_ASHR %0, %3
93+
...
94+
---
95+
name: VectorCst3unknown
96+
body: |
97+
bb.1:
98+
; CHECK-LABEL: name: @VectorCst3unknown
99+
; CHECK-NEXT: %0:_ KnownBits:???????????????? SignBits:1
100+
; CHECK-NEXT: %1:_ KnownBits:???????????????? SignBits:1
101+
; CHECK-NEXT: %2:_ KnownBits:0000000000000011 SignBits:14
102+
; CHECK-NEXT: %3:_ KnownBits:???????????????? SignBits:1
103+
; CHECK-NEXT: %4:_ KnownBits:???????????????? SignBits:1
104+
%0:_(<4 x s16>) = COPY $d0
105+
%2:_(s16) = COPY $h0
106+
%1:_(s16) = G_CONSTANT i16 3
107+
%3:_(<4 x s16>) = G_BUILD_VECTOR %1, %2, %2, %1
108+
%4:_(<4 x s16>) = G_ASHR %0, %3
109+
...

llvm/test/CodeGen/AArch64/aarch64-dup-ext.ll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ define <8 x i16> @dupsext_v8i8_v8i16(i8 %src, <8 x i8> %b) {
1414
; CHECK-GI-LABEL: dupsext_v8i8_v8i16:
1515
; CHECK-GI: // %bb.0: // %entry
1616
; CHECK-GI-NEXT: lsl w8, w0, #8
17-
; CHECK-GI-NEXT: sshll v0.8h, v0.8b, #0
1817
; CHECK-GI-NEXT: sbfx w8, w8, #8, #8
1918
; CHECK-GI-NEXT: dup v1.8h, w8
20-
; CHECK-GI-NEXT: mul v0.8h, v1.8h, v0.8h
19+
; CHECK-GI-NEXT: xtn v1.8b, v1.8h
20+
; CHECK-GI-NEXT: smull v0.8h, v1.8b, v0.8b
2121
; CHECK-GI-NEXT: ret
2222
entry:
2323
%in = sext i8 %src to i16

llvm/test/CodeGen/AArch64/aarch64-smull.ll

Lines changed: 12 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2264,68 +2264,25 @@ define <2 x i64> @lsr_const(<2 x i64> %a, <2 x i64> %b) {
22642264
}
22652265

22662266
define <2 x i64> @asr(<2 x i64> %a, <2 x i64> %b) {
2267-
; CHECK-NEON-LABEL: asr:
2268-
; CHECK-NEON: // %bb.0:
2269-
; CHECK-NEON-NEXT: shrn v0.2s, v0.2d, #32
2270-
; CHECK-NEON-NEXT: shrn v1.2s, v1.2d, #32
2271-
; CHECK-NEON-NEXT: smull v0.2d, v0.2s, v1.2s
2272-
; CHECK-NEON-NEXT: ret
2273-
;
2274-
; CHECK-SVE-LABEL: asr:
2275-
; CHECK-SVE: // %bb.0:
2276-
; CHECK-SVE-NEXT: shrn v0.2s, v0.2d, #32
2277-
; CHECK-SVE-NEXT: shrn v1.2s, v1.2d, #32
2278-
; CHECK-SVE-NEXT: smull v0.2d, v0.2s, v1.2s
2279-
; CHECK-SVE-NEXT: ret
2280-
;
2281-
; CHECK-GI-LABEL: asr:
2282-
; CHECK-GI: // %bb.0:
2283-
; CHECK-GI-NEXT: sshr v0.2d, v0.2d, #32
2284-
; CHECK-GI-NEXT: sshr v1.2d, v1.2d, #32
2285-
; CHECK-GI-NEXT: fmov x10, d0
2286-
; CHECK-GI-NEXT: fmov x11, d1
2287-
; CHECK-GI-NEXT: mov x8, v0.d[1]
2288-
; CHECK-GI-NEXT: mov x9, v1.d[1]
2289-
; CHECK-GI-NEXT: mul x10, x10, x11
2290-
; CHECK-GI-NEXT: mul x8, x8, x9
2291-
; CHECK-GI-NEXT: fmov d0, x10
2292-
; CHECK-GI-NEXT: mov v0.d[1], x8
2293-
; CHECK-GI-NEXT: ret
2267+
; CHECK-LABEL: asr:
2268+
; CHECK: // %bb.0:
2269+
; CHECK-NEXT: shrn v0.2s, v0.2d, #32
2270+
; CHECK-NEXT: shrn v1.2s, v1.2d, #32
2271+
; CHECK-NEXT: smull v0.2d, v0.2s, v1.2s
2272+
; CHECK-NEXT: ret
22942273
%x = ashr <2 x i64> %a, <i64 32, i64 32>
22952274
%y = ashr <2 x i64> %b, <i64 32, i64 32>
22962275
%z = mul nsw <2 x i64> %x, %y
22972276
ret <2 x i64> %z
22982277
}
22992278

23002279
define <2 x i64> @asr_const(<2 x i64> %a, <2 x i64> %b) {
2301-
; CHECK-NEON-LABEL: asr_const:
2302-
; CHECK-NEON: // %bb.0:
2303-
; CHECK-NEON-NEXT: movi v1.2s, #31
2304-
; CHECK-NEON-NEXT: shrn v0.2s, v0.2d, #32
2305-
; CHECK-NEON-NEXT: smull v0.2d, v0.2s, v1.2s
2306-
; CHECK-NEON-NEXT: ret
2307-
;
2308-
; CHECK-SVE-LABEL: asr_const:
2309-
; CHECK-SVE: // %bb.0:
2310-
; CHECK-SVE-NEXT: movi v1.2s, #31
2311-
; CHECK-SVE-NEXT: shrn v0.2s, v0.2d, #32
2312-
; CHECK-SVE-NEXT: smull v0.2d, v0.2s, v1.2s
2313-
; CHECK-SVE-NEXT: ret
2314-
;
2315-
; CHECK-GI-LABEL: asr_const:
2316-
; CHECK-GI: // %bb.0:
2317-
; CHECK-GI-NEXT: adrp x8, .LCPI81_0
2318-
; CHECK-GI-NEXT: sshr v0.2d, v0.2d, #32
2319-
; CHECK-GI-NEXT: ldr q1, [x8, :lo12:.LCPI81_0]
2320-
; CHECK-GI-NEXT: fmov x10, d0
2321-
; CHECK-GI-NEXT: fmov x11, d1
2322-
; CHECK-GI-NEXT: mov x8, v0.d[1]
2323-
; CHECK-GI-NEXT: mov x9, v1.d[1]
2324-
; CHECK-GI-NEXT: mul x10, x10, x11
2325-
; CHECK-GI-NEXT: mul x8, x8, x9
2326-
; CHECK-GI-NEXT: fmov d0, x10
2327-
; CHECK-GI-NEXT: mov v0.d[1], x8
2328-
; CHECK-GI-NEXT: ret
2280+
; CHECK-LABEL: asr_const:
2281+
; CHECK: // %bb.0:
2282+
; CHECK-NEXT: movi v1.2s, #31
2283+
; CHECK-NEXT: shrn v0.2s, v0.2d, #32
2284+
; CHECK-NEXT: smull v0.2d, v0.2s, v1.2s
2285+
; CHECK-NEXT: ret
23292286
%x = ashr <2 x i64> %a, <i64 32, i64 32>
23302287
%z = mul nsw <2 x i64> %x, <i64 31, i64 31>
23312288
ret <2 x i64> %z

llvm/test/CodeGen/AArch64/combine-sdiv.ll

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1631,7 +1631,6 @@ define i8 @combine_i8_sdiv_const100(i8 %x) {
16311631
; CHECK-GI-NEXT: sxtb w8, w0
16321632
; CHECK-GI-NEXT: mov w9, #41 // =0x29
16331633
; CHECK-GI-NEXT: mul w8, w8, w9
1634-
; CHECK-GI-NEXT: sxth w8, w8
16351634
; CHECK-GI-NEXT: sbfx w8, w8, #8, #8
16361635
; CHECK-GI-NEXT: asr w8, w8, #4
16371636
; CHECK-GI-NEXT: ubfx w9, w8, #7, #1

llvm/test/CodeGen/AArch64/rem-by-const.ll

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ define i8 @si8_100(i8 %a, i8 %b) {
5252
; CHECK-GI-NEXT: sxtb w8, w0
5353
; CHECK-GI-NEXT: mov w9, #41 // =0x29
5454
; CHECK-GI-NEXT: mul w8, w8, w9
55-
; CHECK-GI-NEXT: sxth w8, w8
5655
; CHECK-GI-NEXT: sbfx w8, w8, #8, #8
5756
; CHECK-GI-NEXT: asr w8, w8, #4
5857
; CHECK-GI-NEXT: ubfx w9, w8, #7, #1

llvm/test/CodeGen/AMDGPU/GlobalISel/legalize-smulh.mir

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,7 @@ body: |
8080
; GFX8-NEXT: [[MUL:%[0-9]+]]:_(s32) = G_MUL [[SEXT_INREG]], [[SEXT_INREG1]]
8181
; GFX8-NEXT: [[C:%[0-9]+]]:_(s32) = G_CONSTANT i32 16
8282
; GFX8-NEXT: [[ASHR:%[0-9]+]]:_(s32) = G_ASHR [[MUL]], [[C]](s32)
83-
; GFX8-NEXT: [[SEXT_INREG2:%[0-9]+]]:_(s32) = G_SEXT_INREG [[ASHR]], 16
84-
; GFX8-NEXT: $vgpr0 = COPY [[SEXT_INREG2]](s32)
83+
; GFX8-NEXT: $vgpr0 = COPY [[ASHR]](s32)
8584
;
8685
; GFX9-LABEL: name: test_smulh_s16
8786
; GFX9: liveins: $vgpr0, $vgpr1
@@ -93,8 +92,7 @@ body: |
9392
; GFX9-NEXT: [[MUL:%[0-9]+]]:_(s32) = G_MUL [[SEXT_INREG]], [[SEXT_INREG1]]
9493
; GFX9-NEXT: [[C:%[0-9]+]]:_(s32) = G_CONSTANT i32 16
9594
; GFX9-NEXT: [[ASHR:%[0-9]+]]:_(s32) = G_ASHR [[MUL]], [[C]](s32)
96-
; GFX9-NEXT: [[SEXT_INREG2:%[0-9]+]]:_(s32) = G_SEXT_INREG [[ASHR]], 16
97-
; GFX9-NEXT: $vgpr0 = COPY [[SEXT_INREG2]](s32)
95+
; GFX9-NEXT: $vgpr0 = COPY [[ASHR]](s32)
9896
%0:_(s32) = COPY $vgpr0
9997
%1:_(s32) = COPY $vgpr1
10098
%2:_(s16) = G_TRUNC %0
@@ -200,9 +198,7 @@ body: |
200198
; GFX9-NEXT: [[SEXT_INREG3:%[0-9]+]]:_(s32) = G_SEXT_INREG [[UV3]], 16
201199
; GFX9-NEXT: [[MUL1:%[0-9]+]]:_(s32) = G_MUL [[SEXT_INREG2]], [[SEXT_INREG3]]
202200
; GFX9-NEXT: [[ASHR1:%[0-9]+]]:_(s32) = G_ASHR [[MUL1]], [[C]](s32)
203-
; GFX9-NEXT: [[SEXT_INREG4:%[0-9]+]]:_(s32) = G_SEXT_INREG [[ASHR]], 16
204-
; GFX9-NEXT: [[SEXT_INREG5:%[0-9]+]]:_(s32) = G_SEXT_INREG [[ASHR1]], 16
205-
; GFX9-NEXT: [[BUILD_VECTOR:%[0-9]+]]:_(<2 x s32>) = G_BUILD_VECTOR [[SEXT_INREG4]](s32), [[SEXT_INREG5]](s32)
201+
; GFX9-NEXT: [[BUILD_VECTOR:%[0-9]+]]:_(<2 x s32>) = G_BUILD_VECTOR [[ASHR]](s32), [[ASHR1]](s32)
206202
; GFX9-NEXT: $vgpr0_vgpr1 = COPY [[BUILD_VECTOR]](<2 x s32>)
207203
%0:_(<2 x s32>) = COPY $vgpr0_vgpr1
208204
%1:_(<2 x s32>) = COPY $vgpr2_vgpr3

llvm/test/CodeGen/AMDGPU/GlobalISel/postlegalizercombiner-sbfx.mir

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,7 @@ body: |
9292
; GCN-NEXT: [[COPY:%[0-9]+]]:_(s32) = COPY $vgpr0
9393
; GCN-NEXT: [[C:%[0-9]+]]:_(s32) = G_CONSTANT i32 16
9494
; GCN-NEXT: [[ASHR:%[0-9]+]]:_(s32) = G_ASHR [[COPY]], [[C]](s32)
95-
; GCN-NEXT: [[SEXT_INREG:%[0-9]+]]:_(s32) = G_SEXT_INREG [[ASHR]], 20
96-
; GCN-NEXT: $vgpr0 = COPY [[SEXT_INREG]](s32)
95+
; GCN-NEXT: $vgpr0 = COPY [[ASHR]](s32)
9796
%0:_(s32) = COPY $vgpr0
9897
%1:_(s32) = G_CONSTANT i32 16
9998
%2:_(s32) = G_ASHR %0, %1(s32)

0 commit comments

Comments
 (0)