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:
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"]
Other options might add new syntax.
= 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; }
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()
MethodEmboss 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.
This is a moderately complex change, touching both the front end and C++ back end of the compiler, as well as the C++ runtime.
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.
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.
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.