Skip to content

Commit 39698fe

Browse files
committed
Discard reference -- SIMICS-21584
1 parent 74f9e3e commit 39698fe

File tree

7 files changed

+185
-50
lines changed

7 files changed

+185
-50
lines changed

RELEASENOTES-1.4.docu

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,4 +521,14 @@
521521
This fix is only enabled by default with Simics API version 7 or above.
522522
With version 6 or below it must be explicitly enabled by passing
523523
<tt>--no-compat=shared_logs_on_device</tt> to DMLC.</add-note></build-id>
524+
<build-id value="next"><add-note> Added the <em>discard reference</em>
525+
'<tt>_</tt>' &mdash; a non-value expression which may be used as an assign
526+
target in order to explictly discard the result of an evaluated expression
527+
or return value of a method call <bug number="SIMICS-21584"/>.
528+
Example usage:
529+
<pre>
530+
_ = any_expression;
531+
_ = throwing_method();
532+
(_, x, _) = method_with_multiple_return_values();
533+
</pre></add-note></build-id>
524534
</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
@@ -3122,12 +3122,7 @@ def generate_startup_trait_calls(data, idxvars):
31223122
ref = ObjTraitRef(site, node, trait, indices)
31233123
out(f'_tref = {ref.read()};\n')
31243124
for method in trait_methods:
3125-
outargs = [mkLit(method.site,
3126-
('*((%s) {0})'
3127-
% ((TArray(t, mkIntegerLiteral(method.site, 1))
3128-
.declaration('')),)),
3129-
t)
3130-
for (_, t) in method.outp]
3125+
outargs = [mkDiscardRef(method.site) for _ in method.outp]
31313126

31323127
method_ref = TraitMethodDirect(
31333128
method.site, mkLit(method.site, '_tref', TTrait(trait)), method)
@@ -3139,12 +3134,7 @@ def generate_startup_trait_calls(data, idxvars):
31393134
def generate_startup_regular_call(method, idxvars):
31403135
site = method.site
31413136
indices = tuple(mkLit(site, idx, TInt(32, False)) for idx in idxvars)
3142-
outargs = [mkLit(site,
3143-
('*((%s) {0})'
3144-
% ((TArray(t, mkIntegerLiteral(site, 1))
3145-
.declaration('')),)),
3146-
t)
3147-
for (_, t) in method.outp]
3137+
outargs = [mkDiscardRef(method.site) for _ in method.outp]
31483138
# startup memoized methods can throw, which is ignored during startup.
31493139
# Memoization of the throw then allows for the user to check whether
31503140
# 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

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

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

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

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

2404+
lscope = Symtab(scope)
23802405
sym = lscope.add_variable(
23812406
'tmp', type=init_typ, site=init.site, init=init,
23822407
stmt=True)
@@ -2405,22 +2430,27 @@ def stmt_assign(stmt, location, scope):
24052430

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

24262456
@statement_dispatcher
@@ -3615,7 +3645,7 @@ def codegen_inline(site, meth_node, indices, inargs, outargs,
36153645
parmtype if parmtype else arg.ctype(),
36163646
meth_node.name)
36173647
for (arg, var, (parmname, parmtype)) in zip(
3618-
outargs, outvars, meth_node.outp)]
3648+
outargs, outvars, meth_node.outp)]
36193649
exit_handler = GotoExit_dml12()
36203650
with exit_handler:
36213651
code = [codegen_statement(meth_node.astcode,
@@ -4039,15 +4069,20 @@ def copy_outarg(arg, var, parmname, parmtype, method_name):
40394069
an exception. We would be able to skip the proxy variable for
40404070
calls to non-throwing methods when arg.ctype() and parmtype are
40414071
equivalent types, but we don't do this today.'''
4042-
argtype = arg.ctype()
4043-
4044-
if not argtype:
4045-
raise ICE(arg.site, "unknown expression type")
4072+
if isinstance(arg, NonValue):
4073+
if not arg.writable:
4074+
raise arg.exc()
40464075
else:
4047-
ok, trunc, constviol = realtype(parmtype).canstore(realtype(argtype))
4048-
if not ok:
4049-
raise EARGT(arg.site, 'call', method_name,
4050-
arg.ctype(), parmname, parmtype, 'output')
4076+
argtype = arg.ctype()
4077+
4078+
if not argtype:
4079+
raise ICE(arg.site, "unknown expression type")
4080+
else:
4081+
ok, trunc, constviol = realtype(parmtype).canstore(
4082+
realtype(argtype))
4083+
if not ok:
4084+
raise EARGT(arg.site, 'call', method_name,
4085+
arg.ctype(), parmname, parmtype, 'output')
40514086

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

py/dml/ctree.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@
126126
'mkEachIn', 'EachIn',
127127
'mkBoolConstant',
128128
'mkUndefined', 'Undefined',
129+
'mkDiscardRef',
129130
'TraitParameter',
130131
'TraitSessionRef',
131132
'TraitHookRef',
@@ -1034,14 +1035,21 @@ def mkAssignStatement(site, target, init):
10341035
if not target.writable:
10351036
raise EASSIGN(site, target)
10361037

1037-
target_type = target.ctype()
1038+
if isinstance(target, NonValue):
1039+
if not isinstance(init, ExpressionInitializer):
1040+
raise EDATAINIT(target.site,
1041+
f'{target} can only be used as the target of an '
1042+
+ 'assignment if its initializer is a simple '
1043+
+ 'expression or a return value of a method call')
1044+
else:
1045+
target_type = target.ctype()
10381046

1039-
if deep_const(target_type):
1040-
raise ECONST(site)
1047+
if deep_const(target_type):
1048+
raise ECONST(site)
10411049

1042-
if isinstance(init, ExpressionInitializer):
1043-
init = ExpressionInitializer(
1044-
source_for_assignment(site, target_type, init.expr))
1050+
if isinstance(init, ExpressionInitializer):
1051+
init = ExpressionInitializer(
1052+
source_for_assignment(site, target_type, init.expr))
10451053

10461054
return AssignStatement(site, target, init)
10471055

@@ -2448,7 +2456,7 @@ class AssignOp(BinOp):
24482456
def __str__(self):
24492457
return "%s = %s" % (self.lh, self.rh)
24502458

2451-
def discard(self):
2459+
def discard(self, explicit=False):
24522460
return self.lh.write(ExpressionInitializer(self.rh))
24532461

24542462
def read(self):
@@ -3473,6 +3481,18 @@ def exc(self):
34733481

34743482
mkUndefined = Undefined
34753483

3484+
class DiscardRef(NonValue):
3485+
writable = True
3486+
3487+
def __str__(self):
3488+
return '_'
3489+
3490+
def write(self, source):
3491+
assert isinstance(source, ExpressionInitializer)
3492+
return source.expr.discard(explicit=True)
3493+
3494+
mkDiscardRef = DiscardRef
3495+
34763496
def endian_convert_expr(site, idx, endian, size):
34773497
"""Convert a bit index to little-endian (lsb=0) numbering.
34783498
@@ -4692,8 +4712,8 @@ def ctype(self):
46924712
return self.expr.ctype()
46934713
def read(self):
46944714
return self.expr.read()
4695-
def discard(self):
4696-
return self.expr.discard()
4715+
def discard(self, explicit=False):
4716+
return self.expr.discard(explicit)
46974717
# Since addressable and readable are False this may only ever be leveraged
46984718
# by DMLC for optimization purposes
46994719
@property

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)