예제 #1
0
 def _type_to_ir(self, concrete_type: ConcreteType) -> type_mod.Type:
   """Converts a concrete type to its corresponding IR representation."""
   assert isinstance(concrete_type, ConcreteType), concrete_type
   logging.vlog(4, 'Converting concrete type to IR: %s', concrete_type)
   if isinstance(concrete_type, ArrayType):
     element_type = self._type_to_ir(concrete_type.get_element_type())
     element_count = concrete_type.size
     if not isinstance(element_count, int):
       raise ValueError(
           'Expect array element count to be integer; got {!r}'.format(
               element_count))
     result = self.package.get_array_type(element_count, element_type)
     logging.vlog(
         4, 'Converted type to IR; concrete type: %s ir: %s element_count: %d',
         concrete_type, result, concrete_type.size)
     return result
   elif isinstance(concrete_type, BitsType) or isinstance(
       concrete_type, EnumType):
     return self.package.get_bits_type(concrete_type.get_total_bit_count())
   else:
     if not isinstance(concrete_type, TupleType):
       raise ValueError(
           'Expect type to be bits/enum, array, or tuple; got: '
           f'{concrete_type} ({concrete_type!r})')
     members = tuple(
         self._type_to_ir(m) for m in concrete_type.get_unnamed_members())
     return self.package.get_tuple_type(members)
예제 #2
0
def _bind_names(name_def_tree: ast.NameDefTree, type_: ConcreteType,
                ctx: DeduceCtx) -> None:
    """Binds names in name_def_tree to corresponding type given in type_."""
    if name_def_tree.is_leaf():
        name_def = name_def_tree.get_leaf()
        ctx.type_info[name_def] = type_
        return

    if not isinstance(type_, TupleType):
        raise XlsTypeError(
            name_def_tree.span,
            type_,
            rhs_type=None,
            suffix='Expected a tuple type for these names, but got {}.'.format(
                type_))

    if len(name_def_tree.tree) != type_.get_tuple_length():
        raise TypeInferenceError(
            name_def_tree.span, type_,
            'Could not bind names, names are mismatched in number vs type; at '
            'this level of the tuple: {} names, {} types.'.format(
                len(name_def_tree.tree), type_.get_tuple_length()))

    for subtree, subtype in zip(name_def_tree.tree,
                                type_.get_unnamed_members()):
        ctx.type_info[subtree] = subtype
        _bind_names(subtree, subtype, ctx)
예제 #3
0
def _generate_unbiased_argument(concrete_type: ConcreteType,
                                rng: Random) -> Value:
  if isinstance(concrete_type, BitsType):
    bit_count = concrete_type.get_total_bit_count()
    return _generate_bit_value(bit_count, rng, concrete_type.get_signedness())
  else:
    raise NotImplementedError(
        'Generate argument for type: {}'.format(concrete_type))
예제 #4
0
 def _symbolic_bind_array(self, param_type: ConcreteType,
                          arg_type: ConcreteType):
     """Binds any parametric symbols in the "array" param_type."""
     assert isinstance(param_type, ArrayType) and isinstance(
         arg_type, ArrayType)
     self._symbolic_bind(param_type.get_element_type(),
                         arg_type.get_element_type())
     self._symbolic_bind_dims(param_type, arg_type)
예제 #5
0
 def _symbolic_bind_tuple(self, param_type: ConcreteType,
                          arg_type: ConcreteType):
     """Binds any parametric symbols in the "tuple" param_type."""
     assert isinstance(param_type, TupleType) and isinstance(
         arg_type, TupleType)
     for param_member, arg_member in zip(param_type.get_unnamed_members(),
                                         arg_type.get_unnamed_members()):
         self._symbolic_bind(param_member, arg_member)
예제 #6
0
 def _cast_to_array(self, node: ast.Cast, output_type: ConcreteType) -> None:
   bits = self._use(node.expr)
   slices = []
   element_bit_count = output_type.get_element_type().get_total_bit_count()  # pytype: disable=attribute-error
   # MSb becomes lowest-indexed array element.
   for i in range(0, output_type.get_total_bit_count(), element_bit_count):
     slices.append(self.fb.add_bit_slice(bits, i, element_bit_count))
   slices.reverse()
   element_type = self.package.get_bits_type(element_bit_count)
   self._def(node, self.fb.add_array, slices, element_type)
