# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Functions for proving mathematical properties of expressions."""

import math
import fractions
import operator

from compiler.util import ir_data
from compiler.util import ir_data_utils
from compiler.util import ir_util
from compiler.util import traverse_ir


# Create a local alias for math.gcd with a fallback to fractions.gcd if it is
# not available. This can be dropped if pre-3.5 Python support is dropped.
if hasattr(math, 'gcd'):
  _math_gcd = math.gcd
else:
  _math_gcd = fractions.gcd


def compute_constraints_of_expression(expression, ir):
  """Adds appropriate bounding constraints to the given expression."""
  if ir_util.is_constant_type(expression.type):
    return
  expression_variety = expression.WhichOneof("expression")
  if expression_variety == "constant":
    _compute_constant_value_of_constant(expression)
  elif expression_variety == "constant_reference":
    _compute_constant_value_of_constant_reference(expression, ir)
  elif expression_variety == "function":
    _compute_constraints_of_function(expression, ir)
  elif expression_variety == "field_reference":
    _compute_constraints_of_field_reference(expression, ir)
  elif expression_variety == "builtin_reference":
    _compute_constraints_of_builtin_value(expression)
  elif expression_variety == "boolean_constant":
    _compute_constant_value_of_boolean_constant(expression)
  else:
    assert False, "Unknown expression variety {!r}".format(expression_variety)
  if expression.type.WhichOneof("type") == "integer":
    _assert_integer_constraints(expression)


def _compute_constant_value_of_constant(expression):
  value = expression.constant.value
  expression.type.integer.modular_value = value
  expression.type.integer.minimum_value = value
  expression.type.integer.maximum_value = value
  expression.type.integer.modulus = "infinity"


def _compute_constant_value_of_constant_reference(expression, ir):
  referred_object = ir_util.find_object(
      expression.constant_reference.canonical_name, ir)
  expression = ir_data_utils.builder(expression)
  if isinstance(referred_object, ir_data.EnumValue):
    compute_constraints_of_expression(referred_object.value, ir)
    assert ir_util.is_constant(referred_object.value)
    new_value = str(ir_util.constant_value(referred_object.value))
    expression.type.enumeration.value = new_value
  elif isinstance(referred_object, ir_data.Field):
    assert ir_util.field_is_virtual(referred_object), (
        "Non-virtual non-enum-value constant reference should have been caught "
        "in type_check.py")
    compute_constraints_of_expression(referred_object.read_transform, ir)
    expression.type.CopyFrom(referred_object.read_transform.type)
  else:
    assert False, "Unexpected constant reference type."


def _compute_constraints_of_function(expression, ir):
  """Computes the known constraints of the result of a function."""
  for arg in expression.function.args:
    compute_constraints_of_expression(arg, ir)
  op = expression.function.function
  if op in (ir_data.FunctionMapping.ADDITION, ir_data.FunctionMapping.SUBTRACTION):
    _compute_constraints_of_additive_operator(expression)
  elif op == ir_data.FunctionMapping.MULTIPLICATION:
    _compute_constraints_of_multiplicative_operator(expression)
  elif op in (ir_data.FunctionMapping.EQUALITY, ir_data.FunctionMapping.INEQUALITY,
              ir_data.FunctionMapping.LESS, ir_data.FunctionMapping.LESS_OR_EQUAL,
              ir_data.FunctionMapping.GREATER, ir_data.FunctionMapping.GREATER_OR_EQUAL,
              ir_data.FunctionMapping.AND, ir_data.FunctionMapping.OR):
    _compute_constant_value_of_comparison_operator(expression)
  elif op == ir_data.FunctionMapping.CHOICE:
    _compute_constraints_of_choice_operator(expression)
  elif op == ir_data.FunctionMapping.MAXIMUM:
    _compute_constraints_of_maximum_function(expression)
  elif op == ir_data.FunctionMapping.PRESENCE:
    _compute_constraints_of_existence_function(expression, ir)
  elif op in (ir_data.FunctionMapping.UPPER_BOUND, ir_data.FunctionMapping.LOWER_BOUND):
    _compute_constraints_of_bound_function(expression)
  else:
    assert False, "Unknown operator {!r}".format(op)


