C. Managing deprecated language features - intel/device-modeling-language GitHub Wiki

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.

Simics API versions

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:

  • First, while still using version N, make sure all Simics modules are updated to use API version N. Modules can be migrated one by one.
  • Second, update the Simics version to N+1. This should normally have no effect on DML, but may come with other challenges.
  • Third, update modules to API N+1, one by one. Simics version N+1 will always offer full support for API N, so there is no rush to update, but changing the API version early is a way to make sure deprecated features are not introduced in new code.

Breaking changes

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:

  • During migration to a new API version, enabling one breaking change at a time across the whole system makes it easier to analyze errors, because you only know that all errors come from the same change.
  • When a breaking change is first introduced, it will be disabled by default in the latest API version, and only be enabled by default when the next API version is introduced. Thus, until the next Simics major release, the breaking change cannot be activated without explicitly enabling it. There are reasons to adopt changes early, e.g.:
    • It guarantees that newly written code does not rely on the old behaviour, which eases later migration.
    • Avoiding a legacy construct that has a newer replacement makes the code base cleaner and more consistent.
    • Some breaking changes remove redundant representations from models, thereby reducing bloat both in terms of usability and performance.

Controlling deprecation on the DML command-line

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.

List of breaking changes

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.

Changes for migrating from --simics-api=6

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.

dml12-disable-inline-constants

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.

dml12-modern-int

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.

dml12-remove-goto

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.

dml12-remove-misc-quirks

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 expressions
  • select statements over vect types
  • Passing a string literal to char * arguments (now requires const char *)
  • Using the character - in the c_name parameter of interface objects
  • Overriding interface type via the c_name parameter in implement objects
  • loggroup identifiers accessible under same name in generated C
  • Applying & to non-lvalues
  • extern statements without type (extern foo;)
  • Anonymous banks (bank { ... })
  • Unused templates instantiating non-existing templates
  • Using the same symbol for both top-level object ($ scope) and top-level symbol (non-$ scope, e.g. extern, constant or loggroup)
enable-WLOGMIXUP

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.

shared-logs-locally

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.

transaction-by-default

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.

Changes for migrating from --simics-api=7

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.

forbid-broken-conditional-is

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.

forbid-broken-unused-types

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.

forbid-function-in-extern-struct

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;
forbid-warning-statement

Up to Simics 7, the _warning statement was allowed, though rarely useful. When this change is enabled, _warning statements are disallowed.

modern-attributes

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).

range-check-method-indices

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.

remove-port-proxy-attrs

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

Note

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.

remove-port-proxy-ifaces

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

Note

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.

require-version-statement

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.

restrict-log-levels

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.

strict-typechecking

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.

vect-needs-provisional

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.

⚠️ **GitHub.com Fallback** ⚠️