| # Design Sketch: Initializer Values for Fields |
| |
| ## Motivation |
| |
| It is often useful to initialize a structure to some "un-set" value before |
| starting to modify it. For many structures, a simple `memset(buffer, sizeof |
| *buffer, 0)` suffices, but other structures require specific values at specific |
| locations, which are tedious to write out in user code. |
| |
| This design proposes two main elements: |
| |
| 1. A way to specify initializer values for specific fields. |
| 2. A new feature in the generated C++ code to set the memory underlying a view |
| to those initializer values, or to 0 if no initializer was specified. |
| |
| |
| ## Initializer Value Syntax |
| |
| ### Attribute |
| |
| The most straightforward option is to use the existing attribute syntax: |
| |
| struct Foo: |
| 0 [+2] UInt bar |
| [initialize_to: 7] |
| |
| The exact name TBD, but it should be somewhat visually distinct from the |
| existing `$default` keyword for attributes, which specifies that an attribute |
| should be used for all descendants of the current node, unless overridden: |
| |
| [$default byte_order = "LittleEndian"] |
| |
| |
| ### New Syntax |
| |
| Other options might add new syntax. |
| |
| |
| #### Suffix `= value` |
| |
| Suffix `= value` looks somewhat similar to the "initialize on construction" |
| syntax in languages like C++: |
| |
| struct Foo: |
| 0 [+2] UInt bar = 7 |
| |
| class Foo { |
| int bar = 7; |
| }; |
| |
| However, it also looks somewhat confusingly similar to the field number |
| specifiers in Proto: |
| |
| message Foo { |
| optional uint32 bar = 7; |
| } |
| |
| |
| #### Something Else? |
| |
| It is difficult to come up with a syntax that is clear and concise, especially |
| to a reader who is not particularly familiar with Emboss: |
| |
| struct Foo: |
| 0 [+2] UInt bar := 7 |
| |
| struct Foo: |
| 0 [+2] UInt bar [7] |
| |
| struct Foo: |
| 0 [+2] UInt [initialize to 7] bar |
| |
| Feel free to propose other options. |
| |
| |
| ## `Initialize()` Method |
| |
| Emboss *views* do not own their backing storage: creating a view does not |
| allocate memory, it just provides a structured, well, view of existing bytes. |
| This means that there is not a natural place to automatically initialize a |
| struct, the way that there is for an object in a typical programming language. |
| |
| Instead, I propose adding an `Initialize()` method (name TBD) to each view, |
| which can be called to explicitly initialize the underlying memory. |
| |
| For `external` (`UInt`, `Bcd`, etc.) and `enum` views, `Initialize()` should |
| just set the initializer value specified in the `.emb` file, or `0` if none was |
| specified. |
| |
| For structure views (`struct` and `bits`), `Initialize()` should set the |
| initializer values of each of their fields, recursively. |
| |
| TBD whether `Initialize()` should also zero out any bytes that are not part |
| of any concrete field. |
| |
| |
| ## Implementation Notes |
| |
| This is a moderately complex change, touching both the front end and C++ back |
| end of the compiler, as well as the C++ runtime. |
| |
| |
| ### Front End Changes |
| |
| On the front end, adding a new attribute is definitely the most straightforward |
| change: mostly just adding the new attribute to `attributes.py`, and updating a |
| few things in `dependency_checker.py`, `expression_bounds.py`, and possibly |
| `constraints.py` to inspect the new attribute. |
| |
| |
| ### C++ Back End Changes |
| |
| On the back end, there are a couple of implementation strategies. |
| |
| The easiest strategy is to generate an `Initialize()` method on each structure |
| type that recursively calls `Initialize()` on each field within the structure |
| (in dependency-safe order, similar to `WriteToTextStream()`). |
| |
| An alternate strategy would be to generate an "empty image" for each structure, |
| and have `Initialize()` `memcpy()` the image into its backing storage. This |
| seems like it would be faster at runtime, but may bloat binary size quite a bit |
| -- a 4kb `struct` would need 4kb of const data in your binary to support |
| `Initialize()`, whereas iteratively calling `Initialize()` on each element of |
| an array does not require any extra code space for each element of the array. |
| For this reason, there would likely still need to be a fallback to recursively |
| calling `Initialize()`. |
| |
| Either way, fields with an explicit initializer value need to be wrapped in a |
| view adapter when accessed, similar to how `[requires]` is handled now. |
| |
| Enum views can be generated with a simple `Initialize()` method that just sets |
| their backing storage to 0. |
| |
| |
| ### C++ Runtime Changes |
| |
| Each of the views for Prelude types (`UInt`, `Int`, `Flag`, etc.) in |
| `runtime/cpp/emboss_prelude.h` will need to have the new `Initialize()` method. |