diff mbox series

[RFC] range-op restructuring

Message ID e6281b22-bf93-fe03-d461-6f597849bf98@redhat.com
State New
Headers show
Series [RFC] range-op restructuring | expand

Commit Message

Andrew MacLeod June 1, 2023, 8:12 p.m. UTC
With the addition of floating point ranges, we did a lot of additional 
class abstraction, then added a bunch more routines for floating point. 
We didn't know how it would look in the end, so we just marched forward 
and got it working.

Now that has settled down a bit, and before we go and add more kinds of 
ranges, I want to visit restructuring the files and provide a better 
dispatch to the range operators from a vrange.

We currently dispatch based on the type of the statement.. int or 
float.  the line is blurred heavily when we have statements that have 
more than one kid of range.. ie

int_value = (int) float_value
   vs
float_value = (float) int_value

Under the current regime, both kinds of casts have to go into the float 
table.. and this is going to get more complicated if we add more 
distinct kinds of ranges. With the current implementation, the floating 
point range operators don't even inherit from range_operator, they are 
their own kind of operator.   The ideal situation is to have a single 
unified range-operator class which has all the combinations, and they 
rest in a single table.  This simplifies numerous things, and avoid us 
having to classify anything in some arbitrary way. It also moves us back 
in the direction of the original vision I had for range-ops.

Ive done an initial rough conversion so you can see what it looks like. 
I've attached a new range-op.h which shows class range-operator will all 
the virtual function combinations. The new dispatch mechniasm buys us 
about 1% speedup in both VRP and in jump_threading.  The new mechanism 
also handles unsupported combinations of operands smoothly, simply 
returning false if its an unsupported combination of aprameters that is 
invoked, which is what a default routine would do.

   As for conversion, lets take operator_not_equal as an example.    The 
end result in range-operator.h is:

class operator_not_equal : public range_operator
{
public:
   bool fold_range (irange &r, tree type, const irange &op1, const 
irange &op2, relation_trio = TRIO_VARYING) const final override;
   bool fold_range (irange &r, tree type, const frange &op1, const 
frange &op2, relation_trio = TRIO_VARYING) const final override;

   bool op1_range (irange &r, tree type, const irange &lhs, const irange 
&op2, relation_trio = TRIO_VARYING) const final override;
   bool op1_range (frange &r, tree type, const irange &lhs, const frange 
&op2, relation_trio = TRIO_VARYING) const final override;

   bool op2_range (frange &r, tree type, const irange &lhs, const frange 
&op1, relation_trio rel = TRIO_VARYING) const final override;
   bool op2_range (irange &r, tree type, const irange &lhs, const irange 
&op1, relation_trio = TRIO_VARYING) const final override;

   relation_kind op1_op2_relation (const irange &lhs) const;
   void update_bitmask (irange &r, const irange &lh, const irange &rh) 
const;
};
extern operator_not_equal rop_NE_EXPR;

When we add a new range type, such as pointers, you simply add the 
required prototypes, add new dispatch codes, and implement them.

This is going  to cause some churn but I am trying to keep it to a 
minimum.  I've been mucking about with it for a couple of weeks, and I 
was thinking to structure it something like this:

range-op.h and range-op.cc  will have the base range_operator class, 
along with the range_op_handler class  we move all the class headers, 
like the above operator_not_equal class into "range-operator.h"

Where the code goes is the biggest struggle. Initially I was going to 
put it all in one file. This would be best as it allows us to co-locate 
all the code for various classes of routines. But that would already be 
about 8000 lines for int and float combined, and will only get larger 
with new range types. I also considered just including all the 
range-op-int.cc and range-op-float.cc files into range-op.cc when it 
compiles, but you still end up with  a big compilation unit.  So this is 
what I'm think now:

We leave all the existing floating point code in range-op-float.cc, and 
then move all the existing integer code into a range-op-int.cc file (or 
I suppose we could even leave it in range-op.cc to avoid extra churn).  
The classes must move to  a header to be accessible from the various 
files which implement them.  This provides for minimal churn. a few 
deletes and renames, and thats it.  I've attached the diff which moves 
operator_not_equal to this form (once some other structuring is in place).

The plus is you can see in the header file range-operator.h exactly what 
is available for any opcode.  Whats less than ideal is that some of the 
routines are in range-op.cc and some are in range-op-float.cc,   
Ultimately, I dont think thats such a big deal as those floating point 
routines often require common infrastructure, such as nan querying that 
integer things dont need.   When we add say a pointer range class,  we 
would create range-op-pointer.cc and all the new stuff required for 
prange would go in that file.

That is the direction I am headed to clean this up, are there any 
objections or other suggestions?

I hope to start with some of the early infrastructure next wee., then 
move on to each individual opcode.

