Differences in Target Triple/Argument Format between Zig and Clang

So I’ve been deep diving porting embedded arm-none-eabi-gcc projects to Zig’s C/C++ compiler, and I can’t quite grok some subtle differences in how Zig handles the target triples passed to the -target argument vs. Clang that I had to discover via trial and error.

For instance, an incredibly simple example:

dummy.c:

int main() {
    return 0;
}

Compiling with clang:

clang -target thumb-none-eabi -mcpu=cortex-m7 -nostdlib dummy.c 
ld.lld: warning: cannot find entry symbol _start; not setting start address

Makes total sense, this is a freestanding target, I’m not linking in the standard libraries, I wouldn’t expect _start to be defined. All is well.

Compiling with zig cc:

First naive attempt:

zig cc -target thumb-none-eabi -mcpu=cortex-m7 -nostdlib dummy.c 
error: unable to parse target query 'thumb-none-eabi': UnknownOperatingSystem

Huh… Okay. After some fiddling and trial + error, I come up with:

zig cc -target thumb-freestanding-eabi -mcpu=cortex-m7 -nostdlib dummy.c 
info: available CPUs for architecture 'thumb':
 arm1020e
 arm1020t
 arm1022e
 arm10e
 arm10tdmi
 arm1136j_s
 arm1136jf_s
 arm1156t2_s
 arm1156t2f_s
 arm1176jz_s
 arm1176jzf_s
 arm710t
 arm720t
 arm7tdmi
 arm7tdmi_s
 arm8
 arm810
 arm9
 arm920
 arm920t
 arm922t
 arm926ej_s
 arm940t
 arm946e_s
 arm966e_s
 arm968e_s
 arm9e
 arm9tdmi
 baseline
 cortex_a12
 cortex_a15
 cortex_a17
 cortex_a32
 cortex_a35
 cortex_a5
 cortex_a53
 cortex_a55
 cortex_a57
 cortex_a7
 cortex_a710
 cortex_a72
 cortex_a73
 cortex_a75
 cortex_a76
 cortex_a76ae
 cortex_a77
 cortex_a78
 cortex_a78c
 cortex_a8
 cortex_a9
 cortex_m0
 cortex_m0plus
 cortex_m1
 cortex_m23
 cortex_m3
 cortex_m33
 cortex_m35p
 cortex_m4
 cortex_m55
 cortex_m7
 cortex_m85
 cortex_r4
 cortex_r4f
 cortex_r5
 cortex_r52
 cortex_r7
 cortex_r8
 cortex_x1
 cortex_x1c
 cyclone
 ep9312
 exynos_m1
 exynos_m2
 exynos_m3
 exynos_m4
 exynos_m5
 generic
 iwmmxt
 krait
 kryo
 mpcore
 mpcorenovfp
 neoverse_n1
 neoverse_n2
 neoverse_v1
 sc000
 sc300
 strongarm
 strongarm110
 strongarm1100
 strongarm1110
 swift
 xscale

error: unknown CPU: 'cortex'

Huh! Okay getting somewhere, but why is my CPU wrong? Oh :man_facepalming: cortex_m7 with Zig instead of cortex-m7.

So we arrive at:

zig cc -target thumb-freestanding-eabi -mcpu=cortex_m7 -nostdlib dummy.c 
LLD Link... ld.lld: warning: cannot find entry symbol _start; not setting start address

Great! Now per a comment from this thread, it looks like this new “freestanding” option for OS takes away the need for -nostdlib:

zig cc -target thumb-freestanding-eabi -mcpu=cortex_m7 dummy.c 
LLD Link... ld.lld: warning: cannot find entry symbol _start; not setting start address

Perfect, it appears Zig figured out based on “freestanding” not to include the standard libraries automatically in linking.

So… are there any resources/Github issues I could look at explaining some of these small differences and the reasons for them? I’m trying to make a porting guide and want to be able to explain why you need to change certain flag values. Because based on an (albeit old) article written by Andrew here:

