July 06
On 7/5/2024 8:23 PM, Steven Schveighoffer wrote:
> My recommendation still is either:
> 
> 1. Denote D bitfields by a specified layout system (pick the most common C one and do that). C bitfields can match the C compiler.
> 2. Simply forbid problematic alignments at compile time:
> 
> ```d
> struct S {
>     uint x;
>     uint64 a : 24;
>     uint64 b : 24;
>     uint64 c : 16;
> }
> 
> // error, alignment of bitfield `a` may not match C layout, please use padding or aligned bitfields to specify intended layout.
> 
> // these are OK.
> struct SWithPadding {
>     uint x;
>     uint _; // padding
>     uint64 a : 24;
>     uint64 b : 24;
>     uint64 c : 16;
> }
> 
> struct SPacked {
>     uint64 x : 32;
>     uint64 a : 24;
>     uint64 b : 24;
>     uint64 c : 16;
> }
> ```
> 
> Maybe the error only occurs if you specify a compiler switch?

It's clear how to get the desired portable layout.

Consider that nobody ever requested a warning on:

```
struct S {
    uint x;
    ulong y;
}
```
which is the equivalent. Not for D, C, or C++. At least none directed at my compilers.

I would be good with a note about this technique in the specification.

P.S. We've already got soooo many compiler switches, adding another one needs a strong case. Every such switch is a bug :-/
July 07
On 07/07/2024 11:19 AM, Walter Bright wrote:
> On 7/6/2024 8:54 AM, Timon Gehr wrote:
> 
>     The point was: D should actually specify more bitfield layout
>     guarantees than the C standard.
> 
> I understand that. Given that any desired portable bitfield layout can be done with minimal effort, there is no need to add more semantics to the language than what C does.

You have an expert understanding of the subject matter.

Nobody else around here has this knowledge or expertise.

As of right now there does not appear to be a single person on the D Discord server that understands how to use C bit fields to have predictable behavior let alone portable.

I understand that you think that this is simple, but nobody else can understand it, and you are failing to explain it sufficiently.

If somebody has their program failing, it will be hard to diagnose the problem let alone explain it. The only person who can do this is you. That does not scale.

At this point multiple people who are usually responsible for explaining language features to other people and diagnosing programs, are telling you that they cannot use it as intended, this should be sending up major red flags that only you can use this feature.

Please seriously reconsider the ``extern(D)``/``extern(C)`` split, because right now we will have no choice but to have DScanner issue a warning for improper use of bit fields, and that is quite frankly ridicules that a brand new ``extern(D)`` language feature needs a warning.

July 07

On Friday, 5 July 2024 at 05:37:50 UTC, Richard (Rikki) Andrew Cattermole wrote:

>

Today many people have spent some time to try and understand Walter's belief that C is "good enough" for bit fields in terms of guarantees.

Can I define my D struct using bitfields, and then correctly unpack them in a GLSL shader using bitfieldExtract() with the predicted offsets and sizes? If the answer is no, then that bitfield implementation belongs in a garbage can.

Currently the answer to this question is "yes" for std.bitmanip and "no" for "native" D bitfields.

July 07
On 07/07/2024 2:22 PM, cc wrote:
> On Friday, 5 July 2024 at 05:37:50 UTC, Richard (Rikki) Andrew Cattermole wrote:
>> Today many people have spent some time to try and understand Walter's belief that C is "good enough" for bit fields in terms of guarantees.
> 
> Can I define my D struct using bitfields, and then correctly unpack them in a GLSL shader using bitfieldExtract() with the predicted offsets and sizes?  If the answer is no, then that bitfield implementation belongs in a garbage can.
> 
> Currently the answer to this question is "yes" for std.bitmanip and "no" for "native" D bitfields.

Yes and no.

If it is exactly 32bits, its fine.

If it unintentionally crosses above it (due to implementation defined behavior), or if it is (u)int 2/3/4 then it likely won't work.
July 07

On Saturday, 6 July 2024 at 23:26:43 UTC, Walter Bright wrote:

>