def _compute_constraints_of_existence_function(expression, ir):
  """Computes the constraints of a $has(field) expression."""
  field_path = expression.function.args[0].field_reference.path[-1]
  field = ir_util.find_object(field_path, ir)
  compute_constraints_of_expression(field.existence_condition, ir)
  ir_data_utils.builder(expression).type.CopyFrom(field.existence_condition.type)


def _compute_constraints_of_field_reference(expression, ir):
  """Computes the constraints of a reference to a structure's field."""
  field_path = expression.field_reference.path[-1]
  field = ir_util.find_object(field_path, ir)
  if isinstance(field, ir_data.Field) and ir_util.field_is_virtual(field):
    # References to virtual fields should have the virtual field's constraints
    # copied over.
    compute_constraints_of_expression(field.read_transform, ir)
    ir_data_utils.builder(expression).type.CopyFrom(field.read_transform.type)
    return
  # Non-virtual non-integer fields do not (yet) have constraints.
  if expression.type.WhichOneof("type") == "integer":
    # TODO(bolms): These lines will need to change when support is added for
    # fixed-point types.
    expression.type.integer.modulus = "1"
    expression.type.integer.modular_value = "0"
    type_definition = ir_util.find_parent_object(field_path, ir)
    if isinstance(field, ir_data.Field):
      referrent_type = field.type
    else:
      referrent_type = field.physical_type_alias
    if referrent_type.HasField("size_in_bits"):
      type_size = ir_util.constant_value(referrent_type.size_in_bits)
    else:
      field_size = ir_util.constant_value(field.location.size)
      if field_size is None:
        type_size = None
      else:
        type_size = field_size * type_definition.addressable_unit
    assert referrent_type.HasField("atomic_type"), field
    assert not referrent_type.atomic_type.reference.canonical_name.module_file
    _set_integer_constraints_from_physical_type(
        expression, referrent_type, type_size)