예제 #7
0
 def _symbolic_bind_function(self, param_type: ConcreteType,
                             arg_type: ConcreteType):
     """Binds any parametric symbols in the "function" param_type."""
     assert isinstance(param_type, FunctionType) and isinstance(
         arg_type, FunctionType)
     for param_param, arg_param in zip(param_type.get_function_params(),
                                       arg_type.get_function_params()):
         self._symbolic_bind(param_param, arg_param)
     self._symbolic_bind(param_type.get_function_return_type(),
                         arg_type.get_function_return_type())
예제 #8
0
def concrete_type_to_annotation(
    concrete_type: concrete_type_mod.ConcreteType) -> ast.TypeAnnotation:
  if isinstance(concrete_type, concrete_type_mod.BitsType):
    keyword = SN_KEYWORD if concrete_type.get_signedness() else UN_KEYWORD
    num_tok = scanner.Token(scanner.TokenKind.NUMBER, FAKE_SPAN,
                            concrete_type.get_total_bit_count())
    return ast.make_builtin_type_annotation(
        FAKE_SPAN, keyword, dims=(ast.Number(num_tok),))

  raise NotImplementedError(concrete_type)
예제 #9
0
def concrete_type_accepts_value(type_: ConcreteType, value: Value) -> bool:
    """Returns whether 'value' conforms to this concrete type."""
    if value.tag == Tag.UBITS:
        return (isinstance(type_, BitsType) and not type_.signed and
                value.bits_payload.bit_count == type_.get_total_bit_count())
    if value.tag == Tag.SBITS:
        return (isinstance(type_, BitsType) and type_.signed and
                value.bits_payload.bit_count == type_.get_total_bit_count())
    if value.tag in (Tag.ARRAY, Tag.TUPLE, Tag.ENUM):
        return _value_compatible_with_type(type_, value)
    raise NotImplementedError(type_, value)
예제 #10
0
 def check_bitwidth(self, concrete_type: ConcreteType) -> None:
   # TODO(leary): 2019-04-19 Because we punt on typechecking symbolic concrete
   # types here we'll need to do another pass to check whether numerical values
   # fit once the parametric symbols are instantiated as integers.
   if (isinstance(concrete_type, BitsType) and
       isinstance(concrete_type.get_total_bit_count(), int) and
       not bit_helpers.fits_in_bits(self.get_value_as_int(),
                                    concrete_type.get_total_bit_count())):
     msg = 'value {!r} does not fit in the bitwidth of a {} ({})'.format(
         self.value, concrete_type, concrete_type.get_total_bit_count())
     raise TypeInferenceError(span=self.span, type_=concrete_type, suffix=msg)
예제 #11
0
 def _visit_matcher(self, matcher: ast.NameDefTree, index: Tuple[int, ...],
                    matched_value: BValue,
                    matched_type: ConcreteType) -> BValue:
   if matcher.is_leaf():
     leaf = matcher.get_leaf()
     if isinstance(leaf, ast.WildcardPattern):
       return self._def(matcher, self.fb.add_literal_bits,
                        bits_mod.UBits(1, 1))
     elif isinstance(leaf, (ast.Number, ast.EnumRef)):
       leaf.accept(self)
       return self._def(matcher, self.fb.add_eq, self._use(leaf),
                        matched_value)
     elif isinstance(leaf, ast.NameRef):
       result = self._def(matcher, self.fb.add_eq, self._use(leaf.name_def),
                          matched_value)
       self._def_alias(leaf.name_def, to=leaf)
       return result
     else:
       assert isinstance(
           leaf, ast.NameDef
       ), 'Expected leaf to be wildcard, number, or name; got: {!r}'.format(
           leaf)
       ok = self._def(leaf, self.fb.add_literal_bits, bits_mod.UBits(1, 1))
       self.node_to_ir[matcher] = self.node_to_ir[leaf] = matched_value
       return ok
   else:
     ok = self.fb.add_literal_bits(bits_mod.UBits(value=1, bit_count=1))
     for i, (element, element_type) in enumerate(
         zip(matcher.tree, matched_type.get_unnamed_members())):  # pytype: disable=attribute-error
       # Extract the element.
       member = self.fb.add_tuple_index(matched_value, i)
       cond = self._visit_matcher(element, index + (i,), member, element_type)
       ok = self.fb.add_and(ok, cond)
     return ok