On 7/5/2024 8:23 PM, Steven Schveighoffer wrote:

>

On Saturday, 6 July 2024 at 00:16:23 UTC, Walter Bright wrote:

>

The following will also show discrepancies:

struct T {
    unsigned short x;
    unsigned int y;
}

for the same reason.

I tested this struct, and there were no discrepancies between compilers. All compilers put 2 bytes of padding between the ushort and the uint.

Try it with a 16 bit compiler, which aligns on 16 bits rather than 32 bits.

No, I'm not cheating with this - I wanted to point out the consistency between 32 bit compilers, despite the Standard saying nothing about it. But I can still break the example, with a 32/64 bit compiler:

struct U {
    unsigned int x;
    unsigned long y;
}

You'll get different sizes for 32 vs 64 bit compilations, including with D.

Right, but with bitfields, you get discrepancies within the same compiler.

Let's take another example:

struct U {
  unsigned int x;
  unsigned long long y: 30;
  unsigned long long z: 34;
}

struct U2 {
  unsigned int x;
  unsigned long long y: 34;
  unsigned long long z: 30;
}

In the first case, Linux 64-bit clang will layout y to be right after x, and z will be pushed 2 bits further so it lines up on a 64-bit address.

In the second case, in the same compiler, y is pushed 32 bits off so it lines up on a 64-bit address space, and z is past that.

In both cases, 96 bits of data consumes 128 bits (sizeof both structs is 16).

In other words, the compiler makes probably unexpected layout decisions, and the reason is "because C does it".

Truly, with C, you cannot count on any explicit layout. Yes, there are reasons, and all of those have to do with performance. But when the goal is explicit layouts, then confusion rules.

This is why I said, either define what D does explicitly, or warn when it does something really bizarre for the sake of C.

Another option is just to say, "don't use bitfields for anything other than space-saving. Do not attempt to use bitfields for defined bit layout, because the compiler can make arbitrary decisions on layout." But you will have to tell this guy.

D has a chance to do better, but it's clear we are just going to saddle ourselves with C insanity for the sake of zero real use cases.

-Steve

July 06
C and D programmers already know how to align things with pad fields. It's a basic skill.

If extern(C) and extern(D) were added to bitfields, then the programmer would have to learn two new syntactic constructs and what they mean. It's a distinction that is easily forgettable, too. Quick, what's the difference in calling convention between extern(C) and extern(D) functions?

With pad fields, there's nothing new to learn, and it's quite obvious even for a naive programmer what is happening with them.
July 06
On 7/6/2024 8:50 PM, Steven Schveighoffer wrote:
> Let's take another example:
> 
> ```c
> struct U {
>    unsigned int x;
>    unsigned long long y: 30;
>    unsigned long long z: 34;
> }
> 
> struct U2 {
>    unsigned int x;
>    unsigned long long y: 34;
>    unsigned long long z: 30;
> }
> ```

Simple solution:

```
struct U {
    unsigned int x;
    unsigned int y:30;
    unsigned long long z:34;
}
```

or:

```
struct U2 {
    unsigned int x;
    unsigned int pad;
    unsigned long long y:30;
    unsigned long long z:34;
}
```

depending on which layout is desired. This is simple, predictable, and portable. It's not going to be a mystery to anyone reading the code - it's eminently readable.

An anonymous union can be pressed into service, which can be handy if the type of `x` is opaque:

```
struct U {
    T x;
    union {
        ulong pad; // for alignment
        struct {
            ulong y: 30;
            ulong z: 34;
        }
    }
}
```

or use align:

```
struct U {
    T x;
  align(8)
    ulong y:30, z:34;
}
```

There are many existing ways to accomplish this. Adding more language features to duplicate existing capability needs a very strong case.
July 07
On 7/7/24 06:47, Walter Bright wrote:
> On 7/6/2024 8:50 PM, Steven Schveighoffer wrote:
>> Let's take another example:
>>
>> ```c
>> struct U {
>>    unsigned int x;
>>    unsigned long long y: 30;
>>    unsigned long long z: 34;
>> }
>>
>> struct U2 {
>>    unsigned int x;
>>    unsigned long long y: 34;
>>    unsigned long long z: 30;
>> }
>> ```
> 
> Simple solution:
> ...