Andrew

PS For the curious, I also decided to preview the first 3 patches which 
move to a unified range_operator class and implements the new dispatch 
system (which is in patch 0003*).   Whats missing is the follow on 
transition to a single unified table which I would do a single opcode at 
a time to ensure that if a problem comes up, we can easily isolate it to 
a specific change.  Once all the opcodes have been converted, and 
everything is in the new unified tree table, then we can simply switch 
to using just that.  Much of the speedup comes from lack of conditions 
when loading an operator.... it is a much more streamlined mechanism.
diff mbox series

Patch

From d5e2d6eb39d0e1c74b1d0e8c389f62f5e22a0640 Mon Sep 17 00:00:00 2001
From: Andrew MacLeod <amacleod@redhat.com>
Date: Wed, 31 May 2023 13:10:31 -0400
Subject: [PATCH 03/11] Provide a new dispatch mechanism for range-ops.

Simplify range_op_handler to have a single range-operator pointer and
provide a more flexible dispatch mechanism for calls via generic vrange
classes.   This is more extensible for adding new classes of range support.

	* gimple-range-op.cc
	(gimple_range_op_handler::gimple_range_op_handler): Adjust.
	(gimple_range_op_handler::maybe_builtin_call): Adjust.
	* gimple-range-op.h (operand1, operand2): Use m_operator.
	* range-op.cc (integral_table, pointer_table): Relocate.
	(get_op_handler): Rename from get_handler and handle all types.
	(range_op_handler::range_op_handler): Relocate.
	(range_op_handler::set_op_handler): Relocate and adjust.
	(range_op_handler::range_op_handler): Relocate.
	(dispatch_trio): New.
	(RO_III, RO_IFI, RO_IFF, RO_FFF, RO_FIF, RO_FII): New consts.
	(range_op_handler::dispatch_kind): New.
	(range_op_handler::fold_range): Relocate and Use new dispatch value.
	(range_op_handler::op1_range): Ditto.
	(range_op_handler::op2_range): Ditto.
	(range_op_handler::lhs_op1_relation): Ditto.
	(range_op_handler::lhs_op2_relation): Ditto.
	(range_op_handler::op1_op2_relation): Ditto.
	(range_op_handler::set_op_handler): Use m_operator member.
	* range-op.h (range_op_handler::operator bool): Use m_operator.
	(range_op_handler::dispatch_kind): New.
	(range_op_handler::m_valid): Delete.
	(range_op_handler::m_int): Delete
	(range_op_handler::m_float): Delete
	(range_op_handler::m_operator): New.
	(range_op_table::operator[]): Relocate from .cc file.
	(range_op_table::set): Ditto.
	* value-range.h (class vrange): Make range_op_handler a friend.
---
 gcc/gimple-range-op.cc |  84 +++-----
 gcc/gimple-range-op.h  |   4 +-
 gcc/range-op.cc        | 474 ++++++++++++++++++++++-------------------
 gcc/range-op.h         |  27 ++-
 gcc/value-range.h      |   1 +
 5 files changed, 310 insertions(+), 280 deletions(-)

diff --git a/gcc/gimple-range-op.cc b/gcc/gimple-range-op.cc
index 0c32a1d2859..727cc2817c7 100644
--- a/gcc/gimple-range-op.cc
+++ b/gcc/gimple-range-op.cc
@@ -144,7 +144,7 @@  gimple_range_op_handler::gimple_range_op_handler (gimple *s)
   if (type)
     set_op_handler (code, type);
 
