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"""Adds auto-generated virtual fields to the IR.""" 16 17from compiler.front_end import attributes 18from compiler.util import error 19from compiler.util import expression_parser 20from compiler.util import ir_data 21from compiler.util import ir_data_utils 22from compiler.util import ir_util 23from compiler.util import traverse_ir 24 25 26def _mark_as_synthetic(proto): 27 """Marks all source_locations in proto with is_synthetic=True.""" 28 if not isinstance(proto, ir_data.Message): 29 return 30 if hasattr(proto, "source_location"): 31 ir_data_utils.builder(proto).source_location.is_synthetic = True 32 for spec, value in ir_data_utils.get_set_fields(proto): 33 if spec.name != "source_location" and spec.is_dataclass: 34 if spec.is_sequence: 35 for i in value: 36 _mark_as_synthetic(i) 37 else: 38 _mark_as_synthetic(value) 39 40 41def _skip_text_output_attribute(): 42 """Returns the IR for a [text_output: "Skip"] attribute.""" 43 result = ir_data.Attribute( 44 name=ir_data.Word(text=attributes.TEXT_OUTPUT), 45 value=ir_data.AttributeValue(string_constant=ir_data.String(text="Skip"))) 46 _mark_as_synthetic(result) 47 return result 48 49 50# The existence condition for an alias for an anonymous bits' field is the union 51# of the existence condition for the anonymous bits and the existence condition 52# for the field within. The 'x' and 'x.y' are placeholders here; they'll be 53# overwritten in _add_anonymous_aliases. 54_ANONYMOUS_BITS_ALIAS_EXISTENCE_SKELETON = expression_parser.parse( 55 "$present(x) && $present(x.y)") 56 57 58def _add_anonymous_aliases(structure, type_definition): 59 """Adds synthetic alias fields for all fields in anonymous fields. 60 61 This essentially completes the rewrite of this: 62 63 struct Foo: 64 0 [+4] bits: 65 0 [+1] Flag low 66 31 [+1] Flag high 67 68 Into this: 69 70 struct Foo: 71 bits EmbossReservedAnonymous0: 72 [text_output: "Skip"] 73 0 [+1] Flag low 74 31 [+1] Flag high 75 0 [+4] EmbossReservedAnonymous0 emboss_reserved_anonymous_1 76 let low = emboss_reserved_anonymous_1.low 77 let high = emboss_reserved_anonymous_1.high 78 79 Note that this pass runs very, very early -- even before symbols have been 80 resolved -- so very little in ir_util will work at this point. 81 82 Arguments: 83 structure: The ir_data.Structure on which to synthesize fields. 84 type_definition: The ir_data.TypeDefinition containing structure. 85 86 Returns: 87 None 88 """ 89 new_fields = [] 90 for field in structure.field: 91 new_fields.append(field) 92 if not field.name.is_anonymous: 93 continue 94 field.attribute.extend([_skip_text_output_attribute()]) 95 for subtype in type_definition.subtype: 96 if (subtype.name.name.text == 97 field.type.atomic_type.reference.source_name[-1].text): 98 field_type = subtype 99 break 100 else: 101 assert False, ("Unable to find corresponding type {} for anonymous field " 102 "in {}.".format( 103 field.type.atomic_type.reference, type_definition)) 104 anonymous_reference = ir_data.Reference(source_name=[field.name.name]) 105 anonymous_field_reference = ir_data.FieldReference( 106 path=[anonymous_reference]) 107 for subfield in field_type.structure.field: 108 alias_field_reference = ir_data.FieldReference( 109 path=[ 110 anonymous_reference, 111 ir_data.Reference(source_name=[subfield.name.name]), 112 ] 113 ) 114 new_existence_condition = ir_data_utils.copy(_ANONYMOUS_BITS_ALIAS_EXISTENCE_SKELETON) 115 existence_clauses = ir_data_utils.builder(new_existence_condition).function.args 116 existence_clauses[0].function.args[0].field_reference.CopyFrom( 117 anonymous_field_reference) 118 existence_clauses[1].function.args[0].field_reference.CopyFrom( 119 alias_field_reference) 120 new_read_transform = ir_data.Expression( 121 field_reference=ir_data_utils.copy(alias_field_reference)) 122 # This treats *most* of the alias field as synthetic, but not its name(s): 123 # leaving the name(s) as "real" means that symbol collisions with the 124 # surrounding structure will be properly reported to the user. 125 _mark_as_synthetic(new_existence_condition) 126 _mark_as_synthetic(new_read_transform) 127 new_alias = ir_data.Field( 128 read_transform=new_read_transform, 129 existence_condition=new_existence_condition, 130 name=ir_data_utils.copy(subfield.name)) 131 if subfield.HasField("abbreviation"): 132 ir_data_utils.builder(new_alias).abbreviation.CopyFrom(subfield.abbreviation) 133 _mark_as_synthetic(new_alias.existence_condition) 134 _mark_as_synthetic(new_alias.read_transform) 135 new_fields.append(new_alias) 136 # Since the alias field's name(s) are "real," it is important to mark the 137 # original field's name(s) as synthetic, to avoid duplicate error 138 # messages. 139 _mark_as_synthetic(subfield.name) 140 if subfield.HasField("abbreviation"): 141 _mark_as_synthetic(subfield.abbreviation) 142 del structure.field[:] 143 structure.field.extend(new_fields) 144 145 146_SIZE_BOUNDS = { 147 "$max_size_in_bits": expression_parser.parse("$upper_bound($size_in_bits)"), 148 "$min_size_in_bits": expression_parser.parse("$lower_bound($size_in_bits)"), 149 "$max_size_in_bytes": expression_parser.parse( 150 "$upper_bound($size_in_bytes)"), 151 "$min_size_in_bytes": expression_parser.parse( 152 "$lower_bound($size_in_bytes)"), 153} 154 155 156def _add_size_bound_virtuals(structure, type_definition): 157 """Adds ${min,max}_size_in_{bits,bytes} virtual fields to structure.""" 158 names = { 159 ir_data.AddressableUnit.BIT: ("$max_size_in_bits", "$min_size_in_bits"), 160 ir_data.AddressableUnit.BYTE: ("$max_size_in_bytes", "$min_size_in_bytes"), 161 } 162 for name in names[type_definition.addressable_unit]: 163 bound_field = ir_data.Field( 164 read_transform=_SIZE_BOUNDS[name], 165 name=ir_data.NameDefinition(name=ir_data.Word(text=name)), 166 existence_condition=expression_parser.parse("true"), 167 attribute=[_skip_text_output_attribute()] 168 ) 169 _mark_as_synthetic(bound_field.read_transform) 170 structure.field.extend([bound_field]) 171 172 173# Each non-virtual field in a structure generates a clause that is passed to 174# `$max()` in the definition of `$size_in_bits`/`$size_in_bytes`. Additionally, 175# the `$max()` call is seeded with a `0` argument: this ensures that 176# `$size_in_units` is never negative, and ensures that structures with no 177# physical fields don't end up with a zero-argument `$max()` call, which would 178# fail type checking. 179_SIZE_CLAUSE_SKELETON = expression_parser.parse( 180 "existence_condition ? start + size : 0") 181_SIZE_SKELETON = expression_parser.parse("$max(0)") 182 183 184def _add_size_virtuals(structure, type_definition): 185 """Adds a $size_in_bits or $size_in_bytes virtual field to structure.""" 186 names = { 187 ir_data.AddressableUnit.BIT: "$size_in_bits", 188 ir_data.AddressableUnit.BYTE: "$size_in_bytes", 189 } 190 size_field_name = names[type_definition.addressable_unit] 191 size_clauses = [] 192 for field in structure.field: 193 # Virtual fields do not have a physical location, and thus do not contribute 194 # to the size of the structure. 195 if ir_util.field_is_virtual(field): 196 continue 197 size_clause_ir = ir_data_utils.copy(_SIZE_CLAUSE_SKELETON) 198 size_clause = ir_data_utils.builder(size_clause_ir) 199 # Copy the appropriate clauses into `existence_condition ? start + size : 0` 200 size_clause.function.args[0].CopyFrom(field.existence_condition) 201 size_clause.function.args[1].function.args[0].CopyFrom(field.location.start) 202 size_clause.function.args[1].function.args[1].CopyFrom(field.location.size) 203 size_clauses.append(size_clause_ir) 204 size_expression = ir_data_utils.copy(_SIZE_SKELETON) 205 size_expression.function.args.extend(size_clauses) 206 _mark_as_synthetic(size_expression) 207 size_field = ir_data.Field( 208 read_transform=size_expression, 209 name=ir_data.NameDefinition(name=ir_data.Word(text=size_field_name)), 210 existence_condition=ir_data.Expression( 211 boolean_constant=ir_data.BooleanConstant(value=True) 212 ), 213 attribute=[_skip_text_output_attribute()] 214 ) 215 structure.field.extend([size_field]) 216 217 218# The replacement for the "$next" keyword is a simple "start + size" expression. 219# 'x' and 'y' are placeholders, to be replaced. 220_NEXT_KEYWORD_REPLACEMENT_EXPRESSION = expression_parser.parse("x + y") 221 222 223def _maybe_replace_next_keyword_in_expression(expression_ir, last_location, 224 source_file_name, errors): 225 if not expression_ir.HasField("builtin_reference"): 226 return 227 if ir_data_utils.reader(expression_ir).builtin_reference.canonical_name.object_path[0] != "$next": 228 return 229 if not last_location: 230 errors.append([ 231 error.error(source_file_name, expression_ir.source_location, 232 "`$next` may not be used in the first physical field of a " + 233 "structure; perhaps you meant `0`?") 234 ]) 235 return 236 original_location = expression_ir.source_location 237 expression = ir_data_utils.builder(expression_ir) 238 expression.CopyFrom(_NEXT_KEYWORD_REPLACEMENT_EXPRESSION) 239 expression.function.args[0].CopyFrom(last_location.start) 240 expression.function.args[1].CopyFrom(last_location.size) 241 expression.source_location.CopyFrom(original_location) 242 _mark_as_synthetic(expression.function) 243 244 245def _check_for_bad_next_keyword_in_size(expression, source_file_name, errors): 246 if not expression.HasField("builtin_reference"): 247 return 248 if expression.builtin_reference.canonical_name.object_path[0] != "$next": 249 return 250 errors.append([ 251 error.error(source_file_name, expression.source_location, 252 "`$next` may only be used in the start expression of a " + 253 "physical field.") 254 ]) 255 256 257def _replace_next_keyword(structure, source_file_name, errors): 258 last_physical_field_location = None 259 new_errors = [] 260 for field in structure.field: 261 if ir_util.field_is_virtual(field): 262 # TODO(bolms): It could be useful to allow `$next` in a virtual field, in 263 # order to reuse the value (say, to allow overlapping fields in a 264 # mostly-packed structure), but it seems better to add `$end_of(field)`, 265 # `$offset_of(field)`, and `$size_of(field)` constructs of some sort, 266 # instead. 267 continue 268 traverse_ir.fast_traverse_node_top_down( 269 field.location.size, [ir_data.Expression], 270 _check_for_bad_next_keyword_in_size, 271 parameters={ 272 "errors": new_errors, 273 "source_file_name": source_file_name, 274 }) 275 # If `$next` is misused in a field size, it can end up causing a 276 # `RecursionError` in fast_traverse_node_top_down. (When the `$next` node 277 # in the next field is replaced, its replacement gets traversed, but the 278 # replacement also contains a `$next` node, leading to infinite recursion.) 279 # 280 # Technically, we could scan all of the sizes instead of bailing early, but 281 # it seems relatively unlikely that someone will have `$next` in multiple 282 # sizes and not figure out what is going on relatively quickly. 283 if new_errors: 284 errors.extend(new_errors) 285 return 286 traverse_ir.fast_traverse_node_top_down( 287 field.location.start, [ir_data.Expression], 288 _maybe_replace_next_keyword_in_expression, 289 parameters={ 290 "last_location": last_physical_field_location, 291 "errors": new_errors, 292 "source_file_name": source_file_name, 293 }) 294 # The only possible error from _maybe_replace_next_keyword_in_expression is 295 # `$next` occurring in the start expression of the first physical field, 296 # which leads to similar recursion issue if `$next` is used in the start 297 # expression of the next physical field. 298 if new_errors: 299 errors.extend(new_errors) 300 return 301 last_physical_field_location = field.location 302 303 304def _add_virtuals_to_structure(structure, type_definition): 305 _add_anonymous_aliases(structure, type_definition) 306 _add_size_virtuals(structure, type_definition) 307 _add_size_bound_virtuals(structure, type_definition) 308 309 310def desugar(ir): 311 """Translates pure syntactic sugar to its desugared form. 312 313 Replaces `$next` symbols with the start+length of the previous physical 314 field. 315 316 Adds aliases for all fields in anonymous `bits` to the enclosing structure. 317 318 Arguments: 319 ir: The IR to desugar. 320 321 Returns: 322 A list of errors, or an empty list. 323 """ 324 errors = [] 325 traverse_ir.fast_traverse_ir_top_down( 326 ir, [ir_data.Structure], _replace_next_keyword, 327 parameters={"errors": errors}) 328 if errors: 329 return errors 330 traverse_ir.fast_traverse_ir_top_down( 331 ir, [ir_data.Structure], _add_virtuals_to_structure) 332 return [] 333