Skip to content
Open
50 changes: 19 additions & 31 deletions src/hotspot/share/opto/countbitsnode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,37 +117,25 @@ const Type* CountTrailingZerosLNode::Value(PhaseGVN* phase) const {
}
return TypeInt::INT;
}

/*
Lemma 1: For a given known bits information, _lo and _hi bounds of the corresponding value
range is computed using the following formulas:-
- _hi = ~ZEROS
- _lo = ONES
Proof:-
- KnownBits.ZEROS and KnownBits.ONES are inferred out of the common prefix of the value range
delimiting bounds.

- Thus, ~KnownBits.ZEROS not only includes set bits in the common prefix but also optimistically assumes
that all other bits not included in the common prefix are also set.

- Consider the following illustration, which performs round-trip translation
of a value range via knowbits information, e.g.
A) Initial value range bounds to infer knownbits.
_lo = 0b11000100
_hi = 0b11000110
_common_prefix = 0b11000100
_common_prefix_mask = 0b11111100
_known_bits.ones = _lo & _common_prefix_mask = 0b11000100
_known_bits.zeros = ~_lo & _common_prefix_mask = 0b00111000

B) Now, transform the computed knownbits back to the value range.
_new_lo = _known_bits.ones = 0b11000100
_new_hi = ~known_bits.zeros = 0b11000111

- We now know that ~KnownBits.ZEROS >= UB >= LB >= KnownBits.ONES
- Therefore, popcount(ONES) and popcount(~ZEROS) can safely be assumed as the upper and lower
bounds of the result value range.
*/
// We use the KnownBits information from the integer types to derive how many one bits
// we have at least and at most.
// From the definition of KnownBits, we know:
// zeros: Indicates which bits must be 0: ones[i] =1 -> t[i]=0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused by this, is ones[i] mixed up with zeros[i]? I.e., t[i]=0 if zeros[i]=1

Copy link
Member Author

