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

Conversation

JakobOvrum
Copy link

malloc and calloc do not present an unsafe interface - it's reasonable to call them and do the unsafe cast or pointer slice somewhere else. Hence, they should be @trusted.

However, I made this PR mostly to gauge opinion about purity. I suggest these two simple heap allocators should be pure in the same vein new and GC.malloc are considered pure - memory allocation is only an observable side-effect in really niche applications that either a) inspect memory usage using OS-specific functions, or b) recover from OOM. In all other applications it is immensely practical to allow memory allocation in pure functions.

What do you think?

@andralex
Copy link
Member

andralex commented Mar 3, 2015

They are indeed those but it's impossible to use them in any safe way because they return a void* with no extent. Probably not worth doing this.

@JakobOvrum
Copy link
Author

They are indeed those but it's impossible to use them in any safe way because they return a void* with no extent. Probably not worth doing this.

They can be safely passed around to other functions that may or may not do unsafe things.

Whether they are @trusted or @system is not a big deal for me, just something I noticed when writing dlang/phobos#3031.

What do you think about pure though? It might not seem like a big deal at this level of abstraction, but it makes things like constructing RefCounted instances pure (which may be a poor example as destruction is still impure...).

The kinds of memory allocation that are currently considered pure are GC-heap allocations and stack allocations (although I don't know how we've annotated alloca). These are different from the C heap because in a way, they both have automatic lifetime, while the C heap has the attribute-murky free. I'm wondering whether or not that's reason enough to stray from our policy on memory allocation and purity.

@schveiguy
Copy link
Member

These absolutely cannot be @trusted.

They can be pure, and I would argue they should be. However, free would also have to be pure to make pure malloc viable, and that poses some problems. I hashed this out with someone in the NG one time and the short of it is, free(immutable(T)*) could potentially be considered "strong pure" if it's marked pure. This means a free of some address may be optimized out by the compiler thinking it will not make any global changes. It also returns nothing, so even the first call may be optimized out (no observable changes to the program could possibly happen).

So while I think malloc and friends could be pure, because free cannot be, it's not something we should support. A wrapper around it that auto-frees (possibly ref counting) may be something that makes sense, but I'd have to see it to know for sure.

@andralex
Copy link
Member

andralex commented Mar 3, 2015

yah, pure is nice!

@MartinNowak
Copy link
Member

👍 for pure.
We had the @trusted/@safe discussion before. At some point someone accidentally marked alloca as @trusted :o.

I hashed this out with someone in the NG one time and the short of it is, free(immutable(T)*) could potentially be considered "strong pure"

No, it can't be strong pure, you can only call free with a pointer to mutable.
bug2.d(5): Error: function core.stdc.stdlib.free (void* ptr) is not callable using argument types (immutable(int)*)

@MartinNowak
Copy link
Member

What's up with the test failure?

@MartinNowak
Copy link
Member

This reminds me of the fact, that the IAllocator interface (std.allocator) should also have a strongly pure allocate function. This might allow the compiler to optimize allocations even with a dynamic dispatch allocator, because it assumes that an allocation doesn't have a side-effect and always returns "fresh" unaliased memory.

Chandler Carruth was talking about this problem at the last CppCon.
https://www.youtube.com/watch?v=fHNmRkzxHWs&t=3950
https://www.youtube.com/watch?v=fHNmRkzxHWs&t=4037

@schveiguy
Copy link
Member

No, it can't be strong pure, you can only call free with a pointer to mutable.

My concern isn't calling free with an immutable, but calling free from inside a pure function that has an immutable:

void freeIt(immutable(int)* x) pure
{
   free(cast(void *)x);
}

Basically, if you can't free immutable malloc'd data in pure functions, then you can't create immutable malloc'd data with pure functions. I don't think that cast requirement will stop people.

@MartinNowak
Copy link
Member

Indeed, looks troublesome. Especially because memory allocated by strong pure functions is implicitly convertible to immutable (because of the unique ownership).

@MartinNowak
Copy link
Member

Let's close this until better arguments come up.

@JakobOvrum
Copy link
Author

These absolutely cannot be @trusted.

