Skip to content

Commit

Permalink
Make enriched new/delete opt-in
Browse files Browse the repository at this point in the history
  • Loading branch information
lwaern-intel committed Oct 4, 2023
1 parent c541647 commit 4a7b4f4
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 25 deletions.
49 changes: 42 additions & 7 deletions doc/1.4/language.md
Original file line number Diff line number Diff line change
Expand Up @@ -3478,13 +3478,25 @@ method n() -> (bool, int) {

<pre>
delete <em>expr</em>;
delete\<<em>spec</em>\> <em>expr</em>;
</pre>

Deallocates the memory pointed to by the result of evaluating
*`expr`*. The memory must have been allocated with the
`new` operator, and must not have been deallocated previously.
Equivalent to `delete` in C++; however, in DML, `delete`
can only be used as a statement, not as an expression.
Deallocates the memory pointed to by the result of evaluating *`expr`*.

*spec* specifies an *allocation format*, and can either be `enriched` or
`extern`. If not explicitly specified, *spec* will default to `extern`. This is
for backwards compatibility reasons &mdash; in the future the default will be
changed to be `enriched`.

The *enriched* format uses an allocation format specific to the device model,
and so can *only* be used in order to deallocate storage previously allocated
via [`new<enriched>`](#new-expressions) by the same device model.

The *extern* format compiles the `delete` statement to a use of `MM_FREE`,
meaning it may be used to deallocate storage previously allocated by any use of
Simics's memory allocation functions/macros (such as `MM_MALLOC`.) This includes
storage allocated via [`new<extern>`](#new-expressions) (which `new` without
allocation format specifier is equivalent to).

### Try Statements

Expand Down Expand Up @@ -4145,16 +4157,39 @@ independent method callback(int i, void *aux) {
new <em>type</em>

new <em>type</em>[<em>count</em>]

new\<<em>spec</em>\> <em>type</em>

new\<<em>spec</em>\> <em>type</em>[<em>count</em>]
</pre>

Allocates a chunk of memory large enough for a value of the specified
type. If the second form is used, memory for *count* values will
type. If a form specifying *count* is used, then memory for *count* values will
be allocated. The result is a pointer to the allocated memory. (The
pointer is never null; if allocation should fail, the Simics
application will be terminated.)

*spec* specifies an *allocation format*, and can either be `enriched` or
`extern`. If not explicitly specified, *spec* will default to `extern`. This is
for backwards compatibility reasons &mdash; in the future the default will be
changed to be `enriched`.

The *enriched* format uses an allocation format specific to the device model,
and *must* be used in order to allocate storage for values of [resource-enriched
(RAII) type](#raii-types). The fact the allocation format is model-specific
comes with the drawback that a pointer created with `new<enriched>` *cannot be
freed* using `MM_FREE`/`free`: only code from the same device model can free it,
and only by using [`delete<enriched>`](#delete-statements).

The *extern* format compiles `new` to a use of `MM_ZALLOC`, meaning a pointer
allocated this way may be freed using `MM_FREE` outside of the device model.
However, this format does not support allocating storage for values of
resource-enriched type.

When the memory is no longer needed, it should be deallocated using a
`delete` statement.
[`delete` statement](#delete-statements). The allocation format specified for the
`delete` statement *must* match that of the `new` expression used to allocate
the pointer.

### Cast Expressions

Expand Down
30 changes: 26 additions & 4 deletions py/dml/codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -1600,13 +1600,22 @@ def expr_typeop(tree, location, scope):

@expression_dispatcher
def expr_new(tree, location, scope):
[t, count] = tree.args
[spec, t, count] = tree.args
(struct_defs, t) = eval_type(t, tree.site, location, scope)
if t.is_raii and spec != 'enriched':
report(ENEWRAII(tree.site, t.describe(),
'new' + f'<{spec}>'*(spec is not None)))
spec = 'enriched'

for (site, _) in struct_defs:
report(EANONSTRUCT(site, "'new' expression"))
if count:
count = codegen_expression(count, location, scope)
return mkNew(tree.site, t, count)
if spec == 'enriched':
return mkNew(tree.site, t, count)
else:
assert spec is None or spec == 'extern'
return mkNewExtern(tree.site, t, count)

@expression_dispatcher
def expr_apply(tree, location, scope):
Expand Down Expand Up @@ -3107,9 +3116,22 @@ def stmt_default(stmt, location, scope):

@statement_dispatcher
def stmt_delete(stmt, location, scope):
[expr] = stmt.args
[spec, expr] = stmt.args
expr = codegen_expression(expr, location, scope)
return [mkDelete(stmt.site, expr)]
etype = safe_realtype_shallow(expr.ctype())
if not isinstance(etype, TPtr):
raise ENOPTR(stmt.site, expr)
if etype.base.is_raii and spec != 'enriched':
report(EDELETERAII(stmt.site,
etype.base.describe(),
'delete' + f'<{spec}>'*(spec is not None)))
spec = 'enriched'

if spec == 'enriched':
return [mkDelete(stmt.site, expr)]
else:
assert spec is None or spec == 'extern'
return [mkDeleteExtern(stmt.site, expr)]

def probable_loggroups_specification(expr):
subexprs = [expr]
Expand Down
43 changes: 39 additions & 4 deletions py/dml/ctree.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
'mkAssert',
'mkReturn',
'mkDelete',
'mkDeleteExtern',
'mkExpressionStatement',
'mkAfter',
'mkAfterOnHook',
Expand Down Expand Up @@ -126,6 +127,7 @@
'mkHookSendRef', 'HookSendRef',
'mkHookSendApply', 'HookSendApply',
'mkNew',
'mkNewExtern',
#'Constant',
'mkIntegerConstant', 'IntegerConstant',
'mkIntegerLiteral',
Expand Down Expand Up @@ -582,8 +584,16 @@ def toc_stmt(self):
self.linemark()
out(f'DML_DELETE({self.expr.read()});\n')

def mkDelete(site, expr):
return Delete(site, expr)
mkDelete = Delete

class DeleteExtern(Statement):
@auto_init
def __init__(self, site, expr): pass
def toc_stmt(self):
self.linemark()
out(f'MM_FREE({self.expr.read()});\n')

mkDeleteExtern = DeleteExtern

class ExpressionStatement(Statement):
@auto_init
Expand Down Expand Up @@ -3329,9 +3339,9 @@ def __init__(self, site, newtype, count, raii_info):
self.type = TPtr(newtype)
def __str__(self):
if self.count:
return 'new %s[%s]' % (self.newtype, self.count)
return 'new<enriched> %s[%s]' % (self.newtype, self.count)
else:
return 'new %s' % self.newtype
return 'new<enriched> %s' % self.newtype
def read(self):
destructor = (self.raii_info.cident_destructor_array_item
if self.raii_info else '_dml_raii_destructor_ref_none')
Expand All @@ -3349,6 +3359,31 @@ def mkNew(site, newtype, count = None):
info = None
return New(site, newtype, count, info)

class NewExtern(Expression):
priority = 160 # f()
slots = ('type',)
@auto_init
def __init__(self, site, newtype, count):
self.type = TPtr(newtype)
def __str__(self):
if self.count:
return 'new<extern> %s[%s]' % (self.newtype, self.count)
else:
return 'new<extern> %s' % self.newtype
def read(self):
t = self.newtype.declaration('')
if self.count:
return 'MM_ZALLOC(%s, %s)' % (self.count.read(), t)
else:
return 'MM_ZALLOC(1, %s)' % (t)


def mkNewExtern(site, newtype, count = None):
assert not newtype.is_raii
if count:
count = as_int(count)
return NewExtern(site, newtype, count)

class ListItems(metaclass=abc.ABCMeta):
'''A series of consecutive list elements, where each list element
corresponds to one index in a multi-dimensional (but possibly
Expand Down
30 changes: 24 additions & 6 deletions py/dml/dmlparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -1824,15 +1824,33 @@ def typeop_arg_par(t):
'''typeoparg : LPAREN ctypedecl RPAREN'''
t[0] = t[2]

@prod_dml14
def maybe_newdelete_spec_yes(t):
'''maybe_newdelete_spec : LT ID GT
| LT EXTERN GT'''
supported_specs = ('extern', 'enriched')
spec = t[2]
if spec not in supported_specs:
suggestions = ' or '.join(f"'{spec}'" for spec in supported_specs)
report(ESYNTAX(site(t, 1), spec,
f"expected new/delete specification ({suggestions})"))
spec = 'enriched'
t[0] = spec

@prod
def maybe_newdelete_spec_no(t):
'''maybe_newdelete_spec : '''
t[0] = None

@prod
def expression_new(t):
'''expression : NEW ctypedecl'''
t[0] = ast.new(site(t), t[2], None)
'''expression : NEW maybe_newdelete_spec ctypedecl'''
t[0] = ast.new(site(t), t[2], t[3], None)

@prod
def expression_new_array(t):
'''expression : NEW ctypedecl LBRACKET expression RBRACKET'''
t[0] = ast.new(site(t), t[2], t[4])
'''expression : NEW maybe_newdelete_spec ctypedecl LBRACKET expression RBRACKET'''
t[0] = ast.new(site(t), t[2], t[3], t[5])

@prod
def expression_paren(t):
Expand Down Expand Up @@ -2200,8 +2218,8 @@ def case_blocks_list_hashifelse(t):
# Delete is an expression in C++, not a statement, but we don't care.
@prod
def statement_delete(t):
'statement_except_hashif : DELETE expression SEMI'
t[0] = ast.delete(site(t), t[2])
'statement_except_hashif : DELETE maybe_newdelete_spec expression SEMI'
t[0] = ast.delete(site(t), t[2], t[3])

@prod
def statent_try(t):
Expand Down
28 changes: 28 additions & 0 deletions py/dml/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -1603,6 +1603,34 @@ class EANONRAIISTRUCT(DMLError):
fmt = ("method-local anonymous struct declared that has a member of "
+ "resource-enriched (RAII) type")

class ENEWRAII(DMLError):
"""
A `new` expression not specified as `enriched` can't be used to allocate a
storage for values of resource-enriched (RAII) type.
To address this, use `new<enriched>` instead of `new` or `new<extern>`.
"""
fmt = ("'new' expression not specified as 'enriched' used to create "
+ "pointer with resource-enriched (RAII) basetype '%s'. To address "
+ "this, use 'new<enriched>' instead of '%s', and ensure any "
+ "pointer allocated this way is only deallocated using "
+ "'delete<enriched>'!")

class EDELETERAII(DMLError):
"""
A `delete` statement not specified as `enriched` was used on a pointer with
resource-enriched (RAII) basetype. Except for extremely niche cases, this
is incorrect: an allocated pointer of resource-enriched basetype can only
be validly created through a `new` expression specified as `enriched`.
To address this, use `delete<enriched>` instead of `delete` or
`delete<extern>`.
"""
version = "1.4"
fmt = ("'delete' statement not specified as 'enriched' used on pointer "
+ "with resource-enriched (RAII) basetype '%s'. "
+ "To address this, use 'delete<enriched>' instead of '%s', and "
+ "ensure any pointer deallocated through this 'delete' is only "
+ "allocated using 'new<enriched>'!")

class EIDENTSIZEOF(DMLError):
"""
Expand Down
6 changes: 2 additions & 4 deletions test/1.4/types/raii/T_various.dml
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ method init() {
assert prev_len == 36 && p->size == 128; // Shrunk to _DML_BITCEIL(36 + 1)*2
s_ = "";
assert p->size == 32; // Shrunk to _DML_STRING_INITIAL_SIZE
local string *ps = new string;
local string *ps = new<enriched> string;
local int *pater = cast({1,2,3}, int[3]);
assert pater[1] == 2;
try {
Expand All @@ -153,12 +153,10 @@ method init() {
assert *ps == "som bristande båge låga.";
assert strcmp(cast(this, t).whatdoesthisevenmean,
"En ondskans tid nu domnar min hand") == 0;
delete ps;
delete<enriched> ps;

local (string sa, string sb) = splitAt("FLYGANDESPJUT", 8);
assert sa == "FLYGANDE" && sb == "SPJUT";
local char *trab = new char[10];
delete trab;

local (string sST, vect(int) vST) = memo();
assert sST == "SOM EN VÄXANDE VÅG";
Expand Down

0 comments on commit 4a7b4f4

Please sign in to comment.