예제 #12
0
def sign_convert_value(concrete_type: ConcreteType, value: Value) -> Value:
    """Converts the values to matched the signedness of the concrete type.

  Converts bits-typed Values contained within the given Value to match the
  signedness of the ConcreteType. Examples:

  invocation: sign_convert_value(s8, u8:64)
  returns: s8:64

  invocation: sign_convert_value(s3, u8:7)
  returns: s3:-1

  invocation: sign_convert_value((s8, u8), (u8:42, u8:10))
  returns: (s8:42, u8:10)

  This conversion functionality is required because the Values used in the DSLX
  may be signed while Values in IR interpretation and Verilog simulation are
  always unsigned.

  This function is idempotent.

  Args:
    concrete_type: ConcreteType to match.
    value: Input value.

  Returns:
    Sign-converted value.
  """
    if isinstance(concrete_type, concrete_type_mod.TupleType):
        assert value.is_tuple()
        assert len(value.tuple_members) == concrete_type.get_tuple_length()
        return Value.make_tuple(
            tuple(
                sign_convert_value(t, a) for t, a in zip(
                    concrete_type.get_unnamed_members(), value.tuple_members)))
    elif isinstance(concrete_type, concrete_type_mod.ArrayType):
        assert value.is_array()
        assert len(value.array_payload.elements) == concrete_type.size
        return Value.make_array(
            tuple(
                sign_convert_value(concrete_type.get_element_type(), v)
                for v in value.array_payload.elements))
    elif concrete_type_mod.is_sbits(concrete_type):
        return Value.make_sbits(value.get_bit_count(), value.get_bits_value())
    else:
        assert concrete_type_mod.is_ubits(concrete_type)
        return value
예제 #13
0
 def _instantiate_one_arg(self, i: int, param_type: ConcreteType,
                          arg_type: ConcreteType) -> ConcreteType:
     """Binds param_type via arg_type, updating symbolic bindings."""
     assert isinstance(param_type, ConcreteType), repr(param_type)
     assert isinstance(arg_type, ConcreteType), repr(arg_type)
     # Check parameter and arg types are the same kind.
     if type(param_type) != type(arg_type):  # pylint: disable=unidiomatic-typecheck
         raise XlsTypeError(
             self.span,
             param_type,
             arg_type,
             suffix='Parameter {} and argument types are different kinds '
             '({} vs {}).'.format(i, param_type.get_debug_type_name(),
                                  arg_type.get_debug_type_name()))
     logging.vlog(
         3, 'Symbolically binding param_type %d %s against arg_type %s', i,
         param_type, arg_type)
     self._symbolic_bind(param_type, arg_type)
     resolved = self._resolve(param_type)
     logging.vlog(3, 'Resolved param_type: %s', resolved)
     return resolved
예제 #14
0
    def _resolve(self, annotated: ConcreteType) -> ConcreteType:
        """Resolves a parametric type via symbolic_bindings."""

        if self.constraints:
            self._verify_constraints()

        def resolver(dim):
            if isinstance(dim, parametric_expression.ParametricExpression):
                return dim.evaluate(self.symbolic_bindings)
            return dim

        return annotated.map_size(resolver)
