Clang Default Cpu Features Overriding GCC Style Compile Flags

Stumbled on this issue when helping to get some code building here:

The Issue:

If a target is added in build.zig like so:

const target = b.resolveTargetQuery(.{
    .cpu_arch = .thumb,
    .os_tag = .freestanding,
    .abi = .eabihf,
    .cpu_model = std.zig.CrossTarget.CpuModel{ .explicit = &std.Target.arm.cpu.cortex_m7 },
});

All its default “cpu features” get passed to Clang during compilation via -Xclang -target-feature -Xclang +feature for features that default to enabled, and -Xclang -target-feature -Xclang -feature for features that default to disabled. This is a reasonable approach to being explicit about features, however it leads to some problematic behavior.

Now let’s say I’m compiling an incredibly minimal main.c like so:

// Make linker happy, but cursed, don't use in real code :)
int *_start = 0;

int main(void)
{
  __asm volatile(
      "	tst r14, #0x10						\n" /* Some assembly code that uses floating point registers */
      "	it eq								\n"
      "	vstmdbeq r0!, {s16-s31}				\n");

  return -1;
}

And I naively add the following C flags, since I’m porting from GCC and I know Clang is compatible with this method of specifying hardware floating point:

exe.addCSourceFiles(.{
    .files = &.{"src/main.c"},
    .flags = &.{
        "-mfpu=fpv4-sp-d16",
        "-mfloat-abi=hard",
    },
});

I then get this error:

main.c:19:8: error: instruction requires: fp registers
      " vstmdbeq r0!, {s16-s31}    \n");
       ^

This is because with the current behavior, you get a call to clang that looks something like (run zig build --verbose-cc):

usr/local/bin/zig clang main.c ...[lots of flags/features]... -Xclang -vfp4d16sp [removes this feature!] ...[lots of flags/features]... -mfpu=fpv4-sp-d16 -mfloat-abi=hard

The precedence of removing the cpu feature via -Xclang args is overriding my GCC style flags that are supposed to enable the feature.

Open Ended Questions

  • Do we need to explicitly specify “removal” of features that default to not included? It looks like we have a pretty good handle on sane defaults already for ARM freestanding chips (see here)
    • I can see a case where maybe there’s an “LLVM Default” (when you don’t specify any feature flags at all) that’s undesirable, supporting this approach
    • As an aside, it also leads to atrociously long calls to Clang that get in your way when you’re trying to do some low level build system debug
  • Should we potentially throw a warning in addCSourceFiles when certain flags are used? Might be some nice build system sugar to give someone a heads up "you shouldn’t be using these flags here, you must configure all architecture related compile options with b.resolveTargetQuery()