Thread overview | ||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
March 20, 2018 #dbugfix 17592 | ||||
---|---|---|---|---|
| ||||
This is very important to me as I am very interested in using the language for game development. Yes I know that it's marked as "Duplicated", but I strongly disagree as it is different enough to consider is own issue. Alex |
March 21, 2018 Re: #dbugfix 17592 | ||||
---|---|---|---|---|
| ||||
Posted in reply to 12345swordy | On Tuesday, 20 March 2018 at 21:27:53 UTC, 12345swordy wrote:
> This is very important to me as I am very interested in using the language for game development.
>
> Yes I know that it's marked as "Duplicated", but I strongly disagree as it is different enough to consider is own issue.
>
> Alex
The game development part seems to be irrelevant, because D has been used to make plenty of games.
It is however relevant to how your game would be developed, but your post make it seem like D can't be used to develop games at all.
|
March 21, 2018 Re: #dbugfix 17592 | ||||
---|---|---|---|---|
| ||||
Posted in reply to 12345swordy | On Tuesday, 20 March 2018 at 21:27:53 UTC, 12345swordy wrote:
> This is very important to me as I am very interested in using the language for game development.
>
> Yes I know that it's marked as "Duplicated", but I strongly disagree as it is different enough to consider is own issue.
>
> Alex
Noted.
|
March 21, 2018 Re: #dbugfix 17592 | ||||
---|---|---|---|---|
| ||||
Posted in reply to 12345swordy | On Tuesday, 20 March 2018 at 21:27:53 UTC, 12345swordy wrote: > This is very important to me as I am very interested in using the language for game development. > > Yes I know that it's marked as "Duplicated", but I strongly disagree as it is different enough to consider is own issue. > > Alex https://issues.dlang.org/show_bug.cgi?id=17592 Yeah that's pretty poopy. I looked into this and I think this is fixable. Here's my proof of concept: ----------------- import core.stdc.stdio; import std.traits; /******************************************************************* * This code was copied right out of druntime and * attributed with @nogc *******************************************************************/ extern (C) void rt_finalize(void *data, bool det=true) @nogc; void destroy(T)(T obj) @nogc if (is(T == class)) { rt_finalize(cast(void*)obj); } /******************************************************************* * This emplace implementation below was copied * right out of std.conv and attributed with @nogc *******************************************************************/ @nogc pure nothrow @safe void testEmplaceChunk(void[] chunk, size_t typeSize, size_t typeAlignment, string typeName) { assert(chunk.length >= typeSize, "emplace: Chunk size too small."); assert((cast(size_t) chunk.ptr) % typeAlignment == 0, "emplace: Chunk is not aligned."); } T emplace(T, Args...)(T chunk, auto ref Args args) @nogc if (is(T == class)) { static assert(!isAbstractClass!T, T.stringof ~ " is abstract and it can't be emplaced"); // Initialize the object in its pre-ctor state enum classSize = __traits(classInstanceSize, T); (() @trusted => (cast(void*) chunk)[0 .. classSize] = typeid(T).initializer[])(); static if (isInnerClass!T) { static assert(Args.length > 0, "Initializing an inner class requires a pointer to the outer class"); static assert(is(Args[0] : typeof(T.outer)), "The first argument must be a pointer to the outer class"); chunk.outer = args[0]; alias args1 = args[1..$]; } else alias args1 = args; // Call the ctor if any static if (is(typeof(chunk.__ctor(args1)))) { // T defines a genuine constructor accepting args // Go the classic route: write .init first, then call ctor chunk.__ctor(args1); } else { static assert(args1.length == 0 && !is(typeof(&T.__ctor)), "Don't know how to initialize an object of type " ~ T.stringof ~ " with arguments " ~ typeof(args1).stringof); } return chunk; } T emplace(T, Args...)(void[] chunk, auto ref Args args) @nogc if (is(T == class)) { enum classSize = __traits(classInstanceSize, T); testEmplaceChunk(chunk, classSize, classInstanceAlignment!T, T.stringof); return emplace!T(cast(T)(chunk.ptr), args); } /******************************************************************* * This code was copied from https://wiki.dlang.org/Memory_Management#Explicit_Class_Instance_Allocation * and attributed with @nogc *******************************************************************/ class TestClass { int x; this(int x) @nogc { puts("TestClass's constructor called"); this.x = x; } ~this() @nogc { puts("TestClass's destructor called"); } } T heapAllocate(T, Args...) (Args args) @nogc { import core.stdc.stdlib : malloc; import core.memory : GC; // get class size of class instance in bytes auto size = __traits(classInstanceSize, T); // allocate memory for the object auto memory = malloc(size)[0..size]; if(!memory) { import core.exception : onOutOfMemoryError; onOutOfMemoryError(); } puts("Memory allocated"); // notify garbage collector that it should scan this memory GC.addRange(memory.ptr, size); // call T's constructor and emplace instance on // newly allocated memory return emplace!(T, Args)(memory, args); } void heapDeallocate(T)(T obj) @nogc { import core.stdc.stdlib : free; import core.memory : GC; // calls obj's destructor destroy(obj); // garbage collector should no longer scan this memory GC.removeRange(cast(void*)obj); // free memory occupied by object free(cast(void*)obj); puts("Memory deallocated"); } void main() @nogc { // allocate new instance of TestClass on the heap auto test = heapAllocate!TestClass(42); scope(exit) { heapDeallocate(test); } printf("test.x = %d\n", test.x); } ------------- Step 1. Make `emplace` @nogc So we need to attribute `std.conv.emplace` as @nogc. Based on the code above, that looks feasible. The difficulty, though will be writing thorough tests for it. Step 2. Make `destroy` @nogc `destroy` simply calls `rt_finalize` in the runtime, at least for classes. I declared it as `@nogc` in the code above, but that's a cheat, though I think `rt_finalize` can be made `@nogc` in the runtime. I'll try to do step 2. If someone wants to help with step 1, that would be great. Mike |
March 21, 2018 Re: #dbugfix 17592 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Mike Franklin | On Wednesday, 21 March 2018 at 08:49:11 UTC, Mike Franklin wrote:
> I think `rt_finalize` can be made `@nogc` in the runtime.
And this is where you're wrong. Consider this:
class A {
@nogc ~this() {}
}
class B : A {
~this() {}
}
A a = new B();
destroy(a); // Is this @nogc?
Essentially, since @nogc and other qualifiers aren't inherited on dtors, it's impossible to know if destroying an instance of a non-final class is @nogc.
There's one case where you can: final classes where no superclass and no member defines a non-@nogc destructor.
In order for this to be done in the general case, dtors need to inherit their qualifiers somehow. That's at the very least a DIP, and any chosen path is highly likely to break existing code.
--
Simen
|
March 21, 2018 Re: #dbugfix 17592 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Simen Kjærås | On Wednesday, 21 March 2018 at 11:13:41 UTC, Simen Kjærås wrote:
> On Wednesday, 21 March 2018 at 08:49:11 UTC, Mike Franklin wrote:
>
>> I think `rt_finalize` can be made `@nogc` in the runtime.
>
> And this is where you're wrong. Consider this:
>
> class A {
> @nogc ~this() {}
> }
>
> class B : A {
> ~this() {}
> }
>
> A a = new B();
> destroy(a); // Is this @nogc?
>
>
> Essentially, since @nogc and other qualifiers aren't inherited on dtors, it's impossible to know if destroying an instance of a non-final class is @nogc.
>
> There's one case where you can: final classes where no superclass and no member defines a non-@nogc destructor.
>
> In order for this to be done in the general case, dtors need to inherit their qualifiers somehow. That's at the very least a DIP, and any chosen path is highly likely to break existing code.
>
> --
> Simen
You can simply check the .dtor symbols at compile time to see if every .dtor symbol from child to root have a .dtor that have the @nogc attribute(and other attributes as well). If it does, add that attribute to destroy.
|
March 21, 2018 Re: #dbugfix 17592 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Mike Franklin | On Wednesday, 21 March 2018 at 08:49:11 UTC, Mike Franklin wrote: > Step 1. Make `emplace` @nogc > So we need to attribute `std.conv.emplace` as @nogc. No, do not do that! `emplace` is ALREADY `@nogc` when given appropriate arguments. Adding the explicit annotation will limit its flexibility without enabling any new uses. This compiles right now: --- import std.conv; class Foo { this() @nogc {} } @nogc void main() { char[__traits(classInstanceSize, Foo)] buffer; emplace!Foo(buffer[]); } --- `emplace` is `@nogc` if the constructor it calls is `@nogc`. The real bug with nogc and things like emplace are the error messages. They should tell you the item in the call chain where @nogc is NOT inferred. So if the ctor there was not annotated right now it says: Error: @nogc function D main cannot call non-@nogc function std.conv.emplace!(Foo).emplace Well, it should say: Error: @nogc function D main cannot call non-@nogc function std.conv.emplace!(Foo).emplace >> emplace was not inferred because it calls non-@nogc function `Foo.this()` That supplemental bit with the >> indicates the ACTUAL reason nogc failed. Change that and the rest of the call tree becomes nogc automatically. I seem to remember filing this to bugzilla sometime but bugzilla search is so unbelievably bad I can never find anything on there so idk for sure. But this error message change would be my #1 request for nogc. It'd keep others from chasing false leads. > Step 2. Make `destroy` @nogc > `destroy` simply calls `rt_finalize` in the runtime, at least for classes. I declared it as `@nogc` in the code above, but that's a cheat, though I think `rt_finalize` can be made `@nogc` in the runtime. This cannot be proven at compile time since it calls destructors based on a dynamic type which may or may not keep the nogc promise. Of course, dtors that gc allocate are usually broken anyway... but it isn't a compile time guarantee in the current language and a library change cannot fix that alone. |
March 21, 2018 Re: #dbugfix 17592 | ||||
---|---|---|---|---|
| ||||
Posted in reply to 12345swordy | On Wednesday, 21 March 2018 at 13:39:28 UTC, 12345swordy wrote:
> You can simply check the .dtor symbols at compile time to see if every .dtor symbol from child to root have a .dtor that have the @nogc attribute
In Simen's example, the child information is not available at compile time. This line here:
A a = new B();
discards the static type. The compiler could probably cheat and figure it out anyway in this example, but suppose:
---
class A {
@nogc ~this() {}
}
class B : A {
~this() {}
}
class C : A {
@nogc ~this() {}
}
A build(string name) {
if(name == "B") return new B();
else return new C();
}
void main() {
A a = build(readln());
destroy(a);
}
---
This is very clearly a runtime decision: whether it is B or C is determined by user input. But B's dtor is not @nogc... and there's thus no way to tell for sure if destroy(a) is or not, since it will call the child class based on the runtime decision.
so it is impossible for the compiler to know which child class is actually called until runtime... too late for a compile-time nogc check.
|
March 21, 2018 Re: #dbugfix 17592 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Adam D. Ruppe | On Wednesday, 21 March 2018 at 14:04:58 UTC, Adam D. Ruppe wrote:
> On Wednesday, 21 March 2018 at 13:39:28 UTC, 12345swordy wrote:
>> You can simply check the .dtor symbols at compile time to see if every .dtor symbol from child to root have a .dtor that have the @nogc attribute
>
> In Simen's example, the child information is not available at compile time. This line here:
>
> A a = new B();
>
> discards the static type. The compiler could probably cheat and figure it out anyway in this example, but suppose:
>
> ---
> class A {
> @nogc ~this() {}
> }
>
> class B : A {
> ~this() {}
> }
>
> class C : A {
> @nogc ~this() {}
> }
>
>
> A build(string name) {
> if(name == "B") return new B();
> else return new C();
> }
>
> void main() {
> A a = build(readln());
> destroy(a);
> }
> ---
>
>
> This is very clearly a runtime decision: whether it is B or C is determined by user input. But B's dtor is not @nogc... and there's thus no way to tell for sure if destroy(a) is or not, since it will call the child class based on the runtime decision.
>
> so it is impossible for the compiler to know which child class is actually called until runtime... too late for a compile-time nogc check.
That seems to be it's own separate problem, as it involves generating dynamic types at run-time, which it needs run-time equivalent of attribute checking. My example assumes that the classes created are static types not dynamic types. Besides I do not like implied conversions when it comes classes, as I believe it is a horrible idea.
|
March 22, 2018 Re: #dbugfix 17592 | ||||
---|---|---|---|---|
| ||||
Posted in reply to 12345swordy | On Wednesday, 21 March 2018 at 19:21:15 UTC, 12345swordy wrote: > That seems to be it's own separate problem, as it involves generating dynamic types at run-time, which it needs run-time equivalent of attribute checking. But @nogc is a compile time thing, meaning it cannot work here. > My example assumes that the classes created are static types not dynamic types. Besides I do not like implied conversions when it comes classes, as I believe it is a horrible idea. All classes are dynamic types, this is their reason for existing! |
Copyright © 1999-2021 by the D Language Foundation