diff --git a/MODULEINFO b/MODULEINFO index 90295af9c..ef8c00bab 100644 --- a/MODULEINFO +++ b/MODULEINFO @@ -43,6 +43,7 @@ Make: dmlc $(HOST)/bin/dml/python/dml/c_backend.py $(HOST)/bin/dml/python/dml/g_backend.py $(HOST)/bin/dml/python/dml/codegen.py + $(HOST)/bin/dml/python/dml/compat.py $(HOST)/bin/dml/python/dml/crep.py $(HOST)/bin/dml/python/dml/ctree.py $(HOST)/bin/dml/python/dml/dmllex.py diff --git a/Makefile b/Makefile index a4079e7eb..97326e80a 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,7 @@ PYFILES := dml/__init__.py \ dml/c_backend.py \ dml/g_backend.py \ dml/codegen.py \ + dml/compat.py \ dml/crep.py \ dml/ctree.py \ dml/template.py \ @@ -210,9 +211,11 @@ DOC_FILES_14 := $(wildcard $(DOC_SRC_DIR_14)/*.md) $(DOC_SRC_DIR_14)/toc.json DOC_DEST_14 := $(SIMICS_PROJECT)/$(HOST_TYPE)/doc/html/dml-1.4-reference-manual DOC_MARKER_14 := $(DOC_DEST_14)/filelist.json -GENERATED_MD_FILES_14 = $(addprefix generated-md-1.4/,grammar.md messages.md changes-auto.md dml-builtins.md utility.md) +GENERATED_MD_FILES_14 = $(addprefix generated-md-1.4/,grammar.md messages.md changes-auto.md dml-builtins.md utility.md deprecations-auto.md) generated-md-1.4/changes-auto.md: $(SRC_BASE)/$(TARGET)/porting_to_md.py $(DMLC_BIN) | generated-md-1.4 $(PYTHON) $< $(PYTHONPATH) $@ +generated-md-1.4/deprecations-auto.md: $(SRC_BASE)/$(TARGET)/deprecations_to_md.py $(SRC_BASE)/$(TARGET)/doc/1.4/deprecations-header.md $(DMLC_BIN) | generated-md-1.4 + $(PYTHON) $< $(PYTHONPATH) $(word 2,$^) $@ generated-md-1.4/dml-builtins.md generated-md-1.4/utility.md: generated-md-1.4/%.md: $(DMLC_DIR)/lib/1.4/%.dml $(PYTHON) $(DMLC_DIR)/dmlcomments_to_md.py $< $@ DOC_FILES_14 += $(GENERATED_MD_FILES_14) diff --git a/RELEASENOTES.docu b/RELEASENOTES.docu index f671560ed..eb5b8b098 100644 --- a/RELEASENOTES.docu +++ b/RELEASENOTES.docu @@ -1730,4 +1730,11 @@ extern typedef struct { } my_type_t; This warning is only enabled by default with Simics API version 7 or above. With version 6 and below it must be explicitly enabled by passing --warn=WLOGMIXUP to DMLC. + Added a flag --no-compat + to selectively disable compatibility features. In + particular, --no-compat=port_proxy_ifaces disables + generation of interface trampolines, which can speed up + compilation of devices with huge arrays of ports. The + option --help-no-compat lists all features that can be + disabled. diff --git a/deprecations_to_md.py b/deprecations_to_md.py new file mode 100644 index 000000000..fc8669dd5 --- /dev/null +++ b/deprecations_to_md.py @@ -0,0 +1,32 @@ +# © 2023 Intel Corporation +# SPDX-License-Identifier: MPL-2.0 + +import sys +from pathlib import Path +[path_to_dml, header, outfile] = sys.argv[1:] +sys.path.append(path_to_dml) + +from dml import compat +from dml.env import api_versions, default_api_version + +by_version = {} +for feature in compat.features.values(): + if (feature.last_api_version in api_versions() + # don't document features that are unconditionally disabled in + # this Simics version + or feature.last_api_version > compat.apis[default_api_version()]): + by_version.setdefault(feature.last_api_version, []).append(feature) + +with open(outfile, 'w') as f: + f.write(Path(header).read_text()) + for (ver, features) in sorted(by_version.items()): + f.write( + f"### Features available up to --simics-api={ver.str}\n") + f.write("
\n") + for feature in sorted(compat.features.values(), key=lambda f: f.tag()): + assert feature.__doc__ + f.write(f"
{feature.tag()}
\n") + doc = '\n'.join(line[4:] if line.startswith(' ') else line + for line in feature.__doc__.strip().splitlines()) + f.write(f"
\n\n{doc}\n
\n") + f.write("
\n") diff --git a/doc/1.4/deprecations-header.md b/doc/1.4/deprecations-header.md new file mode 100644 index 000000000..391ef5bf0 --- /dev/null +++ b/doc/1.4/deprecations-header.md @@ -0,0 +1,72 @@ + + +# Managing deprecated language features + +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, deprecations can come in many forms. Deprecations 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 deprecation 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 deprecation 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. + +## Deprecation mechanisms + +The simplest deprecation mechanism is Simics API versions: Each deprecated DML +feature is associated with a Simics API version, and each Simics version +supports a number of such API versions. Features reach end-of-life when moving +to a new Simics major version, the features belonging to a previous Simics API +version are dropped. Since Simics is currently the primary distribution channel +for DML, this deprecation 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 + offers full support for API N, so there is no rush to update, but changing + the API version early makes sure deprecated features are not introduced in + new code. + +In addition to the API version, DML offers some compiler flags for selectively +disabling deprecated features that are normally part of the used API. This has +some uses, in particular: +* During migration to a new API version, disabling one deprecated feature at a + time can allow a smoother, more gradual, migration. +* If a legacy feature is still fully supported in the latest API version, then + it cannot be disabled by selecting an API version, so selectively disabling + it is the only way to turn it off. There are reasons to do this, e.g.: + * Disabling a feature before it is deprecated guarantees that it is not + relied upon in new code, which eases later migration. + * Avoiding a feature that has a newer replacement makes the code base + cleaner and more consistent. + * Some legacy features can also bloat models, by exposing features in a + redundant manner. This can also have a negative impact on 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 standard Simics build system, this +is controlled by the `SIMICS_API_VERSION` variable in `make`, or the +`SIMICS_API`/`MODULE_SIMICS_API` variable in `CMake`. + +DMLC also provides the --no-compat=_tag_ flag, which disables the +feature represented by _`tag`_. The available tags are listed in the next +section. The tag also controls the name of a global boolean parameter that the +DML program may use to check whether the feature is available. The parameter's +name is the tag name preceded by `_compat_`. + +## List of deprecated features diff --git a/doc/1.4/toc.json b/doc/1.4/toc.json index 63c5da40b..ac9ac02d8 100644 --- a/doc/1.4/toc.json +++ b/doc/1.4/toc.json @@ -16,6 +16,9 @@ {"file": "messages.md", "numbering": true, "appendix": true}, + {"file": "deprecations-auto.md", + "numbering": true, + "appendix": true}, {"file": "changes.md", "numbering": true, "appendix": true, diff --git a/generate_env.py b/generate_env.py index 417b3dfed..2a94b60b6 100644 --- a/generate_env.py +++ b/generate_env.py @@ -1,7 +1,6 @@ # © 2021-2023 Intel Corporation # SPDX-License-Identifier: MPL-2.0 -import os import sys from pathlib import Path @@ -13,7 +12,7 @@ def generate_env(out): def is_windows(): return {is_windows()} def api_versions(): - return {api_versions()} + return {repr(api_versions())} def default_api_version(): return {repr(default_api_version())} ''') diff --git a/lib/1.2/dml-builtins.dml b/lib/1.2/dml-builtins.dml index c7766e97b..b269bf15c 100644 --- a/lib/1.2/dml-builtins.dml +++ b/lib/1.2/dml-builtins.dml @@ -206,6 +206,16 @@ template device { parameter simics_api_version auto; + parameter _compat_port_proxy_ifaces auto; + parameter _compat_port_proxy_attrs auto; + parameter _compat_port_obj_param auto; + parameter _compat_io_memory auto; + parameter _compat_dml12_inline auto; + parameter _compat_dml12_not auto; + parameter _compat_dml12_goto auto; + parameter _compat_dml12_misc auto; + parameter _compat_dml12_int auto; + // automatic parameters parameter obj auto; parameter logobj = $obj; diff --git a/lib/1.4/dml-builtins.dml b/lib/1.4/dml-builtins.dml index 4725dfa23..e921a3594 100644 --- a/lib/1.4/dml-builtins.dml +++ b/lib/1.4/dml-builtins.dml @@ -539,9 +539,16 @@ template device { param _is_simics_object = true; param simics_api_version auto; - param _api_5_or_older = simics_api_version == "5" - || simics_api_version == "4.8"; - param _api_6_or_older = simics_api_version == "6" || _api_5_or_older; + + param _compat_port_proxy_ifaces auto; + param _compat_port_proxy_attrs auto; + param _compat_port_obj_param auto; + param _compat_io_memory auto; + param _compat_dml12_inline auto; + param _compat_dml12_not auto; + param _compat_dml12_goto auto; + param _compat_dml12_misc auto; + param _compat_dml12_int auto; // automatic parameters param obj auto; @@ -554,7 +561,7 @@ template device { param be_bitorder default _be_bitorder; param log_group = undefined; - param use_io_memory default _api_6_or_older; + param use_io_memory default _compat_io_memory; // this was available in DML 1.2; defensively reserved in 1.4 param banks = undefined; // this carried semantics in DML 1.2; deprecated in 1.4 @@ -1443,7 +1450,7 @@ template port is object { param _is_simics_object = true; // limitations for ports are not recognized param limitations = undefined; - param obj = dev._api_5_or_older #? dev.obj #: _port_obj(); + param obj = dev._compat_port_obj_param #? dev.obj #: _port_obj(); session conf_object_t *_cached_port_obj; shared method _port_obj() -> (conf_object_t *) { @@ -1775,7 +1782,7 @@ template bank is (object, shown_desc) { // compatibility: referencing 'obj' in a bank method must evaluate to // dev.obj in code that can compile on both 5 and 6 // TODO: we should probably make obj an immutable session variable - param obj = dev._api_5_or_older #? dev.obj #: _bank_obj(); + param obj = dev._compat_port_obj_param #? dev.obj #: _bank_obj(); param partial : bool; param overlapping : bool; param _le_byte_order : bool; diff --git a/py/dml/c_backend.py b/py/dml/c_backend.py index 3b7cd38af..343306eba 100644 --- a/py/dml/c_backend.py +++ b/py/dml/c_backend.py @@ -13,7 +13,7 @@ from pathlib import Path from . import objects, logging, crep, output, ctree, serialize, structure -from . import traits +from . import traits, compat import dml.globals from .structure import get_attr_name, port_class_ident, need_port_proxy_attrs from .logging import * @@ -886,36 +886,37 @@ def generate_implement(code, device, impl): # Legacy interface ports are only added for ports and banks that were # available in Simics 5, i.e. zero or one dimensional direct # descendants of the device object - if (port.local_dimensions() == 0 and port.parent.parent is None - and port.objtype != 'subdevice'): - code.out("static const %s = %s;\n" % ( - ifacetype.declaration('port_iface'), - interface_block(device, ifacestruct, methods, ()))) - code.out('SIM_register_port_interface' - '(class, "%s", &port_iface, "%s", %s);\n' - % (impl.name, crep.cname(port), desc)) - elif (port.local_dimensions() == 1 and port.parent.parent is None - and port.objtype != 'subdevice'): - [arrlen] = port.arraylens() - code.out("static const %s%s = %s;\n" % - (ifacetype.declaration("ifaces"), - "[%s]" % (arrlen,), - "{%s\n}" % ",\n".join( - "%s" % interface_block(device, ifacestruct, - methods, (i, )) - for i in range(arrlen)))) - code.out("interface_array_t iface_vect = VNULL;\n") - idxvar = "i0" - code.out("for (int %s = 0; %s < %d; %s++)\n" % - (idxvar, idxvar, arrlen, idxvar), - postindent = 1) - access = "[%s]" % idxvar - code.out("VADD(iface_vect, &ifaces%s);\n" % access, - postindent = -1) - code.out('VT_register_port_array_interface' - '(class, "%s", &iface_vect, "%s", %s);\n' - % (impl.name, crep.cname(port), desc)) - code.out('VFREE(iface_vect);\n') + if (port.parent is dml.globals.device + and port.objtype in {'port', 'bank'} + and compat.port_proxy_ifaces in dml.globals.enabled_compat): + if port.local_dimensions() == 0: + code.out("static const %s = %s;\n" % ( + ifacetype.declaration('port_iface'), + interface_block(device, ifacestruct, methods, ()))) + code.out('SIM_register_port_interface' + '(class, "%s", &port_iface, "%s", %s);\n' + % (impl.name, crep.cname(port), desc)) + elif port.local_dimensions() == 1: + [arrlen] = port.arraylens() + code.out("static const %s%s = %s;\n" % + (ifacetype.declaration("ifaces"), + "[%s]" % (arrlen,), + "{%s\n}" % ",\n".join( + "%s" % interface_block(device, ifacestruct, + methods, (i, )) + for i in range(arrlen)))) + code.out("interface_array_t iface_vect = VNULL;\n") + idxvar = "i0" + code.out("for (int %s = 0; %s < %d; %s++)\n" % + (idxvar, idxvar, arrlen, idxvar), + postindent = 1) + access = "[%s]" % idxvar + code.out("VADD(iface_vect, &ifaces%s);\n" % access, + postindent = -1) + code.out('VT_register_port_array_interface' + '(class, "%s", &iface_vect, "%s", %s);\n' + % (impl.name, crep.cname(port), desc)) + code.out('VFREE(iface_vect);\n') code.out("}\n", preindent = -1) def port_prefix(port): @@ -1544,7 +1545,7 @@ def generate_alloc(device): out('}\n\n', preindent = -1) def generate_initialize(device): - if dml.globals.api_version <= '6': + if dml.globals.api_version <= compat.api_6: start_function_definition( ('void %s_pre_del_notify(conf_object_t *_subscriber,' ' conf_object_t *_notifier, lang_void *_data)') % crep.cname(device)) @@ -1589,7 +1590,7 @@ def generate_initialize(device): codegen_inline_byname(device, (), '_init', [], [], device.site).toc() reset_line_directive() - if dml.globals.api_version <= '6': + if dml.globals.api_version <= compat.api_6: out('SIM_add_notifier(_obj, Sim_Notify_Object_Delete, _obj, ' + crep.cname(device) + '_pre_del_notify, NULL);\n') @@ -1909,7 +1910,7 @@ def generate_init_data_objs(device): for (start, end) in [('A', 'Z'), ('a', 'z'), ('0', '9'), ('_', '_')] for i in range(ord(start), ord(end) + 1)) def init_function_name(device, outprefix): - if dml.globals.api_version in {'4.8'}: + if dml.globals.api_version <= compat.api_4_8: return 'initialize_' + crep.cname(device) return '_initialize_' + ''.join( (ch if ch in ident_chars else '_') for ch in outprefix) @@ -1936,7 +1937,7 @@ def generate_init(device, initcode, outprefix): out('.alloc = '+crep.cname(device)+'_alloc,\n') out('.init = '+crep.cname(device)+'_init,\n') out('.finalize = '+crep.cname(device)+'_finalize,\n') - if dml.globals.api_version >= '7': + if dml.globals.api_version >= compat.api_7: out('.deinit = '+crep.cname(device)+'_deinit,\n') out('.dealloc = '+crep.cname(device)+'_dealloc,\n') out('.description = '+doc.read()+',\n') @@ -3228,12 +3229,10 @@ def generate_cfile(device, footers, full_module): global c_file - if dml.globals.api_version == 'internal': - api_define = '' - else: - sym = 'SIMICS_%s_API' % dml.globals.api_version.replace('.', '_') - api_define = '#ifndef %s\n#define %s\n#endif\n' % ( - sym, sym) + sym = 'SIMICS_%s_API' % ('4_8' if dml.globals.api_version == compat.api_4_8 + else dml.globals.api_version.str) + api_define = '#ifndef %s\n#define %s\n#endif\n' % ( + sym, sym) c_top = '\n'.join([ '/*', @@ -3416,7 +3415,7 @@ def generate_cfile_body(device, footers, full_module, filename_prefix): if full_module: # caught as error earlier on - assert dml.globals.api_version in ['4.8'] + assert dml.globals.api_version == compat.api_4_8 out('\n') out('EXPORTED void\n') out('init_local(void)\n') diff --git a/py/dml/codegen.py b/py/dml/codegen.py index 5674999f0..a952b3b07 100644 --- a/py/dml/codegen.py +++ b/py/dml/codegen.py @@ -5,13 +5,14 @@ from abc import ABC, abstractmethod, abstractproperty import operator import contextlib + from functools import reduce import itertools import os import math from . import objects, crep, ctree, ast, int_register, logging, serialize -from . import dmlparse, output +from . import dmlparse, output, compat from .logging import * from .expr import * from .ctree import * @@ -1079,7 +1080,7 @@ def subast_has_dollar(expr_ast): @expression_dispatcher def expr_unop(tree, location, scope): [op, rh_ast] = tree.args - if (dml.globals.compat_dml12 + if (compat.dml12_misc in dml.globals.enabled_compat and op == 'sizeof' and rh_ast.kind == 'variable_dml12'): var = rh_ast.args[0] if var in typedefs and scope.lookup(var) is None: @@ -1120,7 +1121,7 @@ def expr_unop(tree, location, scope): return ctree.AddressOfMethod(tree.site, func) raise rh.exc() if op == '!': - if dml.globals.compat_dml12 and dml.globals.api_version <= "5": + if compat.dml12_not in dml.globals.enabled_compat: t = rh.ctype() if isinstance(safe_realtype(t), TInt) and subast_has_dollar(rh_ast): # A previous bug caused DMLC to permit expressions on @@ -1142,7 +1143,8 @@ def expr_unop(tree, location, scope): elif op == 'post++': return mkPostInc(tree.site, rh) elif op == 'post--': return mkPostDec(tree.site, rh) elif op == 'sizeof': - if not dml.globals.compat_dml12 and not isinstance(rh, ctree.LValue): + if (compat.dml12_misc not in dml.globals.enabled_compat + and not isinstance(rh, ctree.LValue)): raise ERVAL(rh.site, 'sizeof') return codegen_sizeof(tree.site, rh) elif op == 'defined': return mkBoolConstant(tree.site, True) @@ -1318,9 +1320,9 @@ def expr_cast(tree, location, scope): for (site, _) in struct_defs: report(EANONSTRUCT(site, "'cast' expression")) - if (dml.globals.compat_dml12 and dml.globals.api_version <= "6" + if (compat.dml12_misc in dml.globals.enabled_compat and isinstance(expr, InterfaceMethodRef)): - # Workaround for bug 24144 + # Workaround for SIMICS-9868 return mkLit(tree.site, "%s->%s" % ( expr.node_expr.read(), expr.method_name), type) @@ -1524,18 +1526,17 @@ def eval_type(asttype, site, location, scope, extern=False, typename=None, etype = TInt(width, False, members) elif tag == 'typeof': expr = codegen_expression_maybe_nonvalue(info, location, scope) - if (not dml.globals.compat_dml12 - and not isinstance(expr, ctree.LValue) - # for compatibility with dml-builtins, using 1.2 - and not isinstance(expr, ctree.RegisterWithFields)): - raise ERVAL(expr.site, 'typeof') if isinstance(expr, NonValue): + # for compatibility with dml-builtins, using 1.2 if isinstance(expr, (ctree.NoallocNodeRef, ctree.RegisterWithFields, ctree.IncompleteNodeRefWithStorage)): etype = expr.node_type else: raise expr.exc() + elif (not isinstance(expr, ctree.LValue) + and compat.dml12_misc not in dml.globals.enabled_compat): + raise ERVAL(expr.site, 'typeof') else: etype = expr.ctype().clone() if not etype: @@ -1979,7 +1980,8 @@ def stmt_local(stmt, location, scope): def convert_decl(decl_ast): (name, asttype) = decl_ast.args - if dml.globals.dml_version == (1, 2) and not dml.globals.compat_dml12: + if (dml.globals.dml_version == (1, 2) + and compat.dml12_misc not in dml.globals.enabled_compat): check_varname(stmt.site, name) (struct_decls, etype) = eval_type(asttype, stmt.site, location, scope) stmts.extend(mkStructDefinition(site, t) for (site, t) in struct_decls) @@ -2543,7 +2545,7 @@ def stmt_assert(stmt, location, scope): @statement_dispatcher def stmt_goto(stmt, location, scope): [label] = stmt.args - if not dml.globals.compat_dml12: + if compat.dml12_goto not in dml.globals.enabled_compat: report(ESYNTAX(stmt.site, 'goto', 'goto statement not allowed')) return [mkGoto(stmt.site, label)] @@ -3043,7 +3045,8 @@ def stmt_select(stmt, location, scope): if_chain = mkIf(cond.site, cond, stmt, if_chain) return [if_chain] raise lst.exc() - elif dml.globals.compat_dml12 and isinstance(lst.ctype(), TVector): + elif (compat.dml12_misc in dml.globals.enabled_compat + and isinstance(lst.ctype(), TVector)): itervar = lookup_var(stmt.site, scope, itername) if not itervar: raise EIDENT(stmt.site, itername) @@ -3315,18 +3318,20 @@ def common_inline(site, method, indices, inargs, outargs): return mkNull(site) if dml.globals.debuggable: - if method.fully_typed and (not dml.globals.compat_dml12 - or all(not arg.constant for arg in inargs)): + if method.fully_typed and ( + compat.dml12_inline not in dml.globals.enabled_compat + or all(not arg.constant for arg in inargs)): # call method instead of inlining it func = method_instance(method) else: # create a specialized method instance based on parameter # types, and call that - intypes = tuple(arg if ((ptype is None or dml.globals.compat_dml12) - and (arg.constant or undefined(arg))) - else methfunc_param(ptype, arg) - for ((pname, ptype), arg) - in zip(method.inp, inargs)) + intypes = tuple( + arg if ((ptype is None + or compat.dml12_inline in dml.globals.enabled_compat) + and (arg.constant or undefined(arg))) + else methfunc_param(ptype, arg) + for ((pname, ptype), arg) in zip(method.inp, inargs)) outtypes = tuple(methfunc_param(ptype, arg) for ((pname, ptype), arg) in zip(method.outp, outargs)) @@ -3546,8 +3551,9 @@ def codegen_inline(site, meth_node, indices, inargs, outargs, if inhibit_copyin or undefined(arg): param_scope.add(ExpressionSymbol(parmname, arg, arg.site)) - elif arg.constant and (parmtype is None - or dml.globals.compat_dml12): + elif arg.constant and ( + parmtype is None + or compat.dml12_inline in dml.globals.enabled_compat): # Constants must be passed directly to # provide constant folding. Other values are stored in a # local variable to improve type checking and variable @@ -3761,7 +3767,8 @@ def codegen_method_func(func): method.site) inline_scope = MethodParamScope(global_scope) for (name, e) in func.inp: - if dml.globals.dml_version == (1, 2) and not dml.globals.compat_dml12: + if dml.globals.dml_version == (1, 2) and ( + compat.dml12_misc not in dml.globals.enabled_compat): check_varname(method.site, name) if isinstance(e, Expression): inlined_arg = ( @@ -3969,7 +3976,7 @@ def codegen_call(site, meth_node, indices, inargs, outargs): require_fully_typed(site, meth_node) func = method_instance(meth_node) - if dml.globals.compat_dml12: + if compat.dml12_misc in dml.globals.enabled_compat: # For backward compatibility. See bug 21367. inargs = [mkCast(site, arg, TPtr(TNamed('char'))) if isinstance(arg, StringConstant) else arg diff --git a/py/dml/compat.py b/py/dml/compat.py new file mode 100644 index 000000000..eeae2aab7 --- /dev/null +++ b/py/dml/compat.py @@ -0,0 +1,225 @@ +# © 2023 Intel Corporation +# SPDX-License-Identifier: MPL-2.0 + +import abc +from dataclasses import dataclass + + +@dataclass(order=True, frozen=True) +class API: + ordinal: int + str: str + + +api_4_8 = API(4, "4.8") +api_5 = API(5, "5") +api_6 = API(6, "6") +api_7 = API(7, "7") + + +# All API versions known to the DML implementation. Note that the set +# of APIs accessible to the end-user is limited to what the associated +# Simics version supports. +apis = {api.str: api + for api in [api_4_8, api_5, api_6, api_7]} + + +class CompatFeature(abc.ABC): + def tag(self) -> str: + return self.__class__.__name__ + + @abc.abstractproperty + def __doc__(self): pass + + @abc.abstractproperty + def short(self) -> str: pass + + @abc.abstractproperty + def last_api_version(self) -> API: pass + + +# tag -> feature +features: dict[str, CompatFeature] = {} + + +def feature(cls: type[CompatFeature]): + assert issubclass(cls, CompatFeature) + singleton = cls() + features[singleton.tag()] = singleton + return singleton + + +@feature +class port_proxy_ifaces(CompatFeature): + '''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 feature is enabled, old-style interface ports + are created as proxies to the interfaces on the respective port + objects. Such proxies are not created for all banks and ports; + e.g., banks inside groups were not allowed in Simics 5, so such + banks do not need proxies for backward compatibility. + ''' + short = "Don't generate proxy port interfaces for banks and ports" + last_api_version = api_7 + + +@feature +class port_proxy_attrs(CompatFeature): + r'''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. Such proxy + attributes are only created When this feature is enabled. + Proxy attributes are not created for all banks and ports, in the + same manner as documented in the `port_proxy_ifaces` feature. + ''' + short = ("Don't generate top-level proxy attributes" + + " for attributes in banks and ports") + last_api_version = api_7 + + +@feature +class io_memory(CompatFeature): + '''The `transaction` interface was introduced in 6, and will + eventually replace the `io_memory` interface. When this feature is + enabled, the top-level parameter `use_io_memory` defaults to + `true`, causing `bank` objects to implement `io_memory` instead of + `transaction` by default.''' + short = 'Use the io_memory interface by default in banks' + last_api_version = api_6 + + +@feature +class port_obj_param(CompatFeature): + '''This compatibility feature changes the value of the `obj` + parameter in `bank` and `port` objects: Before Simics 6 there were + no dedicated port objects, so this parameter did not exist and if + you wrote `obj` inside a bank, this would resolve to + `dev.obj`. This feature preserves this legacy behaviour by making + the `obj` parameter of banks and ports resolves to `dev.obj` + rather than the port object. + ''' + short = "bank.obj and port.obj resolve to dev.obj" + last_api_version = api_5 + + +@feature +class dml12_inline(CompatFeature): + '''When using `inline` to inline a method in a DML 1.2 device, + constant parameters passed in typed arguments are inlined as + constants when this feature is enabled. This can improve + compilation time in some cases, but has some unintuitive semantic + implications. + ''' + short = "Don't inline method arguments with a declared type in DML 1.2" + last_api_version = api_6 + + +# separate class only because last_api_version differs +@feature +class dml12_not(CompatFeature): + '''DML 1.2-specific: the operand of the `!` operator is not + type-checked; in particular, negation expressions on the form + `!$reg`, where `reg` is a register, are permitted''' + short = "Disallow ! operator on register references in DML 1.2" + last_api_version = api_5 + + +@feature +class dml12_misc(CompatFeature): + '''This compatibility feature preserves a number of minor language quirks + that were originally in DML 1.2, but were cleaned up in + DML 1.4. When this feature is enabled, DML 1.2 will permit the following: + + * `sizeof(typename)` (see `WSIZEOFTYPE`) + + * the `typeof` operator on an expression that isn't an lvalue + + * `select` statements over `vect` types + + * Passing a string literal in a (non-`const`) `char *` method argument + + * Using the character `-` in the `c_name` parameter of `interface` objects + + * Using the `c_name` parameter to override interface type in + `implement` objects + + * `loggroup` identifiers are accessible under the same name in + generated C code + + * Applying the `&` operator on something that isn't an lvalue + (typically gives broken C code) + + * `extern` statements that do not specify a type (`extern foo;`) + + * Anonymous banks (`bank { ... }`) + + * Unused templates may instantiate non-existing templates + + * The same symbol may be used both for a top-level object (`$` + scope) and a top-level symbol (non-`$` scope, e.g. `extern`, + `constant` or `loggroup`) + + ''' + short = "Disable various DML 1.2 quirks" + last_api_version = api_6 + + +@feature +class dml12_goto(CompatFeature): + '''The `goto` statement is deprecated; this compatibility feature + preserves it. Most `goto` based control structures can be reworked by + changing the `goto` into a `throw`, and its label into a `catch` + block; since this is sometimes nontrivial, it can be useful to disable + the `goto` statement separately. + ''' + short = "Disable the goto statement in DML 1.2" + last_api_version = api_6 + + +@feature +class dml12_int(CompatFeature): + '''This compatibility feature affects many semantic details of + integer arithmetic. When this feature is enabled, DMLC translates + most integer operations directly into C, without compensating for + DML-specifics, like the support for odd-sized + uintNN types; this can sometimes have unexpected + consequences. The most immediate effect of disabling this feature + 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: + + * 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 DML + 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. + + * Some operations have undefined behaviour in C, which is + inherited by traditional DML 1.2. In modern DML this is + well-defined, e.g., an unconditional critical error on negative + shift or division by zero, and truncation on too large shift + operands or signed shift overflow. + + * Comparison operators `<`, `<=`, `==`, `>=`, `>` inherit C + semantics in traditional DML, whereas in modern DML they are + compared as integers. This sometimes makes a difference when + comparing signed and unsigned numbers; in particular, `-1 != + 0xffffffffffffffff` consistently in modern DML, whereas with + compatibility semantics, they are consiered different only if + both are constant. + + The `dml12_int` feature only applies to DML 1.2 files; if a DML + 1.4 file is imported from a DML 1.2 file, then modern DML + semantics is still used for operations in that file. + ''' + short = "Use legacy integer semantics in DML 1.2" + last_api_version = api_6 diff --git a/py/dml/crep.py b/py/dml/crep.py index 4d3a26dd8..8214c8dff 100644 --- a/py/dml/crep.py +++ b/py/dml/crep.py @@ -10,6 +10,7 @@ from .logging import * from .expr_util import * from .messages import * +from . import compat __all__ = ( 'cname', @@ -73,7 +74,8 @@ def cname(node): elif node.objtype == 'interface': # this is weird... kept for compatibility name = param_str(node, 'c_name').replace('-', '_') - if name != node.name and not dml.globals.compat_dml12: + if name != node.name and ( + compat.dml12_misc not in dml.globals.enabled_compat): report(WDEPRECATED(param_expr_site(node, 'c_name'), 'parameter c_name')) return name @@ -176,7 +178,7 @@ def node_storage_type_dml12(node, site): else: return None elif node.objtype == 'implement': - if dml.globals.compat_dml12: + if compat.dml12_misc in dml.globals.enabled_compat: typename = param_str(node, 'c_type') t = TNamed(typename) t.declaration_site = node.site @@ -226,7 +228,7 @@ def conf_object(site, node, indices): cref_portobj(node, indices[:node.dimensions])) def cloggroup(name): - if dml.globals.compat_dml12: + if compat.dml12_misc in dml.globals.enabled_compat: return name else: return '_dml_loggroup_' + name diff --git a/py/dml/ctree.py b/py/dml/ctree.py index 5d9fad091..04878f579 100644 --- a/py/dml/ctree.py +++ b/py/dml/ctree.py @@ -22,6 +22,7 @@ from .expr_util import * from .slotsmeta import auto_init from . import dmlparse, output +from . import compat import dml.globals # set from codegen.py codegen_call_expr = None @@ -1151,8 +1152,13 @@ def make(cls, site, lh, rh): lhtype = lh.ctype() rhtype = rh.ctype() - if isinstance(lhtype, TUnknown) or isinstance(rhtype, TUnknown): - return cls(site, lh, rh) + if (dml.globals.dml_version == (1, 2) + and (isinstance(lhtype, TUnknown) or isinstance(rhtype, TUnknown))): + # urgh, some classes take an extra constructor arg + if issubclass(cls, (ArithBinOp, BitBinOp, BitShift)): + return cls(site, lh, rh, TUnknown) + else: + return cls(site, lh, rh) return cls.make_simple(site, lh, rh) @@ -2455,7 +2461,8 @@ def make_simple(cls, site, rh): TFunction([TPtr(TNamed('conf_object_t')), TPtr(TVoid())], TVoid()))) - if not dml.globals.compat_dml12 and not isinstance(rh, LValue): + if (compat.dml12_misc not in dml.globals.enabled_compat + and not isinstance(rh, LValue)): raise ERVAL(rh.site, '&') return AddressOf(site, rh) @@ -2509,7 +2516,7 @@ class Not(UnaryOp): @staticmethod def make_simple(site, rh): - if not dml.globals.compat_dml12: + if compat.dml12_misc not in dml.globals.enabled_compat: rh = as_bool(rh) if rh.constant: return mkBoolConstant(site, not rh.value) @@ -3788,7 +3795,8 @@ def lookup_component(site, base, indices, name, only_local): indices = indices[:-base.local_dimensions()] return lookup_component(site, base.parent, indices, name, False) - if dml.globals.compat_dml12 and not base.parent: + if (compat.dml12_misc in dml.globals.enabled_compat + and not base.parent): # Last resort is to look for components in anonymous banks for bank in base.get_components('bank'): if not bank.name: diff --git a/py/dml/dmlc.py b/py/dml/dmlc.py index cd6b29da4..5821ff55a 100644 --- a/py/dml/dmlc.py +++ b/py/dml/dmlc.py @@ -12,6 +12,7 @@ from . import serialize from . import dmlparse from . import output +from . import compat import dml.c_backend import dml.info_backend @@ -282,6 +283,28 @@ def print_help(self): for tag in by_ignored[True]: print(f' {tag}') +class CompatHelpAction(HelpAction): + def print_help(self): + print('''\ +Tags accepted by --no-compat. Each of these represents a deprecated +compatibility feature that will be unavailable in all API versions +newer than a particular version. The --no-compat=TAG flag disables a +feature also when an older API version is used. This allows migration +to a new API version in smaller steps, and can also allow disabling +features that are scheduled for removal in a future API version.''') + by_version = {} + for feature in compat.features.values(): + if (feature.last_api_version.str in api_versions() + or (feature.last_api_version + > compat.apis[default_api_version()])): + by_version.setdefault(feature.last_api_version, []).append( + feature) + for (api, features) in sorted(by_version.items()): + print(f' Features available with --simics-api={api.str}' + ' or older:') + for feature in sorted(features, key=lambda f: f.tag()): + print(f' {feature.tag():20s} {feature.short}') + def main(argv): # DML files must be utf8, but are generally opened without specifying # the 'encoding' arg. This works only if utf8_mode is enabled. @@ -385,16 +408,11 @@ def main(argv): parser.add_argument('--werror', action='store_true', help='Turn all warnings into errors') - #
--strict
- #
Report errors for some constructs that will be forbidden in - # future versions of the DML language
parser.add_argument('--strict-dml12', action='store_true', - help='Report errors for some constructs that will be' - + ' forbidden in future versions of the DML language') + help='Alias for --no-compat=dml12_inline' + ',dml12_not,dml12_goto,dml12_misc,dml12_int') parser.add_argument('--strict-int', action='store_true', - help='Use DML 1.4 style integer arithmetic semantics' - + ' when compiling DML 1.2 files. Implied by' - + ' --strict-dml12.') + help='Alias for --no-compat=dml12_int') #
--coverity
#
Adds Synopsys® Coverity® analysis annotations to suppress common @@ -423,7 +441,7 @@ def main(argv): parser.add_argument( '--simics-api', action='store', metavar='VERSION', - default=default_api_version(), + default=str(default_api_version()), help=('specify Simics API version (default %s)' % default_api_version())) @@ -438,6 +456,19 @@ def main(argv): # # + #
--no-compat=TAG
+ #
Disable a compatibility feature
+ parser.add_argument( + '--no-compat', action='append', default=[], + help='Disable a compatibility feature') + + parser.add_argument( + '--help-no-compat', action=CompatHelpAction, + help='List the available tags for --no-compat') + + # + # + # Legacy: write deps to stdout, and assume it's redirected to .dmldep # Should be removed in 6 parser.add_argument( @@ -492,15 +523,14 @@ def main(argv): else: defs[name] = value - if options.simics_api not in api_versions(): - prerr("dmlc: the version '%s' is not a valid API version" % ( - options.simics_api)) - sys.exit(1) + api = dml.globals.api_version = compat.apis.get(options.simics_api) + if api is None: + parser.error(f"dmlc: the version '{options.simics_api}'" + " is not a valid API version") - if options.full_module and options.simics_api not in ['4.8']: - prerr("dmlc: the -m option is only valid together with --api=4.8" - " or older") - sys.exit(1) + if options.full_module and api != compat.api_4_8: + parser.error("dmlc: the -m option is only valid together with --api=4.8" + " or older") # This warning is disabled by default below Simics 7 due to sheer # prominence of the issue it warns about in existing code. @@ -508,7 +538,7 @@ def main(argv): # to handle the bugs as part of migration, instead of suddenly # overwhelming them with a truly massive amount of warnings in an # intermediate release. - if options.simics_api in {'4.8', '5', '6'}: + if api <= compat.api_6: ignore_warning('WLOGMIXUP') for w in options.disabled_warnings: @@ -558,8 +588,32 @@ def main(argv): + "The DMLC developers WILL NOT respect their use. " + "NEVER enable this flag for any kind of production code!!!***") - dml.globals.api_version = options.simics_api + features = {tag: feature + for (tag, feature) in compat.features.items() + if feature.last_api_version >= dml.globals.api_version} + for flag in options.no_compat: + for tag in flag.split(','): + if tag in compat.features: + if tag in features: + del features[tag] + else: + options.error(f'invalid tag {tag} for --no-compat.' + ' Try --help-no-compat.') + + if options.strict_int: + tag = compat.dml12_int.tag() + if tag in features: + del features[tag] + + if options.strict_dml12: + for feature in [compat.dml12_inline, compat.dml12_not, + compat.dml12_goto, compat.dml12_misc, + compat.dml12_int]: + tag = feature.tag() + if tag in features: + del features[tag] + dml.globals.enabled_compat = set(features.values()) inputfilename = options.input_filename @@ -602,14 +656,12 @@ def main(argv): dml.globals.serialized_traits = serialize.SerializedTraits() (dml_version, devname, headers, footers, global_defs, top_tpl, imported) = toplevel.parse_main_file( - inputfilename, options.import_path, options.strict_dml12) + inputfilename, options.import_path) logtime("parsing") if dml_version != (1, 2): logging.show_porting = False - dml.globals.strict_int_flag = options.strict_dml12 or options.strict_int - if 'DMLC_DUMP_INPUT_FILES' in os.environ: dump_input_files(outputbase, dict( imported, **{inputfilename: [os.path.basename(inputfilename)]})) diff --git a/py/dml/globals.py b/py/dml/globals.py index 8fd5592c1..6c31beb86 100644 --- a/py/dml/globals.py +++ b/py/dml/globals.py @@ -3,6 +3,8 @@ # Global variables +from . import compat + # name -> Template instance. An object declaration in a file are # represented implicitly as a template, named @filename.dml. templates = {} @@ -58,14 +60,13 @@ # types.TypeSequence -> codegen.TypeSequenceInfo type_sequence_infos = {} -# 1.4 style integer operations in 1.2, --strict-dml12-int -strict_int_flag = None +enabled_compat = set() + +# 1.4 style integer operations in 1.2 def compat_dml12_int(site): # if site is None, guess DML 1.4 - return not strict_int_flag and site and site.dml_version() == (1, 2) - -# True if compiling DML 1.2 without --strict. Set after parsing. -compat_dml12 = None + return (compat.dml12_int in enabled_compat + and site and site.dml_version() == (1, 2)) debuggable = False diff --git a/py/dml/structure.py b/py/dml/structure.py index 81364662d..59d7d61a6 100644 --- a/py/dml/structure.py +++ b/py/dml/structure.py @@ -16,6 +16,7 @@ from . import topsort from . import slotsmeta from . import ctree +from . import env from . import serialize from .logging import * from .codegen import * @@ -31,6 +32,7 @@ from .reginfo import explode_registers from . import dmlparse from .set import Set +from . import compat __all__ = ( 'mkglobals', 'mkdev' @@ -81,7 +83,7 @@ def mkglobals(stmts): for name in by_name: clash = by_name[name] - if len(clash) > 1 and dml.globals.compat_dml12: + if len(clash) > 1 and compat.dml12_misc in dml.globals.enabled_compat: # DML 1.2 permits multiple redundant 'extern foo;' # declarations; drop these for stmt in redundant_externs(clash): @@ -154,7 +156,7 @@ def mkglobals(stmts): if typ is None: # guaranteed by grammar assert dml.globals.dml_version == (1, 2) - if (not dml.globals.compat_dml12 + if (compat.dml12_misc not in dml.globals.enabled_compat and not site.filename().endswith('simics-api.dml')): report(EEXTERN(stmt.site)) typ = TUnknown() @@ -499,7 +501,7 @@ def add_templates(obj_specs, each_stmts): while i < len(queue): (site, tpl) = queue[i] i += 1 - if (dml.globals.compat_dml12 + if (compat.dml12_misc in dml.globals.enabled_compat and tpl.name in dml.globals.missing_templates): report(ENTMPL(site, tpl.name)) continue @@ -941,7 +943,8 @@ def create_object(site, ident, objtype, parent, assert not arraylen_asts return objects.Device(ident, site) elif objtype == 'bank': - if ident is None and not dml.globals.compat_dml12: + if (ident is None + and compat.dml12_misc not in dml.globals.enabled_compat): report(ESYNTAX(site, 'bank', 'anonymous banks are not allowed')) return objects.Bank(ident, site, parent, array_lens, index_vars) elif objtype == 'group': @@ -1018,8 +1021,6 @@ def make_autoparams(obj, index_vars, index_var_sites): autoparams['index'] = SimpleParamExpr(mkUndefined(site)) else: autoparams['indices'] = IndexListParamExpr(site, index_params) - autoparams['_be_bitorder'] = SimpleParamExpr( - mkBoolConstant(site, site.bitorder() == 'be')) if obj.objtype == 'device': with crep.DeviceInstanceContext(): @@ -1028,9 +1029,13 @@ def make_autoparams(obj, index_vars, index_var_sites): autoparams['banks'] = UninitializedParamExpr(site, 'banks') else: autoparams['NULL'] = NullParamExpr(site) + autoparams['_be_bitorder'] = SimpleParamExpr( + mkBoolConstant(site, site.bitorder() == 'be')) autoparams['simics_api_version'] = SimpleParamExpr( - mkStringConstant(site, dml.globals.api_version)) - + mkStringConstant(site, dml.globals.api_version.str)) + for (tag, feature) in compat.features.items(): + autoparams[f'_compat_{tag}'] = SimpleParamExpr( + mkBoolConstant(site, feature in dml.globals.enabled_compat)) dml.globals.device = obj elif obj.objtype == 'bank': @@ -1076,7 +1081,8 @@ def make_autoparams(obj, index_vars, index_var_sites): # Add common automatic parameters autoparams['qname'] = QNameParamExpr(obj, 'device') - autoparams['_static_qname'] = StaticQNameParamExpr(obj, 'device') + if dml.globals.dml_version != (1, 2): + autoparams['_static_qname'] = StaticQNameParamExpr(obj, 'device') if obj.parent: autoparams['parent'] = ParentParamExpr(obj) @@ -1129,7 +1135,8 @@ def create_parameters(obj, obj_specs, index_vars, index_sites): parameters.setdefault(name, []).append((obj_spec.rank, s)) autoparams = make_autoparams(obj, index_vars, index_sites) - + for name in autoparams: + assert name in parameters, name return [mkparam(obj, autoparams, merge_parameters(parameters[name], obj_specs)) for name in sorted(parameters)] @@ -1875,7 +1882,7 @@ def mkobj2(obj, obj_specs, params, each_stmts): param.get_expr(zero_index * param.dimensions) except DMLError as e: if (dml.globals.dml_version == (1, 2) - and dml.globals.api_version <= '5' + and dml.globals.api_version <= compat.api_5 and isinstance(e, EREF)): # We forgive some errors in unused parameters, to # avoid the annoyance caused by hard errors from code @@ -1895,12 +1902,18 @@ def mkobj2(obj, obj_specs, params, each_stmts): else: report(e) if (dml.globals.dml_version != (1, 2) - or not dml.globals.compat_dml12): + or compat.dml12_misc not in dml.globals.enabled_compat): # TODO: this should be handled cleaner in the case of pure # 1.4 code for p in obj.get_components(): sym = global_scope.lookup(p.name) - if sym: + if sym and ( + # hacky workaround for the ExpressionSymbol + # implicitly added above. Needed when importing 1.4 + # code from 1.2 with --no-compat=dml12_misc + dml.globals.dml_version != (1, 2) + or p.site.dml_version == (1, 2) + or p.site != sym.site): report(ENAMECOLL(p.site, sym.site, p.name)) # At this point, methods and subobjs are created and we can @@ -2582,7 +2595,8 @@ def need_port_proxy_attrs(port): assert port.objtype in {'port', 'bank', 'subdevice'} return (port.objtype in {'port', 'bank'} and port.dimensions <= 1 - and port.parent is dml.globals.device) + and port.parent is dml.globals.device + and compat.port_proxy_attrs in dml.globals.enabled_compat) class ConfAttrParentObjectProxyInfoParamExpr(objects.ParamExpr): '''The _parent_obj_proxy_info parameter of a attribute, register, or diff --git a/py/dml/template.py b/py/dml/template.py index 6f291c145..13b04a1c7 100644 --- a/py/dml/template.py +++ b/py/dml/template.py @@ -5,6 +5,7 @@ import os from . import ast, logging +from . import compat from .logging import * from .messages import * from .set import Set @@ -305,7 +306,8 @@ def process_templates(template_decls): # fallback: add missing templates and retry for missing in all_missing: site = references[missing].site - if missing not in uncond_refs or dml.globals.compat_dml12: + if (missing not in uncond_refs + or compat.dml12_misc in dml.globals.enabled_compat): # delay error until template instantiation dml.globals.missing_templates.add(missing) else: diff --git a/py/dml/toplevel.py b/py/dml/toplevel.py index e5b180406..bbc20efca 100644 --- a/py/dml/toplevel.py +++ b/py/dml/toplevel.py @@ -14,6 +14,7 @@ from ply import lex, yacc from . import objects, logging, codegen, ctree, ast +from . import compat from . import symtab from .messages import * from .logging import * @@ -347,7 +348,7 @@ def exists(filename): return True return os.path.exists(filename) -def parse_main_file(inputfilename, explicit_import_path, strict): +def parse_main_file(inputfilename, explicit_import_path): if not exists(inputfilename): raise ENOFILE(SimpleSite(f"{inputfilename}:0")) (kind, site, name, stmts) = parse_dmlast_or_dml(inputfilename) @@ -357,7 +358,10 @@ def parse_main_file(inputfilename, explicit_import_path, strict): version = site.dml_version() dml.globals.dml_version = version version_str = fmt_version(version) - dml.globals.compat_dml12 = version == (1, 2) and not strict + if version != (1, 2): + for feature in [compat.dml12_inline, compat.dml12_not, + compat.dml12_misc]: + dml.globals.enabled_compat.discard(feature) implicit_imports = [ ast.import_(site, "dml-builtins.dml"), @@ -379,12 +383,10 @@ def parse_main_file(inputfilename, explicit_import_path, strict): for path in [os.path.join(orig_path, version_str), orig_path]] - if version == (1, 2) and dml.globals.api_version not in ( - # we may want to add "8" if we want to postpone the - # deprecation of DML 1.2 - "4.8", "5", "6", "7", "internal"): - raise ESIMAPI(site, fmt_version(version), - dml.globals.api_version) + # we may want to bump last version to 8 if we want to postpone the + # deprecation of DML 1.2 + if version == (1, 2) and dml.globals.api_version > compat.api_7: + raise ESIMAPI(site, fmt_version(version), dml.globals.api_version.str) # Map normalized, absolute path of an imported file, to list of # seen spellings. One spelling is a string in an import statement which diff --git a/py/dml/types.py b/py/dml/types.py index 481b751e2..a329b2e58 100644 --- a/py/dml/types.py +++ b/py/dml/types.py @@ -56,6 +56,7 @@ from .output import out from .messages import * from .logging import * +from . import compat import dml .globals import abc @@ -482,7 +483,7 @@ def cmp(self, other): if isinstance(self, TSize) != isinstance(other, TSize): return NotImplemented if (dml.globals.dml_version == (1, 2) - and not dml.globals.strict_int_flag): + and compat.dml12_int in dml.globals.enabled_compat): # Ignore signedness return 0 if self.bits == other.bits else NotImplemented else: @@ -552,7 +553,8 @@ def canstore(self, other): other = realtype(other) if other.is_int: trunc = (other.bits > self.bits) - if dml.globals.compat_dml12 and isinstance(other, TBool): + if (compat.dml12_misc in dml.globals.enabled_compat + and isinstance(other, TBool)): return (False, False, constviol) return (True, trunc, constviol) if other.is_float and not self.is_bitfields: @@ -737,7 +739,7 @@ def sizeof(self): return None return self.size.value * elt_size def cmp(self, other): - if dml.globals.compat_dml12: + if compat.dml12_misc in dml.globals.enabled_compat: if isinstance(other, (TArray, TPtr)): return self.base.cmp(other.base) elif isinstance(other, (TPtr, TArray)): @@ -768,7 +770,7 @@ def key(self): def describe(self): return 'pointer to %s' % (self.base.describe()) def cmp(self, other): - if dml.globals.compat_dml12: + if compat.dml12_misc in dml.globals.enabled_compat: if isinstance(other, TPtr): # Can only compare for voidness or equality if self.base.void or other.base.void: @@ -1231,9 +1233,9 @@ def parse_type(typename): return TBool() elif typename == 'void': return TVoid() - elif typename == 'integer_t' and dml.globals.api_version < '7': + elif typename == 'integer_t' and dml.globals.api_version < compat.api_7: return TInt(64, True) - elif typename == 'uinteger_t' and dml.globals.api_version < '7': + elif typename == 'uinteger_t' and dml.globals.api_version < compat.api_7: return TInt(64, False) else: return TNamed(typename) diff --git a/test/1.2/syntax/T_int.dml b/test/1.2/syntax/T_int.dml index c9125a4e9..ac9e1a5ad 100644 --- a/test/1.2/syntax/T_int.dml +++ b/test/1.2/syntax/T_int.dml @@ -10,8 +10,8 @@ method test -> (bool ok) { local int i = 123_456; local int b1 = 0b_1010_0101; local int b2 = 0b1_01__00___101; - local int x1 = 0x_dead_beef; - local int x2 = 0xd_ea__db___eef; + local uint32 x1 = 0x_dead_beef; + local uint32 x2 = 0xd_ea__db___eef; ok = (i == 123456 && b1 == 0b10100101 && b2 == 0b10100101 && x1 == 0xdeadbeef && x2 == 0xdeadbeef); diff --git a/test/1.4/legacy/T_port_proxy_attrs_disabled.dml b/test/1.4/legacy/T_port_proxy_attrs_disabled.dml new file mode 100644 index 000000000..ee9d4a4f9 --- /dev/null +++ b/test/1.4/legacy/T_port_proxy_attrs_disabled.dml @@ -0,0 +1,16 @@ +/* + © 2023 Intel Corporation + SPDX-License-Identifier: MPL-2.0 +*/ +dml 1.4; + +device test; + +/// DMLC-FLAG --simics-api=6 +/// DMLC-FLAG --no-compat=port_proxy_attrs + +import "port_proxy_attrs.dml"; + +method init() { + test(false); +} diff --git a/test/1.4/legacy/T_port_proxy_attrs_enabled.dml b/test/1.4/legacy/T_port_proxy_attrs_enabled.dml new file mode 100644 index 000000000..7dd7c2c14 --- /dev/null +++ b/test/1.4/legacy/T_port_proxy_attrs_enabled.dml @@ -0,0 +1,15 @@ +/* + © 2023 Intel Corporation + SPDX-License-Identifier: MPL-2.0 +*/ +dml 1.4; + +device test; + +/// DMLC-FLAG --simics-api=6 + +import "port_proxy_attrs.dml"; + +method init() { + test(true); +} diff --git a/test/1.4/legacy/T_port_proxy_ifaces_disabled.dml b/test/1.4/legacy/T_port_proxy_ifaces_disabled.dml new file mode 100644 index 000000000..7691d5b88 --- /dev/null +++ b/test/1.4/legacy/T_port_proxy_ifaces_disabled.dml @@ -0,0 +1,16 @@ +/* + © 2023 Intel Corporation + SPDX-License-Identifier: MPL-2.0 +*/ +dml 1.4; + +device test; + +/// DMLC-FLAG --simics-api=6 +/// DMLC-FLAG --no-compat=port_proxy_ifaces + +import "port_proxy_ifaces.dml"; + +method init() { + test(false); +} diff --git a/test/1.4/legacy/T_port_proxy_ifaces_enabled.dml b/test/1.4/legacy/T_port_proxy_ifaces_enabled.dml new file mode 100644 index 000000000..b7a04ec9e --- /dev/null +++ b/test/1.4/legacy/T_port_proxy_ifaces_enabled.dml @@ -0,0 +1,15 @@ +/* + © 2023 Intel Corporation + SPDX-License-Identifier: MPL-2.0 +*/ +dml 1.4; + +device test; + +/// DMLC-FLAG --simics-api=6 + +import "port_proxy_ifaces.dml"; + +method init() { + test(true); +} diff --git a/test/1.4/legacy/port_proxy_attrs.dml b/test/1.4/legacy/port_proxy_attrs.dml new file mode 100644 index 000000000..4aaf814de --- /dev/null +++ b/test/1.4/legacy/port_proxy_attrs.dml @@ -0,0 +1,17 @@ +/* + © 2023 Intel Corporation + SPDX-License-Identifier: MPL-2.0 +*/ +dml 1.4; + +import "simics/simulator/conf-object.dml"; + +port p { + attribute a is uint64_attr; +} + +method test(bool proxies_enabled) { + assert _compat_port_proxy_attrs == proxies_enabled; + assert SIM_class_has_attribute(SIM_object_class(dev.obj), "p_a") + == proxies_enabled; +} diff --git a/test/1.4/legacy/port_proxy_ifaces.dml b/test/1.4/legacy/port_proxy_ifaces.dml new file mode 100644 index 000000000..c31ea754e --- /dev/null +++ b/test/1.4/legacy/port_proxy_ifaces.dml @@ -0,0 +1,23 @@ +/* + © 2023 Intel Corporation + SPDX-License-Identifier: MPL-2.0 +*/ +dml 1.4; + +import "simics/devs/signal.dml"; + +saved bool raised = false; + +port p { + implement signal { + method signal_raise() { + raised = true; + } + } +} + +method test(bool proxies_enabled) { + assert _compat_port_proxy_ifaces == proxies_enabled; + assert (SIM_c_get_port_interface(dev.obj, "signal", "p") == NULL) + == !proxies_enabled; +} diff --git a/test/tests.py b/test/tests.py index cc0fe7d02..908f2a676 100644 --- a/test/tests.py +++ b/test/tests.py @@ -936,6 +936,12 @@ def test(self): assert (dir / (self.shortname + '.c')).is_file() all_tests = [] +def subtest(*args, **kwargs): + def register(cls): + all_tests.append(cls(*args, **kwargs)) + return cls + return register + # First, some special cases class ErrorTest(CTestCase): @@ -1102,6 +1108,21 @@ def test(self): all_tests.append(DebuggableCheck('debuggable-check')) +@subtest('--help-no-compat') +@subtest('--help-warn') +@subtest('--help') +class HelpTest(BaseTestCase): + '''Check that some DMLC flag works''' + __slots__ = () + def test(self): + cmd = main_dmlc + [self.fullname] + self.pr(f"Running: {' '.join(cmd)}") + ret = subprocess.run(cmd, capture_output=True, text=True) + self.pr(ret.stderr) + self.pr(ret.stdout) + if ret.returncode: + raise TestFail('dmlc returncode {ret.returncode}') + class DmlDepBase(CTestCase): '''Base class for DML dependency test cases.''' __slots__ = ()