Exemple #1
0
    def from_yaml(yml: object, what: str,
                  insn_operands: List[Operand]) -> 'InsnInformationFlowRule':
        rule = check_keys(yml, what, ['to', 'from'], ['test'])

        to_what = '"to" list for {}'.format(what)
        flows_to = []
        for node_yml in check_list(rule['to'], to_what):
            node_what = 'node {} in {}'.format(node_yml, to_what)
            nodes = _parse_iflow_nodes(check_str(node_yml, node_what),
                                       node_what, insn_operands)
            flows_to.extend(nodes)

        from_what = '"from" list for {}'.format(what)
        flows_from = []
        for node_yml in check_list(rule['from'], from_what):
            node_what = 'node {} in {}'.format(node_yml, from_what)
            nodes = _parse_iflow_nodes(check_str(node_yml, node_what),
                                       node_what, insn_operands)
            flows_from.extend(nodes)

        test_yml = rule.get('test', None)
        if test_yml is None:
            tests = []
        else:
            test_what = 'test field for {}'.format(what)
            tests = [
                _parse_iflow_test(t, test_what, insn_operands)
                for t in check_list(test_yml, test_what)
            ]
        return InsnInformationFlowRule(flows_to, flows_from, MultiTest(tests))
Exemple #2
0
    def __init__(self, yml: object, schemes: EncSchemes, mnemonic: str):
        what = 'encoding for instruction {!r}'.format(mnemonic)
        yd = check_keys(yml, what, ['scheme', 'mapping'], [])

        scheme_what = 'encoding scheme for instruction {!r}'.format(mnemonic)
        scheme_name = check_str(yd['scheme'], scheme_what)
        scheme_fields = schemes.resolve(scheme_name, mnemonic)

        what = 'encoding mapping for instruction {!r}'.format(mnemonic)

        # Check we've got exactly the right fields for the scheme
        ydm = check_keys(yd['mapping'], what, list(scheme_fields.op_fields),
                         [])

        # Build a map from operand name to the name of a field that uses it.
        self.op_to_field_name = {}  # type: Dict[str, str]

        self.fields = {}
        for field_name, scheme_field in scheme_fields.fields.items():
            if scheme_field.value is not None:
                field = EncodingField(scheme_field.value, scheme_field)
            else:
                field_what = ('value for {} field in '
                              'encoding for instruction {!r}'.format(
                                  field_name, mnemonic))
                ef_val = check_str(ydm[field_name], field_what)
                scheme_field = scheme_fields.fields[field_name]
                field = EncodingField.from_yaml(ef_val, scheme_field,
                                                field_what)

                # If the field's value has type str, the field uses an operand
                # rather than a literal. Check for linearity and store the
                # mapping.
                if isinstance(field.value, str):
                    other_field_name = self.op_to_field_name.get(field.value)
                    if other_field_name is not None:
                        raise ValueError(
                            'Non-linear use of operand with name '
                            '{!r} in encoding for instruction '
                            '{!r}: used in fields {!r} and {!r}.'.format(
                                field.value, mnemonic, other_field_name,
                                field_name))
                    self.op_to_field_name[field.value] = field_name

            self.fields[field_name] = field