def _set_integer_constraints_from_physical_type(
    expression, physical_type, type_size):
  """Copies the integer constraints of an expression from a physical type."""
  # SCAFFOLDING HACK: In order to keep changelists manageable, this hardcodes
  # the ranges for all of the Emboss Prelude integer types.   This would break
  # any user-defined `external` integer types, but that feature isn't fully
  # implemented in the C++ backend, so it doesn't matter for now.
  #
  # Adding the attribute(s) for integer bounds will require new operators:
  # integer/flooring division, remainder, and exponentiation (2**N, 10**N).
  #
  # (Technically, there are a few sets of operators that would work: for
  # example, just the choice operator `?:` is sufficient, but very ugly.
  # Bitwise AND, bitshift, and exponentiation would also work, but `10**($bits
  # >> 2) * 2**($bits & 0b11) - 1` isn't quite as clear as `10**($bits // 4) *
  # 2**($bits % 4) - 1`, in my (bolms@) opinion.)
  #
  # TODO(bolms): Add a scheme for defining integer bounds on user-defined
  # external types.
  if type_size is None:
    # If the type_size is unknown, then we can't actually say anything about the
    # minimum and maximum values of the type.  For UInt, Int, and Bcd, an error
    # will be thrown during the constraints check stage.
    expression.type.integer.minimum_value = "-infinity"
    expression.type.integer.maximum_value = "infinity"
    return
  name = tuple(physical_type.atomic_type.reference.canonical_name.object_path)
  if name == ("UInt",):
    expression.type.integer.minimum_value = "0"
    expression.type.integer.maximum_value = str(2**type_size - 1)
  elif name == ("Int",):
    expression.type.integer.minimum_value = str(-(2**(type_size - 1)))
    expression.type.integer.maximum_value = str(2**(type_size - 1) - 1)
  elif name == ("Bcd",):
    expression.type.integer.minimum_value = "0"
    expression.type.integer.maximum_value = str(
        10**(type_size // 4) * 2**(type_size % 4) - 1)
  else:
    assert False, "Unknown integral type " + ".".join(name)


def _compute_constraints_of_parameter(parameter):
  if parameter.type.WhichOneof("type") == "integer":
    type_size = ir_util.constant_value(
        parameter.physical_type_alias.size_in_bits)
    _set_integer_constraints_from_physical_type(
        parameter, parameter.physical_type_alias, type_size)


def _compute_constraints_of_builtin_value(expression):
  """Computes the constraints of a builtin (like $static_size_in_bits)."""
  name = expression.builtin_reference.canonical_name.object_path[0]
  if name == "$static_size_in_bits":
    expression.type.integer.modulus = "1"
    expression.type.integer.modular_value = "0"
    expression.type.integer.minimum_value = "0"
    # The maximum theoretically-supported size of something is 2**64 bytes,
    # which is 2**64 * 8 bits.
    #
    # Really, $static_size_in_bits is only valid in expressions that have to be
    # evaluated at compile time anyway, so it doesn't really matter if the
    # bounds are excessive.
    expression.type.integer.maximum_value = "infinity"
  elif name == "$is_statically_sized":
    # No bounds on a boolean variable.
    pass
  elif name == "$logical_value":
    # $logical_value is the placeholder used in inferred write-through
    # transformations.
    #
    # Only integers (currently) have "real" write-through transformations, but
    # fields that would otherwise be straight aliases, but which have a
    # [requires] attribute, are elevated to write-through fields, so that the
    # [requires] clause can be checked in Write, CouldWriteValue, TryToWrite,
    # Read, and Ok.
    if expression.type.WhichOneof("type") == "integer":
      assert expression.type.integer.modulus
      assert expression.type.integer.modular_value
      assert expression.type.integer.minimum_value
      assert expression.type.integer.maximum_value
    elif expression.type.WhichOneof("type") == "enumeration":
      assert expression.type.enumeration.name
    elif expression.type.WhichOneof("type") == "boolean":
      pass
    else:
      assert False, "Unexpected type for $logical_value"
  else:
    assert False, "Unknown builtin " + name


def _compute_constant_value_of_boolean_constant(expression):
  expression.type.boolean.value = expression.boolean_constant.value


def _add(a, b):
  """Adds a and b, where a and b are ints, "infinity", or "-infinity"."""
  if a in ("infinity", "-infinity"):
    a, b = b, a
  if b == "infinity":
    assert a != "-infinity"
    return "infinity"
  if b == "-infinity":
    assert a != "infinity"
    return "-infinity"
  return int(a) + int(b)


def _sub(a, b):
  """Subtracts b from a, where a and b are ints, "infinity", or "-infinity"."""
  if b == "infinity":
    return _add(a, "-infinity")
  if b == "-infinity":
    return _add(a, "infinity")
  return _add(a, -int(b))


def _sign(a):
  """Returns 1 if a > 0, 0 if a == 0, and -1 if a < 0."""
  if a == "infinity":
    return 1
  if a == "-infinity":
    return -1
  if int(a) > 0:
    return 1
  if int(a) < 0:
    return -1
  return 0


def _mul(a, b):
  """Multiplies a and b, where a and b are ints, "infinity", or "-infinity"."""
  if _is_infinite(a):
    a, b = b, a
  if _is_infinite(b):
    sign = _sign(a) * _sign(b)
    if sign > 0:
      return "infinity"
    if sign < 0:
      return "-infinity"
    return 0
  return int(a) * int(b)


def _is_infinite(a):
  return a in ("infinity", "-infinity")


def _max(a):
  """Returns max of a, where elements are ints, "infinity", or "-infinity"."""
  if any(n == "infinity" for n in a):
    return "infinity"
  if all(n == "-infinity" for n in a):
    return "-infinity"
  return max(int(n) for n in a if not _is_infinite(n))


def _min(a):
  """Returns min of a, where elements are ints, "infinity", or "-infinity"."""
  if any(n == "-infinity" for n in a):
    return "-infinity"
  if all(n == "infinity" for n in a):
    return "infinity"
  return min(int(n) for n in a if not _is_infinite(n))


def _compute_constraints_of_additive_operator(expression):
  """Computes the modular value of an additive expression."""
  funcs = {
      ir_data.FunctionMapping.ADDITION: _add,
      ir_data.FunctionMapping.SUBTRACTION: _sub,
  }
  func = funcs[expression.function.function]
  args = expression.function.args
  for arg in args:
    assert arg.type.integer.modular_value, str(expression)
  left, right = args
  unadjusted_modular_value = func(left.type.integer.modular_value,
                                  right.type.integer.modular_value)
  new_modulus = _greatest_common_divisor(left.type.integer.modulus,
                                         right.type.integer.modulus)
  expression.type.integer.modulus = str(new_modulus)
  if new_modulus == "infinity":
    expression.type.integer.modular_value = str(unadjusted_modular_value)
  else:
    expression.type.integer.modular_value = str(unadjusted_modular_value %
                                                new_modulus)
  lmax = left.type.integer.maximum_value
  lmin = left.type.integer.minimum_value
  if expression.function.function == ir_data.FunctionMapping.SUBTRACTION:
    rmax = right.type.integer.minimum_value
    rmin = right.type.integer.maximum_value
  else:
    rmax = right.type.integer.maximum_value
    rmin = right.type.integer.minimum_value
  expression.type.integer.minimum_value = str(func(lmin, rmin))
  expression.type.integer.maximum_value = str(func(lmax, rmax))


def _compute_constraints_of_multiplicative_operator(expression):
  """Computes the modular value of a multiplicative expression."""
  bounds = [arg.type.integer for arg in expression.function.args]

  # The minimum and maximum values can come from any of the four pairings of
  # (left min, left max) with (right min, right max), depending on the signs and
  # magnitudes of the minima and maxima.  E.g.:
  #
  # max = left max * right max: [ 2,  3] * [ 2,  3]
  # max = left min * right min: [-3, -2] * [-3, -2]
  # max = left max * right min: [-3, -2] * [ 2,  3]
  # max = left min * right max: [ 2,  3] * [-3, -2]
  # max = left max * right max: [-2,  3] * [-2,  3]
  # max = left min * right min: [-3,  2] * [-3,  2]
  #
  # For uncorrelated multiplication, the minimum and maximum will always come
  # from multiplying one extreme by another: if x is nonzero, then
  #
  #     (y + e) * x > y * x  ||  (y - e) * x > y * x
  #
  # for arbitrary nonzero e, so the extrema can only occur when we either cannot
  # add or cannot subtract e.
  #
  # Correlated multiplication (e.g., `x * x`) can have tighter bounds, but
  # Emboss is not currently trying to be that smart.
  lmin, lmax = bounds[0].minimum_value, bounds[0].maximum_value
  rmin, rmax = bounds[1].minimum_value, bounds[1].maximum_value
  extrema = [_mul(lmax, rmax), _mul(lmin, rmax),  #
             _mul(lmax, rmin), _mul(lmin, rmin)]
  expression.type.integer.minimum_value = str(_min(extrema))
  expression.type.integer.maximum_value = str(_max(extrema))

  if all(bound.modulus == "infinity" for bound in bounds):
    # If both sides are constant, the result is constant.
    expression.type.integer.modulus = "infinity"
    expression.type.integer.modular_value = str(int(bounds[0].modular_value) *
                                                int(bounds[1].modular_value))
    return

  if any(bound.modulus == "infinity" for bound in bounds):
    # If one side is constant and the other is not, then the non-constant
    # modulus and modular_value can both be multiplied by the constant.  E.g.,
    # if `a` is congruent to 3 mod 5, then `4 * a` will be congruent to 12 mod
    # 20:
    #
    #   a = ...   |  4 * a = ...  |  4 * a mod 20 = ...
    #   3         |  12           |  12
    #   8         |  32           |  12
    #   13        |  52           |  12
    #   18        |  72           |  12
    #   23        |  92           |  12
    #   28        |  112          |  12
    #   33        |  132          |  12
    #
    # This is trivially shown by noting that the difference between consecutive
    # possible values for `4 * a` always differ by 20.
    if bounds[0].modulus == "infinity":
      constant, variable = bounds
    else:
      variable, constant = bounds
    if int(constant.modular_value) == 0:
      # If the constant is 0, the result is 0, no matter what the variable side
      # is.
      expression.type.integer.modulus = "infinity"
      expression.type.integer.modular_value = "0"
      return
    new_modulus = int(variable.modulus) * abs(int(constant.modular_value))
    expression.type.integer.modulus = str(new_modulus)
    # The `% new_modulus` will force the `modular_value` to be positive, even
    # when `constant.modular_value` is negative.
    expression.type.integer.modular_value = str(
        int(variable.modular_value) * int(constant.modular_value) % new_modulus)
    return

  # If neither side is constant, then the result is more complex.  Full proof is
  # available in g3doc/modular_congruence_multiplication_proof.md
  #
  # Essentially, if:
  #
  # l == _ * l_mod + l_mv
  # r == _ * r_mod + r_mv
  #
  # Then we find l_mod0 and r_mod0 in:
  #
  # l == (_ * l_mod_nz + l_mv_nz) * l_mod0
  # r == (_ * r_mod_nz + r_mv_nz) * r_mod0
  #
  # And finally conclude:
  #
  # l * r == _ * GCD(l_mod_nz, r_mod_nz) * l_mod0 * r_mod0 + l_mv * r_mv
  product_of_zero_congruence_moduli = 1
  product_of_modular_values = 1
  nonzero_congruence_moduli = []
  for bound in bounds:
    zero_congruence_modulus = _greatest_common_divisor(bound.modulus,
                                                       bound.modular_value)
    assert int(bound.modulus) % zero_congruence_modulus == 0
    product_of_zero_congruence_moduli *= zero_congruence_modulus
    product_of_modular_values *= int(bound.modular_value)
    nonzero_congruence_moduli.append(int(bound.modulus) //
                                     zero_congruence_modulus)
  shared_nonzero_congruence_modulus = _greatest_common_divisor(
      nonzero_congruence_moduli[0], nonzero_congruence_moduli[1])
  final_modulus = (shared_nonzero_congruence_modulus *
                   product_of_zero_congruence_moduli)
  expression.type.integer.modulus = str(final_modulus)
  expression.type.integer.modular_value = str(product_of_modular_values %
                                              final_modulus)


def _assert_integer_constraints(expression):
  """Asserts that the integer bounds of expression are self-consistent.

  Asserts that `minimum_value` and `maximum_value` are congruent to
  `modular_value` modulo `modulus`.

  If `modulus` is "infinity", asserts that `minimum_value`, `maximum_value`, and
  `modular_value` are all equal.

  If `minimum_value` is equal to `maximum_value`, asserts that `modular_value`
  is equal to both, and that `modulus` is "infinity".

  Arguments:
      expression: an expression with type.integer

  Returns:
      None
  """
  bounds = expression.type.integer
  if bounds.modulus == "infinity":
    assert bounds.minimum_value == bounds.modular_value
    assert bounds.maximum_value == bounds.modular_value
    return
  modulus = int(bounds.modulus)
  assert modulus > 0
  if bounds.minimum_value != "-infinity":
    assert int(bounds.minimum_value) % modulus == int(bounds.modular_value)
  if bounds.maximum_value != "infinity":
    assert int(bounds.maximum_value) % modulus == int(bounds.modular_value)
  if bounds.minimum_value == bounds.maximum_value:
    # TODO(bolms): I believe there are situations using the not-yet-implemented
    # integer division operator that would trigger these asserts, so they should
    # be turned into assignments (with corresponding tests) when implementing
    # division.
    assert bounds.modular_value == bounds.minimum_value
    assert bounds.modulus == "infinity"
  if bounds.minimum_value != "-infinity" and bounds.maximum_value != "infinity":
    assert int(bounds.minimum_value) <= int(bounds.maximum_value)


def _compute_constant_value_of_comparison_operator(expression):
  """Computes the constant value, if any, of a comparison operator."""
  args = expression.function.args
  if all(ir_util.is_constant(arg) for arg in args):
    functions = {
        ir_data.FunctionMapping.EQUALITY: operator.eq,
        ir_data.FunctionMapping.INEQUALITY: operator.ne,
        ir_data.FunctionMapping.LESS: operator.lt,
        ir_data.FunctionMapping.LESS_OR_EQUAL: operator.le,
        ir_data.FunctionMapping.GREATER: operator.gt,
        ir_data.FunctionMapping.GREATER_OR_EQUAL: operator.ge,
        ir_data.FunctionMapping.AND: operator.and_,
        ir_data.FunctionMapping.OR: operator.or_,
    }
    func = functions[expression.function.function]
    expression.type.boolean.value = func(
        *[ir_util.constant_value(arg) for arg in args])


def _compute_constraints_of_bound_function(expression):
  """Computes the constraints of $upper_bound or $lower_bound."""
  if expression.function.function == ir_data.FunctionMapping.UPPER_BOUND:
    value = expression.function.args[0].type.integer.maximum_value
  elif expression.function.function == ir_data.FunctionMapping.LOWER_BOUND:
    value = expression.function.args[0].type.integer.minimum_value
  else:
    assert False, "Non-bound function"
  expression.type.integer.minimum_value = value
  expression.type.integer.maximum_value = value
  expression.type.integer.modular_value = value
  expression.type.integer.modulus = "infinity"


def _compute_constraints_of_maximum_function(expression):
  """Computes the constraints of the $max function."""
  assert expression.type.WhichOneof("type") == "integer"
  args = expression.function.args
  assert args[0].type.WhichOneof("type") == "integer"
  # The minimum value of the result occurs when every argument takes its minimum
  # value, which means that the minimum result is the maximum-of-minimums.
  expression.type.integer.minimum_value = str(_max(
      [arg.type.integer.minimum_value for arg in args]))
  # The maximum result is the maximum-of-maximums.
  expression.type.integer.maximum_value = str(_max(
      [arg.type.integer.maximum_value for arg in args]))
  # If the expression is dominated by a constant factor, then the result is
  # constant.  I (bolms@) believe this is the only case where
  # _compute_constraints_of_maximum_function might violate the assertions in
  # _assert_integer_constraints.
  if (expression.type.integer.minimum_value ==
      expression.type.integer.maximum_value):
    expression.type.integer.modular_value = (
        expression.type.integer.minimum_value)
    expression.type.integer.modulus = "infinity"
    return
  result_modulus = args[0].type.integer.modulus
  result_modular_value = args[0].type.integer.modular_value
  # The result of $max(a, b) could be either a or b, which means that the result
  # of $max(a, b) uses the _shared_modular_value() of a and b, just like the
  # choice operator '?:'.
  #
  # This also takes advantage of the fact that $max(a, b, c, d, ...) is
  # equivalent to $max(a, $max(b, $max(c, $max(d, ...)))), so it is valid to
  # call _shared_modular_value() in a loop.
  for arg in args[1:]:
    # TODO(bolms): I think the bounds could be tigher in some cases where
    # arg.maximum_value is less than the new expression.minimum_value, and
    # in some very specific cases where arg.maximum_value is greater than the
    # new expression.minimum_value, but arg.maximum_value - arg.modulus is less
    # than expression.minimum_value.
    result_modulus, result_modular_value = _shared_modular_value(
        (result_modulus, result_modular_value),
        (arg.type.integer.modulus, arg.type.integer.modular_value))
  expression.type.integer.modulus = str(result_modulus)
  expression.type.integer.modular_value = str(result_modular_value)


def _shared_modular_value(left, right):
  """Returns the shared modulus and modular value of left and right.

  Arguments:
    left: A tuple of (modulus, modular value)
    right: A tuple of (modulus, modular value)

  Returns:
    A tuple of (modulus, modular_value) such that:

    left.modulus % result.modulus == 0
    right.modulus % result.modulus == 0
    left.modular_value % result.modulus = result.modular_value
    right.modular_value % result.modulus = result.modular_value

    That is, the result.modulus and result.modular_value will be compatible
    with, but (possibly) less restrictive than both left.(modulus,
    modular_value) and right.(modulus, modular_value).
  """
  left_modulus, left_modular_value = left
  right_modulus, right_modular_value = right
  # The combined modulus is gcd(gcd(left_modulus, right_modulus),
  # left_modular_value - right_modular_value).
  #
  # The inner gcd normalizes the left_modulus and right_modulus, but can leave
  # incompatible modular_values.  The outer gcd finds a modulus to which both
  # modular_values are congruent.  Some examples:
  #
  #     left          |  right         |  res
  #     --------------+----------------+--------------------
  #     l % 12 == 7   |  r % 20 == 15  |  res % 4 == 3
  #     l == 35       |  r % 20 == 15  |  res % 20 == 15
  #     l % 24 == 15  |  r % 12 == 7   |  res % 4 == 3
  #     l % 20 == 15  |  r % 20 == 10  |  res % 5 == 0
  #     l % 20 == 16  |  r % 20 == 11  |  res % 5 == 1
  #     l == 10       |  r == 7        |  res % 3 == 1
  #     l == 4        |  r == 4        |  res == 4
  #
  # The cases where one side or the other are constant are handled
  # automatically by the fact that _greatest_common_divisor("infinity", x)
  # is x.
  common_modulus = _greatest_common_divisor(left_modulus, right_modulus)
  new_modulus = _greatest_common_divisor(
      common_modulus, abs(int(left_modular_value) - int(right_modular_value)))
  if new_modulus == "infinity":
    # The only way for the new_modulus to come out as "infinity" *should* be
    # if both if_true and if_false have the same constant value.
    assert left_modular_value == right_modular_value
    assert left_modulus == right_modulus == "infinity"
    return new_modulus, left_modular_value
  else:
    assert (int(left_modular_value) % new_modulus ==
            int(right_modular_value) % new_modulus)
    return new_modulus, int(left_modular_value) % new_modulus


def _compute_constraints_of_choice_operator(expression):
  """Computes the constraints of a choice operation '?:'."""
  condition, if_true, if_false = ir_data_utils.reader(expression).function.args
  expression = ir_data_utils.builder(expression)
  if condition.type.boolean.HasField("value"):
    # The generated expressions for $size_in_bits and $size_in_bytes look like
    #
    #     $max((field1_existence_condition ? field1_start + field1_size : 0),
    #          (field2_existence_condition ? field2_start + field2_size : 0),
    #          (field3_existence_condition ? field3_start + field3_size : 0),
    #          ...)
    #
    # Since most existence_conditions are just "true", it is important to select
    # the tighter bounds in those cases -- otherwise, only zero-length
    # structures could have a constant $size_in_bits or $size_in_bytes.
    side = if_true if condition.type.boolean.value else if_false
    expression.type.CopyFrom(side.type)
    return
  # The type.integer minimum_value/maximum_value bounding code is needed since
  # constraints.check_constraints() will complain if minimum and maximum are not
  # set correctly.  I'm (bolms@) not sure if the modulus/modular_value pulls its
  # weight, but for completeness I've left it in.
  if if_true.type.WhichOneof("type") == "integer":
    # The minimum value of the choice is the minimum value of either side, and
    # the maximum is the maximum value of either side.
    expression.type.integer.minimum_value = str(_min([
        if_true.type.integer.minimum_value,
        if_false.type.integer.minimum_value]))
    expression.type.integer.maximum_value = str(_max([
        if_true.type.integer.maximum_value,
        if_false.type.integer.maximum_value]))
    new_modulus, new_modular_value = _shared_modular_value(
        (if_true.type.integer.modulus, if_true.type.integer.modular_value),
        (if_false.type.integer.modulus, if_false.type.integer.modular_value))
    expression.type.integer.modulus = str(new_modulus)
    expression.type.integer.modular_value = str(new_modular_value)
  else:
    assert if_true.type.WhichOneof("type") in ("boolean", "enumeration"), (
        "Unknown type {} for expression".format(
            if_true.type.WhichOneof("type")))


def _greatest_common_divisor(a, b):
  """Returns the greatest common divisor of a and b.

  Arguments:
    a: an integer, a stringified integer, or the string "infinity"
    b: an integer, a stringified integer, or the string "infinity"

  Returns:
    Conceptually, "infinity" is treated as the product of all integers.

    If both a and b are 0, returns "infinity".

    Otherwise, if either a or b are "infinity", and the other is 0, returns
    "infinity".

    Otherwise, if either a or b are "infinity", returns the other.

    Otherwise, returns the greatest common divisor of a and b.
  """
  if a != "infinity": a = int(a)
  if b != "infinity": b = int(b)
  assert a == "infinity" or a >= 0
  assert b == "infinity" or b >= 0
  if a == b == 0: return "infinity"
  # GCD(0, x) is always x, so it's safe to shortcut when a == 0 or b == 0.
  if a == 0: return b
  if b == 0: return a
  if a == "infinity": return b
  if b == "infinity": return a
  return _math_gcd(a, b)


def compute_constants(ir):
  """Computes constant values for all expressions in ir.

  compute_constants calculates all constant values and adds them to the type
  information for each expression and subexpression.

  Arguments:
      ir: an IR on which to compute constants

  Returns:
      A (possibly empty) list of errors.
  """
  traverse_ir.fast_traverse_ir_top_down(
      ir, [ir_data.Expression], compute_constraints_of_expression,
      skip_descendants_of={ir_data.Expression})
  traverse_ir.fast_traverse_ir_top_down(
      ir, [ir_data.RuntimeParameter], _compute_constraints_of_parameter,
      skip_descendants_of={ir_data.Expression})
  return []
