Skip to content
This repository was archived by the owner on Oct 12, 2022. It is now read-only.
/ druntime Public archive

Comments

Add pure wrappers for C style allocation#1746

Merged
andralex merged 1 commit intodlang:masterfrom
nordlow:pure-c-allocs
Jan 29, 2017
Merged

Add pure wrappers for C style allocation#1746
andralex merged 1 commit intodlang:masterfrom
nordlow:pure-c-allocs

Conversation

@nordlow
Copy link
Contributor

@nordlow nordlow commented Jan 17, 2017

///
void* pureMalloc(size_t size) pure
{
const errno = pureGetErrno();
Copy link
Member

Choose a reason for hiding this comment

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

Why not use void* or auto here? malloc's return type isn't going to change anytime soon .

Copy link
Contributor Author

@nordlow nordlow Jan 18, 2017

Choose a reason for hiding this comment

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

Changed return type to void*.

cast(void)pureSetErrno(errno);
return ret;
}
extern (C) pure pragma(mangle, "getErrno") int pureGetErrno(); // for internal use
Copy link
Member

Choose a reason for hiding this comment

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

Checking in with LDC developers @kinke @JohanEngelen, does this work on LDC?

BTW, I bumped into a related extern (C) issue on LDC - #1728 (comment)

Copy link
Member

Choose a reason for hiding this comment

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

Also, since those functions are for internal use, why not make them private or package, or declare them inside pureMalloc? They should be used only in a controlled manner, and reviewed case-by-case.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, since those functions are for internal use,

I think the plan is to make this public

Copy link
Contributor

Choose a reason for hiding this comment

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

does this work on LDC?

Multiple extern (C) forward-declarations with the same mangled name aren't a problem. This works.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So are you saying I should rename these declarations to getErrno() and setErrno()?

Copy link
Contributor

Choose a reason for hiding this comment

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

Well just saying that ldc-developers/ldc#1020 doesn't apply to these extern (C) fwd declarations. While declaring them with their real name should work too, I think the explicit pure in the function name makes a lot of sense in this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok. I keep it as that for now.

Copy link
Contributor Author

@nordlow nordlow Jan 18, 2017

Choose a reason for hiding this comment

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

This currently fails to compile as

stdlib.d(166,23): Error: pure function 'core.stdc.stdlib.pureMalloc' cannot call impure function 'core.stdc.stdlib.malloc'

I guess I have to use a local pure declaration of malloc as _pureMalloc then?

@JackStouffer
Copy link
Contributor

Andrei stated this should go in core/memory.d

@nordlow
Copy link
Contributor Author

nordlow commented Jan 18, 2017

So, should I move it to core/memory.d, @andralex?

@nordlow
Copy link
Contributor Author

nordlow commented Jan 18, 2017

Should I add similar wrappers for calloc and realloc?

@nordlow nordlow changed the title Add pureMalloc Add pure wrappers for C style allocation Jan 18, 2017
@JackStouffer
Copy link
Contributor

Should I add similar wrappers for calloc and realloc?

Yes

So, should I move it to core/memory.d

#1735 (comment)

@nordlow
Copy link
Contributor Author

nordlow commented Jan 18, 2017

Done.

@nordlow nordlow force-pushed the pure-c-allocs branch 2 times, most recently from f20b859 to 3500531 Compare January 18, 2017 22:52
@kinke
Copy link
Contributor

kinke commented Jan 18, 2017

What about potential race conditions when restoring errno? The docs here don't mention errno at all, instead:

Data races: Only the storage referenced by the returned pointer is modified. No other storage locations are accessed by the call.

And here:

malloc is thread-safe: it behaves as though only accessing the memory locations visible through its argument, and not any static storage.

@JackStouffer
Copy link
Contributor

Pretty sure errno is thread local.

@nordlow
Copy link
Contributor Author

nordlow commented Jan 19, 2017

