Skip to content
Open
38 changes: 38 additions & 0 deletions src/hotspot/share/opto/countbitsnode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "opto/opcodes.hpp"
#include "opto/phaseX.hpp"
#include "opto/type.hpp"
#include "utilities/population_count.hpp"

//------------------------------Value------------------------------------------
const Type* CountLeadingZerosINode::Value(PhaseGVN* phase) const {
Expand Down Expand Up @@ -116,3 +117,40 @@ const Type* CountTrailingZerosLNode::Value(PhaseGVN* phase) const {
}
return TypeInt::INT;
}
// 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) {
return Type::TOP;
}
KnownBits<juint> bits = t->isa_int()->_bits;
return TypeInt::make(population_count(bits._ones), population_count(~bits._zeros), Type::WidenMax);
Copy link
Member

Choose a reason for hiding this comment

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

The widen of the output should be the same as the widen of the input, not WidenMax here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks @merykitty, widen is mainly used for optimistic data flow analysis pass like CCP where type analysis begins with TOP and progressively grows the value range till convergence / fixed point.
it's good to preserve the widen of input to delay eager convergence.


}

const Type* PopCountLNode::Value(PhaseGVN* phase) const {
const Type* t = phase->type(in(1));
if (t == Type::TOP) {
return Type::TOP;
}
KnownBits<julong> bits = t->isa_long()->_bits;
return TypeInt::make(population_count(bits._ones), population_count(~bits._zeros), Type::WidenMax);
}
2 changes: 2 additions & 0 deletions src/hotspot/share/opto/countbitsnode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class PopCountINode : public CountBitsNode {
public:
PopCountINode(Node* in1) : CountBitsNode(in1) {}
virtual int Opcode() const;
virtual const Type* Value(PhaseGVN* phase) const;
};

//---------- PopCountLNode -----------------------------------------------------
Expand All @@ -88,6 +89,7 @@ class PopCountLNode : public CountBitsNode {
public:
PopCountLNode(Node* in1) : CountBitsNode(in1) {}
virtual int Opcode() const;
virtual const Type* Value(PhaseGVN* phase) const;
};


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

/**
* @test
* @bug 8365205
* @summary C2: Optimize popcount value computation using knownbits
* @library /test/lib /
* @run driver compiler.intrinsics.TestPopCountValueTransforms
*/
package compiler.intrinsics;

import compiler.lib.ir_framework.*;
import compiler.lib.generators.*;
import compiler.lib.verify.*;
import static compiler.lib.generators.Generators.*;

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

static final int SIZE = 4096;

static final int rand_bndI1 = G.ints().next();
static final int rand_bndI2 = G.ints().next();
static final int rand_popcI1 = G.uniformInts(0, 32).next();
static final int rand_popcI2 = G.uniformInts(0, 32).next();

static final long rand_bndL1 = G.longs().next();
static final long rand_bndL2 = G.longs().next();
static final long rand_popcL1 = G.uniformLongs(0, 32).next();
static final long rand_popcL2 = G.uniformLongs(0, 32).next();

@Test
@IR(counts = {IRNode.POPCOUNT_L, " 0 "})
public long testPopCountElisionLong1(long num) {
num = Math.clamp(num, 0xF000F000L, 0xF000F0FFL);
// PopCount ValueRange = {lo:8, hi:16}
if (Long.bitCount(num) < 8 || Long.bitCount(num) > 16) {
return 0;
}
return 1;
}

@Run(test = {"testPopCountElisionLong1"})
public void runPopCountElisionLong1() {
long res = 1;
for (int i = 0; i < inL1.length; i++) {
res &= testPopCountElisionLong1(inL1[i]);
}
Verify.checkEQ(res, 1L);
}

@Test
@IR(counts = {IRNode.POPCOUNT_L, " 1 "})
public long testPopCountElisionLong2(long num) {
num = Math.clamp(num, 0x3L, 0xFFFFL);
// PopCount ValueRange = {lo:0, hi:16}
if (Long.bitCount(num) >= 0 && Long.bitCount(num) <= 11) {
return 0;
}
return 1;
}

@Run(test = {"testPopCountElisionLong2"})
public void runPopCountElisionLong2() {
long res = 0;
for (int i = 0; i < inL2.length; i++) {
res |= testPopCountElisionLong2(inL2[i]);
}
Verify.checkEQ(res, 0L);
}

