Skip to content

Commit 7c78d83

Browse files
committed
Discard reference -- SIMICS-21584
1 parent 974b985 commit 7c78d83

File tree

8 files changed

+209
-54
lines changed

8 files changed

+209
-54
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>

lib/1.4/dml-builtins.dml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1955,7 +1955,7 @@ template bank is (object, shown_desc) {
19551955
}
19561956

19571957
shared method _num_registers() -> (uint32) {
1958-
local (const register *_, uint64 table_size) = _reginfo_table();
1958+
local (const register *_table, uint64 table_size) = _reginfo_table();
19591959
return table_size;
19601960
}
19611961

py/dml/c_backend.py

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

31683163
method_ref = TraitMethodDirect(
31693164
method.site, mkLit(method.site, '_tref', TTrait(trait)), method)
@@ -3175,12 +3170,7 @@ def generate_startup_trait_calls(data, idxvars):
31753170
def generate_startup_regular_call(method, idxvars):
31763171
site = method.site
31773172
indices = tuple(mkLit(site, idx, TInt(32, False)) for idx in idxvars)
3178-
outargs = [mkLit(site,
3179-
('*((%s) {0})'
3180-
% ((TArray(t, mkIntegerLiteral(site, 1))
3181-
.declaration('')),)),
3182-
t)
3183-
for (_, t) in method.outp]
3173+
outargs = [mkDiscardRef(method.site) for _ in method.outp]
31843174
# startup memoized methods can throw, which is ignored during startup.
31853175
# Memoization of the throw then allows for the user to check whether
31863176
# or not the method did throw during startup by calling the method

py/dml/codegen.py

Lines changed: 72 additions & 30 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,26 @@ 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_init_for_untyped_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 ExpressionInitializer(
2351+
codegen_expression(src_ast.args[0], location, scope))
2352+
23342353
@statement_dispatcher
23352354
def stmt_assign(stmt, location, scope):
23362355
(_, site, tgt_ast, src_asts) = stmt
23372356
assert tgt_ast.kind in {'assign_target_chain', 'assign_target_tuple'}
2338-
tgts = [codegen_expression(ast, location, scope)
2357+
tgts = [codegen_expression_maybe_nonvalue(ast, location, scope)
23392358
for ast in tgt_ast.args[0]]
23402359
for tgt in tgts:
2341-
if deep_const(tgt.ctype()):
2360+
if not isinstance(tgt, NonValue) and deep_const(tgt.ctype()):
23422361
raise ECONST(tgt.site)
23432362
if tgt_ast.kind == 'assign_target_chain':
23442363
method_tgts = [tgts[0]]
@@ -2360,14 +2379,23 @@ def stmt_assign(stmt, location, scope):
23602379
+ f'initializer: Expected {src_asts}, got 1'))
23612380
return []
23622381

2363-
lscope = Symtab(scope)
2364-
init_typ = tgts[-1].ctype()
2365-
init = eval_initializer(
2366-
tgts[-1].site, init_typ, src_asts[0], location, scope, False)
2382+
if isinstance(tgts[-1], NonValue):
2383+
if len(tgts) != 1:
2384+
raise tgts[-1].exc()
2385+
init_typ = tgts[-1].type if tgts[-1].explicit_type else None
2386+
else:
2387+
init_typ = tgts[-1].ctype()
2388+
2389+
init = (eval_initializer(tgts[-1].site, init_typ, src_asts[0],
2390+
location, scope, False)
2391+
if init_typ is not None else
2392+
codegen_init_for_untyped_target(site, tgts[0], src_asts[0],
2393+
location, scope))
23672394

23682395
if len(tgts) == 1:
23692396
return [mkAssignStatement(tgts[0].site, tgts[0], init)]
23702397

2398+
lscope = Symtab(scope)
23712399
sym = lscope.add_variable(
23722400
'tmp', type=init_typ, site=init.site, init=init,
23732401
stmt=True)
@@ -2396,22 +2424,31 @@ def stmt_assign(stmt, location, scope):
23962424

23972425
stmts = []
23982426
lscope = Symtab(scope)
2399-
syms = []
2427+
stmt_pairs = []
24002428
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))
2429+
if isinstance(tgt, NonValue):
2430+
init = (eval_initializer(site, tgt.type, src_ast, location,
2431+
scope, False)
2432+
if tgt.explicit_type else
2433+
codegen_init_for_untyped_target(site, tgt, src_ast,
2434+
location, scope))
2435+
stmt_pairs.append((mkAssignStatement(tgt.site, tgt, init),
2436+
None))
2437+
else:
2438+
init = eval_initializer(site, tgt.ctype(), src_ast, location,
2439+
scope, False)
2440+
name = 'tmp%d' % (i,)
2441+
sym = lscope.add_variable(
2442+
name, type=tgt.ctype(), site=tgt.site, init=init,
2443+
stmt=True)
2444+
write = AssignStatement(
2445+
tgt.site, tgt,
2446+
ExpressionInitializer(mkLocalVariable(tgt.site, sym)))
2447+
stmt_pairs.append((sym_declaration(sym), write))
2448+
2449+
stmts.extend(first for (first, _) in stmt_pairs)
2450+
stmts.extend(second for (_, second) in stmt_pairs
2451+
if second is not None)
24152452
return [mkCompound(site, stmts)]
24162453

