As the DML language evolves, we sometimes need to change the language in incompatible ways, which requires DML users to migrate their code. This appendix describes the mechanisms we provide to make this migration process smooth for users with large DML code bases.
In DML, breaking changes can come in many forms. Breaking changes in the form of removed or renamed symbols in libraries are rather easy to manage, since they give clear compile errors that often are straightforward to fix. A slightly harder type of breaking change is when some language construct or API function adjusts its semantics; this can make the model behave differently without signalling error messages. A third kind of change is when DML changes how compiled models appear in Simics, typically to adjust changes in the Simics API. Such changes add another dimension because they typically affect the end-users of the DML models, rather than the authors of the models. Thus, as an author of a model you may need to synchronize your migration of such features with your end-users, to ease their transition to a new major version.
The simplest deprecation mechanism is Simics API versions: Each breaking change is associated with a Simics API version, and each Simics version supports a number of such API versions. When moving to a new Simics major version, support for the oldest API version is dropped which means the corresponding changes become mandatory. Since Simics is currently the primary distribution channel for DML, this scheme is used for DML features as well.
This scheme allows users with a large code base to smoothly migrate from one Simics major version, N, to the next, N+1:
DML supports a more fine-grained mechanism for managing breaking changes, where each individual change in a new API version can be enabled individually, without enabling the full API version. This has the following uses:
DMLC provides a command-line flag --api-version to specify the API version to
be used for a model. When building with the CMake based build system in Simics,
this is controlled by the SIMICS_API parameter of the simics_add_module
function.
DMLC also provides a flag --breaking-change=TAG, which enables
the breaking change represented by TAG. The valid tags are listed in the
next section.
Each breaking change has an associated tag, which is passed to the
--breaking-change flag. Each tag also comes with a top-level DML parameter,
which DML code can use to check if a change is enabled or not. A section with
title foo-bar corresponds to the DMLC flag --breaking-change=foo-bar, and
when that flag is passed, the global parameter _breaking_change_foo_bar to
true.
Each breaking change is also implicitly enabled when compiling with a Simics API version above a certain threshold. The breaking changes under the section for API n are useful when migrating to API n+1.
These changes are enabled automatically when compiling using
Simics API 7 or newer. With older Simics API versions, the
changes can be enabled individually by passing
--breaking-change=TAG
to the dmlc compiler.
When using inline in DML 1.2, constant arguments passed in
typed parameters were inlined as constants, which had some
unintuitive semantic implications. In DML 1.4, constants are only
inlined in parameters declared using inline as quasi-type. When
this change is enabled, DML 1.2 only inlines constants in untyped
method parameters, causing its behaviour to closer resemble DML 1.4.
Up to Simics 6, DML 1.2 used legacy integer semantics, translating most operations directly into C without compensating for DML-specifics like the support for odd-sized uintNN types. This can sometimes have unexpected consequences. When this change is enabled, modern DML 1.4 integer semantics is used.
The most immediate effect of enabling this change
is that DMLC will report errors on statements like assert 0; and
while (1) { ... }, which need to change into assert false; and
while (true) { ... }, respectively. Other effects include:
With legacy integer semantics, integers of non-standard sizes
are represented as a native C
type, e.g. uint5 is represented as uint8, allowing it to
store numbers too large to fit in 5 bits. With modern integer
semantics, arithmetic is done on 64-bit integers and bits are
truncated if casting or storing in a smaller type.
Old code sometimes relies on this feature by comparing variables
of type int1 to the value 1. In DML 1.4, the only values of
type int1 are 0 and -1, so such code should be rewritten
to use the uint1 type. It can be a good idea to grep for
[^a-z_]int1[^0-9] and review if uint1 is a better choice.
With legacy semantics, some operations that have undefined behaviour in C effectively counts as undefined behaviour in DML as well, whereas modern semantics have well-defined semantics for all operations. For instance, negative shift or division by zero is handled with an unconditional critical error, and too large shift operands or signed shift overflow is handled by truncation.
With legacy integer semantics, comparison operators <, <=, ==,
>=, > inherit C semantics, whereas with modern integer semantics
they are compared mathematically as integers. This sometimes makes a
difference when comparing signed and unsigned numbers; in particular, -1 != 0xffffffffffffffff consistently with modern semantics, whereas with
legacy semantics, they are consiered different only if
both are constant.
Up to Simics 6, the goto statement was allowed in DML 1.2.
The statement is not allowed in DML 1.4. When this
change is enabled, goto is disallowed also in DML 1.2.
Most goto-based control
structures can be rewritten using throw and catch blocks.
DML 1.2 had several quirks that inadvertedly caused it to permit some strange patterns. In DML 1.4 this was cleaned up and some patterns were disallowed. When this change is enabled, the DML 1.4 behaviour applies also in DML 1.2.
Examples of forbidden patterns include:
sizeof(typename) (see WSIZEOFTYPE)typeof on non-lvalue expressionsselect statements over vect typeschar * arguments (now requires const char *)- in the c_name parameter of interface objectsc_name parameter in implement objectsloggroup identifiers accessible under same name in generated C& to non-lvaluesextern statements without type (extern foo;)bank { ... })$ scope) and
top-level symbol (non-$ scope, e.g. extern, constant or loggroup)Up to Simics 6, the warning WLOGMIXUP
was suppressed by default to avoid overwhelming users, as the
faulty pattern it reports was common. When this change is
enabled, WLOGMIXUP is reported by default. Code using the faulty
pattern should be fixed before enabling this change.
Up to Simics 6, log statements inside shared methods always logged on the device object instead of the nearest enclosing configuration object. This was a bug that many scripts relied on. When this change is enabled, logs inside shared methods behave consistently with non-shared methods and log on the nearest enclosing configuration object.
Up to Simics 6, the top-level parameter use_io_memory defaulted to
true, causing bank objects to implement io_memory instead of
transaction by default. When this change is enabled, banks will
implement transaction by default, and use_io_memory must be set
explicitly to true if the old behavior is desired.
These changes are enabled automatically when compiling using
Simics API 8 or newer. With older Simics API versions, the
changes can be enabled individually by passing
--breaking-change=TAG
to the dmlc compiler.
Up to Simics 7, a bug prevented DMLC from reporting an error
when instantiating a nonexistent template within an #if block.
For example, the following would compile without errors:
#if (true) {
group g {
// should be an error, but silently ignored when this
// feature is enabled
is nonexisting_template;
}
}
This change causes DMLC to report an error on such code.
Up to Simics 7, a bug prevented DMLC from reporting certain
errors on unused unused extern-declared types. For example,
the following would compile without errors:
extern typedef struct {
undefined_type_t member;
} never_used_t;
This change causes DMLC to report an error on such code.
Up to Simics 7, a bug allowed omitting the * in function pointer
members of extern typedef struct declarations. For example, the
following would compile without errors:
extern typedef struct {
void m(conf_object_t *);
} my_interface_t;
This change causes DMLC to report an error on such code. To fix it, use the standard C form:
extern typedef struct {
void (*m)(conf_object_t *);
} my_interface_t;
Up to Simics 7, the _warning statement was allowed, though rarely useful.
When this change is enabled, _warning statements are disallowed.
Up to Simics 7, attributes were registered using the legacy
SIM_register_typed_attribute API, which supported the dictionary
type (“D” in type strings). When this change is enabled,
attributes are registered using the modern
SIM_register_attribute family, and the dictionary type becomes
unsupported. Models using dictionary attributes must migrate to
another representation, such as a list of two-element lists. E.g.,
a dict from integers to strings can be represented as an attribute
of type [[is]*] instead of D. The outer list is created using
SIM_alloc_attr_list instead of SIM_alloc_attr_dict, and items
are added using SIM_set_attr_list_item(&outer, i, SIM_make_attr_list(2, key, value)) rather than
SIM_attr_dict_set_item(&outer, i, key, value).
Up to Simics 7, methods defined under object arrays did not validate that indices used when calling the method were within bounds. When this change is enabled, indices are implicitly range checked. If enabling this change causes crashes, then that definitely signifies a bug in your model; a bug that would very likely result in memory corruption if the assertion were not to be made.
In Simics 5, configuration attributes for connect,
attribute and register objects inside banks and ports were
registered on the device object, named like
bankname_attrname.
When this change is enabled, attributes are only registered on the bank or port object itself. When the change is not yet enabled, ports and banks are also redundantly exposed through a proxy attribute on the device object.
When enabling this change, you can expect that some code that accesses
some attributes directly will have to be updated. For instance, if your device has
a bank regs with a register R, then
your_dev.regs_R = 4711
will give an error, and can be changed into:
your_dev.bank.regs.R = 4711
Proxy attributes are only ever created for constructs that were permitted in Simics 5. For instance, attributes under banks inside groups will not get a proxy attribute even when this change is not enabled.
Version 5 and earlier of Simics relied on interface ports (as
registered by the SIM_register_port_interface API function) for
exposing the interfaces of ports and banks. In newer versions of
Simics, interfaces are instead exposed on separate configuration
objects.
When this change is enabled, ports and banks will only expose interfaces through the dedicated port object. When this change is not yet enabled, ports and banks are also redundantly exposed through an old-style interface port for compatibility.
When enabling this change, you can expect that initialization of connection
attributes of other objects have to be updated. For instance, if your
device has a port named p, then
other_dev.target = [your_dev, "p"]
will give an error, and can be changed into:
other_dev.target = your_dev.port.p
Interface ports are only ever created for constructs that were permitted in Simics 5. For instance, interfaces in banks inside groups will not get an interface port even when this change is not enabled.
Up to Simics 7, the version specification statement (dml 1.4;) at
the start of each file was optional, and dml 1.3; was permitted as
a deprecated alias for dml 1.4;. When this change is enabled, the
version statement becomes mandatory, and dml 1.3; is no longer
accepted.
Up to Simics 7, log levels for “warning”, “error”, and “critical” logs could be any integer between 1 and 5, even though it is only meaningful to provide 1 as the primary level and 5 as the subsequent level. When this change is enabled, the primary log level must be 1, and if the subsequent level is provided it must be 5; any other values will be rejected.
Up to Simics 7, DMLC’s type checking was very lenient compared to GCC,
especially for method overrides and uses of extern macros. When this
change is enabled, type checking becomes more strict.
The most common type of errors triggered by enabling this change are
due to discrepencies between pointer
types. In particular, implicitly discarding const-qualification of a
pointer’s base type will never be tolerated, and void pointers are only
considered equivalent with any other pointer type in the same contexts as
C.
Novel type errors from uses of C macros exposed to DML through extern
declarations can often be resolved by
changing the signature of the extern declaration to more accurately
reflect the macro’s effective type.
Up to Simics 7, the vect syntax was permitted without enabling the
simics_util_vect provisional feature,
issuing only a warning in DML 1.4.
When this change is enabled, the vect syntax is forbidden unless the
provisional feature is explicitly enabled.