…the zig cc sub-command is available, and it supports the same options as Clang, which, in turn, supports the same options as GCC

In this particular case, that unfortunately isn’t entirely accurate given Clang supports the flags I took from arm-none-eabi-gcc verbatim, whereas zig cc requries a bit of massaging to get compiling. I’ll even help write the documentation for these differences! Just need some context :slight_smile:

*Edit:

  • I would also love additional instances where zig cc won’t behave like clang I may not have uncovered yet

zig cc --help displays all the clang options.


zig targets produces a big json file with all the zig available options for target, cpu and its features.
In my system zig targets | jq .native shows:

{
  "triple": "x86_64-linux.6.1...6.1-gnu.2.36",
  "cpu": {
    "arch": "x86_64",
    "name": "skylake",
    "features": [
      "64bit",
      ...
    ]
  },
  "os": "linux",
  "abi": "gnu"
}

Note the tripple is arch-os.version...version-abi.version but normally the version range for os and abi version is omitted and the tripple becomes arch-os-abi.

zig targets | jq .arch shows all the architectures (e.g. aarch64_be, powerpc64le)
zig targets | jq .os shows all the operating systems (e.g. freestanding, fuchsia, ios)
zig targets | jq .abi shows all ABI (e.g. none, android, msvc)
zig targets | jq .libc shows all the included std C libraries in form arch-os-abi (e.g. aarch64-linux-musl, aarch64-windows-gnu, x86_64-macos-none)
zig targets | jq '.cpus | keys[]' shows all the cpu families (e.g. loongarch64, riscv64)
zig targets | jq '.cpus.thumb | keys[]' shows all the cpus of the thumb family (e.g. cortex_m0plus, cortex_m4)
zig targets | jq '.cpus.thumb.cortex_m0plus' shows the features of the cpu ARM Cortex M0+ ["no_branch_predictor","v6m"]

2 Likes

Okay tricky follow-up question for you… Is there an ergonomic way to show all compatible feature flags for a given CPU? The feature flags listed for:

zig targets | jq '.cpus.thumb.cortex_m7'
[
 "use_mipipeliner",
 "use_misched",
 "v7em"
]

Are the default flags that get applied. In reality, there are a number of other features compatible with this CPU, for instance some of the floating point features:

   "fp_armv8sp",
   "fp_armv8",
   "fp_armv8d16",
   "fp_armv8d16sp",
   "fp_armv8sp"

In -mcpu= you can add or remove features using + or -.
e.g. zig cc -target thumb-freestanding-eabi -mcpu=cortex_m7+bf16

One way to get the full list of features is to add an unknown feature, for example -target thumb-freestanding-eabi -mcpu=cortex_m7+foo, then zig outputs:

info: available CPU features for architecture 'thumb':
 32bit: Prefer 32-bit Thumb instrs
 8msecext: Enable support for ARMv8-M Security Extensions
 a76: Cortex-A76 ARM processors
 aapcs_frame_chain: Create an AAPCS compliant frame chain
 aapcs_frame_chain_leaf: Create an AAPCS compliant frame chain for leaf functions
 aclass: Is application profile ('A' series)
 acquire_release: Has v8 acquire/release (lda/ldaex  etc) instructions
 aes: Enable AES support
 atomics_32: Assume that lock-free 32-bit atomics are available
 ...
 wide_stride_vfp: Use a wide stride when allocating VFP registers
 xscale: ARMv5te architecture
 zcz: Has zero-cycle zeroing instructions

error: unknown CPU feature: 'foo'
1 Like

Shoot, that’s all the features for all ARM CPUs though rather than specifically cortex-m7 compatible features. I’m going to mark your initial answer as the solution though since this certainly isn’t a Zig problem after further research. I think it just comes with the territory. It’s an exercise for the user to read reference manuals, etc. if they want to enable a super specific floating point feature for a bare metal chip.

1 Like