예제 #15
0
def generate_argument(arg_type: ConcreteType, rng: Random,
                      prior: Sequence[Value]) -> Value:
    """Generates an argument value of the same type as the concrete type."""
    if isinstance(arg_type, TupleType):
        return Value.make_tuple(
            tuple(
                generate_argument(t, rng, prior)
                for t in arg_type.get_unnamed_members()))
    elif isinstance(arg_type, ArrayType):
        return Value.make_array(
            tuple(
                generate_argument(arg_type.get_element_type(), rng, prior)
                for _ in range(arg_type.size)))
    else:
        assert isinstance(arg_type, BitsType)
        if not prior or rng.random() < 0.5:
            return _generate_unbiased_argument(arg_type, rng)

    to_mutate = rng.choice(prior)
    bit_count = arg_type.get_total_bit_count()
    if bit_count > to_mutate.get_bit_count():
        to_mutate = to_mutate.bits_payload.concat(
            _generate_bit_value(bit_count - to_mutate.get_bit_count(),
                                rng,
                                signed=False).bits_payload)
    else:
        to_mutate = to_mutate.bits_payload.slice(0, bit_count, lsb_is_0=False)

    assert to_mutate.bit_count == bit_count
    value = to_mutate.value
    mutation_count = randrange_biased_towards_zero(bit_count, rng)
    for _ in range(mutation_count):
        # Pick a random bit and flip it.
        bitno = rng.randrange(bit_count)
        value ^= 1 << bitno

    signed = arg_type.get_signedness()
    constructor = Value.make_sbits if signed else Value.make_ubits
    return constructor(value=value, bit_count=bit_count)
예제 #16
0
def _unify_NameDefTree(self: ast.NameDefTree, type_: ConcreteType,
                       ctx: DeduceCtx) -> None:
    """Unifies the NameDefTree AST node with the observed RHS type type_."""
    resolved_rhs_type = resolve(type_, ctx)
    if self.is_leaf():
        leaf = self.get_leaf()
        if isinstance(leaf, ast.NameDef):
            ctx.type_info[leaf] = resolved_rhs_type
        elif isinstance(leaf, ast.WildcardPattern):
            pass
        elif isinstance(leaf, (ast.Number, ast.EnumRef)):
            resolved_leaf_type = resolve(deduce(leaf, ctx), ctx)
            if resolved_leaf_type != resolved_rhs_type:
                raise TypeInferenceError(
                    span=self.span,
                    type_=resolved_rhs_type,
                    suffix=
                    'Conflicting types; pattern expects {} but got {} from value'
                    .format(resolved_rhs_type, resolved_leaf_type))
        else:
            assert isinstance(leaf, ast.NameRef), repr(leaf)
            ref_type = ctx.type_info[leaf.name_def]
            resolved_ref_type = resolve(ref_type, ctx)
            if resolved_ref_type != resolved_rhs_type:
                raise TypeInferenceError(
                    span=self.span,
                    type_=resolved_rhs_type,
                    suffix=
                    'Conflicting types; pattern expects {} but got {} from reference'
                    .format(resolved_rhs_type, resolved_ref_type))
    else:
        assert isinstance(self.tree, tuple)
        if isinstance(type_, TupleType) and type_.get_tuple_length() == len(
                self.tree):
            for subtype, subtree in zip(type_.get_unnamed_members(),
                                        self.tree):
                _unify(subtree, subtype, ctx)
예제 #17
0
def _value_compatible_with_type(type_: ConcreteType, value: Value) -> bool:
    """Returns whether value is compatible with type_ (recursively)."""
    assert isinstance(value, Value), value

    if isinstance(type_, TupleType) and value.is_tuple():
        return all(
            _value_compatible_with_type(ct, m)
            for ct, m in zip(type_.get_unnamed_members(), value.tuple_members))

    if isinstance(type_, ArrayType) and value.is_array():
        et = type_.get_element_type()
        return all(
            _value_compatible_with_type(et, m)
            for m in value.array_payload.elements)

    if isinstance(type_, EnumType) and value.tag == Tag.ENUM:
        return type_.nominal_type == value.type_

    if isinstance(type_,
                  BitsType) and not type_.signed and value.tag == Tag.UBITS:
        return value.bits_payload.bit_count == type_.get_total_bit_count()

    if isinstance(type_, BitsType) and type_.signed and value.tag == Tag.SBITS:
        return value.bits_payload.bit_count == type_.get_total_bit_count()

    if value.tag == Tag.ENUM and isinstance(type_, BitsType):
        return (value.type_.get_signedness() == type_.get_signedness() and
                value.bits_payload.bit_count == type_.get_total_bit_count())

    if value.tag == Tag.ARRAY and is_ubits(type_):
        flat_bit_count = value.array_payload.flatten().bits_payload.bit_count
        return flat_bit_count == type_.get_total_bit_count()

    if isinstance(type_, EnumType) and value.is_bits():
        return (type_.get_signedness() == (value.tag == Tag.SBITS)
                and type_.get_total_bit_count() == value.get_bit_count())

    raise NotImplementedError(type_, value)