-  if (m_valid)
+  if (m_operator)
     switch (gimple_code (m_stmt))
       {
 	case GIMPLE_COND:
@@ -152,7 +152,7 @@  gimple_range_op_handler::gimple_range_op_handler (gimple *s)
 	  m_op2 = gimple_cond_rhs (m_stmt);
 	  // Check that operands are supported types.  One check is enough.
 	  if (!Value_Range::supports_type_p (TREE_TYPE (m_op1)))
-	    m_valid = false;
+	    m_operator = NULL;
 	  return;
 	case GIMPLE_ASSIGN:
 	  m_op1 = gimple_range_base_of_assignment (m_stmt);
@@ -171,7 +171,7 @@  gimple_range_op_handler::gimple_range_op_handler (gimple *s)
 	    m_op2 = gimple_assign_rhs2 (m_stmt);
 	  // Check that operands are supported types.  One check is enough.
 	  if ((m_op1 && !Value_Range::supports_type_p (TREE_TYPE (m_op1))))
-	    m_valid = false;
+	    m_operator = NULL;
 	  return;
 	default:
 	  gcc_unreachable ();
@@ -1199,7 +1199,6 @@  gimple_range_op_handler::maybe_non_standard ()
 	gcc_fallthrough ();
 	case WIDEN_MULT_EXPR:
 	{
-	  m_valid = false;
 	  m_op1 = gimple_assign_rhs1 (m_stmt);
 	  m_op2 = gimple_assign_rhs2 (m_stmt);
 	  tree ret = gimple_assign_lhs (m_stmt);
@@ -1216,14 +1215,13 @@  gimple_range_op_handler::maybe_non_standard ()
 	  if ((signed1 ^ signed2) && signed_ret)
 	    return;
 
-	  m_valid = true;
 	  if (signed2 && !signed1)
 	    std::swap (m_op1, m_op2);
 
 	  if (signed1 || signed2)
-	    m_int = signed_op;
+	    m_operator = signed_op;
 	  else
-	    m_int = unsigned_op;
+	    m_operator = unsigned_op;
 	  break;
 	}
 	default:
@@ -1252,47 +1250,41 @@  gimple_range_op_handler::maybe_builtin_call ()
     {
     case CFN_BUILT_IN_CONSTANT_P:
       m_op1 = gimple_call_arg (call, 0);
-      m_valid = true;
       if (irange::supports_p (TREE_TYPE (m_op1)))
-	m_int = &op_cfn_constant_p;
+	m_operator = &op_cfn_constant_p;
       else if (frange::supports_p (TREE_TYPE (m_op1)))
-	m_float = &op_cfn_constant_float_p;
+	m_operator = &op_cfn_constant_float_p;
       else
-	m_valid = false;
+	m_operator = NULL;
       break;
 
     CASE_FLT_FN (CFN_BUILT_IN_SIGNBIT):
       m_op1 = gimple_call_arg (call, 0);
-      m_float = &op_cfn_signbit;
-      m_valid = true;
+      m_operator = &op_cfn_signbit;
       break;
 
     CASE_CFN_COPYSIGN_ALL:
       m_op1 = gimple_call_arg (call, 0);
       m_op2 = gimple_call_arg (call, 1);
-      m_float = &op_cfn_copysign;
-      m_valid = true;
+      m_operator = &op_cfn_copysign;
       break;
 
     CASE_CFN_SQRT:
     CASE_CFN_SQRT_FN:
       m_op1 = gimple_call_arg (call, 0);
-      m_float = &op_cfn_sqrt;
-      m_valid = true;
+      m_operator = &op_cfn_sqrt;
       break;
 
     CASE_CFN_SIN:
     CASE_CFN_SIN_FN:
       m_op1 = gimple_call_arg (call, 0);
-      m_float = &op_cfn_sin;
-      m_valid = true;
+      m_operator = &op_cfn_sin;
       break;
 
     CASE_CFN_COS:
     CASE_CFN_COS_FN:
       m_op1 = gimple_call_arg (call, 0);
-      m_float = &op_cfn_cos;
-      m_valid = true;
+      m_operator = &op_cfn_cos;
       break;
 
     case CFN_BUILT_IN_TOUPPER:
@@ -1300,68 +1292,57 @@  gimple_range_op_handler::maybe_builtin_call ()
       // Only proceed If the argument is compatible with the LHS.
       m_op1 = gimple_call_arg (call, 0);
       if (range_compatible_p (type, TREE_TYPE (m_op1)))
-	{
-	  m_valid = true;
-	  m_int = (func == CFN_BUILT_IN_TOLOWER) ? &op_cfn_tolower
-						 : &op_cfn_toupper;
-	}
+	m_operator = (func == CFN_BUILT_IN_TOLOWER) ? &op_cfn_tolower
+						    : &op_cfn_toupper;
       break;
 
     CASE_CFN_FFS:
       m_op1 = gimple_call_arg (call, 0);
-      m_int = &op_cfn_ffs;
-      m_valid = true;
+      m_operator = &op_cfn_ffs;
       break;
 
     CASE_CFN_POPCOUNT:
       m_op1 = gimple_call_arg (call, 0);
-      m_int = &op_cfn_popcount;
-      m_valid = true;
+      m_operator = &op_cfn_popcount;
       break;
 
     CASE_CFN_CLZ:
       m_op1 = gimple_call_arg (call, 0);
-      m_valid = true;
       if (gimple_call_internal_p (call))
-	m_int = &op_cfn_clz_internal;
+	m_operator = &op_cfn_clz_internal;
       else
-	m_int = &op_cfn_clz;
+	m_operator = &op_cfn_clz;
       break;
 
     CASE_CFN_CTZ:
       m_op1 = gimple_call_arg (call, 0);
-      m_valid = true;
       if (gimple_call_internal_p (call))
-	m_int = &op_cfn_ctz_internal;
+	m_operator = &op_cfn_ctz_internal;
       else
-	m_int = &op_cfn_ctz;
+	m_operator = &op_cfn_ctz;
       break;
 
     CASE_CFN_CLRSB:
       m_op1 = gimple_call_arg (call, 0);
-      m_valid = true;
-      m_int = &op_cfn_clrsb;
+      m_operator = &op_cfn_clrsb;
       break;
 
     case CFN_UBSAN_CHECK_ADD:
       m_op1 = gimple_call_arg (call, 0);
       m_op2 = gimple_call_arg (call, 1);
-      m_valid = true;
-      m_int = &op_cfn_ubsan_add;
+      m_operator = &op_cfn_ubsan_add;
       break;
 
     case CFN_UBSAN_CHECK_SUB:
       m_op1 = gimple_call_arg (call, 0);
       m_op2 = gimple_call_arg (call, 1);
-      m_valid = true;
-      m_int = &op_cfn_ubsan_sub;
+      m_operator = &op_cfn_ubsan_sub;
       break;
 
     case CFN_UBSAN_CHECK_MUL:
       m_op1 = gimple_call_arg (call, 0);
       m_op2 = gimple_call_arg (call, 1);
-      m_valid = true;
-      m_int = &op_cfn_ubsan_mul;
+      m_operator = &op_cfn_ubsan_mul;
       break;
 
     case CFN_BUILT_IN_STRLEN:
@@ -1371,8 +1352,7 @@  gimple_range_op_handler::maybe_builtin_call ()
 					 == TYPE_PRECISION (TREE_TYPE (lhs))))
 	  {
 	    m_op1 = gimple_call_arg (call, 0);
-	    m_valid = true;
-	    m_int = &op_cfn_strlen;
+	    m_operator = &op_cfn_strlen;
 	  }
 	break;
       }
