Discussion of the default alignment on m68k

Introduction

On m68k, the default alignment has been traditionally 2 bytes instead of 4 bytes. This originates in the design of the original 68000 CPU which used an external 16-bit data bus such that data words with a length of 1 or 2 bytes could be handled by the data bus in one clock cycle instead of two. Starting with the 68020 CPU, the external data bus was extended to 32 bits and the previously used 2 bytes default alignment was no longer necessary to optimize performance of the data bus.

Since both Linux and NetBSD and any other modern operating systems running on m68k will require at least a 68020 CPU, using a 2 bytes default alignment makes no longer sense and is even counterproductive as many upstream projects, in particular language or bytecode interpreters for ?JavaScript or Java assume that the minimum alignment is 4 bytes. This is because these codebases use features like tagged pointers to encode meta information into a pointer type. This means that patching these codebases to work with a default alignment of 2 bytes is not trivial.

This page is meant to discuss a possible switch of the default alignment of m68k on Linux to 4 bytes, a switch that has already happened on modern versions of NetBSD (see below) and will weigh the benefits with the potential problems of such a switch.

Current situation

Problems

Package that FTBFS due to the 2-byte alignment on Linux/m68k

Solution

In GCC, the maximum allowed is tuned with the help of BIGGEST_ALIGNMENT.

On Linux, the value for BIGGEST_ALIGNMENT defaults to 16.

From gcc/config/m68k/m68k.h:

/* No data type wants to be aligned rounder than this.                                                                                                                                        
   Most published ABIs say that ints should be aligned on 16-bit                                                                                                                              
   boundaries, but CPUs with 32-bit busses get better performance                                                                                                                             
   aligned on 32-bit boundaries.  */
#define BIGGEST_ALIGNMENT (TARGET_ALIGN_INT ? 32 : 16)

On NetBSD, the value for BIGGEST_ALIGNMENT defaults to 64.

From gcc/config/m68k/netbsd-elf.h:

/* No data type wants to be aligned rounder than this.                                                                                                                                        
   For m68k/SVR4, some types (doubles for example) are aligned on 8 byte                                                                                                                      
   boundaries */

#undef BIGGEST_ALIGNMENT 
#define BIGGEST_ALIGNMENT 64

The most plausible way would probably just copying the definitions from NetBSD.

Proposed process for bootstrapping Debian/m68k to 4 bytes alignment

1. Patch gcc-13 package to enable 4 bytes alignment

diff --git a/gcc/config/m68k/linux.h b/gcc/config/m68k/linux.h
index 0cf5e136f7a..2c52aa06935 100644
--- a/src/gcc/config/m68k/linux.h
+++ b/src/gcc/config/m68k/linux.h
@@ -242,3 +242,10 @@ along with GCC; see the file COPYING3.  If not see
 /* Install the __sync libcalls.  */
 #undef TARGET_INIT_LIBFUNCS
 #define TARGET_INIT_LIBFUNCS  m68k_init_sync_libfuncs
+
+/* No data type wants to be aligned rounder than this.
+   For m68k/SVR4, some types (doubles for example) are aligned on 8 byte
+   boundaries */
+
+#undef BIGGEST_ALIGNMENT
+#define BIGGEST_ALIGNMENT 64

2. Build patched gcc-13 package and install into chroot

3. Patch glibc package to use gcc-13 as the build compiler and to fix utmp sizes

glaubitz@esk:~/glibc-m68k-vanilla/glibc-2.41$ diff -u ~/glibc-m68k/glibc-2.41/debian/rules debian/rules
--- debian/rules.orig   2025-04-30 21:33:03.000000000 +0200
+++ debian/rules        2025-05-29 13:45:38.872040569 +0200
@@ -107,7 +107,7 @@
 BASE_CXX = g++
 BASE_MIG = mig
 # If you override DEB_GCC_VERSION, consider adding DEB_CFLAGS_APPEND=-Wno-error.