예제 #18
0
def _validate_struct_members_subset(
    members: ast.StructInstanceMembers, struct_type: ConcreteType,
    struct_text: str, ctx: DeduceCtx
) -> Tuple[Set[str], Tuple[ConcreteType], Tuple[ConcreteType]]:
    """Validates a struct instantiation is a subset of members with no dups.

  Args:
    members: Sequence of members used in instantiation. Note this may be a
      subset; e.g. in the case of splat instantiation.
    struct_type: The deduced type for the struct (instantiation).
    struct_text: Display name to use for the struct in case of an error.
    ctx: Wrapper containing node to type mapping context.

  Returns:
    A tuple containing the set of struct member names that were instantiated,
    the ConcreteTypes of the provided arguments, and the ConcreteTypes of the
    corresponding struct member definition.
  """
    seen_names = set()
    arg_types = []
    member_types = []
    for k, v in members:
        if k in seen_names:
            raise TypeInferenceError(
                v.span,
                type_=None,
                suffix=
                'Duplicate value seen for {!r} in this {!r} struct instance.'.
                format(k, struct_text))
        seen_names.add(k)
        expr_type = resolve(deduce(v, ctx), ctx)
        arg_types.append(expr_type)
        try:
            member_type = struct_type.get_member_type_by_name(k)  # pytype: disable=attribute-error
            member_types.append(member_type)
        except KeyError:
            raise TypeInferenceError(
                v.span,
                None,
                suffix=
                'Struct {!r} has no member {!r}, but it was provided by this instance.'
                .format(struct_text, k))

    return seen_names, tuple(arg_types), tuple(member_types)
예제 #19
0
def resolve(type_: ConcreteType, ctx: DeduceCtx) -> ConcreteType:
    """Resolves "type_" via provided symbolic bindings.

  Uses the symbolic bindings of the function we're currently inside of to
  resolve parametric types.

  Args:
    type_: Type to resolve any contained dims for.
    ctx: Deduction context to use in resolving the dims.

  Returns:
    "type_" with dimensions resolved according to bindings in "ctx".
  """
    _, fn_symbolic_bindings = ctx.fn_stack[-1]

    def resolver(dim):
        if isinstance(dim, ParametricExpression):
            return dim.evaluate(fn_symbolic_bindings)
        return dim

    return type_.map_size(resolver)
예제 #20
0
def _concretize_struct_annotation(type_annotation: ast.TypeRefTypeAnnotation,
                                  struct: ast.Struct,
                                  base_type: ConcreteType) -> ConcreteType:
    """Returns concretized struct type using the provided bindings.

  For example, if we have a struct defined as `struct [N: u32, M: u32] Foo`,
  the default TupleType will be (N, M). If a type annotation provides bindings,
  (e.g. Foo[A, 16]), we will replace N, M with those values. In the case above,
  we will return (A, 16) instead.

  Args:
    type_annotation: The provided type annotation for this parametric struct.
    struct: The corresponding struct AST node.
    base_type: The TupleType of the struct, based only on the struct definition.
  """
    assert len(struct.parametric_bindings) == len(type_annotation.parametrics)
    defined_to_annotated = {}
    for defined_parametric, annotated_parametric in zip(
            struct.parametric_bindings, type_annotation.parametrics):
        assert isinstance(defined_parametric,
                          ast.ParametricBinding), defined_parametric
        if isinstance(annotated_parametric, ast.Number):
            defined_to_annotated[defined_parametric.name.identifier] = \
                int(annotated_parametric.value)
        else:
            assert isinstance(annotated_parametric,
                              ast.NameRef), repr(annotated_parametric)
            defined_to_annotated[defined_parametric.name.identifier] = \
                ParametricSymbol(annotated_parametric.identifier,
                                 annotated_parametric.span)

    def resolver(dim):
        if isinstance(dim, ParametricExpression):
            return dim.evaluate(defined_to_annotated)
        return dim

    return base_type.map_size(resolver)