They have a safe interface. They absolutely can.

At some point someone accidentally marked alloca as @trusted :o.

alloca can't be trusted because the memory it allocates is implicitly deallocated with no regard to living references. malloc can be trusted because deallocation only happens with the explicit, unsafe free.

@MartinNowak
Copy link
Member

This comes up from time to time, the main problem to solve is that a pure malloc also requires a pure free to make sense.

@MartinNowak
Copy link
Member

So if we also make free weakly pure, than this would be useful. The only catch is that whomever uses it, needs to be aware of the fact that freeing immutable data might ellide the call. Sounds like it's worth a try, b/c the problem can be handled by lifetime wrappers such as Unique or RefCounted.

@MartinNowak MartinNowak reopened this May 15, 2015
@MartinNowak
Copy link
Member

Ping @JakobOvrum, can you extend that to free please?

@JakobOvrum
Copy link
Author

Made free pure.

Basically, if you can't free immutable malloc'd data in pure functions, then you can't create immutable malloc'd data with pure functions. I don't think that cast requirement will stop people.

You don't have to allocate and deallocate in the same function. As for that cast, free should really take const(void)*, but I'd rather tackle that when we can use emplace or something like it to allocate/construct immutable data.

@ibuclaw
Copy link
Member

ibuclaw commented Oct 11, 2015

This means a free of some address may be optimized out by the compiler thinking it will not make any global changes.

If the compiler can treat D pure in the C sense, I suspect you need more guarantee than strong pure (at least not without false positives).

@DmitryOlshansky
Copy link
Member

Not sure why this fails but anyhow - anything else left for this ?

@nordlow
Copy link
Contributor

nordlow commented Mar 26, 2016

I totally agree on this pure decision.

AFAICT making malloc pure fails make unittest. All other changes pass make unittest.

Seems like DMD purity optimizations on multiple successive calls to malloc gets in the way:

...
Testing link
generated/linux/release/64/link 
*** Error in `generated/linux/release/64/link': double free or corruption (fasttop): 0x00000000014a8a10 ***
...
Testing linkDR
generated/linux/release/64/linkDR 
generated/linux/debug/64/unittest/test_runner rt.arrayassign
*** Error in `generated/linux/release/64/linkD': double free or corruption (fasttop): 0x0000000001fbc890 ***
*** Error in `Testing loadDR
generated/linux/release/64/loadDR /home/per/Work/druntime/generated/linux/release/64/libdruntime.so
0.000s PASS debug64 rt.arrayassign
generated/linux/release/64/linkDR': double free or corruption (fasttop): 0x00000000019a9680 ***
*** Error in `generated/linux/release/64/loadDR': double free or corruption (fasttop): 0x0000000001dc81c0 ***
generated/linux/debug/64/unittest/test_runner rt.arraycast
generated/linux/debug/64/unittest/test_runner rt.arraybyte
0
...

AFAICT, we need some function qualifier, say @Allocator, to indicate that a function is a memory allocator. Such a function may be called by pure callers but shouldn't be subject to strong purity-optimizations.

Any ideas, @WalterBright ?

@WalterBright
Copy link
Member

malloc(10) can sometimes return a pointer, and sometimes null. This makes it not pure. (GC.malloc() never returns null, as it terminates the program if memory is exhausted.)

@PetarKirov
Copy link
Member

I agree with @WalterBright. The appropriate place for these sort of guarantees is std.allocator.mallocator. We really shouldn't endorse direct usage of malloc and free. It's only OK add attributes in a private extern declaration that wrapped appropriately to ensure correct behaviour.

@nordlow
Copy link
Contributor

nordlow commented Mar 28, 2016

@ZombineDev interesting. Does adding "attributes in a private extern declaration" work today?

If so, I see no reason why Mallocator, MmapAllocator and alikes shouldn't be purity-enhanced through such private extern declarations as long as the names are C-mangled.

AFAICT, its allocate and deallocate and rellocate cannot otherwise be used in a pure context which, IMHO, is a severe limitation.

Update: I tried replacing

import core.stdc.stdlib : malloc;

with

extern(C) private void* malloc(size_t size) shared pure @system nothrow @nogc;

