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))
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
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)
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
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)
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))
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)
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)
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
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
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)