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.
+
+ _ = 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/grammar_to_md.py b/grammar_to_md.py
index ba5770548..56a4cfe20 100644
--- a/grammar_to_md.py
+++ b/grammar_to_md.py
@@ -78,7 +78,8 @@
'BCONST' : 'binary-literal',
'FCONST' : 'float-literal',
'ELLIPSIS' : '"..."',
- '' : '<empty>'
+ '' : '<empty>',
+ '_' : '_'
}
for k in (reserved_idents
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..884098f53 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;
}
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..930d390b1 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,
diff --git a/py/dml/dmllex.py b/py/dml/dmllex.py
index 779269c1c..75336b7b3 100644
--- a/py/dml/dmllex.py
+++ b/py/dml/dmllex.py
@@ -21,7 +21,7 @@
'AFTER', 'ASSERT', 'BITFIELDS', 'CALL', 'CAST', 'DEFINED', 'ERROR',
'FOREACH', 'IN', 'IS', 'LAYOUT', 'LOCAL', 'LOG', 'SELECT',
'SIZEOFTYPE', 'TYPEOF', 'UNDEFINED', 'VECT', '_WARNING', 'WHERE',
- 'EACH', 'SESSION', 'SEQUENCE',
+ 'EACH', 'SESSION', 'SEQUENCE', '_',
# ANSI C reserved words
'AUTO', 'BREAK', 'CASE', 'CHAR', 'CONST', 'CONTINUE', 'DEFAULT',
diff --git a/py/dml/dmlparse.py b/py/dml/dmlparse.py
index 1d5bba12b..f2e9a9e3b 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
@@ -1754,7 +1754,7 @@ def expression_ident(t):
@prod_dml14
def expression_ident(t):
- '''expression : objident
+ '''expression : objident_or_underscore
| DEFAULT'''
t[0] = ast.variable(site(t), t[1])
@@ -2616,22 +2616,34 @@ def objident(t):
| REGISTER'''
t[0] = t[1]
-def ident_rule(idents):
- return 'ident : ' + "\n| ".join(idents)
+@prod_dml14
+def objident_or_underscore(t):
+ '''objident_or_underscore : ident_or_underscore
+ | REGISTER'''
+ t[0] = t[1]
+
+def ident_rule(name, idents):
+ return f'{name} : ' + "\n| ".join(idents)
# Most DML top-level keywords are also allowed as identifiers.
@prod_dml12
-@lex.TOKEN(ident_rule(dmllex12.reserved_idents + (
- 'ID', 'EACH', 'SESSION', 'SEQUENCE')))
+@lex.TOKEN(ident_rule('ident', dmllex12.reserved_idents + (
+ 'ID', 'EACH', 'SESSION', 'SEQUENCE', '_')))
def ident(t):
t[0] = t[1]
@prod_dml14
-@lex.TOKEN(ident_rule(dmllex14.reserved_idents + ('ID',)))
+@lex.TOKEN(ident_rule('ident', dmllex14.reserved_idents + ('ID',)))
def ident(t):
t[0] = t[1]
+@prod_dml14
+@lex.TOKEN(ident_rule('ident_or_underscore',
+ dmllex14.reserved_idents + ('ID', '_')))
+def ident_or_underscore(t):
+ t[0] = t[1]
+
reserved_words_12 = [
'CLASS', 'ENUM', 'NAMESPACE', 'PRIVATE', 'PROTECTED', 'PUBLIC',
'RESTRICT', 'UNION', 'USING', 'VIRTUAL', 'VOLATILE']
@@ -2641,15 +2653,27 @@ def ident(t):
'ASYNC', 'AWAIT', 'WITH']
@prod_dml12
-@lex.TOKEN(ident_rule(reserved_words_12))
+@lex.TOKEN(ident_rule('ident', reserved_words_12))
def reserved(t):
raise ESYNTAX(site(t, 1), str(t[1]), "reserved word")
@prod_dml14
-@lex.TOKEN(ident_rule(reserved_words_14))
+@lex.TOKEN(ident_rule('ident', reserved_words_14))
def reserved(t):
raise ESYNTAX(site(t, 1), str(t[1]), "reserved word")
+@prod_dml14
+@lex.TOKEN(ident_rule('ident_or_underscore', reserved_words_14))
+def reserved_(t):
+ raise ESYNTAX(site(t, 1), str(t[1]), "reserved word")
+
+@prod_dml14
+def ident_underscore(t):
+ '''ident : _'''
+ if compat.discard_ref_shadowing not in dml.globals.enabled_compat:
+ report(ESYNTAX(site(t), '_', "reserved identifier"))
+ t[0] = t[1]
+
# Error handling
@prod
def error(t):
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/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..3bc2c6e1c
--- /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-compat=discard_ref_shadowing
+
+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 x;
+ // Explicit discard guarantees GCC doesn't emit -Wunused by always
+ // void-casting, unless the expression is already void
+ _ = x;
+ _ = FUNCLIKE_MACRO();
+ // Explicit discard does generate C, which evaluates the initializer
+ assert counter == 0;
+ _ = VARLIKE_MACRO;
+ assert counter == 1;
+ try
+ _ = t();
+ catch assert false;
+ (x, _) = m2();
+ assert x == 1;
+ local int y;
+ // 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;
+}