Exemple #3
0
    def from_yaml(yml: object, what: str) -> 'EncSchemeField':
        # This is either represented as a dict in the YAML or as a bare string.
        bits_what = 'bits for {}'.format(what)
        value_what = 'value for {}'.format(what)

        if isinstance(yml, dict):
            yd = check_keys(yml, what, ['bits'], ['value'])

            bits_yml = yd['bits']
            if not (isinstance(bits_yml, str) or isinstance(bits_yml, int)):
                raise ValueError(
                    '{} is of type {}, not a string or int.'.format(
                        bits_what,
                        type(bits_yml).__name__))

            # We require value to be given as a string because it's supposed to
            # be in base 2, and PyYAML will parse 111 as one-hundred and
            # eleven, 011 as 9 and 0x11 as 17. Aargh!
            raw_value = None
            val_yml = yd.get('value')
            if val_yml is not None:
                if not isinstance(val_yml, str):
                    raise ValueError(
                        "{} is of type {}, but must be a string "
                        "(we don't allow automatic conversion "
                        "because YAML's int conversion assumes "
                        "base 10 and value should be in base 2).".format(
                            value_what,
                            type(val_yml).__name__))
                raw_value = val_yml

        elif isinstance(yml, str) or isinstance(yml, int):
            bits_yml = yml
            raw_value = None
        else:
            raise ValueError('{} is a {}, but should be a '
                             'dict, string or integer.'.format(
                                 what,
                                 type(yml).__name__))

        # The bits field is usually parsed as a string ("10-4", or similar).
        # But if it's a bare integer then YAML will parse it as an int. That's
        # fine, but we turn it back into a string to be re-parsed by BitRanges.
        assert isinstance(bits_yml, str) or isinstance(bits_yml, int)

        bits = BitRanges.from_yaml(str(bits_yml), bits_what)
        value = None
        if raw_value is not None:
            value = BoolLiteral.from_string(raw_value, value_what)
            if bits.width != value.width:
                raise ValueError('{} has bits that imply a width of {}, but '
                                 'a value with width {}.'.format(
                                     what, bits.width, value.width))

        return EncSchemeField(bits, value)
Exemple #4
0
    def __init__(self, yml: object, mnemonic: str,
                 insn_encoding: Optional[Encoding]) -> None:
        # The YAML representation should be a string (a bare operand name) or a
        # dict.
        what = 'operand for {!r} instruction'.format(mnemonic)
        if isinstance(yml, str):
            name = yml.lower()
            abbrev = None
            op_type = None
            doc = None
            pc_rel = False
            op_what = '{!r} {}'.format(name, what)
        elif isinstance(yml, dict):
            yd = check_keys(yml, what, ['name'],
                            ['type', 'pc-rel', 'doc', 'abbrev'])
            name = check_str(yd['name'], 'name of ' + what).lower()

            op_what = '{!r} {}'.format(name, what)
            abbrev = get_optional_str(yd, 'abbrev', op_what)
            if abbrev is not None:
                abbrev = abbrev.lower()
            op_type = get_optional_str(yd, 'type', op_what)
            pc_rel = check_bool(yd.get('pc-rel', False),
                                'pc-rel field of ' + op_what)
            doc = get_optional_str(yd, 'doc', op_what)

        # If there is an encoding, look up the encoding scheme field that
        # corresponds to this operand.
        enc_scheme_field = None
        if insn_encoding is not None:
            field_name = insn_encoding.op_to_field_name.get(name)
            if field_name is None:
                raise ValueError('The {!r} instruction has an operand called '
                                 '{!r}, but the associated encoding has no '
                                 'field that encodes it.'.format(
                                     mnemonic, name))
            enc_scheme_field = insn_encoding.fields[field_name].scheme_field

        if abbrev is not None:
            if name == abbrev:
                raise ValueError('Operand {!r} of the {!r} instruction has '
                                 'an abbreviated name the same as its '
                                 'actual name.'.format(name, mnemonic))
        self.name = name
        self.abbrev = abbrev
        self.op_type = make_operand_type(op_type, pc_rel, name, mnemonic,
                                         enc_scheme_field)
        self.doc = doc
Exemple #5
0
    def __init__(self,
                 cfg_dir: str,
                 path: str,
                 known_names: Set[str],
                 yml: object):
        yd = check_keys(yml, 'top-level',
                        [],
                        ['gen-weights', 'insn-weights', 'inherit', 'ranges'])

        # The most general form for the inherit field is a list of dictionaries
        # that get parsed into Inheritance objects. As a shorthand, these
        # dictionaries can be represented strings (which implies a weight of
        # 1). As an even shorter shorthand, if you only have one possible
        # inheritance (so don't care about defining weights anyway), you can
        # represent it as just a string.
        y_inherit = yd.get('inherit', [])
        if isinstance(y_inherit, str):
            inherit_lst = [y_inherit]
        elif isinstance(y_inherit, list):
            inherit_lst = y_inherit
        else:
            raise ValueError('inherit field for config at {} '
                             'is not a string or list.'
                             .format(path))

        inhs = [Inheritance(idx + 1, elt)
                for idx, elt in enumerate(inherit_lst)]
        if not inhs:
            merged_ancestors = None
        else:
            # There's at least one possible list of parents. Pick one.
            parents = random.choices([i.names for i in inhs],
                                     weights=[i.weight for i in inhs])[0]
            # The Inheritance class constructor ensures that parents is
            # nonempty.
            assert parents
            merged_ancestors = Config.load_and_merge(cfg_dir,
                                                     parents, known_names)

        self.path = path
        self.gen_weights = Weights('gen-weights', yd.get('gen-weights', {}))
        self.insn_weights = Weights('insn-weights', yd.get('insn-weights', {}))
        self.ranges = MinMaxes('ranges', yd.get('ranges', {}))

        if merged_ancestors is not None:
            self.merge(merged_ancestors)
