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";