def _generate_concat(self, env: Env) -> Tuple[ast.Expr, ast.TypeAnnotation]: """Returns a (potentially vacuous) concatenate operation of values in `env`. Args: env: Environment of values that can be selected from for concatenation. Note: the concat operation will not exceed the maximum bit width so the concat may end up being a nop. """ if self._env_contains_array(env) and self.rng.choice([True, False]): return self._generate_array_concat(env) count = self._generate_nary_operand_count(env) + 2 operands = [] operand_types = [] for i in range(count): make_arg, arg_type = self._choose_env_value( env, self._is_builtin_unsigned) operands.append(make_arg()) operand_types.append(arg_type) result = operands[0] result_bits = builtin_type_to_bits(operand_types[0]) token = scanner.Token(ast.Binop.CONCAT, self.fake_span, ast.Binop.CONCAT.value) for i in range(1, count): this_bits = builtin_type_to_bits(operand_types[i]) if result_bits + this_bits > self.options.max_width_bits_types: break result = ast.Binop(token, result, operands[i]) result_bits += this_bits assert result_bits <= self.options.max_width_bits_types, result_bits return (result, self._make_large_type_annotation('UN', result_bits))
def _generate_cast_bits_to_array( self, env: Env) -> Tuple[ast.Cast, ast.TypeAnnotation]: """Generates a cast from bits to array type.""" # Get a random bits-typed element from the environment. make_arg, arg_type = self._choose_env_value(env, self._is_builtin_unsigned) # Next, find factors of the bit count and select one pair. bit_count = builtin_type_to_bits(arg_type) factors = [] for i in range(1, bit_count + 1): if bit_count % i == 0: factors.append((i, bit_count // i)) element_size, array_size = self.rng.choice(factors) element_type = ast.make_builtin_type_annotation( self.fake_span, scanner.Token(scanner.TokenKind.KEYWORD, value=scanner.Keyword.UN, span=self.fake_span), (self._make_number(element_size, None), )) outer_array_type = self._make_array_type(element_type, array_size) return (ast.Cast(outer_array_type, make_arg()), outer_array_type)
def _generate_array_concat( self, env: Env) -> Tuple[ast.Expr, ast.TypeAnnotation]: """Returns a binary concatenation of two arrays in env. The two arrays to concatenate in env will have the same element type. Args: env: Environment of values that can be selected from for array concatenation. Precondition: There must be an array value present in env. """ make_lhs, lhs_type = self._choose_env_value( env, lambda t: isinstance(t, ast.ArrayTypeAnnotation)) assert isinstance(lhs_type, ast.ArrayTypeAnnotation), lhs_type def array_same_elem_type(t: ast.TypeAnnotation) -> bool: return (isinstance(t, ast.ArrayTypeAnnotation) and t.element_type == lhs_type.element_type) make_rhs, rhs_type = self._choose_env_value(env, array_same_elem_type) token = scanner.Token(ast.Binop.CONCAT, self.fake_span, ast.Binop.CONCAT.value) result = ast.Binop(token, make_lhs(), make_rhs()) lhs_size = self._get_array_size(lhs_type) bits_per_elem = self._get_type_bit_count(lhs_type) // lhs_size result_size = lhs_size + self._get_array_size(rhs_type) dim = self._make_number(result_size, None) result_type = ast.ArrayTypeAnnotation(self.fake_span, lhs_type.element_type, dim) self._type_bit_counts[str(result_type)] = bits_per_elem * result_size return (result, result_type)
def _generate_logical_binop( self, env: Env) -> Tuple[ast.Binop, ast.TypeAnnotation]: """Generates a logical binary operation (e.g. and, xor, or).""" make_lhs, lhs_type = self._choose_env_value(env, self._not_tuple_or_array) make_rhs, rhs_type = self._choose_env_value(env, self._not_tuple_or_array) # Convert into one-bit numbers by checking whether lhs and rhs values are 0. ne_token = scanner.Token(ast.Binop.NE, self.fake_span, ast.Binop.NE.value) lhs = ast.Binop(ne_token, make_lhs(), self._make_number(0, lhs_type)) rhs = ast.Binop(ne_token, make_rhs(), self._make_number(0, rhs_type)) # Pick some operation to do. op = self.rng.choice([ast.Binop.LOGICAL_AND, ast.Binop.LOGICAL_OR]) op_token = scanner.Token(op, self.fake_span, op.value) return ast.Binop(op_token, lhs, rhs), self._make_type_annotation('u1')
def _create_element_invocation(owner: ast.AstNodeOwner, span_: span.Span, callee: Union[ast.NameRef, ast.ModRef], arg_array: ast.Expr) -> ast.Invocation: """Creates a function invocation on the first element of the given array. We need to create a fake invocation to deduce the type of a function in the case where map is called with a builtin as the map function. Normally, map functions (including parametric ones) have their types deduced when their ast.Function nodes are encountered (where a similar fake ast.Invocation node is created). Builtins don't have ast.Function nodes, so that inference can't occur, so we essentually perform that synthesis and deduction here. Args: owner: AST node owner. span_: The location in the code where analysis is occurring. callee: The function to be invoked. arg_array: The array of arguments (at least one) to the function. Returns: An invocation node for the given function when called with an element in the argument array. """ annotation = ast_helpers.make_builtin_type_annotation( owner, span_, scanner.Token(scanner.TokenKind.KEYWORD, span_, scanner.Keyword.U32), ()) index_number = ast.Number(owner, span_, '32', ast.NumberKind.OTHER, annotation) index = ast.Index(owner, span_, arg_array, index_number) return ast.Invocation(owner, span_, callee, (index, ))
def _make_large_type_annotation(self, kw_identifier: Text, width: int) -> ast.TypeAnnotation: """Creates type annotations for widths > 64 bits.""" token = scanner.Token(scanner.TokenKind.KEYWORD, self.fake_span, scanner.Keyword[kw_identifier]) dim = self._make_number(width, None) dims = (dim, ) return ast.make_builtin_type_annotation(self.fake_span, token, dims)
def _generate_type_primitive(self) -> scanner.Token: """Generates a primitive type token for use in building a type.""" # Exclude "bits" from the primitive set because it's unclear what a bits[] # represents. # # TODO(leary): 2019-06-14 Define this! kw_identifier = self.rng.choice(self._kw_identifiers) return scanner.Token(scanner.TokenKind.KEYWORD, self.fake_span, scanner.Keyword(kw_identifier))
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)
def _generate_binop_same_input_type( self, lhs: ast.Expr, rhs: ast.Expr, input_type: ast.TypeAnnotation ) -> Tuple[ast.Binop, ast.TypeAnnotation]: """Generates a binary operator on lhs/rhs which have the same input type.""" if self.rng.random() < 0.1: op = self.rng.choice(ast.Binop.COMPARISON_KIND_LIST) output_type = self._make_type_annotation('bool') else: op = self.rng.choice(self._binops) if op in ast.Binop.SHIFTS and self.rng.random() < 0.8: # Clamp the RHS to be in range most of the time. assert isinstance(input_type, ast.BuiltinTypeAnnotation), input_type bit_count = builtin_type_to_bits(input_type) new_upper = self.rng.randrange(bit_count) rhs = self._generate_umin(rhs, input_type, new_upper) output_type = input_type return ast.Binop(scanner.Token(op, self.fake_span, op.value), lhs, rhs), output_type
def _generate_unop(self, env: Env) -> Tuple[ast.Unop, ast.TypeAnnotation]: make_arg, arg_type = self._choose_env_value(env, self._not_tuple_or_array) op = self.rng.choice(ast.Unop.SAME_TYPE_KIND_LIST) return ast.Unop(scanner.Token(op, self.fake_span, op.value), make_arg()), arg_type
def _make_ge(self, lhs: ast.Expr, rhs: ast.Expr) -> ast.Expr: return ast.Binop( scanner.Token(ast.Binop.GE, self.fake_span, ast.Binop.GE.value), lhs, rhs)
def _make_identifier_token(self, identifier: Text) -> scanner.Token: return scanner.Token(scanner.TokenKind.IDENTIFIER, self.fake_span, identifier)
def _make_type_annotation(self, kw_identifier: Text) -> ast.TypeAnnotation: assert kw_identifier in scanner.TYPE_KEYWORD_STRINGS, kw_identifier token = scanner.Token(scanner.TokenKind.KEYWORD, self.fake_span, scanner.Keyword(kw_identifier)) return ast.make_builtin_type_annotation(self.fake_span, token, dims=())
from xls.dslx import concrete_type as concrete_type_mod from xls.dslx import import_routines from xls.dslx import parser from xls.dslx import parser_helpers from xls.dslx import scanner from xls.dslx import span from xls.dslx import typecheck from xls.dslx import xls_type_error from xls.dslx.interpreter import interpreter as interpreter_mod from xls.dslx.interpreter import value as value_mod FLAGS = flags.FLAGS FILENAME = '/fake/repl.x' FAKE_POS = span.Pos(FILENAME, 0, 0) FAKE_SPAN = span.Span(FAKE_POS, FAKE_POS) UN_KEYWORD = scanner.Token(scanner.TokenKind.KEYWORD, FAKE_SPAN, scanner.Keyword.UN) SN_KEYWORD = scanner.Token(scanner.TokenKind.KEYWORD, FAKE_SPAN, scanner.Keyword.SN) 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)