Skip to content

Commit 5579460

Browse files
committed
Discard reference -- SIMICS-21584
1 parent df4077e commit 5579460

File tree

7 files changed

+188
-52
lines changed

7 files changed

+188
-52
lines changed

RELEASENOTES-1.4.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,3 +356,14 @@
356356
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.
357357
- `release 7 7063`
358358
- `release 6 6362`
359+
- `note 6` Added the _discard reference_ '`_`' — a non-value expression
360+
which may be used as an assign target in order to explictly discard the result
361+
of an evaluated expression or return value of a method call (fixes
362+
SIMICS-21584.)
363+
364+
Example usage:
365+
```
366+
_ = any_expression;
367+
_ = throwing_method();
368+
(_, x, _) = method_with_multiple_return_values();
369+
```

doc/1.4/language.md

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

4066+
### The Discard Reference (`_`)
4067+
```
4068+
_
4069+
```
4070+
4071+
The discard reference *`_`* is an expression without any run-time representation
4072+
that may be used as the target of an assignment in order to explicitly discard
4073+
the result of an evaluated expression or return value of a method call.
4074+
4075+
For backwards compatibility reasons, `_` is not a keyword, but instead behaves
4076+
more closely as a global identifier. What this means is that declared
4077+
identifiers (e.g. local variables) are allowed to shadow it by being named `_`.
4078+
4079+
Example usage:
4080+
```
4081+
// Evaluate an expression and explicitly discard its result.
4082+
// Can be relevant to e.g. suppress Coverity's CHECKED_RETURN checker
4083+
_ = nonthrowing_single_return_method();
4084+
4085+
// Calls to methods that throw or have multiple return values require a target
4086+
// for each return value. `_` can be used to discard return values not of
4087+
// interest.
4088+
_ = throwing_method();
4089+
(_, x, _) = method_with_multiple_return_values();
4090+
```
4091+
40664092
### New Expressions
40674093

40684094
<pre>

py/dml/c_backend.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3160,12 +3160,7 @@ def generate_startup_trait_calls(data, idxvars):
31603160
ref = ObjTraitRef(site, node, trait, indices)
31613161
out(f'_tref = {ref.read()};\n')
31623162
for method in trait_methods:
3163-
outargs = [mkLit(method.site,
3164-
('*((%s) {0})'
3165-
% ((TArray(t, mkIntegerLiteral(method.site, 1))
3166-
.declaration('')),)),
3167-
t)
3168-
for (_, t) in method.outp]
3163+
outargs = [mkDiscardRef(method.site) for _ in method.outp]
31693164

31703165
method_ref = TraitMethodDirect(
31713166
method.site, mkLit(method.site, '_tref', TTrait(trait)), method)
@@ -3177,12 +3172,7 @@ def generate_startup_trait_calls(data, idxvars):
31773172
def generate_startup_regular_call(method, idxvars):
31783173
site = method.site
31793174
indices = tuple(mkLit(site, idx, TInt(32, False)) for idx in idxvars)
3180-
outargs = [mkLit(site,
3181-
('*((%s) {0})'
3182-
% ((TArray(t, mkIntegerLiteral(site, 1))
3183-
.declaration('')),)),
3184-
t)
3185-
for (_, t) in method.outp]
3175+
outargs = [mkDiscardRef(method.site) for _ in method.outp]
31863176
# startup memoized methods can throw, which is ignored during startup.
31873177
# Memoization of the throw then allows for the user to check whether
31883178
# 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
@@ -1222,6 +1222,13 @@ def expr_variable(tree, location, scope):
12221222
if in_dev_tree:
12231223
e = in_dev_tree
12241224
if e is None:
1225+
# TODO/HACK: The discard ref is exposed like this to allow it to be as
1226+
# keyword-like as possible while still allowing it to be shadowed.
1227+
# Once we remove support for discard_ref_shadowing the discard ref
1228+
# should become a proper keyword and its codegen be done via dedicated
1229+
# dispatch
1230+
if name == '_' and tree.site.dml_version() != (1, 2):
1231+
return mkDiscardRef(tree.site)
12251232
raise EIDENT(tree.site, name)
12261233
return e
12271234

@@ -2331,14 +2338,25 @@ def try_codegen_invocation(site, init_ast, outargs, location, scope):
23312338
else:
23322339
return common_inline(site, meth_node, indices, inargs, outargs)
23332340