Exemple #6
0
    def __init__(self, item_num: int, yml: object):
        if isinstance(yml, str):
            cfgs = yml
            weight = 1.0
        else:
            yd = check_keys(yml, 'parent', ['cfgs'], ['weight'])
            cfgs = check_str(yd['cfgs'],
                             'cfgs field for inheritance list {}'
                             .format(item_num))
            yw = yd.get('weight', 1.0)
            if isinstance(yw, float) or isinstance(yw, int):
                weight = float(yw)
            elif isinstance(yw, str):
                try:
                    weight = float(yw)
                except ValueError:
                    raise ValueError('The weight in inheritance list {} is '
                                     '{!r}, not a valid float.'
                                     .format(item_num, yw))
            else:
                raise ValueError('The weight in inheritance list {} is '
                                 'not a number or a string.'
                                 .format(item_num))

            if weight <= 0:
                raise ValueError('The weight in inheritance list {} is '
                                 '{}, which is not positive.'
                                 .format(item_num, weight))

        # cfgs should be a nonempty list of config names, separated by '+'
        # signs.
        if not cfgs:
            raise ValueError('Empty list of config names')

        self.names = cfgs.split('+')
        self.weight = weight

        # Check that each of the names in our list is nonempty (we'll get a
        # less confusing error if we spot that here)
        for n in self.names:
            if not n:
                raise ValueError('Empty name in list of config names: {}'
                                 .format(self.names))
Exemple #7
0
    def __init__(self, yml: object, name: str) -> None:
        what = 'encoding scheme {!r}'.format(name)
        yd = check_keys(yml, what, [], ['parents', 'fields'])

        if not yd:
            raise ValueError('{} has no parents or fields.'.format(what))

        fields_yml = yd.get('fields')
        self.direct_fields = (EncSchemeFields.from_yaml(fields_yml, name)
                              if fields_yml is not None else
                              EncSchemeFields.empty())

        parents_yml = yd.get('parents')
        parents_what = 'parents of {}'.format(what)
        parents = ([
            EncSchemeImport(y, name)
            for y in check_list(parents_yml, parents_what)
        ] if parents_yml is not None else [])
        self.parents = index_list(parents_what, parents,
                                  lambda imp: imp.parent)
Exemple #8
0
    def from_yaml(yml: object, what: str) -> 'LSUDesc':
        yd = check_keys(yml, what, ['type', 'target'], ['bytes'])

        type_what = 'type field for ' + what
        lsu_type = check_str(yd['type'], type_what)
        if lsu_type not in LSUDesc.TYPES:
            raise ValueError('{} is {!r}, but should be one '
                             'of the following: {}.'
                             .format(type_what, lsu_type,
                                     ', '.join(repr(t)
                                               for t in LSUDesc.TYPES)))

        target = yd['target']
        if isinstance(target, str):
            target_parts = [target]
        else:
            target_parts = []
            if not isinstance(target, list):
                raise ValueError('target field for {} should be a string or a '
                                 'list of strings, but actually has type {}.'
                                 .format(what, type(target).__name__))
            for idx, part in enumerate(target):
                elt_what = ('element {} of the target list for {}'
                            .format(idx, what))
                target_parts.append(check_str(part, elt_what))

        if lsu_type.startswith('mem-'):
            if 'bytes' not in yd:
                raise ValueError('{} defines a memory operation, so requires '
                                 'a bytes field (how many bytes does this '
                                 'operation touch?)'
                                 .format(what))
            idx_width = check_int(yd['bytes'], 'bytes field for ' + what)
        else:
            if 'bytes' in yd:
                raise ValueError("{} isn't a memory operation, so cannot have "
                                 "a bytes field."
                                 .format(what))
            idx_width = 1

        return LSUDesc(lsu_type, target_parts, idx_width)
