From 36993aea9a8501f56adb48ca2ff96ccb5dbfcea1 Mon Sep 17 00:00:00 2001 From: Jasmine Karthikeyan Date: Mon, 16 Oct 2023 12:52:01 +0000 Subject: [PATCH] 8316918: Optimize conversions duplicated across phi nodes Reviewed-by: thartmann, kvn --- src/hotspot/share/opto/cfgnode.cpp | 46 ++++ src/hotspot/share/opto/convertnode.cpp | 68 +++++- src/hotspot/share/opto/convertnode.hpp | 175 +++++++------- src/hotspot/share/opto/node.hpp | 3 + src/hotspot/share/runtime/vmStructs.cpp | 1 + .../irTests/TestPhiDuplicatedConversion.java | 155 ++++++++++++ .../compiler/lib/ir_framework/IRNode.java | 5 + .../vm/compiler/PhiDuplicatedConversion.java | 220 ++++++++++++++++++ 8 files changed, 575 insertions(+), 98 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/c2/irTests/TestPhiDuplicatedConversion.java create mode 100644 test/micro/org/openjdk/bench/vm/compiler/PhiDuplicatedConversion.java diff --git a/src/hotspot/share/opto/cfgnode.cpp b/src/hotspot/share/opto/cfgnode.cpp index 0221fe0c3d0..7bffd75d3f6 100644 --- a/src/hotspot/share/opto/cfgnode.cpp +++ b/src/hotspot/share/opto/cfgnode.cpp @@ -1883,6 +1883,17 @@ static Node* split_flow_path(PhaseGVN *phase, PhiNode *phi) { return phi; } +// Returns the BasicType of a given convert node and a type, with special handling to ensure that conversions to +// and from half float will return the SHORT basic type, as that wouldn't be returned typically from TypeInt. +static BasicType get_convert_type(Node* convert, const Type* type) { + int convert_op = convert->Opcode(); + if (type->isa_int() && (convert_op == Op_ConvHF2F || convert_op == Op_ConvF2HF)) { + return T_SHORT; + } + + return type->basic_type(); +} + //============================================================================= //------------------------------simple_data_loop_check------------------------- // Try to determining if the phi node in a simple safe/unsafe data loop. @@ -2557,6 +2568,41 @@ Node *PhiNode::Ideal(PhaseGVN *phase, bool can_reshape) { } #endif + // Try to convert a Phi with two duplicated convert nodes into a phi of the pre-conversion type and the convert node + // proceeding the phi, to de-duplicate the convert node and compact the IR. + if (can_reshape && progress == nullptr) { + ConvertNode* convert = in(1)->isa_Convert(); + if (convert != nullptr) { + int conv_op = convert->Opcode(); + bool ok = true; + + // Check the rest of the inputs + for (uint i = 2; i < req(); i++) { + // Make sure that all inputs are of the same type of convert node + if (in(i)->Opcode() != conv_op) { + ok = false; + break; + } + } + + if (ok) { + // Find the local bottom type to set as the type of the phi + const Type* source_type = Type::get_const_basic_type(convert->in_type()->basic_type()); + const Type* dest_type = convert->bottom_type(); + + PhiNode* newphi = new PhiNode(in(0), source_type, nullptr); + // Set inputs to the new phi be the inputs of the convert + for (uint i = 1; i < req(); i++) { + newphi->init_req(i, in(i)->in(1)); + } + + phase->is_IterGVN()->register_new_node_with_optimizer(newphi, this); + + return ConvertNode::create_convert(get_convert_type(convert, source_type), get_convert_type(convert, dest_type), newphi); + } + } + } + // Phi (VB ... VB) => VB (Phi ...) (Phi ...) if (EnableVectorReboxing && can_reshape && progress == nullptr && type()->isa_oopptr()) { progress = merge_through_phi(this, phase->is_IterGVN()); diff --git a/src/hotspot/share/opto/convertnode.cpp b/src/hotspot/share/opto/convertnode.cpp index 122195f108c..0a2131782a2 100644 --- a/src/hotspot/share/opto/convertnode.cpp +++ b/src/hotspot/share/opto/convertnode.cpp @@ -88,6 +88,54 @@ Node* Conv2BNode::Ideal(PhaseGVN* phase, bool can_reshape) { return nullptr; } +uint ConvertNode::ideal_reg() const { + return _type->ideal_reg(); +} + +Node* ConvertNode::create_convert(BasicType source, BasicType target, Node* input) { + if (source == T_INT) { + if (target == T_LONG) { + return new ConvI2LNode(input); + } else if (target == T_FLOAT) { + return new ConvI2FNode(input); + } else if (target == T_DOUBLE) { + return new ConvI2DNode(input); + } + } else if (source == T_LONG) { + if (target == T_INT) { + return new ConvL2INode(input); + } else if (target == T_FLOAT) { + return new ConvL2FNode(input); + } else if (target == T_DOUBLE) { + return new ConvL2DNode(input); + } + } else if (source == T_FLOAT) { + if (target == T_INT) { + return new ConvF2INode(input); + } else if (target == T_LONG) { + return new ConvF2LNode(input); + } else if (target == T_DOUBLE) { + return new ConvF2DNode(input); + } else if (target == T_SHORT) { + return new ConvF2HFNode(input); + } + } else if (source == T_DOUBLE) { + if (target == T_INT) { + return new ConvD2INode(input); + } else if (target == T_LONG) { + return new ConvD2LNode(input); + } else if (target == T_FLOAT) { + return new ConvD2FNode(input); + } + } else if (source == T_SHORT) { + if (target == T_FLOAT) { + return new ConvHF2FNode(input); + } + } + + assert(false, "Couldn't create conversion for type %s to %s", type2name(source), type2name(target)); + return nullptr; +} // The conversions operations are all Alpha sorted. Please keep it that way! //============================================================================= @@ -193,8 +241,9 @@ const Type* ConvF2DNode::Value(PhaseGVN* phase) const { const Type* ConvF2HFNode::Value(PhaseGVN* phase) const { const Type *t = phase->type( in(1) ); if (t == Type::TOP) return Type::TOP; - if (t == Type::FLOAT) return TypeInt::SHORT; - if (StubRoutines::f2hf_adr() == nullptr) return bottom_type(); + if (t == Type::FLOAT || StubRoutines::f2hf_adr() == nullptr) { + return TypeInt::SHORT; + } const TypeF *tf = t->is_float_constant(); return TypeInt::make( StubRoutines::f2hf(tf->getf()) ); @@ -263,14 +312,15 @@ Node *ConvF2LNode::Ideal(PhaseGVN *phase, bool can_reshape) { const Type* ConvHF2FNode::Value(PhaseGVN* phase) const { const Type *t = phase->type( in(1) ); if (t == Type::TOP) return Type::TOP; - if (t == TypeInt::SHORT) return Type::FLOAT; - if (StubRoutines::hf2f_adr() == nullptr) return bottom_type(); + if (t == TypeInt::SHORT || StubRoutines::hf2f_adr() == nullptr) { + return Type::FLOAT; + } const TypeInt *ti = t->is_int(); if (ti->is_con()) { return TypeF::make( StubRoutines::hf2f(ti->get_con()) ); } - return bottom_type(); + return Type::FLOAT; } //============================================================================= @@ -280,7 +330,7 @@ const Type* ConvI2DNode::Value(PhaseGVN* phase) const { if( t == Type::TOP ) return Type::TOP; const TypeInt *ti = t->is_int(); if( ti->is_con() ) return TypeD::make( (double)ti->get_con() ); - return bottom_type(); + return Type::DOUBLE; } //============================================================================= @@ -290,7 +340,7 @@ const Type* ConvI2FNode::Value(PhaseGVN* phase) const { if( t == Type::TOP ) return Type::TOP; const TypeInt *ti = t->is_int(); if( ti->is_con() ) return TypeF::make( (float)ti->get_con() ); - return bottom_type(); + return Type::FLOAT; } //------------------------------Identity--------------------------------------- @@ -710,7 +760,7 @@ const Type* ConvL2DNode::Value(PhaseGVN* phase) const { if( t == Type::TOP ) return Type::TOP; const TypeLong *tl = t->is_long(); if( tl->is_con() ) return TypeD::make( (double)tl->get_con() ); - return bottom_type(); + return Type::DOUBLE; } //============================================================================= @@ -720,7 +770,7 @@ const Type* ConvL2FNode::Value(PhaseGVN* phase) const { if( t == Type::TOP ) return Type::TOP; const TypeLong *tl = t->is_long(); if( tl->is_con() ) return TypeF::make( (float)tl->get_con() ); - return bottom_type(); + return Type::FLOAT; } //============================================================================= diff --git a/src/hotspot/share/opto/convertnode.hpp b/src/hotspot/share/opto/convertnode.hpp index 45277aead8d..cf76f5ab6fd 100644 --- a/src/hotspot/share/opto/convertnode.hpp +++ b/src/hotspot/share/opto/convertnode.hpp @@ -42,193 +42,190 @@ class Conv2BNode : public Node { virtual uint ideal_reg() const { return Op_RegI; } }; +class ConvertNode : public TypeNode { +protected: + ConvertNode(const Type* t, Node* input) : TypeNode(t, 2) { + init_class_id(Class_Convert); + init_req(1, input); + } +public: + virtual const Type* in_type() const = 0; + virtual uint ideal_reg() const; + + // Create a convert node for a given input and output type. + // Conversions to and from half float are specified via T_SHORT. + static Node* create_convert(BasicType source, BasicType target, Node* input); +}; + // The conversions operations are all Alpha sorted. Please keep it that way! //------------------------------ConvD2FNode------------------------------------ // Convert double to float -class ConvD2FNode : public Node { +class ConvD2FNode : public ConvertNode { public: - ConvD2FNode( Node *in1 ) : Node(0,in1) {} + ConvD2FNode(Node* in1) : ConvertNode(Type::FLOAT,in1) {} virtual int Opcode() const; - virtual const Type *bottom_type() const { return Type::FLOAT; } + virtual const Type* in_type() const { return Type::DOUBLE; } virtual const Type* Value(PhaseGVN* phase) const; virtual Node* Identity(PhaseGVN* phase); - virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); - virtual uint ideal_reg() const { return Op_RegF; } + virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); }; //------------------------------ConvD2INode------------------------------------ // Convert Double to Integer -class ConvD2INode : public Node { +class ConvD2INode : public ConvertNode { public: - ConvD2INode( Node *in1 ) : Node(0,in1) {} + ConvD2INode(Node* in1) : ConvertNode(TypeInt::INT,in1) {} virtual int Opcode() const; - virtual const Type *bottom_type() const { return TypeInt::INT; } + virtual const Type* in_type() const { return Type::DOUBLE; } virtual const Type* Value(PhaseGVN* phase) const; virtual Node* Identity(PhaseGVN* phase); - virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); - virtual uint ideal_reg() const { return Op_RegI; } + virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); }; //------------------------------ConvD2LNode------------------------------------ // Convert Double to Long -class ConvD2LNode : public Node { +class ConvD2LNode : public ConvertNode { public: - ConvD2LNode( Node *dbl ) : Node(0,dbl) {} + ConvD2LNode(Node* in1) : ConvertNode(TypeLong::LONG, in1) {} virtual int Opcode() const; - virtual const Type *bottom_type() const { return TypeLong::LONG; } + virtual const Type* in_type() const { return Type::DOUBLE; } virtual const Type* Value(PhaseGVN* phase) const; virtual Node* Identity(PhaseGVN* phase); - virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); - virtual uint ideal_reg() const { return Op_RegL; } -}; - -class RoundDNode : public Node { - public: - RoundDNode( Node *dbl ) : Node(0,dbl) {} - virtual int Opcode() const; - virtual const Type *bottom_type() const { return TypeLong::LONG; } - virtual uint ideal_reg() const { return Op_RegL; } + virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); }; //------------------------------ConvF2DNode------------------------------------ // Convert Float to a Double. -class ConvF2DNode : public Node { +class ConvF2DNode : public ConvertNode { public: - ConvF2DNode( Node *in1 ) : Node(0,in1) {} + ConvF2DNode(Node* in1) : ConvertNode(Type::DOUBLE,in1) {} virtual int Opcode() const; - virtual const Type *bottom_type() const { return Type::DOUBLE; } + virtual const Type* in_type() const { return Type::FLOAT; } virtual const Type* Value(PhaseGVN* phase) const; - virtual uint ideal_reg() const { return Op_RegD; } }; //------------------------------ConvF2HFNode------------------------------------ // Convert Float to Halffloat -class ConvF2HFNode : public Node { +class ConvF2HFNode : public ConvertNode { public: - ConvF2HFNode( Node *in1 ) : Node(0,in1) {} + ConvF2HFNode(Node* in1) : ConvertNode(TypeInt::SHORT, in1) {} virtual int Opcode() const; - virtual const Type *bottom_type() const { return TypeInt::SHORT; } + virtual const Type* in_type() const { return TypeInt::FLOAT; } virtual const Type* Value(PhaseGVN* phase) const; - virtual uint ideal_reg() const { return Op_RegI; } }; //------------------------------ConvF2INode------------------------------------ // Convert float to integer -class ConvF2INode : public Node { - public: - ConvF2INode( Node *in1 ) : Node(0,in1) {} +class ConvF2INode : public ConvertNode { +public: + ConvF2INode(Node* in1) : ConvertNode(TypeInt::INT, in1) {} virtual int Opcode() const; - virtual const Type *bottom_type() const { return TypeInt::INT; } + virtual const Type* in_type() const { return TypeInt::FLOAT; } virtual const Type* Value(PhaseGVN* phase) const; virtual Node* Identity(PhaseGVN* phase); - virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); - virtual uint ideal_reg() const { return Op_RegI; } + virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); }; - //------------------------------ConvF2LNode------------------------------------ // Convert float to long -class ConvF2LNode : public Node { - public: - ConvF2LNode( Node *in1 ) : Node(0,in1) {} +class ConvF2LNode : public ConvertNode { +public: + ConvF2LNode(Node* in1) : ConvertNode(TypeLong::LONG, in1) {} virtual int Opcode() const; - virtual const Type *bottom_type() const { return TypeLong::LONG; } + virtual const Type* in_type() const { return TypeInt::FLOAT; } virtual const Type* Value(PhaseGVN* phase) const; virtual Node* Identity(PhaseGVN* phase); - virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); - virtual uint ideal_reg() const { return Op_RegL; } + virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); }; //------------------------------ConvHF2FNode------------------------------------ // Convert Halffloat to float -class ConvHF2FNode : public Node { - public: - ConvHF2FNode( Node *in1 ) : Node(0,in1) {} +class ConvHF2FNode : public ConvertNode { +public: + ConvHF2FNode(Node* in1) : ConvertNode(Type::FLOAT, in1) {} virtual int Opcode() const; - virtual const Type *bottom_type() const { return Type::FLOAT; } + virtual const Type* in_type() const { return TypeInt::SHORT; } virtual const Type* Value(PhaseGVN* phase) const; - virtual uint ideal_reg() const { return Op_RegF; } }; //------------------------------ConvI2DNode------------------------------------ // Convert Integer to Double -class ConvI2DNode : public Node { - public: - ConvI2DNode( Node *in1 ) : Node(0,in1) {} +class ConvI2DNode : public ConvertNode { +public: + ConvI2DNode(Node* in1) : ConvertNode(Type::DOUBLE, in1) {} virtual int Opcode() const; - virtual const Type *bottom_type() const { return Type::DOUBLE; } + virtual const Type* in_type() const { return TypeInt::INT; } virtual const Type* Value(PhaseGVN* phase) const; - virtual uint ideal_reg() const { return Op_RegD; } }; //------------------------------ConvI2FNode------------------------------------ // Convert Integer to Float -class ConvI2FNode : public Node { - public: - ConvI2FNode( Node *in1 ) : Node(0,in1) {} +class ConvI2FNode : public ConvertNode { +public: + ConvI2FNode(Node* in1) : ConvertNode(Type::FLOAT, in1) {} virtual int Opcode() const; - virtual const Type *bottom_type() const { return Type::FLOAT; } + virtual const Type* in_type() const { return TypeInt::INT; } virtual const Type* Value(PhaseGVN* phase) const; virtual Node* Identity(PhaseGVN* phase); - virtual uint ideal_reg() const { return Op_RegF; } -}; - -class RoundFNode : public Node { - public: - RoundFNode( Node *in1 ) : Node(0,in1) {} - virtual int Opcode() const; - virtual const Type *bottom_type() const { return TypeInt::INT; } - virtual uint ideal_reg() const { return Op_RegI; } }; //------------------------------ConvI2LNode------------------------------------ // Convert integer to long -class ConvI2LNode : public TypeNode { +class ConvI2LNode : public ConvertNode { public: - ConvI2LNode(Node *in1, const TypeLong* t = TypeLong::INT) - : TypeNode(t, 2) - { init_req(1, in1); } + ConvI2LNode(Node* in1, const TypeLong* t = TypeLong::INT) : ConvertNode(t, in1) {} virtual int Opcode() const; + virtual const Type* in_type() const { return TypeInt::INT; } virtual const Type* Value(PhaseGVN* phase) const; - virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); + virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); virtual Node* Identity(PhaseGVN* phase); - virtual uint ideal_reg() const { return Op_RegL; } }; //------------------------------ConvL2DNode------------------------------------ // Convert Long to Double -class ConvL2DNode : public Node { - public: - ConvL2DNode( Node *in1 ) : Node(0,in1) {} +class ConvL2DNode : public ConvertNode { +public: + ConvL2DNode(Node* in1) : ConvertNode(Type::DOUBLE, in1) {} virtual int Opcode() const; - virtual const Type *bottom_type() const { return Type::DOUBLE; } + virtual const Type* in_type() const { return TypeLong::LONG; } virtual const Type* Value(PhaseGVN* phase) const; - virtual uint ideal_reg() const { return Op_RegD; } }; //------------------------------ConvL2FNode------------------------------------ // Convert Long to Float -class ConvL2FNode : public Node { - public: - ConvL2FNode( Node *in1 ) : Node(0,in1) {} +class ConvL2FNode : public ConvertNode { +public: + ConvL2FNode(Node* in1) : ConvertNode(Type::FLOAT, in1) {} virtual int Opcode() const; - virtual const Type *bottom_type() const { return Type::FLOAT; } + virtual const Type* in_type() const { return TypeLong::LONG; } virtual const Type* Value(PhaseGVN* phase) const; - virtual uint ideal_reg() const { return Op_RegF; } }; //------------------------------ConvL2INode------------------------------------ // Convert long to integer -class ConvL2INode : public TypeNode { +class ConvL2INode : public ConvertNode { public: - ConvL2INode(Node *in1, const TypeInt* t = TypeInt::INT) - : TypeNode(t, 2) { - init_req(1, in1); - } + ConvL2INode(Node* in1, const TypeInt* t = TypeInt::INT) : ConvertNode(t, in1) {} virtual int Opcode() const; + virtual const Type* in_type() const { return TypeLong::LONG; } virtual Node* Identity(PhaseGVN* phase); virtual const Type* Value(PhaseGVN* phase) const; - virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); + virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); +}; + +class RoundDNode : public Node { +public: + RoundDNode(Node* in1) : Node(nullptr, in1) {} + virtual int Opcode() const; + virtual const Type* bottom_type() const { return TypeLong::LONG; } + virtual uint ideal_reg() const { return Op_RegL; } +}; + +class RoundFNode : public Node { +public: + RoundFNode(Node* in1) : Node(nullptr, in1) {} + virtual int Opcode() const; + virtual const Type* bottom_type() const { return TypeInt::INT; } virtual uint ideal_reg() const { return Op_RegI; } }; diff --git a/src/hotspot/share/opto/node.hpp b/src/hotspot/share/opto/node.hpp index c94c439430b..1866ef87def 100644 --- a/src/hotspot/share/opto/node.hpp +++ b/src/hotspot/share/opto/node.hpp @@ -70,6 +70,7 @@ class CodeBuffer; class ConstraintCastNode; class ConNode; class ConINode; +class ConvertNode; class CompareAndSwapNode; class CompareAndExchangeNode; class CountedLoopNode; @@ -731,6 +732,7 @@ public: DEFINE_CLASS_ID(Con, Type, 8) DEFINE_CLASS_ID(ConI, Con, 0) DEFINE_CLASS_ID(SafePointScalarMerge, Type, 9) + DEFINE_CLASS_ID(Convert, Type, 10) DEFINE_CLASS_ID(Proj, Node, 3) @@ -889,6 +891,7 @@ public: DEFINE_CLASS_QUERY(ClearArray) DEFINE_CLASS_QUERY(CMove) DEFINE_CLASS_QUERY(Cmp) + DEFINE_CLASS_QUERY(Convert) DEFINE_CLASS_QUERY(CountedLoop) DEFINE_CLASS_QUERY(CountedLoopEnd) DEFINE_CLASS_QUERY(DecodeNarrowPtr) diff --git a/src/hotspot/share/runtime/vmStructs.cpp b/src/hotspot/share/runtime/vmStructs.cpp index a5177f9d309..2c61cd28ed8 100644 --- a/src/hotspot/share/runtime/vmStructs.cpp +++ b/src/hotspot/share/runtime/vmStructs.cpp @@ -1493,6 +1493,7 @@ declare_c2_type(CastPPNode, ConstraintCastNode) \ declare_c2_type(CheckCastPPNode, TypeNode) \ declare_c2_type(Conv2BNode, Node) \ + declare_c2_type(ConvertNode, TypeNode) \ declare_c2_type(ConvD2FNode, Node) \ declare_c2_type(ConvD2INode, Node) \ declare_c2_type(ConvD2LNode, Node) \ diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestPhiDuplicatedConversion.java b/test/hotspot/jtreg/compiler/c2/irTests/TestPhiDuplicatedConversion.java new file mode 100644 index 00000000000..947d678a4f2 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/irTests/TestPhiDuplicatedConversion.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2023, 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 compiler.c2.irTests; + +import jdk.test.lib.Asserts; +import compiler.lib.ir_framework.*; +import java.util.Random; +import jdk.test.lib.Utils; + +/* + * @test + * @summary Test that patterns involving duplicated conversion nodes behind phi are properly optimized. + * @bug 8316918 + * @library /test/lib / + * @requires vm.compiler2.enabled + * @run driver compiler.c2.irTests.TestPhiDuplicatedConversion + */ +public class TestPhiDuplicatedConversion { + public static void main(String[] args) { + TestFramework.run(); + } + + @Test + @IR(counts = {IRNode.CONV, "1"}) + public static float int2Float(boolean c, int a, int b) { + return c ? a : b; + } + + @Test + @IR(counts = {IRNode.CONV, "1"}) + public static double int2Double(boolean c, int a, int b) { + return c ? a : b; + } + + @Test + @IR(counts = {IRNode.CONV, "1"}) + public static long int2Long(boolean c, int a, int b) { + return c ? a : b; + } + + @Test + @IR(counts = {IRNode.CONV, "1"}) + public static int float2Int(boolean c, float a, float b) { + return c ? (int)a : (int)b; + } + + @Test + @IR(counts = {IRNode.CONV, "1"}) + public static double float2Double(boolean c, float a, float b) { + return c ? (double)a : (double)b; + } + + @Test + @IR(counts = {IRNode.CONV, "1"}) + public static long float2Long(boolean c, float a, float b) { + return c ? (long)a : (long)b; + } + + @Test + @IR(counts = {IRNode.CONV, "1"}) + public static int double2Int(boolean c, double a, double b) { + return c ? (int)a : (int)b; + } + + @Test + @IR(counts = {IRNode.CONV, "1"}) + public static float double2Float(boolean c, double a, double b) { + return c ? (float)a : (float)b; + } + + @Test + @IR(counts = {IRNode.CONV, "1"}) + public static long double2Long(boolean c, double a, double b) { + return c ? (long)a : (long)b; + } + + @Test + @IR(counts = {IRNode.CONV, "1"}) + public static float long2Float(boolean c, long a, long b) { + return c ? a : b; + } + + @Test + @IR(counts = {IRNode.CONV, "1"}) + public static double long2Double(boolean c, long a, long b) { + return c ? a : b; + } + + @Test + @IR(counts = {IRNode.CONV, "1"}) + public static int long2Int(boolean c, long a, long b) { + return c ? (int)a : (int)b; + } + + @Test + @IR(counts = {IRNode.CONV, "1"}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + public static short float2HalfFloat(boolean c, float a, float b) { + return c ? Float.floatToFloat16(a) : Float.floatToFloat16(b); + } + + @Test + @IR(counts = {IRNode.CONV, "1"}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + public static float halfFloat2Float(boolean c, short a, short b) { + return c ? Float.float16ToFloat(a) : Float.float16ToFloat(b); + } + + @Run(test = {"int2Float", "int2Double", "int2Long", + "float2Int", "float2Double", "float2Long", + "double2Int", "double2Float", "double2Long", + "long2Float", "long2Double", "long2Int", + "float2HalfFloat", "halfFloat2Float"}) + public void runTests() { + assertResults(true, 10, 20, 3.14f, -1.6f, 3.1415, -1.618, 30L, 400L, Float.floatToFloat16(10.5f), Float.floatToFloat16(20.5f)); + assertResults(false, 10, 20, 3.14f, -1.6f, 3.1415, -1.618, 30L, 400L, Float.floatToFloat16(10.5f), Float.floatToFloat16(20.5f)); + } + + @DontCompile + public void assertResults(boolean c, int intA, int intB, float floatA, float floatB, double doubleA, double doubleB, long longA, long longB, short halfFloatA, short halfFloatB) { + Asserts.assertEQ(c ? (float)intA : (float)intB, int2Float(c, intA, intB)); + Asserts.assertEQ(c ? (double)intA : (double)intB, int2Double(c, intA, intB)); + Asserts.assertEQ(c ? (long)intA : (long)intB, int2Long(c, intA, intB)); + Asserts.assertEQ(c ? (int)floatA : (int)floatB, float2Int(c, floatA, floatB)); + Asserts.assertEQ(c ? (double)floatA : (double)floatB, float2Double(c, floatA, floatB)); + Asserts.assertEQ(c ? (long)floatA : (long)floatB, float2Long(c, floatA, floatB)); + Asserts.assertEQ(c ? (int)doubleA : (int)doubleB, double2Int(c, doubleA, doubleB)); + Asserts.assertEQ(c ? (float)doubleA : (float)doubleB, double2Float(c, doubleA, doubleB)); + Asserts.assertEQ(c ? (long)doubleA : (long)doubleB, double2Long(c, doubleA, doubleB)); + Asserts.assertEQ(c ? (float)longA : (float)longB, long2Float(c, longA, longB)); + Asserts.assertEQ(c ? (double)longA : (double)longB, long2Double(c, longA, longB)); + Asserts.assertEQ(c ? (int)longA : (int)longB, long2Int(c, longA, longB)); + Asserts.assertEQ(c ? Float.floatToFloat16(floatA) : Float.floatToFloat16(floatB), float2HalfFloat(c, floatA, floatB)); + Asserts.assertEQ(c ? Float.float16ToFloat(halfFloatA) : Float.float16ToFloat(halfFloatB), halfFloat2Float(c, halfFloatA, halfFloatB)); + } +} \ No newline at end of file diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index b55925eb240..1778c2292df 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -442,6 +442,11 @@ public class IRNode { beforeMatchingNameRegex(COMPRESS_BITS, "CompressBits"); } + public static final String CONV = PREFIX + "CONV" + POSTFIX; + static { + beforeMatchingNameRegex(CONV, "Conv"); + } + public static final String CONV_I2L = PREFIX + "CONV_I2L" + POSTFIX; static { beforeMatchingNameRegex(CONV_I2L, "ConvI2L"); diff --git a/test/micro/org/openjdk/bench/vm/compiler/PhiDuplicatedConversion.java b/test/micro/org/openjdk/bench/vm/compiler/PhiDuplicatedConversion.java new file mode 100644 index 00000000000..fe706cac2ce --- /dev/null +++ b/test/micro/org/openjdk/bench/vm/compiler/PhiDuplicatedConversion.java @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2023, 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.vm.compiler; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@Measurement(iterations = 4, time = 1000, timeUnit = TimeUnit.MILLISECONDS) +@Warmup(iterations = 3, time = 1000, timeUnit = TimeUnit.MILLISECONDS) +@Fork(3) +public class PhiDuplicatedConversion { + public static final int SIZE = 300; + + // Ints + + @Benchmark + public void testInt2Float(Blackhole blackhole, BenchState state) { + for (int i = 0; i < SIZE; i++) { + blackhole.consume(i2f(state.bools[i], state.ints[i], state.ints[SIZE - 1 - i])); + } + } + + @Benchmark + public void testInt2Double(Blackhole blackhole, BenchState state) { + for (int i = 0; i < SIZE; i++) { + blackhole.consume(i2d(state.bools[i], state.ints[i], state.ints[SIZE - 1 - i])); + } + } + + @Benchmark + public void testInt2Long(Blackhole blackhole, BenchState state) { + for (int i = 0; i < SIZE; i++) { + blackhole.consume(i2l(state.bools[i], state.ints[i], state.ints[SIZE - 1 - i])); + } + } + + // Floats + + @Benchmark + public void testFloat2Int(Blackhole blackhole, BenchState state) { + for (int i = 0; i < SIZE; i++) { + blackhole.consume(f2i(state.bools[i], state.floats[i], state.floats[SIZE - 1 - i])); + } + } + + @Benchmark + public void testFloat2Double(Blackhole blackhole, BenchState state) { + for (int i = 0; i < SIZE; i++) { + blackhole.consume(f2d(state.bools[i], state.floats[i], state.floats[SIZE - 1 - i])); + } + } + + @Benchmark + public void testFloat2Long(Blackhole blackhole, BenchState state) { + for (int i = 0; i < SIZE; i++) { + blackhole.consume(f2l(state.bools[i], state.floats[i], state.floats[SIZE - 1 - i])); + } + } + + // Doubles + + @Benchmark + public void testDouble2Int(Blackhole blackhole, BenchState state) { + for (int i = 0; i < SIZE; i++) { + blackhole.consume(d2i(state.bools[i], state.doubles[i], state.doubles[SIZE - 1 - i])); + } + } + + @Benchmark + public void testDouble2Float(Blackhole blackhole, BenchState state) { + for (int i = 0; i < SIZE; i++) { + blackhole.consume(d2f(state.bools[i], state.doubles[i], state.doubles[SIZE - 1 - i])); + } + } + + @Benchmark + public void testDouble2Long(Blackhole blackhole, BenchState state) { + for (int i = 0; i < SIZE; i++) { + blackhole.consume(d2l(state.bools[i], state.doubles[i], state.doubles[SIZE - 1 - i])); + } + } + + // Longs + + @Benchmark + public void testLong2Float(Blackhole blackhole, BenchState state) { + for (int i = 0; i < SIZE; i++) { + blackhole.consume(l2f(state.bools[i], state.longs[i], state.longs[SIZE - 1 - i])); + } + } + + @Benchmark + public void testLong2Double(Blackhole blackhole, BenchState state) { + for (int i = 0; i < SIZE; i++) { + blackhole.consume(l2d(state.bools[i], state.longs[i], state.longs[SIZE - 1 - i])); + } + } + + @Benchmark + public void testLong2Int(Blackhole blackhole, BenchState state) { + for (int i = 0; i < SIZE; i++) { + blackhole.consume(l2i(state.bools[i], state.longs[i], state.longs[SIZE - 1 - i])); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public static float i2f(boolean c, int a, int b) { + return c ? a : b; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public static double i2d(boolean c, int a, int b) { + return c ? a : b; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public static long i2l(boolean c, int a, int b) { + return c ? a : b; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public static int f2i(boolean c, float a, float b) { + return c ? (int)a : (int)b; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public static double f2d(boolean c, float a, float b) { + return c ? (double)a : (double)b; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public static long f2l(boolean c, float a, float b) { + return c ? (long)a : (long)b; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public static int d2i(boolean c, double a, double b) { + return c ? (int)a : (int)b; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public static float d2f(boolean c, double a, double b) { + return c ? (float)a : (float)b; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public static long d2l(boolean c, double a, double b) { + return c ? (long)a : (long)b; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public static float l2f(boolean c, long a, long b) { + return c ? a : b; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public static double l2d(boolean c, long a, long b) { + return c ? a : b; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public static int l2i(boolean c, long a, long b) { + return c ? (int)a : (int)b; + } + + @State(Scope.Benchmark) + public static class BenchState { + public boolean[] bools; + public int[] ints; + public long[] longs; + public float[] floats; + public double[] doubles; + public BenchState() { + + } + + @Setup(Level.Iteration) + public void setup() { + Random random = new Random(1000); + bools = new boolean[SIZE]; + ints = new int[SIZE]; + longs = new long[SIZE]; + floats = new float[SIZE]; + doubles = new double[SIZE]; + + for (int i = 0; i < SIZE; i++) { + bools[i] = random.nextBoolean(); + ints[i] = random.nextInt(100); + longs[i] = random.nextLong(100); + floats[i] = random.nextFloat(100); + doubles[i] = random.nextDouble(100); + } + } + } +} \ No newline at end of file