blob: 51efca50830d917d0bf20a28bc32bfe5d7e20c1c [file] [log] [blame]
# 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.which_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.which_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.which_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.which_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.which_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.which_type == "enumeration":
assert expression.type.enumeration.name
elif expression.type.which_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.which_type == "integer"
args = expression.function.args
assert args[0].type.which_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.which_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.which_type in (
"boolean",
"enumeration",
), "Unknown type {} for expression".format(if_true.type.which_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 []