@jatin-bhateja jatin-bhateja Sep 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// ones: Indicates which bits must be 1: zeros[i]=1 -> t[i]=1
//
// From this, we derive:
// numer_of_zeros_in_t >= pop_count(zeros)
// -> number_of_ones_in_t <= bits_per_type - pop_count(zeros) = pop_count(~zeros)
// number_of_ones_in_t >= pop_count(ones)
//
// By definition:
// pop_count(t) = number_of_ones_in_t
//
// It follows:
// pop_count(ones) <= pop_count(t) <= pop_count(~zeros)
//
// Note: signed _lo and _hi, as well as unsigned _ulo and _uhi bounds of the integer types
// are already reflected in the KnownBits information, see TypeInt / TypeLong definitions.
const Type* PopCountINode::Value(PhaseGVN* phase) const {
const Type* t = phase->type(in(1));
if (t == Type::TOP) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,27 @@
import compiler.lib.generators.*;
import compiler.lib.verify.*;
import static compiler.lib.generators.Generators.*;
import jdk.test.lib.Utils;

public class TestPopCountValueTransforms {
int [] inI1;
int [] inI2;
long [] inL1;
long [] inL2;

static final int SIZE = 4096;

static int rand_numI = G.uniformInts(0, Integer.MAX_VALUE).next();
static final int rand_bndI1 = G.uniformInts(0xF, Integer.MAX_VALUE).next();
static final int rand_bndI2 = G.uniformInts(0xFF, Integer.MAX_VALUE).next();
static final int rand_popcI1 = G.uniformInts(0, 2).next();
static final int rand_popcI2 = G.uniformInts(10, 20).next();

static long rand_numL = G.uniformLongs(0, Long.MAX_VALUE).next();
static final long rand_bndL1 = G.uniformLongs(0xFL, Long.MAX_VALUE).next();
static final long rand_bndL2 = G.uniformLongs(0xFFL, Long.MAX_VALUE).next();
static final long rand_popcL1 = G.uniformLongs(0, 3).next();
static final long rand_popcL2 = G.uniformLongs(20, 40).next();

@Test
@IR(counts = {IRNode.POPCOUNT_L, " 0 "})
public long testPopCountElisionLong1(long num) {
Expand All @@ -53,7 +66,7 @@ public long testPopCountElisionLong1(long num) {
return 1;
}

@Run(test = {"testPopCountElisionLong1"}, mode = RunMode.STANDALONE)
@Run(test = {"testPopCountElisionLong1"})
public void runPopCountElisionLong1() {
long res = 1;
for (int i = 0; i < inL1.length; i++) {
Expand All @@ -63,7 +76,7 @@ public void runPopCountElisionLong1() {
}

@Test
@IR(counts = {IRNode.POPCOUNT_L, " >0 "})
@IR(counts = {IRNode.POPCOUNT_L, " 1 "})
public long testPopCountElisionLong2(long num) {
num = Math.clamp(num, 0x3L, 0xFFFFL);
// PopCount ValueRange = {lo:0, hi:16}
Expand All @@ -73,7 +86,7 @@ public long testPopCountElisionLong2(long num) {
return 1;
}

@Run(test = {"testPopCountElisionLong2"}, mode = RunMode.STANDALONE)
@Run(test = {"testPopCountElisionLong2"})
public void runPopCountElisionLong2() {
long res = 0;
for (int i = 0; i < inL2.length; i++) {
Expand All @@ -93,7 +106,7 @@ public int testPopCountElisionInt1(int num) {
return 1;
}

@Run(test = {"testPopCountElisionInt1"}, mode = RunMode.STANDALONE)
@Run(test = {"testPopCountElisionInt1"})
public void runPopCountElisionInt1() {
int res = 1;
for (int i = 0; i < inI1.length; i++) {
Expand All @@ -103,7 +116,7 @@ public void runPopCountElisionInt1() {
}

@Test
@IR(counts = {IRNode.POPCOUNT_I, " >0 "})
@IR(counts = {IRNode.POPCOUNT_I, " 1 "})
public int testPopCountElisionInt2(int num) {
// PopCount ValueRange = {lo:0, hi:8}
num = Math.clamp(num, 0x3, 0xFF);
Expand All @@ -113,7 +126,7 @@ public int testPopCountElisionInt2(int num) {
return 1;
}

@Run(test = {"testPopCountElisionInt2"}, mode = RunMode.STANDALONE)
@Run(test = {"testPopCountElisionInt2"})
public void runPopCountElisionInt2() {
int res = 0;
for (int i = 0; i < inI2.length; i++) {
Expand All @@ -122,7 +135,51 @@ public void runPopCountElisionInt2() {
Verify.checkEQ(res, 0);
}

static final int SIZE = 4096;
@Test
public int testPopCountRandomInt() {
int num = Math.clamp(rand_numI, Math.min(rand_bndI1, rand_bndI2), Math.max(rand_bndI1, rand_bndI2));
if (Integer.bitCount(num) >= rand_popcI1 && Integer.bitCount(num) < rand_popcI2) {
return 1;
} else {
return -1;
}
}

@Check(test = "testPopCountRandomInt")
public void checkPopCountRandomInt(int res) {
if (res == 1) {
int exp = 0;
int num = Math.clamp(rand_numI, Math.min(rand_bndI1, rand_bndI2), Math.max(rand_bndI1, rand_bndI2));
while(num != 0) {
num &= (num - 1);
exp++;
}
Verify.checkEQ(exp >= rand_popcI1 && exp < rand_popcI2, true);
}
}

@Test
public long testPopCountRandomLong() {
long num = Math.clamp(rand_numL, Math.min(rand_bndL1, rand_bndL2), Math.max(rand_bndL1, rand_bndL2));
if (Long.bitCount(num) >= rand_popcL1 && Long.bitCount(num) < rand_popcL2) {
return 1L;
} else {
return -1L;
}
}

@Check(test = "testPopCountRandomLong")
public void checkPopCountRandomLong(long res) {
if (res == 1) {
int exp = 0;
long num = Math.clamp(rand_numL, Math.min(rand_bndL1, rand_bndL2), Math.max(rand_bndL1, rand_bndL2));
while(num != 0) {
num &= (num - 1L);
exp++;
}
Verify.checkEQ(exp >= rand_popcL1 && exp < rand_popcL2, true);
}
}

public TestPopCountValueTransforms() {
inL1 = new long[SIZE];
Expand All @@ -145,6 +202,6 @@ public TestPopCountValueTransforms() {
}

public static void main(String[] args) {
TestFramework.runWithFlags("-XX:-TieredCompilation", "-XX:CompileThresholdScaling=0.2");
TestFramework.run();
}
}
20 changes: 0 additions & 20 deletions test/micro/org/openjdk/bench/java/lang/PopCountValueTransform.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,6 @@ public class PopCountValueTransform {
public int lower_bound = 0;
public int upper_bound = 10000;

@Benchmark
public int StockKernelInt() {
int res = 0;
for (int i = lower_bound; i < upper_bound; i++) {
int constrained_i = i & 0xFFFF;
res += constrained_i;
}
return res;
}

@Benchmark
public int LogicFoldingKerenlInt() {
int res = 0;
Expand All @@ -55,16 +45,6 @@ public int LogicFoldingKerenlInt() {
return res;
}

@Benchmark
public long StockKernelLong() {
long res = 0;
for (int i = lower_bound; i < upper_bound; i++) {
long constrained_i = i & 0xFFFFFFL;
res += constrained_i;
}
return res;
}

@Benchmark
public long LogicFoldingKerenLong() {
long res = 0;
Expand Down