Unaligned access on a Cortex-M4

I’ve recently encountered an instance where the Zig compiler generated an unaligned access that I’m puzzled to explain or fix. A Cortex-M4 will support unaligned access, but here I’m trapping such accesses as Usage Faults (to detect bad pointer manipulations, et.al.). The code deals with bit twiddling memory mapped I/O. When doing such work in the past with gcc, I’ve always included the -mno-unaligned-access argument to the compiler. Given Zig’s usual attention to alignment, I was surprised by the result and wondering if I needed some similar option to the Zig compiler (which, if it exists, I don’t know what it is). I’m happy to include the details of the source and generated code, but thought I would ask if someone has more experience in ensuring the compiler does not generate unaligned accesses or to correct my expectations in this area. I’m working with compiler version: 0.14.0-dev.1762+cfd3bcffe.

Try to create the smallest possible code that demonstrates the unaligned access, using godbolt.

Options: -O ReleaseSmall -target arm-freestanding -mcpu cortex_m4
Code: Compiler Explorer

I don’t need godbolt to show me the code as I can look at it in the debugger and understand what has happened.

The compiler, when packing an outer struct, placed an inner struct consisting of two u5 fields next to another inner struct that consists of a single one byte field. This causes the two byte struct to be at an odd offset from the beginning of the structure. When the value is accessed to pass as a function argument, the compiler generates a half-word load from the odd address that is implied by the structure packing.

I’m seeking some information on what is the expectation for whether the compiler produces unaligned access for Debug builds on processor architectures that allow it (specifically, the v7e-m architecture of the Cortex-M4). If this not expected, then I will extract a minimal code sample, keeping my fingers crossed that the context of the problem does not influence the compiler’s computation of the structure layout, to submit as an issue. If it is expected, then I’m curious if it can be instructed to produce only aligned accesses. If it is expected and there is no control over alignments, I will sulk in the corner for a while and then turn off the unaligned access trap.

If I understand correctly you get field alignments that you don’t like?
You could try to give the fields an explicit alignment (from langref):

const std = @import("std");
const expectEqual = std.testing.expectEqual;

test "aligned struct fields" {
    const S = struct {
        a: u32 align(2),
        b: u32 align(64),
    };
    var foo = S{ .a = 1, .b = 2 };

    try expectEqual(64, @alignOf(S));
    try expectEqual(*align(2) u32, @TypeOf(&foo.a));
    try expectEqual(*align(64) u32, @TypeOf(&foo.b));
}
1 Like

It is impossible to provide further help without seeing the code. That’s why I’m asking you for a godbolt code snippet that demonstrates the problem.


See the article Memory-mapped IO registers in zig for ideas. Note that the IO access is done in u32 volatile loads and saves and not using u8 (byte access).


Zig relevant rules for structs, copied from the reference, are:

  • Zig gives no guarantees about the order of fields and the size of the struct …
  • Unlike normal structs, packed structs have guaranteed in-memory layout:
    • Fields remain in the order declared, least to most significant.
    • There is no padding between fields.
    • Zig supports arbitrary width Integers and although normally, integers with fewer than 8 bits will still use 1 byte of memory, in packed structs, they use exactly their bit width.
    • bool fields use exactly 1 bit.
    • An enum field uses exactly the bit width of its integer tag type.
    • A packed union field uses exactly the bit width of the union field with the largest bit width.
1 Like

I haven’t tested this, but you could try enabling the “strict_align” feature on the target CPU in your build script.

Thank you. Something I can act upon. I’ll report back what I find.

Thanks again to @alp for pointing me to the strict_align feature. It was an interesting dive into targets, features, et.al. Here are some things I found.

Deep in the standard library there is a “database” of target processors and features that they have. I had seen this before, but had not taken much time with it. In the case of ARM processors, the strict_align feature is the default on older processors where the processor architecture did not support any unaligned memory access. That is not the case for the v7em architecture since it does allow for unaligned access. It also can be set to disallow unaligned access so the problem was to find the incantation to the build system to “add” the strict_align feature to the Cortex-M4 mcu.

Previously, my build.zig defined the compilation target as:

const platform_target: std.Target.Query = .{
    .cpu_arch = .thumb,
    .os_tag = .freestanding,
    .abi = .eabihf,
    .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m4 },
 };
 const target = b.resolveTargetQuery(platform_target);

With some spelunking, I found a function to do the heavy lifting:

const platform_target = std.Target.Query.parse(.{
        .arch_os_abi = "thumb-freestanding-eabihf",
        .cpu_features = "cortex_m4+strict_align",
    }) catch @panic("failed to obtain platform target");
const target = b.resolveTargetQuery(platform_target);

Since I choose to disallow unaligned access in my target by setting the UNALIGN_TRP field of the CCR (Configuration and Control) register, it is necessary to tell the compiler not to generate any unaligned memory access. I’m familiar with needing to do this in gcc and now I have learned to do it in Zig. The whole thing is optional, depending upon how you wish to run your device, and I’m sure someone with more skill at Zig build scripts than I have can turn all of this into a command line option to the build.

With this change, the Usage Faults have all gone. I’m surprised that it has taken some thousands of lines of Zig to trigger this behavior. Oh well, now there are rainbows and unicorns again.

2 Likes