Exemple #9
0
    def __init__(self, path: str, encoding_schemes: Optional[EncSchemes],
                 yml: object) -> None:

        yd = check_keys(yml, 'insn-group', ['key', 'title', 'doc', 'insns'],
                        [])
        self.key = check_str(yd['key'], 'insn-group key')
        self.title = check_str(yd['title'], 'insn-group title')
        self.doc = check_str(yd['doc'], 'insn-group doc')

        insns_what = 'insns field for {!r} instruction group'.format(self.key)
        insns_rel_path = check_str(yd['insns'], insns_what)
        insns_path = os.path.normpath(
            os.path.join(os.path.dirname(path), insns_rel_path))
        insns_yaml = load_yaml(insns_path, insns_what)
        try:
            self.insns = [
                Insn(i, encoding_schemes)
                for i in check_list(insns_yaml, insns_what)
            ]
        except ValueError as err:
            raise RuntimeError(
                'Invalid schema in YAML file at {!r}: {}'.format(
                    insns_path, err)) from None
Exemple #10
0
    def __init__(self, path: str, yml: object) -> None:
        yd = check_keys(yml, 'top-level', ['insn-groups'],
                        ['encoding-schemes'])

        enc_scheme_path = get_optional_str(yd, 'encoding-schemes', 'top-level')
        if enc_scheme_path is None:
            self.encoding_schemes = None
        else:
            src_dir = os.path.dirname(path)
            es_path = os.path.normpath(os.path.join(src_dir, enc_scheme_path))
            es_yaml = load_yaml(es_path, 'encoding schemes')
            try:
                self.encoding_schemes = EncSchemes(es_yaml)
            except ValueError as err:
                raise RuntimeError(
                    'Invalid schema in YAML file at {!r}: {}'.format(
                        es_path, err)) from None

        self.groups = InsnGroups(path, self.encoding_schemes,
                                 yd['insn-groups'])

        # The instructions are grouped by instruction group and stored in
        # self.groups. Most of the time, however, we just want "an OTBN
        # instruction" and don't care about the group. Retrieve them here.
        self.insns = []
        for grp in self.groups.groups:
            self.insns += grp.insns

        self.mnemonic_to_insn = index_list('insns', self.insns,
                                           lambda insn: insn.mnemonic.lower())

        masks_exc, ambiguities = self._get_masks()
        if ambiguities:
            raise ValueError('Ambiguous instruction encodings: ' +
                             ', '.join(ambiguities))

        self._masks = masks_exc