in mallocator.d but that change triggers the DMD (linker) error:

mallocator.o: In function `shared(nothrow @nogc @trusted void[] function(ulong)) std.experimental.allocator.mallocator.Mallocator.allocate':
mallocator.d:31: undefined reference to `shared(extern (C) pure nothrow @nogc void* function(ulong)) std.experimental.allocator.mallocator.Mallocator.malloc'
collect2: error: ld returned 1 exit status
--- errorlevel 1

What's wrong with my extern declaration?

Update: Ahh. I solved it. I had to put the extern declaration at the module level and not inside the definition of Mallocator. Otherwise I guess the mangling becomes wrong. Now Mallocator.allocate can be tagged as pure!

Should I create a PR for mallocator.d and alikes?

@WalterBright
Copy link
Member

You can make a pure allocator like this:

extern (C) void *pureMalloc(size_t size) {
    auto p = malloc(size);
    if (!p) abort_program();
    return p;
}

and then in the import:

extern (C) void* mallocPure(size_t size) pure;

which works because C mangling is not affected by the attributes. No compiler extensions are needed.

@nordlow
Copy link
Contributor

nordlow commented Mar 28, 2016

@WalterBright is this pattern preferred over a module private import like

extern(C) void* malloc(size_t size) shared pure @system nothrow @nogc;

?

@PetarKirov
Copy link
Member

@nordlow I think that your previous linker error was becuase you defined a shared function pointer variable named malloc, instead of forward declaring the function malloc. I think this is because shared can only be put on member-functions and variables. It has no meaning on free functions. E.g.:

shared var = 5; // shared variable of type int
shared void* malloc(size_t); shared variable of type function pointer.

Removing shared should solve the linking problem you mentioned.

@JakobOvrum
Copy link
Author

The OOM-checked code posted here was a part of dlang/phobos/pull/3031, which served as much of the motivation for filing this PR. I agree that the null return value is an unacceptable source of impurity. I'll remove pure from this PR.

Note that std.allocator also returns null on OOM, both allocate and make[Array].

@JakobOvrum JakobOvrum changed the title Make malloc and calloc pure and @trusted Make malloc and calloc @trusted Apr 4, 2016
@andralex
Copy link
Member

andralex commented Apr 4, 2016

So... shall we close this?

@DmitryOlshansky
Copy link
Member

So... shall we close this?
Hide all checks

What if we somehow magically mark malloc and friends as weak-pure (regardless of what signature tells) in the compiler? That should allow us to use malloc/free in pure functions w/o the side effect of optimizing them away.

@schveiguy
Copy link
Member

@DmitryOlshansky What about a function that accepts an immutable pointer that it frees? How do we stop it from eliding that call if free is weak pure? See my example above.

If we are going to treat free specially, I'd like to see a general mechanism for this instead of magic compilers. Even if it's undocumented/obscure.

I still don't think it's a good idea...

@DmitryOlshansky
Copy link
Member

Weak pure calls cannot be elided in general, so no point in trying, not even in pure functions.

@DmitryOlshansky
Copy link
Member

Adding special uda "WeakPure" should do the trick, meaning that compiler shouldn't attempt optimizations based on purity (in other words treat as impure except that it's usable in pure functions)

@schveiguy
Copy link
Member

Weak pure calls cannot be elided in general

Right, but strong pure functions that call weak pure functions can. This was my point here: #1183 (comment)

@DmitryOlshansky
Copy link
Member

Right, but strong pure functions that call weak pure functions can. This was my point here: #1183 (comment)

Okay... Just add @dontOptimizeOut and be done about it. I mean we can keep twisting our hands sideways but malloc/free needs to be callable in a pure functions to make looots of stuff pure so let's do it somehow.

@schveiguy
Copy link
Member

Or... we just cheat with extern(C) for the small cases (e.g. Ref counting) where it is safe. Marking free pure opens any users up to a huge number of problems.

@WalterBright
Copy link
Member

I'm going to close this. There are reasonable ways outlined here to use malloc/free in pure code. There is no need to make them pure in the general case, which would make pure a laughingstock.

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