diff --git a/py/dead_dml_methods.py b/py/dead_dml_methods.py index 6083bf0e..9d5735fd 100644 --- a/py/dead_dml_methods.py +++ b/py/dead_dml_methods.py @@ -76,7 +76,7 @@ def traverse_ast(ast): for stmt in stmts: yield from traverse_ast(stmt) elif ast.kind == 'object': - (_, _, _, stmts) = ast.args + (_, _, _, _, stmts) = ast.args for stmt in stmts: yield from traverse_ast(stmt) elif ast.kind == 'method': diff --git a/py/dml/dmlparse.py b/py/dml/dmlparse.py index 4daa296c..aabb3c8a 100644 --- a/py/dml/dmlparse.py +++ b/py/dml/dmlparse.py @@ -297,8 +297,8 @@ def toplevel_else_if(t): @prod_dml12 def object_anonymous_bank(t): - 'object : BANK object_spec' - t[0] = ast.object_(site(t), None, 'bank', [], t[2]) + 'object : maybe_extension BANK object_spec' + t[0] = ast.object_(site(t), None, 'bank', [], None, t[3]) @prod def array_list_empty(t): @@ -312,9 +312,9 @@ def array_list(t): @prod def object_regarray(t): - 'object : REGISTER objident array_list sizespec offsetspec maybe_istemplate object_spec' - t[0] = ast.object_(site(t), t[2], 'register', t[3], - t[4] + t[5] + t[6] + t[7]) + 'object : maybe_extension REGISTER objident array_list sizespec offsetspec maybe_istemplate object_spec' + t[0] = ast.object_(site(t), t[3], 'register', t[4], t[1], + t[5] + t[6] + t[7] + t[8]) @prod def bitrangespec(t): @@ -328,8 +328,8 @@ def bitrangespec_empty(t): @prod_dml14 def object_field(t): - 'object : FIELD objident array_list bitrangespec maybe_istemplate object_spec' - t[0] = ast.object_(site(t), t[2], 'field', t[3], t[4] + t[5] + t[6]) + 'object : maybe_extension FIELD objident array_list bitrangespec maybe_istemplate object_spec' + t[0] = ast.object_(site(t), t[3], 'field', t[4], t[1], t[5] + t[6] + t[7]) def endian_translate_bit(expr, width, bitorder): if bitorder == 'be': @@ -368,10 +368,10 @@ def bitrange_2(t): @prod_dml12 def object_field_1(t): - 'object : FIELD objident bitrange maybe_istemplate object_spec' + 'object : maybe_extension FIELD objident bitrange maybe_istemplate object_spec' if logging.show_porting: - report(PFIELDRANGE(site(t, 3))) - t[0] = ast.object_(site(t), t[2], 'field', [], t[3] + t[4] + t[5]) + report(PFIELDRANGE(site(t, 4))) + t[0] = ast.object_(site(t), t[3], 'field', [], None, t[4] + t[5] + t[6]) @prod_dml12 def field_array_size_no(t): @@ -397,8 +397,8 @@ def field_array_size(t): @prod_dml12 def object_field_2(t): - 'object : FIELD objident fieldarraysize bitrangespec maybe_istemplate object_spec' - t[0] = ast.object_(site(t), t[2], 'field', t[3], t[4] + t[5] + t[6]) + 'object : maybe_extension FIELD objident fieldarraysize bitrangespec maybe_istemplate object_spec' + t[0] = ast.object_(site(t), t[3], 'field', t[4], None, t[5] + t[6] + t[7]) @prod_dml14 def data(t): @@ -472,20 +472,20 @@ def saved_decl_many_init(t): @prod def object3(t): - '''object : CONNECT objident array_list maybe_istemplate object_spec - | INTERFACE objident array_list maybe_istemplate object_spec - | ATTRIBUTE objident array_list maybe_istemplate object_spec - | BANK objident array_list maybe_istemplate object_spec - | EVENT objident array_list maybe_istemplate object_spec - | GROUP objident array_list maybe_istemplate object_spec - | PORT objident array_list maybe_istemplate object_spec - | IMPLEMENT objident array_list maybe_istemplate object_spec''' - t[0] = ast.object_(site(t), t[2], t[1], t[3], t[4] + t[5]) + '''object : maybe_extension CONNECT objident array_list maybe_istemplate object_spec + | maybe_extension INTERFACE objident array_list maybe_istemplate object_spec + | maybe_extension ATTRIBUTE objident array_list maybe_istemplate object_spec + | maybe_extension BANK objident array_list maybe_istemplate object_spec + | maybe_extension EVENT objident array_list maybe_istemplate object_spec + | maybe_extension GROUP objident array_list maybe_istemplate object_spec + | maybe_extension PORT objident array_list maybe_istemplate object_spec + | maybe_extension IMPLEMENT objident array_list maybe_istemplate object_spec''' + t[0] = ast.object_(site(t), t[3], t[2], t[4], t[1], t[5] + t[6]) @prod_dml14 def object_subdevice(t): - '''object : SUBDEVICE objident array_list maybe_istemplate object_spec''' - t[0] = ast.object_(site(t), t[2], t[1], t[3], t[4] + t[5]) + '''object : maybe_extension SUBDEVICE objident array_list maybe_istemplate object_spec''' + t[0] = ast.object_(site(t), t[3], t[2], t[4], t[1], t[5] + t[6]) @prod_dml12 def maybe_extern_yes(t): @@ -884,6 +884,31 @@ def object_desc_none(t): 'object_desc :' t[0] = [] +@prod_dml14 +def maybe_extension_yes(t): + 'maybe_extension : IN' + if not site(t).provisional_enabled( + provisional.explicit_object_extensions): + report(ESYNTAX(site(t), 'in', None)) + t[0] = None + else: + t[0] = True + +@prod_dml14 +def maybe_extension_no(t): + 'maybe_extension : ' + enabled = (provisional.explicit_object_extensions + in t.parser.file_info.provisional) + t[0] = False if enabled else None + +# Note that `maybe_extension` is used even in DML 1.2 exclusive rules, which +# may seem redundant, but no! Removing those uses would lead to shift/reduce +# conflicts. +@prod_dml12 +def maybe_extension(t): + 'maybe_extension : ' + t[0] = None + @prod def object_spec_none(t): 'object_spec : object_desc SEMI' diff --git a/py/dml/messages.py b/py/dml/messages.py index f21cec00..79b77dbe 100644 --- a/py/dml/messages.py +++ b/py/dml/messages.py @@ -1169,6 +1169,41 @@ def log(self): DMLError.log(self) self.print_site_message(self.other_site, "existing declaration") + +class EEXTENSION(DMLError): + """When the `explict_object_extensions` provisional feature is enabled, + any object definition made via `in` syntax is considered an extension such + that there must be some other non-extension declaration of the object, or + DMLC will reject the extension. + To declare and define a new object not already declared, omit the `in` + syntax. + """ + fmt = ("object '%s' not declared elsewhere." + " To declare and define a new object, omit 'in'.") + +class EMULTIOBJDECL(DMLError): + """When the `explicit_object_extensions` provisional feature is enabled, + any object declaration not made using `in` syntax is considered a + declaration of a novel object — because of that, DMLC will reject + it if there already is another non-`in` declaration across files enabling + `explicit_object_extensions`. + """ + fmt = ("object '%s' already declared." + " To extend upon the definition of an object, use 'in %s'") + def __init__(self, site, other_site, objtype, name): + super().__init__(site, name, f'{objtype} {name} ...') + self.other_site = other_site + + def log(self): + from . import provisional + DMLError.log(self) + self.print_site_message(self.other_site, "existing declaration") + prov_site = self.site.provisional_enabled( + provisional.explicit_object_extensions) + self.print_site_message( + prov_site, + "enabled by the explicit_object_extensions provisional feature") + class EVARPARAM(DMLError): """ The value assigned to the parameter is not a well-defined constant. diff --git a/py/dml/provisional.py b/py/dml/provisional.py index 8a64c6d7..9f6f9a7f 100644 --- a/py/dml/provisional.py +++ b/py/dml/provisional.py @@ -135,6 +135,34 @@ class simics_util_vect(ProvisionalFeature): stable = True dml12 = True + +@feature +class explicit_object_extensions(ProvisionalFeature): + ''' + This feature extends the DML syntax for object declarations to distinguish + between an intent to introduce a new object to the model structure, and an + intent to extend the definition of an existing object. + + The following form is introduced to mark the intent to extend an object: +
+    in object-type name ... { ... }
+    
+ E.g. +
+    in bank some_bank { ... }
+    
+ + If this form is used while there is no other non-extension declaration of + the named object, then DMLC will signal an error because the definition + was not intended to introduce the object to the model structure. + + DMLC will also signal an error if there is more than one non-extension + declaration of the object among the files enabling + `explicit_object_extensions`. + ''' + short = "Require `in` syntax for additional declarations of an object" + stable = False + def parse_provisional( provs: list[("Site", str)]) -> dict[ProvisionalFeature, "Site"]: ret = {} diff --git a/py/dml/structure.py b/py/dml/structure.py index b78b3c24..e427a74c 100644 --- a/py/dml/structure.py +++ b/py/dml/structure.py @@ -503,9 +503,9 @@ def wrap_sites(spec, issite, tname): else: raise ICE(issite, 'unknown node type %r %r' % (asttype, stmt)) composite_wrapped = [ - (objtype, name, arrayinfo, + (objtype, name, arrayinfo, is_extension, ObjectSpec(*wrap_sites(spec, issite, tname))) - for (objtype, name, arrayinfo, spec) in composite] + for (objtype, name, arrayinfo, is_extension, spec) in composite] blocks.append((preconds, shallow_wrapped, composite_wrapped)) return (TemplateSite(spec.site, issite, tname), spec.rank, @@ -833,8 +833,8 @@ def sort_method_implementations(implementations, obj_specs): return traits.sort_method_implementations(implementations) def merge_subobj_defs(def1, def2, parent): - (objtype, name, arrayinfo, obj_specs1) = def1 - (objtype2, name2, arrayinfo2, obj_specs2) = def2 + (objtype, name, arrayinfo, extension_status1, obj_specs1) = def1 + (objtype2, name2, arrayinfo2, extension_status2, obj_specs2) = def2 assert name == name2 site1 = obj_specs1[0].site @@ -844,6 +844,26 @@ def merge_subobj_defs(def1, def2, parent): report(ENAMECOLL(site1, site2, name)) return def1 + # extension status: + # None -> dual extension and decl + # True -> extension + # False -> explicit decl + if extension_status1 is extension_status2 is False: + # Blame the one with higher rank if possible. Otherwise, blame the + # new def. + (decl_site_1, decl_site_2) = ( + (site1, site2) if ( + obj_specs2[0].rank in obj_specs1[0].rank.inferior) + else (site2, site1)) + report(EMULTIOBJDECL(decl_site_1, decl_site_2, objtype, name)) + # One is explicit decl: merged is explicit decl + elif extension_status1 is False or extension_status2 is False: + extension_status1 = False + # If either is dual, make dual + elif extension_status1 is None or extension_status2 is None: + extension_status1 = None + # Otherwise, both are explicit extensions. Keep explicit extension. + if len(arrayinfo) != len(arrayinfo2): raise EAINCOMP(site1, site2, name, "mixing declarations with different number " @@ -870,7 +890,8 @@ def merge_subobj_defs(def1, def2, parent): merged_arrayinfo.append((idxvar1, len1)) - return (objtype, name, merged_arrayinfo, obj_specs1 + obj_specs2) + return (objtype, name, merged_arrayinfo, extension_status1, + obj_specs1 + obj_specs2) def method_is_std(node, methname): """ @@ -1708,13 +1729,14 @@ def mkobj2(obj, obj_specs, params, each_stmts): for (stmts, obj_spec) in composite_subobjs: for s in stmts: - (objtype, ident, arrayinfo, subobj_spec) = s + (objtype, ident, arrayinfo, is_extension, subobj_spec) = s if ident is None: assert (dml.globals.dml_version == (1, 2) and objtype in {'bank', 'field'}) - subobj_def = (objtype, ident, arrayinfo, [subobj_spec]) + subobj_def = (objtype, ident, arrayinfo, is_extension, + [subobj_spec]) if ident in subobj_defs: subobj_defs[ident] = merge_subobj_defs(subobj_defs[ident], subobj_def, obj) @@ -1724,7 +1746,10 @@ def mkobj2(obj, obj_specs, params, each_stmts): symbols[ident] = subobj_spec.site subobj_defs[ident] = subobj_def - for (_, _, arrayinfo, specs) in subobj_defs.values(): + for (_, ident, arrayinfo, extension_status, specs) in subobj_defs.values(): + if extension_status is True: + for spec in specs: + report(EEXTENSION(spec.site, ident)) for (i, (idx, dimsize_ast)) in enumerate(arrayinfo): if dimsize_ast is None: idxref = (f" (with index variable '{idx.args[0]}')" @@ -1814,7 +1839,7 @@ def mkobj2(obj, obj_specs, params, each_stmts): # the whole register. if obj.objtype == 'register' and not any( objtype == 'field' - for (objtype, _, _, _) in list(subobj_defs.values())): + for (objtype, _, _, _, _) in list(subobj_defs.values())): # The implicit field instantiates the built-in field # template and does nothing else. subobjs.append(mkobj( @@ -1856,7 +1881,7 @@ def mkobj2(obj, obj_specs, params, each_stmts): subobj_name_defs = {} for name in sorted(subobj_defs, key=lambda name: name or ''): - (objtype, ident, arrayinfo, subobj_specs) = subobj_defs[name] + (objtype, ident, arrayinfo, _, subobj_specs) = subobj_defs[name] if (not obj.accepts_child_type(objtype) # HACK: disallow non-toplevel banks in DML 1.2, see SIMICS-19009 or (dml.globals.dml_version == (1, 2) diff --git a/py/dml/template.py b/py/dml/template.py index 348d66ef..fd5b91b1 100644 --- a/py/dml/template.py +++ b/py/dml/template.py @@ -123,7 +123,7 @@ def defined_symbols(self): symbols[sub.args[0]] = (sub.kind, sub.site) else: assert sub.kind == 'error' - for (_, name, _, specs) in composite: + for (_, name, _, _, specs) in composite: symbols[name] = ('subobj', specs.site) return symbols @@ -299,11 +299,12 @@ def obj_from_asts(site, stmts): block = [] for decl_ast in composite: assert decl_ast.kind == 'object' - (name, objtype, indices, sub_stmts) = decl_ast.args + (name, objtype, indices, is_extension, + sub_stmts) = decl_ast.args spec = obj_from_asts( decl_ast.site, sub_stmts + [ast.is_( decl_ast.site, [(decl_ast.site, objtype)])]) - block.append((objtype, name, indices, spec)) + block.append((objtype, name, indices, is_extension, spec)) blocks.append((preconds, shallow, block)) return ObjectSpec(site, rank, is_stmts, in_eachs, params, blocks) return obj_from_asts(site, stmts) @@ -331,7 +332,7 @@ def rank_structure(asts): while queue: (spec, conditional) = queue.pop() if spec.kind == 'object': - (_, objtype, _, stmts) = spec.args + (_, objtype, _, _, stmts) = spec.args inferior[objtype] = spec queue.extend((stmt, conditional) for stmt in stmts) elif spec.kind == 'is': diff --git a/test/1.4/provisional/T_explicit_object_extensions.dml b/test/1.4/provisional/T_explicit_object_extensions.dml new file mode 100644 index 00000000..9fd8d747 --- /dev/null +++ b/test/1.4/provisional/T_explicit_object_extensions.dml @@ -0,0 +1,37 @@ +/* + © 2025 Intel Corporation + SPDX-License-Identifier: MPL-2.0 +*/ + +dml 1.4; + +provisional explicit_object_extensions; + +device test; + +import "explicit_object_extensions_disabled.dml"; + +template t { + group g1 { + group child; + } + in group g2 { + group child; + } +} + +template u { + in group g1 { + in group child { + session int dummy; + } + } + group g2 { + in group child { + session int dummy; + } + } +} + +group common_1; +in group common_2; diff --git a/test/1.4/provisional/T_explicit_object_extensions_EEXTENSION.dml b/test/1.4/provisional/T_explicit_object_extensions_EEXTENSION.dml new file mode 100644 index 00000000..05f9dbf4 --- /dev/null +++ b/test/1.4/provisional/T_explicit_object_extensions_EEXTENSION.dml @@ -0,0 +1,40 @@ +/* + © 2025 Intel Corporation + SPDX-License-Identifier: MPL-2.0 +*/ + +dml 1.4; + +/// ERROR EMULTIOBJDECL +provisional explicit_object_extensions; + +device test; + +/// ERROR EEXTENSION +in group ext; + +/// ERROR EMULTIOBJDECL +group decl; +/// ERROR EMULTIOBJDECL +group decl; + +template t { + /// ERROR EMULTIOBJDECL + group child_1; + // no error + group child_2; +} + +group g is t { + /// ERROR EMULTIOBJDECL + group child_1; + // no error + in group child_2; +} + +in each t { + /// ERROR EMULTIOBJDECL + group child_1; + // no error + in group child_2; +} diff --git a/test/1.4/provisional/T_explicit_object_extensions_ESYNTAX.dml b/test/1.4/provisional/T_explicit_object_extensions_ESYNTAX.dml new file mode 100644 index 00000000..a5beec71 --- /dev/null +++ b/test/1.4/provisional/T_explicit_object_extensions_ESYNTAX.dml @@ -0,0 +1,11 @@ +/* + © 2025 Intel Corporation + SPDX-License-Identifier: MPL-2.0 +*/ + +dml 1.4; + +device test; + +/// ERROR ESYNTAX +in group g {} diff --git a/test/1.4/provisional/explicit_object_extensions_disabled.dml b/test/1.4/provisional/explicit_object_extensions_disabled.dml new file mode 100644 index 00000000..f8c0aae3 --- /dev/null +++ b/test/1.4/provisional/explicit_object_extensions_disabled.dml @@ -0,0 +1,9 @@ +/* + © 2025 Intel Corporation + SPDX-License-Identifier: MPL-2.0 +*/ + +dml 1.4; + +group common_1; +group common_2;