Skip to content
This repository was archived by the owner on Oct 12, 2022. It is now read-only.
/ druntime Public archive
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 140 additions & 6 deletions src/core/memory.d
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,106 @@ struct GC
}
}

/**
* Pure variants of C's memory allocation functions `malloc`, `calloc`, and
* `realloc` that achieve purity by aborting the program on failure so
* they never visibly change errno.
*
* The functions may terminate the program using `onOutOfMemoryError` or
* `assert(0)`. These functions' purity guarantees no longer hold if
* the program continues execution after catching AssertError or
* OutOfMemoryError.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment needs updating.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept the mention of the possibility so if we later change to using onOutOfMemoryError no one will be blindsided. Do you think it's better to remove it because that's too unlikely?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to change anything immediately, IMO. We can wait to see how the review goes. It just eventually needs to reflect the final implementation.

*
* A similar fail-fast variant of C's memory deallocation function `free`
* is not provided due to the lack of a portable and efficient way to test
* for failure. If purity is required `pureFree` may be used.
*
* See_Also:
* $(LREF pureMalloc), $(LREF pureCalloc), $(LREF pureRealloc),
* and $(LREF pureFree) which achieve purity by saving and restoring
* errno.
* $(LINK2 https://dlang.org/library/object/error.html, Error class documentation)
* on the danger of continued execution after catching an Error.
*/
void* assertMalloc()(size_t size) @nogc nothrow pure @trusted
{
void* ret = fakePureMalloc(size);
if (!ret && size)
{
version (D_Exceptions)
{
import core.exception : onOutOfMemoryError;
onOutOfMemoryError();
}
else assert(0, "Memory allocation failed");
}
return ret;
}
/// ditto
void* assertCalloc()(size_t nmemb, size_t size) @nogc nothrow pure @trusted
{
void* ret = fakePureCalloc(nmemb, size);
if (!ret && nmemb && size)
{
version (D_Exceptions)
{
import core.exception : onOutOfMemoryError;
onOutOfMemoryError();
}
else assert(0, "Memory allocation failed");
}
return ret;
}
/// ditto
void* assertRealloc()(void* ptr, size_t size) @nogc nothrow pure @system
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since realloc() free's memory, it cannot ever be considered pure.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since realloc() free's memory, it cannot ever be considered pure.

We have a pureFree, and GC.free is also marked as pure.

pure functions are allowed to modify their parameters. Freeing is never @safe, but in what way is it less pure than other forms of mutation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also already a pureRealloc.

druntime/src/core/memory.d

Lines 840 to 847 in db2910e

/// ditto
void* pureRealloc(void* ptr, size_t size) @system pure @nogc nothrow
{
const errnosave = fakePureErrno();
void* ret = fakePureRealloc(ptr, size);
fakePureErrno() = errnosave;
return ret;
}

{
void* ret = fakePureRealloc(ptr, size);
if (!ret && size)
{
version (D_Exceptions)
{
import core.exception : onOutOfMemoryError;
onOutOfMemoryError();
}
else assert(0, "Memory allocation failed");
}
return ret;
}

///
@nogc nothrow pure @system unittest
{
void[] allocate(size_t n) pure
{
return assertMalloc(n)[0 .. n];
}

auto buf = allocate(100);
assert(buf.length == 100);
pureFree(buf.ptr);
}

@nogc nothrow pure @system unittest
{
const int errno = fakePureErrno();
Copy link
Contributor

@JinShil JinShil Aug 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fakePureErrno is mangled to fakePureErrnoImpl which is not a template. Therefore this won't work until fakePureErrnoImpl is made into a template, or some other solution is devised.

Edit: I mean it won't work in -betterC until fakePureErrnoImpl is made into a template, or some other solution is devised

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be fixed now.


void* x = assertMalloc(10); // normal allocation
assert(errno == fakePureErrno()); // errno shouldn't change
assert(x !is null); // allocation should succeed

x = assertRealloc(x, 20); // normal reallocation
assert(errno == fakePureErrno()); // errno shouldn't change
assert(x !is null); // allocation should succeed

fakePureFree(x);

void* y = assertCalloc(10, 1); // normal zeroed allocation
assert(errno == fakePureErrno()); // errno shouldn't change
assert(y !is null); // allocation should succeed

fakePureFree(y);
}