You said: Same type, same alignment. It's clearly not true in Steven's example. It seems alignment depends on bit width.

Also consider this:

```d
struct S{
    uint x;
    ulong y:30;
    ulong z:34;
}
pragma(msg, S.y.offsetof, " ", S.y.alignof); // 4LU 8LU

The offset of `y` does not even respect its alignment! This is insanity.

It also happens with `uint`:

```d
struct S{
    ushort x;
    uint y:16;
}
pragma(msg, S.y.offsetof, " ", S.y.alignof); // 2LU 4LU
```

I.e., "stick to `int`/`uint` bitfields, things will be predictable" is not even true. They may be laid out differently based on what's before them.

> ```
> struct U {
>      unsigned int x;
>      unsigned int y:30;
>      unsigned long long z:34;
> }
> ```
> 
> or:
> 
> ```
> struct U2 {
>      unsigned int x;
>      unsigned int pad;
>      unsigned long long y:30;
>      unsigned long long z:34;
> }
> ```
> 
> depending on which layout is desired. This is simple,

If it is simple, you should have no trouble stating how it works completely in a couple sentences.

> predictable, and portable. It's not going to be a mystery to anyone reading the code - it's eminently readable.
> ...

Walter, this is frustrating. It is only obvious to you because having reverse-engineered and implemented it, you already know how it works. Note that the things you were saying earlier suggested it would actually work differently in Steven's example. I hope you understand that this is confusing. I am as a result now not sure whether what you stated is the full truth, or it is still some inadmissible simplification that glosses over some further dragons.

Also, I hope `.offsetof % .alignof != 0` is just a bug in your bitfield implementation.
July 07

On Sunday, 7 July 2024 at 04:47:30 UTC, Walter Bright wrote:

>

On 7/6/2024 8:50 PM, Steven Schveighoffer wrote:

>

Let's take another example:

struct U {
   unsigned int x;
   unsigned long long y: 30;
   unsigned long long z: 34;
}

struct U2 {
   unsigned int x;
   unsigned long long y: 34;
   unsigned long long z: 30;
}

Simple solution:

struct U {
    unsigned int x;
    unsigned int y:30;
    unsigned long long z:34;
}

or:

struct U2 {
    unsigned int x;
    unsigned int pad;
    unsigned long long y:30;
    unsigned long long z:34;
}

depending on which layout is desired. This is simple, predictable, and portable. It's not going to be a mystery to anyone reading the code - it's eminently readable.

Simple, no. Predictable, yes (it's unambiguous). And not obvious. What I want is for the compiler to require you to do this to avoid inconsistencies. It is going to be a mystery to anyone reading it why they put these things in there. (hey, I simplified your code by getting rid of the pad, it comes out the same anyway due to [my wholly understandable but mistaken understanding of] alignment!)

To give some examples, we require empty if statements to use {} and not ;. It doesn't require any new syntax but it helps you avoid issues that many people make, even though it is allowed in C.

We require explicit conversion when narrowing the range of an integer (i.e. assigning a long to an int). This avoids issues that many people would make, even though it is allowed in C.

>

There are many existing ways to accomplish this. Adding more language features to duplicate existing capability needs a very strong case.

I'm not asking for any new features.

-Steve

July 08
On 7/7/2024 3:42 AM, Timon Gehr wrote:
> If it is simple, you should have no trouble stating how it works completely in a couple sentences.

One sentence:

If the bitfields of type T start on a T alignment boundary and do not straddle a T alignment boundary, then the bitfields will be portable.

I agree I sometimes have trouble writing exact specifications, but I'm also confident that you understand this.


> I am as a result now not sure whether what you stated is the full truth, or it is still some inadmissible simplification that glosses over some further dragons.

Feel free to try pathological examples and let me know of any adverse discoveries.


> Also, I hope `.offsetof % .alignof != 0` is just a bug in your bitfield implementation.

??