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