-DEB_GCC_VERSION ?= -14
+DEB_GCC_VERSION ?= -13
 
 RUN_TESTSUITE = yes
 TIMEOUTFACTOR = 25

Run debian/rules debian/control to apply compiler switch change for glibc package.

diff --git a/sysdeps/m68k/utmp-size.h b/sysdeps/m68k/utmp-size.h
index 5946685819..8f21ebe1b6 100644
--- a/sysdeps/m68k/utmp-size.h
+++ b/sysdeps/m68k/utmp-size.h
@@ -1,3 +1,2 @@
-/* m68k has 2-byte alignment.  */
-#define UTMP_SIZE 382
+#define UTMP_SIZE 384
 #define LASTLOG_SIZE 292

4. Build patched glibc package and install into chroot

5. Patch gcc-14 and gcc-15 like gcc-13 above and build them as well.

6. Build the rest of the Debian world

Considerations for QEMU

QEMU needs to know the default alignment for various fundamental types for qemu-user. These are defined in include/user/abitypes.h:

#ifdef TARGET_M68K
#define ABI_INT_ALIGNMENT 2
#define ABI_LONG_ALIGNMENT 2
#define ABI_LLONG_ALIGNMENT 2
#endif

Considerations for the Linux kernel

The kernel has a number of places on m68k, particularly in signal.c where it checks for 2 bytes alignment:

glaubitz@node54:/data/home/glaubitz/linux> git grep 'offsetof' |grep -e m68k |grep BUILD_BUG_ON
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_signo) != 0);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_errno) != 4);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_code)  != 8);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_pid) != 0x0c);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_uid) != 0x10);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_tid)     != 0x0c);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_overrun) != 0x10);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_value)   != 0x14);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_pid)   != 0x0c);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_uid)   != 0x10);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_value) != 0x14);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_pid)    != 0x0c);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_uid)    != 0x10);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_status) != 0x14);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_utime)  != 0x18);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_stime)  != 0x1c);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_addr) != 0x0c);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_addr_lsb) != 0x10);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_lower) != 0x12);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_upper) != 0x16);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_pkey) != 0x12);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_perf_data) != 0x10);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_perf_type) != 0x14);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_perf_flags) != 0x18);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_band)   != 0x0c);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_fd)     != 0x10);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_call_addr) != 0x0c);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_syscall)   != 0x10);
arch/m68k/kernel/signal.c:      BUILD_BUG_ON(offsetof(siginfo_t, si_arch)      != 0x14);
glaubitz@node54:/data/home/glaubitz/linux>

Alignment survey on various m68k targets

SunOS 4.1.1

# dmesg | head -8

Feb 15 12:38
SunOS Release 4.1.1 (GENERIC) #1: Sat Oct 13 06:05:48 PDT 1990
Copyright (c) 1983-1990, Sun Microsystems, Inc.
mem = 24576K (0x18000000)
avail mem = 23486464
Ethernet address = 8:0:20:0:0:3
si at vme24d16 0x200000 vec 0x40
# uname -a
SunOS ferrari 4.1.1 1 sun3
# cat test.c
#include <stdio.h>
#include <sys/stdtypes.h>

#define offsetof(type, member) ((size_t)(unsigned long)(&((type *)0)->member))

struct test { char x; int y; };

int main()
{
    printf("struct test { char x; int y; };\n");
    printf("sizeof(sturct test) = %d\n", sizeof(struct test));
    printf("offsetof(struct test, y) = %d\n", offsetof(struct test, y));
}
# cc test.c
# file a.out
a.out:          mc68020 demand paged dynamically linked executable not stripped
# ./a.out
struct test { char x; int y; };
sizeof(struct test) = 6
offsetof(struct test, y) = 2
#

NetBSD 1.5.3

x68030-% uname -a
NetBSD x68030 1.5.3 NetBSD 1.5.3 (GENERIC) #5: Wed Jul  3 13:29:05 JST 2002     nsmrtks@yellow.magic.or:/lhd/src-1-5-PATCH003/sys/arch/x68k/compile/GENERIC x68k
x68030-% gcc -v
Using builtin specs.
gcc version egcs-2.91.66 19990314 (egcs-1.1.2 release)
x68030-% cat > test.c
#include <stdio.h>
#include <stddef.h>