/**
* Pure variants of C's memory allocation functions `malloc`, `calloc`, and
* `realloc` and deallocation function `free`.
Expand All @@ -818,35 +918,38 @@ struct GC
* Purity is achieved by saving and restoring the value of `errno`, thus
* behaving as if it were never changed.
*
*
* See_Also:
* $(LINK2 https://dlang.org/spec/function.html#pure-functions, D's rules for purity),
* which allow for memory allocation under specific circumstances.
* $(LREF assertMalloc), $(LREF assertCalloc), and $(LREF assertRealloc)
* which achieve purity by terminating on failure.
*/
void* pureMalloc(size_t size) @trusted pure @nogc nothrow
void* pureMalloc()(size_t size) @nogc nothrow pure @trusted
{
const errnosave = fakePureErrno();
void* ret = fakePureMalloc(size);
fakePureErrno() = errnosave;
return ret;
}
/// ditto
void* pureCalloc(size_t nmemb, size_t size) @trusted pure @nogc nothrow
void* pureCalloc()(size_t nmemb, size_t size) @nogc nothrow pure @trusted
{
const errnosave = fakePureErrno();
void* ret = fakePureCalloc(nmemb, size);
fakePureErrno() = errnosave;
return ret;
}
/// ditto
void* pureRealloc(void* ptr, size_t size) @system pure @nogc nothrow
void* pureRealloc()(void* ptr, size_t size) @nogc nothrow pure @system
{
const errnosave = fakePureErrno();
void* ret = fakePureRealloc(ptr, size);
fakePureErrno() = errnosave;
return ret;
}
/// ditto
void pureFree(void* ptr) @system pure @nogc nothrow
void pureFree()(void* ptr) @nogc nothrow pure @system
{
const errnosave = fakePureErrno();
fakePureFree(ptr);
Expand Down Expand Up @@ -899,20 +1002,51 @@ void pureFree(void* ptr) @system pure @nogc nothrow
}

// locally purified for internal use here only
static import core.stdc.errno;

private enum canLinkToErrno = __traits(getOverloads, core.stdc.errno, "errno").length == 1
&& __traits(getLinkage, core.stdc.errno.errno) == "C";

version (D_BetterC) @nogc nothrow private @system
{
static if (canLinkToErrno)
{
extern(C)
pragma(mangle, __traits(identifier, core.stdc.errno.errno)) ref int fakePureErrno() pure;
}
else
{
ref int _errno()() { return core.stdc.errno.errno; }

private ref int fakePureErrno()() pure
{
alias FakePureErrno = ref int function() @nogc nothrow pure;
return (cast(FakePureErrno) &_errno!())();
}
}
}
else
extern (C) private @system @nogc nothrow
{
ref int fakePureErrnoImpl()
{
import core.stdc.errno;
return errno();
}

static if (canLinkToErrno)
{
// We don't need fakePureErrnoImpl but for now we'll keep it for compatibility.
pragma(mangle, __traits(identifier, core.stdc.errno.errno)) ref int fakePureErrno() pure;
}
else
{
pragma(mangle, "fakePureErrnoImpl") ref int fakePureErrno() pure;
}
}

extern (C) private pure @system @nogc nothrow
{
pragma(mangle, "fakePureErrnoImpl") ref int fakePureErrno();

pragma(mangle, "malloc") void* fakePureMalloc(size_t);
pragma(mangle, "calloc") void* fakePureCalloc(size_t nmemb, size_t size);
pragma(mangle, "realloc") void* fakePureRealloc(void* ptr, size_t size);
Expand Down