2341+
def codegen_src_for_nonvalue_target(site, tgt, src_ast, location, scope):
2342+
if not tgt.writable:
2343+
raise EASSIGN(site, tgt)
2344+
if src_ast.kind != 'initializer_scalar':
2345+
raise EDATAINIT(tgt.site,
2346+
f'{tgt} can only be used as the target '
2347+
+ 'of an assignment if its initializer is a '
2348+
+ 'simple expression or a return value of a '
2349+
+ 'method call')
2350+
return codegen_expression(src_ast.args[0], location, scope)
2351+
23342352
@statement_dispatcher
23352353
def stmt_assign(stmt, location, scope):
23362354
(_, site, tgt_ast, src_asts) = stmt
23372355
assert tgt_ast.kind in {'assign_target_chain', 'assign_target_tuple'}
2338-
tgts = [codegen_expression(ast, location, scope)
2356+
tgts = [codegen_expression_maybe_nonvalue(ast, location, scope)
23392357
for ast in tgt_ast.args[0]]
23402358
for tgt in tgts:
2341-
if deep_const(tgt.ctype()):
2359+
if not isinstance(tgt, NonValue) and deep_const(tgt.ctype()):
23422360
raise ECONST(tgt.site)
23432361
if tgt_ast.kind == 'assign_target_chain':
23442362
method_tgts = [tgts[0]]
@@ -2360,14 +2378,21 @@ def stmt_assign(stmt, location, scope):
23602378
+ f'initializer: Expected {src_asts}, got 1'))
23612379
return []
23622380

2363-
lscope = Symtab(scope)
2381+
if isinstance(tgts[-1], NonValue):
2382+
if len(tgts) != 1:
2383+
raise tgts[-1].exc()
2384+
expr = codegen_src_for_nonvalue_target(site, tgts[0], src_asts[0],
2385+
location, scope)
2386+
return [mkCopyData(site, expr, tgts[0])]
2387+
23642388
init_typ = tgts[-1].ctype()
23652389
init = eval_initializer(
23662390
tgts[-1].site, init_typ, src_asts[0], location, scope, False)
23672391

23682392
if len(tgts) == 1:
23692393
return [mkAssignStatement(tgts[0].site, tgts[0], init)]
23702394

2395+
lscope = Symtab(scope)
23712396
sym = lscope.add_variable(
23722397
'tmp', type=init_typ, site=init.site, init=init,
23732398
stmt=True)
@@ -2396,22 +2421,27 @@ def stmt_assign(stmt, location, scope):
23962421

23972422
stmts = []
23982423
lscope = Symtab(scope)
2399-
syms = []
2424+
stmt_pairs = []
24002425
for (i, (tgt, src_ast)) in enumerate(zip(tgts, src_asts)):
2401-
init = eval_initializer(site, tgt.ctype(), src_ast, location,
2402-
scope, False)
2403-
name = 'tmp%d' % (i,)
2404-
sym = lscope.add_variable(
2405-
name, type=tgt.ctype(), site=tgt.site, init=init,
2406-
stmt=True)
2407-
syms.append(sym)
2408-
2409-
stmts.extend(sym_declaration(sym) for sym in syms)
2410-
stmts.extend(
2411-
AssignStatement(
2412-
tgt.site, tgt,
2413-
ExpressionInitializer(mkLocalVariable(tgt.site, sym)))
2414-
for (tgt, sym) in zip(tgts, syms))
2426+
if isinstance(tgt, NonValue):
2427+
expr = codegen_src_for_nonvalue_target(site, tgt, src_ast,
2428+
location, scope)
2429+
stmt_pairs.append((mkCopyData(tgt.site, expr, tgt), None))
2430+
else:
2431+
init = eval_initializer(site, tgt.ctype(), src_ast, location,
2432+
scope, False)
2433+
name = 'tmp%d' % (i,)
2434+
sym = lscope.add_variable(
2435+
name, type=tgt.ctype(), site=tgt.site, init=init,
2436+
stmt=True)
2437+
write = AssignStatement(
2438+
tgt.site, tgt,
2439+
ExpressionInitializer(mkLocalVariable(tgt.site, sym)))
2440+
stmt_pairs.append((sym_declaration(sym), write))
2441+
2442+
stmts.extend(first for (first, _) in stmt_pairs)
2443+
stmts.extend(second for (_, second) in stmt_pairs
2444+
if second is not None)
24152445
return [mkCompound(site, stmts)]
24162446

