| # Copyright 2019 Google LLC |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Adds auto-generated virtual fields to the IR.""" |
| |
| from compiler.front_end import attributes |
| from compiler.util import error |
| from compiler.util import expression_parser |
| from compiler.util import ir_data |
| from compiler.util import ir_data_utils |
| from compiler.util import ir_util |
| from compiler.util import traverse_ir |
| |
| |
| def _mark_as_synthetic(proto): |
| """Marks all source_locations in proto with is_synthetic=True.""" |
| if not isinstance(proto, ir_data.Message): |
| return |
| if hasattr(proto, "source_location"): |
| ir_data_utils.builder(proto).source_location.is_synthetic = True |
| for spec, value in ir_data_utils.get_set_fields(proto): |
| if spec.name != "source_location" and spec.is_dataclass: |
| if spec.is_sequence: |
| for i in value: |
| _mark_as_synthetic(i) |
| else: |
| _mark_as_synthetic(value) |
| |
| |
| def _skip_text_output_attribute(): |
| """Returns the IR for a [text_output: "Skip"] attribute.""" |
| result = ir_data.Attribute( |
| name=ir_data.Word(text=attributes.TEXT_OUTPUT), |
| value=ir_data.AttributeValue(string_constant=ir_data.String(text="Skip")), |
| ) |
| _mark_as_synthetic(result) |
| return result |
| |
| |
| # The existence condition for an alias for an anonymous bits' field is the union |
| # of the existence condition for the anonymous bits and the existence condition |
| # for the field within. The 'x' and 'x.y' are placeholders here; they'll be |
| # overwritten in _add_anonymous_aliases. |
| _ANONYMOUS_BITS_ALIAS_EXISTENCE_SKELETON = expression_parser.parse( |
| "$present(x) && $present(x.y)" |
| ) |
| |
| |
| def _add_anonymous_aliases(structure, type_definition): |
| """Adds synthetic alias fields for all fields in anonymous fields. |
| |
| This essentially completes the rewrite of this: |
| |
| struct Foo: |
| 0 [+4] bits: |
| 0 [+1] Flag low |
| 31 [+1] Flag high |
| |
| Into this: |
| |
| struct Foo: |
| bits EmbossReservedAnonymous0: |
| [text_output: "Skip"] |
| 0 [+1] Flag low |
| 31 [+1] Flag high |
| 0 [+4] EmbossReservedAnonymous0 emboss_reserved_anonymous_1 |
| let low = emboss_reserved_anonymous_1.low |
| let high = emboss_reserved_anonymous_1.high |
| |
| Note that this pass runs very, very early -- even before symbols have been |
| resolved -- so very little in ir_util will work at this point. |
| |
| Arguments: |
| structure: The ir_data.Structure on which to synthesize fields. |
| type_definition: The ir_data.TypeDefinition containing structure. |
| |
| Returns: |
| None |
| """ |
| new_fields = [] |
| for field in structure.field: |
| new_fields.append(field) |
| if not field.name.is_anonymous: |
| continue |
| field.attribute.extend([_skip_text_output_attribute()]) |
| for subtype in type_definition.subtype: |
| if ( |
| subtype.name.name.text |
| == field.type.atomic_type.reference.source_name[-1].text |
| ): |
| field_type = subtype |
| break |
| else: |
| assert False, ( |
| "Unable to find corresponding type {} for anonymous field " |
| "in {}.".format(field.type.atomic_type.reference, type_definition) |
| ) |
| anonymous_reference = ir_data.Reference(source_name=[field.name.name]) |
| anonymous_field_reference = ir_data.FieldReference(path=[anonymous_reference]) |
| for subfield in field_type.structure.field: |
| alias_field_reference = ir_data.FieldReference( |
| path=[ |
| anonymous_reference, |
| ir_data.Reference(source_name=[subfield.name.name]), |
| ] |
| ) |
| new_existence_condition = ir_data_utils.copy( |
| _ANONYMOUS_BITS_ALIAS_EXISTENCE_SKELETON |
| ) |
| existence_clauses = ir_data_utils.builder( |
| new_existence_condition |
| ).function.args |
| existence_clauses[0].function.args[0].field_reference.CopyFrom( |
| anonymous_field_reference |
| ) |
| existence_clauses[1].function.args[0].field_reference.CopyFrom( |
| alias_field_reference |
| ) |
| new_read_transform = ir_data.Expression( |
| field_reference=ir_data_utils.copy(alias_field_reference) |
| ) |
| # This treats *most* of the alias field as synthetic, but not its name(s): |
| # leaving the name(s) as "real" means that symbol collisions with the |
| # surrounding structure will be properly reported to the user. |
| _mark_as_synthetic(new_existence_condition) |
| _mark_as_synthetic(new_read_transform) |
| new_alias = ir_data.Field( |
| read_transform=new_read_transform, |
| existence_condition=new_existence_condition, |
| name=ir_data_utils.copy(subfield.name), |
| ) |
| if subfield.HasField("abbreviation"): |
| ir_data_utils.builder(new_alias).abbreviation.CopyFrom( |
| subfield.abbreviation |
| ) |
| _mark_as_synthetic(new_alias.existence_condition) |
| _mark_as_synthetic(new_alias.read_transform) |
| new_fields.append(new_alias) |
| # Since the alias field's name(s) are "real," it is important to mark the |
| # original field's name(s) as synthetic, to avoid duplicate error |
| # messages. |
| _mark_as_synthetic(subfield.name) |
| if subfield.HasField("abbreviation"): |
| _mark_as_synthetic(subfield.abbreviation) |
| del structure.field[:] |
| structure.field.extend(new_fields) |
| |
| |
| _SIZE_BOUNDS = { |
| "$max_size_in_bits": expression_parser.parse("$upper_bound($size_in_bits)"), |
| "$min_size_in_bits": expression_parser.parse("$lower_bound($size_in_bits)"), |
| "$max_size_in_bytes": expression_parser.parse("$upper_bound($size_in_bytes)"), |
| "$min_size_in_bytes": expression_parser.parse("$lower_bound($size_in_bytes)"), |
| } |
| |
| |
| def _add_size_bound_virtuals(structure, type_definition): |
| """Adds ${min,max}_size_in_{bits,bytes} virtual fields to structure.""" |
| names = { |
| ir_data.AddressableUnit.BIT: ("$max_size_in_bits", "$min_size_in_bits"), |
| ir_data.AddressableUnit.BYTE: ("$max_size_in_bytes", "$min_size_in_bytes"), |
| } |
| for name in names[type_definition.addressable_unit]: |
| bound_field = ir_data.Field( |
| read_transform=_SIZE_BOUNDS[name], |
| name=ir_data.NameDefinition(name=ir_data.Word(text=name)), |
| existence_condition=expression_parser.parse("true"), |
| attribute=[_skip_text_output_attribute()], |
| ) |
| _mark_as_synthetic(bound_field.read_transform) |
| structure.field.extend([bound_field]) |
| |
| |
| # Each non-virtual field in a structure generates a clause that is passed to |
| # `$max()` in the definition of `$size_in_bits`/`$size_in_bytes`. Additionally, |
| # the `$max()` call is seeded with a `0` argument: this ensures that |
| # `$size_in_units` is never negative, and ensures that structures with no |
| # physical fields don't end up with a zero-argument `$max()` call, which would |
| # fail type checking. |
| _SIZE_CLAUSE_SKELETON = expression_parser.parse( |
| "existence_condition ? start + size : 0" |
| ) |
| _SIZE_SKELETON = expression_parser.parse("$max(0)") |
| |
| |
| def _add_size_virtuals(structure, type_definition): |
| """Adds a $size_in_bits or $size_in_bytes virtual field to structure.""" |
| names = { |
| ir_data.AddressableUnit.BIT: "$size_in_bits", |
| ir_data.AddressableUnit.BYTE: "$size_in_bytes", |
| } |
| size_field_name = names[type_definition.addressable_unit] |
| size_clauses = [] |
| for field in structure.field: |
| # Virtual fields do not have a physical location, and thus do not contribute |
| # to the size of the structure. |
| if ir_util.field_is_virtual(field): |
| continue |
| size_clause_ir = ir_data_utils.copy(_SIZE_CLAUSE_SKELETON) |
| size_clause = ir_data_utils.builder(size_clause_ir) |
| # Copy the appropriate clauses into `existence_condition ? start + size : 0` |
| size_clause.function.args[0].CopyFrom(field.existence_condition) |
| size_clause.function.args[1].function.args[0].CopyFrom(field.location.start) |
| size_clause.function.args[1].function.args[1].CopyFrom(field.location.size) |
| size_clauses.append(size_clause_ir) |
| size_expression = ir_data_utils.copy(_SIZE_SKELETON) |
| size_expression.function.args.extend(size_clauses) |
| _mark_as_synthetic(size_expression) |
| size_field = ir_data.Field( |
| read_transform=size_expression, |
| name=ir_data.NameDefinition(name=ir_data.Word(text=size_field_name)), |
| existence_condition=ir_data.Expression( |
| boolean_constant=ir_data.BooleanConstant(value=True) |
| ), |
| attribute=[_skip_text_output_attribute()], |
| ) |
| structure.field.extend([size_field]) |
| |
| |
| # The replacement for the "$next" keyword is a simple "start + size" expression. |
| # 'x' and 'y' are placeholders, to be replaced. |
| _NEXT_KEYWORD_REPLACEMENT_EXPRESSION = expression_parser.parse("x + y") |
| |
| |
| def _maybe_replace_next_keyword_in_expression( |
| expression_ir, last_location, source_file_name, errors |
| ): |
| """Replaces the `$next` keyword in an expression.""" |
| if not expression_ir.HasField("builtin_reference"): |
| return |
| if ( |
| ir_data_utils.reader( |
| expression_ir |
| ).builtin_reference.canonical_name.object_path[0] |
| != "$next" |
| ): |
| return |
| if not last_location: |
| errors.append( |
| [ |
| error.error( |
| source_file_name, |
| expression_ir.source_location, |
| "`$next` may not be used in the first physical field of a " |
| + "structure; perhaps you meant `0`?", |
| ) |
| ] |
| ) |
| return |
| original_location = expression_ir.source_location |
| expression = ir_data_utils.builder(expression_ir) |
| expression.CopyFrom(_NEXT_KEYWORD_REPLACEMENT_EXPRESSION) |
| expression.function.args[0].CopyFrom(last_location.start) |
| expression.function.args[1].CopyFrom(last_location.size) |
| expression.source_location.CopyFrom(original_location) |
| _mark_as_synthetic(expression.function) |
| |
| |
| def _check_for_bad_next_keyword_in_size(expression, source_file_name, errors): |
| if not expression.HasField("builtin_reference"): |
| return |
| if expression.builtin_reference.canonical_name.object_path[0] != "$next": |
| return |
| errors.append( |
| [ |
| error.error( |
| source_file_name, |
| expression.source_location, |
| "`$next` may only be used in the start expression of a " |
| + "physical field.", |
| ) |
| ] |
| ) |
| |
| |
| def _replace_next_keyword(structure, source_file_name, errors): |
| last_physical_field_location = None |
| new_errors = [] |
| for field in structure.field: |
| if ir_util.field_is_virtual(field): |
| # TODO(bolms): It could be useful to allow `$next` in a virtual field, in |
| # order to reuse the value (say, to allow overlapping fields in a |
| # mostly-packed structure), but it seems better to add `$end_of(field)`, |
| # `$offset_of(field)`, and `$size_of(field)` constructs of some sort, |
| # instead. |
| continue |
| traverse_ir.fast_traverse_node_top_down( |
| field.location.size, |
| [ir_data.Expression], |
| _check_for_bad_next_keyword_in_size, |
| parameters={ |
| "errors": new_errors, |
| "source_file_name": source_file_name, |
| }, |
| ) |
| # If `$next` is misused in a field size, it can end up causing a |
| # `RecursionError` in fast_traverse_node_top_down. (When the `$next` node |
| # in the next field is replaced, its replacement gets traversed, but the |
| # replacement also contains a `$next` node, leading to infinite recursion.) |
| # |
| # Technically, we could scan all of the sizes instead of bailing early, but |
| # it seems relatively unlikely that someone will have `$next` in multiple |
| # sizes and not figure out what is going on relatively quickly. |
| if new_errors: |
| errors.extend(new_errors) |
| return |
| traverse_ir.fast_traverse_node_top_down( |
| field.location.start, |
| [ir_data.Expression], |
| _maybe_replace_next_keyword_in_expression, |
| parameters={ |
| "last_location": last_physical_field_location, |
| "errors": new_errors, |
| "source_file_name": source_file_name, |
| }, |
| ) |
| # The only possible error from _maybe_replace_next_keyword_in_expression is |
| # `$next` occurring in the start expression of the first physical field, |
| # which leads to similar recursion issue if `$next` is used in the start |
| # expression of the next physical field. |
| if new_errors: |
| errors.extend(new_errors) |
| return |
| last_physical_field_location = field.location |
| |
| |
| def _add_virtuals_to_structure(structure, type_definition): |
| _add_anonymous_aliases(structure, type_definition) |
| _add_size_virtuals(structure, type_definition) |
| _add_size_bound_virtuals(structure, type_definition) |
| |
| |
| def desugar(ir): |
| """Translates pure syntactic sugar to its desugared form. |
| |
| Replaces `$next` symbols with the start+length of the previous physical |
| field. |
| |
| Adds aliases for all fields in anonymous `bits` to the enclosing structure. |
| |
| Arguments: |
| ir: The IR to desugar. |
| |
| Returns: |
| A list of errors, or an empty list. |
| """ |
| errors = [] |
| traverse_ir.fast_traverse_ir_top_down( |
| ir, [ir_data.Structure], _replace_next_keyword, parameters={"errors": errors} |
| ) |
| if errors: |
| return errors |
| traverse_ir.fast_traverse_ir_top_down( |
| ir, [ir_data.Structure], _add_virtuals_to_structure |
| ) |
| return [] |