{
const errno = _pureGetErrno();
void* ret = _pureMalloc(size);
cast(void)_pureSetErrno(errno);
Copy link
Member

Choose a reason for hiding this comment

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

space after closing paren in cast - cc @wilzbach

/// Pure variant of `malloc` that doesn't affect global variable `errno`.
void* pureMalloc(size_t size) pure
{
const errno = _pureGetErrno();
Copy link
Member

Choose a reason for hiding this comment

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

Does druntime use this convention of prefixing private symbols with one underscore?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Renamed it.

pragma(mangle, "malloc") void* _pureMalloc(size_t);
pragma(mangle, "calloc") void* _pureCalloc(size_t nmemb, size_t size);
pragma(mangle, "realloc") void* _pureRealloc(void* ptr, size_t size);
}
Copy link
Member

Choose a reason for hiding this comment

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

please add one unittest to cover all, or at best short ddoc unittests showing the use of each inside a pure function

@andralex
Copy link
Member

OK, this is good progress. Thanks!

@andralex
Copy link
Member

One idea for documentation: simply group all three together with documentation such as "Pure wrappers for the C stdlib memory allocation primitives malloc, calloc, and realloc." Then you use one ddoc unittest featuring all. That way you get nice compact documentation and one unifying example.

@andralex
Copy link
Member

I suspect pureCalloc is safe, too. Awesomeness.

}
}

/// Pure variant of `malloc` that doesn't affect global variable `errno`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Please replace this with the following:

/**
 * Pure variants of C's memory allocation functions. Purity is achieved via
 * resetting the `errno` to it's value prior to being called, removing the
 * function's global state mutation.
 *
 * See_Also:
 *     $(LINK2 https://dlang.org/spec/function.html#pure-functions, D's rules for purity),
 *     which allow for memory allocation under specific circumstances.
 */

IMO it addresses the intuitiveness of a pure memory allocation function for the reader.

Copy link
Member

Choose a reason for hiding this comment

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

The teleological description is better than the description in terms of implementation details. I.e. if a platform offered access to an errno-less malloc (as e.g. would be easy to hack with jemalloc), pureMalloc would use that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