예제 #21
0
def ir_value_to_interpreter_value(value: ir_value.Value,
                                  dslx_type: ConcreteType) -> dslx_value.Value:
    """Converts an IR Value to an interpreter Value."""
    if value.is_bits():
        assert isinstance(dslx_type, BitsType), dslx_type
        ir_bits_val = value.get_bits()
        if dslx_type.get_signedness():
            return dslx_value.Value.make_sbits(
                ir_bits_val.bit_count(), bits_to_int(ir_bits_val, signed=True))
        return dslx_value.Value.make_ubits(
            ir_bits_val.bit_count(), bits_to_int(ir_bits_val, signed=False))
    elif value.is_array():
        assert isinstance(dslx_type, ArrayType), dslx_type
        return dslx_value.Value.make_array(
            tuple(
                ir_value_to_interpreter_value(e, dslx_type.element_type)
                for e in value.get_elements()))
    else:
        assert value.is_tuple()
        assert isinstance(dslx_type, TupleType), dslx_type
        return dslx_value.Value.make_tuple(
            tuple(
                ir_value_to_interpreter_value(e, t) for e, t in zip(
                    value.get_elements(), t.get_unnamed_members())))
예제 #22
0
def concrete_type_convert_value(type_: ConcreteType, value: Value, span: Span,
                                enum_values: Optional[Tuple[Value, ...]],
                                enum_signed: Optional[bool]) -> Value:
    """Converts 'value' into a value of this concrete type."""
    logging.vlog(3, 'Converting value %s to type %s', value, type_)
    if value.tag == Tag.UBITS and isinstance(type_, ArrayType):
        bits_per_element = type_.get_element_type().get_total_bit_count()
        bits = value.bits_payload

        def bit_slice_value_at_index(i):
            return Value(
                Tag.UBITS,
                bits.slice(i * bits_per_element, (i + 1) * bits_per_element,
                           lsb_is_0=False))

        return Value.make_array(
            tuple(bit_slice_value_at_index(i) for i in range(type_.size)))

    if (isinstance(type_, EnumType)
            and value.tag in (Tag.UBITS, Tag.SBITS, Tag.ENUM)
            and value.get_bit_count() == type_.get_total_bit_count()):
        # Check that the bits we're converting from are present in the enum type
        # we're converting to.
        for enum_value in enum_values:
            if value.bits_payload == enum_value.bits_payload:
                break
        else:
            raise FailureError(
                span, 'Value is not valid for enum {}: {}'.format(
                    type_.nominal_type.identifier, value))
        return Value.make_enum(value.bits_payload, type_.nominal_type)

    if (value.tag == Tag.ENUM and isinstance(type_, BitsType)
            and type_.get_total_bit_count() == value.get_bit_count()):
        constructor = Value.make_sbits if type_.signed else Value.make_ubits  # pytype: disable=attribute-error
        bit_count = type_.get_total_bit_count()
        return constructor(bit_count, value.bits_payload.value)

    def zero_ext() -> Value:
        assert isinstance(type_, BitsType)
        constructor = Value.make_sbits if type_.signed else Value.make_ubits
        bit_count = type_.get_total_bit_count()
        return constructor(
            bit_count,
            value.get_bits_value() & bit_helpers.to_mask(bit_count))

    def sign_ext() -> Value:
        assert isinstance(type_, BitsType)
        constructor = Value.make_sbits if type_.signed else Value.make_ubits
        bit_count = type_.get_total_bit_count()
        logging.vlog(3, 'Sign extending %s to %s', value, bit_count)
        return constructor(bit_count,
                           value.bits_payload.sign_ext(bit_count).value)

    if value.tag == Tag.UBITS:
        return zero_ext()

    if value.tag == Tag.SBITS:
        return sign_ext()

    if value.tag == Tag.ENUM:
        assert enum_signed is not None
        return sign_ext() if enum_signed else zero_ext()

    # If we're converting an array into bits, flatten the array payload.
    if value.tag == Tag.ARRAY and isinstance(type_, BitsType):
        return value.array_payload.flatten()

    if concrete_type_accepts_value(type_, value):  # Vacuous conversion.
        return value

    raise FailureError(
        span,
        'Interpreter failure: cannot convert value %s (of type %s) to type %s'
        % (value, concrete_type_from_value(value), type_))