@@ -1384,21 +1364,18 @@  gimple_range_op_handler::maybe_builtin_call ()
       // This call will ensure all the asserts are triggered.
       oacc_get_ifn_dim_arg (call);
       m_op1 = gimple_call_arg (call, 0);
-      m_valid = true;
-      m_int = &op_cfn_goacc_dim_size;
+      m_operator = &op_cfn_goacc_dim_size;
       break;
 
     case CFN_GOACC_DIM_POS:
       // This call will ensure all the asserts are triggered.
       oacc_get_ifn_dim_arg (call);
       m_op1 = gimple_call_arg (call, 0);
-      m_valid = true;
-      m_int = &op_cfn_goacc_dim_pos;
+      m_operator = &op_cfn_goacc_dim_pos;
       break;
 
     CASE_CFN_PARITY:
-      m_valid = true;
-      m_int = &op_cfn_parity;
+      m_operator = &op_cfn_parity;
       break;
 
     default:
@@ -1406,9 +1383,8 @@  gimple_range_op_handler::maybe_builtin_call ()
 	unsigned arg;
 	if (gimple_call_fnspec (call).returns_arg (&arg) && arg == 0)
 	  {
-	    m_valid = true;
 	    m_op1 = gimple_call_arg (call, 0);
-	    m_int = &op_cfn_pass_through_arg1;
+	    m_operator = &op_cfn_pass_through_arg1;
 	  }
 	break;
       }
diff --git a/gcc/gimple-range-op.h b/gcc/gimple-range-op.h
index 1bf63c5ce6f..e7bb0095440 100644
--- a/gcc/gimple-range-op.h
+++ b/gcc/gimple-range-op.h
@@ -32,8 +32,8 @@  public:
   gimple_range_op_handler (gimple *s);
   inline gimple *stmt () const { return m_stmt; }
   inline tree lhs () const { return gimple_get_lhs (m_stmt); }
-  tree operand1 () const { gcc_checking_assert (m_valid); return m_op1; }
-  tree operand2 () const { gcc_checking_assert (m_valid); return m_op2; }
+  tree operand1 () const { gcc_checking_assert (m_operator); return m_op1; }
+  tree operand2 () const { gcc_checking_assert (m_operator); return m_op2; }
   bool calc_op1 (vrange &r, const vrange &lhs_range);
   bool calc_op1 (vrange &r, const vrange &lhs_range, const vrange &op2_range,
 		 relation_trio = TRIO_VARYING);
diff --git a/gcc/range-op.cc b/gcc/range-op.cc
index 203c30f6e94..b24832922f0 100644
--- a/gcc/range-op.cc
+++ b/gcc/range-op.cc
@@ -48,6 +48,260 @@  along with GCC; see the file COPYING3.  If not see
 #include "range-op.h"
 #include "tree-ssa-ccp.h"
 