{
const errno = fakePureGetErrno();
void* ret = fakePureMalloc(size);
cast(void)fakePureSetErrno(errno);
Copy link
Member

Choose a reason for hiding this comment

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

I'd say it's more efficient to guard the call like this:

if (!ret || errno != ERR_OK) cast(void)fakePureSetErrno(errno);

The common case is malloc succeeds and the previous state was ERR_OK so no need to reset errno.

Copy link
Contributor Author

@nordlow nordlow Jan 28, 2017

Choose a reason for hiding this comment

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

  1. ERR_OK is not defined. I guess you mean 0, right?

  2. I can't find any good docs on {set,get}Errno. Shouldn't we just read and write the errno directly instead of calling {set,get}Errno? Provided that errno is thread-local.

Otherwise, LGTM.

@nordlow
Copy link
Contributor Author

nordlow commented Jan 28, 2017

Ready for squashing?

@andralex
Copy link
Member

lgtm

@nordlow
Copy link
Contributor Author

nordlow commented Jan 28, 2017

Squashed.

@nordlow
Copy link
Contributor Author

nordlow commented Jan 28, 2017

BTW: Is manual squashing still needed by PR authors?

I heard somewhere that Github had added an automatic squash option upon merge.

@andralex
Copy link
Member

BTW: Is manual squashing still needed by PR authors?

I forgot. cc @MartinNowak @CyberShadow

@andralex
Copy link
Member

One more thing - please add a doc unittest featuring the use of all three. Thanks!

@wilzbach
Copy link
Contributor

BTW: Is manual squashing still needed by PR authors?
I forgot. cc @MartinNowak @CyberShadow

GitHub squashing is already enabled and the dlang bot supports squashed merges. @CyberShadow had some concerns related to digger and PR number inference, but IIRC they have been resolved as the PR number is part of the commit message?

{
void* p = pureMalloc(n);
enforce(n == 0 || p !is null);

Copy link
Member

Choose a reason for hiding this comment

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

Vertical space in a five-liner is a net negative (ironically your implementations themselves don't have useless vertical space, and indeed they shouldn't). There is no value added by it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

void* p = pureMalloc(n);
enforce(n == 0 || p !is null);

scope(failure) free(p);
Copy link
Member

Choose a reason for hiding this comment

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

Use p = pureRealloc(p, 0); to free memory in a pure manner.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

import std.exception : enforce;
import core.stdc.stdlib : free;

ubyte[] fun(size_t n) // TODO pure?
Copy link
Member

Choose a reason for hiding this comment

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

one way or another the example must be pure!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.


p = pureRealloc(p, n *= 2);
enforce(n == 0 || p !is null);

Copy link
Member

Choose a reason for hiding this comment

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

space, the final frontier - let's not

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@nordlow nordlow force-pushed the pure-c-allocs branch 2 times, most recently from a07e188 to 67918c3 Compare January 29, 2017 20:54
{
import core.stdc.stdlib : free;

ubyte[] fun(size_t n) // TODO pure?
Copy link
Member

Choose a reason for hiding this comment

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

make pure

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@nordlow
Copy link
Contributor Author

nordlow commented Jan 29, 2017

Needed to use assert instead. enforce is in Phobos :)

{
void* p = pureMalloc(n);
p !is null || n == 0 || assert(0);
scope(failure) { p = pureRealloc(p, 0); }
Copy link
Member

Choose a reason for hiding this comment

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

braces are redundant - think of an inlined if, would one put braces?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@nordlow
Copy link
Contributor Author

nordlow commented Jan 29, 2017

Ok now, @andralex ?

@andralex
Copy link
Member

thx!

@andralex andralex merged commit 6146508 into dlang:master Jan 29, 2017
@nordlow
Copy link
Contributor Author

nordlow commented Jan 29, 2017

Great!

@nordlow
Copy link
Contributor Author

nordlow commented Jan 30, 2017

Why is pureFree disallowed when pureRealloc can be used to free aswell?

@andralex
Copy link
Member

Ask @schveiguy :)

@nordlow
Copy link
Contributor Author

nordlow commented Jan 30, 2017

@schveiguy ?

@schveiguy
Copy link
Member

It's a good question. I would question it the other way, why is pureRealloc allowed when pureFree is not.

Technically, realloc(ptr, 0) is essentially the same as free(ptr). However, one is not likely to realloc without wanting to re-assign to the original pointer. That is, if one has a function as has been discussed like:

void foo(immutable int *ptr) pure { ... }

Using realloc in such a function doesn't make a whole lot of sense, since you potentially destroy the original pointer, and have no way to inform the caller of the new location.

So while technically realloc can be used as a crude instrument to achieve pure free, the likelihood that someone does this is quite low. I would say you are misusing realloc if you are doing it that way.

IMO, realloc is ok to mark pure.

@rainers
Copy link
Member

rainers commented Jan 30, 2017

There are no auto-tester builds for druntime anymore?
This PR broke master because the Digital Mars malloc is able to allocate size_t.max bytes! (In fact it just adds 2 to the size before calling the Windows API functions.)

@CyberShadow
Copy link
Member

Wow, the autotester did not report any status for the last commit.

I'm going to enable the autotester being green as a requirement for merging Druntime PRs.

@nordlow
Copy link
Contributor Author

nordlow commented Jan 30, 2017

Oops, sorry about that. Should we just remove this test case, @andralex?

@CyberShadow
Copy link
Member

Done. @rainers Can you code up a fix or should we revert?

@nordlow
Copy link
Contributor Author

nordlow commented Jan 30, 2017

I can remove the part where size_t.max bytes is allocated if you feel like.

@rainers
Copy link
Member

rainers commented Jan 30, 2017

Subtracting 2 works, too: #1754

@nordlow
Copy link
Contributor Author

nordlow commented Jan 30, 2017

Thanks.

@schveiguy
Copy link
Member

Wow, the autotester did not report any status for the last commit.

The merge was done without auto-tester requirement (i.e. merge button was pushed, not auto-merge).

I'm going to enable the autotester being green as a requirement for merging Druntime PRs.

This is probably a good requirement, but I'd much rather the auto-merge feature be used, since there is usually a lag between some other merge happening and github deciding that it's no longer green.

@CyberShadow
Copy link
Member

I see... so the problem is that @andralex did not use auto-merge.

This is probably a good requirement, but I'd much rather the auto-merge feature be used, since there is usually a lag between some other merge happening and github deciding that it's no longer green.

We might be able to fix this by enabling the GitHub-side requirement that PR branches can only be merged if they are rebased off the latest commit of the target branch, and have the autotester do that rebase on top of the target branch commit it tested with before attempting to merge the PR. Not sure it would be worth the effort, though - have we ever had breakages due to this?

@andralex
Copy link
Member

I saw "All tests passed" so I merged directly. It looks like a race condition.

@schveiguy
Copy link
Member

It looks like a race condition.

Yes, this is the lag I spoke of. github can take some time to sync with the status of the auto tester, or the auto tester could possibly not report the "invalid" status quickly enough. In any case, using the auto-tester auto-merge feature is the safest path -- if all tests have passed, it will merge immediately, so it's no different.

@nordlow nordlow deleted the pure-c-allocs branch October 2, 2018 06:59
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants