Thread overview
[core.reflect] Detect Padding in structs or classes
Oct 01, 2021
Stefan Koch
Oct 01, 2021
Stefan Koch
Oct 01, 2021
Stefan Koch
Oct 02, 2021
Stefan Koch
October 01, 2021

There has been a PR to introduce -vpadding into dmd which warns on padding in structs.
This can actually be trivially done by core reflect.

// detect padding in aggregates
import core.reflect.reflect;
import core.reflect.transitiveVisitor;

struct PadDetctedTuple
{
    string aggregateName;
    ulong number_of_pad_bytes;
}

struct S
{
    ubyte a; // <--- is at offset 0
    ubyte b; // <--- is at offset 1
    // two bytes of padding
    uint d; // <--- is at offset 4
}

static assert(detectPadding(node)[0] == PadDetctedTuple("S", 2)); // yes we detected that S has 2 bytes of padding

static assert(S.init.a.offsetof == 0);
static assert(S.init.b.offsetof == 1);
static assert(S.init.d.offsetof == 4);

static immutable sc = currentScope();
static immutable node = nodeFromName("S", ReflectFlags.Members, sc);


pragma(msg, detectPadding(node));


PadDetctedTuple[] detectPadding(const Node n)
{
    class PadDetectVisitor : TransitiveVisitor
    {
        alias visit = TransitiveVisitor.visit;

        override void visit(AggregateDeclaration ad)
        {
            ulong lastOffset;
            ulong lastSize;
            ulong wastedBytes;
            foreach(f;ad.fields)
            {
                wastedBytes += ((f.offset - lastOffset) - lastSize);
                lastOffset = f.offset;
                lastSize = f.type.size;
            }
            if (wastedBytes)
                pads ~= PadDetctedTuple(ad.name, wastedBytes);
        }

        PadDetctedTuple[] pads = null;
    }

    scope padDetector = new PadDetectVisitor();
    (cast()n).accept(padDetector);

    return padDetector.pads;
}
October 01, 2021

On Friday, 1 October 2021 at 17:35:24 UTC, Stefan Koch wrote:

>

There has been a PR to introduce -vpadding into dmd which warns on padding in structs.
This can actually be trivially done by core reflect.

Ah dang.

This code will be bogus for classes as we need it iterate the base classes for this
Which we can do but not when visiting the superclass of both Struct and Class Declarations.

We need to specialize for classes.
And add the following code to the visitor

            override void visit(ClassDeclaration cd)
            {
                ulong lastOffset;
                ulong lastSize;
                ulong wastedBytes;

                // for classes we need to keep a stack of bases
                // let's go!
                ClassDeclaration[] baseStack = [cd];
                for(auto base = cd.base; base; base = base.base)
                {
                    baseStack ~= base;
                }

                foreach_reverse(base;baseStack)
                {
                    foreach(f;base.fields)
                    {
                        wastedBytes += ((f.offset - lastOffset) - lastSize);
                        lastOffset = f.offset;
                        lastSize = f.type.size;
                    }
                }

                if (wastedBytes)
                    pads ~= PadDetctedTuple(cd.name, wastedBytes);
            }

And once we did that

static immutable stdio_n = nodeFromName("std_stdio", ReflectFlags.Members | ReflectFlags.MemberRecursion, sc);
pragma(msg, detectPadding(stdio_n));

will result in
[PadDetctedTuple("_IO_FILE", 8LU), PadDetctedTuple("_IO_FILE", 8LU), PadDetctedTuple("StdioException", 16LU)]

So yes the StdioExecption is somewhat fatty. But not 76 bytes of waste ;)

October 01, 2021

On Friday, 1 October 2021 at 18:30:35 UTC, Stefan Koch wrote:

>

On Friday, 1 October 2021 at 17:35:24 UTC, Stefan Koch wrote:

>

There has been a PR to introduce -vpadding into dmd which warns on padding in structs.
This can actually be trivially done by core reflect.

Ah dang.

This code will be bogus for classes as we need it iterate the base classes for this
Which we can do but not when visiting the superclass of both Struct and Class Declarations.

We need to specialize for classes.
And add the following code to the visitor

            override void visit(ClassDeclaration cd)
            {
                ulong lastOffset;

Ah double dang.
This code needs to be fixed.
For classes we start at offset 16 because of class-metadata like the vtbl and monitor being prepended. So it should read
ulong lastOffset = 16

October 02, 2021

On Friday, 1 October 2021 at 17:35:24 UTC, Stefan Koch wrote:

>

There has been a PR to introduce -vpadding into dmd which warns on padding in structs.
This can actually be trivially done by core reflect.

And there are some results.

diff --git a/std/file.d b/std/file.d
index 741656d..0d1c17c 100644
--- a/std/file.d
+++ b/std/file.d
@@ -4578,7 +4578,7 @@ version (Posix) @system unittest
 /**
  * Dictates directory spanning policy for $(D_PARAM dirEntries) (see below).
  */
-enum SpanMode
+enum SpanMode : ubyte
 {
     /** Only spans one directory. */
     shallow,
@@ -4635,15 +4635,15 @@ enum SpanMode
 private struct DirIteratorImpl
 {
   @safe:
-    SpanMode _mode;
+    DirEntry _cur;
+    DirHandle[] _stack;
+    DirEntry[] _stashed; //used in depth first mode
     // Whether we should follow symlinked directories while iterating.
     // It also indicates whether we should avoid functions which call
     // stat (since we should only need lstat in this case and it would
     // be more efficient to not call stat in addition to lstat).
     bool _followSymlink;
-    DirEntry _cur;
-    DirHandle[] _stack;
-    DirEntry[] _stashed; //used in depth first mode
+    SpanMode _mode;

This patch to phobos saves 4 bytes or so ;)
It's not much but it comes practically for free :-)

With my core.reflect extension it was just a questing of asking in between which fields the padding was.

You could save another byte by folding _followSymlink into the SpanMode flags.
But that and the PR to Phobos are left as an exercise for the reader.