+// Instantiate a range op table for integral operations.
+
+class integral_table : public range_op_table
+{
+public:
+  integral_table ();
+} integral_tree_table;
+
+// Instantiate a range op table for pointer operations.
+
+class pointer_table : public range_op_table
+{
+public:
+  pointer_table ();
+} pointer_tree_table;
+
+
+// The tables are hidden and accessed via a simple extern function.
+
+range_operator *
+get_op_handler (enum tree_code code, tree type)
+{
+  // First check if there is a pointer specialization.
+  if (POINTER_TYPE_P (type))
+    return pointer_tree_table[code];
+  if (INTEGRAL_TYPE_P (type))
+    return integral_tree_table[code];
+  if (frange::supports_p (type))
+    return (*floating_tree_table)[code];
+  return NULL;
+}
+
+range_op_handler::range_op_handler ()
+{
+  m_operator = NULL;
+}
+
+void
+range_op_handler::set_op_handler (tree_code code, tree type)
+{
+  m_operator = get_op_handler (code, type);
+}
+
+range_op_handler::range_op_handler (tree_code code, tree type)
+{
+  set_op_handler (code, type);
+}
+
+// Create a dispatch pattern for value range discriminators LHS, OP1, and OP2.
+// This is used to produce a unique value for each dispatch pattern.  The
+// denser the values, the more efficient dispatch can be created via
+// jump tables.
+// Ensure the shifts are appropriate for the number of range types supported.
+// Current only INT and FLOAT are supported, so a shift of 1 is sufficient
+// producing a value of 0-7 value for dispatching.
+
+constexpr unsigned
+dispatch_trio (unsigned lhs, unsigned op1, unsigned op2)
+{
+  return ((lhs << 2) + (op1 << 1) + (op2));
+}
+
+// These are the supported dispatch patterns. These map to the parameter list
+// of the routines in range_operator.  Note the last 3 characters are
+// shorthand for the LHS, OP1, and OP2 range discriminator class.
+
+const unsigned RO_III =	dispatch_trio (VR_IRANGE, VR_IRANGE, VR_IRANGE);
+const unsigned RO_IFI = dispatch_trio (VR_IRANGE, VR_FRANGE, VR_IRANGE);
+const unsigned RO_IFF = dispatch_trio (VR_IRANGE, VR_FRANGE, VR_FRANGE);
+const unsigned RO_FFF = dispatch_trio (VR_FRANGE, VR_FRANGE, VR_FRANGE);
+const unsigned RO_FIF = dispatch_trio (VR_FRANGE, VR_IRANGE, VR_FRANGE);
+const unsigned RO_FII = dispatch_trio (VR_FRANGE, VR_IRANGE, VR_IRANGE);
+
+// Return a dispatch value for parameter types LHS, OP1 and OP2.
+
+unsigned
+range_op_handler::dispatch_kind (const vrange &lhs, const vrange &op1,
+				 const vrange& op2) const
+{
+  return dispatch_trio (lhs.m_discriminator, op1.m_discriminator,
+			op2.m_discriminator);
+}
+
+// Dispatch a call to fold_range based on the types of R, LH and RH.
+
+bool
+range_op_handler::fold_range (vrange &r, tree type,
+			      const vrange &lh,
+			      const vrange &rh,
+			      relation_trio rel) const
+{
+  gcc_checking_assert (m_operator);
+  switch (dispatch_kind (r, lh, rh))
+    {
+      case RO_III:
+	return m_operator->fold_range (as_a <irange> (r), type,
+				       as_a <irange> (lh),
+				       as_a <irange> (rh), rel);
+      case RO_IFI:
+	return m_operator->fold_range (as_a <irange> (r), type,
+				       as_a <frange> (lh),
+				       as_a <irange> (rh), rel);
+      case RO_IFF:
+	return m_operator->fold_range (as_a <irange> (r), type,
+				       as_a <frange> (lh),
+				       as_a <frange> (rh), rel);
+      case RO_FFF:
+	return m_operator->fold_range (as_a <frange> (r), type,
+				       as_a <frange> (lh),
+				       as_a <frange> (rh), rel);
+      default:
+	return false;
+    }
+}
+
+// Dispatch a call to op1_range based on the types of R, LHS and OP2.
+
+bool
+range_op_handler::op1_range (vrange &r, tree type,
+			     const vrange &lhs,
+			     const vrange &op2,
+			     relation_trio rel) const
+{
+  gcc_checking_assert (m_operator);
+
+  if (lhs.undefined_p ())
+    return false;
+  switch (dispatch_kind (r, lhs, op2))
+    {
+      case RO_III:
+	return m_operator->op1_range (as_a <irange> (r), type,
+				      as_a <irange> (lhs),
+				      as_a <irange> (op2), rel);
+      case RO_FIF:
+	return m_operator->op1_range (as_a <frange> (r), type,
+				      as_a <irange> (lhs),
+				      as_a <frange> (op2), rel);
+      case RO_FFF:
+	return m_operator->op1_range (as_a <frange> (r), type,
+				      as_a <frange> (lhs),
+				      as_a <frange> (op2), rel);
+      default:
+	return false;
+    }
+}
+
+// Dispatch a call to op2_range based on the types of R, LHS and OP1.
+
+bool
+range_op_handler::op2_range (vrange &r, tree type,
+			     const vrange &lhs,
+			     const vrange &op1,
+			     relation_trio rel) const
+{
+  gcc_checking_assert (m_operator);
+  if (lhs.undefined_p ())
+    return false;
+
+  switch (dispatch_kind (r, lhs, op1))
+    {
+      case RO_III:
+	return m_operator->op2_range (as_a <irange> (r), type,
+				      as_a <irange> (lhs),
+				      as_a <irange> (op1), rel);
+      case RO_FIF:
+	return m_operator->op2_range (as_a <frange> (r), type,
+				      as_a <irange> (lhs),
+				      as_a <frange> (op1), rel);
+      case RO_FFF:
+	return m_operator->op2_range (as_a <frange> (r), type,
+				      as_a <frange> (lhs),
+				      as_a <frange> (op1), rel);
+      default:
+	return false;
+    }
+}
+
+// Dispatch a call to lhs_op1_relation based on the types of LHS, OP1 and OP2.
+
+relation_kind
+range_op_handler::lhs_op1_relation (const vrange &lhs,
+				    const vrange &op1,
+				    const vrange &op2,
+				    relation_kind rel) const
+{
+  gcc_checking_assert (m_operator);
+
+  switch (dispatch_kind (lhs, op1, op2))
+    {
+      case RO_III:
+	return m_operator->lhs_op1_relation (as_a <irange> (lhs),
+					     as_a <irange> (op1),
+					     as_a <irange> (op2), rel);
+      case RO_IFF:
+	return m_operator->lhs_op1_relation (as_a <irange> (lhs),
+					     as_a <frange> (op1),
+					     as_a <frange> (op2), rel);
+      case RO_FFF:
+	return m_operator->lhs_op1_relation (as_a <frange> (lhs),
+					     as_a <frange> (op1),
+					     as_a <frange> (op2), rel);
+      default:
+	return VREL_VARYING;
+    }
+}
+
+// Dispatch a call to lhs_op2_relation based on the types of LHS, OP1 and OP2.
+
+relation_kind
+range_op_handler::lhs_op2_relation (const vrange &lhs,
+				    const vrange &op1,
+				    const vrange &op2,
+				    relation_kind rel) const
+{
+  gcc_checking_assert (m_operator);
+  switch (dispatch_kind (lhs, op1, op2))
+    {
+      case RO_III:
+	return m_operator->lhs_op2_relation (as_a <irange> (lhs),
+					     as_a <irange> (op1),
+					     as_a <irange> (op2), rel);
+      case RO_IFF:
+	return m_operator->lhs_op2_relation (as_a <irange> (lhs),
+					     as_a <frange> (op1),
+					     as_a <frange> (op2), rel);
+      case RO_FFF:
+	return m_operator->lhs_op2_relation (as_a <frange> (lhs),
+					     as_a <frange> (op1),
+					     as_a <frange> (op2), rel);
+      default:
+	return VREL_VARYING;
+    }
+}
+
+// Dispatch a call to op1_op2_relation based on the type of LHS.
+
+relation_kind
+range_op_handler::op1_op2_relation (const vrange &lhs) const
+{
+  gcc_checking_assert (m_operator);
+  switch (dispatch_kind (lhs, lhs, lhs))
+    {
+      case RO_III:
+	return m_operator->op1_op2_relation (as_a <irange> (lhs));
+
+      case RO_FFF:
+	return m_operator->op1_op2_relation (as_a <frange> (lhs));
+
+      default:
+	return VREL_VARYING;
+    }
+}
+
+
 // Convert irange bitmasks into a VALUE MASK pair suitable for calling CCP.
 
 static void
