Skip to content

Commit dce32e4

Browse files
committed
Discard reference -- SIMICS-21584
1 parent 480dbcf commit dce32e4

File tree

7 files changed

+187
-52
lines changed

7 files changed

+187
-52
lines changed

RELEASENOTES-1.4.docu

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,4 +523,14 @@
523523
<tt>--no-compat=shared_logs_on_device</tt> to DMLC.</add-note></build-id>
524524
<build-id _6="next" _7="next"><add-note> Improved the run-time performance
525525
of accesses to registers with many fields.</add-note></build-id>
526+
<build-id _6="next" _7="next"><add-note> Added the <em>discard reference</em>
527+
'<tt>_</tt>' &mdash; a non-value expression which may be used as an assign
528+
target in order to explictly discard the result of an evaluated expression
529+
or return value of a method call <bug number="SIMICS-21584"/>.
530+
Example usage:
531+
<pre>
532+
_ = any_expression;
533+
_ = throwing_method();
534+
(_, x, _) = method_with_multiple_return_values();
535+
</pre></add-note></build-id>
526536
</rn>

doc/1.4/language.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3808,6 +3808,32 @@ independent method callback(int i, void *aux) {
38083808
}
38093809
```
38103810

3811+
### The Discard Reference (`_`)
3812+
```
3813+
_
3814+
```
3815+
3816+
The discard reference *`_`* is an expression without any run-time representation
3817+
that may be used as the target of an assignment in order to explicitly discard
3818+
the result of an evaluated expression or return value of a method call.
3819+
3820+
For backwards compatibility reasons, `_` is not a keyword, but instead behaves
3821+
more closely as a global identifier. What this means is that declared
3822+
identifiers (e.g. local variables) are allowed to shadow it by being named `_`.
3823+
3824+
Example usage:
3825+
```
3826+
// Evaluate an expression and explicitly discard its result.
3827+
// Can be relevant to e.g. suppress Coverity's CHECKED_RETURN checker
3828+
_ = nonthrowing_single_return_method();
3829+
3830+
// Calls to methods that throw or have multiple return values require a target
3831+
// for each return value. `_` can be used to discard return values not of
3832+
// interest.
3833+
_ = throwing_method();
3834+
(_, x, _) = method_with_multiple_return_values();
3835+
```
3836+
38113837
### New Expressions
38123838

38133839
<pre>

py/dml/c_backend.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3116,12 +3116,7 @@ def generate_startup_trait_calls(data, idxvars):
31163116
ref = ObjTraitRef(site, node, trait, indices)
31173117
out(f'_tref = {ref.read()};\n')
31183118
for method in trait_methods:
3119-
outargs = [mkLit(method.site,
3120-
('*((%s) {0})'
3121-
% ((TArray(t, mkIntegerLiteral(method.site, 1))
3122-
.declaration('')),)),
3123-
t)
3124-
for (_, t) in method.outp]
3119+
outargs = [mkDiscardRef(method.site) for _ in method.outp]
31253120

31263121
method_ref = TraitMethodDirect(
31273122
method.site, mkLit(method.site, '_tref', TTrait(trait)), method)
@@ -3133,12 +3128,7 @@ def generate_startup_trait_calls(data, idxvars):
31333128
def generate_startup_regular_call(method, idxvars):
31343129
site = method.site
31353130
indices = tuple(mkLit(site, idx, TInt(32, False)) for idx in idxvars)
3136-
outargs = [mkLit(site,
3137-
('*((%s) {0})'
3138-
% ((TArray(t, mkIntegerLiteral(site, 1))
3139-
.declaration('')),)),
3140-
t)
3141-
for (_, t) in method.outp]
3131+
outargs = [mkDiscardRef(method.site) for _ in method.outp]
31423132
# startup memoized methods can throw, which is ignored during startup.
31433133
# Memoization of the throw then allows for the user to check whether
31443134
# or not the method did throw during startup by calling the method

py/dml/codegen.py

Lines changed: 62 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,13 @@ def expr_variable(tree, location, scope):
12071207
if in_dev_tree:
12081208
e = in_dev_tree
12091209
if e is None:
1210+
# TODO/HACK: The discard ref is exposed like this to allow it to be as
1211+
# keyword-like as possible while still allowing it to be shadowed.
1212+
# Once we remove support for discard_ref_shadowing the discard ref
1213+
# should become a proper keyword and its codegen be done via dedicated
1214+
# dispatch
1215+
if name == '_' and tree.site.dml_version() != (1, 2):
1216+
return mkDiscardRef(tree.site)
12101217
raise EIDENT(tree.site, name)
12111218
return e
12121219

@@ -2341,14 +2348,25 @@ def try_codegen_invocation(site, init_ast, outargs, location, scope):
23412348
else:
23422349
return common_inline(site, meth_node, indices, inargs, outargs)
23432350

2351+
def codegen_src_for_nonvalue_target(site, tgt, src_ast, location, scope):
2352+
if not tgt.writable:
2353+
raise EASSIGN(site, tgt)
2354+
if src_ast.kind != 'initializer_scalar':
2355+
raise EDATAINIT(tgt.site,
2356+
f'{tgt} can only be used as the target '
2357+
+ 'of an assignment if its initializer is a '
2358+
+ 'simple expression or a return value of a '
2359+
+ 'method call')
2360+
return codegen_expression(src_ast.args[0], location, scope)
2361+
23442362
@statement_dispatcher
23452363
def stmt_assign(stmt, location, scope):
23462364
(_, site, tgt_ast, src_asts) = stmt
23472365
assert tgt_ast.kind in ('assign_target_chain', 'assign_target_tuple')
2348-
tgts = [codegen_expression(ast, location, scope)
2366+
tgts = [codegen_expression_maybe_nonvalue(ast, location, scope)
23492367
for ast in tgt_ast.args[0]]
23502368
for tgt in tgts:
2351-
if deep_const(tgt.ctype()):
2369+
if not isinstance(tgt, NonValue) and deep_const(tgt.ctype()):
23522370
raise ECONST(tgt.site)
23532371
if tgt_ast.kind == 'assign_target_chain':
23542372
method_tgts = [tgts[0]]
@@ -2370,14 +2388,21 @@ def stmt_assign(stmt, location, scope):
23702388
+ f'initializer: Expected {src_asts}, got 1'))
23712389
return []
23722390

2373-
lscope = Symtab(scope)
2391+
if isinstance(tgts[-1], NonValue):
2392+
if len(tgts) != 1:
2393+
raise tgts[-1].exc()
2394+
expr = codegen_src_for_nonvalue_target(site, tgts[0], src_asts[0],
2395+
location, scope)
2396+
return [mkCopyData(site, expr, tgts[0])]
2397+
23742398
init_typ = tgts[-1].ctype()
23752399
init = eval_initializer(
23762400
tgts[-1].site, init_typ, src_asts[0], location, scope, False)
23772401

23782402
if len(tgts) == 1:
23792403
return [mkAssignStatement(tgts[0].site, tgts[0], init)]
23802404

2405+
lscope = Symtab(scope)
23812406
sym = lscope.add_variable(
23822407
'tmp', type=init_typ, site=init.site, init=init,
23832408
stmt=True)
@@ -2406,22 +2431,27 @@ def stmt_assign(stmt, location, scope):
24062431

24072432
stmts = []
24082433
lscope = Symtab(scope)
2409-
syms = []
2434+
stmt_pairs = []
24102435
for (i, (tgt, src_ast)) in enumerate(zip(tgts, src_asts)):
2411-
init = eval_initializer(site, tgt.ctype(), src_ast, location,
2412-
scope, False)
2413-
name = 'tmp%d' % (i,)
2414-
sym = lscope.add_variable(
2415-
name, type=tgt.ctype(), site=tgt.site, init=init,
2416-
stmt=True)
2417-
syms.append(sym)
2418-
2419-
stmts.extend(sym_declaration(sym) for sym in syms)
2420-
stmts.extend(
2421-
AssignStatement(
2422-
tgt.site, tgt,
2423-
ExpressionInitializer(mkLocalVariable(tgt.site, sym)))
2424-
for (tgt, sym) in zip(tgts, syms))
2436+
if isinstance(tgt, NonValue):
2437+
expr = codegen_src_for_nonvalue_target(site, tgt, src_ast,
2438+
location, scope)
2439+
stmt_pairs.append((mkCopyData(tgt.site, expr, tgt), None))
2440+
else:
2441+
init = eval_initializer(site, tgt.ctype(), src_ast, location,
2442+
scope, False)
2443+
name = 'tmp%d' % (i,)
2444+
sym = lscope.add_variable(
2445+
name, type=tgt.ctype(), site=tgt.site, init=init,
2446+
stmt=True)
2447+
write = AssignStatement(
2448+
tgt.site, tgt,
2449+
ExpressionInitializer(mkLocalVariable(tgt.site, sym)))
2450+
stmt_pairs.append((sym_declaration(sym), write))
2451+
2452+
stmts.extend(first for (first, _) in stmt_pairs)
2453+
stmts.extend(second for (_, second) in stmt_pairs
2454+
if second is not None)
24252455
return [mkCompound(site, stmts)]
24262456

24272457
@statement_dispatcher
@@ -3616,7 +3646,7 @@ def codegen_inline(site, meth_node, indices, inargs, outargs,
36163646
parmtype if parmtype else arg.ctype(),
36173647
meth_node.name)
36183648
for (arg, var, (parmname, parmtype)) in zip(
3619-
outargs, outvars, meth_node.outp)]
3649+
outargs, outvars, meth_node.outp)]
36203650
exit_handler = GotoExit_dml12()
36213651
with exit_handler:
36223652
code = [codegen_statement(meth_node.astcode,
@@ -4040,15 +4070,20 @@ def copy_outarg(arg, var, parmname, parmtype, method_name):
40404070
an exception. We would be able to skip the proxy variable for
40414071
calls to non-throwing methods when arg.ctype() and parmtype are
40424072
equivalent types, but we don't do this today.'''
4043-
argtype = arg.ctype()
4044-
4045-
if not argtype:
4046-
raise ICE(arg.site, "unknown expression type")
4073+
if isinstance(arg, NonValue):
4074+
if not arg.writable:
4075+
raise arg.exc()
40474076
else:
4048-
ok, trunc, constviol = realtype(parmtype).canstore(realtype(argtype))
4049-
if not ok:
4050-
raise EARGT(arg.site, 'call', method_name,
4051-
arg.ctype(), parmname, parmtype, 'output')
4077+
argtype = arg.ctype()
4078+
4079+
if not argtype:
4080+
raise ICE(arg.site, "unknown expression type")
4081+
else:
4082+
ok, trunc, constviol = realtype(parmtype).canstore(
4083+
realtype(argtype))
4084+
if not ok:
4085+
raise EARGT(arg.site, 'call', method_name,
4086+
arg.ctype(), parmname, parmtype, 'output')
40524087

40534088
return mkCopyData(var.site, var, arg)
40544089

py/dml/ctree.py

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@
129129
'mkEachIn', 'EachIn',
130130
'mkBoolConstant',
131131
'mkUndefined', 'Undefined',
132+
'mkDiscardRef',
132133
'TraitParameter',
133134
'TraitSessionRef',
134135
'TraitHookRef',
@@ -1100,14 +1101,21 @@ def mkAssignStatement(site, target, init):
11001101
if not target.writable:
11011102
raise EASSIGN(site, target)
11021103

1103-
target_type = target.ctype()
1104+
if isinstance(target, NonValue):
1105+
if not isinstance(init, ExpressionInitializer):
1106+
raise EDATAINIT(target.site,
1107+
f'{target} can only be used as the target of an '
1108+
+ 'assignment if its initializer is a simple '
1109+
+ 'expression or a return value of a method call')
1110+
else:
1111+
target_type = target.ctype()
11041112

1105-
if deep_const(target_type):
1106-
raise ECONST(site)
1113+
if deep_const(target_type):
1114+
raise ECONST(site)
11071115

1108-
if isinstance(init, ExpressionInitializer):
1109-
init = ExpressionInitializer(
1110-
source_for_assignment(site, target_type, init.expr))
1116+
if isinstance(init, ExpressionInitializer):
1117+
init = ExpressionInitializer(
1118+
source_for_assignment(site, target_type, init.expr))
11111119

11121120
return AssignStatement(site, target, init)
11131121

@@ -2514,7 +2522,7 @@ class AssignOp(BinOp):
25142522
def __str__(self):
25152523
return "%s = %s" % (self.lh, self.rh)
25162524

2517-
def discard(self):
2525+
def discard(self, explicit=False):
25182526
return self.lh.write(ExpressionInitializer(self.rh))
25192527

25202528
def read(self):
@@ -3539,6 +3547,18 @@ def exc(self):
35393547

35403548
mkUndefined = Undefined
35413549

3550+
class DiscardRef(NonValue):
3551+
writable = True
3552+
3553+
def __str__(self):
3554+
return '_'
3555+
3556+
def write(self, source):
3557+
assert isinstance(source, ExpressionInitializer)
3558+
return source.expr.discard(explicit=True)
3559+
3560+
mkDiscardRef = DiscardRef
3561+
35423562
def endian_convert_expr(site, idx, endian, size):
35433563
"""Convert a bit index to little-endian (lsb=0) numbering.
35443564
@@ -4758,8 +4778,8 @@ def ctype(self):
47584778
return self.expr.ctype()
47594779
def read(self):
47604780
return self.expr.read()
4761-
def discard(self):
4762-
return self.expr.discard()
4781+
def discard(self, explicit=False):
4782+
return self.expr.discard(explicit)
47634783
def incref(self):
47644784
self.expr.incref()
47654785
def decref(self):
@@ -4959,15 +4979,15 @@ def assign_to(self, dest, typ):
49594979
rt = safe_realtype_shallow(typ)
49604980
# There is a reasonable implementation for this case (memcpy), but it
49614981
# never occurs today
4962-
assert not isinstance(typ, TArray)
4982+
assert not isinstance(rt, TArray)
49634983
if isinstance(rt, TEndianInt):
49644984
return (f'{rt.dmllib_fun("copy")}((void *)&{dest},'
49654985
+ f' {self.expr.read()})')
49664986
elif deep_const(typ):
49674987
shallow_deconst_typ = safe_realtype_unconst(typ)
49684988
# a const-qualified ExternStruct can be leveraged by the user as a
49694989
# sign that there is some const-qualified member unknown to DMLC
4970-
if (isinstance(typ, TExternStruct)
4990+
if (isinstance(shallow_deconst_typ, TExternStruct)
49714991
or deep_const(shallow_deconst_typ)):
49724992
# Expression statement to delimit lifetime of compound literal
49734993
# TODO it's possible to improve the efficiency of this by not

py/dml/expr.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,16 @@ def read(self):
136136
raise ICE(self.site, "can't read %r" % self)
137137

138138
# Produce a C expression but don't worry about the value.
139-
def discard(self):
140-
return self.read()
139+
def discard(self, explicit=False):
140+
if not explicit or safe_realtype_shallow(self.ctype()).void:
141+
return self.read()
142+
143+
if self.constant:
144+
return '(void)0'
145+
from .ctree import Cast
146+
expr = (f'({self.read()})'
147+
if self.priority < Cast.priority else self.read())
148+
return f'(void){expr}'
141149

142150
def ctype(self):
143151
'''The corresponding DML type of this expression'''
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
© 2023 Intel Corporation
3+
SPDX-License-Identifier: MPL-2.0
4+
*/
5+
dml 1.4;
6+
device test;
7+
8+
header %{
9+
#define FUNCLIKE_MACRO() 4
10+
#define VARLIKE_MACRO ++counter
11+
12+
static int counter = 0;
13+
%}
14+
15+
extern int FUNCLIKE_MACRO(void);
16+
extern int VARLIKE_MACRO;
17+
extern int counter;
18+
19+
method t() -> (int) throws {
20+
return 1;
21+
}
22+
method m2() -> (int, int) {
23+
return (1, 2);
24+
}
25+
26+
method init() {
27+
local int x;
28+
// Explicit discard guarantees GCC doesn't emit -Wunused by always
29+
// void-casting, unless the expression is already void
30+
_ = x;
31+
_ = FUNCLIKE_MACRO();
32+
// Explicit discard does generate C, which evaluates the initializer
33+
assert counter == 0;
34+
_ = VARLIKE_MACRO;
35+
assert counter == 1;
36+
try
37+
_ = t();
38+
catch assert false;
39+
(x, _) = m2();
40+
assert x == 1;
41+
local int y;
42+
// Tuple initializers retain the property of each expression being
43+
// evaluated left-to-right
44+
(_, y) = (x++, x);
45+
assert y == 2;
46+
}

0 commit comments

Comments
 (0)