24172447
@statement_dispatcher
@@ -3633,7 +3663,7 @@ def codegen_inline(site, meth_node, indices, inargs, outargs,
36333663
parmtype if parmtype else arg.ctype(),
36343664
meth_node.name)
36353665
for (arg, var, (parmname, parmtype)) in zip(
3636-
outargs, outvars, meth_node.outp)]
3666+
outargs, outvars, meth_node.outp)]
36373667
exit_handler = GotoExit_dml12()
36383668
with exit_handler:
36393669
code = [codegen_statement(meth_node.astcode,
@@ -4063,15 +4093,20 @@ def copy_outarg(arg, var, parmname, parmtype, method_name):
40634093
an exception. We would be able to skip the proxy variable for
40644094
calls to non-throwing methods when arg.ctype() and parmtype are
40654095
equivalent types, but we don't do this today.'''
4066-
argtype = arg.ctype()
4067-
4068-
if not argtype:
4069-
raise ICE(arg.site, "unknown expression type")
4096+
if isinstance(arg, NonValue):
4097+
if not arg.writable:
4098+
raise arg.exc()
40704099
else:
4071-
ok, trunc, constviol = realtype(parmtype).canstore(realtype(argtype))
4072-
if not ok:
4073-
raise EARGT(arg.site, 'call', method_name,
4074-
arg.ctype(), parmname, parmtype, 'output')
4100+
argtype = arg.ctype()
4101+
4102+
if not argtype:
4103+
raise ICE(arg.site, "unknown expression type")
4104+
else:
4105+
ok, trunc, constviol = realtype(parmtype).canstore(
4106+
realtype(argtype))
4107+
if not ok:
4108+
raise EARGT(arg.site, 'call', method_name,
4109+
arg.ctype(), parmname, parmtype, 'output')
40754110

40764111
return mkCopyData(var.site, var, arg)
40774112

py/dml/ctree.py

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
'mkEachIn', 'EachIn',
138138
'mkBoolConstant',
139139
'mkUndefined', 'Undefined',
140+
'mkDiscardRef',
140141
'TraitParameter',
141142
'TraitSessionRef',
142143
'TraitHookRef',
@@ -1127,14 +1128,21 @@ def mkAssignStatement(site, target, init):
11271128
if not target.writable:
11281129
raise EASSIGN(site, target)
11291130

1130-
target_type = target.ctype()
1131+
if isinstance(target, NonValue):
1132+
if not isinstance(init, ExpressionInitializer):
1133+
raise EDATAINIT(target.site,
1134+
f'{target} can only be used as the target of an '
1135+
+ 'assignment if its initializer is a simple '
1136+
+ 'expression or a return value of a method call')
1137+
else:
1138+
target_type = target.ctype()
11311139

1132-
if deep_const(target_type):
1133-
raise ECONST(site)
1140+
if deep_const(target_type):
1141+
raise ECONST(site)
11341142

1135-
if isinstance(init, ExpressionInitializer):
1136-
init = ExpressionInitializer(
1137-
source_for_assignment(site, target_type, init.expr))
1143+
if isinstance(init, ExpressionInitializer):
1144+
init = ExpressionInitializer(
1145+
source_for_assignment(site, target_type, init.expr))
11381146

11391147
return AssignStatement(site, target, init)
11401148

@@ -2552,7 +2560,7 @@ class AssignOp(BinOp):
25522560
def __str__(self):
25532561
return "%s = %s" % (self.lh, self.rh)
25542562

2555-
def discard(self):
2563+
def discard(self, explicit=False):
25562564
return self.lh.write(ExpressionInitializer(self.rh))
25572565

25582566
def read(self):
@@ -3577,6 +3585,18 @@ def exc(self):
35773585

35783586
mkUndefined = Undefined
35793587

3588+
class DiscardRef(NonValue):
3589+
writable = True
3590+
3591+
def __str__(self):
3592+
return '_'
3593+
3594+
def write(self, source):
3595+
assert isinstance(source, ExpressionInitializer)
3596+
return source.expr.discard(explicit=True)
3597+
3598+
mkDiscardRef = DiscardRef
3599+
35803600
def endian_convert_expr(site, idx, endian, size):
35813601
"""Convert a bit index to little-endian (lsb=0) numbering.
35823602
@@ -5026,8 +5046,8 @@ def ctype(self):
50265046
return self.expr.ctype()
50275047
def read(self):
50285048
return self.expr.read()
5029-
def discard(self):
5030-
return self.expr.discard()
5049+
def discard(self, explicit=False):
5050+
return self.expr.discard(explicit)
50315051
def incref(self):
50325052
self.expr.incref()
50335053
def decref(self):
@@ -5234,15 +5254,15 @@ def assign_to(self, dest, typ):
52345254
rt = safe_realtype_shallow(typ)
52355255
# There is a reasonable implementation for this case (memcpy), but it
52365256
# never occurs today
5237-
assert not isinstance(typ, TArray)
5257+
assert not isinstance(rt, TArray)
52385258
if isinstance(rt, TEndianInt):
52395259
return (f'{rt.dmllib_fun("copy")}((void *)&{dest},'
52405260
+ f' {self.expr.read()})')
52415261
elif deep_const(typ):
52425262
shallow_deconst_typ = safe_realtype_unconst(typ)
52435263
# a const-qualified ExternStruct can be leveraged by the user as a
52445264
# sign that there is some const-qualified member unknown to DMLC
5245-
if (isinstance(typ, TExternStruct)
5265+
if (isinstance(shallow_deconst_typ, TExternStruct)
52465266
or deep_const(shallow_deconst_typ)):
52475267
# Expression statement to delimit lifetime of compound literal
52485268
# 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)