예제 #23
0
def _is_acceptable_cast(from_: ConcreteType, to: ConcreteType) -> bool:
    if {type(from_), type(to)} == {ArrayType, BitsType}:
        return from_.get_total_bit_count() == to.get_total_bit_count()
    return True
예제 #24
0
def _deduce_slice_type(self: ast.Index, ctx: DeduceCtx,
                       lhs_type: ConcreteType) -> ConcreteType:
    """Deduces the concrete type of an Index AST node with a slice spec."""
    index_slice = self.index
    assert isinstance(index_slice, (ast.Slice, ast.WidthSlice)), index_slice

    # TODO(leary): 2019-10-28 Only slicing bits types for now, and only with
    # number ast nodes, generalize to arrays and constant expressions.
    if not isinstance(lhs_type, BitsType):
        raise XlsTypeError(self.span, lhs_type, None,
                           'Value to slice is not of "bits" type.')

    bit_count = lhs_type.get_total_bit_count()

    if isinstance(index_slice, ast.WidthSlice):
        start = index_slice.start
        if isinstance(start, ast.Number) and start.type_ is None:
            start_type = lhs_type.to_ubits()
            resolved_start_type = resolve(start_type, ctx)
            if not bit_helpers.fits_in_bits(
                    start.get_value_as_int(),
                    resolved_start_type.get_total_bit_count()):
                raise TypeInferenceError(
                    start.span, resolved_start_type,
                    'Cannot fit {} in {} bits (inferred from bits to slice).'.
                    format(start.get_value_as_int(),
                           resolved_start_type.get_total_bit_count()))
            ctx.type_info[start] = start_type
        else:
            start_type = deduce(start, ctx)

        # Check the start is unsigned.
        if start_type.signed:
            raise TypeInferenceError(
                start.span,
                type_=start_type,
                suffix='Start index for width-based slice must be unsigned.')

        width_type = deduce(index_slice.width, ctx)
        if isinstance(width_type.get_total_bit_count(), int) and isinstance(
                lhs_type.get_total_bit_count(),
                int) and width_type.get_total_bit_count(
                ) > lhs_type.get_total_bit_count():
            raise XlsTypeError(
                start.span, lhs_type, width_type,
                'Slice type must have <= original number of bits; attempted slice from {} to {} bits.'
                .format(lhs_type.get_total_bit_count(),
                        width_type.get_total_bit_count()))

        # Check the width type is bits-based (no enums, since value could be out
        # of range of the enum values).
        if not isinstance(width_type, BitsType):
            raise TypeInferenceError(
                self.span,
                type_=width_type,
                suffix='Require a bits-based type for width-based slice.')

        # The width type is the thing returned from the width-slice.
        return width_type

    assert isinstance(index_slice, ast.Slice), index_slice
    limit = index_slice.limit.get_value_as_int() if index_slice.limit else None
    # PyType has trouble figuring out that start is definitely an Number at this
    # point.
    start = index_slice.start
    assert isinstance(start, (ast.Number, type(None)))
    start = start.get_value_as_int() if start else None

    _, fn_symbolic_bindings = ctx.fn_stack[-1]
    if isinstance(bit_count, ParametricExpression):
        bit_count = bit_count.evaluate(fn_symbolic_bindings)
    start, width = bit_helpers.resolve_bit_slice_indices(
        bit_count, start, limit)
    key = tuple(fn_symbolic_bindings.items())
    ctx.type_info.add_slice_start_width(index_slice, key, (start, width))
    return BitsType(signed=False, size=width)