24172454
@statement_dispatcher
@@ -3633,7 +3670,7 @@ def codegen_inline(site, meth_node, indices, inargs, outargs,
36333670
parmtype if parmtype else arg.ctype(),
36343671
meth_node.name)
36353672
for (arg, var, (parmname, parmtype)) in zip(
3636-
outargs, outvars, meth_node.outp)]
3673+
outargs, outvars, meth_node.outp)]
36373674
exit_handler = GotoExit_dml12()
36383675
with exit_handler:
36393676
code = [codegen_statement(meth_node.astcode,
@@ -4063,15 +4100,20 @@ def copy_outarg(arg, var, parmname, parmtype, method_name):
40634100
an exception. We would be able to skip the proxy variable for
40644101
calls to non-throwing methods when arg.ctype() and parmtype are
40654102
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")
4103+
if isinstance(arg, NonValue):
4104+
if not arg.writable:
4105+
raise arg.exc()
40704106
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')
4107+
argtype = arg.ctype()
4108+
4109+
if not argtype:
4110+
raise ICE(arg.site, "unknown expression type")
4111+
else:
4112+
ok, trunc, constviol = realtype(parmtype).canstore(
4113+
realtype(argtype))
4114+
if not ok:
4115+
raise EARGT(arg.site, 'call', method_name,
4116+
arg.ctype(), parmname, parmtype, 'output')
40754117

40764118
return mkCopyData(var.site, var, arg)
40774119

py/dml/ctree.py

Lines changed: 41 additions & 9 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,17 +1128,27 @@ 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+
target_type = target.type if target.explicit_type else None
1133+
else:
1134+
target_type = target.ctype()
11311135

1132-
if deep_const(target_type):
1136+
1137+
if target_type is not None and deep_const(target_type):
11331138
raise ECONST(site)
11341139

11351140
return AssignStatement(site, target, init)
11361141

11371142

11381143
def mkCopyData(site, source, target):
11391144
"Convert a copy statement to intermediate representation"
1140-
source = source_for_assignment(site, target.ctype(), source)
1145+
if isinstance(target, NonValue):
1146+
typ = target.type if target.explicit_type else None
1147+
else:
1148+
typ = target.ctype()
1149+
1150+
if typ is not None:
1151+
source = source_for_assignment(site, typ, source)
11411152

11421153
return mkAssignStatement(site, target, ExpressionInitializer(source))
11431154

@@ -2550,7 +2561,7 @@ class AssignOp(BinOp):
25502561
def __str__(self):
25512562
return "%s = %s" % (self.lh, self.rh)
25522563

2553-
def discard(self):
2564+
def discard(self, explicit=False):
25542565
return self.lh.write(ExpressionInitializer(self.rh))
25552566

25562567
def read(self):
@@ -3575,6 +3586,27 @@ def exc(self):
35753586

35763587
mkUndefined = Undefined
35773588

3589+
class DiscardRef(NonValue):
3590+
slots = ('explicit_type', 'type')
3591+
writable = True
3592+
3593+
@auto_init
3594+
def __init__(self, site, type):
3595+
self.explicit_type = type is not None
3596+
3597+
def __str__(self):
3598+
return '_'
3599+
3600+
def write(self, source):
3601+
if self.explicit_type:
3602+
return source.as_expr(self.type).discard(explicit=True)
3603+
else:
3604+
assert isinstance(source, ExpressionInitializer)
3605+
return source.expr.discard(explicit=True)
3606+
3607+
def mkDiscardRef(site, type=None):
3608+
return DiscardRef(site, type)
3609+
35783610
def endian_convert_expr(site, idx, endian, size):
35793611
"""Convert a bit index to little-endian (lsb=0) numbering.
35803612
@@ -4007,7 +4039,6 @@ def node_type(node, site):
40074039
class NodeRef(Expression):
40084040
"A reference to a node in the device specification"
40094041
priority = 1000
4010-
explicit_type = True
40114042
@auto_init
40124043
def __init__(self, site, node, indices):
40134044
assert isinstance(node, objects.DMLObject)
@@ -4034,6 +4065,7 @@ class NodeRefWithStorage(NodeRef, LValue):
40344065
'''Reference to node that also contains storage, such as allocated
40354066
register, field or attribute in DML 1.2'''
40364067
slots = ('type',)
4068+
explicit_type = True
40374069

40384070
@auto_init
40394071
def __init__(self, site, node, indices):
@@ -5024,8 +5056,8 @@ def ctype(self):
50245056
return self.expr.ctype()
50255057
def read(self):
50265058
return self.expr.read()
5027-
def discard(self):
5028-
return self.expr.discard()
5059+
def discard(self, explicit=False):
5060+
return self.expr.discard(explicit)
50295061
def incref(self):
50305062
self.expr.incref()
50315063
def decref(self):
@@ -5232,15 +5264,15 @@ def assign_to(self, dest, typ):
52325264
rt = safe_realtype_shallow(typ)
52335265
# There is a reasonable implementation for this case (memcpy), but it
52345266
# never occurs today
5235-
assert not isinstance(typ, TArray)
5267+
assert not isinstance(rt, TArray)
52365268
if isinstance(rt, TEndianInt):
52375269
return (f'{rt.dmllib_fun("copy")}((void *)&{dest},'
52385270
+ f' {self.expr.read()})')
52395271
elif deep_const(typ):
52405272
shallow_deconst_typ = safe_realtype_unconst(typ)
52415273
# a const-qualified ExternStruct can be leveraged by the user as a
52425274
# sign that there is some const-qualified member unknown to DMLC
5243-
if (isinstance(typ, TExternStruct)
5275+
if (isinstance(shallow_deconst_typ, TExternStruct)
52445276
or deep_const(shallow_deconst_typ)):
52455277
# Expression statement to delimit lifetime of compound literal
52465278
# 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'''

0 commit comments

Comments
 (0)