Exemple #11
0
    def __init__(self, yml: object,
                 encoding_schemes: Optional[EncSchemes]) -> None:
        yd = check_keys(yml, 'instruction', ['mnemonic', 'operands'], [
            'group', 'rv32i', 'synopsis', 'syntax', 'doc', 'errs', 'note',
            'encoding', 'glued-ops', 'literal-pseudo-op', 'python-pseudo-op',
            'lsu', 'straight-line'
        ])

        self.mnemonic = check_str(yd['mnemonic'], 'mnemonic for instruction')

        what = 'instruction with mnemonic {!r}'.format(self.mnemonic)

        encoding_yml = yd.get('encoding')
        self.encoding = None
        if encoding_yml is not None:
            if encoding_schemes is None:
                raise ValueError(
                    '{} specifies an encoding, but the file '
                    'didn\'t specify any encoding schemes.'.format(what))

            self.encoding = Encoding(encoding_yml, encoding_schemes,
                                     self.mnemonic)

        self.operands = [
            Operand(y, self.mnemonic, self.encoding)
            for y in check_list(yd['operands'], 'operands for ' + what)
        ]
        self.name_to_operand = index_list('operands for ' + what,
                                          self.operands, lambda op: op.name)

        # The call to index_list has checked that operand names are distinct.
        # We also need to check that no operand abbreviation clashes with
        # anything else.
        operand_names = set(self.name_to_operand.keys())
        for op in self.operands:
            if op.abbrev is not None:
                if op.abbrev in operand_names:
                    raise ValueError('The name {!r} appears as an operand or '
                                     'abbreviation more than once for '
                                     'instruction {!r}.'.format(
                                         op.abbrev, self.mnemonic))
                operand_names.add(op.abbrev)

        if self.encoding is not None:
            # If we have an encoding, we passed it to the Operand constructors
            # above. This ensured that each operand has a field. However, it's
            # possible that there are some operand names the encoding mentions
            # that don't actually have an operand. Check for that here.
            missing_ops = (set(self.encoding.op_to_field_name.keys()) -
                           set(self.name_to_operand.keys()))
            if missing_ops:
                raise ValueError('Encoding scheme for {} specifies '
                                 'some non-existent operands: {}.'.format(
                                     what, ', '.join(list(missing_ops))))

        self.rv32i = check_bool(yd.get('rv32i', False),
                                'rv32i flag for ' + what)
        self.glued_ops = check_bool(yd.get('glued-ops', False),
                                    'glued-ops flag for ' + what)
        self.synopsis = get_optional_str(yd, 'synopsis', what)
        self.doc = get_optional_str(yd, 'doc', what)
        self.note = get_optional_str(yd, 'note', what)

        self.errs = None
        if 'errs' in yd:
            errs_what = 'errs field for ' + what
            y_errs = check_list(yd.get('errs'), errs_what)
            self.errs = []
            for idx, err_desc in enumerate(y_errs):
                self.errs.append(
                    check_str(err_desc,
                              'element {} of the {}'.format(idx, errs_what)))

        raw_syntax = get_optional_str(yd, 'syntax', what)
        if raw_syntax is not None:
            self.syntax = InsnSyntax.from_yaml(self.mnemonic,
                                               raw_syntax.strip())
        else:
            self.syntax = InsnSyntax.from_list(
                [op.name for op in self.operands])

        pattern, op_to_grp = self.syntax.asm_pattern()
        self.asm_pattern = re.compile(pattern)
        self.pattern_op_to_grp = op_to_grp

        # Make sure we have exactly the operands we expect.
        if set(self.name_to_operand.keys()) != self.syntax.op_set:
            raise ValueError("Operand syntax for {!r} doesn't have the "
                             "same list of operands as given in the "
                             "operand list. The syntax uses {}, "
                             "but the list of operands gives {}.".format(
                                 self.mnemonic,
                                 list(sorted(self.syntax.op_set)),
                                 list(sorted(self.name_to_operand))))

        self.python_pseudo_op = check_bool(yd.get('python-pseudo-op', False),
                                           'python-pseudo-op flag for ' + what)
        if self.python_pseudo_op and self.encoding is not None:
            raise ValueError('{} specifies an encoding and also sets '
                             'python-pseudo-op.'.format(what))

        lpo = yd.get('literal-pseudo-op')
        if lpo is None:
            self.literal_pseudo_op = None
        else:
            lpo_lst = check_list(lpo, 'literal-pseudo-op flag for ' + what)
            for idx, item in enumerate(lpo_lst):
                if not isinstance(item, str):
                    raise ValueError(
                        'Item {} of literal-pseudo-op list for '
                        '{} is {!r}, which is not a string.'.format(
                            idx, what, item))
            self.literal_pseudo_op = cast(Optional[List[str]], lpo_lst)

            if self.python_pseudo_op:
                raise ValueError('{} specifies both python-pseudo-op and '
                                 'literal-pseudo-op.'.format(what))
            if self.encoding is not None:
                raise ValueError('{} specifies both an encoding and '
                                 'literal-pseudo-op.'.format(what))

        lsu_yaml = yd.get('lsu', None)
        if lsu_yaml is None:
            self.lsu = None
        else:
            self.lsu = LSUDesc.from_yaml(lsu_yaml,
                                         'lsu field for {}'.format(what))
            for idx, op_name in enumerate(self.lsu.target):
                if op_name not in self.name_to_operand:
                    raise ValueError('element {} of the target for the lsu '
                                     'field for {} is {!r}, which is not a '
                                     'operand name of the instruction.'.format(
                                         idx, what, op_name))

        self.straight_line = yd.get('straight-line', True)