@@ -4612,33 +4866,6 @@  pointer_or_operator::wi_fold (irange &r, tree type,
     r.set_varying (type);
 }
 
-// Return a pointer to the range_operator instance, if there is one
-// associated with tree_code CODE.
-
-range_operator *
-range_op_table::operator[] (enum tree_code code)
-{
-  gcc_checking_assert (code > 0 && code < MAX_TREE_CODES);
-  return m_range_tree[code];
-}
-
-// Add OP to the handler table for CODE.
-
-void
-range_op_table::set (enum tree_code code, range_operator &op)
-{
-  gcc_checking_assert (m_range_tree[code] == NULL);
-  m_range_tree[code] = &op;
-}
-
-// Instantiate a range op table for integral operations.
-
-class integral_table : public range_op_table
-{
-public:
-  integral_table ();
-} integral_tree_table;
-
 integral_table::integral_table ()
 {
   set (EQ_EXPR, op_equal);
@@ -4682,14 +4909,6 @@  integral_table::integral_table ()
   set (ADDR_EXPR, op_addr);
 }
 
-// Instantiate a range op table for pointer operations.
-
-class pointer_table : public range_op_table
-{
-public:
-  pointer_table ();
-} pointer_tree_table;
-
 pointer_table::pointer_table ()
 {
   set (BIT_AND_EXPR, op_pointer_and);
@@ -4714,191 +4933,6 @@  pointer_table::pointer_table ()
   set (BIT_XOR_EXPR, op_bitwise_xor);
 }
 
