xref: /aosp_15_r20/external/emboss/compiler/util/attribute_util.py (revision 99e0aae7469b87d12f0ad23e61142c2d74c1ef70)
1# Copyright 2019 Google LLC
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Module which verifies attributes in an Emboss IR.
16
17The main entry point is check_attributes_in_ir(), which checks attributes in an
18IR.
19"""
20
21from compiler.util import error
22from compiler.util import ir_data
23from compiler.util import ir_data_utils
24from compiler.util import ir_util
25from compiler.util import traverse_ir
26
27
28# Error messages used by multiple attribute type checkers.
29_BAD_TYPE_MESSAGE = "Attribute '{name}' must have {type} value."
30_MUST_BE_CONSTANT_MESSAGE = "Attribute '{name}' must have a constant value."
31
32
33def _attribute_name_for_errors(attr):
34  if ir_data_utils.reader(attr).back_end.text:
35    return f"({attr.back_end.text}) {attr.name.text}"
36  else:
37    return attr.name.text
38
39
40# Attribute type checkers
41def _is_constant_boolean(attr, module_source_file):
42  """Checks if the given attr is a constant boolean."""
43  if not attr.value.expression.type.boolean.HasField("value"):
44    return [[error.error(module_source_file,
45                         attr.value.source_location,
46                         _BAD_TYPE_MESSAGE.format(
47                             name=_attribute_name_for_errors(attr),
48                             type="a constant boolean"))]]
49  return []
50
51
52def _is_boolean(attr, module_source_file):
53  """Checks if the given attr is a boolean."""
54  if attr.value.expression.type.WhichOneof("type") != "boolean":
55    return [[error.error(module_source_file,
56                         attr.value.source_location,
57                         _BAD_TYPE_MESSAGE.format(
58                             name=_attribute_name_for_errors(attr),
59                             type="a boolean"))]]
60  return []
61
62
63def _is_constant_integer(attr, module_source_file):
64  """Checks if the given attr is an integer constant expression."""
65  if (not attr.value.HasField("expression") or
66      attr.value.expression.type.WhichOneof("type") != "integer"):
67    return [[error.error(module_source_file,
68                         attr.value.source_location,
69                         _BAD_TYPE_MESSAGE.format(
70                             name=_attribute_name_for_errors(attr),
71                             type="an integer"))]]
72  if not ir_util.is_constant(attr.value.expression):
73    return [[error.error(module_source_file,
74                         attr.value.source_location,
75                         _MUST_BE_CONSTANT_MESSAGE.format(
76                             name=_attribute_name_for_errors(attr)))]]
77  return []
78
79
80def _is_string(attr, module_source_file):
81  """Checks if the given attr is a string."""
82  if not attr.value.HasField("string_constant"):
83    return [[error.error(module_source_file,
84                         attr.value.source_location,
85                         _BAD_TYPE_MESSAGE.format(
86                             name=_attribute_name_for_errors(attr),
87                             type="a string"))]]
88  return []
89
90
91# Provide more readable names for these functions when used in attribute type
92# specifiers.
93BOOLEAN_CONSTANT = _is_constant_boolean
94BOOLEAN = _is_boolean
95INTEGER_CONSTANT = _is_constant_integer
96STRING = _is_string
97
98
99def string_from_list(valid_values):
100  """Checks if the given attr has one of the valid_values."""
101  def _string_from_list(attr, module_source_file):
102    if ir_data_utils.reader(attr).value.string_constant.text not in valid_values:
103      return [[error.error(module_source_file,
104                           attr.value.source_location,
105                           "Attribute '{name}' must be '{options}'.".format(
106                               name=_attribute_name_for_errors(attr),
107                               options="' or '".join(sorted(valid_values))))]]
108    return []
109  return _string_from_list
110
111
112def check_attributes_in_ir(ir,
113                           *,
114                           back_end=None,
115                           types=None,
116                           module_attributes=None,
117                           struct_attributes=None,
118                           bits_attributes=None,
119                           enum_attributes=None,
120                           enum_value_attributes=None,
121                           external_attributes=None,
122                           structure_virtual_field_attributes=None,
123                           structure_physical_field_attributes=None):
124  """Performs basic checks on all attributes in the given ir.
125
126  This function calls _check_attributes on each attribute list in ir.
127
128  Arguments:
129    ir: An ir_data.EmbossIr to check.
130    back_end: A string specifying the attribute qualifier to check (such as
131        `cpp` for `[(cpp) namespace = "foo"]`), or None to check unqualified
132        attributes.
133
134        Attributes with a different qualifier will not be checked.
135    types: A map from attribute names to validators, such as:
136        {
137            "maximum_bits": attribute_util.INTEGER_CONSTANT,
138            "requires": attribute_util.BOOLEAN,
139        }
140    module_attributes: A set of (attribute_name, is_default) tuples specifying
141        the attributes that are allowed at module scope.
142    struct_attributes: A set of (attribute_name, is_default) tuples specifying
143        the attributes that are allowed at `struct` scope.
144    bits_attributes: A set of (attribute_name, is_default) tuples specifying
145        the attributes that are allowed at `bits` scope.
146    enum_attributes: A set of (attribute_name, is_default) tuples specifying
147        the attributes that are allowed at `enum` scope.
148    enum_value_attributes: A set of (attribute_name, is_default) tuples
149        specifying the attributes that are allowed at the scope of enum values.
150    external_attributes: A set of (attribute_name, is_default) tuples
151        specifying the attributes that are allowed at `external` scope.
152    structure_virtual_field_attributes: A set of (attribute_name, is_default)
153        tuples specifying the attributes that are allowed at the scope of
154        virtual fields (`let` fields) in structures (both `struct` and `bits`).
155    structure_physical_field_attributes: A set of (attribute_name, is_default)
156        tuples specifying the attributes that are allowed at the scope of
157        physical fields in structures (both `struct` and `bits`).
158
159  Returns:
160    A list of lists of error.error, or an empty list if there were no errors.
161  """
162
163  def check_module(module, errors):
164    errors.extend(_check_attributes(
165        module.attribute, types, back_end, module_attributes,
166        "module '{}'".format(
167            module.source_file_name), module.source_file_name))
168
169  def check_type_definition(type_definition, source_file_name, errors):
170    if type_definition.HasField("structure"):
171      if type_definition.addressable_unit == ir_data.AddressableUnit.BYTE:
172        errors.extend(_check_attributes(
173            type_definition.attribute, types, back_end, struct_attributes,
174            "struct '{}'".format(
175                type_definition.name.name.text), source_file_name))
176      elif type_definition.addressable_unit == ir_data.AddressableUnit.BIT:
177        errors.extend(_check_attributes(
178            type_definition.attribute, types, back_end, bits_attributes,
179            "bits '{}'".format(
180                type_definition.name.name.text), source_file_name))
181      else:
182        assert False, "Unexpected addressable_unit '{}'".format(
183            type_definition.addressable_unit)
184    elif type_definition.HasField("enumeration"):
185      errors.extend(_check_attributes(
186          type_definition.attribute, types, back_end, enum_attributes,
187          "enum '{}'".format(
188              type_definition.name.name.text), source_file_name))
189    elif type_definition.HasField("external"):
190      errors.extend(_check_attributes(
191          type_definition.attribute, types, back_end, external_attributes,
192          "external '{}'".format(
193              type_definition.name.name.text), source_file_name))
194
195  def check_struct_field(field, source_file_name, errors):
196    if ir_util.field_is_virtual(field):
197      field_attributes = structure_virtual_field_attributes
198      field_adjective = "virtual "
199    else:
200      field_attributes = structure_physical_field_attributes
201      field_adjective = ""
202    errors.extend(_check_attributes(
203        field.attribute, types, back_end, field_attributes,
204        "{}struct field '{}'".format(field_adjective, field.name.name.text),
205        source_file_name))
206
207  def check_enum_value(value, source_file_name, errors):
208    errors.extend(_check_attributes(
209        value.attribute, types, back_end, enum_value_attributes,
210        "enum value '{}'".format(value.name.name.text), source_file_name))
211
212  errors = []
213  # TODO(bolms): Add a check that only known $default'ed attributes are
214  # used.
215  traverse_ir.fast_traverse_ir_top_down(
216      ir, [ir_data.Module], check_module,
217      parameters={"errors": errors})
218  traverse_ir.fast_traverse_ir_top_down(
219      ir, [ir_data.TypeDefinition], check_type_definition,
220      parameters={"errors": errors})
221  traverse_ir.fast_traverse_ir_top_down(
222      ir, [ir_data.Field], check_struct_field,
223      parameters={"errors": errors})
224  traverse_ir.fast_traverse_ir_top_down(
225      ir, [ir_data.EnumValue], check_enum_value,
226      parameters={"errors": errors})
227  return errors
228
229
230def _check_attributes(attribute_list, types, back_end, attribute_specs,
231                      context_name, module_source_file):
232  """Performs basic checks on the given list of attributes.
233
234  Checks the given attribute_list for duplicates, unknown attributes, attributes
235  with incorrect type, and attributes whose values are not constant.
236
237  Arguments:
238    attribute_list: An iterable of ir_data.Attribute.
239    back_end: The qualifier for attributes to check, or None.
240    attribute_specs: A dict of attribute names to _Attribute structures
241      specifying the allowed attributes.
242    context_name: A name for the context of these attributes, such as "struct
243      'Foo'" or "module 'm.emb'".  Used in error messages.
244    module_source_file: The value of module.source_file_name from the module
245      containing 'attribute_list'.  Used in error messages.
246
247  Returns:
248    A list of lists of error.Errors.  An empty list indicates no errors were
249    found.
250  """
251  if attribute_specs is None:
252    attribute_specs = []
253  errors = []
254  already_seen_attributes = {}
255  for attr in attribute_list:
256    field_checker = ir_data_utils.reader(attr)
257    if field_checker.back_end.text:
258      if attr.back_end.text != back_end:
259        continue
260    else:
261      if back_end is not None:
262        continue
263    attribute_name = _attribute_name_for_errors(attr)
264    attr_key = (field_checker.name.text, field_checker.is_default)
265    if attr_key in already_seen_attributes:
266      original_attr = already_seen_attributes[attr_key]
267      errors.append([
268          error.error(module_source_file,
269                      attr.source_location,
270                      "Duplicate attribute '{}'.".format(attribute_name)),
271          error.note(module_source_file,
272                     original_attr.source_location,
273                     "Original attribute")])
274      continue
275    already_seen_attributes[attr_key] = attr
276
277    if attr_key not in attribute_specs:
278      if attr.is_default:
279        error_message = "Attribute '{}' may not be defaulted on {}.".format(
280            attribute_name, context_name)
281      else:
282        error_message = "Unknown attribute '{}' on {}.".format(attribute_name,
283                                                               context_name)
284      errors.append([error.error(module_source_file,
285                                 attr.name.source_location,
286                                 error_message)])
287    else:
288      errors.extend(types[attr.name.text](attr, module_source_file))
289  return errors
290
291
292def gather_default_attributes(obj, defaults):
293  """Gathers default attributes for an IR object
294
295  This is designed to be able to be used as-is as an incidental action in an IR
296  traversal to accumulate defaults for child nodes.
297
298  Arguments:
299    defaults: A dict of `{ "defaults": { attr.name.text: attr } }`
300
301  Returns:
302    A dict of `{ "defaults": { attr.name.text: attr } }` with any defaults
303    provided by `obj` added/overridden.
304  """
305  defaults = defaults.copy()
306  for attr in obj.attribute:
307    if attr.is_default:
308      defaulted_attr = ir_data_utils.copy(attr)
309      defaulted_attr.is_default = False
310      defaults[attr.name.text] = defaulted_attr
311  return {"defaults": defaults}
312