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 Expressionsdiff --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];