diff --git a/doc/1.4/language.md b/doc/1.4/language.md
index 0ac9ca5f4..8efd961ae 100644
--- a/doc/1.4/language.md
+++ b/doc/1.4/language.md
@@ -3478,13 +3478,25 @@ method n() -> (bool, int) {
delete expr;
+delete\<spec\> expr;
-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 — 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`](#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`](#new-expressions) (which `new` without
+allocation format specifier is equivalent to).
### Try Statements
@@ -4145,16 +4157,39 @@ independent method callback(int i, void *aux) {
new type
new type[count]
+
+new\<spec\> type
+
+new\<spec\> type[count]
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 — 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` *cannot be
+freed* using `MM_FREE`/`free`: only code from the same device model can free it,
+and only by using [`delete`](#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
diff --git a/py/dml/codegen.py b/py/dml/codegen.py
index 9e2c5629d..ff1789114 100644
--- a/py/dml/codegen.py
+++ b/py/dml/codegen.py
@@ -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):
@@ -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]
diff --git a/py/dml/ctree.py b/py/dml/ctree.py
index 86026bc2f..6ead70bf6 100644
--- a/py/dml/ctree.py
+++ b/py/dml/ctree.py
@@ -51,6 +51,7 @@
'mkAssert',
'mkReturn',
'mkDelete',
+ 'mkDeleteExtern',
'mkExpressionStatement',
'mkAfter',
'mkAfterOnHook',
@@ -126,6 +127,7 @@
'mkHookSendRef', 'HookSendRef',
'mkHookSendApply', 'HookSendApply',
'mkNew',
+ 'mkNewExtern',
#'Constant',
'mkIntegerConstant', 'IntegerConstant',
'mkIntegerLiteral',
@@ -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
@@ -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 %s[%s]' % (self.newtype, self.count)
else:
- return 'new %s' % self.newtype
+ return 'new %s' % self.newtype
def read(self):
destructor = (self.raii_info.cident_destructor_array_item
if self.raii_info else '_dml_raii_destructor_ref_none')
@@ -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 %s[%s]' % (self.newtype, self.count)
+ else:
+ return 'new %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
diff --git a/py/dml/dmlparse.py b/py/dml/dmlparse.py
index 34b5bf25b..005bea610 100644
--- a/py/dml/dmlparse.py
+++ b/py/dml/dmlparse.py
@@ -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):
@@ -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):
diff --git a/py/dml/messages.py b/py/dml/messages.py
index c8e660fdb..3a0f934fa 100644
--- a/py/dml/messages.py
+++ b/py/dml/messages.py
@@ -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` instead of `new` or `new`.
+ """
+ fmt = ("'new' expression not specified as 'enriched' used to create "
+ + "pointer with resource-enriched (RAII) basetype '%s'. To address "
+ + "this, use 'new' instead of '%s', and ensure any "
+ + "pointer allocated this way is only deallocated using "
+ + "'delete'!")
+
+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` instead of `delete` or
+ `delete`.
+ """
+ version = "1.4"
+ fmt = ("'delete' statement not specified as 'enriched' used on pointer "
+ + "with resource-enriched (RAII) basetype '%s'. "
+ + "To address this, use 'delete' instead of '%s', and "
+ + "ensure any pointer deallocated through this 'delete' is only "
+ + "allocated using 'new'!")
class EIDENTSIZEOF(DMLError):
"""
diff --git a/test/1.4/types/raii/T_various.dml b/test/1.4/types/raii/T_various.dml
index 22829908f..e6474806e 100644
--- a/test/1.4/types/raii/T_various.dml
+++ b/test/1.4/types/raii/T_various.dml
@@ -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 string;
local int *pater = cast({1,2,3}, int[3]);
assert pater[1] == 2;
try {
@@ -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 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";