@Test
@IR(counts = {IRNode.POPCOUNT_I, " 0 "})
public int testPopCountElisionInt1(int num) {
// PopCount ValueRange = {lo:11, hi:15}
num = Math.clamp(num, 0xFE00F000, 0xFE00F00F);
if (Integer.bitCount(num) < 11 || Integer.bitCount(num) > 15) {
return 0;
}
return 1;
}

@Run(test = {"testPopCountElisionInt1"})
public void runPopCountElisionInt1() {
int res = 1;
for (int i = 0; i < inI1.length; i++) {
res &= testPopCountElisionInt1(inI1[i]);
}
Verify.checkEQ(res, 1);
}

@Test
@IR(counts = {IRNode.POPCOUNT_I, " 1 "})
public int testPopCountElisionInt2(int num) {
// PopCount ValueRange = {lo:0, hi:8}
num = Math.clamp(num, 0x3, 0xFF);
if (Integer.bitCount(num) >= 0 && Integer.bitCount(num) <= 5) {
return 0;
}
return 1;
}

@Run(test = {"testPopCountElisionInt2"})
public void runPopCountElisionInt2() {
int res = 0;
for (int i = 0; i < inI2.length; i++) {
res |= testPopCountElisionInt2(inI2[i]);
}
Verify.checkEQ(res, 0);
}

@Test
public void testPopCountRandomInt(int rand_numI) {
int res = 0;
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) {
res = 1;
} else {
res = -1;
}
checkPopCountRandomInt(rand_numI, res);
}

public void checkPopCountRandomInt(int rand_numI, int res) {
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 ? 1 : -1, res);
}

@Run(test="testPopCountRandomInt")
public void runPopCountRandomInt() {
testPopCountRandomInt(G.ints().next());
}


@Test
public void testPopCountRandomLong(long rand_numL) {
long res = 0;
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) {
res = 1L;
} else {
res = -1L;
}
checkPopCountRandomLong(rand_numL, res);
}

public void checkPopCountRandomLong(long rand_numL, long res) {
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 ? 1L : -1L, res);
}

@Run(test="testPopCountRandomLong")
public void runPopCountRandomLong() {
testPopCountRandomLong(G.longs().next());
}

public TestPopCountValueTransforms() {
inL1 = new long[SIZE];
G.fill(G.longs(), inL1);

inL2 = new long[SIZE];
Generator<Long> genL = G.uniformLongs(0x3L, 0xFFCL);
for (int i = 0; i < SIZE; i++) {
inL2[i] = genL.next();
}

inI1 = new int[SIZE];
G.fill(G.ints(), inI1);

inI2 = new int[SIZE];
Generator<Integer> genI = G.uniformInts(0x3, 0x1F);
for (int i = 0; i < SIZE; i++) {
inI2[i] = genI.next();
}
}

public static void main(String[] args) {
TestFramework.run();
}
}
5 changes: 5 additions & 0 deletions test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -1610,6 +1610,11 @@ public class IRNode {
beforeMatchingNameRegex(PHI, "Phi");
}

public static final String POPCOUNT_I = PREFIX + "POPCOUNT_I" + POSTFIX;
static {
beforeMatchingNameRegex(POPCOUNT_I, "PopCountI");
}

public static final String POPCOUNT_L = PREFIX + "POPCOUNT_L" + POSTFIX;
static {
beforeMatchingNameRegex(POPCOUNT_L, "PopCountL");
Expand Down
60 changes: 60 additions & 0 deletions test/micro/org/openjdk/bench/java/lang/PopCountValueTransform.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.java.lang;

import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Thread)
public class PopCountValueTransform {
public int lower_bound = 0;
public int upper_bound = 10000;

@Benchmark
public int LogicFoldingKerenlInt() {
int res = 0;
for (int i = lower_bound; i < upper_bound; i++) {
int constrained_i = i & 0xFFFF;
if (Integer.bitCount(constrained_i) > 16) {
throw new AssertionError("Uncommon trap");
}
res += constrained_i;
}
return res;
}

@Benchmark
public long LogicFoldingKerenLong() {
long res = 0;
for (int i = lower_bound; i < upper_bound; i++) {
long constrained_i = i & 0xFFFFFFL;
if (Long.bitCount(constrained_i) > 24) {
throw new AssertionError("Uncommon trap");
}
res += constrained_i;
}
return res;
}
}