Repository: cc65/cc65 (C)

Issue:
cc65: field alignment bug
Hi,

I encountered a strange problem in CC65 that appears to be an interplay between structs, pointers,  and unsigned longs.

The smallest case I can come up with that demonstrates the problem is below:

```

#include <stdio.h>

typedef struct {
    unsigned long Field1;
    unsigned long Field2;
} RecordType;

typedef struct {
    char dummy; // -- Offsets the Rec field by 1 byte.
    RecordType Rec;
} StructTypeA;

typedef struct {
    RecordType Rec;
} StructTypeB;

int main(void)
{
    StructTypeA A;
    StructTypeB B;
    StructTypeA * Aptr;

    A.Rec.Field1 = 0x11111111;
    A.Rec.Field2 = 0x22222222;

    Aptr = &A;
    B.Rec = Aptr->Rec;

    printf("%lx, %lx\n", B.Rec.Field1, B.Rec.Field2);

    return 0;
}
```

Expected output (also what GCC shows with the same code):

```
11111111, 22222222
```

Actual output with CC65 (using sim65, which initializes unused memory to 0xff):

```
111111ff, 22222211
```

This is some offset error introduced by the 'dummy' field in StructTypeA. I tried to make the program smaller but it's not so easy, with the most obvious attempts the issue disappears. Perhaps someone else has more luck to come up with a smaller demonstration?



Discussion: Ok, the issue was introduced 4 years ago with a pretty small commit: https://github.com/cc65/cc65/commit/c3a6b399456937093eda9994f19b7f722731528d

That's the good news. The bad news is that I don't understand the compiler internals enough to dig any further.

Pinging @acqn and @oliverschmidt who authored resp merged that specific commit.
I'm sorry if merged a regression :flushed:. However, I'm not maintaining cc65 anymore so I can't be of any help here.
Okay thanks for the heads-up.

Last commit activity from @acqn was in February 2024, let's hope he shows up. Otherwise, we'll need to find someone willing and able to dig into this.
unfortunately @acqn seems to be MIA :(
how i would proceed: make a test from the above example (which should fail on current head), then revert the mentioned commit and see if that fixes it (and not breaks any other tests). If that is the case, reverting it seems safe - it seems to be a "cleanup" style change anyway (but seems to be not 100% :))

I don't know the compiler internals enough to comment on the change itself, or to suggest an alternative fix, for that matter
Unfortunately, it appears that there has been considerable reworking of the file in question (src/cc65/assignment.c) since the offending change was made -- about twenty commits, mostly by @acqn. Here's a word count that tells the story.

```
  271 assignment_pre.c              (version just before introducing the bug)
  280 assignment_post.c             (version just after introducing the bug)
  787 src/cc65/assignment.c         (today's HEAD)
```

This will require someone who groks the compiler internals to clean up, I'm afraid. So, well, uhm, yeah.
Added a test in https://github.com/cc65/cc65/pull/2570




Fix (patch):
diff --git a/src/cc65/assignment.c b/src/cc65/assignment.c
index e51dde9ab4..a66c495da5 100644
--- a/src/cc65/assignment.c
+++ b/src/cc65/assignment.c
@@ -104,10 +104,16 @@ static void CopyStruct (ExprDesc* LExpr, ExprDesc* RExpr)
 
     } else {
 
-        /* The rhs cannot happen to be loaded in the primary as it is too big */
+        /* Load the address of rhs into the primary */
         if (!ED_IsLocExpr (RExpr)) {
             ED_AddrExpr (RExpr);
             LoadExpr (CF_NONE, RExpr);
+        } else if (RExpr->IVal != 0) {
+            /* We have an expression in the primary plus a constant
+            ** offset. Adjust the value in the primary accordingly.
+            */
+            g_inc (CF_PTR | CF_CONST, RExpr->IVal);
+            RExpr->IVal = 0;
         }
 
         /* Push the address of the rhs as the source of memcpy */