struct test { char x; int y; };
typedef struct test test_t;

int main(void)
{
    printf("struct test { char x; int y; };\n");
    printf("sizeof(struct test) = %d\n", sizeof(struct test));
    printf("offsetof(struct test, y) = %d\n", offsetof(struct test, y));
}
x68030-% cc test.c
x68030-% file a.out
a.out: NetBSD/m68k demand paged dynamically linked executable not stripped
x68030-% ./a.out
struct test { char x; int y; };
sizeof(struct test) = 6
offsetof(struct test, y) = 2
x68030-%

NetBSD 10.1

milan-% uname -a
NetBSD milan 10.1 NetBSD 10.1 (MILAN-PCIIDE) #0: Mon Dec 16 13:08:11 UTC 2024  mkrepro@mkrepro.NetBSD.org:/usr/src/sys/arch/atari/compile/MILAN-PCIIDE atari
milan-% gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/lto-wrapper
Target: m68k--netbsdelf
Configured with: /usr/src/tools/gcc/../../external/gpl3/gcc/dist/configure --target=m68k--netbsdelf --enable-long-long --enable-threads --with-bugurl=http://www.NetBSD.org/support/send-pr.html --with-pkgversion='NetBSD nb2 20230710' --with-system-zlib --without-isl --enable-__cxa_atexit --enable-libstdcxx-time=rt --enable-libstdcxx-threads --with-diagnostics-color=auto-if-env --with-default-libstdcxx-abi=new --with-mpc-lib=/var/obj/mknative/mvme68k-m68k/usr/src/external/lgpl3/mpc/lib/libmpc --with-mpfr-lib=/var/obj/mknative/mvme68k-m68k/usr/src/external/lgpl3/mpfr/lib/libmpfr --with-gmp-lib=/var/obj/mknative/mvme68k-m68k/usr/src/external/lgpl3/gmp/lib/libgmp --with-mpc-include=/usr/src/external/lgpl3/mpc/dist/src --with-mpfr-include=/usr/src/external/lgpl3/mpfr/dist/src --with-gmp-include=/usr/src/external/lgpl3/gmp/lib/libgmp/arch/m68k --enable-tls --disable-multilib --disable-libstdcxx-pch --build=m68k--netbsdelf --host=m68k--netbsdelf --with-sysroot=/var/obj/mknative/mvme!
 68k-m68k/usr/src/destdir.mvme68k
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 10.5.0 (nb3 20231008)
milan-% cat > test.c
#include <stdio.h>
#include <stddef.h>

struct test { char x; int y; };
typedef struct test test_t;

int main(void)
{
    printf("struct test { char x; int y; };\n");
    printf("sizeof(struct test) = %d\n", sizeof(struct test));
    printf("offsetof(struct test, y) = %d\n", offsetof(struct test, y));
}
milan-% cc test.c
milan-% file a.out
a.out: ELF 32-bit MSB executable, Motorola m68k, 68020, version 1 (SYSV), dynamically linked, interpreter /usr/libexec/ld.elf_so, for NetBSD 10.1, with debug_info, not stripped
milan-% ./a.out
struct test { char x; int y; };
sizeof(struct test) = 8
offsetof(struct test, y) = 4
milan-%

Linux