-// The tables are hidden and accessed via a simple extern function.
-
-static inline range_operator *
-get_handler (enum tree_code code, tree type)
-{
-  // First check if there is a pointer specialization.
-  if (POINTER_TYPE_P (type))
-    return pointer_tree_table[code];
-  if (INTEGRAL_TYPE_P (type))
-    return integral_tree_table[code];
-  return NULL;
-}
-
-// Return the floating point operator for CODE or NULL if none available.
-
-static inline range_operator *
-get_float_handler (enum tree_code code, tree)
-{
-  return (*floating_tree_table)[code];
-}
-
-void
-range_op_handler::set_op_handler (tree_code code, tree type)
-{
-  if (irange::supports_p (type))
-    {
-      m_float = NULL;
-      m_int = get_handler (code, type);
-      m_valid = m_int != NULL;
-    }
-  else if (frange::supports_p (type))
-    {
-      m_int = NULL;
-      m_float = get_float_handler (code, type);
-      m_valid = m_float != NULL;
-    }
-  else
-    {
-      m_int = NULL;
-      m_float = NULL;
-      m_valid = false;
-    }
-}
-
-range_op_handler::range_op_handler ()
-{
-  m_int = NULL;
-  m_float = NULL;
-  m_valid = false;
-}
-
-range_op_handler::range_op_handler (tree_code code, tree type)
-{
-  set_op_handler (code, type);
-}
-
-
-bool
-range_op_handler::fold_range (vrange &r, tree type,
-			      const vrange &lh,
-			      const vrange &rh,
-			      relation_trio rel) const
-{
-  gcc_checking_assert (m_valid);
-  if (m_int)
-    return m_int->fold_range (as_a <irange> (r), type,
-			   as_a <irange> (lh),
-			   as_a <irange> (rh), rel);
-
-  if (is_a <irange> (r))
-    {
-      if (is_a <irange> (rh))
-	return m_float->fold_range (as_a <irange> (r), type,
-				    as_a <frange> (lh),
-				    as_a <irange> (rh), rel);
-      else
-	return m_float->fold_range (as_a <irange> (r), type,
-				    as_a <frange> (lh),
-				    as_a <frange> (rh), rel);
-    }
-  return m_float->fold_range (as_a <frange> (r), type,
-			      as_a <frange> (lh),
-			      as_a <frange> (rh), rel);
-}
-
-bool
-range_op_handler::op1_range (vrange &r, tree type,
-			     const vrange &lhs,
-			     const vrange &op2,
-			     relation_trio rel) const
-{
-  gcc_checking_assert (m_valid);
-
-  if (lhs.undefined_p ())
-    return false;
-  if (m_int)
-    return m_int->op1_range (as_a <irange> (r), type,
-			     as_a <irange> (lhs),
-			     as_a <irange> (op2), rel);
-
-  if (is_a <irange> (lhs))
-    return m_float->op1_range (as_a <frange> (r), type,
-			       as_a <irange> (lhs),
-			       as_a <frange> (op2), rel);
-  return m_float->op1_range (as_a <frange> (r), type,
-			     as_a <frange> (lhs),
-			     as_a <frange> (op2), rel);
-}
-
-bool
-range_op_handler::op2_range (vrange &r, tree type,
-			     const vrange &lhs,
-			     const vrange &op1,
-			     relation_trio rel) const
-{
-  gcc_checking_assert (m_valid);
-  if (lhs.undefined_p ())
-    return false;
-  if (m_int)
-    return m_int->op2_range (as_a <irange> (r), type,
-			     as_a <irange> (lhs),
-			     as_a <irange> (op1), rel);
-
-  if (is_a <irange> (lhs))
-    return m_float->op2_range (as_a <frange> (r), type,
-			       as_a <irange> (lhs),
-			       as_a <frange> (op1), rel);
-  return m_float->op2_range (as_a <frange> (r), type,
-			     as_a <frange> (lhs),
-			     as_a <frange> (op1), rel);
-}
-
-relation_kind
-range_op_handler::lhs_op1_relation (const vrange &lhs,
-				    const vrange &op1,
-				    const vrange &op2,
-				    relation_kind rel) const
-{
-  gcc_checking_assert (m_valid);
-  if (m_int)
-    return m_int->lhs_op1_relation (as_a <irange> (lhs),
-				    as_a <irange> (op1),
-				    as_a <irange> (op2), rel);
-
-  if (is_a <irange> (lhs))
-    return m_float->lhs_op1_relation (as_a <irange> (lhs),
-				 as_a <frange> (op1),
-				 as_a <frange> (op2), rel);
-  return m_float->lhs_op1_relation (as_a <frange> (lhs),
-			       as_a <frange> (op1),
-			       as_a <frange> (op2), rel);
-}
-
-relation_kind
-range_op_handler::lhs_op2_relation (const vrange &lhs,
-				    const vrange &op1,
-				    const vrange &op2,
-				    relation_kind rel) const
-{
-  gcc_checking_assert (m_valid);
-  if (m_int)
-    return m_int->lhs_op2_relation (as_a <irange> (lhs),
-				    as_a <irange> (op1),
-				    as_a <irange> (op2), rel);
-
-  if (is_a <irange> (lhs))
-    return m_float->lhs_op2_relation (as_a <irange> (lhs),
-				      as_a <frange> (op1),
-				      as_a <frange> (op2), rel);
-  return m_float->lhs_op2_relation (as_a <frange> (lhs),
-				      as_a <frange> (op1),
-				      as_a <frange> (op2), rel);
-}
-
-relation_kind
-range_op_handler::op1_op2_relation (const vrange &lhs) const
-{
-  gcc_checking_assert (m_valid);
-  if (m_int)
-    return m_int->op1_op2_relation (as_a <irange> (lhs));
-  if (is_a <irange> (lhs))
-    return m_float->op1_op2_relation (as_a <irange> (lhs));
-  return m_float->op1_op2_relation (as_a <frange> (lhs));
-}
-
 // Cast the range in R to TYPE.
 
 bool
