Bug 9614 – Uninitialized holes in function stack frames confuses GC

Status
NEW
Severity
enhancement
Priority
P4
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2013-02-27T22:39:42Z
Last change time
2024-12-13T18:04:25Z
Assigned to
No Owner
Creator
Denis Shelomovskii
Moved to GitHub: dmd#18527 →

Comments

Comment #0 by verylonglogin.reg — 2013-02-27T22:39:42Z
Because of uninitialized holes in function stack frames for padding and temporaries lots of incorrect roots are created. Unless we have a precise GC we need to zero-fill such holes. And even with precise GC holes for temporaries which can point to GC memory also have to be zero-filled. Example: --- ... // `f` has some stack pointers to GC memory f(); ... // some stack pointers from `f` will be in holes // of `collect` and subsequent functions GC.collect(); ... --- A quick library workaround for now can be at least clear enough stack memory on `GC.collect` call to allow it collect in cases like in example above.
Comment #1 by ag0aep6g — 2015-05-01T22:33:26Z
A complete test case: ---- import std.stdio; size_t from_f, from_g; void main () { put(); f(); writefln("%#x", from_f); /* prints "0x1111" */ writefln("%#x", from_g); /* prints "0x1111" */ } void put() { /* Put 0x1111 where there will be a gap between f's stack frame and g's stack frame. */ size_t[2] a; a[0] = 0x1111; } void f() { size_t a; from_f = *(&a - 1); /* This reads put's 0x1111. This isn't so bad as we're reading from beyond the live stack. */ g(); } void g() { size_t a; from_g = *(&a + 3); /* Reads put's 0x1111 again. This is bad, because we're reading from the middle of the live stack. If the value were a GC pointer, it would keep its allocation alive. */ } ---- Tested with dmd 2.067.1 on linux. Problem doesn't show with -m32. ldc doesn't seem to have the issue.
Comment #2 by belka — 2017-10-05T11:55:11Z
This problem affects dmd, with or without -m32, and ldc aswell. When referencing memory via a variable on the stack, by the time the garbage collector runs, due to alignment and =voids the stack reference to the variable has not necessarily been overwritten. As a result, memory may be kept alive by garbage pointers. Even calling the GC manually high up the stack does not necessarily solve this, as by the time the GC determines the stack pointer, it's already several stackframes down from usercode. This is especially problematic if the pointer is to a heavily-crossreferenced data structure, such as an XML tree, where a single pointer anywhere into the tree may keep the whole thing alive. The correct solution would be to find a way to make stack garbage collection type-aware; however, the mechanics of this heavily depend on the compiler backend. A compiler-level workaround may be to add a flag to zero any gaps in the stack and initialize void variables to 0. Alternatively, a user-level workaround may be to periodically wipe the stack somewhere early in the calltree. import core.memory, std.stdio, core.thread; void test() { // Allocate some memory and store it on a variable on the stack. auto array = new ubyte[128*1024*1024]; // The variable's lifetime ends here. } void purify() { ubyte[0x1000] filler = void; import core.stdc.string: memset; // To ensure the compiler doesn't optimize out the unused variable, zero it manually. memset(filler.ptr, 0, 0x1000); } void main() { writeln("Allocate 100MB"); auto before = GC.stats.usedSize; test(); // purify(); GC.collect(); auto after = GC.stats.usedSize; assert(after < before + 100*1024*1024, "GC failed to free the allocation!"); writeln("GC successfully freed allocated memory."); }
Comment #3 by robert.schadek — 2024-12-13T18:04:25Z
THIS ISSUE HAS BEEN MOVED TO GITHUB https://github.com/dlang/dmd/issues/18527 DO NOT COMMENT HERE ANYMORE, NOBODY WILL SEE IT, THIS ISSUE HAS BEEN MOVED TO GITHUB