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;