diff --git a/RELEASENOTES-1.4.docu b/RELEASENOTES-1.4.docu index 592e86d75..6945da14e 100644 --- a/RELEASENOTES-1.4.docu +++ b/RELEASENOTES-1.4.docu @@ -521,4 +521,20 @@ This fix is only enabled by default with Simics API version 7 or above. With version 6 or below it must be explicitly enabled by passing --no-compat=shared_logs_on_device to DMLC. + Added the discard reference + '_' — a non-value expression which may be used as an assign + target in order to explictly discard the result of an evaluated expression + or return value of a method call . + Example usage: +
+      _ = any_expression;
+      _ = throwing_method();
+      (_, x, _) = method_with_multiple_return_values();
+      
+ For backwards compatibility, declared variables and object members are + still allowed to be named '_' with Simics API version 6 or below. + Any such declaration will shadow the discard reference — + i.e. make it unavailable within the scope that the declaration is + accessible. This compatibility feature can be disabled by passing + --no-compat=discard_ref_shadowing to DMLC.
diff --git a/doc/1.4/language.md b/doc/1.4/language.md index 7c9923baa..c5bc22bce 100644 --- a/doc/1.4/language.md +++ b/doc/1.4/language.md @@ -3808,6 +3808,33 @@ independent method callback(int i, void *aux) { } ``` +### The Discard Reference (`_`) +``` +_ +``` + +The discard reference *`_`* is an expression without any run-time representation +that may be used as the target of an assignment in order to explicitly discard +the result of an evaluated expression or return value of a method call. + +When the compatibility feature `discard_ref_shadowing` is enabled, `_` is not a +keyword, but instead behaves more closely as a global identifier. +What this means is that declared identifiers (e.g. local variables) are allowed +to shadow it by being named `_`. + +Example usage: +``` +// Evaluate an expression and explicitly discard its result. +// Can be relevant to e.g. suppress Coverity's CHECKED_RETURN checker +_ = nonthrowing_single_return_method(); + +// Calls to methods that throw or have multiple return values require a target +// for each return value. `_` can be used to discard return values not of +// interest. +_ = throwing_method(); +(_, x, _) = method_with_multiple_return_values(); +``` + ### New Expressions
diff --git a/lib/1.2/dml-builtins.dml b/lib/1.2/dml-builtins.dml
index 0b29285ea..a75c1120b 100644
--- a/lib/1.2/dml-builtins.dml
+++ b/lib/1.2/dml-builtins.dml
@@ -211,6 +211,7 @@ template device {
     parameter _compat_port_obj_param auto;
     parameter _compat_io_memory auto;
     parameter _compat_shared_logs_on_device auto;
+    parameter _compat_discard_ref_shadowing auto;
     parameter _compat_dml12_inline auto;
     parameter _compat_dml12_not auto;
     parameter _compat_dml12_goto auto;
diff --git a/lib/1.4/dml-builtins.dml b/lib/1.4/dml-builtins.dml
index 86c3b8031..64b4d2b6f 100644
--- a/lib/1.4/dml-builtins.dml
+++ b/lib/1.4/dml-builtins.dml
@@ -545,6 +545,7 @@ template device {
     param _compat_port_obj_param auto;
     param _compat_io_memory auto;
     param _compat_shared_logs_on_device auto;
+    param _compat_discard_ref_shadowing auto;
     param _compat_dml12_inline auto;
     param _compat_dml12_not auto;
     param _compat_dml12_goto auto;
@@ -1848,7 +1849,7 @@ template bank is (object, shown_desc) {
     }
 
     shared method _num_registers() -> (uint32) {
-        local (const register *_, uint64 table_size) = _reginfo_table();
+        local (const register *_table, uint64 table_size) = _reginfo_table();
         return table_size;
     }
 
@@ -2808,7 +2809,7 @@ template register is (_conf_attribute, get, set, shown_desc,
     shared method _size() -> (int) { return this.bitsize / 8; }
     shared method _num_fields() -> (uint32) {
         local uint32 num_fields = 0;
-        foreach f in (fields) {
+        foreach _f in (fields) {
             num_fields++;
         }
         return num_fields;
@@ -2991,7 +2992,6 @@ template register is (_conf_attribute, get, set, shown_desc,
         local uint64 unmapped_bits = unmapped & ~field_bits;
         local uint64 val = (this.val & default_access_bits & enabled_bytes);
 
-        local int r_lsb = _enabled_bytes_to_offset(enabled_bytes) * 8;
         for (local int f = 0; f < num_fields; f++) {
             local int f_lsb = fields[f].lsb;
             local int f_msb = f_lsb + fields[f].bitsize - 1;
@@ -3080,8 +3080,6 @@ template register is (_conf_attribute, get, set, shown_desc,
             return;
         }
 
-        local int r_lsb = _enabled_bytes_to_offset(enabled_bytes) * 8;
-        local int r_msb = _enabled_bytes_to_size(enabled_bytes) * 8 - 1;
         for (local int f = 0; f < num_fields; f++) {
             local int f_lsb = fields[f].lsb;
             local int f_msb = f_lsb + fields[f].bitsize - 1;
diff --git a/py/dml/c_backend.py b/py/dml/c_backend.py
index 2cb4384e4..f01bf63b4 100644
--- a/py/dml/c_backend.py
+++ b/py/dml/c_backend.py
@@ -1837,7 +1837,7 @@ def generate_init_data_objs(device):
     start_function_definition(
         'void _init_data_objs(%s *_dev)' % (crep.structtype(device),))
     out('{\n', postindent = 1)
-    with crep.DeviceInstanceContext():
+    with crep.DeviceInstanceContext(), allow_linemarks():
         for node in device.initdata:
             # Usually, the initializer is constant, but we permit that it
             # depends on index. When the initializer is constant, we use a loop
@@ -1859,25 +1859,26 @@ def generate_init_data_objs(device):
             # mainly meant to capture EIDXVAR; for other errors, the error will
             # normally re-appear when evaluating per instance
             except DMLError:
-                with allow_linemarks():
-                    for indices in node.all_indices():
-                        index_exprs = tuple(mkIntegerLiteral(node.site, i)
-                                            for i in indices)
-                        nref = mkNodeRef(node.site, node, index_exprs)
-                        try:
-                            init = eval_initializer(
-                                node.site, node._type, node.astinit,
-                                Location(node.parent, index_exprs),
-                                global_scope, True)
-                        except DMLError as e:
-                            report(e)
-                        else:
-                            markers = ([('store_writes_const_field', 'FALSE')]
-                                       if deep_const(node._type) else [])
-                            coverity_markers(markers, init.site)
-                            init.assign_to(nref, node._type)
+                for indices in node.all_indices():
+                    index_exprs = tuple(mkIntegerLiteral(node.site, i)
+                                        for i in indices)
+                    nref = mkNodeRef(node.site, node, index_exprs)
+                    try:
+                        init = eval_initializer(
+                            node.site, node._type, node.astinit,
+                            Location(node.parent, index_exprs),
+                            global_scope, True)
+                    except DMLError as e:
+                        report(e)
+                    else:
+                        markers = ([('store_writes_const_field', 'FALSE')]
+                                   if deep_const(node._type) else [])
+                        coverity_markers(markers, init.site)
+                        out(init.assign_to(nref.read(), node._type) + ';\n')
             else:
                 index_exprs = ()
+                if node.dimensions:
+                    reset_line_directive()
                 for (i, sz) in enumerate(node.dimsizes):
                     var = 'i%d' % (i,)
                     out(('for (int %s = 0; %s < %s; ++%s) {\n'
@@ -1885,11 +1886,12 @@ def generate_init_data_objs(device):
                         postindent=1)
                     index_exprs += (mkLit(node.site, var, TInt(64, True)),)
                 nref = mkNodeRef(node.site, node, index_exprs)
-                with allow_linemarks():
-                    markers = ([('store_writes_const_field', 'FALSE')]
-                               if deep_const(node._type) else [])
-                    coverity_markers(markers, init.site)
-                    init.assign_to(nref, node._type)
+                markers = ([('store_writes_const_field', 'FALSE')]
+                           if deep_const(node._type) else [])
+                coverity_markers(markers, init.site)
+                out(init.assign_to(nref.read(), node._type) + ';\n')
+                if node.dimensions:
+                    reset_line_directive()
                 for _ in range(node.dimensions):
                     out('}\n', postindent=-1)
     out('}\n\n', preindent = -1)
@@ -3120,12 +3122,7 @@ def generate_startup_trait_calls(data, idxvars):
         ref = ObjTraitRef(site, node, trait, indices)
         out(f'_tref = {ref.read()};\n')
         for method in trait_methods:
-            outargs = [mkLit(method.site,
-                             ('*((%s) {0})'
-                              % ((TArray(t, mkIntegerLiteral(method.site, 1))
-                                  .declaration('')),)),
-                             t)
-                       for (_, t) in method.outp]
+            outargs = [mkDiscardRef(method.site) for _ in method.outp]
 
             method_ref = TraitMethodDirect(
                 method.site, mkLit(method.site, '_tref', TTrait(trait)), method)
@@ -3137,12 +3134,7 @@ def generate_startup_trait_calls(data, idxvars):
 def generate_startup_regular_call(method, idxvars):
     site = method.site
     indices = tuple(mkLit(site, idx, TInt(32, False)) for idx in idxvars)
-    outargs = [mkLit(site,
-                     ('*((%s) {0})'
-                      % ((TArray(t, mkIntegerLiteral(site, 1))
-                          .declaration('')),)),
-                     t)
-               for (_, t) in method.outp]
+    outargs = [mkDiscardRef(method.site) for _ in method.outp]
     # startup memoized methods can throw, which is ignored during startup.
     # Memoization of the throw then allows for the user to check whether
     # or not the method did throw during startup by calling the method
diff --git a/py/dml/codegen.py b/py/dml/codegen.py
index 644e9d064..b1c540df2 100644
--- a/py/dml/codegen.py
+++ b/py/dml/codegen.py
@@ -704,9 +704,9 @@ def error_out_at_index(_i, exc, msg):
             site, val_expr, targets, error_out_at_index,
             f'deserialization of arguments to {self.method.name}')
         if self.args_type:
-            ctree.mkAssignStatement(site, out_expr,
-                                    ctree.ExpressionInitializer(
-                                        tmp_out_ref)).toc()
+            ctree.AssignStatement(site, out_expr,
+                                  ctree.ExpressionInitializer(
+                                      tmp_out_ref)).toc()
 
     @property
     def args_type(self):
@@ -822,8 +822,8 @@ def error_out_at_index(_i, exc, msg):
                 'deserialization of arguments to a send_now')
 
 
-        ctree.mkAssignStatement(site, out_expr,
-                                ctree.ExpressionInitializer(tmp_out_ref)).toc()
+        ctree.AssignStatement(site, out_expr,
+                              ctree.ExpressionInitializer(tmp_out_ref)).toc()
 
     @property
     def args_type(self):
@@ -1148,7 +1148,7 @@ def expr_unop(tree, location, scope):
     elif op == 'post--':  return mkPostDec(tree.site, rh)
     elif op == 'sizeof':
         if (compat.dml12_misc not in dml.globals.enabled_compat
-            and not isinstance(rh, ctree.LValue)):
+            and not rh.addressable):
             raise ERVAL(rh.site, 'sizeof')
         return codegen_sizeof(tree.site, rh)
     elif op == 'defined': return mkBoolConstant(tree.site, True)
@@ -1207,6 +1207,13 @@ def expr_variable(tree, location, scope):
         if in_dev_tree:
             e = in_dev_tree
     if e is None:
+        # TODO/HACK: The discard ref is exposed like this to allow it to be as
+        # keyword-like as possible while still allowing it to be shadowed.
+        # Once we remove support for discard_ref_shadowing the discard ref
+        # should become a proper keyword and its codegen be done via dedicated
+        # dispatch
+        if name == '_' and tree.site.dml_version() != (1, 2):
+            return mkDiscardRef(tree.site)
         raise EIDENT(tree.site, name)
     return e
 
@@ -1538,7 +1545,7 @@ def eval_type(asttype, site, location, scope, extern=False, typename=None,
                     etype = expr.node_type
                 else:
                     raise expr.exc()
-            elif (not isinstance(expr, ctree.LValue)
+            elif (not expr.addressable
                   and compat.dml12_misc not in dml.globals.enabled_compat):
                 raise ERVAL(expr.site, 'typeof')
             else:
@@ -2131,8 +2138,8 @@ def make_static_var(site, location, static_sym_type, name, init=None,
         with init_code:
             if deep_const(static_sym_type):
                 coverity_marker('store_writes_const_field', 'FALSE')
-            init.assign_to(mkStaticVariable(site, static_sym),
-                           static_sym_type)
+            out(init.assign_to(mkStaticVariable(site, static_sym).read(),
+                               static_sym_type) + ';\n')
         c_init = init_code.buf
     else:
         c_init = None
@@ -2340,21 +2347,31 @@ def try_codegen_invocation(site, init_ast, outargs, location, scope):
     else:
         return common_inline(site, meth_node, indices, inargs, outargs)
 
+def codegen_src_for_nonvalue_target(site, tgt, src_ast, location, scope):
+    if not tgt.writable:
+        raise EASSIGN(site, tgt)
+    if src_ast.kind != 'initializer_scalar':
+        raise EDATAINIT(tgt.site,
+                        f'{tgt} can only be used as the target '
+                        + 'of an assignment if its initializer is a '
+                        + 'simple expression or a return value of a '
+                        + 'method call')
+    return codegen_expression(src_ast.args[0], location, scope)
+
 @statement_dispatcher
 def stmt_assign(stmt, location, scope):
     (_, site, tgt_ast, src_asts) = stmt
     assert tgt_ast.kind in ('assign_target_chain', 'assign_target_tuple')
-    tgts = [codegen_expression(ast, location, scope)
+    tgts = [codegen_expression_maybe_nonvalue(ast, location, scope)
             for ast in tgt_ast.args[0]]
     for tgt in tgts:
-        if deep_const(tgt.ctype()):
+        if not isinstance(tgt, NonValue) and deep_const(tgt.ctype()):
             raise ECONST(tgt.site)
     if tgt_ast.kind == 'assign_target_chain':
         method_tgts = [tgts[0]]
     else:
         method_tgts = tgts
 
-    # TODO support multiple assign sources. It should be generalized.
     method_invocation = try_codegen_invocation(site, src_asts, method_tgts,
                                                location, scope)
     if method_invocation:
@@ -2370,19 +2387,33 @@ def stmt_assign(stmt, location, scope):
                            + f'initializer: Expected {src_asts}, got 1'))
             return []
 
-        stmts = []
-        lscope = Symtab(scope)
+        if isinstance(tgts[-1], NonValue):
+            if len(tgts) != 1:
+                raise tgts[-1].exc()
+            expr = codegen_src_for_nonvalue_target(site, tgts[0], src_asts[0],
+                                                   location, scope)
+            return [mkCopyData(site, expr, tgts[0])]
+
+        init_typ = tgts[-1].ctype()
         init = eval_initializer(
-            site, tgts[-1].ctype(), src_asts[0], location, scope, False)
-
-        for (i, tgt) in enumerate(reversed(tgts[1:])):
-            name = 'tmp%d' % (i,)
-            sym = lscope.add_variable(
-                name, type=tgt.ctype(), site=tgt.site, init=init, stmt=True)
-            init = ExpressionInitializer(mkLocalVariable(tgt.site, sym))
-            stmts.extend([sym_declaration(sym),
-                          mkAssignStatement(tgt.site, tgt, init)])
-        return stmts + [mkAssignStatement(tgts[0].site, tgts[0], init)]
+            tgts[-1].site, init_typ, src_asts[0], location, scope, False)
+
+        if len(tgts) == 1:
+            return [mkAssignStatement(tgts[0].site, tgts[0], init)]
+
+        lscope = Symtab(scope)
+        sym = lscope.add_variable(
+            'tmp', type=init_typ, site=init.site, init=init,
+            stmt=True)
+        init_expr = mkLocalVariable(init.site, sym)
+        stmts = [sym_declaration(sym)]
+        for tgt in reversed(tgts[1:]):
+            stmts.append(mkCopyData(tgt.site, init_expr, tgt))
+            init_expr = (tgt if isinstance(tgt, NonValue)
+                         else source_for_assignment(tgt.site, tgt.ctype(),
+                                                    init_expr))
+        stmts.append(mkCopyData(tgts[0].site, init_expr, tgts[0]))
+        return [mkCompound(site, stmts)]
     else:
         # Guaranteed by grammar
         assert tgt_ast.kind == 'assign_target_tuple' and len(tgts) > 1
@@ -2399,53 +2430,66 @@ def stmt_assign(stmt, location, scope):
 
         stmts = []
         lscope = Symtab(scope)
-        syms = []
+        stmt_pairs = []
         for (i, (tgt, src_ast)) in enumerate(zip(tgts, src_asts)):
-            init = eval_initializer(site, tgt.ctype(), src_ast, location,
-                                    scope, False)
-            name = 'tmp%d' % (i,)
-            sym = lscope.add_variable(
-                    name, type=tgt.ctype(), site=tgt.site, init=init,
-                    stmt=True)
-            syms.append(sym)
-
-        stmts.extend(map(sym_declaration, syms))
-        stmts.extend(
-            mkAssignStatement(
-                tgt.site, tgt, ExpressionInitializer(mkLocalVariable(tgt.site,
-                                                                     sym)))
-            for (tgt, sym) in zip(tgts, syms))
-        return stmts
+            if isinstance(tgt, NonValue):
+                expr = codegen_src_for_nonvalue_target(site, tgt, src_ast,
+                                                       location, scope)
+                stmt_pairs.append((mkCopyData(tgt.site, expr, tgt), None))
+            else:
+                init = eval_initializer(site, tgt.ctype(), src_ast, location,
+                                        scope, False)
+                name = 'tmp%d' % (i,)
+                sym = lscope.add_variable(
+                        name, type=tgt.ctype(), site=tgt.site, init=init,
+                        stmt=True)
+                write = AssignStatement(
+                    tgt.site, tgt,
+                    ExpressionInitializer(mkLocalVariable(tgt.site, sym)))
+                stmt_pairs.append((sym_declaration(sym), write))
+
+        stmts.extend(first for (first, _) in stmt_pairs)
+        stmts.extend(second for (_, second) in stmt_pairs
+                     if second is not None)
+        return [mkCompound(site, stmts)]
 
 @statement_dispatcher
 def stmt_assignop(stmt, location, scope):
-    (kind, site, tgt_ast, op, src_ast) = stmt
+    (_, site, tgt_ast, op, src_ast) = stmt
 
     tgt = codegen_expression(tgt_ast, location, scope)
-    if deep_const(tgt.ctype()):
+    if isinstance(tgt, ctree.InlinedParam):
+        raise EASSINL(tgt.site, tgt.name)
+    if not tgt.writable:
+        raise EASSIGN(site, tgt)
+
+    ttype = tgt.ctype()
+    if deep_const(ttype):
         raise ECONST(tgt.site)
-    if isinstance(tgt, ctree.BitSlice):
-        # destructive hack
-        return stmt_assign(
-            ast.assign(site, ast.assign_target_chain(site, [tgt_ast]),
-                       [ast.initializer_scalar(
-                           site,
-                           ast.binop(site, tgt_ast, op[:-1], src_ast))]),
-            location, scope)
+
     src = codegen_expression(src_ast, location, scope)
-    ttype = tgt.ctype()
-    lscope = Symtab(scope)
-    sym = lscope.add_variable(
-        'tmp', type = TPtr(ttype), site = tgt.site,
-        init = ExpressionInitializer(mkAddressOf(tgt.site, tgt)), stmt=True)
-    # Side-Effect Free representation of the tgt lvalue
-    tgt_sef = mkDereference(site, mkLocalVariable(tgt.site, sym))
-    return [
-        sym_declaration(sym), mkExpressionStatement(
-        site,
-            mkAssignOp(site, tgt_sef, arith_binops[op[:-1]](
-                site, tgt_sef, src)))]
 
+    if tgt.addressable:
+        lscope = Symtab(scope)
+        tmp_tgt_sym = lscope.add_variable(
+            '_tmp_tgt', type = TPtr(ttype), site = tgt.site,
+            init = ExpressionInitializer(mkAddressOf(tgt.site, tgt)),
+            stmt=True)
+        # Side-Effect Free representation of the tgt lvalue
+        tgt = mkDereference(site, mkLocalVariable(tgt.site, tmp_tgt_sym))
+    else:
+        # TODO Not ideal. This path is needed to deal with writable
+        # expressions that do not correspond to C lvalues; such as bit slices.
+        # The incurred repeated evaluation is painful.
+        tmp_tgt_sym = None
+
+    assign_src = source_for_assignment(site, ttype,
+                                       arith_binops[op[:-1]](site, tgt, src))
+
+    return [mkCompound(site,
+        ([sym_declaration(tmp_tgt_sym)] if tmp_tgt_sym else [])
+        + [mkExpressionStatement(site,
+                                 ctree.AssignOp(site, tgt, assign_src))])]
 @statement_dispatcher
 def stmt_expression(stmt, location, scope):
     [expr] = stmt.args
@@ -3601,7 +3645,7 @@ def codegen_inline(site, meth_node, indices, inargs, outargs,
                                 parmtype if parmtype else arg.ctype(),
                                 meth_node.name)
                     for (arg, var, (parmname, parmtype)) in zip(
-                            outargs, outvars, meth_node.outp)] 
+                            outargs, outvars, meth_node.outp)]
             exit_handler = GotoExit_dml12()
             with exit_handler:
                 code = [codegen_statement(meth_node.astcode,
@@ -3885,7 +3929,7 @@ def prelude():
                         param = mkDereference(site,
                                               mkLit(site, name, TPtr(typ)))
                         fnscope.add(ExpressionSymbol(name, param, site))
-                        code.append(mkAssignStatement(site, param, init))
+                        code.append(AssignStatement(site, param, init))
                 else:
                     code = []
 
@@ -4025,15 +4069,20 @@ def copy_outarg(arg, var, parmname, parmtype, method_name):
     an exception. We would be able to skip the proxy variable for
     calls to non-throwing methods when arg.ctype() and parmtype are
     equivalent types, but we don't do this today.'''
-    argtype = arg.ctype()
-
-    if not argtype:
-        raise ICE(arg.site, "unknown expression type")
+    if isinstance(arg, NonValue):
+        if not arg.writable:
+            raise arg.exc()
     else:
-        ok, trunc, constviol = realtype(parmtype).canstore(realtype(argtype))
-        if not ok:
-            raise EARGT(arg.site, 'call', method_name,
-                         arg.ctype(), parmname, parmtype, 'output')
+        argtype = arg.ctype()
+
+        if not argtype:
+            raise ICE(arg.site, "unknown expression type")
+        else:
+            ok, trunc, constviol = realtype(parmtype).canstore(
+                realtype(argtype))
+            if not ok:
+                raise EARGT(arg.site, 'call', method_name,
+                             arg.ctype(), parmname, parmtype, 'output')
 
     return mkCopyData(var.site, var, arg)
 
diff --git a/py/dml/compat.py b/py/dml/compat.py
index 3df8385ef..35c904ef6 100644
--- a/py/dml/compat.py
+++ b/py/dml/compat.py
@@ -117,6 +117,13 @@ class shared_logs_on_device(CompatFeature):
     short = "Make logs inside shared methods always log on the device object"
     last_api_version = api_6
 
+@feature
+class discard_ref_shadowing(CompatFeature):
+    '''This compatibility feature allows declarations (within methods or
+    objects) to be named '_'. This will cause the discard reference `_` to be
+    inaccessible (*shadowed*) in all scopes with such a declaration.'''
+    short = "Allow declarations to shadow '_'"
+    last_api_version = api_6
 
 @feature
 class dml12_inline(CompatFeature):
diff --git a/py/dml/ctree.py b/py/dml/ctree.py
index 24541c295..d1716f079 100644
--- a/py/dml/ctree.py
+++ b/py/dml/ctree.py
@@ -66,7 +66,7 @@
     'mkVectorForeach',
     'mkBreak',
     'mkContinue',
-    'mkAssignStatement',
+    'mkAssignStatement', 'AssignStatement',
     'mkCopyData',
     'mkIfExpr', 'IfExpr',
     #'BinOp',
@@ -126,6 +126,7 @@
     'mkEachIn', 'EachIn',
     'mkBoolConstant',
     'mkUndefined', 'Undefined',
+    'mkDiscardRef',
     'TraitParameter',
     'TraitSessionRef',
     'TraitHookRef',
@@ -599,8 +600,11 @@ def mkExpressionStatement(site, expr):
 def toc_constsafe_pointer_assignment(site, source, target, typ):
     target_val = mkDereference(site,
         Cast(site, mkLit(site, target, TPtr(void)), TPtr(typ)))
-    mkAssignStatement(site, target_val,
-                      ExpressionInitializer(mkLit(site, source, typ))).toc()
+
+    init = ExpressionInitializer(
+        source_for_assignment(site, typ, mkLit(site, source, typ)))
+
+    return AssignStatement(site, target_val, init).toc()
 
 class After(Statement):
     @auto_init
@@ -1020,22 +1024,39 @@ class AssignStatement(Statement):
     @auto_init
     def __init__(self, site, target, initializer):
         assert isinstance(initializer, Initializer)
+
     def toc_stmt(self):
         self.linemark()
-        out('{\n', postindent=1)
-        self.toc_inline()
-        self.linemark()
-        out('}\n', preindent=-1)
-    def toc_inline(self):
-        self.linemark()
-        self.initializer.assign_to(self.target, self.target.ctype())
+        out(self.target.write(self.initializer) + ';\n')
+
+def mkAssignStatement(site, target, init):
+    if isinstance(target, InlinedParam):
+        raise EASSINL(target.site, target.name)
+    if not target.writable:
+        raise EASSIGN(site, target)
+
+    if isinstance(target, NonValue):
+        if not isinstance(init, ExpressionInitializer):
+            raise EDATAINIT(target.site,
+                            f'{target} can only be used as the target of an '
+                            + 'assignment if its initializer is a simple '
+                            + 'expression or a return value of a method call')
+    else:
+        target_type = target.ctype()
+
+        if deep_const(target_type):
+            raise ECONST(site)
+
+        if isinstance(init, ExpressionInitializer):
+            init = ExpressionInitializer(
+                source_for_assignment(site, target_type, init.expr))
+
+    return AssignStatement(site, target, init)
 
-mkAssignStatement = AssignStatement
 
 def mkCopyData(site, source, target):
     "Convert a copy statement to intermediate representation"
-    assignexpr = mkAssignOp(site, target, source)
-    return mkExpressionStatement(site, assignexpr)
+    return mkAssignStatement(site, target, ExpressionInitializer(source))
 
 #
 # Expressions
@@ -1094,21 +1115,12 @@ def truncate_int_bits(value, signed, bits=64):
         return value & mask
 
 class LValue(Expression):
-    "Somewhere to read or write data"
+    """An expression whose C representation is always an LValue, whose address
+    is always safe to take, in the sense that the duration that address
+    remains valid is intuitively predictable by the user"""
     writable = True
-
-    def write(self, source):
-        rt = realtype(self.ctype())
-        if isinstance(rt, TEndianInt):
-            return (f'{rt.dmllib_fun("copy")}(&{self.read()},'
-                    + f' {source.read()})')
-        return '%s = %s' % (self.read(), source.read())
-
-    @property
-    def is_stack_allocated(self):
-        '''Returns true only if it's known that writing to the lvalue will
-           write to stack-allocated data'''
-        return False
+    addressable = True
+    c_lval = True
 
 class IfExpr(Expression):
     priority = 30
@@ -2444,8 +2456,8 @@ class AssignOp(BinOp):
     def __str__(self):
         return "%s = %s" % (self.lh, self.rh)
 
-    def discard(self):
-        return self.lh.write(self.rh)
+    def discard(self, explicit=False):
+        return self.lh.write(ExpressionInitializer(self.rh))
 
     def read(self):
         return '((%s), (%s))' % (self.discard(), self.lh.read())
@@ -2524,13 +2536,13 @@ def make_simple(cls, site, rh):
                                TPtr(TVoid())],
                               TVoid())))
         if (compat.dml12_misc not in dml.globals.enabled_compat
-            and not isinstance(rh, LValue)):
+            and not rh.addressable):
             raise ERVAL(rh.site, '&')
         return AddressOf(site, rh)
 
     @property
     def is_pointer_to_stack_allocation(self):
-        return isinstance(self.rh, LValue) and self.rh.is_stack_allocated
+        return self.rh.is_stack_allocated
 
 def mkAddressOf(site, rh):
     if dml.globals.compat_dml12_int(site):
@@ -2568,7 +2580,8 @@ def is_stack_allocated(self):
 
     @property
     def is_pointer_to_stack_allocation(self):
-        return isinstance(self.type, TArray) and self.is_stack_allocated
+        return (isinstance(safe_realtype_shallow(self.type), TArray)
+                and self.is_stack_allocated)
 
 mkDereference = Dereference.make
 
@@ -2690,7 +2703,7 @@ def mkUnaryPlus(site, rh):
         rh, _ = promote_integer(rh, rhtype)
     else:
         raise ICE(site, "Unexpected arith argument to unary +")
-    if isinstance(rh, LValue):
+    if rh.addressable or rh.writable:
         # +x is a rvalue
         rh = mkRValue(rh)
     return rh
@@ -2716,7 +2729,7 @@ def make_simple(cls, site, rh):
         rhtype = safe_realtype(rh.ctype())
         if not isinstance(rhtype, (IntegerType, TPtr)):
             raise EINCTYPE(site, cls.op)
-        if not isinstance(rh, LValue):
+        if not rh.addressable:
             if isinstance(rh, BitSlice):
                 hint = 'try %s= 1' % (cls.base_op[0],)
             else:
@@ -2922,7 +2935,8 @@ def writable(self):
         return self.expr.writable
 
     def write(self, source):
-        source_expr = source
+        assert isinstance(source, ExpressionInitializer)
+        source_expr = source.expr
         # if not self.size.constant or source.ctype() > self.type:
         #     source = mkBitAnd(source, self.mask)
 
@@ -2944,7 +2958,7 @@ def write(self, source):
         target_type = realtype(self.expr.ctype())
         if target_type.is_int and target_type.is_endian:
             expr = mkCast(self.site, expr, target_type)
-        return self.expr.write(expr)
+        return self.expr.write(ExpressionInitializer(expr))
 
 def mkBitSlice(site, expr, msb, lsb, bitorder):
     # lsb == None means that only one bit number was given (expr[i]
@@ -3467,6 +3481,18 @@ def exc(self):
 
 mkUndefined = Undefined
 
+class DiscardRef(NonValue):
+    writable = True
+
+    def __str__(self):
+        return '_'
+
+    def write(self, source):
+        assert isinstance(source, ExpressionInitializer)
+        return source.expr.discard(explicit=True)
+
+mkDiscardRef = DiscardRef
+
 def endian_convert_expr(site, idx, endian, size):
     """Convert a bit index to little-endian (lsb=0) numbering.
 
@@ -4293,14 +4319,28 @@ def read(self):
 
 mkStaticVariable = StaticVariable
 
-class StructMember(LValue):
+class StructMember(Expression):
     priority = 160
     explicit_type = True
     @auto_init
     def __init__(self, site, expr, sub, type, op):
+        # Write of StructMembers rely on them being C lvalues
+        assert not expr.writable or expr.c_lval
         assert_type(site, expr, Expression)
         assert_type(site, sub, str)
 
+    @property
+    def writable(self):
+        return self.expr.writable
+
+    @property
+    def addressable(self):
+        return self.expr.addressable
+
+    @property
+    def c_lval(self):
+        return self.expr.c_lval
+
     def __str__(self):
         s = str(self.expr)
         if self.expr.priority < self.priority:
@@ -4314,11 +4354,12 @@ def read(self):
 
     @property
     def is_stack_allocated(self):
-        return isinstance(self.expr, LValue) and self.expr.is_stack_allocated
+        return self.expr.is_stack_allocated
 
     @property
     def is_pointer_to_stack_allocation(self):
-        return isinstance(self.type, TArray) and self.is_stack_allocated
+        return (isinstance(safe_realtype_shallow(self.type), TArray)
+                and self.is_stack_allocated)
 
 def mkSubRef(site, expr, sub, op):
     if isinstance(expr, NodeRef):
@@ -4425,18 +4466,28 @@ def is_stack_allocated(self):
 
     @property
     def is_pointer_to_stack_allocation(self):
-        return isinstance(self.type, TArray) and self.is_stack_allocated
+        return (isinstance(safe_realtype_shallow(self.type), TArray)
+                and self.is_stack_allocated)
 
-class VectorRef(LValue):
+class VectorRef(Expression):
     slots = ('type',)
     @auto_init
     def __init__(self, site, expr, idx):
+        assert not expr.writable or expr.c_lval
         self.type = realtype(self.expr.ctype()).base
     def read(self):
         return 'VGET(%s, %s)' % (self.expr.read(), self.idx.read())
-    def write(self, source):
-        return "VSET(%s, %s, %s)" % (self.expr.read(), self.idx.read(),
-                                     source.read())
+    # No need for write, VGET results in an lvalue
+
+    @property
+    def writable(self):
+        return self.expr.writable
+    @property
+    def addressable(self):
+        return self.expr.addressable
+    @property
+    def c_lval(self):
+        return self.expr.c_lval
 
 def mkIndex(site, expr, idx):
     if isinstance(idx, NonValue):
@@ -4510,7 +4561,7 @@ def read(self):
 
     @property
     def is_pointer_to_stack_allocation(self):
-        return (isinstance(self.type, TPtr)
+        return (isinstance(safe_realtype_shallow(self.type), TPtr)
                 and self.expr.is_pointer_to_stack_allocation)
 
 def mkCast(site, expr, new_type):
@@ -4653,7 +4704,6 @@ def mkCast(site, expr, new_type):
 class RValue(Expression):
     '''Wraps an lvalue to prohibit write. Useful when a composite
     expression is reduced down to a single variable.'''
-    writable = False
     @auto_init
     def __init__(self, site, expr): pass
     def __str__(self):
@@ -4662,10 +4712,19 @@ def ctype(self):
         return self.expr.ctype()
     def read(self):
         return self.expr.read()
-    def discard(self): pass
+    def discard(self, explicit=False):
+        return self.expr.discard(explicit)
+    # Since addressable and readable are False this may only ever be leveraged
+    # by DMLC for optimization purposes
+    @property
+    def c_lval(self):
+        return self.expr.c_lval
+    @property
+    def is_pointer_to_stack_allocation(self):
+        return self.expr.is_pointer_to_stack_allocation
 
 def mkRValue(expr):
-    if isinstance(expr, LValue) or expr.writable:
+    if expr.addressable or expr.writable:
         return RValue(expr.site, expr)
     return expr
 
@@ -4847,15 +4906,35 @@ def assign_to(self, dest, typ):
         # be UB as long as the session variable hasn't been initialized
         # previously.
         site = self.expr.site
-        if deep_const(typ):
-            out('memcpy((void *)&%s, (%s){%s}, sizeof %s);\n'
-                % (dest.read(),
-                   TArray(typ, mkIntegerLiteral(site, 1)).declaration(''),
-                   mkCast(site, self.expr, typ).read(),
-                   dest.read()))
+        rt = safe_realtype_shallow(typ)
+        # There is a reasonable implementation for this case (memcpy), but it
+        # never occurs today
+        assert not isinstance(typ, TArray)
+        if isinstance(rt, TEndianInt):
+            return (f'{rt.dmllib_fun("copy")}((void *)&{dest},'
+                    + f' {self.expr.read()})')
+        elif deep_const(typ):
+            shallow_deconst_typ = safe_realtype_unconst(typ)
+            # a const-qualified ExternStruct can be leveraged by the user as a
+            # sign that there is some const-qualified member unknown to DMLC
+            if (isinstance(typ, TExternStruct)
+                or deep_const(shallow_deconst_typ)):
+                # Expression statement to delimit lifetime of compound literal
+                # TODO it's possible to improve the efficiency of this by not
+                # using a compound literal if self.expr is c_lval. However,
+                # this requires require strict cmp to ensure safety, and it's
+                # unclear if that path could ever be taken.
+                return ('({ memcpy((void *)&%s, (%s){%s}, sizeof(%s)); })'
+                        % (dest,
+                           TArray(typ,
+                                  mkIntegerLiteral(site, 1)).declaration(''),
+                           mkCast(site, self.expr, typ).read(),
+                           dest))
+            else:
+                return (f'*({TPtr(shallow_deconst_typ).declaration("")})'
+                        + f'&{dest} = {self.expr.read()}')
         else:
-            with disallow_linemarks():
-                mkCopyData(site, self.expr, dest).toc()
+            return f'{dest} = {self.expr.read()}'
 
 class CompoundInitializer(Initializer):
     '''Initializer for a variable of struct or array type, using the
@@ -4883,21 +4962,12 @@ def assign_to(self, dest, typ):
         '''output C statements to assign an lvalue'''
         # (void *) cast to avoid GCC erroring if the target type is (partially)
         # const-qualified. See ExpressionInitializer.assign_to
-        if isinstance(typ, TNamed):
-            out('memcpy((void *)&%s, &(%s)%s, sizeof %s);\n' %
-                (dest.read(), typ.declaration(''), self.read(),
-                 dest.read()))
-        elif isinstance(typ, TArray):
-            out('memcpy((void *)%s, (%s)%s, sizeof %s);\n'
-                % (dest.read(), typ.declaration(''),
-                   self.read(), dest.read()))
-        elif isinstance(typ, TStruct):
-            out('memcpy((void *)&%s, (%s){%s}, sizeof %s);\n' % (
-                dest.read(),
-                TArray(typ, mkIntegerLiteral(self.site, 1)).declaration(''),
-                self.read(), dest.read()))
+        if isinstance(typ, (TNamed, TArray, TStruct)):
+            # Expression statement to delimit lifetime of compound literal
+            return ('({ memcpy((void *)&%s, &(%s)%s, sizeof(%s)); })'
+                    % (dest, typ.declaration(''), self.read(), dest))
         else:
-            raise ICE(self.site, 'strange type %s' % typ)
+            raise ICE(self.site, f'unexpected type for initializer: {typ}')
 
 class DesignatedStructInitializer(Initializer):
     '''Initializer for a variable of an extern-declared struct type, using
@@ -4937,10 +5007,11 @@ def assign_to(self, dest, typ):
         if isinstance(typ, StructType):
             # (void *) cast to avoid GCC erroring if the target type is
             # (partially) const-qualified. See ExpressionInitializer.assign_to
-            out('memcpy((void *)&%s, (%s){%s}, sizeof %s);\n' % (
-                dest.read(),
-                TArray(typ, mkIntegerLiteral(self.site, 1)).declaration(''),
-                self.read(), dest.read()))
+            return ('({ memcpy((void *)&%s, (%s){%s}, sizeof(%s)); })'
+                    % (dest,
+                       TArray(typ,
+                              mkIntegerLiteral(self.site, 1)).declaration(''),
+                       self.read(), dest))
         else:
             raise ICE(self.site, f'unexpected type for initializer: {typ}')
 
@@ -4979,8 +5050,7 @@ def assign_to(self, dest, typ):
                            THook))
         # (void *) cast to avoid GCC erroring if the target type is
         # (partially) const-qualified. See ExpressionInitializer.assign_to
-        out('memset((void *)&%s, 0, sizeof(%s));\n'
-            % (dest.read(), typ.declaration('')))
+        return f'memset((void *)&{dest}, 0, sizeof({typ.declaration("")}))'
 
 class CompoundLiteral(Expression):
     @auto_init
@@ -5039,8 +5109,7 @@ def toc(self):
             # zero-initialize VLAs
             self.type.print_declaration(self.name, unused = self.unused)
             site_linemark(self.init.site)
-            self.init.assign_to(mkLit(self.site, self.name, self.type),
-                                self.type)
+            out(self.init.assign_to(self.name, self.type) + ';\n')
         else:
             self.type.print_declaration(
                 self.name, init=self.init.read() if self.init else None,
@@ -5077,7 +5146,8 @@ def sym_declaration(sym, unused=False):
         return None
 
     # This will prevent warnings from the C compiler
-    unused = unused or (refcount == 0) or sym.value.startswith("__")
+    unused = (unused or (refcount == 0 and dml.globals.suppress_wunused)
+              or sym.name.startswith("_") or sym.value.startswith("__"))
 
     return mkDeclaration(sym.site, sym.value, sym.type,
                          sym.init, unused)
diff --git a/py/dml/dmlc.py b/py/dml/dmlc.py
index 1c5dad849..7e103eaac 100644
--- a/py/dml/dmlc.py
+++ b/py/dml/dmlc.py
@@ -455,9 +455,6 @@ def main(argv):
         default="0",
         help=('Limit the number of error messages to N'))
 
-    # 
-    # 
-
     # 
--no-compat=TAG
#
Disable a compatibility feature
parser.add_argument( @@ -468,6 +465,14 @@ def main(argv): '--help-no-compat', action=CompatHelpAction, help='List the available tags for --no-compat') + #
--no-suppress-wunused
+ #
Disable the automatic suppression of GCC warnings for unused local + # variables
+ parser.add_argument( + '--no-suppress-wunused', action='store_true', + help=("Disable the automatic suppression of GCC warnings for unused " + + "local variables")) + # # @@ -576,6 +581,7 @@ def main(argv): sys.exit(1) dml.globals.coverity = options.coverity + dml.globals.suppress_wunused = not options.no_suppress_wunused dml.globals.linemarks_enabled = not options.noline diff --git a/py/dml/dmlparse.py b/py/dml/dmlparse.py index 1d5bba12b..f8730ba81 100644 --- a/py/dml/dmlparse.py +++ b/py/dml/dmlparse.py @@ -8,7 +8,7 @@ from .logging import * from .messages import * -from . import ast, logging +from . import ast, logging, compat import dml.globals from . import dmllex12 from . import dmllex14 @@ -176,6 +176,7 @@ def prod(f): @prod def device(t): 'dml : DEVICE objident SEMI maybe_bitorder device_statements' + ident_enforce_not_discard_ref(site(t, 2), t[2]) t[0] = ast.dml(site(t), t[2], t[5]) # Entry point for imported files @@ -290,6 +291,7 @@ def array_list(t): @prod def object_regarray(t): 'object : REGISTER objident array_list sizespec offsetspec maybe_istemplate object_spec' + ident_enforce_not_discard_ref(site(t, 2), t[2]) t[0] = ast.object_(site(t), t[2], 'register', t[3], t[4] + t[5] + t[6] + t[7]) @@ -306,6 +308,7 @@ def bitrangespec_empty(t): @prod_dml14 def object_field(t): 'object : FIELD objident array_list bitrangespec maybe_istemplate object_spec' + ident_enforce_not_discard_ref(site(t, 2), t[2]) t[0] = ast.object_(site(t), t[2], 'field', t[3], t[4] + t[5] + t[6]) def endian_translate_bit(expr, width, bitorder): @@ -454,11 +457,13 @@ def object3(t): | GROUP objident array_list maybe_istemplate object_spec | PORT objident array_list maybe_istemplate object_spec | IMPLEMENT objident array_list maybe_istemplate object_spec''' + ident_enforce_not_discard_ref(site(t, 2), t[2]) t[0] = ast.object_(site(t), t[2], t[1], t[3], t[4] + t[5]) @prod_dml14 def object_subdevice(t): '''object : SUBDEVICE objident array_list maybe_istemplate object_spec''' + ident_enforce_not_discard_ref(site(t, 2), t[2]) t[0] = ast.object_(site(t), t[2], t[1], t[3], t[4] + t[5]) @prod_dml12 @@ -582,6 +587,7 @@ def method_qualifiers_check(site, qualifiers, inp, outp, throws, default): def object_method(t): '''method : method_qualifiers METHOD objident method_params_typed maybe_default compound_statement''' name = t[3] + ident_enforce_not_discard_ref(site(t, 3), name) (inp, outp, throws) = t[4] body = t[6] (inp, outp) = method_qualifiers_check(site(t), t[1], inp, outp, throws, @@ -594,6 +600,7 @@ def object_method(t): def object_inline_method(t): '''method : INLINE METHOD objident method_params_maybe_untyped maybe_default compound_statement''' name = t[3] + ident_enforce_not_discard_ref(site(t, 3), name) (inp, outp, throws) = t[4] if all(typ for (_, asite, name, typ) in inp): # inline annotation would have no effect for fully typed methods. @@ -642,11 +649,13 @@ def arraydef2(t): @prod_dml14 def arraydef(t): '''arraydef : ident LT expression''' + ident_enforce_not_discard_ref(site(t, 1), t[1]) t[0] = (t[1], t[3]) @prod_dml14 def arraydef_implicit(t): '''arraydef : ident LT ELLIPSIS''' + ident_enforce_not_discard_ref(site(t, 1), t[1]) t[0] = (t[1], None) # Traits @@ -745,15 +754,18 @@ def trait_method(t): @prod def shared_method_abstract(t): '''shared_method : ident method_params_typed SEMI''' + ident_enforce_not_discard_ref(site(t, 1), t[1]) t[0] = (t[1], t[2], True, None, site(t, 3)) @prod def shared_method_default(t): '''shared_method : ident method_params_typed DEFAULT compound_statement''' + ident_enforce_not_discard_ref(site(t, 1), t[1]) t[0] = (t[1], t[2], True, t[4], lex_end_site(t, -1)) @prod def shared_method_final(t): '''shared_method : ident method_params_typed compound_statement''' + ident_enforce_not_discard_ref(site(t, 1), t[1]) t[0] = (t[1], t[2], False, t[3], lex_end_site(t, -1)) @prod_dml12 @@ -784,6 +796,7 @@ def template(t): @prod_dml14 def template(t): 'toplevel : TEMPLATE objident maybe_istemplate LBRACE template_stmts RBRACE' + ident_enforce_not_discard_ref(site(t, 2), t[2]) ises = [s for s in t[5] if s.kind == 'is'] shared_methods = [s for s in t[5] if s.kind == 'sharedmethod'] if ises and shared_methods: @@ -812,6 +825,7 @@ def impl_header(t): @prod def loggroup(t): 'toplevel : LOGGROUP ident SEMI' + ident_enforce_not_discard_ref(site(t, 2), t[2]) t[0] = ast.loggroup(site(t), t[2]) # constant/extern @@ -819,6 +833,7 @@ def loggroup(t): @prod def constant(t): 'toplevel : CONSTANT ident EQUALS expression SEMI' + ident_enforce_not_discard_ref(site(t, 2), t[2]) t[0] = ast.constant(site(t), t[2], t[4]) if logging.show_porting: report(PCONSTANT(site(t))) @@ -1019,6 +1034,7 @@ def object_else_if(t): @prod def object_parameter(t): '''parameter : param_ objident paramspec''' + ident_enforce_not_discard_ref(site(t, 2), t[2]) if logging.show_porting: if t[2] == 'hard_reset_value': report(PHARD_RESET_VALUE(site(t, 2))) @@ -1031,6 +1047,7 @@ def object_parameter(t): @prod_dml14 def object_typedparam(t): '''parameter : param_ objident COLON ctypedecl SEMI''' + ident_enforce_not_discard_ref(site(t, 2), t[2]) t[0] = ast.typedparam(site(t), site(t, 2), t[2], t[4]) @prod @@ -1207,6 +1224,7 @@ def cdecl_or_ident_decl(t): @prod_dml14 def cdecl_or_ident_inline(t): '''cdecl_or_ident : INLINE ident''' + ident_enforce_not_discard_ref(site(t, 2), t[2]) t[0] = ast.cdecl(site(t), t[2], None) # A C-like declaration with required identifier name @@ -1309,6 +1327,7 @@ def cdecl3(t): @prod_dml14 def cdecl3(t): 'cdecl3 : ident' + ident_enforce_not_discard_ref(site(t, 1), t[1]) t[0] = [t[1]] @prod @@ -2211,6 +2230,7 @@ def ident_list_nonempty(t): @prod_dml14 def ident_list_one(t): 'nonempty_ident_list : ident' + ident_enforce_not_discard_ref(site(t, 1), t[1]) t[0] = [(site(t, 1), t[1])] @prod_dml14 @@ -2226,6 +2246,7 @@ def statement_delay_hook(t): @prod_dml14 def statement_delay_hook_one_msg_param(t): 'statement_except_hashif : AFTER expression ARROW ident COLON expression SEMI %prec bind' + ident_enforce_not_discard_ref(site(t, 4), t[4]) t[0] = ast.afteronhook(site(t), t[2], [(site(t, 4), t[4])], t[6]) @@ -2350,11 +2371,13 @@ def hashselect(t): @prod def select(t): 'statement_except_hashif : hashselect ident IN LPAREN expression RPAREN WHERE LPAREN expression RPAREN statement hashelse statement' + ident_enforce_not_discard_ref(site(t, 2), t[2]) t[0] = ast.select(site(t), t[2], t[5], t[9], t[11], t[13]) @prod_dml12 def foreach(t): 'statement_except_hashif : FOREACH ident IN LPAREN expression RPAREN statement' + ident_enforce_not_discard_ref(site(t, 2), t[2]) t[0] = ast.foreach_dml12(site(t), t[2], t[5], t[7]) if logging.show_porting: report(PHASH(site(t))) @@ -2362,11 +2385,13 @@ def foreach(t): @prod_dml14 def foreach(t): 'statement_except_hashif : FOREACH ident IN LPAREN expression RPAREN statement' + ident_enforce_not_discard_ref(site(t, 2), t[2]) t[0] = ast.foreach(site(t), t[2], t[5], t[7]) @prod_dml14 def hashforeach(t): 'statement_except_hashif : HASHFOREACH ident IN LPAREN expression RPAREN statement' + ident_enforce_not_discard_ref(site(t, 2), t[2]) t[0] = ast.hashforeach(site(t), t[2], t[5], t[7]) @prod_dml12 @@ -2397,6 +2422,7 @@ def case_statement_default(t): @prod_dml14 def goto_statement(t): 'statement_except_hashif : GOTO ident SEMI' + ident_enforce_not_discard_ref(site(t, 2), t[2]) # Restricted goto should be implemented, see SIMICS-6130 report(ESYNTAX(site(t), 'goto', 'goto statements are not yet implemented in DML 1.4')) @@ -2573,6 +2599,7 @@ def simple_array_list(t): @prod_dml14 def hook_decl(t): '''hook_decl : HOOK LPAREN cdecl_list RPAREN ident simple_array_list SEMI''' + ident_enforce_not_discard_ref(site(t, 5), t[5]) cdecl_list_enforce_unnamed(t[3]) if t[6]: # Hook arrays are an internal feature, as their design depends on if we @@ -2632,6 +2659,12 @@ def ident(t): def ident(t): t[0] = t[1] +def ident_enforce_not_discard_ref(site, ident): + if (str(ident) == '_' + and site.dml_version() != (1, 2) + and compat.discard_ref_shadowing not in dml.globals.enabled_compat): + report(ESYNTAX(site, '_', "reserved identifier")) + reserved_words_12 = [ 'CLASS', 'ENUM', 'NAMESPACE', 'PRIVATE', 'PROTECTED', 'PUBLIC', 'RESTRICT', 'UNION', 'USING', 'VIRTUAL', 'VOLATILE'] diff --git a/py/dml/expr.py b/py/dml/expr.py index ebb0c5217..c467eeb29 100644 --- a/py/dml/expr.py +++ b/py/dml/expr.py @@ -109,11 +109,19 @@ class Expression(Code): # bitslicing. explicit_type = False - # Can the expression be assigned to? - # If writable is True, there is a method write() which returns a C - # expression to make the assignment. + # Can the expression be safely assigned to in DML? + # This implies write() can be safely used. writable = False + # Can the address of the expression be taken safely in DML? + # This implies c_lval, and typically implies writable. + addressable = False + + # Is the C representation of the expression an lvalue? + # If True, then the default implementation of write() must not be + # overridden. + c_lval = False + def __init__(self, site): assert not site or isinstance(site, Site) self.site = site @@ -128,8 +136,16 @@ def read(self): raise ICE(self.site, "can't read %r" % self) # Produce a C expression but don't worry about the value. - def discard(self): - return self.read() + def discard(self, explicit=False): + if not explicit or safe_realtype_shallow(self.ctype()).void: + return self.read() + + if self.constant: + return '(void)0' + from .ctree import Cast + expr = (f'({self.read()})' + if self.priority < Cast.priority else self.read()) + return f'(void){expr}' def ctype(self): '''The corresponding DML type of this expression''' @@ -139,10 +155,16 @@ def apply(self, inits, location, scope): 'Apply this expression as a function' return mkApplyInits(self.site, self, inits, location, scope) + @property + def is_stack_allocated(self): + '''Returns true only if it's known that the storage for the value that + this expression evaluates to is temporary to a method scope''' + return False + @property def is_pointer_to_stack_allocation(self): '''Returns True only if it's known that the expression is a pointer - to stack-allocated data''' + to storage that is temporary to a method scope''' return False def incref(self): @@ -156,6 +178,15 @@ def copy(self, site): return type(self)( site, *(getattr(self, name) for name in self.init_args[2:])) + # Return a (principally) void-typed C expression that write a source to the + # storage this expression represents + # This should only be called if either writable or c_lval is True + def write(self, source): + assert self.c_lval, repr(self) + # Wrap .read() in parantheses if its priority is less than that of & + dest = self.read() if self.priority >= 150 else f'({self.read()})' + return source.assign_to(dest, self.ctype()) + class NonValue(Expression): '''An expression that is not really a value, but which may validly appear as a subexpression of certain expressions. @@ -202,11 +233,14 @@ def __str__(self): return self.str or self.cexpr def read(self): return self.cexpr - def write(self, source): - assert self.writable - return "%s = %s" % (self.cexpr, source.read()) @property def writable(self): + return self.c_lval + @property + def addressable(self): + return self.c_lval + @property + def c_lval(self): return self.type is not None mkLit = Lit diff --git a/py/dml/globals.py b/py/dml/globals.py index 1f06bf2ca..b716d3389 100644 --- a/py/dml/globals.py +++ b/py/dml/globals.py @@ -106,3 +106,6 @@ def compat_dml12_int(site): # Relevant during C emission: indicates if linemarks should currently be generated linemarks = False + +# Insert UNUSED for user variable declarations when they are indeed unused +suppress_wunused = False diff --git a/py/dml/io_memory.py b/py/dml/io_memory.py index 391c75c6e..81d682d91 100644 --- a/py/dml/io_memory.py +++ b/py/dml/io_memory.py @@ -221,7 +221,8 @@ def dim_sort_key(data): regvar, size.read())]) lines.append( ' %s;' % ( - size2.write(mkLit(site, 'bytes', TInt(64, False))))) + size2.write(ExpressionInitializer(mkLit(site, 'bytes', + TInt(64, False)))))) if partial: if bigendian: lines.extend([ @@ -246,7 +247,8 @@ def dim_sort_key(data): regvar, indices, memop.read(), bytepos_args), ' if (ret) return true;', ' %s;' % ( - value2.write(mkLit(site, 'val', TInt(64, False)))), + value2.write(ExpressionInitializer( + mkLit(site, 'val', TInt(64, False))))), ' return false;']) else: # Shifting/masking can normally be skipped in banks with @@ -272,7 +274,8 @@ def dim_sort_key(data): ' if (offset >= %s[last].offset' % (regvar,) + ' && offset < %s[last].offset + %s[last].size) {' % (regvar, regvar), - ' %s;' % (size2.write(mkIntegerLiteral(site, 0)),), + ' %s;' % (size2.write(ExpressionInitializer( + mkIntegerLiteral(site, 0))),), ' return false;', ' }']) lines.extend([ diff --git a/py/dml/serialize.py b/py/dml/serialize.py index 17de64f02..6a29ff0fc 100644 --- a/py/dml/serialize.py +++ b/py/dml/serialize.py @@ -116,8 +116,8 @@ def serialize(real_type, current_expr, target_expr): def construct_assign_apply(funname, intype): apply_expr = apply_c_fun(current_site, funname, [current_expr], attr_value_t) - return ctree.mkAssignStatement(current_site, target_expr, - ctree.ExpressionInitializer(apply_expr)) + return ctree.AssignStatement(current_site, target_expr, + ctree.ExpressionInitializer(apply_expr)) if real_type.is_int: if real_type.signed: funname = "SIM_make_attr_int64" @@ -133,7 +133,7 @@ def construct_assign_apply(funname, intype): [converted_arg], function_type) return ctree.mkCompound(current_site, - [ctree.mkAssignStatement( + [ctree.AssignStatement( current_site, target_expr, ctree.ExpressionInitializer( apply_expr))]) @@ -164,15 +164,15 @@ def construct_assign_apply(funname, intype): len(dimsizes)), elem_serializer], attr_value_t) - return ctree.mkAssignStatement(current_site, target_expr, - ctree.ExpressionInitializer(apply_expr)) + return ctree.AssignStatement(current_site, target_expr, + ctree.ExpressionInitializer(apply_expr)) elif isinstance(real_type, (TStruct, TVector)): apply_expr = apply_c_fun( current_site, lookup_serialize(real_type), [ctree.mkAddressOf(current_site, current_expr)], attr_value_t) - return ctree.mkAssignStatement(current_site, target_expr, - ctree.ExpressionInitializer(apply_expr)) + return ctree.AssignStatement(current_site, target_expr, + ctree.ExpressionInitializer(apply_expr)) elif isinstance(real_type, TTrait): id_infos = expr.mkLit(current_site, '_id_infos', TPtr(TNamed('_id_info_t', const = True))) @@ -180,8 +180,8 @@ def construct_assign_apply(funname, intype): TNamed("_identity_t"), ".") apply_expr = apply_c_fun(current_site, "_serialize_identity", [id_infos, identity_expr], attr_value_t) - return ctree.mkAssignStatement(current_site, target_expr, - ctree.ExpressionInitializer(apply_expr)) + return ctree.AssignStatement(current_site, target_expr, + ctree.ExpressionInitializer(apply_expr)) elif isinstance(real_type, THook): id_infos = expr.mkLit(current_site, '_hook_id_infos' if dml.globals.hooks @@ -189,8 +189,8 @@ def construct_assign_apply(funname, intype): TPtr(TNamed('_id_info_t', const = True))) apply_expr = apply_c_fun(current_site, "_serialize_identity", [id_infos, current_expr], attr_value_t) - return ctree.mkAssignStatement(current_site, target_expr, - ctree.ExpressionInitializer(apply_expr)) + return ctree.AssignStatement(current_site, target_expr, + ctree.ExpressionInitializer(apply_expr)) else: # Callers are responsible for checking that the type is serializeable, # which should be done with the mark_for_serialization function @@ -202,11 +202,12 @@ def construct_assign_apply(funname, intype): # with a given set_error_t and message. def deserialize(real_type, current_expr, target_expr, error_out): current_site = current_expr.site - def construct_assign_apply(attr_typ, intype): + def construct_assign_apply(attr_typ, intype, mod_apply_expr=lambda x: x): check_expr = apply_c_fun(current_site, 'SIM_attr_is_' + attr_typ, [current_expr], TBool()) - apply_expr = apply_c_fun(current_site, 'SIM_attr_' + attr_typ, - [current_expr], intype) + apply_expr = mod_apply_expr(apply_c_fun(current_site, + 'SIM_attr_' + attr_typ, + [current_expr], intype)) error_stmts = error_out('Sim_Set_Illegal_Type', 'expected ' + attr_typ) target = target_expr @@ -223,7 +224,7 @@ def construct_assign_apply(attr_typ, intype): return ctree.mkIf(current_site, check_expr, - ctree.mkAssignStatement( + ctree.AssignStatement( current_site, target, ctree.ExpressionInitializer(apply_expr)), ctree.mkCompound(current_site, error_stmts)) @@ -237,7 +238,7 @@ def addressof_target_unconst(): def construct_subcall(apply_expr): (sub_success_decl, sub_success_arg) = \ declare_variable(current_site, "_sub_success", set_error_t) - assign_stmt = ctree.mkAssignStatement( + assign_stmt = ctree.AssignStatement( current_site, sub_success_arg, ctree.ExpressionInitializer(apply_expr)) check_expr = ctree.mkLit(current_site, @@ -253,8 +254,13 @@ def construct_subcall(apply_expr): if real_type.is_int: if real_type.is_endian: - real_type = TInt(real_type.bits, real_type.signed) - return construct_assign_apply("integer", real_type) + def mod_apply_expr(expr): + return ctree.source_for_assignment(expr.site, real_type, expr) + else: + def mod_apply_expr(expr): + return expr + return construct_assign_apply("integer", TInt(64, True), + mod_apply_expr) elif isinstance(real_type, TBool): return construct_assign_apply("boolean", real_type) elif isinstance(real_type, TFloat): @@ -442,7 +448,7 @@ def serialize_sources_to_list(site, sources, out_attr): site, "SIM_alloc_attr_list", [ctree.mkIntegerConstant(site, size, False)], attr_value_t) - attr_assign_statement = ctree.mkAssignStatement( + attr_assign_statement = ctree.AssignStatement( site, out_attr, ctree.ExpressionInitializer(attr_alloc_expr)) imm_attr_decl, imm_attr_ref = declare_variable( site, "_imm_attr", attr_value_t) @@ -457,7 +463,7 @@ def serialize_sources_to_list(site, sources, out_attr): if typ is not None: sub_serialize = serialize(typ, source, imm_attr_ref) else: - sub_serialize = ctree.mkAssignStatement( + sub_serialize = ctree.AssignStatement( site, imm_attr_ref, ctree.ExpressionInitializer(source)) sim_attr_list_set_statement = call_c_fun( site, "SIM_attr_list_set_item", [ctree.mkAddressOf(site, out_attr), @@ -517,7 +523,7 @@ def deserialize_list_to_targets(site, val_attr, targets, error_out_at_index, index = ctree.mkIntegerConstant(site, i, False) sim_attr_list_item = apply_c_fun(site, "SIM_attr_list_item", [val_attr, index], attr_value_t) - imm_set = ctree.mkAssignStatement( + imm_set = ctree.AssignStatement( site, imm_attr_ref, ctree.ExpressionInitializer(sim_attr_list_item)) statements.append(imm_set) @@ -535,7 +541,7 @@ def sub_error_out(exc, msg): sub_deserialize = deserialize(typ, imm_attr_ref, target, sub_error_out) else: - sub_deserialize = ctree.mkAssignStatement( + sub_deserialize = ctree.AssignStatement( site, target, ctree.ExpressionInitializer(imm_attr_ref)) statements.append(sub_deserialize) else: @@ -620,9 +626,9 @@ def error_out_at_index(_i, exc, msg): deserialize_list_to_targets(site, in_arg, targets, error_out_at_index, f'deserialization of {real_type}') - ctree.mkAssignStatement(site, - ctree.mkDereference(site, out_arg), - ctree.ExpressionInitializer( + ctree.AssignStatement(site, + ctree.mkDereference(site, out_arg), + ctree.ExpressionInitializer( ctree.mkDereference( site, tmp_out_ref))).toc() diff --git a/py/dml/types.py b/py/dml/types.py index a329b2e58..a62abf1a7 100644 --- a/py/dml/types.py +++ b/py/dml/types.py @@ -12,6 +12,7 @@ 'realtype', 'safe_realtype_shallow', 'safe_realtype', + 'safe_realtype_unconst', 'conv_const', 'deep_const', 'type_union', @@ -155,6 +156,20 @@ def safe_realtype_shallow(t): except DMLUnknownType as e: raise ETYPE(e.type.declaration_site or None, e.type) +def safe_realtype_unconst(t0): + def sub(t): + if isinstance(t, (TArray, TVector)): + base = sub(t.base) + if t.const or base is not t.base: + t = t.clone() + t.const = False + t.base = base + elif t.const: + t = t.clone() + t.const = False + return t + return sub(safe_realtype(t0)) + def conv_const(const, t): if const and not t.const: t = t.clone() diff --git a/test/1.4/expressions/T_discard_ref.dml b/test/1.4/expressions/T_discard_ref.dml new file mode 100644 index 000000000..b74c9c630 --- /dev/null +++ b/test/1.4/expressions/T_discard_ref.dml @@ -0,0 +1,48 @@ +/* + © 2023 Intel Corporation + SPDX-License-Identifier: MPL-2.0 +*/ +dml 1.4; +device test; + +/// DMLC-FLAG --no-suppress-wunused + +header %{ + #define FUNCLIKE_MACRO() 4 + #define VARLIKE_MACRO ++counter + + static int counter = 0; +%} + +extern int FUNCLIKE_MACRO(void); +extern int VARLIKE_MACRO; +extern int counter; + +method t() -> (int) throws { + return 1; +} +method m2() -> (int, int) { + return (1, 2); +} + +method init() { + local int unused; + // Explicit discard guarantees GCC doesn't emit -Wunused by always + // void-casting, unless the expression is already void + _ = unused; + _ = FUNCLIKE_MACRO(); + // Explicit discard does generate C, which evaluates the initializer + assert counter == 0; + _ = VARLIKE_MACRO; + assert counter == 1; + try + _ = t(); + catch assert false; + local (int x, int y); + (x, _) = m2(); + assert x == 1; + // Tuple initializers retain the property of each expression being + // evaluated left-to-right + (_, y) = (x++, x); + assert y == 2; +} diff --git a/test/1.4/legacy/T_discard_ref_shadowing_disabled.dml b/test/1.4/legacy/T_discard_ref_shadowing_disabled.dml new file mode 100644 index 000000000..e6d989521 --- /dev/null +++ b/test/1.4/legacy/T_discard_ref_shadowing_disabled.dml @@ -0,0 +1,27 @@ +/* + © 2023 Intel Corporation + SPDX-License-Identifier: MPL-2.0 +*/ +dml 1.4; + +device test; + +/// DMLC-FLAG --no-compat=discard_ref_shadowing + +/// ERROR ESYNTAX +constant _ = 1; + +group g is init { + /// ERROR ESYNTAX + param _ = 2; + method init() { + assert _ == 2; + } +} + +method init() { + assert _ == 1; + /// ERROR ESYNTAX + local int _ = 2; + assert _ == 2; +} diff --git a/test/1.4/legacy/T_discard_ref_shadowing_enabled.dml b/test/1.4/legacy/T_discard_ref_shadowing_enabled.dml new file mode 100644 index 000000000..476707882 --- /dev/null +++ b/test/1.4/legacy/T_discard_ref_shadowing_enabled.dml @@ -0,0 +1,24 @@ +/* + © 2023 Intel Corporation + SPDX-License-Identifier: MPL-2.0 +*/ +dml 1.4; + +device test; + +/// DMLC-FLAG --simics-api=6 + +constant _ = 1; + +group g is init { + param _ = 2; + method init() { + assert _ == 2; + } +} + +method init() { + assert _ == 1; + local int _ = 2; + assert _ == 2; +}