diff --git a/RELEASENOTES-1.4.md b/RELEASENOTES-1.4.md index 1ddd86160..68b42ed39 100644 --- a/RELEASENOTES-1.4.md +++ b/RELEASENOTES-1.4.md @@ -356,3 +356,40 @@ The bugfix is opt-in, because an immediate bugfix would risk breaking existing builds; the error will only be reported when the flag `--no-compat=broken_unused_types` is passed to DMLC. This flag will be automatically enabled in Simics 8. - `release 7 7063` - `release 6 6362` +- `note 6` 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 (fixes + SIMICS-21584.) + + Example usage: + ``` + _ = any_expression; + _ = throwing_method(); + (_, x, _) = method_with_multiple_return_values(); + ``` +- `note 6` '`_`' can no longer be used as the name for arbitrary declarations. + Instead, it is now only permitted in particular contexts, and in these + contexts, '`_`' will affect the declaration in a way suitable for when the + declaration is _unused_ in some particular way. These contexts are: + - Method-local bindings (e.g. variables and input parameters.) + + When a method-local binding is given the name '`_`', it will not be added + to scope. This is useful for e.g. unused method parameters. + - Index variables for object arrays + + When '`_`' is specified as an index variable, a parameter will not be + created for it, meaning it cannot conflict with any other definition, and + it cannot be referenced in the code in order to get the value of the index + in question. It also isn't considered to conflict with any other + definition that gives the index variable a different name. This is useful + when defining an object array specification which does not depend on the + index. + - Layout member names + + When a layout member is given the name '`_`', that member will not be + referencable within DML code, but will still affect the memory + representation of the layout. This is useful to represent e.g. reserved + or padding bytes. + + Note that as a consequence of these semantics, any reference to '`_`' in code + will _always_ resolve to the discard reference. diff --git a/doc/1.4/language.md b/doc/1.4/language.md index 9868a802d..cf7612993 100644 --- a/doc/1.4/language.md +++ b/doc/1.4/language.md @@ -78,9 +78,19 @@ Identifiers Identifiers in DML are defined as in C; an identifier may begin with a letter or underscore, followed by any number of letters, -numbers, or underscores. Identifiers that begin with an underscore (`_`) -are reserved by the DML language and standard library and should not -be used. +numbers, or underscores. + + +Identifiers that begin with an underscore (`_`) are reserved by the DML language +and standard library and should not be used, with the exception of the single +underscore `_`; this is considered to be the *discard identifier*, and is only +permitted as the name of a declaration in specific contexts, where it gives the +declaration special semantics. Currently, these contexts are: +* Method-local bindings, e.g. [local variables](#local-statements) — +see that section for more information. +* Index parameters for object arrays. See the documentation for the + [`object` template](dml-builtins.html#object) for more information. +* As the name of one or more members of a [layout type](#layouts).
session type identifier [= initializer];
@@ -4063,6 +4110,32 @@ 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.
+
+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();
+```
+
+The discard reference is related to the [discard
+identifier](#discard-identifier), and have some use-cases in common.
+
### New Expressions
diff --git a/lib/1.4/dml-builtins.dml b/lib/1.4/dml-builtins.dml
index f10fc051c..ab6351aa0 100644
--- a/lib/1.4/dml-builtins.dml
+++ b/lib/1.4/dml-builtins.dml
@@ -508,12 +508,25 @@ which cannot be overridden:
single field inside a register array, the value is the empty list.
* Each array has an *individual index parameter*, to make it possible
- to refer to both inner and outer indexes when arrays are nested
+ to refer to both inner and outer indices when arrays are nested
(cf. the `indices` parameter, above). The parameter name is
specified in the array declaration; for instance, the declaration
`register regs[i < 4][j < 11];` defines two index parameters, `i` and `j`.
In this case, the `indices` parameter is `[i, j]`.
+ "\_" is also allowed as the name of one or more index parameters. If
+ used, then it *won't* result in corresponding parameter definitions for those
+ indices; in other words, if an index variable is named "\_", then it can't be
+ accessed by that name (however, the `indices` parameter is unaffected and
+ could be used instead.) It's also allowed to have an object array
+ specification name an index parameter "\_" even if some other object array
+ specification uses a different name (which *will* be given a corresponding
+ parameter definition.) This is useful if a specific object array
+ specification doesn't need to make use of some particular indices; or if the
+ specification needs to support that the naming of the index variable may vary
+ depending on what other specifications of the same object array are present
+ in the device model.
+
The `object` template provides the non-overridable method `cancel_after()`,
which cancels all pending events posted using `after` which are associated with
the object (any events associated with subobjects are unaffected).
diff --git a/py/dml/ast.py b/py/dml/ast.py
index 1ef928ad3..9ef99e179 100644
--- a/py/dml/ast.py
+++ b/py/dml/ast.py
@@ -48,6 +48,7 @@ def __setstate__(self, data):
'case_dml12',
'cast',
'cdecl',
+ 'cdecl_maybe_discarded',
'compound',
'conditional',
'constant',
@@ -55,6 +56,7 @@ def __setstate__(self, data):
'default',
'default_dml12',
'delete',
+ 'discard',
'dml',
'dml_typedef',
'dowhile',
diff --git a/py/dml/c_backend.py b/py/dml/c_backend.py
index 5159a984b..363344361 100644
--- a/py/dml/c_backend.py
+++ b/py/dml/c_backend.py
@@ -249,8 +249,8 @@ def generate_structfile(device, filename, outprefix):
"%s(%s)" % (name,
", ".join((["conf_object_t *_obj"]
* (not func.independent))
- + [t.declaration(n)
- for (n, t) in (
+ + [decl
+ for (_, _, decl) in (
func.cparams
if func.independent
else func.cparams[1:])]))))
@@ -718,7 +718,7 @@ def wrap_method(meth, wrapper_name, indices=()):
is a port object
"""
- inparams = [t.declaration(p) for p, t in meth.inp]
+ inparams = [p.declaration() for p in meth.inp]
if not meth.outp:
retvar = None
rettype = TVoid()
@@ -749,7 +749,7 @@ def wrap_method(meth, wrapper_name, indices=()):
None, None, None)).toc()
with LogFailure(meth.site, meth, indices):
- inargs = [mkLit(meth.site, v, t) for v, t in meth.inp]
+ inargs = [mkLit(meth.site, p.c_ident, p.typ) for p in meth.inp]
outargs = [mkLit(meth.site, v, t) for v, t in meth.outp]
codegen_call(meth.site, meth,
indices,
@@ -804,10 +804,10 @@ def generate_implement_method(device, ifacestruct, meth, indices):
# currently impossible to implement a varargs interface
# method in DML
raise EMETH(meth.site, None, 'interface method is variadic')
- for ((mp, mt), it) in zip(meth.inp, iface_input_types):
- if not safe_realtype_unconst(mt).eq(safe_realtype_unconst(it)):
+ for (mp, it) in zip(meth.inp, iface_input_types):
+ if not safe_realtype_unconst(mp.typ).eq(safe_realtype_unconst(it)):
raise EARGT(meth.site, 'implement', meth.name,
- mt, mp, it, 'method')
+ mp.typ, mp.logref, it, 'method')
if iface_num_outputs and dml.globals.dml_version != (1, 2):
[(_, mt)] = meth.outp
if not safe_realtype_unconst(mt).eq(
@@ -1355,7 +1355,7 @@ def generate_events(device):
def generate_reg_callback(meth, name):
dev_t = crep.structtype(dml.globals.device)
out('static bool\n')
- params = [t.declaration(p) for p, t in meth.inp] + [
+ params = [p.declaration() for p in meth.inp] + [
TPtr(t).declaration(p) for p, t in meth.outp]
out('%s(void *_obj, const uint16 *indices, ' % (name,)
+ ', '.join(params) + ')\n')
@@ -1363,7 +1363,7 @@ def generate_reg_callback(meth, name):
out('%s *_dev = _obj;\n' % dev_t)
fail = ReturnFailure(meth.site)
with fail, crep.DeviceInstanceContext():
- inargs = [mkLit(meth.site, n, t) for n, t in meth.inp]
+ inargs = [mkLit(meth.site, p.c_ident, p.typ) for p in meth.inp]
outargs = [mkLit(meth.site, "*" + n, t) for n, t in meth.outp]
code = [codegen_call(
meth.site, meth,
@@ -1913,7 +1913,7 @@ def generate_init_data_objs(device):
markers = ([('store_writes_const_field', 'FALSE')]
if deep_const(node._type) else [])
coverity_markers(markers, init.site)
- init.assign_to(nref, node._type)
+ out(init.assign_to(nref.read(), node._type) + ';\n')
else:
index_exprs = ()
for (i, sz) in enumerate(node.dimsizes):
@@ -1927,7 +1927,7 @@ def generate_init_data_objs(device):
markers = ([('store_writes_const_field', 'FALSE')]
if deep_const(node._type) else [])
coverity_markers(markers, init.site)
- init.assign_to(nref, node._type)
+ out(init.assign_to(nref.read(), node._type) + ';\n')
for _ in range(node.dimensions):
out('}\n', postindent=-1)
out('}\n\n', preindent = -1)
@@ -2027,48 +2027,50 @@ def generate_init(device, initcode, outprefix):
def generate_static_trampoline(func):
# static trampolines never need to be generated for independent methods
assert not func.independent
- params = [("_obj", TPtr(TNamed("conf_object_t")))] + func.cparams[1:]
- params_string = ('void' if not params
- else ", ".join(t.declaration(n) for (n, t) in params))
+
+ params_string = ''.join(", " + decl for (_, _, decl) in func.cparams[1:])
start_function_definition(func.rettype.declaration(
- "%s(%s)" % ("_trampoline" + func.get_cname(), params_string)))
+ "%s(conf_object_t *_obj%s)" % ("_trampoline" + func.get_cname(),
+ params_string)))
out("{\n", postindent=1)
out('ASSERT(_obj);\n')
out('ASSERT(SIM_object_class(_obj) == _dev_class);\n')
- (name, typ) = func.cparams[0]
- out("%s = (%s)_obj;\n" % (typ.declaration(name), typ.declaration("")))
+ (name, typ, decl) = func.cparams[0]
+ out("%s = (%s)_obj;\n" % (decl, typ.declaration("")))
out("%s%s(%s);\n" % ("" if func.rettype.void
else func.rettype.declaration("result") + " = ",
func.get_cname(),
- ", ".join(n for (n, t) in func.cparams)))
+ ", ".join(n for (n, _, _) in func.cparams)))
output_dml_state_change(name)
if not func.rettype.void:
out("return result;\n")
out("}\n", preindent=-1)
def generate_extern_trampoline(exported_name, func):
- params = (func.cparams if func.independent else
- [("_obj", TPtr(TNamed("conf_object_t")))] + func.cparams[1:])
- params_string = ('void' if not params
- else ", ".join(t.declaration(n) for (n, t) in params))
+ cparams = list(func.cparams)
+ if not func.independent:
+ cparams[0] = (
+ '_obj', TPtr(TNamed("conf_object_t")),
+ TPtr(TNamed("conf_object_t")).declaration('_obj'))
+ params_string = ('void' if not cparams else ", ".join(
+ decl for (_, _, decl) in cparams))
out("extern %s\n" % (func.rettype.declaration(
"%s(%s)" % (exported_name, params_string))))
out("{\n", postindent=1)
out("%s%s(%s);\n" % ("return " * (not func.rettype.void),
"_trampoline" * (not func.independent)
+ func.get_cname(),
- ", ".join(n for (n, t) in params)))
+ ", ".join(n for (n, _, _) in cparams)))
out("}\n", preindent=-1)
def generate_extern_trampoline_dml12(exported_name, func):
out("static UNUSED %s\n" % (func.rettype.declaration(
- "%s(%s)" % (exported_name,
- ", ".join(t.declaration(n)
- for (n, t) in func.cparams)))))
+ "%s(%s)" % (exported_name,", ".join(
+ decl for (_, _, decl) in func.cparams)))))
out("{\n", postindent=1)
out("%s%s(%s);\n" % ("" if func.rettype.void else "return ",
func.get_cname(),
- ", ".join(n for (n, t) in func.cparams)))
+ ", ".join(n for (n, _, _) in func.cparams)))
out("}\n", preindent=-1)
def generate_each_in_table(trait, instances):
@@ -2202,7 +2204,7 @@ def generate_adjustor_thunk(traitname, name, inp, outp, throws, independent,
assert vtable_trait is def_path[-1]
implicit_inargs = vtable_trait.implicit_args()
preargs = crep.maybe_dev_arg(independent) + implicit_inargs
- inargs = c_inargs(inp, outp, throws)
+ inargs = [(p.c_ident, p.typ) for p in inp] + c_extra_inargs(outp, throws)
out('(%s)\n{\n' % (", ".join(t.declaration(n) for (n, t) in (preargs
+ inargs))),
postindent=1)
@@ -2745,11 +2747,12 @@ def resolve_trait_param_values(node):
def generate_trait_trampoline(method, vtable_trait):
implicit_inargs = vtable_trait.implicit_args()
- explicit_inargs = c_inargs(list(method.inp), method.outp, method.throws)
- inparams = ", ".join(
- t.declaration(n)
- for (n, t) in (crep.maybe_dev_arg(method.independent) + implicit_inargs
- + explicit_inargs))
+ extra_inargs = c_extra_inargs(method.outp, method.throws)
+ inparams = ", ".join([t.declaration(n)
+ for (n, t) in (crep.maybe_dev_arg(method.independent)
+ + implicit_inargs)]
+ + [p.declaration() for p in method.inp]
+ + [t.declaration(n) for (n, t) in extra_inargs])
rettype = c_rettype(method.outp, method.throws)
# guaranteed to exist; created by ObjTraits.mark_referenced
@@ -2770,7 +2773,8 @@ def generate_trait_trampoline(method, vtable_trait):
reduce(operator.mul, obj.dimsizes[dim + 1:], 1),
obj.dimsizes[dim]), TInt(32, False))
for dim in range(obj.dimensions)]
- args = [mkLit(site, n, t) for (n, t) in explicit_inargs]
+ args = ([mkLit(site, p.c_ident, p.typ) for p in method.inp]
+ + [mkLit(site, n, t) for (n, t) in extra_inargs])
call_expr = mkcall_method(site, func, indices)(args)
if not rettype.void:
out('return ')
@@ -3158,12 +3162,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)
@@ -3175,12 +3174,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
@@ -3384,9 +3378,10 @@ def generate_cfile_body(device, footers, full_module, filename_prefix):
generated_funcs.add(func)
code = codegen_method_func(func)
- specializations = [(n, 'undefined' if undefined(v) else v.value)
- for (n, v) in func.inp
- if isinstance(v, Expression)]
+ specializations = [(p.ident,
+ 'undefined' if undefined(p.expr) else p.expr.value)
+ for p in func.inp
+ if p.ident is not None and p.expr is not None]
if gather_size_statistics:
ctx = StrOutput(lineno=output.current().lineno,
diff --git a/py/dml/codegen.py b/py/dml/codegen.py
index f5b327940..092ae270d 100644
--- a/py/dml/codegen.py
+++ b/py/dml/codegen.py
@@ -51,7 +51,7 @@
'IgnoreFailure',
'c_rettype',
- 'c_inargs',
+ 'c_extra_inargs',
'method_instance',
'require_fully_typed',
'codegen_method_func',
@@ -210,7 +210,7 @@ def fail(self, site):
class IgnoreFailure(Failure):
'''Ignore exceptions'''
def fail(self, site):
- return mkNull(site)
+ return mkNoop(site)
class ExitHandler(ABC):
current = None
@@ -595,7 +595,7 @@ class AfterDelayIntoMethodInfo(AfterDelayInfo):
def __init__(self, method, uniq):
self.method = method
super().__init__(method, method.dimsizes, uniq)
- self._args_type = (TStruct(dict(method.inp),
+ self._args_type = (TStruct({p.c_ident: p.typ for p in method.inp},
label=f'_simple_event_{self.uniq}_args')
if method.inp else None)
@@ -615,8 +615,8 @@ def generate_callback_call(self, indices_lit, args_lit):
site = self.method.site
indices = tuple(mkLit(site, f'{indices_lit}[{i}]', TInt(32, False))
for i in range(self.method.dimensions))
- args = tuple(mkLit(site, f'{args_lit}->{pname}', ptype)
- for (pname, ptype) in self.method.inp)
+ args = tuple(mkLit(site, f'{args_lit}->{p.c_ident}', p.typ)
+ for p in self.method.inp)
with LogFailure(site, self.method, indices), \
crep.DeviceInstanceContext():
code = codegen_call(site, self.method, indices, args, ())
@@ -671,8 +671,8 @@ def __init__(self, typeseq_info, method, param_to_msg_comp):
super().__init__(method.dimsizes, method.parent, typeseq_info, method,
param_to_msg_comp, method.inp, bool(self.method.inp))
self._args_type = (
- TStruct({name: typ
- for (i, (name, typ)) in enumerate(method.inp)
+ TStruct({p.c_ident: p.typ
+ for (i, p) in enumerate(method.inp)
if i not in param_to_msg_comp},
label=f'_after_on_hook_{self.uniq}_args')
if len(self.method.inp) > len(param_to_msg_comp) else None)
@@ -684,9 +684,10 @@ def generate_callback_call(self, indices_lit, args_lit, msg_lit):
args = tuple(
mkLit(site,
f'{msg_lit}->comp{self.param_to_msg_comp[i]}'
- if i in self.param_to_msg_comp else f'{args_lit}->{pname}',
- ptype)
- for (i, (pname, ptype)) in enumerate(self.method.inp))
+ if i in self.param_to_msg_comp
+ else f'{args_lit}->{p.c_ident}',
+ p.typ)
+ for (i, p) in enumerate(self.method.inp))
with LogFailure(site, self.method, indices), \
crep.DeviceInstanceContext():
code = codegen_call(site, self.method, indices, args, ())
@@ -694,10 +695,10 @@ def generate_callback_call(self, indices_lit, args_lit, msg_lit):
code.toc()
def generate_args_serializer(self, site, args_expr, out_expr):
- sources = tuple((ctree.mkSubRef(site, args_expr, name, "."),
- safe_realtype(typ))
+ sources = tuple((ctree.mkSubRef(site, args_expr, p.c_ident, "."),
+ safe_realtype(p.typ))
if i not in self.param_to_msg_comp else None
- for (i, (name, typ)) in enumerate(self.method.inp))
+ for (i, p) in enumerate(self.method.inp))
serialize.serialize_sources_to_list(site, sources, out_expr)
def generate_args_deserializer(self, site, val_expr, out_expr, error_out):
@@ -707,10 +708,10 @@ def generate_args_deserializer(self, site, val_expr, out_expr, error_out):
tmp_out_decl.toc()
else:
tmp_out_ref = None
- targets = tuple((ctree.mkSubRef(site, tmp_out_ref, name, "."),
- safe_realtype(typ))
+ targets = tuple((ctree.mkSubRef(site, tmp_out_ref, p.c_ident, "."),
+ safe_realtype(p.typ))
if i not in self.param_to_msg_comp else None
- for (i, (name, typ)) in enumerate(self.method.inp))
+ for (i, p) in enumerate(self.method.inp))
def error_out_at_index(_i, exc, msg):
return error_out(exc, msg)
@@ -719,9 +720,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):
@@ -837,8 +838,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):
@@ -856,7 +857,7 @@ class ImmediateAfterIntoMethodInfo(ImmediateAfterInfo):
def __init__(self, method, uniq):
self.method = method
super().__init__(method, method.dimsizes, uniq)
- self._args_type = (TStruct(dict(method.inp),
+ self._args_type = (TStruct({p.c_ident: p.typ for p in method.inp},
label=f'_immediate_after_{self.uniq}_args')
if method.inp else None)
@@ -872,8 +873,8 @@ def generate_callback_call(self, indices_lit, args_lit):
site = self.method.site
indices = tuple(mkLit(site, f'{indices_lit}[{i}]', TInt(32, False))
for i in range(self.method.dimensions))
- args = tuple(mkLit(site, f'{args_lit}->{pname}', ptype)
- for (pname, ptype) in self.method.inp)
+ args = tuple(mkLit(site, f'{args_lit}->{p.c_ident}', p.typ)
+ for p in self.method.inp)
with LogFailure(site, self.method, indices), \
crep.DeviceInstanceContext():
code = codegen_call(site, self.method, indices, args, ())
@@ -967,7 +968,7 @@ def declarations(scope):
if sym.stmt:
continue
decl = sym_declaration(sym)
- if decl:
+ if not decl.is_empty:
decls.append(decl)
return decls
@@ -1163,7 +1164,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)
@@ -1225,6 +1226,10 @@ def expr_variable(tree, location, scope):
raise EIDENT(tree.site, name)
return e
+@expression_dispatcher
+def expr_discard(tree, location, scope):
+ return mkDiscardRef(tree.site)
+
@expression_dispatcher
def expr_objectref(tree, location, scope):
[name] = tree.args
@@ -1482,17 +1487,20 @@ def eval_type(asttype, site, location, scope, extern=False, typename=None,
raise ELAYOUT(site, "extern layout not permitted,"
+ " use 'struct { }' instead")
endian, fields = info
- members = {}
- for (_, msite, name, type_ast) in fields:
+ member_decls = []
+ for (_, msite, ident, type_ast) in fields:
(member_struct_defs, member_type) = eval_type(
type_ast, msite, location, scope, False)
if isinstance(member_type, TFunction):
raise EFUNSTRUCT(msite)
- members[name] = (msite, member_type)
+ member_decls.append((
+ msite,
+ ident.args[0] if ident.kind == 'variable' else None,
+ member_type))
struct_defs.extend(member_struct_defs)
- if not members:
+ if not member_decls:
raise EEMPTYSTRUCT(site)
- etype = TLayout(endian, members, label=typename)
+ etype = TLayout(endian, member_decls, label=typename)
struct_defs.append((site, etype))
elif tag == 'bitfields':
width, fields = info
@@ -1525,7 +1533,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:
@@ -1585,12 +1593,7 @@ def eval_type(asttype, site, location, scope, extern=False, typename=None,
raise EANONSTRUCT(site, "function return type")
arg_struct_defs = []
- inarg_asts = asttype[1]
- if inarg_asts and inarg_asts[-1] == '...':
- varargs = True
- inarg_asts = inarg_asts[:-1]
- else:
- varargs = False
+ (inarg_asts, varargs) = asttype[1]
inargs = []
for (_, tsite, name, type_ast) in inarg_asts:
(arg_struct_defs, argt) = eval_type(
@@ -1632,17 +1635,80 @@ def eval_type(asttype, site, location, scope, extern=False, typename=None,
return (struct_defs, etype)
+class MethodInParam:
+ def __init__(self, site, ident, typ, expr=None):
+ assert ident is not None
+ self.site = site
+ self.ident = ident
+ self.typ = typ
+ self.expr = expr
+
+ @property
+ def inlined(self):
+ return self.typ is None or self.expr is not None
+
+ @property
+ def c_ident(self):
+ return self.ident
+
+ @property
+ def logref(self):
+ return f"'{self.ident}'"
+
+ def declaration(self):
+ assert self.typ is not None
+ return self.typ.declaration(self.c_ident)
+
+ def with_expr(self, expr):
+ assert (self.typ is None
+ or compat.dml12_inline in dml.globals.enabled_compat)
+ return MethodInParam(self.site, self.ident, self.typ, expr)
+
+ def with_type(self, typ):
+ return MethodInParam(self.site, self.ident, typ)
+
+class DiscardedInParam(MethodInParam):
+ ident = None
+ def __init__(self, site, idx, typ, expr=None):
+ self.site = site
+ self.idx = idx
+ self.typ = typ
+ self.expr = expr
+ assert typ is None or expr is None
+
+ @property
+ def c_ident(self):
+ return f'_anon_param_{self.idx}'
+
+ @property
+ def logref(self):
+ return f'{self.idx + 1} (anonymous)'
+
+ def declaration(self):
+ return super().declaration() + ' UNUSED'
+
+ def with_expr(self, expr):
+ assert self.typ is None
+ return DiscardedInParam(self.site, self.idx, None, expr)
+
+ def with_type(self, typ):
+ return DiscardedInParam(self.site, self.ident, typ)
+
def eval_method_inp(inp_asts, location, scope):
'''evaluate the inarg ASTs of a method declaration'''
inp = []
- for (_, tsite, argname, type_ast) in inp_asts:
+ for (idx, (_, tsite, ident, type_ast)) in enumerate(inp_asts):
if type_ast:
(struct_defs, t) = eval_type(type_ast, tsite, location, scope)
for (site, _) in struct_defs:
report(EANONSTRUCT(site, "method argument"))
else:
t = None
- inp.append((argname, t))
+ if ident.kind != 'discard':
+ p = MethodInParam(ident.site, ident.args[0], t)
+ else:
+ p = DiscardedInParam(ident.site, idx, t)
+ inp.append(p)
return inp
def eval_method_outp(outp_asts, location, scope):
@@ -1674,9 +1740,9 @@ def check_designated_initializers(site, etype, init_asts, allow_partial):
shallow_real_etype = safe_realtype_shallow(etype)
duplicates = set()
bad_fields = set()
- remaining = set(shallow_real_etype.members)
+ remaining = set(shallow_real_etype.named_members)
for (field, init) in init_asts:
- if field not in shallow_real_etype.members:
+ if field not in shallow_real_etype.named_members:
bad_fields.add(field)
elif field not in remaining:
duplicates.add(field)
@@ -1837,14 +1903,14 @@ def do_eval(etype, astinit):
init = tuple(do_eval(etype.base, e) for e in init_asts)
return CompoundInitializer(site, init)
elif isinstance(etype, TStruct):
- if len(etype.members) != len(init_asts):
+ members = list(etype.members_qualified)
+ if len(members) != len(init_asts):
raise EDATAINIT(site, 'mismatched number of fields')
init = tuple(do_eval(mt, e)
- for ((_, mt), e) in zip(etype.members_qualified,
- init_asts))
+ for ((mn, mt), e) in zip(members, init_asts))
return CompoundInitializer(site, init)
elif isinstance(etype, TExternStruct):
- if len(etype.members) != len(init_asts):
+ if len(etype.named_members) != len(init_asts):
raise EDATAINIT(site, 'mismatched number of fields')
init = {mn: do_eval(mt, e)
for ((mn, mt), e) in zip(etype.members_qualified,
@@ -1916,9 +1982,10 @@ def codegen_statements(trees, *args):
return stmts
def codegen_statement(tree, *args):
- rbrace_site = tree.args[1] if tree.kind == 'compound' else None
- return mkCompound(tree.site, codegen_statements([tree], *args),
- rbrace_site)
+ stmts = codegen_statements([tree], *args)
+ if len(stmts) == 1 and not stmts[0].is_declaration:
+ return stmts[0]
+ return mkCompound(tree.site, stmts)
@statement_dispatcher
def stmt_compound(stmt, location, scope):
@@ -1977,8 +2044,10 @@ def stmt_local(stmt, location, scope):
stmts = []
def convert_decl(decl_ast):
- (name, asttype) = decl_ast.args
- if (dml.globals.dml_version == (1, 2)
+ (ident_ast, asttype) = decl_ast.args
+ name = ident_ast.args[0] if ident_ast.kind == 'variable' else None
+ if (name is not None
+ and dml.globals.dml_version == (1, 2)
and compat.dml12_misc not in dml.globals.enabled_compat):
check_varname(stmt.site, name)
(struct_decls, etype) = eval_type(asttype, stmt.site, location, scope)
@@ -1987,8 +2056,9 @@ def convert_decl(decl_ast):
rt = safe_realtype_shallow(etype)
if isinstance(rt, TArray) and not rt.size.constant and deep_const(rt):
raise EVLACONST(stmt.site)
- check_shadowing(scope, name, stmt.site)
- return (name, etype)
+ if name is not None:
+ check_shadowing(scope, name, stmt.site)
+ return (ident_ast.site, name, etype)
decls = list(map(convert_decl, decls))
@@ -2000,52 +2070,75 @@ def mk_sym(name, typ, mkunique=not dml.globals.debuggable):
syms_to_add = []
tgt_syms = []
late_declared_syms = []
+ tgts = []
+
+ for (ident_site, name, typ) in decls:
+ if name is None:
+ tgts.append(mkDiscardRef(ident_site,
+ safe_realtype_unconst(typ)))
+ continue
- for (name, typ) in decls:
sym = mk_sym(name, typ)
tgt_typ = safe_realtype_shallow(typ)
if shallow_const(tgt_typ):
nonconst_typ = safe_realtype_unconst(tgt_typ)
tgt_sym = mk_sym('_tmp_' + name, nonconst_typ, True)
- sym.init = ExpressionInitializer(mkLocalVariable(stmt.site,
+ sym.init = ExpressionInitializer(mkLocalVariable(ident_site,
tgt_sym))
late_declared_syms.append(sym)
else:
tgt_sym = sym
syms_to_add.append(sym)
tgt_syms.append(tgt_sym)
+ tgts.append(mkLocalVariable(ident_site, tgt_sym))
- tgts = [mkLocalVariable(stmt.site, sym) for sym in tgt_syms]
- method_invocation = try_codegen_invocation(stmt.site,
- inits,
- tgts, location, scope)
+ method_invocation = try_codegen_invocation(stmt.site, inits, tgts,
+ location, scope)
if method_invocation is not None and stmt.site.dml_version != (1, 2):
for sym in syms_to_add:
scope.add(sym)
stmts.extend(sym_declaration(sym) for sym in tgt_syms)
stmts.append(method_invocation)
- stmts.extend(sym_declaration(sym)
- for sym in late_declared_syms)
+ stmts.extend(sym_declaration(sym) for sym in late_declared_syms)
else:
- if len(tgts) != 1:
- report(ERETLVALS(stmt.site, 1, len(tgts)))
+ if len(decls) != 1:
+ report(ERETLVALS(stmt.site, 1, len(decls)))
else:
- sym = syms_to_add[0]
- sym.init = eval_initializer(
- inits[0].site, sym.type, inits[0], location, scope, False)
- scope.add(sym)
- stmts.append(sym_declaration(sym))
+ (_, _, typ) = decls[0]
+ init = eval_initializer(
+ inits[0].site, typ, inits[0], location, scope, False)
+
+ if syms_to_add:
+ sym = syms_to_add[0]
+ sym.init = init
+ scope.add(sym)
+ stmts.append(sym_declaration(sym))
+ else:
+ # Discard identifier in play
+ stmts.append(mkExpressionStatement(
+ stmt.site, init.as_expr(typ), explicit_discard=True))
else:
# Initializer evaluation and variable declarations are done in separate
# passes in order to prevent the newly declared variables from being in
# scope when the initializers are evaluated
inits = [get_initializer(stmt.site, typ, init, location, scope)
- for ((_, typ), init) in zip(decls, inits)]
- for ((name, typ), init) in zip(decls, inits):
- sym = scope.add_variable(
- name, type = typ, site = stmt.site, init = init, stmt = True,
- make_unique=not dml.globals.debuggable)
- stmts.append(sym_declaration(sym))
+ if name is not None or init is not None else None
+ for ((_, name, typ), init) in zip(decls, inits)]
+ for ((ident_site, name, typ), init) in zip(decls, inits):
+ if name is not None:
+ sym = scope.add_variable(
+ name, type = typ, site = ident_site, init = init,
+ stmt = True, make_unique=not dml.globals.debuggable)
+ stmts.append(sym_declaration(sym))
+ elif init is None:
+ # Corresponds to e.g. 'local int _;'
+ # This would be pointless except for the niche case of
+ # forcing an error if a type is invalid
+ check_named_types(typ)
+ stmts.append(mkNoop(ident_site))
+ else:
+ stmts.append(mkExpressionStatement(
+ init.site, init.as_expr(typ), explicit_discard=True))
return stmts
@@ -2121,8 +2214,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
@@ -2190,7 +2283,7 @@ def stmt_saved(stmt, location, scope):
@statement_dispatcher
def stmt_null(stmt, location, scope):
- return []
+ return [mkNull(stmt.site)]
@statement_dispatcher
def stmt_if(stmt, location, scope):
@@ -2330,21 +2423,32 @@ def try_codegen_invocation(site, init_ast, outargs, location, scope):
else:
return common_inline(site, meth_node, indices, inargs, outargs)
+def codegen_init_for_untyped_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 ExpressionInitializer(
+ 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:
@@ -2360,19 +2464,35 @@ def stmt_assign(stmt, location, scope):
+ f'initializer: Expected {src_asts}, got 1'))
return []
- stmts = []
+ if isinstance(tgts[-1], NonValue):
+ if len(tgts) != 1:
+ raise tgts[-1].exc()
+ init_typ = tgts[-1].type if tgts[-1].explicit_type else None
+ else:
+ init_typ = tgts[-1].ctype()
+
+ init = (eval_initializer(tgts[-1].site, init_typ, src_asts[0],
+ location, scope, False)
+ if init_typ is not None else
+ codegen_init_for_untyped_target(site, tgts[0], src_asts[0],
+ location, scope))
+
+ if len(tgts) == 1:
+ return [mkAssignStatement(tgts[0].site, tgts[0], init)]
+
lscope = Symtab(scope)
- 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)]
+ 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
@@ -2389,53 +2509,70 @@ 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):
+ init = (eval_initializer(site, tgt.type, src_ast, location,
+ scope, False)
+ if tgt.explicit_type else
+ codegen_init_for_untyped_target(site, tgt, src_ast,
+ location, scope))
+ stmt_pairs.append((mkAssignStatement(tgt.site, tgt, init),
+ 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 and not isinstance(tgt, Variable):
+ 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
@@ -2791,11 +2928,13 @@ def stmt_after(stmt, location, scope):
require_fully_typed(site, method)
func = method_instance(method)
inp = func.inp
+ inp_types = [p.typ for p in inp]
kind = 'method'
elif isinstance(methodref, HookSendNowRef):
indices = ()
send_now_hookref = methodref.hookref_expr
- msg_types = safe_realtype_shallow(send_now_hookref.ctype()).msg_types
+ inp_types = msg_types = safe_realtype_shallow(
+ send_now_hookref.ctype()).msg_types
inp = [(f'comp{i}', typ) for (i, typ) in enumerate(msg_types)]
kind = 'send_now'
else:
@@ -2805,15 +2944,15 @@ def stmt_after(stmt, location, scope):
# After-call is only possible for methods with serializable parameters
unserializable = []
- for (pname, ptype) in inp:
+ for (i, typ) in enumerate(inp_types):
try:
- serialize.mark_for_serialization(site, ptype)
+ serialize.mark_for_serialization(site, typ)
except ESERIALIZE:
- unserializable.append((pname, ptype))
+ unserializable.append(i)
if kind == 'method':
if len(unserializable) > 0:
- raise EAFTER(site, None, method, unserializable)
+ raise EAFTER(site, None, method, [inp[i] for i in unserializable])
else:
mark_method_referenced(func)
after_info = get_after_delay(method)
@@ -2822,10 +2961,9 @@ def stmt_after(stmt, location, scope):
assert kind == 'send_now'
if len(unserializable) > 0:
raise EAFTERSENDNOW(site, None, methodref.hookref_expr,
- unserializable)
+ [(i, inp_types[i]) for i in unserializable])
else:
- typeseq_info = get_type_sequence_info(
- (typ for (_, typ) in inp), create_new=True)
+ typeseq_info = get_type_sequence_info(inp_types, create_new=True)
after_info = get_after_delay(typeseq_info)
args_init = AfterIntoSendNowArgsInit(inargs,
methodref.hookref_expr)
@@ -2891,11 +3029,13 @@ def stmt_afteronhook(stmt, location, scope):
require_fully_typed(site, method)
func = method_instance(method)
inp = func.inp
+ inp_types = [p.typ for p in inp]
kind = 'method'
elif isinstance(methodref, HookSendNowRef):
indices = ()
send_now_hookref = methodref.hookref_expr
- msg_types = safe_realtype_shallow(send_now_hookref.ctype()).msg_types
+ inp_types = msg_types = safe_realtype_shallow(
+ send_now_hookref.ctype()).msg_types
inp = [(f'comp{i}', typ) for (i, typ) in enumerate(msg_types)]
kind = 'send_now'
else:
@@ -2907,7 +3047,11 @@ def stmt_afteronhook(stmt, location, scope):
len(msg_comp_param_asts))
msg_comp_params = {}
- for (idx, (mcp_site, mcp_name)) in enumerate(msg_comp_param_asts):
+ for (idx, p) in enumerate(msg_comp_param_asts):
+ if p.kind == 'discard':
+ continue
+ (_, mcp_site, mcp_name) = p
+
if mcp_name in msg_comp_params:
raise EDVAR(mcp_site, msg_comp_params[mcp_name][1],
mcp_name)
@@ -2945,16 +3089,17 @@ def stmt_afteronhook(stmt, location, scope):
if i not in arg_index_to_msg_comp_param]
unserializable = []
- for (idx, (pname, ptype)) in enumerate(inp):
+ for (idx, typ) in enumerate(inp_types):
if idx not in arg_index_to_msg_comp_param:
try:
- serialize.mark_for_serialization(site, ptype)
+ serialize.mark_for_serialization(site, typ)
except ESERIALIZE:
- unserializable.append((pname, ptype))
+ unserializable.append(idx)
if kind == 'method':
if len(unserializable) > 0:
- raise EAFTER(site, hookref_expr, method, unserializable)
+ raise EAFTER(site, hookref_expr, method,
+ [inp[i] for i in unserializable])
else:
mark_method_referenced(func)
aoh_key = method
@@ -2963,10 +3108,9 @@ def stmt_afteronhook(stmt, location, scope):
assert kind == 'send_now'
if len(unserializable) > 0:
raise EAFTERSENDNOW(site, hookref_expr, methodref.hookref_expr,
- unserializable)
+ [(i, inp_types[i]) for i in unserializable])
else:
- aoh_key = get_type_sequence_info(
- (typ for (_, typ) in inp), create_new=True)
+ aoh_key = get_type_sequence_info(inp_types, create_new=True)
args_init = AfterIntoSendNowArgsInit(filtered_inargs,
methodref.hookref_expr)
@@ -3044,7 +3188,8 @@ def stmt_immediateafter(stmt, location, scope):
@statement_dispatcher
def stmt_select(stmt, location, scope):
- [itername, lst, cond_ast, stmt_ast, else_ast] = stmt.args
+ [iter_ident, lst, cond_ast, stmt_ast, else_ast] = stmt.args
+ itername = iter_ident.args[0] if iter_ident.kind == 'variable' else None
# dbg('SELNODE %r, %r, %r' % (location.node, location.indices, lst))
lst = codegen_expression_maybe_nonvalue(lst, location, scope)
# dbg('SELECT %s in %r' % (itername, lst))
@@ -3057,7 +3202,8 @@ def stmt_select(stmt, location, scope):
clauses = []
for it in l:
condscope = Symtab(scope)
- condscope.add(ExpressionSymbol(itername, it, stmt.site))
+ if itername is not None:
+ condscope.add(ExpressionSymbol(itername, it, stmt.site))
cond = as_bool(codegen_expression(
cond_ast, location, condscope))
if cond.constant and not cond.value:
@@ -3079,7 +3225,8 @@ def stmt_select(stmt, location, scope):
return [if_chain]
raise lst.exc()
elif (compat.dml12_misc in dml.globals.enabled_compat
- and isinstance(lst.ctype(), TVector)):
+ and isinstance(lst.ctype(), TVector)
+ and itername is not None):
itervar = lookup_var(stmt.site, scope, itername)
if not itervar:
raise EIDENT(stmt.site, itername)
@@ -3093,9 +3240,10 @@ def foreach_each_in(site, itername, trait, each_in,
body_ast, location, scope):
inner_scope = Symtab(scope)
trait_type = TTrait(trait)
- inner_scope.add_variable(
- itername, type=trait_type, site=site,
- init=ForeachSequence.itervar_initializer(site, trait))
+ if itername is not None:
+ inner_scope.add_variable(
+ itername, type=trait_type, site=site,
+ init=ForeachSequence.itervar_initializer(site, trait))
context = ForeachSequenceLoopContext()
with context:
inner_body = mkCompound(site, declarations(inner_scope)
@@ -3140,7 +3288,8 @@ def stmt_foreach_dml12(stmt, location, scope):
@statement_dispatcher
def stmt_foreach(stmt, location, scope):
- [itername, lst, statement] = stmt.args
+ [iter_ident, lst, statement] = stmt.args
+ itername = iter_ident.args[0] if iter_ident.kind == 'variable' else None
lst = codegen_expression(lst, location, scope)
list_type = safe_realtype(lst.ctype())
if isinstance(list_type, TTraitList):
@@ -3154,7 +3303,8 @@ def stmt_foreach(stmt, location, scope):
@statement_dispatcher
def stmt_hashforeach(stmt, location, scope):
- [itername, lst, statement] = stmt.args
+ [iter_ident, lst, statement] = stmt.args
+ itername = iter_ident.args[0] if iter_ident.kind == 'variable' else None
lst = codegen_expression_maybe_nonvalue(lst, location, scope)
if isinstance(lst, NonValue):
if not isinstance(lst, AbstractList):
@@ -3176,8 +3326,9 @@ def foreach_constant_list(site, itername, lst, statement, location, scope):
TInt(32, True))
for dim in range(len(items.dimsizes)))
loopscope = Symtab(scope)
- loopscope.add(ExpressionSymbol(
- itername, items.expr(loopvars), site))
+ if itername is not None:
+ loopscope.add(ExpressionSymbol(
+ itername, items.expr(loopvars), site))
stmt = codegen_statement(statement, location, loopscope)
if stmt.is_empty:
@@ -3207,7 +3358,7 @@ def stmt_while(stmt, location, scope):
[cond, statement] = stmt.args
cond = as_bool(codegen_expression(cond, location, scope))
if stmt.site.dml_version() == (1, 2) and cond.constant and not cond.value:
- return [mkNull(stmt.site)]
+ return [mkNoop(stmt.site)]
else:
with CLoopContext():
res = mkWhile(stmt.site, cond,
@@ -3345,7 +3496,7 @@ def mkcall_method(site, func, indices):
def common_inline(site, method, indices, inargs, outargs):
if not verify_args(site, method.inp, method.outp, inargs, outargs):
- return mkNull(site)
+ return mkNoop(site)
if dml.globals.debuggable:
if method.fully_typed and (
@@ -3357,11 +3508,11 @@ def common_inline(site, method, indices, inargs, outargs):
# create a specialized method instance based on parameter
# types, and call that
intypes = tuple(
- arg if ((ptype is None
+ arg if ((p.typ is None
or compat.dml12_inline in dml.globals.enabled_compat)
and (arg.constant or undefined(arg)))
- else methfunc_param(ptype, arg)
- for ((pname, ptype), arg) in zip(method.inp, inargs))
+ else methfunc_param(p.typ, arg)
+ for (p, arg) in zip(method.inp, inargs))
outtypes = tuple(methfunc_param(ptype, arg)
for ((pname, ptype), arg)
in zip(method.outp, outargs))
@@ -3369,8 +3520,7 @@ def common_inline(site, method, indices, inargs, outargs):
mark_method_referenced(func)
# Filter out inlined arguments
- used_args = [i for (i, (n, t)) in enumerate(func.inp)
- if isinstance(t, DMLType)]
+ used_args = [i for (i, p) in enumerate(func.inp) if not p.inlined]
inargs = [inargs[i] for i in used_args]
inp = [func.inp[i] for i in used_args]
@@ -3559,42 +3709,46 @@ def codegen_inline(site, meth_node, indices, inargs, outargs,
# call is safe.
return codegen_call(site, meth_node, indices,
inargs, outargs)
- for (arg, (parmname, parmtype), argno) in zip(inargs, meth_node.inp,
- list(range(len(inargs)))):
+ pre = []
+ for (arg, p, argno) in zip(inargs, meth_node.inp,
+ list(range(len(inargs)))):
# Create an alias
- if parmtype:
+ if not p.inlined:
if undefined(arg):
raise arg.exc()
argtype = arg.ctype()
if not argtype:
raise ICE(arg.site, "unknown expression type")
- parmt = safe_realtype(parmtype)
+ parmt = safe_realtype(p.typ)
argt = safe_realtype(argtype)
(ok, trunc, constviol) = parmt.canstore(argt)
if not ok:
raise EARGT(site, 'inline', meth_node.name,
- arg.ctype(), parmname, parmtype, 'input')
+ arg.ctype(), p.logref, p.typ, 'input')
if constviol:
- raise ECONSTP(site, parmname, "method call")
+ raise ECONSTP(site, p.logref, "method call")
arg = coerce_if_eint(arg)
- if inhibit_copyin or undefined(arg):
- param_scope.add(ExpressionSymbol(parmname, arg, arg.site))
+ if p.ident is None:
+ pre.append(mkExpressionStatement(arg.site, arg,
+ explicit_discard=True))
+ elif inhibit_copyin or undefined(arg):
+ param_scope.add(ExpressionSymbol(p.ident, arg, arg.site))
elif arg.constant and (
- parmtype is None
+ p.inlined
or compat.dml12_inline in dml.globals.enabled_compat):
# Constants must be passed directly to
# provide constant folding. Other values are stored in a
# local variable to improve type checking and variable
# scoping.
- inlined_arg = mkInlinedParam(site, arg, parmname,
- parmtype or arg.ctype())
+ inlined_arg = mkInlinedParam(site, arg, p.ident,
+ p.typ or arg.ctype())
param_scope.add(ExpressionSymbol(
- parmname, inlined_arg, site))
+ p.ident, inlined_arg, site))
else:
- param_scope.add_variable(parmname,
- type = parmtype or arg.ctype(),
+ param_scope.add_variable(p.ident,
+ type = p.typ or arg.ctype(),
site = arg.site,
init = ExpressionInitializer(arg))
arg.decref()
@@ -3618,7 +3772,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,
@@ -3636,13 +3790,13 @@ def codegen_inline(site, meth_node, indices, inargs, outargs,
exit_handler = GotoExit_dml14(outargs)
with exit_handler:
code = codegen_statements(subs, location, param_scope)
- decls = declarations(param_scope)
+ pre.extend(declarations(param_scope))
post = ([mkLabel(site, exit_handler.label)]
if exit_handler.used else [])
- body = mkCompound(site, decls + code, rbrace_site)
+ body = mkCompound(site, pre + code, rbrace_site)
if meth_node.outp and body.control_flow().fallthrough:
report(ENORET(meth_node.astcode.site))
- return mkInlinedMethod(site, meth_node, decls, code, post)
+ return mkInlinedMethod(site, meth_node, pre, code, post)
def c_rettype(outp, throws):
if throws:
@@ -3653,17 +3807,16 @@ def c_rettype(outp, throws):
else:
return TVoid()
-def c_inargs(inp, outp, throws):
- '''Return the signature of the C function representing a DML method,
- on the form (outtype, [arg1, ...]), where each arg is a pair
- (name, type). inp includes any implicit arguments
- (device struct pointer, indices, etc)'''
+def c_extra_inargs(outp, throws):
+ '''Return required additional input parameters for a C function
+ given output parameters and throws, through the form
+ [arg1, ...]) where each arg is a pair (name, type).'''
if throws:
- return inp + [(n, TPtr(t)) for (n, t) in outp]
+ return [(n, TPtr(t)) for (n, t) in outp]
elif outp:
- return inp + [(n, TPtr(t)) for (n, t) in outp[1:]]
+ return [(n, TPtr(t)) for (n, t) in outp[1:]]
else:
- return list(inp)
+ return []
# TODO is startup a necessary member?
class MethodFunc(object):
@@ -3680,13 +3833,10 @@ class MethodFunc(object):
'memoized', 'cparams', 'rettype', 'suffix')
def __init__(self, method, inp, outp, throws, independent, startup,
- memoized, cparams, suffix):
- '''(inp, outp, throws) describe the method's signature; cparams
- describe the generated C function parameters corresponding to
- inp. If some method parameters are constant propagated, then
- the corresponding method parameter is on the form (name,
- value), instead of (name, type), and the corresponding C
- function parameter is omitted.'''
+ memoized, suffix):
+ '''(inp, outp, throws) describe the method's signature.
+ If some method parameters are constant propagated, then
+ the corresponding C function parameters will be omitted.'''
self.method = method
@@ -3700,22 +3850,25 @@ def __init__(self, method, inp, outp, throws, independent, startup,
# rettype is the return type of the C function
self.rettype = c_rettype(outp, throws)
- self.cparams = c_inargs(
- implicit_params(method) + list(cparams), outp, throws)
+ self.cparams = ([(n, t, t.declaration(n))
+ for (n, t) in implicit_params(method)]
+ + [(p.c_ident, p.typ, p.declaration())
+ for p in inp if not p.inlined]
+ + [(n, t, t.declaration(n))
+ for (n, t) in c_extra_inargs(outp, throws)])
@property
def prototype(self):
return self.rettype.declaration(
- "%s(%s)" % (self.get_cname(),
- ", ".join([t.declaration(n)
- for (n, t) in self.cparams])))
+ "%s(%s)" % (self.get_cname(), ", ".join(
+ decl for (_, _, decl) in self.cparams)))
def cfunc_expr(self, site):
return mkLit(site, self.get_cname(), self.cfunc_type)
@property
def cfunc_type(self):
- return TFunction([t for (_, t) in self.cparams], self.rettype)
+ return TFunction([t for (_, t, _) in self.cparams], self.rettype)
def get_name(self):
'''textual description of method, used in comment'''
@@ -3751,16 +3904,15 @@ def untyped_method_instance(method, signature):
return method.funcs[canon_signature]
(intypes, outtypes) = signature
- inp = [(arg, stype)
- for stype, (arg, etype) in zip(intypes, method.inp)]
+ inp = [(p.with_expr
+ if isinstance(stype, Expression) else p.with_type)(stype)
+ for stype, p in zip(intypes, method.inp)]
assert all(isinstance(t, DMLType) for t in outtypes)
outp = [(arg, stype)
for stype, (arg, etype) in zip(outtypes, method.outp)]
- cparams = [(n, t) for (n, t) in inp if isinstance(t, DMLType)]
-
func = MethodFunc(method, inp, outp, method.throws, method.independent,
- method.startup, method.memoized, cparams,
+ method.startup, method.memoized,
"__"+str(len(method.funcs)))
method.funcs[canon_signature] = func
@@ -3773,8 +3925,7 @@ def method_instance(method):
return method.funcs[None]
func = MethodFunc(method, method.inp, method.outp, method.throws,
- method.independent, method.startup, method.memoized,
- method.inp, "")
+ method.independent, method.startup, method.memoized, "")
method.funcs[None] = func
return func
@@ -3785,7 +3936,8 @@ def codegen_method_func(func):
method = func.method
indices = tuple(mkLit(method.site, '_idx%d' % i, TInt(32, False),
- str=dollar(method.site) + "%s" % (idxvar,))
+ str=(dollar(method.site)
+ + ("_" if idxvar is None else "")))
for (i, idxvar) in enumerate(method.parent.idxvars()))
intercepted = intercepted_method(method)
if intercepted:
@@ -3793,20 +3945,23 @@ def codegen_method_func(func):
with crep.DeviceInstanceContext():
return intercepted(
method.parent, indices,
- [mkLit(method.site, n, t) for (n, t) in func.inp],
+ [mkLit(method.site, p.c_ident, p.typ) for p in func.inp],
[mkLit(method.site, "*%s" % n, t) for (n, t) in func.outp],
SimpleSite(method.site.loc()))
inline_scope = MethodParamScope(global_scope)
- for (name, e) in func.inp:
- if dml.globals.dml_version == (1, 2) and (
- compat.dml12_misc not in dml.globals.enabled_compat):
- check_varname(method.site, name)
- if isinstance(e, Expression):
+ for p in func.inp:
+ e = p.expr
+ if (p.ident is not None
+ and dml.globals.dml_version == (1, 2)
+ and compat.dml12_misc not in dml.globals.enabled_compat):
+ check_varname(p.site, p.ident)
+ if e and p.ident is not None:
inlined_arg = (
- mkInlinedParam(method.site, e, name, e.ctype())
+ mkInlinedParam(p.site, e, p.ident, e.ctype())
if defined(e) else e)
- inline_scope.add(ExpressionSymbol(name, inlined_arg, method.site))
- inp = [(n, t) for (n, t) in func.inp if isinstance(t, DMLType)]
+ inline_scope.add(ExpressionSymbol(
+ p.ident, inlined_arg, p.site))
+ inp = [p for p in func.inp if not p.inlined]
with ErrorContext(method):
location = Location(method, indices)
@@ -3855,8 +4010,10 @@ def codegen_method(site, inp, outp, throws, independent, memoization, ast,
default, location, fnscope, rbrace_site):
with (crep.DeviceInstanceContext() if not independent
else contextlib.nullcontext()):
- for (arg, etype) in inp:
- fnscope.add_variable(arg, type=etype, site=site, make_unique=False)
+ for p in inp:
+ if p.ident is not None:
+ fnscope.add_variable(p.ident, type=p.typ, site=site,
+ make_unique=False)
initializers = [get_initializer(site, parmtype, None, None, None)
for (_, parmtype) in outp]
@@ -3902,7 +4059,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 = []
@@ -3979,12 +4136,12 @@ def methfunc_param(ptype, arg):
def require_fully_typed(site, meth_node):
if not meth_node.fully_typed:
- for (parmname, parmtype) in meth_node.inp:
- if not parmtype:
- raise ENARGT(meth_node.site, parmname, 'input', site)
+ for p in meth_node.inp:
+ if p.inlined:
+ raise ENARGT(meth_node.site, p.logref, 'input', site)
for (parmname, parmtype) in meth_node.outp:
if not parmtype:
- raise ENARGT(meth_node.site, parmname, 'output', site)
+ raise ENARGT(meth_node.site, f"'{parmname}'", 'output', site)
raise ICE(site, "no missing parameter type")
def codegen_call_expr(site, meth_node, indices, inits, location, scope):
@@ -3999,7 +4156,7 @@ def codegen_call_traitmethod(site, expr, inargs, outargs):
if not isinstance(expr, TraitMethodRef):
raise ICE(site, "cannot call %r: not a trait method" % (expr,))
if not verify_args(site, expr.inp, expr.outp, inargs, outargs):
- return mkNull(site)
+ return mkNoop(site)
def mkcall(args):
rettype = c_rettype(expr.outp, expr.throws)
# implicitly convert endian int arguments to integers
@@ -4011,7 +4168,7 @@ def mkcall(args):
def codegen_call(site, meth_node, indices, inargs, outargs):
'''Generate a call using a direct reference to the method node'''
if not verify_args(site, meth_node.inp, meth_node.outp, inargs, outargs):
- return mkNull(site)
+ return mkNoop(site)
require_fully_typed(site, meth_node)
func = method_instance(meth_node)
@@ -4048,15 +4205,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/ctree.py b/py/dml/ctree.py
index 163d9f045..e07f00e29 100644
--- a/py/dml/ctree.py
+++ b/py/dml/ctree.py
@@ -40,7 +40,8 @@
'param_bool_fixup',
'mkCompound',
- 'mkNull', 'Null',
+ 'mkNull',
+ 'mkNoop',
'mkLabel',
'mkUnrolledLoop',
'mkGoto',
@@ -70,7 +71,7 @@
'mkVectorForeach',
'mkBreak',
'mkContinue',
- 'mkAssignStatement',
+ 'mkAssignStatement', 'AssignStatement',
'mkCopyData',
'mkIfExpr', 'IfExpr',
#'BinOp',
@@ -136,6 +137,7 @@
'mkEachIn', 'EachIn',
'mkBoolConstant',
'mkUndefined', 'Undefined',
+ 'mkDiscardRef',
'TraitParameter',
'TraitSessionRef',
'TraitHookRef',
@@ -432,6 +434,9 @@ def mkCompound(site, statements, rbrace_site=None):
return Compound(site, collapsed, rbrace_site)
class Null(Statement):
+ '''Should only be used to represent stand-alone ; present in DML code. For
+ all other purposes (artificially created empty statements) Noop should
+ be used instead.'''
is_empty = True
def toc_stmt(self):
self.linemark()
@@ -441,6 +446,19 @@ def toc(self):
mkNull = Null
+class Noop(Statement):
+ '''An empty statement represented by a pair of braces in generated C. This
+ avoids certain GCC and Coverity warnings that could manifest if ';' were
+ used instead.'''
+ is_empty = True
+ def toc_stmt(self):
+ self.linemark()
+ out('{}\n')
+ def toc(self):
+ pass
+
+mkNoop = Noop
+
class Label(Statement):
def __init__(self, site, label, unused=False):
Statement.__init__(self, site)
@@ -657,21 +675,24 @@ def mkDelete(site, expr):
class ExpressionStatement(Statement):
@auto_init
- def __init__(self, site, expr): pass
+ def __init__(self, site, expr, explicit_discard): pass
def toc_stmt(self):
self.linemark()
- out(self.expr.discard()+';\n')
+ out(self.expr.discard(explicit=self.explicit_discard)+';\n')
-def mkExpressionStatement(site, expr):
- if isinstance(expr, Constant):
- return mkNull(site)
- return ExpressionStatement(site, expr)
+def mkExpressionStatement(site, expr, explicit_discard=False):
+ if expr.constant and explicit_discard:
+ return mkNoop(site)
+ return ExpressionStatement(site, expr, explicit_discard)
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
@@ -840,7 +861,7 @@ def mkIf(site, cond, truebranch, falsebranch = None, else_site=None):
elif falsebranch:
return falsebranch
else:
- return mkNull(site)
+ return mkNoop(site)
return If(site, cond, truebranch, falsebranch, else_site)
class While(Statement):
@@ -905,7 +926,8 @@ def toc_stmt(self):
if all(isinstance(post, ExpressionStatement) for post in self.posts):
# common case: all post statements are expressions, so
# traditional for loop can be produced
- out(', '.join(post.expr.discard() for post in self.posts))
+ out(', '.join(post.expr.discard(explicit=post.explicit_discard)
+ for post in self.posts))
else:
# general case: arbitrary statements in post code;
# encapsulate in a statement expression
@@ -1096,22 +1118,40 @@ 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):
+ target_type = target.type if target.explicit_type else None
+ else:
+ target_type = target.ctype()
+
+
+ if target_type is not None and deep_const(target_type):
+ raise ECONST(site)
+
+ 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)
+ if isinstance(target, NonValue):
+ typ = target.type if target.explicit_type else None
+ else:
+ typ = target.ctype()
+
+ if typ is not None:
+ source = source_for_assignment(site, typ, source)
+
+ return mkAssignStatement(site, target, ExpressionInitializer(source))
#
# Expressions
@@ -1170,21 +1210,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
@@ -2531,8 +2562,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())
@@ -2611,13 +2642,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):
@@ -2655,7 +2686,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
@@ -2777,7 +2809,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
@@ -2803,7 +2835,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:
@@ -3009,7 +3041,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)
@@ -3031,7 +3064,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]
@@ -3486,11 +3519,10 @@ def copy(self, site):
class AddressOfMethod(Constant):
def ctype(self):
- params = (self.value.cparams if self.value.independent else
- [("_obj", TPtr(TNamed("conf_object_t")))]
- + self.value.cparams[1:])
- return TPtr(TFunction([typ for (_, typ) in params],
- self.value.rettype))
+ types = [t for (_, t, _) in self.value.cparams]
+ if not self.value.independent:
+ types[0] = TPtr(TNamed("conf_object_t"))
+ return TPtr(TFunction(types, self.value.rettype))
def read(self):
prefix = '_trampoline' * (not self.value.independent)
@@ -3554,6 +3586,30 @@ def exc(self):
mkUndefined = Undefined
+class DiscardRef(NonValue):
+ slots = ('explicit_type', 'type')
+ writable = True
+
+ @auto_init
+ def __init__(self, site, type):
+ self.explicit_type = type is not None
+
+ def __str__(self):
+ return '_'
+
+ def exc(self):
+ return EDISCARDREF(self.site)
+
+ def write(self, source):
+ if self.explicit_type:
+ return source.as_expr(self.type).discard(explicit=True)
+ else:
+ assert isinstance(source, ExpressionInitializer)
+ return source.expr.discard(explicit=True)
+
+def mkDiscardRef(site, type=None):
+ return DiscardRef(site, type)
+
def endian_convert_expr(site, idx, endian, size):
"""Convert a bit index to little-endian (lsb=0) numbering.
@@ -3986,7 +4042,6 @@ def node_type(node, site):
class NodeRef(Expression):
"A reference to a node in the device specification"
priority = 1000
- explicit_type = True
@auto_init
def __init__(self, site, node, indices):
assert isinstance(node, objects.DMLObject)
@@ -4013,6 +4068,7 @@ class NodeRefWithStorage(NodeRef, LValue):
'''Reference to node that also contains storage, such as allocated
register, field or attribute in DML 1.2'''
slots = ('type',)
+ explicit_type = True
@auto_init
def __init__(self, site, node, indices):
@@ -4569,14 +4625,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:
@@ -4590,11 +4660,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 try_resolve_len(site, lh):
if isinstance(lh, NonValue):
@@ -4733,18 +4804,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):
@@ -4818,7 +4899,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):
@@ -4978,8 +5059,8 @@ def ctype(self):
return self.expr.ctype()
def read(self):
return self.expr.read()
- def discard(self):
- return self.expr.discard()
+ def discard(self, explicit=False):
+ return self.expr.discard(explicit)
def incref(self):
self.expr.incref()
def decref(self):
@@ -4991,12 +5072,17 @@ def explicit_type(self):
def type(self):
assert self.explicit_type
return self.expr.type
+ # 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
@@ -5178,15 +5264,36 @@ 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(rt, 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(shallow_deconst_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 a strict type equality check to ensure
+ # safety (which, horrifically, compat_dml12_int may subvert),
+ # 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
@@ -5214,21 +5321,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
@@ -5268,10 +5366,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}')
@@ -5310,8 +5409,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
@@ -5370,8 +5468,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,
@@ -5415,7 +5512,7 @@ def sym_declaration(sym):
# dbg('ignoring %r (init = %r)' % (sym.value, sym.init))
if sym.init:
sym.init.decref()
- return None
+ return mkNoop(sym.site)
# This will prevent warnings from the C compiler
# HACK: Always True to not rely on the broken symbol usage tracking
diff --git a/py/dml/ctree_test.py b/py/dml/ctree_test.py
index ec8e27508..a5b505cc6 100644
--- a/py/dml/ctree_test.py
+++ b/py/dml/ctree_test.py
@@ -1368,9 +1368,8 @@ def null_pointers(self):
@subtest()
def assign_trunc(self):
target_type = types.TInt(5, True)
- stmt = ctree.mkAssignStatement(
- site, variable('x', target_type),
- ctree.ExpressionInitializer(int_const(0x5f)))
+ stmt = ctree.mkCopyData(site, int_const(0x5f),
+ variable('x', target_type))
code = output.StrOutput()
with code:
stmt.toc()
@@ -1401,8 +1400,8 @@ def const_types(self):
types.TExternStruct({}, 'struct_t', 'struct_t'),
types.TStruct({'x': types.TBool()}, 'struct_label'),
types.TLayout(
- 'big-endian', {
- 'x': (site, types.TEndianInt(24, True, 'big-endian'))},
+ 'big-endian', [(site, 'x',
+ types.TEndianInt(24, True, 'big-endian'))],
'struct_label'),
types.THook([]),
]
diff --git a/py/dml/dmllex14.py b/py/dml/dmllex14.py
index e4f5e23e0..baa8b471d 100644
--- a/py/dml/dmllex14.py
+++ b/py/dml/dmllex14.py
@@ -13,10 +13,12 @@
tokens = (common_tokens
+ ('HASHCONDOP', 'HASHCOLON')
+ + ('DISCARD',)
+ tuple(hashids.values()))
t_HASHCONDOP = r'\#\?'
t_HASHCOLON = r'\#:'
+t_DISCARD = r'_'
keywords_dml14 = dict(keywords_common)
for kw in ['param', 'saved', 'async', 'await', 'with', 'shared', 'stringify',
@@ -24,6 +26,8 @@
keywords_dml14[kw] = kw.upper()
tokens += (kw.upper(),)
+keywords_dml14['_'] = 'DISCARD'
+
reserved_idents = reserved_idents_common + (
'PARAM', 'SAVED', 'INDEPENDENT', 'STARTUP', 'MEMOIZED')
diff --git a/py/dml/dmlparse.py b/py/dml/dmlparse.py
index a580ae743..5fbb57772 100644
--- a/py/dml/dmlparse.py
+++ b/py/dml/dmlparse.py
@@ -383,7 +383,8 @@ def field_array_size(t):
if t[4].kind != 'int' or t[4].args != (0,):
report(EZRANGE(site(t, 4)))
s = site(t)
- t[0] = [(t[2], ast.binop(s, ast.int(s, 1), '+', t[6]))] + t[8]
+ t[0] = [(ast.variable(site(t, 2), t[2]),
+ ast.binop(s, ast.int(s, 1), '+', t[6]))] + t[8]
if logging.show_porting:
# j in 0..expr => j < expr + 1
# We allow this conversion to be less polished than the
@@ -427,14 +428,16 @@ def session_decl_init(t):
@prod_dml14
def session_decl_many(t):
- 'session_decl : data LPAREN cdecl_list_nonempty RPAREN SEMI'
- cdecl_list_enforce_named(t[3])
+ 'session_decl : data LPAREN cdecl_maybe_discarded_list_nonempty RPAREN SEMI'
+ cdecl_maybe_discarded_list_enforce_named(t[3])
+ cdecl_maybe_discarded_list_enforce_not_discarded(t[3])
t[0] = ast.session(site(t), t[3], None)
@prod_dml14
def session_decl_many_init(t):
- 'session_decl : data LPAREN cdecl_list_nonempty RPAREN EQUALS initializer SEMI'
- cdecl_list_enforce_named(t[3])
+ 'session_decl : data LPAREN cdecl_maybe_discarded_list_nonempty RPAREN EQUALS initializer SEMI'
+ cdecl_maybe_discarded_list_enforce_named(t[3])
+ cdecl_maybe_discarded_list_enforce_not_discarded(t[3])
t[0] = ast.session(site(t), t[3], t[6])
@prod_dml14
@@ -454,14 +457,16 @@ def saved_decl_init(t):
@prod_dml14
def saved_decl_many(t):
- 'saved_decl : SAVED LPAREN cdecl_list_nonempty RPAREN SEMI'
- cdecl_list_enforce_named(t[3])
+ 'saved_decl : SAVED LPAREN cdecl_maybe_discarded_list_nonempty RPAREN SEMI'
+ cdecl_maybe_discarded_list_enforce_named(t[3])
+ cdecl_maybe_discarded_list_enforce_not_discarded(t[3])
t[0] = ast.saved(site(t), t[3], None)
@prod_dml14
def saved_decl_many_init(t):
- 'saved_decl : SAVED LPAREN cdecl_list_nonempty RPAREN EQUALS initializer SEMI'
- cdecl_list_enforce_named(t[3])
+ 'saved_decl : SAVED LPAREN cdecl_maybe_discarded_list_nonempty RPAREN EQUALS initializer SEMI'
+ cdecl_maybe_discarded_list_enforce_named(t[3])
+ cdecl_maybe_discarded_list_enforce_not_discarded(t[3])
t[0] = ast.saved(site(t), t[3], t[6])
@prod
@@ -550,7 +555,7 @@ def object_method_noinparams(t):
@prod_dml12
def object_method(t):
- '''method : METHOD maybe_extern objident LPAREN cdecl_or_ident_list RPAREN method_outparams maybe_nothrow maybe_default compound_statement'''
+ '''method : METHOD maybe_extern objident LPAREN cdecl_maybe_discarded_or_ident_list RPAREN method_outparams maybe_nothrow maybe_default compound_statement'''
name = t[3]
inp = t[5]
outp = t[7]
@@ -559,7 +564,7 @@ def object_method(t):
# some standard methods are assigned a type later on
if name not in {'set', 'write'}:
report(PINLINEDECL(site(t), 'method', 'inline method'))
- for (_, decl_site, argname, typ) in inp:
+ for (_, decl_site, (_, _, argname), typ) in inp:
if not typ:
report(PINLINEDECL(decl_site, argname, 'inline ' + argname))
if logging.show_porting and outp:
@@ -629,7 +634,7 @@ def object_inline_method(t):
@prod_dml12
def arraydef1(t):
'''arraydef : expression'''
- t[0] = ('i', t[1])
+ t[0] = (ast.variable(site(t), 'i'), t[1])
if logging.show_porting:
report(PARRAY_I(site(t)))
@@ -639,7 +644,8 @@ def arraydef2(t):
if t[3].kind != 'int' or t[3].args != (0,):
report(EZRANGE(site(t, 3)))
s = site(t)
- t[0] = (t[1], ast.binop(s, ast.int(s, 1), '+', t[5]))
+ t[0] = (ast.variable(site(t, 1), t[1]),
+ ast.binop(s, ast.int(s, 1), '+', t[5]))
if logging.show_porting:
if t[5].kind == 'int':
# j in 0..4 => j < 5
@@ -661,12 +667,12 @@ def arraydef2(t):
@prod_dml14
def arraydef(t):
- '''arraydef : ident LT expression'''
+ '''arraydef : ident_or_discard LT expression'''
t[0] = (t[1], t[3])
@prod_dml14
def arraydef_implicit(t):
- '''arraydef : ident LT ELLIPSIS'''
+ '''arraydef : ident_or_discard LT ELLIPSIS'''
t[0] = (t[1], None)
# Traits
@@ -833,12 +839,12 @@ def constant(t):
@prod_dml12
def extern(t):
- 'toplevel : EXTERN cdecl_or_ident SEMI'
- t[0] = ast.extern(site(t), t[2])
+ 'toplevel : EXTERN cdecl_maybe_discarded_or_ident SEMI'
+ t[0] = ast.extern(site(t), cdecl_enforce_not_discarded(t[2]))
@prod_dml14
def extern(t):
- 'toplevel : EXTERN cdecl SEMI'
+ 'toplevel : EXTERN named_cdecl SEMI'
t[0] = ast.extern(site(t), t[2])
@prod
@@ -1104,45 +1110,69 @@ def method_outparams_none(t):
@prod_dml12
def method_outparams_some(t):
- 'method_outparams : ARROW LPAREN cdecl_or_ident_list RPAREN'
+ 'method_outparams : ARROW LPAREN cdecl_maybe_discarded_or_ident_list RPAREN'
+ cdecl_maybe_discarded_list_enforce_not_discarded(t[3])
t[0] = t[3]
@prod_dml14
def method_outparams_some(t):
- 'method_outparams : ARROW LPAREN cdecl_list RPAREN'
+ 'method_outparams : ARROW LPAREN cdecl_maybe_discarded_list RPAREN'
for (i, (kind, psite, name, typ)) in enumerate(t[3]):
if name:
# It would be logical to just use ESYNTAX here, but be nicer
# because this is a very common mistake until 1.2 is
# deprecated
- report(ERETARGNAME(psite, name))
- t[3][i] = ast.cdecl(psite, None, typ)
+ report(ERETARGNAME(
+ psite, name.args[0] if name.kind == 'variable' else '_'))
+ t[3][i] = ast.cdecl(psite, None, typ)
t[0] = t[3]
@prod_dml14
def method_params_maybe_untyped(t):
- 'method_params_maybe_untyped : LPAREN cdecl_or_ident_list RPAREN method_outparams throws'
+ 'method_params_maybe_untyped : LPAREN cdecl_maybe_discarded_or_ident_list RPAREN method_outparams throws'
t[0] = (t[2], t[4], t[5])
-def cdecl_list_enforce_unnamed(decls):
- for (kind, psite, name, _) in decls:
- assert kind == 'cdecl'
+def cdecl_maybe_discarded_list_enforce_unnamed(decls):
+ for (i, (kind, psite, name, typ)) in enumerate(decls):
+ assert kind == 'cdecl_maybe_discarded'
if name:
- report(ESYNTAX(psite, name, ''))
+ report(ESYNTAX(
+ psite, name.args[0] if name.kind == 'variable' else '_', ''))
+ decls[i] = ast.cdecl(psite, None, typ)
-def cdecl_list_enforce_named(decls):
+def cdecl_maybe_discarded_list_enforce_named(decls):
for (i, (kind, psite, name, typ)) in enumerate(decls):
- assert kind == 'cdecl'
+ assert kind == 'cdecl_maybe_discarded', kind
if not name:
report(ESYNTAX(psite, None,
'name omitted in parameter declaration'))
- decls[i] = ast.cdecl(psite, '_name_omitted%d' % (i,), typ)
+ decls[i] = ast.cdecl_maybe_discarded(
+ psite,
+ ast.variable(psite, '_name_omitted%d' % (i,)),
+ typ)
+
+def cdecl_enforce_not_discarded(decl):
+ (kind, psite, ident, typ) = decl
+ assert kind == 'cdecl_maybe_discarded'
+ if ident is None:
+ name = None
+ elif ident.kind == 'discard':
+ discard_error(psite)
+ name = '__'
+ else:
+ assert ident.kind == 'variable', ident.kind
+ name = ident.args[0]
+ return ast.cdecl(psite, name, typ)
+
+def cdecl_maybe_discarded_list_enforce_not_discarded(decls):
+ for (i, decl) in enumerate(decls):
+ decls[i] = cdecl_enforce_not_discarded(decl)
@prod
def method_params_typed(t):
- 'method_params_typed : LPAREN cdecl_list RPAREN method_outparams throws'
- cdecl_list_enforce_named(t[2])
+ 'method_params_typed : LPAREN cdecl_maybe_discarded_list RPAREN method_outparams throws'
+ cdecl_maybe_discarded_list_enforce_named(t[2])
t[0] = (t[2], t[4], t[5])
@prod_dml12
@@ -1229,8 +1259,8 @@ def offsetspec_empty(t):
# A C-like declaration, or a simple name
@prod_dml12
-def cdecl_or_ident_decl(t):
- '''cdecl_or_ident : cdecl'''
+def cdecl_maybe_discarded_or_ident_decl(t):
+ '''cdecl_maybe_discarded_or_ident : cdecl_maybe_discarded'''
(_, site, name, typ) = t[1]
if name:
t[0] = t[1]
@@ -1238,48 +1268,60 @@ def cdecl_or_ident_decl(t):
# Hack: a single identifier is parsed as an anonymous
# parameter; i.e., a simple type ident with no name attached
# to it. We convert that to an untyped identifier.
- t[0] = ast.cdecl(site, typ[0], None)
+ t[0] = ast.cdecl_maybe_discarded(site,
+ ast.variable(site, typ[0]),
+ None)
else:
raise ESYNTAX(site, None, "missing parameter name")
@prod_dml14
-def cdecl_or_ident_decl(t):
- '''cdecl_or_ident : named_cdecl'''
+def cdecl_maybe_discarded_or_ident_decl(t):
+ '''cdecl_maybe_discarded_or_ident : named_cdecl_maybe_discarded'''
t[0] = t[1]
@prod_dml14
-def cdecl_or_ident_inline(t):
- '''cdecl_or_ident : INLINE ident'''
- t[0] = ast.cdecl(site(t), t[2], None)
+def cdecl_maybe_discarded_or_ident_inline(t):
+ '''cdecl_maybe_discarded_or_ident : INLINE ident'''
+ t[0] = ast.cdecl_maybe_discarded(site(t),
+ ast.variable(site(t, 2), t[2]),
+ None)
# A C-like declaration with required identifier name
@prod
-def named_cdecl(t):
- '''named_cdecl : cdecl'''
+def named_cdecl_maybe_discarded(t):
+ '''named_cdecl_maybe_discarded : cdecl_maybe_discarded'''
_, site, name, typ = t[1]
if name:
t[0] = t[1]
else:
report(ESYNTAX(site, None, "missing name in declaration"))
- t[0] = ast.cdecl(site, '_name_omitted', typ)
+ t[0] = ast.cdecl_maybe_discarded(site,
+ ast.variable(site, '_name_omitted'),
+ typ)
+
+@prod
+def named_cdecl(t):
+ '''named_cdecl : named_cdecl_maybe_discarded'''
+ t[0] = cdecl_enforce_not_discarded(t[1])
+
# A C-like declaration
@prod
-def cdecl(t):
- '''cdecl : basetype cdecl2'''
+def cdecl_maybe_discarded(t):
+ '''cdecl_maybe_discarded : basetype cdecl2'''
# t[2] is a list of modifiers, innermost last
- name = t[2][-1]
+ ident = t[2][-1]
info = [t[1]] + t[2][:-1]
- t[0] = ast.cdecl(site(t), name, info)
+ t[0] = ast.cdecl_maybe_discarded(site(t), ident, info)
@prod
-def cdecl_const(t):
- '''cdecl : CONST basetype cdecl2'''
- # t[2] is a list of modifiers, innermost last
- name = t[3][-1]
+def cdecl_maybe_discarded_const(t):
+ '''cdecl_maybe_discarded : CONST basetype cdecl2'''
+ # t[3] is a list of modifiers, innermost last
+ ident = t[3][-1]
info = [t[2], 'const'] + t[3][:-1]
- t[0] = ast.cdecl(site(t), name, info)
+ t[0] = ast.cdecl_maybe_discarded(site(t), ident, info)
@prod_dml14
def basetype(t):
@@ -1308,8 +1350,8 @@ def basetype_each(t):
@prod_dml14
def basetype_hook(t):
- '''basetype : HOOK LPAREN cdecl_list RPAREN'''
- cdecl_list_enforce_unnamed(t[3])
+ '''basetype : HOOK LPAREN cdecl_maybe_discarded_list RPAREN'''
+ cdecl_maybe_discarded_list_enforce_unnamed(t[3])
t[0] = ('hook', t[3])
@prod
@@ -1347,11 +1389,11 @@ def cdecl3(t):
# The declaration 'data int int;' is also accepted, but gives
# invalid C code.
'cdecl3 : typeident'
- t[0] = [t[1]]
+ t[0] = [ast.variable(site(t, 1), t[1])]
@prod_dml14
def cdecl3(t):
- 'cdecl3 : ident'
+ 'cdecl3 : ident_or_discard'
t[0] = [t[1]]
@prod
@@ -1366,7 +1408,7 @@ def cdecl3_arr(t):
@prod
def cdecl3_fun(t):
- 'cdecl3 : cdecl3 LPAREN cdecl_list_opt_ellipsis RPAREN'
+ 'cdecl3 : cdecl3 LPAREN cdecl_maybe_discarded_list_opt_ellipsis RPAREN'
t[0] = ['funcall', t[3]] + t[1]
@prod
@@ -1376,61 +1418,60 @@ def cdecl3_par(t):
# A comma-separated cdecl list, used in function parameter lists
@prod
-def cdecl_list_empty(t):
- 'cdecl_list : '
+def cdecl_maybe_discarded_list_empty(t):
+ 'cdecl_maybe_discarded_list : '
t[0] = []
@prod
-def cdecl_list_nonempty(t):
- 'cdecl_list : cdecl_list_nonempty'
+def cdecl_maybe_discarded_list_nonempty(t):
+ 'cdecl_maybe_discarded_list : cdecl_maybe_discarded_list_nonempty'
t[0] = t[1]
@prod
-def cdecl_list_one(t):
- 'cdecl_list_nonempty : cdecl'
+def cdecl_maybe_discarded_list_one(t):
+ 'cdecl_maybe_discarded_list_nonempty : cdecl_maybe_discarded'
t[0] = [t[1]]
@prod
-def cdecl_list_many(t):
- '''cdecl_list_nonempty : cdecl_list_nonempty COMMA cdecl'''
+def cdecl_maybe_discarded_list_many(t):
+ '''cdecl_maybe_discarded_list_nonempty : cdecl_maybe_discarded_list_nonempty COMMA cdecl_maybe_discarded'''
t[0] = t[1] + [t[3]]
# Variant that allows ELLIPSIS in the end
@prod
-def cdecl_list_opt_ellipsis(t):
- '''cdecl_list_opt_ellipsis : cdecl_list
- | cdecl_list_ellipsis'''
- t[0] = t[1]
+def cdecl_maybe_discarded_list_opt_ellipsis_no(t):
+ 'cdecl_maybe_discarded_list_opt_ellipsis : cdecl_maybe_discarded_list'
+ t[0] = (t[1], False)
@prod
-def cdecl_list_ellipsis_only(t):
- 'cdecl_list_ellipsis : ELLIPSIS'
- t[0] = [t[1]]
+def cdecl_maybe_discarded_list_opt_ellipsis_only(t):
+ 'cdecl_maybe_discarded_list_opt_ellipsis : ELLIPSIS'
+ t[0] = ([], True)
@prod
-def cdecl_list_ellipsis_last(t):
- 'cdecl_list_ellipsis : cdecl_list_nonempty COMMA ELLIPSIS'
- t[0] = t[1] + [t[3]]
+def cdecl_maybe_discarded_list_opt_tellipsis_last(t):
+ 'cdecl_maybe_discarded_list_opt_ellipsis : cdecl_maybe_discarded_list_nonempty COMMA ELLIPSIS'
+ t[0] = (t[1], True)
-# A comma-separated cdecl_or_ident list, used in method parameter lists
+# A comma-separated cdecl_maybe_discarded_or_ident list, used in method parameter lists
@prod
-def cdecl_or_ident_list_empty(t):
- 'cdecl_or_ident_list : '
+def cdecl_maybe_discarded_or_ident_list_empty(t):
+ 'cdecl_maybe_discarded_or_ident_list : '
t[0] = []
@prod
-def cdecl_or_ident_list_nonempty(t):
- 'cdecl_or_ident_list : cdecl_or_ident_list2'
+def cdecl_maybe_discarded_or_ident_list_nonempty(t):
+ 'cdecl_maybe_discarded_or_ident_list : cdecl_maybe_discarded_or_ident_list2'
t[0] = t[1]
@prod
-def cdecl_or_ident_list2_one(t):
- 'cdecl_or_ident_list2 : cdecl_or_ident'
+def cdecl_maybe_discarded_or_ident_list2_one(t):
+ 'cdecl_maybe_discarded_or_ident_list2 : cdecl_maybe_discarded_or_ident'
t[0] = [t[1]]
@prod
-def cdecl_or_ident_list2(t):
- 'cdecl_or_ident_list2 : cdecl_or_ident_list2 COMMA cdecl_or_ident'
+def cdecl_maybe_discarded_or_ident_list2(t):
+ 'cdecl_maybe_discarded_or_ident_list2 : cdecl_maybe_discarded_or_ident_list2 COMMA cdecl_maybe_discarded_or_ident'
t[0] = t[1] + [t[3]]
@prod
@@ -1441,7 +1482,13 @@ def typeof(t):
def check_struct_namecoll(member_decls):
sites_by_name = {}
for decl in member_decls:
- (name, _) = decl.args
+ if decl.kind == 'cdecl':
+ (name, _) = decl.args
+ else:
+ (ident, _) = decl.args
+ if ident.kind == 'discard':
+ continue
+ (name,) = ident.args
if name in sites_by_name:
report(ENAMECOLL(decl.site, sites_by_name[name], name))
else:
@@ -1471,14 +1518,21 @@ def layout_decl(t):
field_names = set()
fields = []
for cdecl in t[4]:
- (name, typ) = cdecl.args
- if name in field_names:
- while (name in field_names
- or any(name == d.args[0] for d in t[4])):
- name = '_' + name
- cdecl = ast.cdecl(cdecl.site, name, typ)
+ (ident, typ) = cdecl.args
+ if ident.kind == 'variable':
+ (name,) = ident.args
+ if name in field_names:
+ while (name in field_names
+ or any(name == d.args[0].args[0]
+ for d in t[4]
+ if d.args[0].kind == 'variable')):
+ name = '_' + name
+ cdecl = ast.cdecl_maybe_discarded(
+ cdecl.site,
+ ast.variable(ident.site, name),
+ typ)
+ field_names.add(name)
fields.append(cdecl)
- field_names.add(name)
else:
fields = t[4]
check_struct_namecoll(fields)
@@ -1495,7 +1549,7 @@ def layout(t):
@prod
def layout_decls(t):
- 'layout_decls : layout_decls named_cdecl SEMI'
+ 'layout_decls : layout_decls named_cdecl_maybe_discarded SEMI'
t[0] = t[1] + (t[2],)
@prod
@@ -1797,10 +1851,15 @@ def expression_ident(t):
@prod_dml14
def expression_ident(t):
- '''expression : objident
+ '''expression : objident_base
| DEFAULT'''
t[0] = ast.variable(site(t), t[1])
+@prod_dml14
+def expression_discardref(t):
+ '''expression : discard'''
+ t[0] = t[1]
+
@prod_dml14
def expression_this(t):
'''expression : THIS'''
@@ -1808,8 +1867,8 @@ def expression_this(t):
@prod
def expression_member(t):
- '''expression : expression PERIOD objident
- | expression ARROW objident'''
+ '''expression : expression PERIOD objident_base
+ | expression ARROW objident_base'''
t[0] = ast.member(site(t, 2), t[1], t[2], t[3])
@prod
@@ -2261,16 +2320,42 @@ def ident_list_many(t):
'nonempty_ident_list : nonempty_ident_list COMMA ident'
t[0] = t[1] + [(site(t, 3), t[3])]
+@prod_dml14
+def ident_or_discard_list_empty(t):
+ 'ident_or_discard_list : '
+ t[0] = []
+
+@prod_dml14
+def ident_or_discard_list_nonempty(t):
+ 'ident_or_discard_list : nonempty_ident_or_discard_list'
+ t[0] = t[1]
+
+@prod_dml14
+def ident_or_discard_list_one(t):
+ 'nonempty_ident_or_discard_list : ident_or_discard'
+ t[0] = [t[1]]
+
+@prod_dml14
+def ident_or_discard_list_many(t):
+ 'nonempty_ident_or_discard_list : nonempty_ident_or_discard_list COMMA ident_or_discard'
+ t[0] = t[1] + [t[3]]
+
@prod_dml14
def statement_delay_hook(t):
- 'statement_except_hashif : AFTER expression ARROW LPAREN ident_list RPAREN COLON expression SEMI'
+ 'statement_except_hashif : AFTER expression ARROW LPAREN ident_or_discard_list RPAREN COLON expression SEMI'
t[0] = ast.afteronhook(site(t), t[2], t[5], t[8])
+# Not using ident_or_discard in order to avoid reduce/reduce conflict
+# with expression_member
@prod_dml14
def statement_delay_hook_one_msg_param(t):
'statement_except_hashif : AFTER expression ARROW ident COLON expression SEMI %prec bind'
- t[0] = ast.afteronhook(site(t), t[2], [(site(t, 4), t[4])], t[6])
+ t[0] = ast.afteronhook(site(t), t[2], [ast.variable(site(t, 4), t[4])], t[6])
+@prod_dml14
+def statement_delay_hook_discarded_msg_param(t):
+ 'statement_except_hashif : AFTER expression ARROW discard COLON expression SEMI %prec bind'
+ t[0] = ast.afteronhook(site(t), t[2], [t[4]], t[6])
@prod_dml14
def statement_delay_hook_no_msg_params(t):
@@ -2393,7 +2478,7 @@ def hashselect(t):
@prod
def select(t):
- 'statement_except_hashif : hashselect ident IN LPAREN expression RPAREN WHERE LPAREN expression RPAREN statement hashelse statement'
+ 'statement_except_hashif : hashselect ident_or_discard IN LPAREN expression RPAREN WHERE LPAREN expression RPAREN statement hashelse statement'
t[0] = ast.select(site(t), t[2], t[5], t[9], t[11], t[13])
@prod_dml12
@@ -2405,12 +2490,12 @@ def foreach(t):
@prod_dml14
def foreach(t):
- 'statement_except_hashif : FOREACH ident IN LPAREN expression RPAREN statement'
+ 'statement_except_hashif : FOREACH ident_or_discard IN LPAREN expression RPAREN statement'
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'
+ 'statement_except_hashif : HASHFOREACH ident_or_discard IN LPAREN expression RPAREN statement'
t[0] = ast.hashforeach(site(t), t[2], t[5], t[7])
@prod_dml12
@@ -2564,44 +2649,55 @@ def local_decl_kind(t):
@prod
def local_one(t):
- '''local : local_decl_kind cdecl'''
- (_, tsite, name, typ) = t[2]
+ '''local : local_decl_kind cdecl_maybe_discarded'''
+ (_, tsite, name, typ) = decl = t[2]
+
assert typ
if not name:
raise ESYNTAX(tsite, ";", "variable name omitted")
- t[0] = ast.get(t[1])(site(t), [t[2]], None)
+ if t[1] in {'session', 'saved'}:
+ decl = cdecl_enforce_not_discarded(decl)
+
+ t[0] = ast.get(t[1])(site(t), [decl], None)
@prod_dml14
def saved_local_one(t):
- '''local : SAVED cdecl'''
+ '''local : SAVED cdecl_maybe_discarded'''
local_one(t)
@prod
def local_one_init(t):
- '''local : local_decl_kind cdecl EQUALS initializer'''
- (name, typ) = t[2].args
+ '''local : local_decl_kind cdecl_maybe_discarded EQUALS initializer'''
+ (_, _, name, typ) = decl = t[2]
assert typ
if not name:
raise ESYNTAX(site(t, 3), "=", "variable name omitted")
- t[0] = ast.get(t[1])(site(t), [t[2]], t[4])
+ if t[1] in {'session', 'saved'}:
+ decl = cdecl_enforce_not_discarded(decl)
+
+ t[0] = ast.get(t[1])(site(t), [decl], t[4])
@prod_dml14
def saved_local_one_init(t):
- '''local : SAVED cdecl EQUALS initializer'''
+ '''local : SAVED cdecl_maybe_discarded EQUALS initializer'''
local_one_init(t)
@prod_dml14
def local_decl_multiple(t):
- '''local : local_decl_kind LPAREN cdecl_list_nonempty RPAREN
- | SAVED LPAREN cdecl_list_nonempty RPAREN'''
- cdecl_list_enforce_named(t[3])
+ '''local : local_decl_kind LPAREN cdecl_maybe_discarded_list_nonempty RPAREN
+ | SAVED LPAREN cdecl_maybe_discarded_list_nonempty RPAREN'''
+ cdecl_maybe_discarded_list_enforce_named(t[3])
+ if t[1] in {'session', 'saved'}:
+ cdecl_maybe_discarded_list_enforce_not_discarded(t[3])
t[0] = ast.get(t[1])(site(t), t[3], None)
@prod_dml14
def local_one_multiple_init(t):
- '''local : local_decl_kind LPAREN cdecl_list_nonempty RPAREN EQUALS initializer
- | SAVED LPAREN cdecl_list_nonempty RPAREN EQUALS initializer'''
- cdecl_list_enforce_named(t[3])
+ '''local : local_decl_kind LPAREN cdecl_maybe_discarded_list_nonempty RPAREN EQUALS initializer
+ | SAVED LPAREN cdecl_maybe_discarded_list_nonempty RPAREN EQUALS initializer'''
+ cdecl_maybe_discarded_list_enforce_named(t[3])
+ if t[1] in {'session', 'saved'}:
+ cdecl_maybe_discarded_list_enforce_not_discarded(t[3])
t[0] = ast.get(t[1])(site(t), t[3], t[6])
@prod_dml14
@@ -2616,8 +2712,8 @@ def simple_array_list(t):
@prod_dml14
def hook_decl(t):
- '''hook_decl : HOOK LPAREN cdecl_list RPAREN ident simple_array_list SEMI'''
- cdecl_list_enforce_unnamed(t[3])
+ '''hook_decl : HOOK LPAREN cdecl_maybe_discarded_list RPAREN ident simple_array_list SEMI'''
+ cdecl_maybe_discarded_list_enforce_unnamed(t[3])
if t[6]:
# Hook arrays are an internal feature, as their design depends on if we
# are able to make hooks compound objects in the future
@@ -2646,20 +2742,55 @@ def objident_list(t):
# Object/parameter names may use some additional keywords for now...
@prod_dml12
-def objident(t):
- '''objident : ident
- | THIS
- | REGISTER
- | SIGNED
- | UNSIGNED'''
+def objident_base(t):
+ '''objident_base : ident
+ | THIS
+ | REGISTER
+ | SIGNED
+ | UNSIGNED'''
t[0] = t[1]
@prod_dml14
+def objident_base(t):
+ '''objident_base : ident
+ | REGISTER'''
+ t[0] = t[1]
+
+@prod
def objident(t):
- '''objident : ident
- | REGISTER'''
+ 'objident : objident_base'
t[0] = t[1]
+@prod_dml14
+def objident_discard(t):
+ 'objident : DISCARD'
+ discard_error(site(t))
+ t[0] = '__'
+
+def discard_error(site):
+ report(ESYNTAX(site,
+ "_",
+ "can't use the name '_' (the discard identifier) in this "
+ + "context. See the description of 'Identifiers' within "
+ + "the Lexical Structure section of the DML 1.4 Reference "
+ + "Manual for an overview of when '_' may be used as a "
+ + "name."))
+
+@prod
+def ident_or_discard_ident(t):
+ 'ident_or_discard : ident'
+ t[0] = ast.variable(site(t, 1), t[1])
+
+@prod_dml14
+def ident_or_discard_discard(t):
+ '''ident_or_discard : discard'''
+ t[0] = t[1]
+
+@prod_dml14
+def discard(t):
+ 'discard : DISCARD'
+ t[0] = ast.discard(site(t, 1))
+
def ident_rule(idents):
return 'ident : ' + "\n| ".join(idents)
diff --git a/py/dml/expr.py b/py/dml/expr.py
index 19253a3c5..d5a225494 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; otherwise, it must be.
+ 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
@@ -233,7 +267,13 @@ def typecheck_inargs(site, args, inp, kind="function", known_arglen=None):
if arglen != len(inp):
raise EARG(site, kind)
- for (i, (arg, (pname, ptype))) in enumerate(zip(args, inp)):
+ for (i, (arg, p)) in enumerate(zip(args, inp)):
+ if kind == 'method':
+ logref = p.logref
+ ptype = p.typ
+ else:
+ (pname, ptype) = p
+ logref = f"'{pname}'"
argtype = safe_realtype(arg.ctype())
if not argtype:
raise ICE(site, "unknown expression type")
@@ -243,9 +283,9 @@ def typecheck_inargs(site, args, inp, kind="function", known_arglen=None):
(ok, trunc, constviol) = rtype.canstore(argtype)
if ok:
if constviol:
- raise ECONSTP(site, pname, kind + " call")
+ raise ECONSTP(site, logref, kind + " call")
else:
- raise EPTYPE(site, arg, rtype, pname, kind)
+ raise EPTYPE(site, arg, rtype, logref, kind)
# Typecheck a DML method application, where the arguments are given as a list
# where each element is either an AST of an initializer, or an initializer
@@ -264,7 +304,14 @@ def typecheck_inarg_inits(site, inits, inp, location, scope,
from .ctree import Initializer, ExpressionInitializer
args = []
- for (init, (pname, ptype)) in zip(inits, inp):
+ for (init, p) in zip(inits, inp):
+ if kind == 'method':
+ logref = p.logref
+ ptype = p.typ
+ else:
+ (pname, ptype) = p
+ logref = pname
+
if isinstance(init, Initializer):
if ptype is None:
assert isinstance(init, ExpressionInitializer)
@@ -274,13 +321,13 @@ def typecheck_inarg_inits(site, inits, inp, location, scope,
arg = init.as_expr(ptype)
except EASTYPE as e:
if e.site is init.site:
- raise EPTYPE(site, e.source, e.target_type, pname,
+ raise EPTYPE(site, e.source, e.target_type, logref,
kind) from e
raise
# better error message
except EDISCONST as e:
if e.site is init.site:
- raise ECONSTP(site, pname, kind + " call") from e
+ raise ECONSTP(site, logref, kind + " call") from e
raise
elif ptype is None:
if init.kind != 'initializer_scalar':
@@ -307,22 +354,22 @@ def typecheck_inarg_inits(site, inits, inp, location, scope,
if ok:
if constviol:
- raise ECONSTP(site, pname, kind + " call")
+ raise ECONSTP(site, logref, kind + " call")
else:
- raise EPTYPE(site, arg, rtype, pname, kind)
+ raise EPTYPE(site, arg, rtype, logref, kind)
else:
try:
arg = eval_initializer(init.site, ptype, init, location,
scope, False).as_expr(ptype)
except EASTYPE as e:
if e.site is init.site:
- raise EPTYPE(site, e.source, e.target_type, pname,
+ raise EPTYPE(site, e.source, e.target_type, logref,
kind) from e
raise
# better error message
except EDISCONST as e:
if e.site is init.site:
- raise ECONSTP(site, pname, kind + " call") from e
+ raise ECONSTP(site, logref, kind + " call") from e
raise
if (on_ptr_to_stack
and isinstance(safe_realtype_shallow(ptype), TPtr)
@@ -433,6 +480,6 @@ class StaticIndex(NonValue):
def __init__(self, site, var):
pass
def __str__(self):
- return dollar(self.site) + self.var
+ return dollar(self.site) + ("_" if self.var is None else self.var)
def exc(self):
- return EIDXVAR(self.site, dollar(self.site) + self.var)
+ return EIDXVAR(self.site, str(self))
diff --git a/py/dml/g_backend.py b/py/dml/g_backend.py
index 2454b897f..c4a9929d1 100644
--- a/py/dml/g_backend.py
+++ b/py/dml/g_backend.py
@@ -68,7 +68,7 @@ def en_method(node):
for f in list(node.funcs.values()):
fs.append((f.get_cname(),
f.independent,
- tuple((n, str(t)) for n, t in f.inp),
+ tuple((p.ident, str(p.typ)) for p in f.inp),
tuple((n, str(t)) for n, t in f.outp)))
return (ID_METHOD, node.name, tuple(fs))
diff --git a/py/dml/io_memory.py b/py/dml/io_memory.py
index 2314fc822..f11028824 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/messages.py b/py/dml/messages.py
index 29c45dd57..0985ebefd 100644
--- a/py/dml/messages.py
+++ b/py/dml/messages.py
@@ -53,9 +53,9 @@ def log(self):
self.method.site,
"method declaration"
+ ''.join(
- f"\nmethod parameter '{pname}' is of unserializable type: "
- + f"{ptype}"
- for (pname, ptype) in self.unserializable or []))
+ f"\nmethod parameter {p.logref} is of unserializable type: "
+ + f"{p.typ}"
+ for p in self.unserializable or []))
class EAFTERSENDNOW(DMLError):
"""
@@ -74,7 +74,7 @@ def __init__(self, site, target_hook, callback_hook, unserializable):
clarification = ("not provided through a message component parameter "
"of the 'after' " * (target_hook is not None))
unserializable_msg = (''.join(
- f"\nmessage component {idx} is of unserializable type: "
+ f"\nmessage component {idx + 1} is of unserializable type: "
+ f"{ptype}"
for (idx, ptype) in unserializable))
@@ -935,8 +935,7 @@ class EAUNKDIMSIZE(DMLError):
The size of an array dimension of an object array must be defined at least
once across all declarations of that object array.
"""
- fmt = ("the size of dimension %d (with index variable '%s') is never "
- + "defined")
+ fmt = ("the size of dimension %d%s is never defined")
class ENCONST(DMLError):
"""
@@ -1013,11 +1012,11 @@ class EARGT(DMLError):
The data type of the argument value given for the mentioned method
parameter differs from the method definition.
"""
- fmt = ("wrong type in %s parameter '%s' when %s '%s'\n"
+ fmt = ("wrong type in %s parameter %s when %s '%s'\n"
"got: '%s'\n"
"expected: '%s'")
def __init__(self, site, invocation_type, method_name,
- got_type, pname, ptype, direction):
+ got_type, pref, ptype, direction):
if invocation_type == 'call':
invok = "calling"
elif invocation_type == 'inline':
@@ -1025,7 +1024,7 @@ def __init__(self, site, invocation_type, method_name,
elif invocation_type == 'implement':
invok = "implementing"
DMLError.__init__(self, site,
- direction, pname, invok, method_name,
+ direction, pref, invok, method_name,
got_type, ptype)
class ENARGT(DMLError):
@@ -1033,9 +1032,9 @@ class ENARGT(DMLError):
Methods that are called must have data type declarations for all
their parameters. (Methods that are only inlined do not need this.)
"""
- fmt = "no type for %s parameter '%s'"
- def __init__(self, site, pname, direction, callsite = None):
- DMLError.__init__(self, site, direction, pname)
+ fmt = "no type for %s parameter %s"
+ def __init__(self, site, pref, direction, callsite = None):
+ DMLError.__init__(self, site, direction, pref)
self.callsite = callsite
def log(self):
DMLError.log(self)
@@ -1050,8 +1049,8 @@ class EPTYPE(DMLError):
fmt = ("wrong type for parameter %s in %s call\n"
"got: %s\n"
"expected: %s")
- def __init__(self, site, arg, ptype, argname, kind):
- DMLError.__init__(self, site, argname, kind, arg.ctype(), ptype)
+ def __init__(self, site, arg, ptype, pref, kind):
+ DMLError.__init__(self, site, pref, kind, arg.ctype(), ptype)
class ENAMECOLL(DMLError):
"""
@@ -1893,6 +1892,16 @@ class EPRAGMA(DMLError):
"""
fmt = "Unknown pragma: %s"
+class EDISCARDREF(DMLError):
+ """
+ The expression *`_`* resolves to the [discard
+ reference](language.html#discard-reference), and can only be used as an
+ assignment target, in order to e.g. throw away return values of a function.
+ """
+ version = "1.4"
+ fmt = ("'_' can only be used as an assignment target "
+ + "(to discard some value)")
+
#
# WARNINGS (keep these as few as possible)
#
diff --git a/py/dml/objects.py b/py/dml/objects.py
index 1ed20c263..3ac5fc089 100644
--- a/py/dml/objects.py
+++ b/py/dml/objects.py
@@ -268,7 +268,8 @@ def logname(self, indices=(), relative='device'):
if self.isindexed():
suff = "".join('[%s]' % i for i in
indices[-self.local_dimensions():])
- suff += "".join(f'[{dollar(self.site)}{idxvar}]'
+ suff += "".join(f'[{dollar(self.site)}'
+ + f'{"_" if idxvar is None else idxvar}]'
for idxvar in self._idxvars[len(indices):])
indices = indices[:-self.local_dimensions()]
else:
@@ -283,8 +284,8 @@ def logname_anonymized(self, indices=(), relative='device'):
if self.isindexed():
suff = "".join('[%s]' % i for i in
indices[-self.local_dimensions():])
- suff += "".join('[$%s]' % idxvar for idxvar in
- self._idxvars[len(indices):])
+ suff += "".join('[$%s]' % ('_' if idxvar is None else idxvar,)
+ for idxvar in self._idxvars[len(indices):])
indices = indices[:-self.local_dimensions()]
else:
suff = ''
@@ -295,7 +296,8 @@ def identity(self, indices=(), relative='device'):
if self.isindexed():
suff = "".join('[%s]' % i for i in
indices[-self.local_dimensions():])
- suff += "".join(f'[{dollar(self.site)}{idxvar} < {arrlen}]'
+ suff += "".join(f'[{dollar(self.site)}'
+ + f'{"_" if idxvar is None else idxvar} < {arrlen}]'
for (idxvar, arrlen) in
itertools.islice(
zip(self._idxvars, self._arraylens),
@@ -590,7 +592,8 @@ def __init__(self, name, site, parent, inp, outp, throws, independent,
self.memoized = memoized
self.astcode = astcode
# A flag indicating whether all parameters have types
- self.fully_typed = all(t for p, t in self.inp + self.outp)
+ self.fully_typed = (all(not p.inlined for p in self.inp)
+ and all(t for _, t in self.outp))
# MethodDefault instance
self.default_method = default_method
self.rbrace_site = rbrace_site
diff --git a/py/dml/serialize.py b/py/dml/serialize.py
index 8436e0e3d..4742a83d9 100644
--- a/py/dml/serialize.py
+++ b/py/dml/serialize.py
@@ -99,6 +99,25 @@ def prepare_array_de_serialization(site, t):
dimsizes_expr = expr.mkLit(site, dimsizes_lit, TPtr(TInt(32, False)))
return (base, dims, sizeof_base, dimsizes_expr)
+def mkSubRefLit(site, expr, sub, typ, op):
+ real_etype = safe_realtype_shallow(expr.ctype())
+
+ if isinstance(real_etype, TPtr):
+ if op == '.':
+ raise ENOSTRUCT(site, expr)
+ basetype = real_etype.base
+ real_basetype = safe_realtype(basetype)
+ else:
+ if op == '->':
+ raise ENOPTR(site, expr)
+ real_basetype = safe_realtype(etype)
+
+ real_basetype = real_basetype.resolve()
+
+ return ctree.StructMember(site, expr, sub,
+ conv_const(real_basetype.const, typ), op)
+
+
# This works on the assumption that args do not need to be hard-cast
# to fit the actual fun signature
def apply_c_fun(site, fun, args, rettype):
@@ -117,8 +136,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"
@@ -134,7 +153,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))])
@@ -165,15 +184,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)))
@@ -181,8 +200,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 objects.Device.hooks
@@ -190,8 +209,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
@@ -203,11 +222,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
@@ -224,7 +244,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))
@@ -238,7 +258,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,
@@ -254,8 +274,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):
@@ -348,7 +373,7 @@ def map_dmltype_to_attrtype(site, dmltype):
return 'f'
if isinstance(real_type, TStruct):
return '[%s]' % "".join([map_dmltype_to_attrtype(site, mt)
- for mt in real_type.members.values()])
+ for (_, mt) in real_type.members])
if isinstance(real_type, TArray):
assert real_type.size.constant
arr_attr_type = map_dmltype_to_attrtype(site, real_type.base)
@@ -369,7 +394,7 @@ def mark_for_serialization(site, dmltype):
'''
real_type = safe_realtype(dmltype)
if isinstance(real_type, TStruct):
- for mt in real_type.members.values():
+ for (_, mt) in real_type.members:
mark_for_serialization(site, mt)
elif isinstance(real_type, TArray):
# Can only serialize constant-size arrays
@@ -443,7 +468,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)
@@ -458,7 +483,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),
@@ -490,9 +515,12 @@ def generate_serialize(real_type):
in_arg_decl.toc()
out_arg_decl.toc()
if isinstance(real_type, TStruct):
- sources = ((ctree.mkSubRef(site, in_arg, name, "->"),
- safe_realtype(typ))
- for (name, typ) in real_type.members.items())
+ sources = (
+ (mkSubRefLit(
+ site, in_arg, name or TStruct.anon_member_cident(i),
+ typ, "->"),
+ safe_realtype(typ))
+ for (i, (name, typ)) in enumerate(real_type.members))
serialize_sources_to_list(site, sources, out_arg)
elif isinstance(real_type, TVector):
raise ICE(site, "TODO: serialize vector")
@@ -518,7 +546,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)
@@ -536,7 +564,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:
@@ -613,17 +641,20 @@ def error_out(exc, msg):
else ctree.mkCast(site, tmp_out_ref, TPtr(void)))
cleanup.append(ctree.mkDelete(site, cleanup_ref))
tmp_out_decl.toc()
- targets = tuple((ctree.mkSubRef(site, tmp_out_ref, name, "->"),
- conv_const(real_type.const, safe_realtype(typ)))
- for (name, typ) in real_type.members.items())
+ targets = tuple(
+ (mkSubRefLit(
+ site, tmp_out_ref,
+ name or TStruct.anon_member_cident(i), typ, "->"),
+ conv_const(real_type.const, safe_realtype(typ)))
+ for (i, (name, typ)) in enumerate(real_type.members))
def error_out_at_index(_i, exc, msg):
return error_out(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/structure.py b/py/dml/structure.py
index c605fa601..b842779b0 100644
--- a/py/dml/structure.py
+++ b/py/dml/structure.py
@@ -325,7 +325,7 @@ def type_deps(t, include_structs, expanded_typedefs):
deps = []
if include_structs:
deps.append(t.label)
- for (mn, mt) in t.members.items():
+ for (_, mt) in t.members:
deps.extend(type_deps(mt, True, expanded_typedefs))
return deps
elif isinstance(t, TArray):
@@ -727,7 +727,7 @@ def typecheck_method_override(m1, m2, location):
raise EMETH(m1.site, m2.site, "different number of input parameters")
if len(outp1) != len(outp2):
raise EMETH(m1.site, m2.site, "different number of output parameters")
- for (a1, a2) in zip(inp1, inp2):
+ for (idx, (a1, a2)) in enumerate(zip(inp1, inp2)):
((n1, t1), (n2, t2)) = (a1.args, a2.args)
if (t1 is None) != (t2 is None):
if dml.globals.dml_version == (1, 2):
@@ -741,7 +741,10 @@ def typecheck_method_override(m1, m2, location):
# parameter
pass
else:
- report(PINARGTYPE(a1.site, type2.declaration(n1)))
+ # Not that we really EXPECT the discard identifier here
+ ident = n1.args[0] if n1.kind == 'variable' else '_'
+
+ report(PINARGTYPE(a1.site, type2.declaration(ident)))
else:
raise EMETH(m1.site, m2.site, "different inline args")
if (t1 and t2
@@ -757,8 +760,10 @@ def typecheck_method_override(m1, m2, location):
if compat.lenient_typechecking in dml.globals.enabled_compat
else type1.eq(type2))
if not ok:
+ ref = f"'{n1.args[0]}'" if n1.kind == 'variable' else (idx + 1)
+
raise EMETH(a1.site, a2.site,
- f"mismatching types in input argument {n1}")
+ f"mismatching types in input argument {ref}")
for (i, (a1, a2)) in enumerate(zip(outp1, outp2)):
if a1.site.dml_version() != (1, 2) and a2.site.dml_version() != (1, 2):
@@ -849,7 +854,10 @@ def merge_subobj_defs(def1, def2, parent):
parent_scope = Location(parent, static_indices(parent))
for ((idxvar1, len1), (idxvar2, len2)) in zip(arrayinfo, arrayinfo2):
- if idxvar1 != idxvar2:
+ if idxvar1.kind == 'discard':
+ idxvar1 = idxvar2
+ elif (idxvar2.kind != 'discard'
+ and idxvar1.args[0] != idxvar2.args[0]):
raise EAINCOMP(site1, site2, name,
"mismatching index variables")
@@ -1012,10 +1020,10 @@ def mkobj(ident, objtype, arrayinfo, obj_specs, parent, each_stmts):
# rank, so that's a relevant site to pick.
site = obj_specs[0].site
- (index_vars, arraylen_asts) = list(zip(*arrayinfo)) or ((), ())
+ (index_var_asts, arraylen_asts) = list(zip(*arrayinfo)) or ((), ())
obj = create_object(site, ident, objtype, parent,
- arraylen_asts, index_vars)
+ arraylen_asts, index_var_asts)
num_elems = functools.reduce(operator.mul, obj.dimsizes, 1)
if num_elems >= 1 << 31:
raise EASZLARGE(site, num_elems)
@@ -1023,15 +1031,16 @@ def mkobj(ident, objtype, arrayinfo, obj_specs, parent, each_stmts):
with ErrorContext(obj):
(obj_specs, used_templates) = add_templates(obj_specs, each_stmts)
obj.templates = used_templates
- index_sites = [ast.site for ast in arraylen_asts]
- obj_params = create_parameters(obj, obj_specs, index_vars, index_sites)
+ obj_params = create_parameters(obj, obj_specs, index_var_asts)
return mkobj2(obj, obj_specs, obj_params, each_stmts)
def create_object(site, ident, objtype, parent,
- arraylen_asts, index_vars):
+ arraylen_asts, index_var_asts):
array_lens = tuple(
eval_arraylen(len_ast, Location(parent, static_indices(parent)))
for len_ast in arraylen_asts)
+ index_vars = tuple(var.args[0] if var.kind == 'variable' else None
+ for var in index_var_asts)
if objtype == 'device':
assert not arraylen_asts
@@ -1068,7 +1077,7 @@ def create_object(site, ident, objtype, parent,
raise ICE(site, "unknown object type %s" % (objtype,))
-def make_autoparams(obj, index_vars, index_var_sites):
+def make_autoparams(obj, index_var_asts):
site = obj.site
autoparams = {}
@@ -1083,14 +1092,16 @@ def make_autoparams(obj, index_vars, index_var_sites):
index_params = ()
# Handle array information
- for (dim, (index_var, var_site)) in enumerate(
- zip(index_vars, index_var_sites)):
- idx_param = IndexParamExpr(var_site, obj.parent.dimensions + dim,
- index_var)
- index_params += (idx_param,)
- # This will refer to the index coupled with the idxvar,
- # innermost overrides
- autoparams[index_var] = idx_param
+ for (dim, index_var) in enumerate(index_var_asts):
+ index_param = IndexParamExpr(index_var.site,
+ obj.parent.dimensions + dim,
+ index_var.args[0]
+ if index_var.kind == 'variable' else None)
+ index_params += (index_param,)
+ if index_var.kind == 'variable':
+ # This will refer to the index coupled with the idxvar,
+ # innermost overrides
+ autoparams[index_var.args[0]] = index_param
# Assign auto parameters related to array info
# In 1.4; The 'indices' auto-param is a list containing local indices
@@ -1100,16 +1111,19 @@ def make_autoparams(obj, index_vars, index_var_sites):
# The 'indexvar' auto-param is the name of index variable that the
# local index is stored in if in a simple array, undefined otherwise
if dml.globals.dml_version == (1, 2):
- if len(index_vars) == 1:
- [index_var] = index_vars
+ if len(index_var_asts) == 1:
+ ([index_var_ast], [index_param]) = (index_var_asts, index_params)
+ # TODO or maybe 'i'?
+ index_var = (index_var_ast.args[0]
+ if index_var_ast.kind == 'variable' else '')
# TODO: Add this documentation to dml.docu
# If in a multi-dimensional array, this will be set to undefined
# So in 1.2 you can verify if you are in a multi-dimensional
# array by checking if this is defined
autoparams['indexvar'] = SimpleParamExpr(
mkStringConstant(site, index_var))
- autoparams['index'] = autoparams[index_var]
- elif index_vars:
+ autoparams['index'] = index_param
+ elif index_var_asts:
autoparams['indexvar'] = SimpleParamExpr(mkUndefined(site))
autoparams['index'] = IndexListParamExpr(site, index_params)
else:
@@ -1196,14 +1210,17 @@ def make_autoparams(obj, index_vars, index_var_sites):
return autoparams
-def implicit_params(obj, index_vars):
+def implicit_params(obj, index_var_asts):
# Find index_vars collisions here
- sorted_ivars = sorted(index_vars)
- for v1, v2 in zip(sorted_ivars, sorted_ivars[1:]):
+ sorted_ivars = sorted(((var.site, var.args[0])
+ for var in index_var_asts
+ if var.kind == 'variable'),
+ key=lambda t: t[1])
+ for (s1, v1), (s2, v2) in zip(sorted_ivars, sorted_ivars[1:]):
if v1 == v2:
- report(ENAMECOLL(obj.site, obj.site, v1))
- params = [ast.param(obj.site, var, ast.auto(obj.site), False, None)
- for var in index_vars]
+ report(ENAMECOLL(s2, s1, v1))
+ params = [ast.param(var.site, var.args[0], ast.auto(var.site), False, None)
+ for var in index_var_asts if var.kind == 'variable']
if (dml.globals.dml_version == (1, 2)
and obj.objtype == 'field'
@@ -1215,7 +1232,7 @@ def implicit_params(obj, index_vars):
ast.param(obj.site, 'lsb', None, False, ast.int(site, 0))])
return params
-def create_parameters(obj, obj_specs, index_vars, index_sites):
+def create_parameters(obj, obj_specs, index_var_asts):
'''Merge parameter ASTs and convert to Parameter objects'''
# "automatic" parameters are declared 'parameter xyz auto;' in
@@ -1227,14 +1244,14 @@ def create_parameters(obj, obj_specs, index_vars, index_sites):
''))
# map parameter name -> list of (Rank, ast.param object)
parameters = {param.args[0]: [(implicit_rank, param)]
- for param in implicit_params(obj, index_vars)}
+ for param in implicit_params(obj, index_var_asts)}
for obj_spec in obj_specs:
for s in obj_spec.params:
assert s.kind == 'param'
(name, _, _, _) = s.args
parameters.setdefault(name, []).append((obj_spec.rank, s))
- autoparams = make_autoparams(obj, index_vars, index_sites)
+ autoparams = make_autoparams(obj, index_var_asts)
for name in autoparams:
assert name in parameters, name
return [mkparam(obj, autoparams,
@@ -1378,6 +1395,9 @@ def report_pbefaft(obj, method_asts):
# find name of 'value' arg
(_, _, _, value_cdecl) = bef_inp
(value_arg, _) = value_cdecl.args
+ assert value_arg.kind == 'variable'
+ (value_arg,) = value_arg.args
+
method_decl = method_decl.replace('value', value_arg)
default_call = default_call.replace('value', value_arg)
report(PBEFAFT(bef.site, dmlparse.start_site(bef_body.site),
@@ -1709,7 +1729,9 @@ def mkobj2(obj, obj_specs, params, each_stmts):
for (_, _, arrayinfo, specs) in subobj_defs.values():
for (i, (idx, dimsize_ast)) in enumerate(arrayinfo):
if dimsize_ast is None:
- report(EAUNKDIMSIZE(specs[0].site, i, idx))
+ idxref = (f" (with index variable '{idx.args[0]}')"
+ if idx.kind == 'variable' else "")
+ report(EAUNKDIMSIZE(specs[0].site, i, idxref))
arrayinfo[i] = (idx, ast.int(specs[0].site, 1))
explicit_traits = Set(t for (_, t) in obj_traits)
@@ -1933,8 +1955,8 @@ def mkobj2(obj, obj_specs, params, each_stmts):
(tsite, tinp, toutp, tthrows, tindep, tstartup, tmemod) \
= vtable_trait.vtable_methods[member]
if not override.fully_typed:
- for (n, t) in override.inp:
- if not t:
+ for p in override.inp:
+ if p.inlined:
raise EMETH(
override.site, tsite,
'input argument declared without a type')
@@ -2050,7 +2072,7 @@ def mkobj2(obj, obj_specs, params, each_stmts):
# implicitly added above. Needed when importing 1.4
# code from 1.2 with --no-compat=dml12_misc
dml.globals.dml_version != (1, 2)
- or p.site.dml_version == (1, 2)
+ or p.site.dml_version() == (1, 2)
or p.site != sym.site):
report(ENAMECOLL(p.site, sym.site, p.name))
@@ -2895,7 +2917,8 @@ def port_builtin_method_overrides(name, site, inp_ast, parent_obj):
for (old_idx, new_type) in args:
if isinstance(old_idx, int):
old_arg = inp_ast[old_idx]
- (n, _) = old_arg.args
+ ((kind, _, n), _) = old_arg.args
+ assert kind == 'variable'
else:
n = old_idx
new_inp.append(new_type + n)
@@ -3078,7 +3101,9 @@ def mkmethod(site, rbrace_site, location, parent_obj, name, inp_ast,
outp_ast, throws, independent, startup, memoized, body, default,
default_level, template):
# check for duplicate parameter names
- named_args = inp_ast
+ named_args = [ast.cdecl(s, ident.args[0], typ)
+ for (_, s, ident, typ) in inp_ast
+ if ident.kind == 'variable']
if body.site.dml_version() == (1, 2):
named_args = named_args + outp_ast
argnames = set()
@@ -3105,7 +3130,7 @@ def mkmethod(site, rbrace_site, location, parent_obj, name, inp_ast,
inp = eval_method_inp(inp_ast, location, global_scope)
outp = eval_method_outp(outp_ast, location, global_scope)
- for (n, t) in inp + outp:
+ for t in [p.typ for p in inp] + [t for (_, t) in outp]:
if t:
check_named_types(t)
t = realtype(t)
diff --git a/py/dml/symtab.py b/py/dml/symtab.py
index 9be141c11..b136ad432 100644
--- a/py/dml/symtab.py
+++ b/py/dml/symtab.py
@@ -75,6 +75,7 @@ def __init__(self, parent=None, location=None):
global symtab_idx
symtab_idx += 1
self.idx = symtab_idx
+ self.anonymous_count = 0
self.symdict = {}
self.symlist = []
@@ -91,6 +92,8 @@ def lookup(self, name, local = False):
def add(self, sym):
if not isinstance(sym, Symbol):
raise TypeError(repr(sym) + " is not a Symbol")
+ if sym.name is None:
+ raise ICE(sym.site, "Anonymous symbol added to Symtab")
if sym.name in self.symdict:
raise ICE(sym.site, "duplicate symbol %s" % sym.name)
self.symdict[sym.name] = sym
@@ -117,6 +120,9 @@ def add_variable(self, name, type=None, init=None, site=None, stmt=False,
return sym
def unique_cname(self, name):
+ if name is None:
+ name = f'_anon_var_{self.anonymous_count}'
+ self.anonymous_count += 1
return f'v{self.idx}_{name}'
class MethodParamScope(Symtab):
diff --git a/py/dml/traits.py b/py/dml/traits.py
index 46faa85d6..cf8a68643 100644
--- a/py/dml/traits.py
+++ b/py/dml/traits.py
@@ -209,11 +209,14 @@ def downcast_path(self):
def declaration(self):
implicit_inargs = self.vtable_trait.implicit_args()
- args = ", ".join(t.declaration(n)
- for (n, t) in c_inargs(
- crep.maybe_dev_arg(self.independent)
- + implicit_inargs + list(self.inp),
- self.outp, self.throws))
+ args = ", ".join([t.declaration(n)
+ for (n, t) in (
+ crep.maybe_dev_arg(self.independent)
+ + implicit_inargs)]
+ + [p.declaration() for p in self.inp]
+ + [t.declaration(n)
+ for (n, t) in c_extra_inargs(self.outp,
+ self.throws)])
return c_rettype(self.outp, self.throws).declaration(
'%s(%s)' % (self.cname(), args))
@@ -254,8 +257,8 @@ def codegen_body(self):
else:
memoization = None
body = codegen_method(
- self.astbody.site, self.inp, self.outp, self.throws, self.independent,
- memoization, self.astbody, default,
+ self.astbody.site, self.inp, self.outp, self.throws,
+ self.independent, memoization, self.astbody, default,
Location(dml.globals.device, ()), scope, self.rbrace_site)
downcast_path = self.downcast_path()
@@ -322,8 +325,15 @@ def mktrait(site, tname, ancestors, methods, params, sessions, hooks,
del sessions[name]
bad_methods = set()
- for (name, (msite, inp, outp, throws, independent, startup, memoized, overridable,
- body, rbrace_site)) in list(methods.items()):
+ for (name, (msite, inp, outp, throws, independent, startup, memoized,
+ overridable, body, rbrace_site)) in list(methods.items()):
+ argnames = set()
+ for p in inp:
+ if p.ident:
+ if p.ident in argnames:
+ report(EARGD(msite, p.ident))
+ bad_methods.add(name)
+ argnames.add(p.ident)
for ancestor in direct_parents:
coll = ancestor.member_declaration(name)
if coll:
@@ -393,15 +403,15 @@ def typecheck_method_override(left, right):
raise EMETH(site0, site1, "different number of output arguments")
if throws0 != throws1:
raise EMETH(site0, site1, "different nothrow annotations")
- for ((n, t0), (_, t1)) in zip(inp0, inp1):
- t0 = safe_realtype_unconst(t0)
- t1 = safe_realtype_unconst(t1)
+ for (p0, p1) in zip(inp0, inp1):
+ t0 = safe_realtype_unconst(p0.typ)
+ t1 = safe_realtype_unconst(p1.typ)
ok = (t0.eq_fuzzy(t1)
if compat.lenient_typechecking in dml.globals.enabled_compat
else t0.eq(t1))
if not ok:
raise EMETH(site0, site1,
- "mismatching types in input argument %s" % (n,))
+ f"mismatching types in input argument {p0.logref}")
for (i, ((_, t0), (_, t1))) in enumerate(zip(outp0, outp1)):
t0 = safe_realtype_unconst(t0)
t1 = safe_realtype_unconst(t1)
@@ -754,7 +764,7 @@ def type(self):
def typecheck_methods(self):
for (_, inp, outp, _, _, _, _) in self.vtable_methods.values():
- for (_, t) in inp + outp:
+ for t in [p.typ for p in inp] + [t for (_, t) in outp]:
try:
check_named_types(t)
except DMLError as e:
@@ -764,7 +774,7 @@ def typecheck_methods(self):
# To avoid duplicating error messages
bad = False
if sm.name not in self.vtable_methods:
- for (_, t) in sm.inp + sm.outp:
+ for t in [p.typ for p in sm.inp] + [t for (_, t) in sm.outp]:
try:
check_named_types(t)
except DMLError as e:
@@ -911,9 +921,10 @@ def implicit_args(self):
def vtable_method_type(self, inp, outp, throws, independent):
return TPtr(TFunction(
- [t for (n, t) in c_inargs(
- crep.maybe_dev_arg(independent) + self.implicit_args() + inp,
- outp, throws)],
+ [t for (_, t) in
+ crep.maybe_dev_arg(independent) + self.implicit_args()]
+ + [p.typ for p in inp]
+ + [t for (_, t) in c_extra_inargs(outp, throws)],
c_rettype(outp, throws)))
def mark_referenced(self):
diff --git a/py/dml/types.py b/py/dml/types.py
index 0b0544321..0c9f0b3b6 100644
--- a/py/dml/types.py
+++ b/py/dml/types.py
@@ -96,7 +96,7 @@ def check_named_types(t):
raise ETYPE(t.declaration_site, t)
elif isinstance(t, StructType):
t.resolve()
- for (mn, mt) in t.members.items():
+ for (mn, mt) in t.members:
check_named_types(mt)
elif isinstance(t, (TPtr, TVector, TArray)):
check_named_types(t.base)
@@ -229,7 +229,7 @@ def deep_const(origt):
if isinstance(st, TArray):
subtypes.append(st.base)
elif isinstance(st, StructType):
- subtypes.extend(st.members.values())
+ subtypes.extend(t for (_, t) in st.members)
# TODO This should be added once the types of bitfields member are
# respected by subreferences to them (SIMICS-18394 and SIMICS-8857).
# elif st.is_int and st.is_bitfields:
@@ -540,6 +540,10 @@ def __init__(self, bits, signed, members=None, const=False):
is_arith = True
is_endian = False
+ @property
+ def named_members(self):
+ return self.members
+
@property
def is_bitfields(self):
return self.members is not None
@@ -1134,18 +1138,22 @@ def declaration(self, var):
class StructType(DMLType):
'''common superclass for DML-defined structs and extern structs'''
- __slots__ = ('members',)
- def __init__(self, members, const):
+ __slots__ = ('named_members',)
+ def __init__(self, named_members, const):
super(StructType, self).__init__(const)
- self.members = members
+ self.named_members = named_members
+
+ @property
+ def members(self):
+ yield from self.named_members.items()
@property
def members_qualified(self):
return ((name, conv_const(self.const, typ))
- for (name, typ) in self.members.items())
+ for (name, typ) in self.members)
def get_member_qualified(self, member):
- t = self.members.get(member)
+ t = self.named_members.get(member)
return t if t is None else conv_const(self.const, t)
class TExternStruct(StructType):
@@ -1155,8 +1163,8 @@ class TExternStruct(StructType):
__slots__ = ('typename', 'id')
count = 0
- def __init__(self, members, id, typename=None, const=False):
- super(TExternStruct, self).__init__(members, const)
+ def __init__(self, named_members, id, typename=None, const=False):
+ super(TExternStruct, self).__init__(named_members, const)
# unique object (wrt ==) representing this type in type comparisons
# integer for anonymous structs, string for named types
self.id = id
@@ -1165,7 +1173,7 @@ def __init__(self, members, id, typename=None, const=False):
def __repr__(self):
return 'TExternStruct(%r,%r,%r,%r)' % (
- self.members, self.id, self.typename, self.const)
+ self.named_members, self.id, self.typename, self.const)
@staticmethod
def unique_id():
@@ -1193,7 +1201,8 @@ def hashed(self):
return hash((TExternStruct, self.const, self.id))
def clone(self):
- return TExternStruct(self.members, self.id, self.typename, self.const)
+ return TExternStruct(self.named_members,
+ self.id, self.typename, self.const)
def add_late_global_struct_defs(decls):
TStruct.late_global_struct_defs.extend((site, t.resolve())
@@ -1216,7 +1225,8 @@ def __init__(self, members, label=None, const=False):
super().__init__(members, const)
def __repr__(self):
- return 'TStruct(%r,%r,%r)' % (self.members, self.label, self.const)
+ return 'TStruct(%r,%r,%r)' % (self.named_members, self.label,
+ self.const)
def key(self):
if self.anonymous:
@@ -1233,12 +1243,19 @@ def declaration(self, var):
self.const_str,
var)
+
+ @staticmethod
+ def anon_member_cident(i):
+ return f'_anon_member_{i}'
+
def print_struct_definition(self):
output.site_linemark(self.declaration_site)
out("struct %s {\n" % (cident(self.label),), postindent = 1)
- for (n, t) in self.members.items():
+ for (i, (n, t)) in enumerate(self.members):
output.site_linemark(t.declaration_site)
- t.print_declaration(n)
+ t.print_declaration(n
+ if n is not None else
+ TStruct.anon_member_cident(i))
output.site_linemark(self.declaration_site)
out("};\n", preindent = -1)
@@ -1249,10 +1266,10 @@ def hashed(self):
return hash((TStruct, self.const, self.label))
def clone(self):
- return TStruct(self.members, self.label, self.const)
+ return TStruct(self.named_members, self.label, self.const)
class TLayout(TStruct):
- __slots__= ('endian', 'member_decls', 'size')
+ __slots__= ('endian', 'member_decls', 'size', 'discarded')
def __init__(self, endian, member_decls, label=None, const=False):
# Intentionally wait with setting member types until
@@ -1261,6 +1278,7 @@ def __init__(self, endian, member_decls, label=None, const=False):
self.member_decls = member_decls
self.endian = endian
self.size = None
+ self.discarded = None
def __repr__(self):
return 'TLayout(%r, %r, %r, %r)' % (self.endian, self.member_decls,
@@ -1274,15 +1292,26 @@ def key(self):
def describe(self):
return 'layout'
+ @property
+ def members(self):
+ self.resolve()
+ for (i, member) in enumerate(self.named_members.items()):
+ if i in self.discarded:
+ yield from ((None, t) for t in self.discarded[i])
+ yield member
+ if len(self.named_members) in self.discarded:
+ yield from ((None, t)
+ for t in self.discarded[len(self.named_members)])
+
def resolve(self):
#dbg('resolve %r' % self)
- if self.members != None:
+ if self.named_members is not None:
return self
# Checks if t is a valid layout member type
# returning a sometimes patched type representing t
# and the real, resolved, type of t used for verifying sizeof
- def check_layout_member_type(site, t, name):
+ def check_layout_member_type(site, t, memberref):
rt = t
# We cannot use non-shallow instead of this loop because we need
# to keep track of when we move through arrays
@@ -1295,7 +1324,8 @@ def check_layout_member_type(site, t, name):
return t, rt
if rt.is_int:
if (rt.bits % 8) != 0:
- raise ELAYOUT(site, "size of %s is not a whole byte" % name)
+ raise ELAYOUT(site,
+ f"size of {memberref} is not a whole byte")
if (isinstance(rt, TInt)
or (dml.globals.compat_dml12_int(site)
and isinstance(rt, TSize))):
@@ -1309,18 +1339,28 @@ def check_layout_member_type(site, t, name):
# the original declaration when necessary, and one array
# that is the fully resolved type
new_base, real_base = check_layout_member_type(
- site, rt.base, name)
+ site, rt.base, memberref)
return (TArray(new_base, rt.size, rt.const),
TArray(real_base, rt.size, rt.const),)
raise ELAYOUT(site, "illegal layout member type: %s" % t)
self.size = 0
- self.members = {}
- for (m, (site, t)) in self.member_decls.items():
+ self.named_members = {}
+ self.discarded = {}
+ curr_discarded = []
+ for (i, (site, m, t)) in enumerate(self.member_decls):
try:
+ memberref = m or f"member {i + 1} (anonymous)"
# t = the member type, rt = real, resolved, underlying type
- t, rt = check_layout_member_type(site, t, m)
- self.members[m] = t
+ t, rt = check_layout_member_type(site, t, memberref)
+ if m is not None:
+ if curr_discarded:
+ self.discarded[
+ len(self.named_members)] = curr_discarded
+ curr_discarded = []
+ self.named_members[m] = t
+ else:
+ curr_discarded.append(t)
size = rt.sizeof()
if size is None:
@@ -1331,6 +1371,9 @@ def check_layout_member_type(site, t, name):
except DMLError as e:
report(e)
+ if curr_discarded:
+ self.discarded[len(self.named_members)] = curr_discarded
+
return self
def sizeof(self):
@@ -1339,8 +1382,9 @@ def sizeof(self):
def clone(self):
cloned = TLayout(self.endian, self.member_decls, self.label,
self.const)
- if self.members is not None:
- cloned.members = self.members
+ if self.named_members is not None:
+ cloned.named_members = self.named_members
+ cloned.discarded = self.discarded
cloned.size = self.size
return cloned
diff --git a/py/dml/types_test.py b/py/dml/types_test.py
index c8dc1f6e3..27970800a 100644
--- a/py/dml/types_test.py
+++ b/py/dml/types_test.py
@@ -26,7 +26,7 @@ def test(self):
TVector(typ0),
TTrait(object()),
TStruct({"name": TInt(32, False)}),
- TLayout("big-endian", {}),
+ TLayout("big-endian", []),
TDevice("a")):
typ_clone = typ.clone()
self.assertTrue(
diff --git a/test/1.2/errors/ENAMECOLL_dml14.dml b/test/1.2/errors/ENAMECOLL_dml14.dml
new file mode 100644
index 000000000..b977c2f59
--- /dev/null
+++ b/test/1.2/errors/ENAMECOLL_dml14.dml
@@ -0,0 +1,9 @@
+/*
+ © 2023 Intel Corporation
+ SPDX-License-Identifier: MPL-2.0
+*/
+dml 1.4;
+
+bank b {
+ register r4[_ < ...][_ < ...];
+}
diff --git a/test/1.2/errors/EREF_dml14.dml b/test/1.2/errors/EREF_dml14.dml
new file mode 100644
index 000000000..69cc31c9c
--- /dev/null
+++ b/test/1.2/errors/EREF_dml14.dml
@@ -0,0 +1,8 @@
+/*
+ © 2023 Intel Corporation
+ SPDX-License-Identifier: MPL-2.0
+*/
+dml 1.4;
+
+group g1[_ < 4];
+group g2[_ < 4];
diff --git a/test/1.2/errors/T_ENAMECOLL.dml b/test/1.2/errors/T_ENAMECOLL.dml
index 9987127d1..acfc6574d 100644
--- a/test/1.2/errors/T_ENAMECOLL.dml
+++ b/test/1.2/errors/T_ENAMECOLL.dml
@@ -5,6 +5,8 @@
dml 1.2;
device test;
+import "ENAMECOLL_dml14.dml";
+
/// ERROR ENAMECOLL
method bar() default { }
/// ERROR ENAMECOLL
@@ -56,7 +58,7 @@ extern t5;
port p0 {
/// ERROR ENAMECOLL
- parameter foo = 1;
+ parameter foo = 1;
/// ERROR ENAMECOLL
parameter foo = 1;
}
@@ -89,6 +91,17 @@ trait tr2 {}
/// ERROR ENAMECOLL
template tr2 {}
+bank b {
+ /// ERROR ENAMECOLL
+ register r1[4][4] size 4 @ undefined;
+ /// ERROR ENAMECOLL
+ register r2[i in 0..3][i in 0..3] size 4 @ undefined;
+ /// ERROR ENAMECOLL
+ register r3[_ in 0..3][_ in 0..3] size 4 @ undefined;
+ // no error, despite the declaration in ENAMECOLL_dml14.dml
+ register r4[_ in 0..3][j in 0..3] size 4 @ undefined;
+}
+
method init() {
// no error! (error with --no-compat=dml12_int)
local layout "little-endian" {
diff --git a/test/1.2/errors/T_EREF.dml b/test/1.2/errors/T_EREF.dml
index a19ca55f5..c45870ede 100644
--- a/test/1.2/errors/T_EREF.dml
+++ b/test/1.2/errors/T_EREF.dml
@@ -5,6 +5,8 @@
dml 1.2;
device test;
+import "EREF_dml14.dml";
+
data int x;
bank b0 {
parameter register_size = 4;
@@ -33,6 +35,10 @@ method init {
// Bug 4970, don't look up stuff in anonymous banks
/// ERROR EREF
$b1.r1;
+ /// ERROR EREF
+ $g1[1]._;
+ // no error
+ $g2[1]._;
}
@@ -55,3 +61,5 @@ bank b3 {
/// ERROR EREF
parameter p = $garbage;
+
+group g2[_ in 0..3];
diff --git a/test/1.2/operators/T_arith.dml b/test/1.2/operators/T_arith.dml
index 0379922bb..7030466ac 100644
--- a/test/1.2/operators/T_arith.dml
+++ b/test/1.2/operators/T_arith.dml
@@ -58,7 +58,7 @@ method test -> (bool ok) {
&& ((fn()!=0 && false) || true)
&& fn() == 5;
// ... but dead subexpressions are eliminated early
- false && undefined;
- true || undefined;
- true ? true : undefined;
+ ok = ok && !(false && undefined);
+ ok = ok && (true || undefined);
+ ok = ok && (true ? true : undefined);
}
diff --git a/test/1.2/operators/T_arrayref_node_nonint.dml b/test/1.2/operators/T_arrayref_node_nonint.dml
index 6b8564315..dc82beed1 100644
--- a/test/1.2/operators/T_arrayref_node_nonint.dml
+++ b/test/1.2/operators/T_arrayref_node_nonint.dml
@@ -12,6 +12,8 @@ bank b {
}
method test -> (bool b) {
- $b.r[1].size;
+ if ($b.r[1].size != 4) {
+ error "size is nonconstant or not 4";
+ }
b = true;
}
diff --git a/test/1.4/errors/T_EAINCOMP.dml b/test/1.4/errors/T_EAINCOMP.dml
index 67efd07d9..2c1aab1c0 100644
--- a/test/1.4/errors/T_EAINCOMP.dml
+++ b/test/1.4/errors/T_EAINCOMP.dml
@@ -9,3 +9,7 @@ device test;
group g[i < ...];
/// ERROR EAINCOMP
group g[j < 4];
+
+// No error
+group h[i < ...];
+group h[_ < 4];
diff --git a/test/1.4/errors/T_EARGD.dml b/test/1.4/errors/T_EARGD.dml
index cde52c6e8..2a175c7b6 100644
--- a/test/1.4/errors/T_EARGD.dml
+++ b/test/1.4/errors/T_EARGD.dml
@@ -9,3 +9,20 @@ device test;
/// ERROR EARGD
method m(int x, int x) {
}
+
+template t {
+ /// ERROR EARGD
+ shared method m(int x, int x);
+ /// ERROR EARGD
+ shared method n(int x, int x) {}
+}
+
+is t;
+
+method init() {
+ // Ensure that the bad methods do not become part of the device structure
+ /// ERROR EREF
+ this.m(1,1);
+ /// ERROR EREF
+ this.n(1,1);
+}
diff --git a/test/1.4/errors/T_ENVAL.dml b/test/1.4/errors/T_ENVAL.dml
index aa85f5d09..babc413ad 100644
--- a/test/1.4/errors/T_ENVAL.dml
+++ b/test/1.4/errors/T_ENVAL.dml
@@ -46,6 +46,14 @@ method init() {
b.r1;
/// ERROR ENVAL
b.r2.f;
+
+ // Discard identifier; not added to scope
+ local int _;
+ // ... so this resolves to the discard reference
+ /// ERROR EDISCARDREF
+ _;
+ // no error
+ _ = true;
}
// dev is not a value in 1.4. Need to capture this with a rather unnatural
diff --git a/test/1.4/errors/T_ESYNTAX_underscore.dml b/test/1.4/errors/T_ESYNTAX_underscore.dml
new file mode 100644
index 000000000..470fb3e08
--- /dev/null
+++ b/test/1.4/errors/T_ESYNTAX_underscore.dml
@@ -0,0 +1,26 @@
+/*
+ © 2023 Intel Corporation
+ SPDX-License-Identifier: MPL-2.0
+*/
+dml 1.4;
+
+device test;
+
+/// ERROR ENAMECOLL T_ESYNTAX_underscore.dml
+
+/// ERROR ESYNTAX
+group _ {
+ /// ERROR ESYNTAX
+ session int _ = 4;
+ /// ERROR ESYNTAX
+ saved int _ = 4;
+}
+
+/// ERROR ESYNTAX
+typedef int _;
+
+/// ERROR ESYNTAX
+extern typedef struct {} _;
+
+/// ERROR ESYNTAX
+extern int _;
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..64e433a97
--- /dev/null
+++ b/test/1.4/expressions/T_discard_ref.dml
@@ -0,0 +1,46 @@
+/*
+ © 2023 Intel Corporation
+ SPDX-License-Identifier: MPL-2.0
+*/
+dml 1.4;
+device test;
+
+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/serialize/T_saved_declaration.dml b/test/1.4/serialize/T_saved_declaration.dml
index 98dbb0867..f6c6b2403 100644
--- a/test/1.4/serialize/T_saved_declaration.dml
+++ b/test/1.4/serialize/T_saved_declaration.dml
@@ -10,6 +10,8 @@ device test;
import "utility.dml";
+extern int memcmp(const void *s1, const void *s2, size_t n);
+
// Simple types
saved int saved_int = 2;
@@ -108,6 +110,13 @@ typedef layout "little-endian" {
saved layout "big-endian" {
int32 i32;
inner_layout_t il;
+ // It's important that the values of members with the discard identifier
+ // are preserved.
+ // Imagine reading a packet from software, checkpointing, and then
+ // propagating that packet elsewere; the device may not care about a
+ // particular field, but it may still matter for what it interacts with.
+ uint32 _;
+ int16 _;
bitfields 24 {
uint24 f @ [23:0];
} b24;
@@ -323,6 +332,10 @@ attribute test_later is write_only_attr {
assert saved_layout.i32 == -5;
assert saved_layout.il.i == 0xFF0FF00F0;
assert saved_layout.b24 == 0xFF00F0;
+ local uint32_be_t expected_1 = 17;
+ assert memcmp(cast(&saved_layout, char *) + 11, &expected_1, 4) == 0;
+ local int16_be_t expected_2 = -13;
+ assert memcmp(cast(&saved_layout, char *) + 15, &expected_2, 2) == 0;
assert p_saved1.v == -5;
assert p_saved2.v == 5;
diff --git a/test/1.4/serialize/T_saved_declaration.py b/test/1.4/serialize/T_saved_declaration.py
index cf9901c22..96c8b3321 100644
--- a/test/1.4/serialize/T_saved_declaration.py
+++ b/test/1.4/serialize/T_saved_declaration.py
@@ -79,8 +79,8 @@
stest.expect_equal(obj.saved_uint48_be, 0xF00F00F00)
obj.saved_bitfields = 0xF0F0FF
stest.expect_equal(obj.saved_bitfields, 0xF0F0FF)
-obj.saved_layout = [-5, [0xFF0FF00F0], 0xFF00F0]
-stest.expect_equal(obj.saved_layout, [-5, [0xFF0FF00F0], 0xFF00F0])
+obj.saved_layout = [-5, [0xFF0FF00F0], 17, -13, 0xFF00F0]
+stest.expect_equal(obj.saved_layout, [-5, [0xFF0FF00F0], 17, -13, 0xFF00F0])
obj.port.p_saved1.v = -5
stest.expect_equal(obj.port.p_saved1.v, -5)
obj.port.p_saved2.v = 5
diff --git a/test/1.4/syntax/T_discard_ident.dml b/test/1.4/syntax/T_discard_ident.dml
new file mode 100644
index 000000000..9bc76cff8
--- /dev/null
+++ b/test/1.4/syntax/T_discard_ident.dml
@@ -0,0 +1,98 @@
+/*
+ © 2023 Intel Corporation
+ SPDX-License-Identifier: MPL-2.0
+*/
+dml 1.4;
+device test;
+
+typedef struct {
+ int x;
+} new_int_t;
+
+typedef layout "little-endian" {
+ uint32 _;
+ uint32 x;
+ layout "little-endian" {
+ int48 _;
+ } _;
+ int16 _;
+ int16 y;
+ uint24 _;
+} layout_t;
+
+session int count;
+method inc() -> (new_int_t) {
+ return {++count};
+}
+
+method m1(int _) -> (int) {
+ // to verify _ is not added to scope
+ _ = inc();
+ return 1;
+}
+
+template t {
+ shared method m3(int _, bool _) {
+ _ = inc();
+ }
+}
+
+is t;
+
+method m2(int _, bool _) -> (int, bool) {
+ _ = inc();
+ return (1, true);
+}
+
+method callback() {
+ ++count;
+}
+
+hook(int, bool) h;
+
+method init() {
+ assert count == 0;
+ local int _ = m1(2);
+ assert count == 1;
+ local (int _, bool _) = m2(2, true);
+ assert count == 2;
+ m3(2, true);
+ assert count == 3;
+
+ after h -> (_, _): callback();
+ assert count == 3;
+ h.send_now(2, true);
+ assert count == 4;
+
+ #foreach _ in ([1]) {
+ _ = inc();
+ }
+ assert count == 5;
+
+ #select _ in ([1]) where (true) {
+ _ = inc();
+ } #else assert false;
+ assert count == 6;
+
+ foreach _ in (each bank in (dev)) {
+ _ = inc();
+ }
+ assert count == 7;
+ for (local uint32 i = 0; i < g.len; ++i) {
+ for (local uint32 j = 0; i < g[0].len; ++i) {
+ assert g[i][j].indices[0] == i;
+ assert g[i][j].indices[1] == j;
+ }
+ }
+
+ // no error
+ local layout_t l = { .x = 4, .y = 17 };
+ l = { 0, 4, {0}, 0, 17, 0 };
+ assert sizeoftype(layout_t) == 21;
+ assert cast(&l.x, char *) - cast(&l, char *) == 4;
+ assert cast(&l.y, char *) - cast(&l, char *) == 16;
+}
+
+bank b;
+
+group g[_ < 2][_ < 3];