diff --git a/gcc/range-op.h b/gcc/range-op.h
index cad16f4cd20..7af58736c3f 100644
--- a/gcc/range-op.h
+++ b/gcc/range-op.h
@@ -186,7 +186,7 @@  class range_op_handler
 public:
   range_op_handler ();
   range_op_handler (enum tree_code code, tree type);
-  inline operator bool () const { return m_valid; }
+  inline operator bool () const { return m_operator != NULL; }
 
   bool fold_range (vrange &r, tree type,
 		   const vrange &lh,
@@ -210,10 +210,10 @@  public:
 				  relation_kind = VREL_VARYING) const;
   relation_kind op1_op2_relation (const vrange &lhs) const;
 protected:
+  unsigned dispatch_kind (const vrange &lhs, const vrange &op1,
+			  const vrange& op2) const;
   void set_op_handler (enum tree_code code, tree type);
-  bool m_valid;
-  range_operator *m_int;
-  range_operator *m_float;
+  range_operator *m_operator;
 };
 
 extern bool range_cast (vrange &, tree type);
@@ -296,6 +296,25 @@  private:
 };
 
 
+// Return a pointer to the range_operator instance, if there is one
+// associated with tree_code CODE.
+
+inline range_operator *
+range_op_table::operator[] (enum tree_code code)
+{
+  gcc_checking_assert (code > 0 && code < MAX_TREE_CODES);
+  return m_range_tree[code];
+}
+
+// Add OP to the handler table for CODE.
+
+inline void
+range_op_table::set (enum tree_code code, range_operator &op)
+{
+  gcc_checking_assert (m_range_tree[code] == NULL);
+  m_range_tree[code] = &op;
+}
+
 // This holds the range op table for floating point operations.
 extern range_op_table *floating_tree_table;
 
diff --git a/gcc/value-range.h b/gcc/value-range.h
index 2b4ebabe7c8..9103e9c41c7 100644
--- a/gcc/value-range.h
+++ b/gcc/value-range.h
@@ -77,6 +77,7 @@  class GTY((user)) vrange
   template <typename T> friend bool is_a (vrange &);
   friend class Value_Range;
   friend void streamer_write_vrange (struct output_block *, const vrange &);
+  friend class range_op_handler;
 public:
   virtual void accept (const class vrange_visitor &v) const = 0;
   virtual void set (tree, tree, value_range_kind = VR_RANGE);
-- 
2.40.1