glaubitz@mitchy:~$ uname -a
Linux mitchy 6.2.0-rc3-virt-00063-gfb879e581032 #44 Thu Jan 12 15:35:35 UTC 2023 m68k GNU/Linux
glaubitz@mitchy:~$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/m68k-linux-gnu/13/lto-wrapper
Target: m68k-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 13.3.0-12' --with-bugurl=file:///usr/share/doc/gcc-13/README.Bugs --enable-languages=c,ada,c++,fortran,objc,obj-c++,m2 --prefix=/usr --
with-gcc-major-version-only --program-suffix=-13 --program-prefix=m68k-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/libexec --without-included-gettext --enable-threads=posix -
-libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-libstdcxx-backtrace --enable-gnu-unique-object --
disable-libssp --disable-libitm --disable-libsanitizer --disable-libquadmath --disable-libquadmath-support --enable-plugin --with-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror
--disable-werror --disable-multilib --enable-checking=release --build=m68k-linux-gnu --host=m68k-linux-gnu --target=m68k-linux-gnu
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 13.3.0 (Debian 13.3.0-12)
glaubitz@mitchy:~$ dmesg | head -8
[    0.000000] Linux version 6.2.0-rc3-virt-00063-gfb879e581032 (glaubitz@node54) (m68k-suse-linux-gcc (SUSE Linux) 11.3.0, GNU ld (GNU Binutils; SUSE Linux Enterprise 15) 2.37.20211103-150100.7.37)
#44 Thu Jan 12 15:35:35 UTC 2023
[    0.000000] random: crng init done
[    0.000000] earlycon: early_gf_tty0 at MMIO 0xff008000 (options '')
[    0.000000] printk: bootconsole [early_gf_tty0] enabled
[    0.000000] Zone ranges:
[    0.000000]   DMA      [mem 0x0000000000000000-0x00000cf7fdffffff]
[    0.000000]   Normal   empty
[    0.000000] Movable zone start for each node
glaubitz@mitchy:~$ cat test.c
#include <stdio.h>
#include <stdlib.h>

#define offsetof(type, member) ((size_t)(unsigned long)(&((type *)0)->member))

struct test { char x; int y; };

int main()
{
    printf("struct test { char x; int y; };\n");
    printf("sizeof(sturct test) = %d\n", sizeof(struct test));
    printf("offsetof(struct test, y) = %d\n", offsetof(struct test, y));
}
glaubitz@mitchy:~$ cc test.c
glaubitz@mitchy:~$ file a.out
a.out: ELF 32-bit MSB executable, Motorola m68k, 68020, version 1 (SYSV), dynamically linked, interpreter /lib/ld.so.1, BuildID[sha1]=a00f49a8cc806d23f7e5994c3a02a5dea3455a67, for GNU/Linux 3.2.0, not
stripped
glaubitz@mitchy:~$ ./a.out
struct test { char x; int y; };
sizeof(sturct test) = 6
offsetof(struct test, y) = 2
glaubitz@mitchy:~$

Amiga Unix 2.1

# uname -a
UNIX_System_V amiga 4.0 2.1 0800430 Amiga (Unlimited) m68k
# cc -v
GNU GCC version 1.40.5 (68k, SGS/AT&T syntax) compiled by GNU C version 1.40.5.
# cat test.c
#include <stdio.h>
#include <stddef.h>

struct test { char x; int y; };
typedef struct test test_t;

int main(void)
{
  printf("struct test { char x; int y; };\n");
  printf("sizeof(struct test) = %d\n", sizeof(struct test));
  printf("offsetof(struct test, y) = %d\n", offsetof(struct test, y));
}
# cc test.c -o test
# file test
test:           ELF 32-bit MSB executable M68000 Version 1
# ./test
struct test { char x; int y; };
sizeof(struct test) = 8
offsetof(struct test, y) = 4
#

Apple A/UX 3.0.1

localhost.root # uname -a
A/UX localhos 3.0.1 SVR2 mc68040
localhost.root # cat test.c
#include <stdio.h>
#include <stddef.h>

struct test { char x; int y; };
typedef struct test test_t;

int main()
{
  printf("struct test { char x; int y; };\n");
  printf("sizeof(struct test) = %d\n", sizeof(struct test));
  printf("offsetof(struct test, y) = %d\n", offsetof(struct test, y));
}
localhost.root # cc test.c -o test
localhost.root # file ./test
./test:         COFF object not stripped    paged executable
localhost.root # ./test
struct test { char x; int y; };
sizeof(struct test) = 6
offsetof(struct test, y) = 2
localhost.root #

References