Regarding emplace, arrays, and helper functions
Andrej Mitrovic
andrej.mitrovich at gmail.com
Thu Aug 29 05:20:10 PDT 2013
The emplace docs state that the chunk where to store the class object
instance needs to be aligned to the class type alignment. But it
doesn't say much on how to get this alignment from a class (we could
add a note about using the classInstanceAlignment template), or even
how to use it to create e.g. an array of class objects (not an array
of references).
Here's how one can run into a problem:
-----
import std.conv;
class C
{
this(ubyte b) { _b = b; }
ubyte _b;
}
void main()
{
// 9 bytes (x32), it's not going to align itself properly
enum Size = __traits(classInstanceSize, C);
ubyte[Size][2] buffer;
// off-topic: cast needed -> yet another emplace bug
auto obj1 = emplace!C(buffer[0], cast(ubyte)20);
assert(obj1._b == 20);
auto obj2 = emplace!C(buffer[1], cast(ubyte)20); // Boom!
assert(obj2._b == 20);
}
-----
On the second emplace call, an exception is thrown:
-----
std.conv.ConvException at std\conv.d(3832): emplace: Misaligned memory
block (0x18FD41): it must be 4-byte aligned for type C
-----
So one has to figure out how to do alignment properly, but there's no
real documentation on how to do it. I had a look at the scoped()
implementation for reference, but it's quite complex, here's a
snippet:
-----
template scoped(T)
if (is(T == class))
{
// _d_newclass now use default GC alignment (looks like
(void*).sizeof * 2 for
// small objects). We will just use the maximum of filed alignments.
alias classInstanceAlignment!T alignment;
alias _alignUp!alignment aligned;
static struct Scoped
{
// Addition of `alignment` is required as `Scoped_store` can
be misaligned in memory.
private void[aligned(__traits(classInstanceSize, T) +
size_t.sizeof) + alignment] Scoped_store = void;
@property inout(T) Scoped_payload() inout
{
void* alignedStore = cast(void*) aligned(cast(size_t)
Scoped_store.ptr);
// As `Scoped` can be unaligned moved in memory class
instance should be moved accordingly.
immutable size_t d = alignedStore - Scoped_store.ptr;
size_t* currD = cast(size_t*) &Scoped_store[$ - size_t.sizeof];
if(d != *currD)
{
import core.stdc.string;
memmove(alignedStore, Scoped_store.ptr + *currD,
__traits(classInstanceSize, T));
*currD = d;
}
return cast(inout(T)) alignedStore;
}
alias Scoped_payload this;
@disable this();
@disable this(this);
~this()
{
// `destroy` will also write .init but we have no
functions in druntime
// for deterministic finalization and memory releasing for now.
.destroy(Scoped_payload);
}
}
/// Returns the scoped object
@system auto scoped(Args...)(auto ref Args args)
{
Scoped result = void;
void* alignedStore = cast(void*) aligned(cast(size_t)
result.Scoped_store.ptr);
immutable size_t d = alignedStore - result.Scoped_store.ptr;
*cast(size_t*) &result.Scoped_store[$ - size_t.sizeof] = d;
emplace!(Unqual!T)(result.Scoped_store[d .. $ - size_t.sizeof], args);
return result;
}
}
----
It uses a private _alignUp helper template as well:
-----
private size_t _alignUp(size_t alignment)(size_t n)
if(alignment > 0 && !((alignment - 1) & alignment))
{
enum badEnd = alignment - 1; // 0b11, 0b111, ...
return (n + badEnd) & ~badEnd;
}
-----
There is a lot of magic here that's hard to understand.
Anyway, since we already have emplace() as a public Phobos function, I
thought it might be good if we had another overload which created an
array of objects, or even a static array of objects wrapped in a
convenient random access range on top. Otherwise guaranteeing
alignment by hand seems to be complicated (judging from the scoped
template implementation).
I'm just pseudocoding here:
-----
class Point
{
this(int x, int y)
{
_x = x;
_y = y;
}
int _x;
int _y;
}
void main()
{
// arr is a struct instance which holds the aligned static array,
// which itself holds the Point objects.
// The struct has opIndex, alias this, etc, to simulate
// an array of references.
auto arr = emplaceStaticArray!(Point, 2); // 2 items
// note: one /could/ also allow calling the ctors in the above call,
// although the syntax could be tricky
arr[0].initialize(1, 2); // destroys existing state (if any),
calls the ctor
arr[1].initialize(3, 4); // ditto
Point point = arr[0]; // get the object reference via alias this
(just like in scoped)
assert(point.x == 1 && point.y == 2);
}
-----
An equivalent emplaceArray for dynamic (resizable) arrays could also
be implemented, where the memory can expand and shrink as necessary.
Thoughts? I know bearophile is probably interested. :)
More information about the Digitalmars-d-learn
mailing list